From 5e41222c630424990d377a718b724853f9857752 Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Wed, 25 Jun 2025 20:20:14 +1000 Subject: [PATCH] Handling reasoning then messages at least --- .../src/components/messages/ChatMessage.tsx | 2 +- .../components/messages/ReasoningBlock.tsx | 72 +----------- .../components/messages/ReasoningMessage.tsx | 78 +++++++++++-- .../chat-ui/src/hooks/useStreamResponse.ts | 103 +++--------------- 4 files changed, 87 insertions(+), 168 deletions(-) diff --git a/examples/chat-ui/src/components/messages/ChatMessage.tsx b/examples/chat-ui/src/components/messages/ChatMessage.tsx index cc459ec..9eb0d5d 100644 --- a/examples/chat-ui/src/components/messages/ChatMessage.tsx +++ b/examples/chat-ui/src/components/messages/ChatMessage.tsx @@ -33,7 +33,7 @@ const ChatMessage: React.FC = ({ message }) => { // Handle regular messages (user, assistant, system) return (
-
+
= ({ reasoning, isStreaming = false, startTime, endTime }) => { - const [isExpanded, setIsExpanded] = useState(isStreaming) // Start expanded when streaming - - // Auto-collapse instantly when streaming finishes - useEffect(() => { - if (!isStreaming && startTime && endTime) { - setIsExpanded(false) - } - }, [isStreaming, startTime, endTime]) - - if (!reasoning || reasoning.trim().length === 0) { - return null - } - - const toggleExpanded = () => { - setIsExpanded(!isExpanded) - } - - // Calculate thinking duration - const duration = startTime && endTime ? (endTime - startTime) / 1000 : null - - // Format duration nicely - const formatDuration = (seconds: number) => { - if (seconds < 1) { - return `${Math.round(seconds * 1000)}ms` - } - return `${seconds.toFixed(1)}s` - } - - return ( -
-
-
- 💭 - - {isExpanded ? ( - // Expanded view: show reasoning content -
- - {reasoning} - {isStreaming && } - -
- ) : ( - // Collapsed view: show timing summary -
- - {isStreaming ? 'Thinking...' : duration ? `Thought for ${formatDuration(duration)}` : 'Thought process'} - -
- )} - - -
-
-
- ) -} +const ReasoningBlock: React.FC = ({ reasoning, isStreaming = false, startTime, endTime }) => {} export default ReasoningBlock diff --git a/examples/chat-ui/src/components/messages/ReasoningMessage.tsx b/examples/chat-ui/src/components/messages/ReasoningMessage.tsx index 7dae8d0..4d3db1e 100644 --- a/examples/chat-ui/src/components/messages/ReasoningMessage.tsx +++ b/examples/chat-ui/src/components/messages/ReasoningMessage.tsx @@ -1,22 +1,82 @@ -import React from 'react' +import React, { useEffect, useState } from 'react' import { type ReasoningMessage } from '../../types' -import ReasoningBlock from './ReasoningBlock.tsx' interface ReasoningMessageProps { message: ReasoningMessage } const ReasoningMessage: React.FC = ({ message }) => { - console.log({ ReasoningMessage: message }) + const [isExpanded, setIsExpanded] = useState(message.isReasoningStreaming) // Start expanded when streaming + + // Auto-collapse instantly when streaming finishes + useEffect(() => { + if (!message.isReasoningStreaming && message.reasoningStartTime && message.reasoningEndTime) { + setIsExpanded(false) + } + }, [message.isReasoningStreaming, message.reasoningStartTime, message.reasoningEndTime]) + + if (!message.content || message.content.trim().length === 0) { + return null + } + + const toggleExpanded = () => { + setIsExpanded(!isExpanded) + } + + // Calculate thinking duration + const duration = + message.reasoningStartTime && message.reasoningEndTime ? (message.reasoningEndTime - message.reasoningStartTime) / 1000 : null + + // Format duration nicely + const formatDuration = (seconds: number) => { + if (seconds < 1) { + return `${Math.round(seconds * 1000)}ms` + } + return `${seconds.toFixed(1)}s` + } + return (
- +
+
+ 💭 + + {isExpanded ? ( + // Expanded view: show reasoning content +
+ + {message.content} + {message.isReasoningStreaming && } + +
+ ) : ( + // Collapsed view: show timing summary +
+ + {message.isReasoningStreaming ? 'Thinking...' : duration ? `Thought for ${formatDuration(duration)}` : 'Thought process'} + +
+ )} + + +
+
) diff --git a/examples/chat-ui/src/hooks/useStreamResponse.ts b/examples/chat-ui/src/hooks/useStreamResponse.ts index c1b70a4..9bf85d8 100644 --- a/examples/chat-ui/src/hooks/useStreamResponse.ts +++ b/examples/chat-ui/src/hooks/useStreamResponse.ts @@ -172,7 +172,7 @@ export const useStreamResponse = ({ // We have an existing reasoning block! updatedMessage = { ...lastMessage, - content: lastMessage.content + event.textDelta.trim(), + content: lastMessage.content + event.textDelta, } } else { // We need a new reasoning block! @@ -183,7 +183,7 @@ export const useStreamResponse = ({ newMessage = { role: 'assistant', type: 'reasoning', - content: event.textDelta.trim(), + content: event.textDelta, isReasoningStreaming: true, reasoningStartTime: currentReasoningStartTime!, } @@ -234,90 +234,19 @@ export const useStreamResponse = ({ // console.warn('Tool result event missing toolName or toolCallId:', event) // } } else if (event.type === 'text-delta') { - console.info(`TODO TEXT DELTA`, event) - - // // Check if we have an existing reasoning message that needs to be finalized - // let needsNewAssistantMessage = false - // if (currentReasoningStartTime && !currentReasoningEndTime) { - // currentReasoningEndTime = Date.now() - // - // // Update reasoning end time and stop streaming - // setConversations((prev) => { - // const updated = [...prev] - // const conv = updated.find((c) => c.id === conversationId) - // if (conv && assistantMessageIndex >= 0) { - // const assistantMessage = conv.messages[assistantMessageIndex] - // if (assistantMessage && hasContent(assistantMessage) && assistantMessage.role === 'assistant') { - // assistantMessage.isReasoningStreaming = false - // assistantMessage.reasoningEndTime = currentReasoningEndTime - // } - // } - // return updated - // }) - // - // // Reset reasoning state after reasoning ends - // currentReasoningStartTime = undefined - // currentReasoningEndTime = undefined - // // Force creation of new assistant message for content - // needsNewAssistantMessage = true - // assistantMessageCreated = false - // } - // - // // Create assistant message on first text delta if not already created OR if we just finished reasoning - // if (!assistantMessageCreated || needsNewAssistantMessage) { - // setConversations((prev) => { - // const updated = [...prev] - // const conv = updated.find((c) => c.id === conversationId) - // if (conv) { - // conv.messages.push({ role: 'assistant', content: '' }) - // assistantMessageIndex = conv.messages.length - 1 - // debugMessages( - // 'Added new content assistant message at index', - // assistantMessageIndex, - // '(after reasoning:', - // needsNewAssistantMessage, - // ')' - // ) - // debugMessages('Current messages:', JSON.stringify(conv.messages)) - // } - // return updated - // }) - // assistantMessageCreated = true - // } - // - // aiResponse += event.textDelta - // aiResponseRef.current = aiResponse - // - // //custom extraction of tag - // const titleMatch = aiResponse.match(/(.*?)<\/chat-title>/) - // if (titleMatch) { - // const title = titleMatch[1].trim() - // setConversations((prev) => { - // const updated = [...prev] - // const conv = updated.find((c) => c.id === conversationId) - // if (conv) { - // conv.title = title - // } - // return updated - // }) - // aiResponse = aiResponse.replace(/.*?<\/chat-title>/, '').trim() - // } - // - // setConversations((prev) => { - // const updated = [...prev] - // const conv = updated.find((c) => c.id === conversationId) - // if (conv) { - // // Update the specific assistant message we created, not just the last message - // const assistantMessage = conv.messages[assistantMessageIndex] - // if (assistantMessage && hasContent(assistantMessage) && assistantMessage.role === 'assistant') { - // assistantMessage.content = aiResponse - // } - // } else { - // } - // return updated - // }) - // - // scrollToBottom(true) + if (lastMessage?.role !== 'assistant' || lastMessage.type !== 'content') { + newMessage = { + role: 'assistant', + content: event.textDelta, + type: 'content', + } + } else { + updatedMessage = { + ...lastMessage, + content: lastMessage.content + event.textDelta, + } + } + scrollToBottom(true) } } } catch (e) { @@ -327,7 +256,7 @@ export const useStreamResponse = ({ if (updatedMessage || newMessage) { updated = prev.map((c) => { if (c.id !== conversationId) return c - let messages = [...c.messages] + const messages = [...c.messages] if (updatedMessage) { messages.splice(-1, 1, updatedMessage)