diff --git a/src/components/ClaudeCodeSession.tsx b/src/components/ClaudeCodeSession.tsx index c3f1010..ac0c015 100644 --- a/src/components/ClaudeCodeSession.tsx +++ b/src/components/ClaudeCodeSession.tsx @@ -285,16 +285,6 @@ export const ClaudeCodeSession: React.FC = ({ // Set up event listeners before executing console.log('[ClaudeCodeSession] Setting up event listeners...'); - // Listen for the session started event to get the Claude session ID - const sessionStartedUnlisten = await listen(`claude-session-started:*`, (event) => { - const eventName = event.event; - const sessionId = eventName.split(':')[1]; - if (sessionId && !claudeSessionId) { - console.log('[ClaudeCodeSession] Received Claude session ID:', sessionId); - setClaudeSessionId(sessionId); - } - }); - // If we already have a Claude session ID, use isolated listeners const eventSuffix = claudeSessionId ? `:${claudeSessionId}` : ''; @@ -315,14 +305,22 @@ export const ClaudeCodeSession: React.FC = ({ }); // Extract session info from system init message - if (message.type === "system" && message.subtype === "init" && message.session_id && !extractedSessionInfo) { + if (message.type === "system" && message.subtype === "init" && message.session_id) { console.log('[ClaudeCodeSession] Extracting session info from init message'); // Extract project ID from the project path const projectId = projectPath.replace(/[^a-zA-Z0-9]/g, '-'); - setExtractedSessionInfo({ - sessionId: message.session_id, - projectId: projectId - }); + + // Set both claudeSessionId and extractedSessionInfo + if (!claudeSessionId) { + setClaudeSessionId(message.session_id); + } + + if (!extractedSessionInfo) { + setExtractedSessionInfo({ + sessionId: message.session_id, + projectId: projectId + }); + } } } catch (err) { console.error("Failed to parse message:", err, event.payload); @@ -364,7 +362,7 @@ export const ClaudeCodeSession: React.FC = ({ } }); - unlistenRefs.current = [sessionStartedUnlisten, outputUnlisten, errorUnlisten, completeUnlisten]; + unlistenRefs.current = [outputUnlisten, errorUnlisten, completeUnlisten]; // Add the user message immediately to the UI (after setting up listeners) const userMessage: ClaudeStreamMessage = { diff --git a/src/components/StreamMessage.tsx b/src/components/StreamMessage.tsx index 0070268..9289ebf 100644 --- a/src/components/StreamMessage.tsx +++ b/src/components/StreamMessage.tsx @@ -33,7 +33,8 @@ import { SystemReminderWidget, SystemInitializedWidget, TaskWidget, - LSResultWidget + LSResultWidget, + ThinkingWidget } from "./ToolWidgets"; interface StreamMessageProps { @@ -73,6 +74,15 @@ const StreamMessageComponent: React.FC = ({ message, classNa if (!toolId) return null; return toolResults.get(toolId) || null; }; + + // Debug logging to understand message structure + console.log('[StreamMessage] Rendering message:', { + type: message.type, + hasMessage: !!message.message, + messageStructure: message.message ? Object.keys(message.message) : 'no message field', + fullMessage: message + }); + try { // Skip rendering for meta messages that don't have meaningful content if (message.isMeta && !message.leafUuid && !message.summary) { @@ -147,6 +157,19 @@ const StreamMessageComponent: React.FC = ({ message, classNa ); } + // Thinking content - render with ThinkingWidget + if (content.type === "thinking") { + renderedSomething = true; + return ( +
+ +
+ ); + } + // Tool use - render custom widgets based on tool name if (content.type === "tool_use") { const toolName = content.name?.toLowerCase(); @@ -258,6 +281,7 @@ const StreamMessageComponent: React.FC = ({ message, classNa return null; })} + {msg.usage && (
Tokens: {msg.usage.input_tokens} in, {msg.usage.output_tokens} out @@ -268,16 +292,18 @@ const StreamMessageComponent: React.FC = ({ message, classNa ); + if (!renderedSomething) return null; return renderedCard; } - // User message - if (message.type === "user" && message.message) { + // User message - handle both nested and direct content structures + if (message.type === "user") { // Don't render meta messages, which are for system use if (message.isMeta) return null; - const msg = message.message; + // Handle different message structures + const msg = message.message || message; let renderedSomething = false; @@ -288,9 +314,9 @@ const StreamMessageComponent: React.FC = ({ message, classNa
{/* Handle content that is a simple string (e.g. from user commands) */} - {typeof msg.content === 'string' && ( + {(typeof msg.content === 'string' || (msg.content && !Array.isArray(msg.content))) && ( (() => { - const contentStr = msg.content as string; + const contentStr = typeof msg.content === 'string' ? msg.content : String(msg.content); if (contentStr.trim() === '') return null; renderedSomething = true; @@ -316,9 +342,9 @@ const StreamMessageComponent: React.FC = ({ message, classNa // Otherwise render as plain text return ( -
+                      
{contentStr} -
+
); })() )} @@ -646,8 +672,8 @@ const StreamMessageComponent: React.FC = ({ message, classNa )}
- {message.cost_usd !== undefined && ( -
Cost: ${message.cost_usd.toFixed(4)} USD
+ {(message.cost_usd !== undefined || message.total_cost_usd !== undefined) && ( +
Cost: ${((message.cost_usd || message.total_cost_usd)!).toFixed(4)} USD
)} {message.duration_ms !== undefined && (
Duration: {(message.duration_ms / 1000).toFixed(2)}s
diff --git a/src/components/ToolWidgets.tsx b/src/components/ToolWidgets.tsx index 394427b..de9c814 100644 --- a/src/components/ToolWidgets.tsx +++ b/src/components/ToolWidgets.tsx @@ -1869,3 +1869,53 @@ export const TaskWidget: React.FC<{
); }; + +/** + * Widget for displaying AI thinking/reasoning content + * Collapsible and closed by default + */ +export const ThinkingWidget: React.FC<{ + thinking: string; + signature?: string; +}> = ({ thinking, signature }) => { + const [isExpanded, setIsExpanded] = useState(false); + + return ( +
+ + + {isExpanded && ( +
+
+
+              {thinking}
+            
+
+ + {signature && ( +
+ Signature: {signature.slice(0, 16)}... +
+ )} +
+ )} +
+ ); +};