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': {
|
||||
// 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 });
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import {
|
|||
clearAll,
|
||||
currentModel,
|
||||
sessionStats,
|
||||
contextUsage,
|
||||
currentSessionId,
|
||||
messageToDb,
|
||||
addMonitorEvent,
|
||||
|
|
@ -319,6 +320,15 @@ export async function initEventListeners(): Promise<void> {
|
|||
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,
|
||||
}));
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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 @@
|
|||
</span>
|
||||
<span class="sep">|</span>
|
||||
{/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 class="sep">|</span>
|
||||
<span>Kosten: {formatCost($sessionStats.totalCost)}</span>
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in a new issue