From 0c66b05706235ec1b39b0594853fc63fba080cca Mon Sep 17 00:00:00 2001 From: Soumyadas15 Date: Thu, 3 Jul 2025 09:41:47 +0530 Subject: [PATCH] fix: fix tool call views in share page --- frontend/src/app/share/[threadId]/page.tsx | 81 +++------------------- 1 file changed, 11 insertions(+), 70 deletions(-) diff --git a/frontend/src/app/share/[threadId]/page.tsx b/frontend/src/app/share/[threadId]/page.tsx index 677c87c4..ae2fd0da 100644 --- a/frontend/src/app/share/[threadId]/page.tsx +++ b/frontend/src/app/share/[threadId]/page.tsx @@ -31,8 +31,8 @@ import { useAgentStream } from '@/hooks/useAgentStream'; import { threadErrorCodeMessages } from '@/lib/constants/errorCodeMessages'; import { ThreadSkeleton } from '@/components/thread/content/ThreadSkeleton'; import { useAgent } from '@/hooks/react-query/agents/use-agents'; +import { extractToolName } from '@/components/thread/tool-views/xml-parser'; -// Extend the base Message type with the expected database fields interface ApiMessageType extends BaseApiMessageType { message_id?: string; thread_id?: string; @@ -48,7 +48,6 @@ interface ApiMessageType extends BaseApiMessageType { }; } -// Add a simple interface for streaming tool calls interface StreamingToolCall { id?: string; name?: string; @@ -78,7 +77,6 @@ export default function ThreadPage({ const [currentToolIndex, setCurrentToolIndex] = useState(0); const [autoOpenedPanel, setAutoOpenedPanel] = useState(false); - // Playback control states const [isPlaying, setIsPlaying] = useState(false); const [currentMessageIndex, setCurrentMessageIndex] = useState(0); const [streamingText, setStreamingText] = useState(''); @@ -194,7 +192,7 @@ export default function ThreadPage({ (toolCall: StreamingToolCall | null) => { if (!toolCall) return; - // Normalize the tool name by replacing underscores with hyphens + // Normalize the tool name like the project thread page does const rawToolName = toolCall.name || toolCall.xml_tag_name || 'Unknown Tool'; const toolName = rawToolName.replace(/_/g, '-').toLowerCase(); @@ -210,6 +208,7 @@ export default function ThreadPage({ // Format the arguments in a way that matches the expected XML format for each tool // This ensures the specialized tool views render correctly let formattedContent = toolArguments; + if ( toolName.includes('command') && !toolArguments.includes('') @@ -432,17 +431,10 @@ export default function ThreadPage({ return assistantMsg.content; } })(); - - // Try to extract tool name from content - const xmlMatch = assistantContent.match( - /<([a-zA-Z\-_]+)(?:\s+[^>]*)?>|<([a-zA-Z\-_]+)(?:\s+[^>]*)?\/>/, - ); - if (xmlMatch) { - // Normalize tool name: replace underscores with hyphens and lowercase - const rawToolName = xmlMatch[1] || xmlMatch[2] || 'unknown'; - toolName = rawToolName.replace(/_/g, '-').toLowerCase(); + const extractedToolName = extractToolName(assistantContent); + if (extractedToolName) { + toolName = extractedToolName; } else { - // Fallback to checking for tool_calls JSON structure const assistantContentParsed = safeJsonParse<{ tool_calls?: Array<{ function?: { name?: string }; name?: string }>; }>(assistantMsg.content, {}); @@ -452,7 +444,6 @@ export default function ThreadPage({ ) { const firstToolCall = assistantContentParsed.tool_calls[0]; const rawName = firstToolCall.function?.name || firstToolCall.name || 'unknown'; - // Normalize tool name here too toolName = rawName.replace(/_/g, '-').toLowerCase(); } } @@ -460,7 +451,6 @@ export default function ThreadPage({ let isSuccess = true; try { - // Parse tool result content const toolResultContent = (() => { try { const parsed = safeJsonParse<{ content?: string }>(resultMessage.content, {}); @@ -469,15 +459,11 @@ export default function ThreadPage({ return resultMessage.content; } })(); - - // Check for ToolResult pattern first if (toolResultContent && typeof toolResultContent === 'string') { - // Look for ToolResult(success=True/False) pattern const toolResultMatch = toolResultContent.match(/ToolResult\s*\(\s*success\s*=\s*(True|False|true|false)/i); if (toolResultMatch) { isSuccess = toolResultMatch[1].toLowerCase() === 'true'; } else { - // Fallback: only check for error keywords if no ToolResult pattern found const toolContent = toolResultContent.toLowerCase(); isSuccess = !(toolContent.includes('failed') || toolContent.includes('error') || @@ -489,19 +475,17 @@ export default function ThreadPage({ historicalToolPairs.push({ assistantCall: { name: toolName, - content: assistantMsg.content, // Store original content + content: assistantMsg.content, timestamp: assistantMsg.created_at, }, toolResult: { - content: resultMessage.content, // Store original content + content: resultMessage.content, isSuccess: isSuccess, timestamp: resultMessage.created_at, }, }); } }); - - // Sort the tool calls chronologically by timestamp historicalToolPairs.sort((a, b) => { const timeA = new Date(a.assistantCall.timestamp || '').getTime(); const timeB = new Date(b.assistantCall.timestamp || '').getTime(); @@ -509,8 +493,6 @@ export default function ThreadPage({ }); setToolCalls(historicalToolPairs); - - // When loading is complete, prepare for playback initialLoadCompleted.current = true; } } catch (err) { @@ -525,9 +507,7 @@ export default function ThreadPage({ if (isMounted) setIsLoading(false); } } - loadData(); - return () => { isMounted = false; }; @@ -546,7 +526,6 @@ export default function ThreadPage({ messagesEndRef.current?.scrollIntoView({ behavior }); }; - // Handle tool clicks const handleToolClick = useCallback( (clickedAssistantMessageId: string | null, clickedToolName: string) => { if (!clickedAssistantMessageId) { @@ -557,7 +536,6 @@ export default function ThreadPage({ return; } - // Reset user closed state when explicitly clicking a tool userClosedPanelRef.current = false; console.log( @@ -567,21 +545,16 @@ export default function ThreadPage({ clickedToolName, ); - // Find the index of the tool call associated with the clicked assistant message const toolIndex = toolCalls.findIndex((tc) => { - // Check if the assistant message ID matches the one stored in the tool result's metadata if (!tc.toolResult?.content || tc.toolResult.content === 'STREAMING') - return false; // Skip streaming or incomplete calls + return false; - // Find the original assistant message based on the ID const assistantMessage = messages.find( (m) => m.message_id === clickedAssistantMessageId && m.type === 'assistant', ); if (!assistantMessage) return false; - - // Find the corresponding tool message using metadata const toolMessage = messages.find((m) => { if (m.type !== 'tool' || !m.metadata) return false; try { @@ -593,9 +566,6 @@ export default function ThreadPage({ return false; } }); - - // Check if the current toolCall 'tc' corresponds to this assistant/tool message pair - // Compare the original content directly without parsing return ( tc.assistantCall?.content === assistantMessage.content && tc.toolResult?.content === toolMessage?.content @@ -608,7 +578,7 @@ export default function ThreadPage({ ); setExternalNavIndex(toolIndex); setCurrentToolIndex(toolIndex); - setIsSidePanelOpen(true); // Explicitly open the panel + setIsSidePanelOpen(true); setTimeout(() => setExternalNavIndex(undefined), 100); } else { @@ -630,7 +600,6 @@ export default function ThreadPage({ setFileViewerOpen(true); }, []); - // Initialize PlaybackControls const playbackController: PlaybackController = PlaybackControls({ messages, isSidePanelOpen, @@ -641,7 +610,6 @@ export default function ThreadPage({ projectName: projectName || 'Shared Conversation', }); - // Extract the playback state and functions const { playbackState, renderHeader, @@ -652,21 +620,17 @@ export default function ThreadPage({ skipToEnd, } = playbackController; - // Connect playbackState to component state useEffect(() => { - // Keep the isPlaying state in sync with playbackState setIsPlaying(playbackState.isPlaying); setCurrentMessageIndex(playbackState.currentMessageIndex); }, [playbackState.isPlaying, playbackState.currentMessageIndex]); - // Auto-scroll when new messages appear during playback useEffect(() => { if (playbackState.visibleMessages.length > 0 && !userHasScrolled) { scrollToBottom('smooth'); } }, [playbackState.visibleMessages, userHasScrolled]); - // Scroll button visibility useEffect(() => { if (!latestMessageRef.current || playbackState.visibleMessages.length === 0) return; @@ -683,7 +647,6 @@ export default function ThreadPage({ `[PAGE] 🔄 Page AgentStatus: ${agentStatus}, Hook Status: ${streamHookStatus}, Target RunID: ${agentRunId || 'none'}, Hook RunID: ${currentHookRunId || 'none'}`, ); - // If the stream hook reports completion/stopping but our UI hasn't updated if ( (streamHookStatus === 'completed' || streamHookStatus === 'stopped' || @@ -700,7 +663,6 @@ export default function ThreadPage({ } }, [agentStatus, streamHookStatus, agentRunId, currentHookRunId]); - // Auto-scroll function for use throughout the component const autoScrollToBottom = useCallback( (behavior: ScrollBehavior = 'smooth') => { if (!userHasScrolled && messagesEndRef.current) { @@ -710,31 +672,21 @@ export default function ThreadPage({ [userHasScrolled], ); - // Very direct approach to update the tool index during message playback useEffect(() => { if (!isPlaying || currentMessageIndex <= 0 || !messages.length) return; - - // Check if current message is a tool message - const currentMsg = messages[currentMessageIndex - 1]; // Look at previous message that just played - + const currentMsg = messages[currentMessageIndex - 1]; if (currentMsg?.type === 'tool' && currentMsg.metadata) { try { const metadata = safeJsonParse(currentMsg.metadata, {}); const assistantId = metadata.assistant_message_id; - if (assistantId) { - // Find the tool call that matches this assistant message const toolIndex = toolCalls.findIndex((tc) => { - // Find the assistant message const assistantMessage = messages.find( (m) => m.message_id === assistantId && m.type === 'assistant' ); if (!assistantMessage) return false; - - // Check if this tool call matches return tc.assistantCall?.content === assistantMessage.content; }); - if (toolIndex !== -1) { console.log( `Direct mapping: Setting tool index to ${toolIndex} for message ${assistantId}`, @@ -748,28 +700,19 @@ export default function ThreadPage({ } }, [currentMessageIndex, isPlaying, messages, toolCalls]); - // Force an explicit update to the tool panel based on the current message index useEffect(() => { - // Skip if not playing or no messages if (!isPlaying || messages.length === 0 || currentMessageIndex <= 0) return; - - // Get all messages up to the current index const currentMessages = messages.slice(0, currentMessageIndex); - - // Find the most recent tool message to determine which panel to show for (let i = currentMessages.length - 1; i >= 0; i--) { const msg = currentMessages[i]; if (msg.type === 'tool' && msg.metadata) { try { const metadata = safeJsonParse(msg.metadata, {}); const assistantId = metadata.assistant_message_id; - if (assistantId) { console.log( `Looking for tool panel for assistant message ${assistantId}`, ); - - // Scan for matching tool call for (let j = 0; j < toolCalls.length; j++) { const content = toolCalls[j].assistantCall?.content || ''; if (content.includes(assistantId)) { @@ -788,14 +731,12 @@ export default function ThreadPage({ } }, [currentMessageIndex, isPlaying, messages, toolCalls]); - // Loading skeleton UI if (isLoading && !initialLoadCompleted.current) { return ( ); } - // Error state UI if (error) { return (