diff --git a/hooks/chat/useChatCore.ts b/hooks/chat/useChatCore.ts index 79aa61c1..6757c26e 100644 --- a/hooks/chat/useChatCore.ts +++ b/hooks/chat/useChatCore.ts @@ -111,14 +111,43 @@ export function useChat({ propsId, onTitleUpdate }: { propsId?: Id<"threads">, o const thread: Thread = { id: threadId, title: 'New Chat', - messages: fetchMessages.map((message: any) => ({ - ...message, - toolInvocations: message.tool_invocations ? message.tool_invocations.map((invocation: ToolInvocation) => ({ - ...invocation, - args: typeof invocation.args === 'string' ? JSON.parse(invocation.args) : invocation.args, - result: invocation.state === 'result' ? (typeof invocation.result === 'string' ? JSON.parse(invocation.result) : invocation.result) : undefined, - })) : undefined, - })) as Message[], + messages: fetchMessages.map((message: any) => { + const toolInvocations = message.tool_invocations + ? message.tool_invocations.map((invocation: ToolInvocation) => { + let args = invocation.args; + let result = invocation.result; + + // Parse args if it's a string + if (typeof args === 'string') { + try { + args = JSON.parse(args); + } catch (e) { + console.error('Error parsing args:', e); + } + } + + // Parse result if it's a string and state is 'result' + if (invocation.state === 'result' && typeof result === 'string') { + try { + result = JSON.parse(result); + } catch (e) { + console.error('Error parsing result:', e); + } + } + + return { + ...invocation, + args, + result: invocation.state === 'result' ? result : undefined, + }; + }) + : undefined; + + return { + ...message, + toolInvocations, + }; + }) as Message[], createdAt: threadData.createdAt || new Date(), userId: user?.id as Id<"users"> || '' as Id<"users">, path: '' @@ -178,4 +207,4 @@ export function useChat({ propsId, onTitleUpdate }: { propsId?: Id<"threads">, o error, stop: vercelChatProps.stop, } -} +} \ No newline at end of file diff --git a/panes/chat/ChatMessage.tsx b/panes/chat/ChatMessage.tsx index dda52c03..fa4b5055 100644 --- a/panes/chat/ChatMessage.tsx +++ b/panes/chat/ChatMessage.tsx @@ -1,4 +1,4 @@ -import React from "react" +import React, { useMemo } from "react" import remarkGfm from "remark-gfm" import remarkMath from "remark-math" import { CodeBlock } from "@/components/ui/codeblock" @@ -14,6 +14,20 @@ export interface ChatMessageProps { } export function ChatMessage({ message, ...props }: ChatMessageProps) { + const renderedToolInvocations = useMemo(() => { + const renderedSet = new Set(); + const toolInvocations = [...(message.toolInvocations || []), ...(message.tool_invocations || [])]; + + return toolInvocations.filter(invocation => { + const key = `${invocation.toolName}-${JSON.stringify(invocation.args)}`; + if (!renderedSet.has(key)) { + renderedSet.add(key); + return true; + } + return false; + }); + }, [message.toolInvocations, message.tool_invocations]); + return (
)} - {message.toolInvocations && message.toolInvocations.map(invocation => ( - - ))} - {message.tool_invocations && message.tool_invocations.map(invocation => ( + {renderedToolInvocations.map(invocation => (
) -} +} \ No newline at end of file diff --git a/panes/chat/ToolResult.tsx b/panes/chat/ToolResult.tsx index 8bb4268c..727b0ce5 100644 --- a/panes/chat/ToolResult.tsx +++ b/panes/chat/ToolResult.tsx @@ -1,5 +1,5 @@ import { CheckCircle2, GitCompare, Loader2 } from "lucide-react" -import React, { useEffect, useState } from "react" +import React, { useEffect, useState, useRef } from "react" import { cn } from "@/lib/utils" import { FileViewer } from "./FileViewer" @@ -16,25 +16,6 @@ const truncateLines = (str: string, maxLines: number) => { return lines.slice(0, maxLines).join('\n') + '\n...'; }; -const prettyPrintJson = (obj: any): string => { - if (typeof obj === 'string') { - try { - obj = JSON.parse(obj); - } catch (e) { - // If it's not valid JSON, just return the string - return obj; - } - } - const prettyJson = JSON.stringify(obj, (key, value) => { - if (key === 'token') return '[REDACTED]'; - if (typeof value === 'string' && value.length > 50) { - return value.substring(0, 47) + '...'; - } - return value; - }, 2); - return truncateLines(prettyJson, 5); -}; - const getToolParams = (toolName: string, args: any): string => { if (typeof args === 'string') { try { @@ -51,25 +32,80 @@ export const ToolResult: React.FC = ({ toolName, args, result, const [currentState, setCurrentState] = useState(state); const [currentResult, setCurrentResult] = useState(result); const [showOldContent, setShowOldContent] = useState(false); + const [processedResult, setProcessedResult] = useState(null); + const resultRef = useRef(null); useEffect(() => { setCurrentState(state); setCurrentResult(result); + setProcessedResult(null); + resultRef.current = null; + console.log('ToolResult useEffect - state:', state, 'result:', result); }, [state, result]); const renderResult = () => { + console.log('renderResult - currentState:', currentState, 'currentResult:', currentResult); + if (currentState === 'result' && currentResult) { - if (typeof currentResult === 'object' && currentResult !== null) { - if ('summary' in currentResult) { - return currentResult.summary; + if (resultRef.current !== null) { + console.log('Returning cached result'); + return resultRef.current; + } + + let resultToRender = currentResult; + + console.log('Initial resultToRender:', resultToRender); + + // Handle rehydrated data structure (stringified JSON) + if (typeof resultToRender === 'string') { + try { + resultToRender = JSON.parse(resultToRender); + console.log('Parsed stringified result:', resultToRender); + } catch (e) { + console.log('Failed to parse stringified result, using as-is'); + } + } + + // Handle nested result structure + if (typeof resultToRender === 'object' && 'result' in resultToRender) { + resultToRender = resultToRender.result; + console.log('After handling nested result:', resultToRender); + } + + let finalResult: string; + + if (typeof resultToRender === 'string') { + console.log('Using string result:', resultToRender); + finalResult = resultToRender; + } else if (typeof resultToRender === 'object' && resultToRender !== null) { + console.log('Handling object result'); + if ('summary' in resultToRender) { + console.log('Using summary:', resultToRender.summary); + finalResult = resultToRender.summary; + } else if ('content' in resultToRender) { + console.log('Using content:', resultToRender.content); + finalResult = resultToRender.content; + } else if ('details' in resultToRender) { + console.log('Using details:', resultToRender.details); + finalResult = resultToRender.details; + } else { + console.log('Stringifying object:', resultToRender); + finalResult = JSON.stringify(resultToRender, null, 2); } + } else { + console.log('Fallback: stringifying result:', resultToRender); + finalResult = JSON.stringify(resultToRender, null, 2); } - return prettyPrintJson(currentResult); + + resultRef.current = finalResult; + return finalResult; } if (currentState === 'call') { + console.log('Returning call state message'); return `Calling ${toolName}...`; } - return prettyPrintJson(args); + console.log('Returning empty string'); + return ''; }; const renderStateIcon = () => { @@ -87,6 +123,8 @@ export const ToolResult: React.FC = ({ toolName, args, result, setShowOldContent(!showOldContent); }; + console.log('ToolResult render - toolName:', toolName, 'args:', args, 'state:', currentState); + return (
@@ -123,4 +161,4 @@ export const ToolResult: React.FC = ({ toolName, args, result,
); -}; +}; \ No newline at end of file