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,16 +99,19 @@
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if $isProcessing}
|
{#if $isProcessing}
|
||||||
<div class="message assistant typing-msg">
|
{@const lastMsg = $messages.at(-1)}
|
||||||
<div class="message-header">
|
{#if !lastMsg || lastMsg.role !== 'assistant' || lastMsg.content === ''}
|
||||||
<span class="message-role">🤖 Claude</span>
|
<div class="message assistant typing-msg">
|
||||||
|
<div class="message-header">
|
||||||
|
<span class="message-role">🤖 Claude</span>
|
||||||
|
</div>
|
||||||
|
<div class="message-content typing">
|
||||||
|
<span class="dot"></span>
|
||||||
|
<span class="dot"></span>
|
||||||
|
<span class="dot"></span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="message-content typing">
|
{/if}
|
||||||
<span class="dot"></span>
|
|
||||||
<span class="dot"></span>
|
|
||||||
<span class="dot"></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -41,20 +41,18 @@ interface ResultEvent {
|
||||||
input: number;
|
input: number;
|
||||||
output: number;
|
output: number;
|
||||||
};
|
};
|
||||||
|
session_id?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Listener-Handles
|
// Listener-Handles
|
||||||
let listeners: UnlistenFn[] = [];
|
let listeners: UnlistenFn[] = [];
|
||||||
|
|
||||||
// Aktuelle Nachricht (wird während Streaming aufgebaut)
|
// Streaming: ID der aktuellen Live-Nachricht
|
||||||
let currentResponseText = '';
|
let streamingMessageId: string | null = null;
|
||||||
let currentResponseAgentId: string | null = null;
|
|
||||||
|
|
||||||
// Events initialisieren
|
// Events initialisieren
|
||||||
export async function initEventListeners(): Promise<void> {
|
export async function initEventListeners(): Promise<void> {
|
||||||
console.log('🎧 Initialisiere Event-Listener...');
|
console.log('🎧 Initialisiere Event-Listener...');
|
||||||
|
|
||||||
// Aufräumen falls bereits initialisiert
|
|
||||||
await cleanupEventListeners();
|
await cleanupEventListeners();
|
||||||
|
|
||||||
// Bridge bereit
|
// Bridge bereit
|
||||||
|
|
@ -70,10 +68,21 @@ export async function initEventListeners(): Promise<void> {
|
||||||
const { id, type, task } = event.payload;
|
const { id, type, task } = event.payload;
|
||||||
console.log('🤖 Agent gestartet:', id, type);
|
console.log('🤖 Agent gestartet:', id, type);
|
||||||
|
|
||||||
const agentType = mapAgentType(type || 'main');
|
addAgent(mapAgentType(type || 'main'), task || 'Verarbeite...');
|
||||||
addAgent(agentType, task || 'Verarbeite...');
|
|
||||||
currentResponseAgentId = id;
|
|
||||||
isProcessing.set(true);
|
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) => {
|
await listen<AgentEvent>('agent-stopped', (event) => {
|
||||||
const { id } = event.payload;
|
const { id } = event.payload;
|
||||||
console.log('⏹️ Agent gestoppt:', id);
|
console.log('⏹️ Agent gestoppt:', id);
|
||||||
|
|
||||||
updateAgentStatus(id, 'stopped');
|
updateAgentStatus(id, 'stopped');
|
||||||
|
streamingMessageId = null;
|
||||||
// Falls das der Haupt-Agent war, Antwort finalisieren
|
|
||||||
if (currentResponseAgentId === id && currentResponseText) {
|
|
||||||
addMessage('assistant', currentResponseText, id);
|
|
||||||
currentResponseText = '';
|
|
||||||
currentResponseAgentId = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prüfen ob noch Agents aktiv
|
// Prüfen ob noch Agents aktiv
|
||||||
agents.update((ags) => {
|
agents.update((ags) => {
|
||||||
|
|
@ -109,21 +111,16 @@ export async function initEventListeners(): Promise<void> {
|
||||||
console.log('⏹️ Alle Agents gestoppt');
|
console.log('⏹️ Alle Agents gestoppt');
|
||||||
agents.update((ags) => ags.map((a) => ({ ...a, status: 'stopped' as const })));
|
agents.update((ags) => ags.map((a) => ({ ...a, status: 'stopped' as const })));
|
||||||
isProcessing.set(false);
|
isProcessing.set(false);
|
||||||
|
streamingMessageId = null;
|
||||||
if (currentResponseText) {
|
|
||||||
addMessage('assistant', currentResponseText);
|
|
||||||
currentResponseText = '';
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
// Tool Start
|
// Tool Start
|
||||||
listeners.push(
|
listeners.push(
|
||||||
await listen<ToolEvent>('tool-start', (event) => {
|
await listen<ToolEvent>('tool-start', (event) => {
|
||||||
const { id, tool, input } = event.payload;
|
const { tool, input } = event.payload;
|
||||||
console.log('🔧 Tool Start:', tool);
|
console.log('🔧 Tool Start:', tool);
|
||||||
|
|
||||||
// Dem aktiven Haupt-Agent zuordnen
|
|
||||||
agents.update((ags) => {
|
agents.update((ags) => {
|
||||||
const activeAgent = ags.find((a) => a.status === 'active');
|
const activeAgent = ags.find((a) => a.status === 'active');
|
||||||
if (activeAgent) {
|
if (activeAgent) {
|
||||||
|
|
@ -139,34 +136,43 @@ export async function initEventListeners(): Promise<void> {
|
||||||
await listen<ToolEvent>('tool-end', (event) => {
|
await listen<ToolEvent>('tool-end', (event) => {
|
||||||
const { id, success, output } = event.payload;
|
const { id, success, output } = event.payload;
|
||||||
console.log('✅ Tool Ende:', id, success ? 'OK' : 'FEHLER');
|
console.log('✅ Tool Ende:', id, success ? 'OK' : 'FEHLER');
|
||||||
|
|
||||||
completeToolCall(id, output, !success);
|
completeToolCall(id, output, !success);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
// Text-Streaming
|
// Text-Streaming — live in die aktuelle Nachricht schreiben
|
||||||
listeners.push(
|
listeners.push(
|
||||||
await listen<TextEvent>('claude-text', (event) => {
|
await listen<TextEvent>('claude-text', (event) => {
|
||||||
const { text } = event.payload;
|
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)
|
// Ergebnis (Kosten, Token)
|
||||||
listeners.push(
|
listeners.push(
|
||||||
await listen<ResultEvent>('claude-result', (event) => {
|
await listen<ResultEvent>('claude-result', (event) => {
|
||||||
const { cost, tokens } = event.payload;
|
const { cost, tokens, session_id } = event.payload;
|
||||||
console.log('📊 Ergebnis:', {
|
console.log('📊 Ergebnis:', {
|
||||||
cost: cost ? `$${cost.toFixed(4)}` : 'unbekannt',
|
cost: cost ? `$${cost.toFixed(4)}` : 'unbekannt',
|
||||||
tokens
|
tokens,
|
||||||
|
session_id
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
// Agents gestoppt (vom STOPP-Button)
|
// STOPP-Signal
|
||||||
listeners.push(
|
listeners.push(
|
||||||
await listen('agents-stopped', () => {
|
await listen('agents-stopped', () => {
|
||||||
console.log('🛑 STOPP-Signal empfangen');
|
console.log('🛑 STOPP-Signal empfangen');
|
||||||
|
streamingMessageId = null;
|
||||||
clearAll();
|
clearAll();
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
@ -187,6 +193,7 @@ function mapAgentType(type: string): 'main' | 'explore' | 'plan' | 'bash' {
|
||||||
const typeMap: Record<string, 'main' | 'explore' | 'plan' | 'bash'> = {
|
const typeMap: Record<string, 'main' | 'explore' | 'plan' | 'bash'> = {
|
||||||
main: 'main',
|
main: 'main',
|
||||||
'Main Agent': 'main',
|
'Main Agent': 'main',
|
||||||
|
Main: 'main',
|
||||||
explore: 'explore',
|
explore: 'explore',
|
||||||
Explore: 'explore',
|
Explore: 'explore',
|
||||||
plan: 'plan',
|
plan: 'plan',
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue