wip, changing the logic in useStreamResponse

This commit is contained in:
Glen Maddern 2025-06-25 17:06:50 +10:00
commit 022747ba04
3 changed files with 35 additions and 63 deletions

View file

@ -115,10 +115,6 @@ export const useStreamResponse = ({
const streamResponse = async (messages: Message[]) => {
let aiResponse = ''
let currentReasoning = ''
let currentReasoningStartTime: number | undefined = undefined
let currentReasoningEndTime: number | undefined = undefined
let assistantMessageIndex = -1 // Track the index of our assistant message
let assistantMessageCreated = false // Track if we've created the assistant message yet
// Check if API key is available
let apiKey = getApiKey(selectedModel.provider.id)
@ -165,51 +161,38 @@ export const useStreamResponse = ({
console.log(JSON.stringify(event))
try {
if (event.type === 'reasoning') {
// Track reasoning start time and create assistant message if needed
if (!currentReasoningStartTime) {
currentReasoningStartTime = Date.now()
currentReasoning = '' // Reset reasoning content for new session
setConversations((prev) => {
const updated = [...prev]
const conv = updated.find((c) => c.id === conversationId)
if (!conv) {
console.error(`Missing conversation for ID ${conversationId}! Ignoring ${JSON.stringify(event)}`)
return updated
}
const lastMessage = conv.messages?.at(-1)
// Create a new assistant message for reasoning (even if we already have one)
// This handles multiple reasoning sessions in the same conversation turn
setConversations((prev) => {
const updated = [...prev]
const conv = updated.find((c) => c.id === conversationId)
if (lastMessage?.role === 'assistant' && lastMessage.type === 'reasoning') {
// We have an existing reasoning block!
lastMessage.content = lastMessage.content + event.textDelta.trim()
} else {
// We need a new reasoning block!
const currentReasoningStartTime = Date.now()
// Create a new assistant message for reasoning (even if we already have one)
// This handles multiple reasoning sessions in the same conversation turn
if (conv) {
conv.messages.push({
role: 'assistant',
content: '',
reasoning: '',
type: 'reasoning',
content: event.textDelta.trim(),
isReasoningStreaming: true,
reasoningStartTime: currentReasoningStartTime,
reasoningStartTime: currentReasoningStartTime!,
})
assistantMessageIndex = conv.messages.length - 1
debugMessages('Added new reasoning assistant message at index', assistantMessageIndex)
debugMessages('Added new reasoning assistant message at index', conv.messages.length - 1)
debugMessages('Current messages:', JSON.stringify(conv.messages))
}
return updated
})
assistantMessageCreated = true
}
currentReasoning += (event as any).textDelta || ''
// Update the assistant message with streaming reasoning in real-time
if (assistantMessageCreated) {
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.reasoning = currentReasoning.trim()
assistantMessage.isReasoningStreaming = true
assistantMessage.reasoningStartTime = currentReasoningStartTime
}
}
return updated
})
}
}
return updated
})
} else if (event.type === 'tool-call') {
// End reasoning phase immediately when we see a tool call for linear flow
if (currentReasoningStartTime && !currentReasoningEndTime) {
@ -345,21 +328,6 @@ export const useStreamResponse = ({
aiResponse = aiResponse.replace(/<chat-title>.*?<\/chat-title>/, '').trim()
}
// Remove thinking tags from the display text (they should be extracted by middleware)
let cleanedResponse = aiResponse
if (supportsReasoning(selectedModel)) {
// Remove complete thinking blocks
cleanedResponse = aiResponse.replace(/<think>[\s\S]*?<\/think>/g, '').trim()
// Remove unclosed think tags (everything from <think> to end)
cleanedResponse = cleanedResponse.replace(/<think>[\s\S]*$/g, '').trim()
// Remove any remaining opening think tags
cleanedResponse = cleanedResponse.replace(/<think>/g, '').trim()
// Remove any remaining closing think tags
cleanedResponse = cleanedResponse.replace(/<\/think>/g, '').trim()
// Clean up any remaining whitespace or newlines at the start
cleanedResponse = cleanedResponse.replace(/^\s+/, '')
}
setConversations((prev) => {
const updated = [...prev]
const conv = updated.find((c) => c.id === conversationId)
@ -367,8 +335,7 @@ export const useStreamResponse = ({
// 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 = cleanedResponse
} else {
assistantMessage.content = aiResponse
}
} else {
}

View file

@ -12,10 +12,15 @@ export interface SystemMessage extends BaseMessage {
export interface AssistantMessage extends BaseMessage {
role: 'assistant'
reasoning?: string
reasoningStartTime?: number
type: 'content'
}
export interface AssistantReasoningMessage extends BaseMessage {
role: 'assistant'
type: 'reasoning'
reasoningStartTime: number
reasoningEndTime?: number
isReasoningStreaming?: boolean
isReasoningStreaming: boolean
}
export interface ToolCallMessage {
@ -33,7 +38,7 @@ export interface ToolResultMessage {
callId: string
}
export type Message = UserMessage | SystemMessage | AssistantMessage | ToolCallMessage | ToolResultMessage
export type Message = UserMessage | SystemMessage | AssistantMessage | AssistantReasoningMessage | ToolCallMessage | ToolResultMessage
export interface Conversation {
id?: number

View file

@ -3,7 +3,7 @@
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,