Ok _much_ better timing and just me understanding this stuff

This commit is contained in:
Glen Maddern 2025-06-25 21:52:21 +10:00
commit f460842bbc
2 changed files with 39 additions and 61 deletions

View file

@ -29,9 +29,8 @@ const ReasoningMessage: React.FC<ReasoningMessageProps> = ({ message }) => {
// Format duration nicely // Format duration nicely
const formatDuration = (seconds: number) => { const formatDuration = (seconds: number) => {
if (seconds < 1) { if (seconds < 0.001) return `< 1ms`
return `${Math.round(seconds * 1000)}ms` if (seconds < 1) return `${Math.round(seconds * 1000)}ms`
}
return `${seconds.toFixed(1)}s` return `${seconds.toFixed(1)}s`
} }
@ -39,8 +38,8 @@ const ReasoningMessage: React.FC<ReasoningMessageProps> = ({ message }) => {
<div className={`flex justify-start`}> <div className={`flex justify-start`}>
<div className={`text-zinc-900 w-full`}> <div className={`text-zinc-900 w-full`}>
<div <div
className={`text-xs/5 text-zinc-600 border opacity-80 hover:opacity-100 border-zinc-200 rounded-lg p-3 bg-zinc-50 cursor-pointer hover:bg-zinc-100 ${ className={`text-xs/5 text-zinc-600 border border-zinc-200 rounded-lg p-3 bg-zinc-50 cursor-pointer hover:bg-zinc-100 ${
isExpanded ? 'border-zinc-300 opacity-100' : '' isExpanded ? 'border-zinc-300' : ''
}`} }`}
onClick={toggleExpanded} onClick={toggleExpanded}
> >
@ -61,7 +60,11 @@ const ReasoningMessage: React.FC<ReasoningMessageProps> = ({ message }) => {
// Collapsed view: show timing summary // Collapsed view: show timing summary
<div className="flex-1"> <div className="flex-1">
<span className="text-zinc-500"> <span className="text-zinc-500">
{message.isReasoningStreaming ? 'Thinking...' : duration ? `Thought for ${formatDuration(duration)}` : 'Thought process'} {message.isReasoningStreaming
? 'Thinking...'
: typeof duration === 'number'
? `Thought for ${formatDuration(duration)}`
: 'Thought process'}
</span> </span>
</div> </div>
)} )}

View file

