Feature: Kontext-Auslastung im Footer (X% ctx)
- 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 <noreply@anthropic.com>
This commit is contained in:
parent
48fd61fd01
commit
f191cd062c
4 changed files with 65 additions and 7 deletions
|
|
@ -547,25 +547,39 @@ async function sendMessage(message, requestId, model = null, contextOverride = n
|
||||||
case 'result': {
|
case 'result': {
|
||||||
// Endergebnis
|
// Endergebnis
|
||||||
const durationMs = Date.now() - startTime;
|
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;
|
const cost = event.total_cost_usd || 0;
|
||||||
|
|
||||||
sendEvent('result', {
|
sendEvent('result', {
|
||||||
text: fullText,
|
text: fullText,
|
||||||
cost,
|
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 || '',
|
session_id: event.session_id || '',
|
||||||
duration_ms: durationMs,
|
duration_ms: durationMs,
|
||||||
model: usedModel,
|
model: usedModel,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Monitor: API-Response
|
// Monitor: API-Response
|
||||||
const tokenK = ((inputTokens + outputTokens) / 1000).toFixed(1);
|
const tokenK = ((contextTokens + outputTokens) / 1000).toFixed(1);
|
||||||
sendMonitorEvent('api', `← ${usedModel} [${durationMs}ms] ${tokenK}k tok $${cost.toFixed(4)}`, {
|
sendMonitorEvent('api', `← ${usedModel} [${durationMs}ms] ${tokenK}k ctx $${cost.toFixed(4)}`, {
|
||||||
model: usedModel,
|
model: usedModel,
|
||||||
inputTokens,
|
contextTokens,
|
||||||
outputTokens,
|
outputTokens,
|
||||||
|
cacheRead,
|
||||||
|
cacheCreation,
|
||||||
cost,
|
cost,
|
||||||
sessionId: event.session_id,
|
sessionId: event.session_id,
|
||||||
}, { durationMs });
|
}, { durationMs });
|
||||||
|
|
|
||||||
|
|
@ -70,6 +70,19 @@ export const sessionStats = writable({
|
||||||
messageCount: 0,
|
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)
|
// Sticky Context Status (beim App-Start geladen)
|
||||||
export interface StickyContextInfo {
|
export interface StickyContextInfo {
|
||||||
loaded: boolean;
|
loaded: boolean;
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ import {
|
||||||
clearAll,
|
clearAll,
|
||||||
currentModel,
|
currentModel,
|
||||||
sessionStats,
|
sessionStats,
|
||||||
|
contextUsage,
|
||||||
currentSessionId,
|
currentSessionId,
|
||||||
messageToDb,
|
messageToDb,
|
||||||
addMonitorEvent,
|
addMonitorEvent,
|
||||||
|
|
@ -319,6 +320,15 @@ export async function initEventListeners(): Promise<void> {
|
||||||
totalCost: s.totalCost + (cost || 0),
|
totalCost: s.totalCost + (cost || 0),
|
||||||
messageCount: s.messageCount + 1,
|
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,
|
||||||
|
}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
import '../app.css';
|
import '../app.css';
|
||||||
import { onMount, onDestroy } from 'svelte';
|
import { onMount, onDestroy } from 'svelte';
|
||||||
import { invoke } from '@tauri-apps/api/core';
|
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';
|
import StopButton from '$lib/components/StopButton.svelte';
|
||||||
|
|
||||||
// Session-Typ vom Backend
|
// Session-Typ vom Backend
|
||||||
|
|
@ -178,6 +178,12 @@
|
||||||
</span>
|
</span>
|
||||||
<span class="sep">|</span>
|
<span class="sep">|</span>
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if $contextUsage.inputTokens > 0}
|
||||||
|
<span class="context-percent" class:warning={$contextPercent > 60} class:danger={$contextPercent > 80} title="{formatTokens($contextUsage.inputTokens)} von {formatTokens($contextUsage.contextLimit)} Token">
|
||||||
|
{$contextPercent}% ctx
|
||||||
|
</span>
|
||||||
|
<span class="sep">|</span>
|
||||||
|
{/if}
|
||||||
<span>Token: {formatTokens($sessionStats.totalTokensIn)} in / {formatTokens($sessionStats.totalTokensOut)} out</span>
|
<span>Token: {formatTokens($sessionStats.totalTokensIn)} in / {formatTokens($sessionStats.totalTokensOut)} out</span>
|
||||||
<span class="sep">|</span>
|
<span class="sep">|</span>
|
||||||
<span>Kosten: {formatCost($sessionStats.totalCost)}</span>
|
<span>Kosten: {formatCost($sessionStats.totalCost)}</span>
|
||||||
|
|
@ -311,6 +317,21 @@
|
||||||
cursor: help;
|
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 {
|
.footer-stats .mode-badge {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
cursor: help;
|
cursor: help;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue