From a3159bcebcf148c96745be3e672a7d63e998c287 Mon Sep 17 00:00:00 2001 From: Siddharth Ganesan Date: Tue, 8 Jul 2025 18:30:35 -0700 Subject: [PATCH] Fix streaming bug --- apps/sim/app/api/copilot/chat/route.ts | 78 ++++++++----------- .../copilot-modal/copilot-modal.tsx | 17 ++-- .../panel/components/copilot/copilot.tsx | 11 ++- 3 files changed, 53 insertions(+), 53 deletions(-) diff --git a/apps/sim/app/api/copilot/chat/route.ts b/apps/sim/app/api/copilot/chat/route.ts index aeac8af9da..c6a7d97379 100644 --- a/apps/sim/app/api/copilot/chat/route.ts +++ b/apps/sim/app/api/copilot/chat/route.ts @@ -78,6 +78,22 @@ function extractCitationsFromResponse(response: any): Array<{ title: string url: string }> { + // Handle ReadableStream responses + if (response instanceof ReadableStream) { + return [] + } + + // Handle string responses + if (typeof response === 'string') { + return [] + } + + // Handle object responses + if (typeof response !== 'object' || !response) { + return [] + } + + // Check for tool results if (!response.toolResults || !Array.isArray(response.toolResults)) { return [] } @@ -131,39 +147,28 @@ async function generateChatResponse( - Troubleshooting issues - Best practices -You have access to the Sim Studio documentation through a search tool, but use it SELECTIVELY. +You have access to the Sim Studio documentation through a search tool. Use it when users ask about Sim Studio features, tools, or functionality. WHEN TO SEARCH DOCUMENTATION: -- User asks "How do I create a workflow?" -- User asks about specific tools or blocks -- User needs help with Sim Studio features +- User asks about specific Sim Studio features or tools +- User needs help with workflows or blocks - User has technical questions about the platform +- User asks "How do I..." questions about Sim Studio -WHEN NOT TO SEARCH DOCUMENTATION: -- Simple greetings like "hi", "hello", "hey" -- General conversation like "how are you?" -- Thank you messages +WHEN NOT TO SEARCH: +- Simple greetings or casual conversation - General programming questions unrelated to Sim Studio -- Small talk or casual conversation +- Thank you messages or small talk -Guidelines: -- Be conversational and helpful -- For greetings and casual conversation, respond directly without searching -- Only use docs_search_internal when the user specifically needs information about Sim Studio features -- When you do search, synthesize the information and provide clear, actionable answers -- Be friendly and natural in your responses +CITATION FORMAT: +When you reference information from documentation sources, use this format: +- Use [1], [2], [3] etc. to cite sources +- Place citations at the end of sentences that reference specific information +- Each source should only be cited once in your response +- Continue your full response after adding citations - don't stop mid-answer -CITATION INSTRUCTIONS: -When you search documentation and reference information from the sources, use inline citations strategically and sparingly: -- Use citation markers like {cite:1}, {cite:2}, etc. to reference specific sources -- Cite each source only ONCE at the specific header or topic that relates to that source -- Place citations directly after the header or concept that the source specifically addresses -- If multiple sources support the same topic, cite them together like {cite:1}{cite:2}{cite:3} -- Do NOT repeatedly cite the same source throughout your response -- Only cite sources that you actually reference in your answer +IMPORTANT: Always provide complete, helpful responses. If you add citations, continue writing your full answer. Do not stop your response after adding a citation.` -MAKE SURE YOU FULLY ANSWER THE USER'S QUESTION. -` // Define the documentation search tool for the LLM const tools = [ { @@ -237,7 +242,6 @@ MAKE SURE YOU FULLY ANSWER THE USER'S QUESTION. }) // Store citations for later use in the main streaming handler - ;(streamResponse as any)._citations = responseCitations return streamResponse @@ -426,22 +430,12 @@ export async function POST(req: NextRequest) { } // Extract citations from response if available - const citations = - typeof response === 'object' && 'citations' in response - ? response.citations - : typeof response === 'object' && 'toolResults' in response - ? extractCitationsFromResponse(response) - : [] + const citations = extractCitationsFromResponse(response) const assistantMessage = { id: crypto.randomUUID(), role: 'assistant', - content: - typeof response === 'string' - ? response - : 'content' in response - ? response.content - : '[Error generating response]', + content: typeof response === 'string' ? response : (typeof response === 'object' && 'content' in response ? response.content : '[Error generating response]') || '[Error generating response]', timestamp: new Date().toISOString(), citations: citations.length > 0 ? citations : undefined, } @@ -470,13 +464,9 @@ export async function POST(req: NextRequest) { return NextResponse.json({ success: true, - response: - typeof response === 'string' - ? response - : 'content' in response - ? response.content - : '[Error generating response]', + response: typeof response === 'string' ? response : (typeof response === 'object' && 'content' in response ? response.content : '[Error generating response]') || '[Error generating response]', chatId: currentChat?.id, + citations: extractCitationsFromResponse(response), metadata: { requestId, message, diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/components/copilot-modal/copilot-modal.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/components/copilot-modal/copilot-modal.tsx index 4837f99330..36780049c0 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/components/copilot-modal/copilot-modal.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/components/copilot-modal/copilot-modal.tsx @@ -48,12 +48,17 @@ function ModalCopilotMessage({ message }: CopilotModalMessage) { if (!citations || citations.length === 0) return text let processedText = text - citations.forEach((citation) => { - const citationRegex = new RegExp(`\\{cite:${citation.id}\\}`, 'g') - processedText = processedText.replace( - citationRegex, - `↗` - ) + + // Replace [1], [2], [3] etc. with clickable citation icons + processedText = processedText.replace(/\[(\d+)\]/g, (match, num) => { + const citationIndex = Number.parseInt(num) - 1 + const citation = citations?.[citationIndex] + + if (citation) { + return `↗` + } + + return match }) return processedText diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/copilot.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/copilot.tsx index 28ad81d4aa..f4c19b6ef4 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/copilot.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/copilot.tsx @@ -209,10 +209,11 @@ export const Copilot = forwardRef( let accumulatedContent = '' let newChatId: string | undefined let responseCitations: Array<{ id: number; title: string; url: string }> = [] + let streamComplete = false while (true) { const { done, value } = await reader.read() - if (done) break + if (done || streamComplete) break const chunk = decoder.decode(value, { stream: true }) const lines = chunk.split('\n') @@ -276,6 +277,10 @@ export const Copilot = forwardRef( // Reload chats in background to get the updated list loadChats() } + + // Mark stream as complete to exit outer loop + streamComplete = true + break } else if (data.type === 'error') { throw new Error(data.error || 'Streaming error') } @@ -325,8 +330,8 @@ export const Copilot = forwardRef( let processedContent = content - // Replace {cite:1}, {cite:2}, etc. with clickable citation icons - processedContent = processedContent.replace(/\{cite:(\d+)\}/g, (match, num) => { + // Replace [1], [2], [3] etc. with clickable citation icons + processedContent = processedContent.replace(/\[(\d+)\]/g, (match, num) => { const citationIndex = Number.parseInt(num) - 1 const citation = citations?.[citationIndex]