- 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>
222 lines
5 KiB
TypeScript
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';
|
|
}
|