claude-desktop/src/lib/stores/events.ts
Eddy e09fb8815c Statusleiste mit Token/Kosten + Modell-Badge + STOPP funktionsfähig
- 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) <noreply@anthropic.com>
2026-04-13 22:14:46 +02:00

222 lines
5 KiB
TypeScript

// Claude Desktop — Event-Bridge
// Empfängt Events vom Tauri-Backend und aktualisiert die Stores
import { listen, type UnlistenFn } from '@tauri-apps/api/event';
import {
agents,
toolCalls,
messages,
isProcessing,
addMessage,
addAgent,
updateAgentStatus,
addToolCall,
completeToolCall,
clearAll,
currentModel,
sessionStats
} from './app';
// Event-Typen vom Backend
interface AgentEvent {
id: string;
type?: string;
task?: string;
code?: number;
}
interface ToolEvent {
id: string;
tool?: string;
input?: Record<string, unknown>;
output?: string;
success?: boolean;
}
interface TextEvent {
text: string;
}
interface ResultEvent {
cost?: number;
tokens?: {
input: number;
output: number;
};
session_id?: string;
model?: string;
}
// Listener-Handles
let listeners: UnlistenFn[] = [];
// Streaming: ID der aktuellen Live-Nachricht
let streamingMessageId: string | null = null;
// Events initialisieren
export async function initEventListeners(): Promise<void> {
console.log('🎧 Initialisiere Event-Listener...');
await cleanupEventListeners();
// Bridge bereit
listeners.push(
await listen('bridge-ready', () => {
console.log('✅ Bridge bereit');
})
);
// Agent gestartet
listeners.push(
await listen<AgentEvent>('agent-started', (event) => {
const { id, type, task } = event.payload;
console.log('🤖 Agent gestartet:', id, type);
addAgent(mapAgentType(type || 'main'), task || 'Verarbeite...');
isProcessing.set(true);
// Leere Streaming-Nachricht anlegen
streamingMessageId = crypto.randomUUID();
messages.update((msgs) => [
...msgs,
{
id: streamingMessageId!,
role: 'assistant',
content: '',
timestamp: new Date(),
agentId: id
}
]);
})
);
// Agent gestoppt
listeners.push(
await listen<AgentEvent>('agent-stopped', (event) => {
const { id } = event.payload;
console.log('⏹️ Agent gestoppt:', id);
updateAgentStatus(id, 'stopped');
streamingMessageId = null;
// Prüfen ob noch Agents aktiv
agents.update((ags) => {
const stillActive = ags.some((a) => a.status === 'active');
if (!stillActive) {
isProcessing.set(false);
}
return ags;
});
})
);
// Alle Agents gestoppt
listeners.push(
await listen('all-stopped', () => {
console.log('⏹️ Alle Agents gestoppt');
agents.update((ags) => ags.map((a) => ({ ...a, status: 'stopped' as const })));
isProcessing.set(false);
streamingMessageId = null;
})
);
// Tool Start
listeners.push(
await listen<ToolEvent>('tool-start', (event) => {
const { tool, input } = event.payload;
console.log('🔧 Tool Start:', tool);
agents.update((ags) => {
const activeAgent = ags.find((a) => a.status === 'active');
if (activeAgent) {
addToolCall(activeAgent.id, tool || 'unknown', input || {});
}
return ags;
});
})
);
// Tool Ende
listeners.push(
await listen<ToolEvent>('tool-end', (event) => {
const { id, success, output } = event.payload;
console.log('✅ Tool Ende:', id, success ? 'OK' : 'FEHLER');
completeToolCall(id, output, !success);
})
);
// Text-Streaming — live in die aktuelle Nachricht schreiben
listeners.push(
await listen<TextEvent>('claude-text', (event) => {
const { text } = event.payload;
if (streamingMessageId) {
messages.update((msgs) =>
msgs.map((m) =>
m.id === streamingMessageId
? { ...m, content: m.content + text }
: m
)
);
}
})
);
// Ergebnis (Kosten, Token, Modell)
listeners.push(
await listen<ResultEvent>('claude-result', (event) => {
const { cost, tokens, session_id, model } = event.payload;
console.log('📊 Ergebnis:', { cost: cost ? `$${cost.toFixed(4)}` : '-', tokens, model });
// Modell an die Streaming-Nachricht anhängen
if (model && streamingMessageId) {
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,
}));
}
})
);
// STOPP-Signal
listeners.push(
await listen('agents-stopped', () => {
console.log('🛑 STOPP-Signal empfangen');
streamingMessageId = null;
clearAll();
})
);
console.log('✅ Event-Listener initialisiert');
}
// Listener aufräumen
export async function cleanupEventListeners(): Promise<void> {
for (const unlisten of listeners) {
unlisten();
}
listeners = [];
}
// Agent-Typ mappen
function mapAgentType(type: string): 'main' | 'explore' | 'plan' | 'bash' {
const typeMap: Record<string, 'main' | 'explore' | 'plan' | 'bash'> = {
main: 'main',
'Main Agent': 'main',
Main: 'main',
explore: 'explore',
Explore: 'explore',
plan: 'plan',
Plan: 'plan',
bash: 'bash',
Bash: 'bash'
};
return typeMap[type] || 'main';
}