diff --git a/src/lib/components/ChatPanel.svelte b/src/lib/components/ChatPanel.svelte
index 961ebc4..387703e 100644
--- a/src/lib/components/ChatPanel.svelte
+++ b/src/lib/components/ChatPanel.svelte
@@ -2,7 +2,7 @@
import { invoke } from '@tauri-apps/api/core';
import { emit } from '@tauri-apps/api/event';
import { messages, currentInput, isProcessing, addMessage, currentSessionId, messageToDb, queuedMessage, messageQueue, type Message } from '$lib/stores/app';
- import { currentTool } from '$lib/stores/events';
+ import { currentTool, processingPhase } from '$lib/stores/events';
import { marked, type Tokens } from 'marked';
import { tick, onDestroy, onMount } from 'svelte';
import { get } from 'svelte/store';
@@ -110,7 +110,7 @@
case 'Read': return `Liest ${(input.file_path as string)?.split('/').pop() || 'Datei'}...`;
case 'Write': return `Schreibt ${(input.file_path as string)?.split('/').pop() || 'Datei'}...`;
case 'Edit': return `Bearbeitet ${(input.file_path as string)?.split('/').pop() || 'Datei'}...`;
- case 'Bash': return `Fuehrt aus: ${((input.command as string) || '').substring(0, 40)}...`;
+ case 'Bash': return `Führt aus: ${((input.command as string) || '').substring(0, 40)}...`;
case 'Grep': return `Sucht: ${(input.pattern as string) || ''}...`;
case 'Glob': return `Sucht Dateien: ${(input.pattern as string) || ''}...`;
case 'Task': case 'Agent': return `Delegiert: ${((input.description as string) || (input.prompt as string) || '').substring(0, 40)}...`;
@@ -118,6 +118,27 @@
}
}
+ // Phasen-basierte Status-Anzeige
+ function getPhaseIcon(phase: string): string {
+ switch(phase) {
+ case 'thinking': return '\u{1F9E0}'; // 🧠
+ case 'streaming': return '\u{270D}\u{FE0F}'; // ✍️
+ case 'tool-use': return '\u{1F527}'; // 🔧
+ case 'subagent': return '\u{1F916}'; // 🤖
+ default: return '\u{2699}\u{FE0F}'; // ⚙️
+ }
+ }
+
+ function getPhaseLabel(phase: string): string {
+ switch(phase) {
+ case 'thinking': return 'Denkt nach';
+ case 'streaming': return 'Schreibt Antwort';
+ case 'tool-use': return 'Arbeitet';
+ case 'subagent': return 'Subagent aktiv';
+ default: return 'Verarbeitet';
+ }
+ }
+
// Svelte Action: Copy-Buttons zu Code-Blöcken hinzufügen
function addCopyButtons(node: HTMLElement) {
function processCodeBlocks() {
@@ -939,22 +960,20 @@
{/if}
{/if}
{:else if $isProcessing}
- {#if $currentTool}
-
- {getToolIcon($currentTool.tool)}
- {getToolLabel($currentTool.tool, $currentTool.input)}
-
- {:else}
-
- {'\u{1F9E0}'}
- Denkt nach...
-
- {/if}
-
-
-
-
-
+
+ {#if $currentTool}
+ {getToolIcon($currentTool.tool)}
+ {getToolLabel($currentTool.tool, $currentTool.input)}
+ {:else}
+ {getPhaseIcon($processingPhase)}
+ {getPhaseLabel($processingPhase)}
+ {/if}
+
+
+
+
+
+
{/if}
{:else}
{#if message.role === 'user' && message.content && shouldCollapse(message.content, 'user') && !expandedMessages.includes(message.id)}
@@ -987,22 +1006,20 @@
{'\u{1F916}'} Claude
- {#if $currentTool}
-
- {getToolIcon($currentTool.tool)}
- {getToolLabel($currentTool.tool, $currentTool.input)}
-
- {:else}
-
- {'\u{1F9E0}'}
- Denkt nach...
-
- {/if}
-
-
-
-
-
+
+ {#if $currentTool}
+ {getToolIcon($currentTool.tool)}
+ {getToolLabel($currentTool.tool, $currentTool.input)}
+ {:else}
+ {getPhaseIcon($processingPhase)}
+ {getPhaseLabel($processingPhase)}
+ {/if}
+
+
+
+
+
+
{/if}
@@ -1537,30 +1554,60 @@
border-radius: 2px;
}
- /* Tool-Aktivitaetsanzeige */
- .tool-status {
+ /* Aktivitätsanzeige — kompakte Zeile mit Icon, Label und Dots */
+ .activity-indicator {
display: flex;
align-items: center;
- gap: 0.4rem;
- padding: 0.3rem 0.8rem;
+ gap: 0.5rem;
+ padding: 0.3rem 0;
margin: 0.2rem 0;
- font-size: 0.7rem;
+ font-size: 0.78rem;
color: var(--text-secondary, #9ca3af);
- animation: fadeInTool 0.2s ease;
+ animation: fadeInActivity 0.25s ease;
}
- .tool-icon {
- font-size: 0.75rem;
+ .activity-icon {
+ font-size: 0.85rem;
+ flex-shrink: 0;
}
- .tool-label {
- opacity: 0.8;
+ .activity-label {
font-family: var(--font-mono, monospace);
- font-size: 0.65rem;
+ font-size: 0.72rem;
+ opacity: 0.85;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ max-width: 280px;
}
- @keyframes fadeInTool {
- from { opacity: 0; transform: translateY(-4px); }
+ .activity-dots {
+ display: inline-flex;
+ gap: 3px;
+ margin-left: 2px;
+ flex-shrink: 0;
+ }
+
+ .activity-dot {
+ width: 4px;
+ height: 4px;
+ background: currentColor;
+ border-radius: 50%;
+ opacity: 0.5;
+ animation: dotPulse 1.2s infinite ease-in-out;
+ }
+
+ .activity-dot:nth-child(1) { animation-delay: 0s; }
+ .activity-dot:nth-child(2) { animation-delay: 0.2s; }
+ .activity-dot:nth-child(3) { animation-delay: 0.4s; }
+
+ @keyframes dotPulse {
+ 0%, 60%, 100% { opacity: 0.3; transform: scale(1); }
+ 30% { opacity: 1; transform: scale(1.3); }
+ }
+
+ @keyframes fadeInActivity {
+ from { opacity: 0; transform: translateY(-3px); }
to { opacity: 1; transform: translateY(0); }
}
@@ -1712,27 +1759,7 @@
font-weight: 600;
}
- /* Typing-Animation */
- .typing {
- display: flex;
- gap: 4px;
- }
-
- .dot {
- width: 8px;
- height: 8px;
- background: var(--text-secondary);
- border-radius: 50%;
- animation: bounce 1.4s infinite ease-in-out;
- }
-
- .dot:nth-child(1) { animation-delay: -0.32s; }
- .dot:nth-child(2) { animation-delay: -0.16s; }
-
- @keyframes bounce {
- 0%, 80%, 100% { transform: scale(0); }
- 40% { transform: scale(1); }
- }
+ /* Alte Typing-Animation entfernt — ersetzt durch .activity-indicator */
/* Input-Bereich */
.chat-input {
diff --git a/src/lib/stores/events.ts b/src/lib/stores/events.ts
index 6e7784c..64b53d1 100644
--- a/src/lib/stores/events.ts
+++ b/src/lib/stores/events.ts
@@ -34,6 +34,10 @@ import {
// Aktuell laufendes Tool (für inline Aktivitätsanzeige)
export const currentTool = writable<{ tool: string; input: Record } | null>(null);
+// Detaillierte Verarbeitungsphase für Status-Anzeige
+export type ProcessingPhase = 'thinking' | 'streaming' | 'tool-use' | 'subagent' | 'idle';
+export const processingPhase = writable('idle');
+
// Event-Typen vom Backend
interface AgentEvent {
id: string;
@@ -145,6 +149,7 @@ export async function initEventListeners(): Promise {
// WICHTIG: id mitgeben, sonst stimmt parentAgentId der Sub-Agents nicht!
addAgent(mapAgentType(type || 'main'), task || 'Verarbeite...', { id, model });
isProcessing.set(true);
+ processingPhase.set('thinking');
// Leere Streaming-Nachricht anlegen
streamingMessageId = crypto.randomUUID();
@@ -187,6 +192,7 @@ export async function initEventListeners(): Promise {
agents.update((ags) => ags.map((a) => ({ ...a, status: 'stopped' as const })));
isProcessing.set(false);
currentTool.set(null);
+ processingPhase.set('idle');
streamingMessageId = null;
})
);
@@ -196,6 +202,7 @@ export async function initEventListeners(): Promise {
await listen('subagent-started', (event) => {
const { id, parentAgentId, type, task, depth, model } = event.payload;
console.log('🤖 Subagent gestartet:', id, type, '(Parent:', parentAgentId, ')');
+ processingPhase.set('subagent');
addSubAgent(
parentAgentId,
@@ -223,6 +230,7 @@ export async function initEventListeners(): Promise {
// Inline-Aktivitätsanzeige aktualisieren
currentTool.set({ tool: tool || 'unknown', input: input || {} });
+ processingPhase.set('tool-use');
agents.update((ags) => {
const activeAgent = ags.find((a) => a.status === 'active');
@@ -272,6 +280,7 @@ export async function initEventListeners(): Promise {
const { id, tool, success, output } = event.payload;
console.log('✅ Tool Ende:', id, success ? 'OK' : 'FEHLER');
currentTool.set(null);
+ processingPhase.set('thinking');
completeToolCall(id, output, !success);
// Hook: post-tool-use (fire-and-forget, Fehler blockieren nicht)
@@ -312,6 +321,7 @@ export async function initEventListeners(): Promise {
listeners.push(
await listen('claude-text', (event) => {
const { text } = event.payload;
+ processingPhase.set('streaming');
if (streamingMessageId) {
messages.update((msgs) =>
msgs.map((m) =>
@@ -415,6 +425,7 @@ export async function initEventListeners(): Promise {
agents.update((ags) => ags.map((a) => ({ ...a, status: 'stopped' as const })));
toolCalls.set([]);
currentTool.set(null);
+ processingPhase.set('idle');
isProcessing.set(false);
})
);