MCP tool calling woo!
This commit is contained in:
parent
f5f1943ac5
commit
e94958865a
6 changed files with 143 additions and 22 deletions
4
examples/chat-ui/pnpm-lock.yaml
generated
4
examples/chat-ui/pnpm-lock.yaml
generated
|
|
@ -51,8 +51,8 @@ importers:
|
||||||
specifier: ^4.0.14
|
specifier: ^4.0.14
|
||||||
version: 4.1.6
|
version: 4.1.6
|
||||||
use-mcp:
|
use-mcp:
|
||||||
specifier: link:..
|
specifier: link:../..
|
||||||
version: link:..
|
version: link:../..
|
||||||
workers-ai-provider:
|
workers-ai-provider:
|
||||||
specifier: ^0.1.3
|
specifier: ^0.1.3
|
||||||
version: 0.1.3(react@19.1.0)
|
version: 0.1.3(react@19.1.0)
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import { useIndexedDB } from "../hooks/useIndexedDB";
|
||||||
import { type Model } from "../types/models";
|
import { type Model } from "../types/models";
|
||||||
import { getSelectedModel, setSelectedModel as saveSelectedModel } from "../utils/modelPreferences";
|
import { getSelectedModel, setSelectedModel as saveSelectedModel } from "../utils/modelPreferences";
|
||||||
import { type IDBPDatabase } from "idb";
|
import { type IDBPDatabase } from "idb";
|
||||||
|
import { type Tool } from "use-mcp/react";
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
||||||
interface ChatAppProps {}
|
interface ChatAppProps {}
|
||||||
|
|
@ -20,6 +21,7 @@ const ChatApp: React.FC<ChatAppProps> = () => {
|
||||||
const [sidebarVisible, setSidebarVisible] = useState<boolean>(true);
|
const [sidebarVisible, setSidebarVisible] = useState<boolean>(true);
|
||||||
const [selectedModel, setSelectedModel] = useState<Model>(getSelectedModel());
|
const [selectedModel, setSelectedModel] = useState<Model>(getSelectedModel());
|
||||||
const [apiKeyUpdateTrigger, setApiKeyUpdateTrigger] = useState<number>(0);
|
const [apiKeyUpdateTrigger, setApiKeyUpdateTrigger] = useState<number>(0);
|
||||||
|
const [mcpTools, setMcpTools] = useState<Tool[]>([]);
|
||||||
const db = useIndexedDB();
|
const db = useIndexedDB();
|
||||||
|
|
||||||
const handleApiKeyUpdate = () => {
|
const handleApiKeyUpdate = () => {
|
||||||
|
|
@ -103,18 +105,19 @@ const ChatApp: React.FC<ChatAppProps> = () => {
|
||||||
<div className="flex h-dvh w-screen overflow-clip bg-white">
|
<div className="flex h-dvh w-screen overflow-clip bg-white">
|
||||||
<div className="flex flex-row flex-grow flex-1 overflow-hidden relative">
|
<div className="flex flex-row flex-grow flex-1 overflow-hidden relative">
|
||||||
<ChatSidebar
|
<ChatSidebar
|
||||||
sidebarVisible={sidebarVisible}
|
sidebarVisible={sidebarVisible}
|
||||||
setSidebarVisible={setSidebarVisible}
|
setSidebarVisible={setSidebarVisible}
|
||||||
conversations={conversations}
|
conversations={conversations}
|
||||||
conversationId={conversationId}
|
conversationId={conversationId}
|
||||||
setConversationId={setConversationId}
|
setConversationId={setConversationId}
|
||||||
deleteConversation={deleteConversation}
|
deleteConversation={deleteConversation}
|
||||||
editConversationTitle={editConversationTitle}
|
editConversationTitle={editConversationTitle}
|
||||||
startNewConversation={startNewConversation}
|
startNewConversation={startNewConversation}
|
||||||
selectedModel={selectedModel}
|
selectedModel={selectedModel}
|
||||||
onModelChange={handleModelChange}
|
onModelChange={handleModelChange}
|
||||||
apiKeyUpdateTrigger={apiKeyUpdateTrigger}
|
apiKeyUpdateTrigger={apiKeyUpdateTrigger}
|
||||||
/>
|
onMcpToolsUpdate={setMcpTools}
|
||||||
|
/>
|
||||||
<div className="flex flex-col flex-grow h-full w-[calc(100%-16rem)] lg:w-[calc(100%-32rem)]">
|
<div className="flex flex-col flex-grow h-full w-[calc(100%-16rem)] lg:w-[calc(100%-32rem)]">
|
||||||
<ChatNavbar
|
<ChatNavbar
|
||||||
sidebarVisible={sidebarVisible}
|
sidebarVisible={sidebarVisible}
|
||||||
|
|
@ -128,6 +131,7 @@ const ChatApp: React.FC<ChatAppProps> = () => {
|
||||||
db={db as IDBPDatabase}
|
db={db as IDBPDatabase}
|
||||||
selectedModel={selectedModel}
|
selectedModel={selectedModel}
|
||||||
onApiKeyUpdate={handleApiKeyUpdate}
|
onApiKeyUpdate={handleApiKeyUpdate}
|
||||||
|
mcpTools={mcpTools}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import ModelSelector from "./ModelSelector";
|
||||||
import { McpServers } from "./McpServers";
|
import { McpServers } from "./McpServers";
|
||||||
import { type Model } from "../types/models";
|
import { type Model } from "../types/models";
|
||||||
import { type Message } from "../types";
|
import { type Message } from "../types";
|
||||||
|
import { type Tool } from "use-mcp/react";
|
||||||
|
|
||||||
interface Conversation {
|
interface Conversation {
|
||||||
id?: number;
|
id?: number;
|
||||||
|
|
@ -29,6 +30,7 @@ interface ChatSidebarProps {
|
||||||
selectedModel: Model;
|
selectedModel: Model;
|
||||||
onModelChange: (model: Model) => void;
|
onModelChange: (model: Model) => void;
|
||||||
apiKeyUpdateTrigger: number;
|
apiKeyUpdateTrigger: number;
|
||||||
|
onMcpToolsUpdate: (tools: Tool[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ChatSidebar: React.FC<ChatSidebarProps> = ({
|
const ChatSidebar: React.FC<ChatSidebarProps> = ({
|
||||||
|
|
@ -43,6 +45,7 @@ const ChatSidebar: React.FC<ChatSidebarProps> = ({
|
||||||
selectedModel,
|
selectedModel,
|
||||||
onModelChange,
|
onModelChange,
|
||||||
apiKeyUpdateTrigger,
|
apiKeyUpdateTrigger,
|
||||||
|
onMcpToolsUpdate,
|
||||||
}) => {
|
}) => {
|
||||||
const handleConversationClick = (id: number | undefined) => {
|
const handleConversationClick = (id: number | undefined) => {
|
||||||
setConversationId(id);
|
setConversationId(id);
|
||||||
|
|
@ -163,7 +166,7 @@ const ChatSidebar: React.FC<ChatSidebarProps> = ({
|
||||||
onModelChange={onModelChange}
|
onModelChange={onModelChange}
|
||||||
apiKeyUpdateTrigger={apiKeyUpdateTrigger}
|
apiKeyUpdateTrigger={apiKeyUpdateTrigger}
|
||||||
/>
|
/>
|
||||||
<McpServers />
|
<McpServers onToolsUpdate={onMcpToolsUpdate} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import '../styles/github.css'
|
||||||
import { type Conversation, type Message } from '../types'
|
import { type Conversation, type Message } from '../types'
|
||||||
import { type Model } from '../types/models'
|
import { type Model } from '../types/models'
|
||||||
import { type IDBPDatabase } from 'idb'
|
import { type IDBPDatabase } from 'idb'
|
||||||
|
import { type Tool } from 'use-mcp/react'
|
||||||
import ChatMessage from './ChatMessage'
|
import ChatMessage from './ChatMessage'
|
||||||
import ChatInput from './ChatInput'
|
import ChatInput from './ChatInput'
|
||||||
import { useAutoscroll } from '../hooks/useAutoscroll'
|
import { useAutoscroll } from '../hooks/useAutoscroll'
|
||||||
|
|
@ -20,6 +21,7 @@ interface ConversationThreadProps {
|
||||||
db: IDBPDatabase
|
db: IDBPDatabase
|
||||||
selectedModel: Model
|
selectedModel: Model
|
||||||
onApiKeyUpdate: () => void
|
onApiKeyUpdate: () => void
|
||||||
|
mcpTools: Tool[]
|
||||||
}
|
}
|
||||||
|
|
||||||
const ConversationThread: React.FC<ConversationThreadProps> = ({
|
const ConversationThread: React.FC<ConversationThreadProps> = ({
|
||||||
|
|
@ -30,6 +32,7 @@ const ConversationThread: React.FC<ConversationThreadProps> = ({
|
||||||
db,
|
db,
|
||||||
selectedModel,
|
selectedModel,
|
||||||
onApiKeyUpdate,
|
onApiKeyUpdate,
|
||||||
|
mcpTools,
|
||||||
}) => {
|
}) => {
|
||||||
const [input, setInput] = useState<string>('')
|
const [input, setInput] = useState<string>('')
|
||||||
const [apiKeyModal, setApiKeyModal] = useState<{ isOpen: boolean; model: Model | null }>({
|
const [apiKeyModal, setApiKeyModal] = useState<{ isOpen: boolean; model: Model | null }>({
|
||||||
|
|
@ -80,6 +83,7 @@ const ConversationThread: React.FC<ConversationThreadProps> = ({
|
||||||
scrollToBottom,
|
scrollToBottom,
|
||||||
selectedModel,
|
selectedModel,
|
||||||
onApiKeyRequired: handleApiKeyRequired,
|
onApiKeyRequired: handleApiKeyRequired,
|
||||||
|
mcpTools,
|
||||||
})
|
})
|
||||||
|
|
||||||
const currentConversation = conversations.find((conv) => conv.id === conversationId) || { messages: [], title: '' }
|
const currentConversation = conversations.find((conv) => conv.id === conversationId) || { messages: [], title: '' }
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
import { useState, useRef } from 'react'
|
import { useState, useRef } from 'react'
|
||||||
import { streamText } from 'ai'
|
import { streamText, tool, jsonSchema } 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 } from '../types'
|
import { type Message, type Conversation } 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'
|
||||||
|
|
||||||
interface UseStreamResponseProps {
|
interface UseStreamResponseProps {
|
||||||
conversationId?: number
|
conversationId?: number
|
||||||
|
|
@ -12,6 +13,7 @@ interface UseStreamResponseProps {
|
||||||
scrollToBottom: () => void
|
scrollToBottom: () => void
|
||||||
selectedModel: Model
|
selectedModel: Model
|
||||||
onApiKeyRequired: (model: Model) => Promise<boolean>
|
onApiKeyRequired: (model: Model) => Promise<boolean>
|
||||||
|
mcpTools: Tool[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useStreamResponse = ({
|
export const useStreamResponse = ({
|
||||||
|
|
@ -20,12 +22,52 @@ export const useStreamResponse = ({
|
||||||
scrollToBottom,
|
scrollToBottom,
|
||||||
selectedModel,
|
selectedModel,
|
||||||
onApiKeyRequired,
|
onApiKeyRequired,
|
||||||
|
mcpTools,
|
||||||
}: UseStreamResponseProps) => {
|
}: UseStreamResponseProps) => {
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
const [streamStarted, setStreamStarted] = useState(false)
|
const [streamStarted, setStreamStarted] = useState(false)
|
||||||
const [controller, setController] = useState(new AbortController())
|
const [controller, setController] = useState(new AbortController())
|
||||||
const aiResponseRef = useRef<string>('')
|
const aiResponseRef = useRef<string>('')
|
||||||
|
|
||||||
|
// Convert MCP tools to the format expected by the 'ai' package
|
||||||
|
const convertMcpToolsToAiTools = (mcpTools: Tool[]) => {
|
||||||
|
const aiTools: Record<string, any> = {}
|
||||||
|
|
||||||
|
console.log(`[useStreamResponse] Converting ${mcpTools.length} MCP tools to AI format`)
|
||||||
|
|
||||||
|
mcpTools.forEach((mcpTool) => {
|
||||||
|
const schema = mcpTool.inputSchema || { type: 'object', properties: {} }
|
||||||
|
console.log(`[useStreamResponse] Converting tool: ${mcpTool.name}`, { description: mcpTool.description, schema })
|
||||||
|
|
||||||
|
aiTools[mcpTool.name] = tool({
|
||||||
|
description: mcpTool.description,
|
||||||
|
parameters: jsonSchema(schema as any),
|
||||||
|
execute: async (args: unknown, options?: any) => {
|
||||||
|
console.log(`[useStreamResponse] Tool "${mcpTool.name}" execute called with args:`, args)
|
||||||
|
console.log(`[useStreamResponse] Tool execute options:`, options)
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Call the MCP tool using the callTool method attached to the tool
|
||||||
|
if ('callTool' in mcpTool && typeof mcpTool.callTool === 'function') {
|
||||||
|
console.log(`[useStreamResponse] Calling MCP tool "${mcpTool.name}"...`)
|
||||||
|
const result = await mcpTool.callTool(args as Record<string, unknown>)
|
||||||
|
console.log(`[useStreamResponse] Tool "${mcpTool.name}" returned result:`, result)
|
||||||
|
return result
|
||||||
|
} else {
|
||||||
|
throw new Error(`Tool ${mcpTool.name} does not have a callable implementation`)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`[useStreamResponse] Error calling MCP tool ${mcpTool.name}:`, error)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log(`[useStreamResponse] Converted tools:`, Object.keys(aiTools))
|
||||||
|
return aiTools
|
||||||
|
}
|
||||||
|
|
||||||
const getModelInstance = (model: Model, apiKey: string) => {
|
const getModelInstance = (model: Model, apiKey: string) => {
|
||||||
switch (model.provider.id) {
|
switch (model.provider.id) {
|
||||||
case 'groq': {
|
case 'groq': {
|
||||||
|
|
@ -48,7 +90,10 @@ export const useStreamResponse = ({
|
||||||
|
|
||||||
const streamResponse = async (messages: Message[]) => {
|
const streamResponse = async (messages: Message[]) => {
|
||||||
let aiResponse = ''
|
let aiResponse = ''
|
||||||
console.log({ messages })
|
console.log(`[useStreamResponse] streamResponse called with ${messages.length} messages`)
|
||||||
|
console.log(`[useStreamResponse] Messages:`, messages)
|
||||||
|
console.log(`[useStreamResponse] Current conversationId:`, conversationId)
|
||||||
|
console.log(`[useStreamResponse] MCP tools available:`, mcpTools.length)
|
||||||
|
|
||||||
// Check if API key is available
|
// Check if API key is available
|
||||||
let apiKey = getApiKey(selectedModel.provider.id)
|
let apiKey = getApiKey(selectedModel.provider.id)
|
||||||
|
|
@ -66,35 +111,88 @@ export const useStreamResponse = ({
|
||||||
try {
|
try {
|
||||||
const modelInstance = getModelInstance(selectedModel, apiKey)
|
const modelInstance = getModelInstance(selectedModel, apiKey)
|
||||||
|
|
||||||
|
console.log(`[useStreamResponse] Adding empty assistant message to conversation ${conversationId}`)
|
||||||
setConversations((prev) => {
|
setConversations((prev) => {
|
||||||
const updated = [...prev]
|
const updated = [...prev]
|
||||||
const conv = updated.find((c) => c.id === conversationId)
|
const conv = updated.find((c) => c.id === conversationId)
|
||||||
if (conv) {
|
if (conv) {
|
||||||
console.log('POPPING')
|
console.log(`[useStreamResponse] Found conversation, adding assistant message. Current message count: ${conv.messages.length}`)
|
||||||
conv.messages.push({ role: 'assistant', content: '' })
|
conv.messages.push({ role: 'assistant', content: '' })
|
||||||
|
console.log(`[useStreamResponse] After adding assistant message, message count: ${conv.messages.length}`)
|
||||||
|
} else {
|
||||||
|
console.warn(`[useStreamResponse] Could not find conversation with id: ${conversationId}`)
|
||||||
}
|
}
|
||||||
return updated
|
return updated
|
||||||
})
|
})
|
||||||
|
console.log(`[useStreamResponse] Setting stream started to true`)
|
||||||
setStreamStarted(true)
|
setStreamStarted(true)
|
||||||
|
|
||||||
|
// Convert MCP tools to AI package format
|
||||||
|
const aiTools = convertMcpToolsToAiTools(mcpTools)
|
||||||
|
|
||||||
|
console.log(`[useStreamResponse] Starting streamText with ${Object.keys(aiTools).length} tools available`)
|
||||||
|
console.log(`[useStreamResponse] Messages:`, messages.map(m => ({ role: m.role, content: m.content.substring(0, 100) + '...' })))
|
||||||
|
|
||||||
const result = await streamText({
|
const result = await streamText({
|
||||||
model: modelInstance,
|
model: modelInstance,
|
||||||
messages: messages.map((msg) => ({
|
messages: messages.map((msg) => ({
|
||||||
role: msg.role,
|
role: msg.role,
|
||||||
content: msg.content,
|
content: msg.content,
|
||||||
})),
|
})),
|
||||||
|
tools: Object.keys(aiTools).length > 0 ? aiTools : undefined,
|
||||||
|
maxSteps: 5, // Allow up to 5 steps for tool calling
|
||||||
|
onStepFinish({ text, toolCalls, toolResults, finishReason, usage }) {
|
||||||
|
console.log(`[useStreamResponse] Step finished:`, {
|
||||||
|
text: text ? text.substring(0, 100) + '...' : 'no text',
|
||||||
|
toolCallsCount: toolCalls?.length || 0,
|
||||||
|
toolResultsCount: toolResults?.length || 0,
|
||||||
|
finishReason,
|
||||||
|
usage
|
||||||
|
})
|
||||||
|
if (toolCalls && toolCalls.length > 0) {
|
||||||
|
console.log(`[useStreamResponse] Tool calls in step:`, toolCalls)
|
||||||
|
}
|
||||||
|
if (toolResults && toolResults.length > 0) {
|
||||||
|
console.log(`[useStreamResponse] Tool results in step:`, toolResults)
|
||||||
|
}
|
||||||
|
},
|
||||||
abortSignal: controller.signal,
|
abortSignal: controller.signal,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
console.log(`[useStreamResponse] streamText result obtained, starting to process stream`)
|
||||||
|
console.log(`[useStreamResponse] Result object:`, {
|
||||||
|
hasTextStream: !!result.textStream,
|
||||||
|
hasToolCalls: !!result.toolCalls,
|
||||||
|
hasToolResults: !!result.toolResults,
|
||||||
|
hasFullStream: !!result.fullStream
|
||||||
|
})
|
||||||
|
|
||||||
|
// Log tool calls if they exist
|
||||||
|
if (result.toolCalls) {
|
||||||
|
console.log(`[useStreamResponse] Tool calls detected:`, result.toolCalls)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log tool results if they exist
|
||||||
|
if (result.toolResults) {
|
||||||
|
console.log(`[useStreamResponse] Tool results detected:`, result.toolResults)
|
||||||
|
}
|
||||||
|
|
||||||
|
let chunkCount = 0
|
||||||
for await (const chunk of result.textStream) {
|
for await (const chunk of result.textStream) {
|
||||||
try {
|
try {
|
||||||
|
chunkCount++
|
||||||
|
console.log(`[useStreamResponse] Processing chunk ${chunkCount}:`, JSON.stringify(chunk))
|
||||||
|
|
||||||
aiResponse += chunk
|
aiResponse += chunk
|
||||||
aiResponseRef.current = aiResponse
|
aiResponseRef.current = aiResponse
|
||||||
|
|
||||||
|
console.log(`[useStreamResponse] Updated aiResponse (length: ${aiResponse.length}):`, aiResponse.substring(0, 200) + '...')
|
||||||
|
|
||||||
//custom extraction of <chat-title> tag
|
//custom extraction of <chat-title> tag
|
||||||
const titleMatch = aiResponse.match(/<chat-title>(.*?)<\/chat-title>/)
|
const titleMatch = aiResponse.match(/<chat-title>(.*?)<\/chat-title>/)
|
||||||
if (titleMatch) {
|
if (titleMatch) {
|
||||||
const title = titleMatch[1].trim()
|
const title = titleMatch[1].trim()
|
||||||
|
console.log(`[useStreamResponse] Extracted title: ${title}`)
|
||||||
setConversations((prev) => {
|
setConversations((prev) => {
|
||||||
const updated = [...prev]
|
const updated = [...prev]
|
||||||
const conv = updated.find((c) => c.id === conversationId)
|
const conv = updated.find((c) => c.id === conversationId)
|
||||||
|
|
@ -110,32 +208,44 @@ export const useStreamResponse = ({
|
||||||
const updated = [...prev]
|
const updated = [...prev]
|
||||||
const conv = updated.find((c) => c.id === conversationId)
|
const conv = updated.find((c) => c.id === conversationId)
|
||||||
if (conv) {
|
if (conv) {
|
||||||
|
console.log(`[useStreamResponse] Updating conversation message content (length: ${aiResponse.length})`)
|
||||||
conv.messages[conv.messages.length - 1].content = aiResponse
|
conv.messages[conv.messages.length - 1].content = aiResponse
|
||||||
|
} else {
|
||||||
|
console.warn(`[useStreamResponse] Could not find conversation with id: ${conversationId}`)
|
||||||
}
|
}
|
||||||
return updated
|
return updated
|
||||||
})
|
})
|
||||||
|
|
||||||
scrollToBottom()
|
scrollToBottom()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log('Error in text chunk', e)
|
console.error('[useStreamResponse] Error in text chunk processing:', e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log(`[useStreamResponse] Finished processing ${chunkCount} chunks. Final response length: ${aiResponse.length}`)
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
if (controller.signal.aborted) {
|
if (controller.signal.aborted) {
|
||||||
console.log('Stream aborted')
|
console.log('[useStreamResponse] Stream aborted')
|
||||||
} else {
|
} else {
|
||||||
console.error('Error generating response:', error)
|
console.error('[useStreamResponse] Error generating response:', error)
|
||||||
|
// Log additional error details
|
||||||
|
if (error instanceof Error) {
|
||||||
|
console.error('[useStreamResponse] Error stack:', error.stack)
|
||||||
|
console.error('[useStreamResponse] Error message:', error.message)
|
||||||
|
}
|
||||||
// Remove the assistant message if there was an error
|
// Remove the assistant message if there was an error
|
||||||
setConversations((prev) => {
|
setConversations((prev) => {
|
||||||
const updated = [...prev]
|
const updated = [...prev]
|
||||||
const conv = updated.find((c) => c.id === conversationId)
|
const conv = updated.find((c) => c.id === conversationId)
|
||||||
if (conv && conv.messages[conv.messages.length - 1].role === 'assistant') {
|
if (conv && conv.messages[conv.messages.length - 1].role === 'assistant') {
|
||||||
|
console.log('[useStreamResponse] Removing assistant message due to error')
|
||||||
conv.messages.pop()
|
conv.messages.pop()
|
||||||
}
|
}
|
||||||
return updated
|
return updated
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
|
console.log('[useStreamResponse] Cleaning up stream state')
|
||||||
setStreamStarted(false)
|
setStreamStarted(false)
|
||||||
setController(new AbortController())
|
setController(new AbortController())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue