diff --git a/desktop/src/components/chat/AssistantMessage.tsx b/desktop/src/components/chat/AssistantMessage.tsx index 36359f4f..62a8a622 100644 --- a/desktop/src/components/chat/AssistantMessage.tsx +++ b/desktop/src/components/chat/AssistantMessage.tsx @@ -20,27 +20,17 @@ export function AssistantMessage({ content, isStreaming }: Props) { } return ( -
- {/* Avatar */} -
- - - - - -
- {/* Content */} -
- {!isStreaming && content.trim() && ( -
- -
- )} +
+ {/* Copy button — absolute positioned, no reserved space */} + {!isStreaming && content.trim() && ( + + )} +
{isStreaming && ( diff --git a/desktop/src/components/chat/StreamingIndicator.tsx b/desktop/src/components/chat/StreamingIndicator.tsx index c1826d1f..5a8a2f05 100644 --- a/desktop/src/components/chat/StreamingIndicator.tsx +++ b/desktop/src/components/chat/StreamingIndicator.tsx @@ -1,26 +1,18 @@ import { useChatStore } from '../../stores/chatStore' export function StreamingIndicator() { - const { chatState, elapsedSeconds, tokenUsage } = useChatStore() + const { chatState } = useChatStore() const verb = chatState === 'thinking' ? 'Thinking' : chatState === 'tool_executing' - ? 'Executing' - : 'Crafting' + ? 'Running' + : 'Working' return ( -
- - {verb}... - - {elapsedSeconds}s - - {tokenUsage.output_tokens > 0 && ( - - · ↓ {tokenUsage.output_tokens} tokens - - )} +
+ + {verb}...
) } diff --git a/desktop/src/components/chat/ThinkingBlock.tsx b/desktop/src/components/chat/ThinkingBlock.tsx index 191cc82e..94481f02 100644 --- a/desktop/src/components/chat/ThinkingBlock.tsx +++ b/desktop/src/components/chat/ThinkingBlock.tsx @@ -2,58 +2,53 @@ import { useState, useEffect, useRef } from 'react' export function ThinkingBlock({ content, isActive = false }: { content: string; isActive?: boolean }) { const [expanded, setExpanded] = useState(false) - - // Auto-scroll to bottom of content area when expanded and content is still growing const contentRef = useRef(null) + useEffect(() => { if (expanded && isActive && contentRef.current) { contentRef.current.scrollTop = contentRef.current.scrollHeight } }, [content, expanded, isActive]) - const showCursor = isActive && expanded - const inlinePreview = content.replace(/\s+/g, ' ').trim() - const collapsedText = inlinePreview.slice(0, 140) - const hasOverflow = inlinePreview.length > collapsedText.length + // Preview: take first meaningful line, not first 140 chars + const lines = content.split('\n').filter((l) => l.trim()) + const firstLine = lines[0]?.replace(/\s+/g, ' ').trim() || '' + const preview = firstLine.length > 80 ? firstLine.slice(0, 80) + '...' : firstLine return ( -
+
{expanded && ( -
-
- {content} - {showCursor && } -
+
+ {content} + {isActive && expanded && }
)}
) } -/** Shared keyframe styles injected once per ThinkingBlock mount */ const thinkingStyles = ` @keyframes thinking-cursor-blink { 0%, 100% { opacity: 1; }