From e09fb8815ce71242ec79a262bd2d237a2e67bd00 Mon Sep 17 00:00:00 2001 From: Eddy Date: Mon, 13 Apr 2026 22:14:46 +0200 Subject: [PATCH] =?UTF-8?q?Statusleiste=20mit=20Token/Kosten=20+=20Modell-?= =?UTF-8?q?Badge=20+=20STOPP=20funktionsf=C3=A4hig?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Titlebar: Token in/out, Kosten, Modell-Badge (z.B. "Opus 4.6") - sessionStats Store: kumulierte Token/Kosten pro Session - STOPP-Button ruft invoke('stop_all_agents') auf - Escape-Hotkey zum Stoppen - Kompakteres Layout Co-Authored-By: Claude Opus 4.6 (1M context) --- src/lib/stores/app.ts | 9 +++ src/lib/stores/events.ts | 15 +++- src/routes/+layout.svelte | 141 ++++++++++++++++++-------------------- 3 files changed, 91 insertions(+), 74 deletions(-) diff --git a/src/lib/stores/app.ts b/src/lib/stores/app.ts index d6dcf2b..6d324b7 100644 --- a/src/lib/stores/app.ts +++ b/src/lib/stores/app.ts @@ -50,6 +50,15 @@ export const permissions = writable([]); export const isProcessing = writable(false); export const currentInput = writable(''); export const selectedAgentId = writable(null); +export const currentModel = writable(''); + +// Session-Statistiken (kumuliert) +export const sessionStats = writable({ + totalTokensIn: 0, + totalTokensOut: 0, + totalCost: 0, + messageCount: 0, +}); // Abgeleitete Stores export const activeAgents = derived(agents, ($agents) => diff --git a/src/lib/stores/events.ts b/src/lib/stores/events.ts index 14f53fa..46e3d0d 100644 --- a/src/lib/stores/events.ts +++ b/src/lib/stores/events.ts @@ -12,7 +12,9 @@ import { updateAgentStatus, addToolCall, completeToolCall, - clearAll + clearAll, + currentModel, + sessionStats } from './app'; // Event-Typen vom Backend @@ -168,6 +170,17 @@ export async function initEventListeners(): Promise { messages.update((msgs) => msgs.map((m) => m.id === streamingMessageId ? { ...m, model } : m) ); + currentModel.set(model); + } + + // Session-Statistiken aktualisieren + if (tokens || cost) { + sessionStats.update((s) => ({ + totalTokensIn: s.totalTokensIn + (tokens?.input || 0), + totalTokensOut: s.totalTokensOut + (tokens?.output || 0), + totalCost: s.totalCost + (cost || 0), + messageCount: s.messageCount + 1, + })); } }) ); diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 94013ae..be80cd3 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -2,10 +2,9 @@ import '../app.css'; import { onMount, onDestroy } from 'svelte'; import { invoke } from '@tauri-apps/api/core'; - import { isProcessing, agentCount, initEventListeners, cleanupEventListeners } from '$lib/stores'; + import { isProcessing, agentCount, currentModel, sessionStats, initEventListeners, cleanupEventListeners } from '$lib/stores'; import StopButton from '$lib/components/StopButton.svelte'; - // Events beim Laden initialisieren onMount(async () => { await initEventListeners(); }); @@ -14,9 +13,7 @@ await cleanupEventListeners(); }); - // STOPP-Funktion async function handleStop() { - console.log('STOPP gedrückt — breche alle Agents ab'); try { await invoke('stop_all_agents'); } catch (err) { @@ -25,12 +22,23 @@ $isProcessing = false; } - // Hotkey: Escape zum Stoppen function handleKeydown(event: KeyboardEvent) { if (event.key === 'Escape' && $isProcessing) { handleStop(); } } + + function formatCost(usd: number): string { + if (usd === 0) return '$0'; + if (usd < 0.01) return `$${usd.toFixed(4)}`; + return `$${usd.toFixed(2)}`; + } + + function formatTokens(n: number): string { + if (n === 0) return '0'; + if (n < 1000) return String(n); + return `${(n / 1000).toFixed(1)}k`; + } @@ -39,38 +47,36 @@
- 🤖

