immersive2/server/api/session.js
Michael Mainguy 7769910027 Add server-side session persistence for chat
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>
2025-12-20 13:42:01 -06:00

145 lines
3.5 KiB
JavaScript

import { Router } from "express";
import {
createSession,
getSession,
findSessionByDiagram,
syncEntities,
addMessage,
clearHistory,
deleteSession,
getStats
} from "../services/sessionStore.js";
const router = Router();
/**
* GET /api/session/debug/stats
* Get session statistics (for debugging)
* Query params:
* - details=true: Include full entity and conversation data
* NOTE: Must be before /:id routes to avoid matching "debug" as an id
*/
router.get("/debug/stats", (req, res) => {
const includeDetails = req.query.details === 'true';
const stats = getStats(includeDetails);
console.log('[Session Debug] Stats requested:', JSON.stringify(stats, null, 2));
res.json(stats);
});
/**
* POST /api/session/create
* Create a new session or return existing one for a diagram
*/
router.post("/create", (req, res) => {
const { diagramId } = req.body;
if (!diagramId) {
return res.status(400).json({ error: "diagramId is required" });
}
// Check for existing session
let session = findSessionByDiagram(diagramId);
if (session) {
console.log(`[Session] Resuming existing session ${session.id} for diagram ${diagramId} (${session.conversationHistory.length} messages, ${session.entities.length} entities)`);
return res.json({
session,
isNew: false
});
}
// Create new session
session = createSession(diagramId);
console.log(`[Session] Created new session ${session.id} for diagram ${diagramId}`);
res.json({
session,
isNew: true
});
});
/**
* GET /api/session/:id
* Get session details including history
*/
router.get("/:id", (req, res) => {
const session = getSession(req.params.id);
if (!session) {
return res.status(404).json({ error: "Session not found" });
}
res.json({ session });
});
/**
* PUT /api/session/:id/sync
* Sync entities from client to server
*/
router.put("/:id/sync", (req, res) => {
const { entities } = req.body;
if (!entities || !Array.isArray(entities)) {
return res.status(400).json({ error: "entities array is required" });
}
const session = syncEntities(req.params.id, entities);
if (!session) {
return res.status(404).json({ error: "Session not found" });
}
console.log(`[Session ${req.params.id}] Synced ${entities.length} entities:`,
entities.map(e => `${e.text || '(no label)'} (${e.template})`).join(', ') || 'none');
res.json({ success: true, entityCount: entities.length });
});
/**
* POST /api/session/:id/message
* Add a message to history (used after successful Claude response)
*/
router.post("/:id/message", (req, res) => {
const { role, content, toolResults } = req.body;
if (!role || !content) {
return res.status(400).json({ error: "role and content are required" });
}
const session = addMessage(req.params.id, { role, content, toolResults });
if (!session) {
return res.status(404).json({ error: "Session not found" });
}
res.json({ success: true, messageCount: session.conversationHistory.length });
});
/**
* DELETE /api/session/:id/history
* Clear conversation history
*/
router.delete("/:id/history", (req, res) => {
const session = clearHistory(req.params.id);
if (!session) {
return res.status(404).json({ error: "Session not found" });
}
res.json({ success: true });
});
/**
* DELETE /api/session/:id
* Delete a session entirely
*/
router.delete("/:id", (req, res) => {
const deleted = deleteSession(req.params.id);
if (!deleted) {
return res.status(404).json({ error: "Session not found" });
}
res.json({ success: true });
});
export default router;