- Add Cloudflare Workers AI as third provider alongside Claude and Ollama - New cloudflare.js API handler with format conversion - Tool converter functions for Cloudflare's OpenAI-compatible format - Handle [TOOL_CALLS] and [Called tool:] text formats from Mistral - Robust parser that handles truncated JSON responses - Add usage tracking with cost display - New usageTracker.js service for tracking token usage per session - UsageDetailModal component showing per-request breakdown - Cost display in ChatPanel header - Add new diagram manipulation features - Entity scale and rotation support via modify_entity tool - Wikipedia search tool for researching topics before diagramming - Clear conversation tool to reset chat history - JSON import from hamburger menu (moved from ChatPanel) - Fix connection label rotation in billboard mode - Labels no longer have conflicting local rotation when billboard enabled - Update rotation when rendering mode changes - Improve tool calling reliability - Add MAX_TOOL_ITERATIONS safety limit - Break loop after model switch to prevent context issues - Increase max_tokens to 4096 to prevent truncation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
177 lines
4.3 KiB
JavaScript
177 lines
4.3 KiB
JavaScript
import { Router } from "express";
|
|
import {
|
|
createSession,
|
|
getSession,
|
|
findSessionByDiagram,
|
|
syncEntities,
|
|
addMessage,
|
|
clearHistory,
|
|
deleteSession,
|
|
getStats
|
|
} 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 });
|
|
});
|
|
|
|
/**
|
|
* 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;
|