diff --git a/server/api/claude.js b/server/api/claude.js
index 0139e8f..29262ec 100644
--- a/server/api/claude.js
+++ b/server/api/claude.js
@@ -9,6 +9,9 @@ import {
const router = Router();
+// Context limits for Claude models (all have 200K)
+const CLAUDE_CONTEXT_LIMIT = 200000;
+
router.post("/*path", async (req, res) => {
const requestStart = Date.now();
console.log(`[Claude API] ========== REQUEST START ==========`);
@@ -66,7 +69,8 @@ router.post("/*path", async (req, res) => {
const usageRecord = trackUsage(sessionId, modelId, data.usage, {
inputText,
outputText,
- toolCalls
+ toolCalls,
+ contextLimit: CLAUDE_CONTEXT_LIMIT
});
console.log(`[Claude API] REQUEST USAGE: ${getUsageSummary(usageRecord)}`);
@@ -77,6 +81,14 @@ router.post("/*path", async (req, res) => {
console.log(`[Claude API] Total input: ${sessionStats.totalInputTokens} tokens`);
console.log(`[Claude API] Total output: ${sessionStats.totalOutputTokens} tokens`);
console.log(`[Claude API] Total cost: ${formatCost(sessionStats.totalCost)}`);
+ console.log(`[Claude API] Context: ${sessionStats.contextUsed}/${sessionStats.contextLimit} (${sessionStats.contextPercent.toFixed(1)}%)`);
+
+ // Context warnings
+ if (sessionStats.contextPercent >= 95) {
+ console.error(`[Claude API] ⚠️ CONTEXT CRITICAL: ${sessionStats.contextPercent.toFixed(0)}% used! Consider clearing conversation.`);
+ } else if (sessionStats.contextPercent >= 80) {
+ console.warn(`[Claude API] ⚠️ CONTEXT WARNING: ${sessionStats.contextPercent.toFixed(0)}% of context window used`);
+ }
}
}
}
diff --git a/server/api/cloudflare.js b/server/api/cloudflare.js
index 1f1ed01..35d3e3c 100644
--- a/server/api/cloudflare.js
+++ b/server/api/cloudflare.js
@@ -7,6 +7,16 @@ import { buildLangChainMessages, aiMessageToClaudeResponse } from "../services/l
const router = Router();
+// Context limits for Cloudflare models
+const CLOUDFLARE_CONTEXT_LIMITS = {
+ '@cf/mistralai/mistral-small-3.1-24b-instruct': 32000,
+ '@hf/nousresearch/hermes-2-pro-mistral-7b': 8000,
+ '@cf/meta/llama-3.3-70b-instruct-fp8-fast': 128000,
+ '@cf/meta/llama-3.1-8b-instruct': 128000,
+ '@cf/deepseek-ai/deepseek-r1-distill-qwen-32b': 32000,
+ '@cf/qwen/qwen2.5-coder-32b-instruct': 32000
+};
+
router.post("/*path", async (req, res) => {
const requestStart = Date.now();
console.log(`[Cloudflare API] ========== REQUEST START ==========`);
@@ -68,10 +78,12 @@ router.post("/*path", async (req, res) => {
?.filter(c => c.type === 'tool_use')
.map(c => ({ name: c.name, input: c.input })) || [];
+ const contextLimit = CLOUDFLARE_CONTEXT_LIMITS[modelId] || 32000;
const usageRecord = trackUsage(sessionId, modelId, data.usage, {
inputText,
outputText,
- toolCalls
+ toolCalls,
+ contextLimit
});
console.log(`[Cloudflare API] REQUEST USAGE: ${getUsageSummary(usageRecord)}`);
@@ -82,6 +94,14 @@ router.post("/*path", async (req, res) => {
console.log(`[Cloudflare API] Total input: ${sessionStats.totalInputTokens} tokens`);
console.log(`[Cloudflare API] Total output: ${sessionStats.totalOutputTokens} tokens`);
console.log(`[Cloudflare API] Total cost: ${formatCost(sessionStats.totalCost)}`);
+ console.log(`[Cloudflare API] Context: ${sessionStats.contextUsed}/${sessionStats.contextLimit} (${sessionStats.contextPercent.toFixed(1)}%)`);
+
+ // Context warnings
+ if (sessionStats.contextPercent >= 95) {
+ console.error(`[Cloudflare API] ⚠️ CONTEXT CRITICAL: ${sessionStats.contextPercent.toFixed(0)}% used! Consider clearing conversation.`);
+ } else if (sessionStats.contextPercent >= 80) {
+ console.warn(`[Cloudflare API] ⚠️ CONTEXT WARNING: ${sessionStats.contextPercent.toFixed(0)}% of context window used`);
+ }
}
}
}
diff --git a/server/services/usageTracker.js b/server/services/usageTracker.js
index ef6ef2c..25754df 100644
--- a/server/services/usageTracker.js
+++ b/server/services/usageTracker.js
@@ -114,6 +114,7 @@ function calculateCost(model, usage) {
* @param {string} content.inputText - User input text
* @param {string} content.outputText - Assistant output text
* @param {array} content.toolCalls - Tool calls made
+ * @param {number} content.contextLimit - Model's context window limit
*/
export function trackUsage(sessionId, model, usage, content = {}) {
if (!usage) return null;
@@ -150,7 +151,10 @@ export function trackUsage(sessionId, model, usage, content = {}) {
totalCost: 0,
requestCount: 0,
requests: [],
- startTime: Date.now()
+ startTime: Date.now(),
+ // Context tracking
+ contextUsed: 0,
+ contextLimit: content.contextLimit || 32000
});
}
@@ -163,6 +167,12 @@ export function trackUsage(sessionId, model, usage, content = {}) {
session.requestCount += 1;
session.requests.push(usageRecord);
+ // Update context tracking - input tokens represent current context size
+ session.contextUsed = usageRecord.inputTokens;
+ if (content.contextLimit) {
+ session.contextLimit = content.contextLimit;
+ }
+
// Keep only last 100 requests per session to limit memory
if (session.requests.length > 100) {
session.requests.shift();
@@ -198,7 +208,18 @@ export function trackUsage(sessionId, model, usage, content = {}) {
* Get usage for a session
*/
export function getSessionUsage(sessionId) {
- return sessionUsage.get(sessionId) || null;
+ const session = sessionUsage.get(sessionId);
+ if (!session) return null;
+
+ // Calculate context percentage
+ const contextPercent = session.contextLimit > 0
+ ? (session.contextUsed / session.contextLimit) * 100
+ : 0;
+
+ return {
+ ...session,
+ contextPercent
+ };
}
/**
diff --git a/src/diagram/diagramManager.ts b/src/diagram/diagramManager.ts
index 43e3065..37c255f 100644
--- a/src/diagram/diagramManager.ts
+++ b/src/diagram/diagramManager.ts
@@ -110,7 +110,9 @@ export class DiagramManager {
this._logger.debug('chatCreateEntity', entity);
// Generate a default label if none is provided
- if (!entity.text) {
+ // Use strict check to allow empty string "" (explicit no label) while still
+ // generating labels for undefined/null (user didn't specify)
+ if (entity.text === undefined || entity.text === null) {
entity.text = this.generateDefaultLabel(entity);
this._logger.debug('Generated default label:', entity.text);
}
diff --git a/src/react/components/ChatPanel.tsx b/src/react/components/ChatPanel.tsx
index 09f76bf..5ba04f2 100644
--- a/src/react/components/ChatPanel.tsx
+++ b/src/react/components/ChatPanel.tsx
@@ -1,5 +1,5 @@
import React, {useEffect, useRef, useState} from "react";
-import {ActionIcon, Alert, Box, CloseButton, Group, Paper, ScrollArea, Text, Textarea, Tooltip, UnstyledButton} from "@mantine/core";
+import {ActionIcon, Alert, Badge, Box, CloseButton, Group, Paper, ScrollArea, Text, Textarea, Tooltip, UnstyledButton} from "@mantine/core";
import {IconAlertCircle, IconCoins, IconRobot, IconSend, IconTrash} from "@tabler/icons-react";
import ChatMessage from "./ChatMessage";
import UsageDetailModal from "./UsageDetailModal";
@@ -177,6 +177,8 @@ export default function ChatPanel({width = 400, onClose}: ChatPanelProps) {
setError(err instanceof Error ? err.message : 'Failed to send message');
} finally {
setIsLoading(false);
+ // Refocus the input after message completes
+ textareaRef.current?.focus();
}
};
@@ -226,6 +228,20 @@ export default function ChatPanel({width = 400, onClose}: ChatPanelProps) {
)}
+ {usage && usage.contextPercent !== undefined && (
+
+ 80 ? 'red' : usage.contextPercent > 50 ? 'yellow' : 'green'}
+ >
+ {usage.contextPercent.toFixed(0)}%
+
+
+ )}