immersive2/server/api/session.js
Michael Mainguy 4ca98cf980 Add LangChain model wrappers and enhance diagram AI tools
- Migrate to LangChain for model abstraction (@langchain/anthropic, @langchain/ollama)
- Add custom ChatCloudflare class for Cloudflare Workers AI
- Simplify API routes using unified LangChain interface
- Add session preferences API for storing user settings
- Add connection label preference (ask user once, remember for session)
- Add shape modification support (change entity shapes via AI)
- Add template setter to DiagramObject for shape changes
- Improve entity inference with fuzzy matching
- Map colors to 16 toolbox palette colors
- Limit conversation history to last 6 messages
- Fix model switching to accept display names

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-14 10:17:15 -06:00

235 lines
5.7 KiB
JavaScript

import { Router } from "express";
import {
createSession,
getSession,
findSessionByDiagram,
syncEntities,
syncCameraPosition,
addMessage,
clearHistory,
deleteSession,
getStats,
getPreferences,
setPreference
} from "../services/sessionStore.js";
import { getSessionUsage, getGlobalUsage, formatCost } from "../services/usageTracker.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);
});
/**
* GET /api/session/usage/global
* Get global token usage and cost statistics
* NOTE: Must be before /:id routes
*/
router.get("/usage/global", (req, res) => {
const usage = getGlobalUsage();
res.json({
...usage,
totalCostFormatted: formatCost(usage.totalCost),
uptimeFormatted: `${Math.round(usage.uptime / 1000 / 60)} minutes`
});
});
/**
* GET /api/session/:id/usage
* Get token usage and cost for a specific session
*/
router.get("/:id/usage", (req, res) => {
const usage = getSessionUsage(req.params.id);
if (!usage) {
return res.status(404).json({ error: "No usage data for session" });
}
res.json({
...usage,
totalCostFormatted: formatCost(usage.totalCost)
});
});
/**
* 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 });
});
/**
* PUT /api/session/:id/camera
* Sync camera position from client to server
*/
router.put("/:id/camera", (req, res) => {
const { cameraPosition } = req.body;
if (!cameraPosition) {
return res.status(400).json({ error: "cameraPosition is required" });
}
const session = syncCameraPosition(req.params.id, cameraPosition);
if (!session) {
return res.status(404).json({ error: "Session not found" });
}
res.json({ success: true });
});
/**
* 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 });
});
/**
* GET /api/session/:id/preferences
* Get session preferences
*/
router.get("/:id/preferences", (req, res) => {
const preferences = getPreferences(req.params.id);
if (preferences === null) {
return res.status(404).json({ error: "Session not found" });
}
res.json({ preferences });
});
/**
* PUT /api/session/:id/preferences
* Set a session preference
*/
router.put("/:id/preferences", (req, res) => {
const { key, value } = req.body;
if (!key) {
return res.status(400).json({ error: "key is required" });
}
const preferences = setPreference(req.params.id, key, value);
if (preferences === null) {
return res.status(404).json({ error: "Session not found" });
}
console.log(`[Session ${req.params.id}] Set preference: ${key} = ${value}`);
res.json({ success: true, preferences });
});
export default router;