@ -1,20 +1,13 @@
import { useState, useRef } from 'react' import { useRef, useState } from 'react'
import { streamText, tool, jsonSchema } from 'ai' import { CoreMessage, jsonSchema, streamText, tool } from 'ai'
import { createGroq } from '@ai-sdk/groq' import { createGroq } from '@ai-sdk/groq'
import { createAnthropic } from '@ai-sdk/anthropic' import { createAnthropic } from '@ai-sdk/anthropic'
import { type Message, type Conversation, type UserMessage, type AssistantMessage, type SystemMessage } from '../types' import { type AssistantMessage, type Conversation, type Message, type SystemMessage, type UserMessage } from '../types'
import { type Model } from '../types/models' import { type Model } from '../types/models'
import { getApiKey } from '../utils/apiKeys' import { getApiKey } from '../utils/apiKeys'
import { type Tool } from 'use-mcp/react' import { type Tool } from 'use-mcp/react'
import { useConversationUpdater } from './useConversationUpdater' import { useConversationUpdater } from './useConversationUpdater'
// Debug logging for message operations
const debugMessages = (...args: any[]) => {
if (typeof window !== 'undefined' && localStorage.getItem('USE_MCP_DEBUG') === 'true') {
console.log('[Messages]', ...args)
}
}
// Type guard for messages with content // Type guard for messages with content
const hasContent = (message: Message): message is UserMessage | AssistantMessage | SystemMessage => { const hasContent = (message: Message): message is UserMessage | AssistantMessage | SystemMessage => {
return 'content' in message return 'content' in message
@ -44,7 +37,7 @@ export const useStreamResponse = ({
const { updateConversation } = useConversationUpdater({ const { updateConversation } = useConversationUpdater({
conversationId, conversationId,
setConversations setConversations,
}) })
// Convert MCP tools to the format expected by the 'ai' package // Convert MCP tools to the format expected by the 'ai' package
@ -140,18 +133,20 @@ export const useStreamResponse = ({
// Convert MCP tools to AI package format // Convert MCP tools to AI package format
const aiTools = convertMcpToolsToAiTools(mcpTools) const aiTools = convertMcpToolsToAiTools(mcpTools)
console.log({ messages })
const messagesToSend: CoreMessage[] = messages
.filter((msg) => ['user', 'assistant', 'system'].includes(msg.role) && hasContent(msg))
.map((msg) => {
const contentMsg = msg as UserMessage | AssistantMessage | SystemMessage
return {
role: contentMsg.role,
content: contentMsg.content,
}
})
// Prepare stream options // Prepare stream options
const streamOptions = { const streamOptions = {
model: modelInstance, model: modelInstance,
messages: messages messages: messagesToSend,
.filter((msg) => ['user', 'assistant', 'system'].includes(msg.role) && hasContent(msg))
.map((msg) => {
const contentMsg = msg as UserMessage | AssistantMessage | SystemMessage
return {
role: contentMsg.role,
content: contentMsg.content,
}
}),
tools: Object.keys(aiTools).length > 0 ? aiTools : undefined, tools: Object.keys(aiTools).length > 0 ? aiTools : undefined,
maxSteps: 5, // Allow up to 5 steps for tool calling maxSteps: 5, // Allow up to 5 steps for tool calling
abortSignal: controller.signal, abortSignal: controller.signal,
@ -161,10 +156,11 @@ export const useStreamResponse = ({
// Use fullStream to get all events including tool calls, results, and text // Use fullStream to get all events including tool calls, results, and text
for await (const event of result.fullStream) { for await (const event of result.fullStream) {
console.log({ 'result.fullStream.event': event }) const eventTime = Date.now()
// console.log({ 'result.fullStream.event': event })
updateConversation((conv) => { updateConversation((conv) => {
const lastMessage = conv.messages?.at(-1) const lastMessage = conv.messages?.at(-1)
console.log({ setConversations: event, conv: JSON.parse(JSON.stringify(conv)) }) // console.log({ setConversations: event, conv: JSON.parse(JSON.stringify(conv)) })
let updatedMessage: Message | undefined let updatedMessage: Message | undefined
let newMessage: Message | undefined let newMessage: Message | undefined
try { try {
@ -177,7 +173,7 @@ export const useStreamResponse = ({
} }
} else { } else {
// We need a new reasoning block! // We need a new reasoning block!
const currentReasoningStartTime = Date.now() const currentReasoningStartTime = eventTime
// Create a new assistant message for reasoning (even if we already have one) // Create a new assistant message for reasoning (even if we already have one)
// This handles multiple reasoning sessions in the same conversation turn // This handles multiple reasoning sessions in the same conversation turn
@ -194,9 +190,15 @@ export const useStreamResponse = ({
if (lastMessage?.role === 'assistant' && lastMessage.type === 'reasoning') { if (lastMessage?.role === 'assistant' && lastMessage.type === 'reasoning') {
updatedMessage = { updatedMessage = {
...lastMessage, ...lastMessage,
reasoningEndTime: Date.now(), reasoningEndTime: eventTime,
isReasoningStreaming: false, isReasoningStreaming: false,
} }
// Try hacking the tool call loop to include reasoning blocks
messagesToSend.push({
role: 'assistant',
content: `<think>${lastMessage.content}</think>`,
})
} }
if (event.type === 'tool-call') { if (event.type === 'tool-call') {
@ -255,39 +257,13 @@ export const useStreamResponse = ({
messages.push(newMessage) messages.push(newMessage)
} }
console.log({ updatedMessage, newMessage }) // console.log({ updatedMessage, newMessage })
return { ...conv, messages } return { ...conv, messages }
} }
return conv return conv
}) })
} }
// Show final conversation state
updateConversation((conv) => {
debugMessages('Final conversation state:', JSON.stringify(conv.messages))
return conv
})
//
// // Final cleanup - ensure reasoning streaming is stopped
// if (assistantMessageCreated && currentReasoningStartTime) {
// if (!currentReasoningEndTime) {
// currentReasoningEndTime = Date.now()
// }
//
// 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
// })
// }
} catch (error: unknown) { } catch (error: unknown) {
if (controller.signal.aborted) { if (controller.signal.aborted) {
} else { } else {
@ -296,7 +272,6 @@ export const useStreamResponse = ({
} finally { } finally {
setStreamStarted(false) setStreamStarted(false)
setController(new AbortController()) setController(new AbortController())
debugMessages('Stream response function completed')
} }
} }