Live-Streaming im Chat — Text erscheint Wort für Wort
- events.ts: Leere Streaming-Nachricht bei agent-started anlegen, claude-text Events schreiben direkt in die aktuelle Nachricht - ChatPanel: Typing-Dots nur bei leerer/fehlender Streaming-Nachricht - Kein Warten auf agent-stopped mehr — Text erscheint sofort Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
3e5021dbf0
commit
df3b33a6ed
2 changed files with 48 additions and 38 deletions
|
|
@ -99,6 +99,8 @@
|
|||
{/if}
|
||||
|
||||
{#if $isProcessing}
|
||||
{@const lastMsg = $messages.at(-1)}
|
||||
{#if !lastMsg || lastMsg.role !== 'assistant' || lastMsg.content === ''}
|
||||
<div class="message assistant typing-msg">
|
||||
<div class="message-header">
|
||||
<span class="message-role">🤖 Claude</span>
|
||||
|
|
@ -110,6 +112,7 @@
|
|||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="chat-input">
|
||||
|
|
|
|||
|
|
@ -41,20 +41,18 @@ interface ResultEvent {
|
|||
input: number;
|
||||
output: number;
|
||||
};
|
||||
session_id?: string;
|
||||
}
|
||||
|
||||
// Listener-Handles
|
||||
let listeners: UnlistenFn[] = [];
|
||||
|
||||
// Aktuelle Nachricht (wird während Streaming aufgebaut)
|
||||
let currentResponseText = '';
|
||||
let currentResponseAgentId: string | null = null;
|
||||
// Streaming: ID der aktuellen Live-Nachricht
|
||||
let streamingMessageId: string | null = null;
|
||||
|
||||
// Events initialisieren
|
||||
export async function initEventListeners(): Promise<void> {
|
||||
console.log('🎧 Initialisiere Event-Listener...');
|
||||
|
||||
// Aufräumen falls bereits initialisiert
|
||||
await cleanupEventListeners();
|
||||
|
||||
// Bridge bereit
|
||||
|
|
@ -70,10 +68,21 @@ export async function initEventListeners(): Promise<void> {
|
|||
const { id, type, task } = event.payload;
|
||||
console.log('🤖 Agent gestartet:', id, type);
|
||||
|
||||
const agentType = mapAgentType(type || 'main');
|
||||
addAgent(agentType, task || 'Verarbeite...');
|
||||
currentResponseAgentId = id;
|
||||
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
|
||||
}
|
||||
]);
|
||||
})
|
||||
);
|
||||
|
||||
|
|
@ -82,15 +91,8 @@ export async function initEventListeners(): Promise<void> {
|
|||
await listen<AgentEvent>('agent-stopped', (event) => {
|
||||
const { id } = event.payload;
|
||||
console.log('⏹️ Agent gestoppt:', id);
|
||||
|
||||
updateAgentStatus(id, 'stopped');
|
||||
|
||||
// Falls das der Haupt-Agent war, Antwort finalisieren
|
||||
if (currentResponseAgentId === id && currentResponseText) {
|
||||
addMessage('assistant', currentResponseText, id);
|
||||
currentResponseText = '';
|
||||
currentResponseAgentId = null;
|
||||
}
|
||||
streamingMessageId = null;
|
||||
|
||||
// Prüfen ob noch Agents aktiv
|
||||
agents.update((ags) => {
|
||||
|
|
@ -109,21 +111,16 @@ export async function initEventListeners(): Promise<void> {
|
|||
console.log('⏹️ Alle Agents gestoppt');
|
||||
agents.update((ags) => ags.map((a) => ({ ...a, status: 'stopped' as const })));
|
||||
isProcessing.set(false);
|
||||
|
||||
if (currentResponseText) {
|
||||
addMessage('assistant', currentResponseText);
|
||||
currentResponseText = '';
|
||||
}
|
||||
streamingMessageId = null;
|
||||
})
|
||||
);
|
||||
|
||||
// Tool Start
|
||||
listeners.push(
|
||||
await listen<ToolEvent>('tool-start', (event) => {
|
||||
const { id, tool, input } = event.payload;
|
||||
const { tool, input } = event.payload;
|
||||
console.log('🔧 Tool Start:', tool);
|
||||
|
||||
// Dem aktiven Haupt-Agent zuordnen
|
||||
agents.update((ags) => {
|
||||
const activeAgent = ags.find((a) => a.status === 'active');
|
||||
if (activeAgent) {
|
||||
|
|
@ -139,34 +136,43 @@ export async function initEventListeners(): Promise<void> {
|
|||
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
|
||||
// Text-Streaming — live in die aktuelle Nachricht schreiben
|
||||
listeners.push(
|
||||
await listen<TextEvent>('claude-text', (event) => {
|
||||
const { text } = event.payload;
|
||||
currentResponseText += text;
|
||||
if (streamingMessageId) {
|
||||
messages.update((msgs) =>
|
||||
msgs.map((m) =>
|
||||
m.id === streamingMessageId
|
||||
? { ...m, content: m.content + text }
|
||||
: m
|
||||
)
|
||||
);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
// Ergebnis (Kosten, Token)
|
||||
listeners.push(
|
||||
await listen<ResultEvent>('claude-result', (event) => {
|
||||
const { cost, tokens } = event.payload;
|
||||
const { cost, tokens, session_id } = event.payload;
|
||||
console.log('📊 Ergebnis:', {
|
||||
cost: cost ? `$${cost.toFixed(4)}` : 'unbekannt',
|
||||
tokens
|
||||
tokens,
|
||||
session_id
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
// Agents gestoppt (vom STOPP-Button)
|
||||
// STOPP-Signal
|
||||
listeners.push(
|
||||
await listen('agents-stopped', () => {
|
||||
console.log('🛑 STOPP-Signal empfangen');
|
||||
streamingMessageId = null;
|
||||
clearAll();
|
||||
})
|
||||
);
|
||||
|
|
@ -187,6 +193,7 @@ 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',
|
||||
|
|
|
|||
Loading…
Reference in a new issue