refactor: Improve session message handling and enhance loading logic in ChatInterface

This commit is contained in:
simos
2025-08-12 14:37:02 +03:00
parent 3e7e60a3a8
commit 28e27ed2fb
4 changed files with 79 additions and 34 deletions

View File

@@ -1067,7 +1067,7 @@ async function startServer() {
console.log(`Claude Code UI server running on http://0.0.0.0:${PORT}`);
// Start watching the projects folder for changes
await setupProjectsWatcher(); // Re-enabled with better-sqlite3
await setupProjectsWatcher();
});
} catch (error) {
console.error('❌ Failed to start server:', error);

View File

@@ -582,7 +582,6 @@ router.get('/sessions/:sessionId', async (req, res) => {
const cwdId = crypto.createHash('md5').update(projectPath || process.cwd()).digest('hex');
const storeDbPath = path.join(os.homedir(), '.cursor', 'chats', cwdId, sessionId, 'store.db');
console.log(`📖 Reading Cursor session from: ${storeDbPath}`);
// Open SQLite database
const db = await open({
@@ -592,9 +591,14 @@ router.get('/sessions/:sessionId', async (req, res) => {
});
// Get all blobs (conversation data)
// Use rowid for chronological ordering (it's an auto-incrementing integer)
// Use ROW_NUMBER() for clean sequential numbering and rowid for chronological ordering
const blobs = await db.all(`
SELECT rowid, id, data FROM blobs
SELECT
ROW_NUMBER() OVER (ORDER BY rowid) as sequence_num,
rowid as original_rowid,
id,
data
FROM blobs
ORDER BY rowid ASC
`);
@@ -661,7 +665,8 @@ router.get('/sessions/:sessionId', async (req, res) => {
}
messages.push({
id: blob.id,
rowid: blob.rowid,
sequence: blob.sequence_num,
rowid: blob.original_rowid,
content: parsed
});
}

View File

@@ -194,7 +194,6 @@ router.get('/branches', async (req, res) => {
try {
const projectPath = await getActualProjectPath(project);
console.log('Git branches for project:', project, '-> path:', projectPath);
// Validate git repository
await validateGitRepository(projectPath);

View File

@@ -1330,8 +1330,10 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess
converted.push({
type: 'assistant',
content: '',
timestamp: new Date(Date.now() + blobIdx),
timestamp: new Date(Date.now() + blobIdx * 1000),
blobId: blob.id,
sequence: blob.sequence,
rowid: blob.rowid,
isToolUse: true,
toolName: toolName,
toolId: toolCallId,
@@ -1367,8 +1369,10 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess
type: role,
content: textParts.join('\n'),
reasoning: reasoningText,
timestamp: new Date(Date.now() + blobIdx),
blobId: blob.id
timestamp: new Date(Date.now() + blobIdx * 1000),
blobId: blob.id,
sequence: blob.sequence,
rowid: blob.rowid
});
textParts.length = 0;
reasoningText = null;
@@ -1449,8 +1453,10 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess
const toolMessage = {
type: 'assistant',
content: '',
timestamp: new Date(Date.now() + blobIdx),
timestamp: new Date(Date.now() + blobIdx * 1000),
blobId: blob.id,
sequence: blob.sequence,
rowid: blob.rowid,
isToolUse: true,
toolName: toolName,
toolId: toolId,
@@ -1466,8 +1472,10 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess
type: role,
content: textParts.join('\n'),
reasoning: reasoningText,
timestamp: new Date(Date.now() + blobIdx),
blobId: blob.id
timestamp: new Date(Date.now() + blobIdx * 1000),
blobId: blob.id,
sequence: blob.sequence,
rowid: blob.rowid
});
textParts.length = 0;
reasoningText = null;
@@ -1479,8 +1487,10 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess
const toolMessage = {
type: 'assistant',
content: '',
timestamp: new Date(Date.now() + blobIdx),
timestamp: new Date(Date.now() + blobIdx * 1000),
blobId: blob.id,
sequence: blob.sequence,
rowid: blob.rowid,
isToolUse: true,
toolName: toolName,
toolId: toolId,
@@ -1503,8 +1513,10 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess
type: role,
content: '',
reasoning: reasoningText,
timestamp: new Date(Date.now() + blobIdx),
blobId: blob.id
timestamp: new Date(Date.now() + blobIdx * 1000),
blobId: blob.id,
sequence: blob.sequence,
rowid: blob.rowid
});
text = ''; // Clear to avoid duplicate
}
@@ -1537,8 +1549,10 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess
const message = {
type: role,
content: text,
timestamp: new Date(Date.now() + blobIdx),
blobId: blob.id
timestamp: new Date(Date.now() + blobIdx * 1000),
blobId: blob.id,
sequence: blob.sequence,
rowid: blob.rowid
};
// Add reasoning if we have it
@@ -1550,11 +1564,15 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess
}
}
// Sort messages by blob ID to maintain chronological order
// Sort messages by sequence/rowid to maintain chronological order
converted.sort((a, b) => {
// First sort by blobId if available
if (a.blobId && b.blobId) {
return parseInt(a.blobId) - parseInt(b.blobId);
// First sort by sequence if available (clean 1,2,3... numbering)
if (a.sequence !== undefined && b.sequence !== undefined) {
return a.sequence - b.sequence;
}
// Then try rowid (original SQLite row IDs)
if (a.rowid !== undefined && b.rowid !== undefined) {
return a.rowid - b.rowid;
}
// Fallback to timestamp
return new Date(a.timestamp) - new Date(b.timestamp);
@@ -1774,11 +1792,18 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess
setCurrentSessionId(selectedSession.id);
sessionStorage.setItem('cursorSessionId', selectedSession.id);
// Load historical messages for Cursor session from SQLite
const projectPath = selectedProject.fullPath || selectedProject.path;
const converted = await loadCursorSessionMessages(projectPath, selectedSession.id);
setSessionMessages([]);
setChatMessages(converted);
// Only load messages from SQLite if this is NOT a system-initiated session change
// For system-initiated changes, preserve existing messages
if (!isSystemSessionChange) {
// Load historical messages for Cursor session from SQLite
const projectPath = selectedProject.fullPath || selectedProject.path;
const converted = await loadCursorSessionMessages(projectPath, selectedSession.id);
setSessionMessages([]);
setChatMessages(converted);
} else {
// Reset the flag after handling system session change
setIsSystemSessionChange(false);
}
} else {
// For Claude, load messages normally with pagination
setCurrentSessionId(selectedSession.id);
@@ -1799,8 +1824,12 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess
}
}
} else {
setChatMessages([]);
setSessionMessages([]);
// Only clear messages if this is NOT a system-initiated session change AND we're not loading
// During system session changes or while loading, preserve the chat messages
if (!isSystemSessionChange && !isLoading) {
setChatMessages([]);
setSessionMessages([]);
}
setCurrentSessionId(null);
sessionStorage.removeItem('cursorSessionId');
setMessagesOffset(0);
@@ -1810,7 +1839,7 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess
};
loadMessages();
}, [selectedSession, selectedProject, loadCursorSessionMessages, scrollToBottom, isSystemSessionChange]);
}, [selectedSession, selectedProject, loadCursorSessionMessages, scrollToBottom, isSystemSessionChange, isLoading]);
// Update chatMessages when convertedMessages changes
useEffect(() => {
@@ -2152,11 +2181,23 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess
const raw = String(latestMessage.data ?? '');
const cleaned = raw.replace(/\x1b\[[0-9;?]*[A-Za-z]/g, '').replace(/[\x00-\x08\x0B\x0C\x0E-\x1F]/g, '').trim();
if (cleaned) {
setChatMessages(prev => [...prev, {
type: 'assistant',
content: cleaned,
timestamp: new Date()
}]);
setChatMessages(prev => {
// If the last message is from assistant and not a tool use, append to it
if (prev.length > 0 && prev[prev.length - 1].type === 'assistant' && !prev[prev.length - 1].isToolUse) {
const updatedMessages = [...prev];
const lastMessage = updatedMessages[updatedMessages.length - 1];
// Append with a newline if there's already content
lastMessage.content = lastMessage.content ? `${lastMessage.content}\n${cleaned}` : cleaned;
return updatedMessages;
} else {
// Otherwise create a new assistant message
return [...prev, {
type: 'assistant',
content: cleaned,
timestamp: new Date()
}];
}
});
}
} catch (e) {
console.warn('Error handling cursor-output message:', e);