Claude Desktop

{#if $isProcessing} - + Arbeitet... {:else} - + Bereit {/if} + {#if $currentModel} + {$currentModel.replace('claude-', '').replace(/-\d{8}$/, '').replace(/(\D)-(\d)/g, '$1 $2').replace(/(\d)-(\d)/g, '$1.$2').replace(/\b[a-z]/g, c => c.toUpperCase())} + {/if}
- - {$agentCount.active} aktiv | {$agentCount.waiting} wartend | {$agentCount.idle} idle - + {formatTokens($sessionStats.totalTokensIn)} in + | + {formatTokens($sessionStats.totalTokensOut)} out + | + {formatCost($sessionStats.totalCost)}
-
- +
-
- Token: 0 / 200k - | - CPU: 0% -
@@ -82,93 +88,82 @@ background: var(--bg-primary); } - /* Titelleiste */ .titlebar { display: flex; align-items: center; justify-content: space-between; - padding: var(--spacing-sm) var(--spacing-md); + padding: var(--spacing-xs) var(--spacing-md); background: var(--bg-secondary); - border-bottom: 1px solid var(--bg-tertiary); - -webkit-app-region: drag; + border-bottom: 1px solid var(--border); user-select: none; - } - - .titlebar-left { - display: flex; - align-items: center; - gap: var(--spacing-sm); - } - - .app-icon { - font-size: 1.5rem; + height: 36px; } .titlebar h1 { - font-size: 1rem; + font-size: 0.85rem; font-weight: 600; + color: var(--text-heading); } .titlebar-center { display: flex; align-items: center; gap: var(--spacing-xs); - font-size: 0.875rem; - color: var(--text-secondary); - } - - .status-indicator { - width: 8px; - height: 8px; - border-radius: 50%; - } - - .status-indicator.active { - background: var(--success); - animation: pulse 1.5s ease-in-out infinite; - } - - .status-indicator.idle { - background: var(--text-secondary); - } - - .titlebar-right { font-size: 0.75rem; color: var(--text-secondary); } - /* Haupt-Inhalt */ + .status-dot { + width: 7px; + height: 7px; + border-radius: 50%; + } + + .status-dot.active { + background: var(--success); + animation: pulse 1.5s ease-in-out infinite; + } + + .status-dot.idle { + background: var(--text-secondary); + } + + .model-badge { + padding: 1px 6px; + background: var(--bg-tertiary); + border-radius: var(--radius-sm); + font-size: 0.65rem; + color: var(--accent); + font-weight: 600; + } + + .titlebar-right { + display: flex; + gap: var(--spacing-xs); + font-size: 0.65rem; + color: var(--text-secondary); + font-family: var(--font-mono); + } + + .sep { + opacity: 0.3; + } + .main-content { flex: 1; overflow: hidden; } - /* STOPP-Footer */ .stop-footer { display: flex; flex-direction: column; align-items: center; - gap: var(--spacing-xs); - padding: var(--spacing-sm) var(--spacing-md); + padding: var(--spacing-xs) var(--spacing-md); background: var(--bg-secondary); - border-top: 1px solid var(--bg-tertiary); - transition: border-color 0.3s ease; + border-top: 1px solid var(--border); } .stop-footer.active { - border-top: 2px solid var(--accent); - animation: glow 1.5s ease-in-out infinite; - } - - @keyframes glow { - 0%, 100% { box-shadow: 0 -2px 10px rgba(218, 68, 83, 0.2); } - 50% { box-shadow: 0 -2px 20px rgba(218, 68, 83, 0.4); } - } - - .footer-stats { - display: flex; - gap: var(--spacing-md); - font-size: 0.75rem; - color: var(--text-secondary); + border-top: 2px solid var(--error); }