fix(FN-1817): enrich session list with lastMessagePreview and restore active session on remount
- Add lastMessagePreview field to ChatSession to show message previews in session list - Restore active session state on component remount via stored lastSessionId - Add chat routes for session management (create, list, read, update/delete) - Add tests for useChat hook, chat store, and chat routes - Remove unused serveCommand assignment in serve.ts command
This commit is contained in:
@@ -449,6 +449,68 @@ describe("ChatStore", () => {
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("getLastMessageForSessions", () => {
|
||||
it("returns the most recent message for each session", async () => {
|
||||
const session1 = createTestSession(store);
|
||||
const session2 = createTestSession(store);
|
||||
|
||||
// Add messages to session1
|
||||
store.addMessage(session1.id, { role: "user", content: "Hello" });
|
||||
await new Promise((r) => setTimeout(r, 5));
|
||||
const latestMsg1 = store.addMessage(session1.id, {
|
||||
role: "assistant",
|
||||
content: "Latest for session 1",
|
||||
});
|
||||
|
||||
// Add only one message to session2
|
||||
const latestMsg2 = store.addMessage(session2.id, {
|
||||
role: "assistant",
|
||||
content: "Latest for session 2",
|
||||
});
|
||||
|
||||
const result = store.getLastMessageForSessions([session1.id, session2.id]);
|
||||
|
||||
expect(result.size).toBe(2);
|
||||
expect(result.get(session1.id)).toBeDefined();
|
||||
expect(result.get(session1.id)!.content).toBe("Latest for session 1");
|
||||
expect(result.get(session2.id)).toBeDefined();
|
||||
expect(result.get(session2.id)!.content).toBe("Latest for session 2");
|
||||
});
|
||||
|
||||
it("handles empty session list", () => {
|
||||
const result = store.getLastMessageForSessions([]);
|
||||
expect(result.size).toBe(0);
|
||||
});
|
||||
|
||||
it("handles sessions with no messages", () => {
|
||||
const session1 = createTestSession(store);
|
||||
const session2 = createTestSession(store);
|
||||
|
||||
// Only add message to session1
|
||||
store.addMessage(session1.id, { role: "user", content: "Hello" });
|
||||
|
||||
const result = store.getLastMessageForSessions([session1.id, session2.id]);
|
||||
|
||||
expect(result.size).toBe(1);
|
||||
expect(result.has(session1.id)).toBe(true);
|
||||
expect(result.has(session2.id)).toBe(false);
|
||||
});
|
||||
|
||||
it("handles non-existent session IDs", () => {
|
||||
const session = createTestSession(store);
|
||||
store.addMessage(session.id, { role: "user", content: "Hello" });
|
||||
|
||||
const result = store.getLastMessageForSessions([
|
||||
session.id,
|
||||
"non-existent-1",
|
||||
"non-existent-2",
|
||||
]);
|
||||
|
||||
expect(result.size).toBe(1);
|
||||
expect(result.has(session.id)).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// ── Event Emission Tests ─────────────────────────────────────────
|
||||
|
||||
@@ -339,4 +339,39 @@ export class ChatStore extends EventEmitter<ChatStoreEvents> {
|
||||
if (!row) return undefined;
|
||||
return this.rowToMessage(row);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the latest message for each session in the provided list.
|
||||
* Uses a single SQL query with GROUP BY and MAX to efficiently fetch last messages.
|
||||
*
|
||||
* @param sessionIds - Array of session IDs to fetch last messages for
|
||||
* @returns Map of sessionId -> latest ChatMessage for that session
|
||||
*/
|
||||
getLastMessageForSessions(sessionIds: string[]): Map<string, ChatMessage> {
|
||||
if (!sessionIds || sessionIds.length === 0) {
|
||||
return new Map();
|
||||
}
|
||||
|
||||
// Create placeholders for the IN clause
|
||||
const placeholders = sessionIds.map(() => "?").join(", ");
|
||||
|
||||
// Use a subquery to get the latest message per session using MAX(createdAt)
|
||||
// Then join back to get the full message row
|
||||
const rows = this.db.prepare(`
|
||||
SELECT cm.* FROM chat_messages cm
|
||||
INNER JOIN (
|
||||
SELECT sessionId, MAX(createdAt) as maxCreatedAt
|
||||
FROM chat_messages
|
||||
WHERE sessionId IN (${placeholders})
|
||||
GROUP BY sessionId
|
||||
) latest ON cm.sessionId = latest.sessionId AND cm.createdAt = latest.maxCreatedAt
|
||||
`).all(...sessionIds);
|
||||
|
||||
const result = new Map<string, ChatMessage>();
|
||||
for (const row of rows as any[]) {
|
||||
const message = this.rowToMessage(row);
|
||||
result.set(message.sessionId, message);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user