From f191cd062c14dc208653f9859231eaf70cacea15 Mon Sep 17 00:00:00 2001 From: Eddy Date: Wed, 15 Apr 2026 13:40:34 +0200 Subject: [PATCH] Feature: Kontext-Auslastung im Footer (X% ctx) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Bridge: Token-Berechnung inkl. Cache (input + cache_read + cache_creation) - Store: contextUsage + contextPercent (derived) - Layout: Farbcodierte Anzeige (grün/gelb/rot bei 60%/80%) - Tooltip zeigt absolute Token-Zahlen Co-Authored-By: Claude Opus 4.5 --- scripts/claude-bridge.js | 26 ++++++++++++++++++++------ src/lib/stores/app.ts | 13 +++++++++++++ src/lib/stores/events.ts | 10 ++++++++++ src/routes/+layout.svelte | 23 ++++++++++++++++++++++- 4 files changed, 65 insertions(+), 7 deletions(-) diff --git a/scripts/claude-bridge.js b/scripts/claude-bridge.js index b13adf4..cefdb13 100644 --- a/scripts/claude-bridge.js +++ b/scripts/claude-bridge.js @@ -547,25 +547,39 @@ async function sendMessage(message, requestId, model = null, contextOverride = n case 'result': { // Endergebnis const durationMs = Date.now() - startTime; - const inputTokens = event.usage?.input_tokens || 0; - const outputTokens = event.usage?.output_tokens || 0; + + // Token-Zählung: input_tokens + cache_read + cache_creation = tatsächlicher Kontext + const usage = event.usage || {}; + const inputTokens = usage.input_tokens || 0; + const cacheRead = usage.cache_read_input_tokens || 0; + const cacheCreation = usage.cache_creation_input_tokens || 0; + const contextTokens = inputTokens + cacheRead + cacheCreation; + const outputTokens = usage.output_tokens || 0; const cost = event.total_cost_usd || 0; sendEvent('result', { text: fullText, cost, - tokens: { input: inputTokens, output: outputTokens }, + tokens: { + input: contextTokens, // Gesamter Kontext (inkl. Cache) + output: outputTokens, + raw_input: inputTokens, // Nur neue Token (für Debug) + cache_read: cacheRead, + cache_creation: cacheCreation, + }, session_id: event.session_id || '', duration_ms: durationMs, model: usedModel, }); // Monitor: API-Response - const tokenK = ((inputTokens + outputTokens) / 1000).toFixed(1); - sendMonitorEvent('api', `← ${usedModel} [${durationMs}ms] ${tokenK}k tok $${cost.toFixed(4)}`, { + const tokenK = ((contextTokens + outputTokens) / 1000).toFixed(1); + sendMonitorEvent('api', `← ${usedModel} [${durationMs}ms] ${tokenK}k ctx $${cost.toFixed(4)}`, { model: usedModel, - inputTokens, + contextTokens, outputTokens, + cacheRead, + cacheCreation, cost, sessionId: event.session_id, }, { durationMs }); diff --git a/src/lib/stores/app.ts b/src/lib/stores/app.ts index b7cfb68..7021448 100644 --- a/src/lib/stores/app.ts +++ b/src/lib/stores/app.ts @@ -70,6 +70,19 @@ export const sessionStats = writable({ messageCount: 0, }); +// Kontext-Auslastung (aktueller API-Call) +// inputTokens = was Claude bei diesem Request "gelesen" hat (System + Konversation) +export const contextUsage = writable({ + inputTokens: 0, // Aktuelle Kontext-Tokens + outputTokens: 0, // Tokens der letzten Antwort + contextLimit: 200000, // Claude 3.5/Opus Context Window +}); + +// Abgeleitet: Prozent der Kontext-Auslastung +export const contextPercent = derived(contextUsage, ($ctx) => + Math.round(($ctx.inputTokens / $ctx.contextLimit) * 100) +); + // Sticky Context Status (beim App-Start geladen) export interface StickyContextInfo { loaded: boolean; diff --git a/src/lib/stores/events.ts b/src/lib/stores/events.ts index b8d94fa..c77c790 100644 --- a/src/lib/stores/events.ts +++ b/src/lib/stores/events.ts @@ -18,6 +18,7 @@ import { clearAll, currentModel, sessionStats, + contextUsage, currentSessionId, messageToDb, addMonitorEvent, @@ -319,6 +320,15 @@ export async function initEventListeners(): Promise { totalCost: s.totalCost + (cost || 0), messageCount: s.messageCount + 1, })); + + // Kontext-Auslastung aktualisieren (input_tokens = aktuelle Kontext-Größe) + if (tokens?.input) { + contextUsage.update((ctx) => ({ + ...ctx, + inputTokens: tokens.input, + outputTokens: tokens.output || 0, + })); + } } }) ); diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 6b37a17..23e0937 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -2,7 +2,7 @@ import '../app.css'; import { onMount, onDestroy } from 'svelte'; import { invoke } from '@tauri-apps/api/core'; - import { isProcessing, agentCount, currentModel, sessionStats, initEventListeners, cleanupEventListeners, currentSessionId, setMessagesFromDb, stickyContextInfo, agentMode, type DbMessage, type StickyContextInfo, type AgentMode } from '$lib/stores'; + import { isProcessing, agentCount, currentModel, sessionStats, contextPercent, contextUsage, initEventListeners, cleanupEventListeners, currentSessionId, setMessagesFromDb, stickyContextInfo, agentMode, type DbMessage, type StickyContextInfo, type AgentMode } from '$lib/stores'; import StopButton from '$lib/components/StopButton.svelte'; // Session-Typ vom Backend @@ -178,6 +178,12 @@ | {/if} + {#if $contextUsage.inputTokens > 0} + 60} class:danger={$contextPercent > 80} title="{formatTokens($contextUsage.inputTokens)} von {formatTokens($contextUsage.contextLimit)} Token"> + {$contextPercent}% ctx + + | + {/if} Token: {formatTokens($sessionStats.totalTokensIn)} in / {formatTokens($sessionStats.totalTokensOut)} out | Kosten: {formatCost($sessionStats.totalCost)} @@ -311,6 +317,21 @@ cursor: help; } + .footer-stats .context-percent { + color: #22c55e; + font-weight: 600; + cursor: help; + } + + .footer-stats .context-percent.warning { + color: #eab308; + } + + .footer-stats .context-percent.danger { + color: #ef4444; + animation: pulse 1.5s ease-in-out infinite; + } + .footer-stats .mode-badge { font-weight: 600; cursor: help;