Implement session management to maintain conversation history and entity context across page refreshes. Sessions are stored in-memory and include: - Conversation history (stored server-side, restored on reconnect) - Entity snapshots synced before each message for LLM context - Auto-injection of diagram state into Claude's system prompt Key changes: - Add session store service with create/resume/sync/clear operations - Add session API endpoints (/api/session/*) - Update Claude API to inject entity context and manage history - Update ChatPanel to initialize sessions and sync entities - Add debug endpoint for inspecting session state 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
159 lines
3.4 KiB
JavaScript
159 lines
3.4 KiB
JavaScript
/**
|
|
* In-memory session store for diagram chat sessions.
|
|
* Stores conversation history and entity snapshots.
|
|
*/
|
|
|
|
import { v4 as uuidv4 } from 'uuid';
|
|
|
|
// Session structure:
|
|
// {
|
|
// id: string,
|
|
// diagramId: string,
|
|
// conversationHistory: Array<{role, content, toolResults?, timestamp}>,
|
|
// entities: Array<{id, template, text, color, position}>,
|
|
// createdAt: Date,
|
|
// lastAccess: Date
|
|
// }
|
|
|
|
const sessions = new Map();
|
|
|
|
// Session timeout (1 hour of inactivity)
|
|
const SESSION_TIMEOUT_MS = 60 * 60 * 1000;
|
|
|
|
/**
|
|
* Create a new session for a diagram
|
|
*/
|
|
export function createSession(diagramId) {
|
|
const id = uuidv4();
|
|
const session = {
|
|
id,
|
|
diagramId,
|
|
conversationHistory: [],
|
|
entities: [],
|
|
createdAt: new Date(),
|
|
lastAccess: new Date()
|
|
};
|
|
sessions.set(id, session);
|
|
return session;
|
|
}
|
|
|
|
/**
|
|
* Get a session by ID
|
|
*/
|
|
export function getSession(sessionId) {
|
|
const session = sessions.get(sessionId);
|
|
if (session) {
|
|
session.lastAccess = new Date();
|
|
}
|
|
return session || null;
|
|
}
|
|
|
|
/**
|
|
* Find existing session for a diagram
|
|
*/
|
|
export function findSessionByDiagram(diagramId) {
|
|
for (const [, session] of sessions) {
|
|
if (session.diagramId === diagramId) {
|
|
session.lastAccess = new Date();
|
|
return session;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Update entities snapshot for a session
|
|
*/
|
|
export function syncEntities(sessionId, entities) {
|
|
const session = sessions.get(sessionId);
|
|
if (!session) return null;
|
|
|
|
session.entities = entities;
|
|
session.lastAccess = new Date();
|
|
return session;
|
|
}
|
|
|
|
/**
|
|
* Add a message to conversation history
|
|
*/
|
|
export function addMessage(sessionId, message) {
|
|
const session = sessions.get(sessionId);
|
|
if (!session) return null;
|
|
|
|
session.conversationHistory.push({
|
|
...message,
|
|
timestamp: new Date()
|
|
});
|
|
session.lastAccess = new Date();
|
|
return session;
|
|
}
|
|
|
|
/**
|
|
* Get conversation history for API calls (formatted for Claude)
|
|
*/
|
|
export function getConversationForAPI(sessionId) {
|
|
const session = sessions.get(sessionId);
|
|
if (!session) return [];
|
|
|
|
// Convert to Claude message format
|
|
return session.conversationHistory.map(msg => ({
|
|
role: msg.role,
|
|
content: msg.content
|
|
}));
|
|
}
|
|
|
|
/**
|
|
* Clear conversation history but keep session
|
|
*/
|
|
export function clearHistory(sessionId) {
|
|
const session = sessions.get(sessionId);
|
|
if (!session) return null;
|
|
|
|
session.conversationHistory = [];
|
|
session.lastAccess = new Date();
|
|
return session;
|
|
}
|
|
|
|
/**
|
|
* Delete a session
|
|
*/
|
|
export function deleteSession(sessionId) {
|
|
return sessions.delete(sessionId);
|
|
}
|
|
|
|
/**
|
|
* Clean up expired sessions
|
|
*/
|
|
export function cleanupExpiredSessions() {
|
|
const now = Date.now();
|
|
for (const [id, session] of sessions) {
|
|
if (now - session.lastAccess.getTime() > SESSION_TIMEOUT_MS) {
|
|
sessions.delete(id);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Run cleanup every 15 minutes
|
|
setInterval(cleanupExpiredSessions, 15 * 60 * 1000);
|
|
|
|
/**
|
|
* Get session stats (for debugging)
|
|
*/
|
|
export function getStats(includeDetails = false) {
|
|
return {
|
|
activeSessions: sessions.size,
|
|
sessions: Array.from(sessions.values()).map(s => ({
|
|
id: s.id,
|
|
diagramId: s.diagramId,
|
|
messageCount: s.conversationHistory.length,
|
|
entityCount: s.entities.length,
|
|
lastAccess: s.lastAccess,
|
|
// Include full details if requested
|
|
...(includeDetails && {
|
|
entities: s.entities,
|
|
conversationHistory: s.conversationHistory
|
|
})
|
|
}))
|
|
};
|
|
}
|