diff --git a/backend/agent/prompt.py b/backend/agent/prompt.py index 1826d81b..c48646a0 100644 --- a/backend/agent/prompt.py +++ b/backend/agent/prompt.py @@ -441,6 +441,30 @@ For casual conversation and social interactions: - Tool Results: Carefully analyze all tool execution results to inform your next actions. **Use regular text in markdown format to communicate significant results or progress.** +## 7.3 ATTACHMENT PROTOCOL +- **CRITICAL: ALL VISUALIZATIONS MUST BE ATTACHED:** + * When using the 'ask' tool , ALWAYS attach ALL visualizations, markdown files, charts, graphs, reports, and any viewable content created + * This includes but is not limited to: HTML files, PDF documents, markdown files, images, data visualizations, presentations, reports, dashboards, and UI mockups + * NEVER mention a visualization or viewable content without attaching it + * If you've created multiple visualizations, attach ALL of them + * Always make visualizations available to the user BEFORE marking tasks as complete + * For web applications or interactive content, always attach the main HTML file + * When creating data analysis results, charts must be attached, not just described + * Remember: If the user should SEE it, you must ATTACH it with the 'ask' tool + * Verify that ALL visual outputs have been attached before proceeding + +- **Attachment Checklist:** + * Data visualizations (charts, graphs, plots) + * Web interfaces (HTML/CSS/JS files) + * Reports and documents (PDF, HTML) + * Presentation materials + * Images and diagrams + * Interactive dashboards + * Analysis results with visual components + * UI designs and mockups + * Any file intended for user viewing or interaction + + # 8. COMPLETION PROTOCOLS ## 8.1 TERMINATION RULES diff --git a/frontend/src/app/share/[threadId]/page.tsx b/frontend/src/app/share/[threadId]/page.tsx index 456a4fc7..5e4ec3fb 100644 --- a/frontend/src/app/share/[threadId]/page.tsx +++ b/frontend/src/app/share/[threadId]/page.tsx @@ -1,11 +1,11 @@ 'use client'; -import React, { useCallback, useEffect, useRef, useState, useMemo } from 'react'; +import React, { useCallback, useEffect, useRef, useState } from 'react'; import Image from 'next/image'; import { useRouter } from 'next/navigation'; import { Button } from '@/components/ui/button'; import { - ArrowDown, CircleDashed, Info, File, ChevronRight, Play, Pause, MonitorPlay, ExternalLink + ArrowDown, CircleDashed, Info, File, ChevronRight, Play, Pause } from 'lucide-react'; import { getMessages, getProject, getThread, Project, Message as BaseApiMessageType } from '@/lib/api'; import { toast } from 'sonner'; @@ -20,28 +20,6 @@ import { cn } from "@/lib/utils"; import { UnifiedMessage, ParsedContent, ParsedMetadata, ThreadParams } from '@/components/thread/types'; import { getToolIcon, extractPrimaryParam, safeJsonParse } from '@/components/thread/utils'; -// Function to extract tool calls from a message -function extractToolCallsFromMessage(messageText: string) { - const result: { name: string; fullMatch: string }[] = []; - - // Define regex to find tool calls in text - const toolCallRegex = /<([a-zA-Z\-_]+)(?:\s+[^>]*)?>(?:[\s\S]*?)<\/\1>|<([a-zA-Z\-_]+)(?:\s+[^>]*)?\/>/g; - - let match; - while ((match = toolCallRegex.exec(messageText)) !== null) { - const toolName = match[1] || match[2]; - // Skip tags - if (toolName !== 'ask') { - result.push({ - name: toolName, - fullMatch: match[0] - }); - } - } - - return result; -} - // Define the set of tags whose raw XML should be hidden during streaming const HIDE_STREAMING_XML_TAGS = new Set([ 'execute-command', @@ -1117,7 +1095,6 @@ export default function ThreadPage({ params }: { params: Promise } } }, [projectName]); - // In the useEffect where streamingTextContent is handled useEffect(() => { if (streamingTextContent && streamHookStatus === 'streaming' && messages.length > 0) { // Find the last assistant message to update with streaming content @@ -1154,6 +1131,257 @@ export default function ThreadPage({ params }: { params: Promise } } }, [streamingTextContent, streamHookStatus, messages, isStreamingText, isPlaying, currentMessageIndex, streamText]); + // Create a message-to-tool-index map for faster lookups + const [messageToToolIndex, setMessageToToolIndex] = useState>({}); + + // Build the message-to-tool-index map when tool calls change + useEffect(() => { + if (!toolCalls.length) return; + + const mapBuilder: Record = {}; + + toolCalls.forEach((tool, index) => { + const content = tool.assistantCall?.content || ''; + const match = content.match(//); + if (match && match[1]) { + mapBuilder[match[1]] = index; + console.log(`Mapped message ID ${match[1]} to tool index ${index}`); + } + }); + + setMessageToToolIndex(mapBuilder); + }, [toolCalls]); + + // 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 + + if (currentMsg?.type === 'tool' && currentMsg.metadata) { + try { + const metadata = safeJsonParse(currentMsg.metadata, {}); + const assistantId = metadata.assistant_message_id; + + if (assistantId && messageToToolIndex[assistantId] !== undefined) { + const toolIndex = messageToToolIndex[assistantId]; + console.log(`Direct mapping: Setting tool index to ${toolIndex} for message ${assistantId}`); + setCurrentToolIndex(toolIndex); + } + } catch (e) { + console.error('Error in direct tool mapping:', e); + } + } + }, [currentMessageIndex, isPlaying, messages, messageToToolIndex]); + + // Add a helper function to extract tool calls from message content + const extractToolCallsFromMessage = (content: string) => { + const toolCallRegex = /<([a-zA-Z\-_]+)(?:\s+[^>]*)?>(?:[\s\S]*?)<\/\1>|<([a-zA-Z\-_]+)(?:\s+[^>]*)?\/>/g; + const results = []; + let match; + + while ((match = toolCallRegex.exec(content)) !== null) { + const toolName = match[1] || match[2]; + results.push({ + name: toolName, + fullMatch: match[0] + }); + } + + return results; + }; + + // 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)) { + console.log(`Found matching tool call at index ${j}, updating panel`); + setCurrentToolIndex(j); + return; + } + } + } + } catch (e) { + console.error('Error parsing tool message metadata:', e); + } + } + } + }, [currentMessageIndex, isPlaying, messages, toolCalls]); + + // Add a special button to each tool call to show its debug info + // This replaces the existing ToolCallSidePanel component with a wrapper that adds debug info + const ToolCallPanelWithDebugInfo = React.useMemo(() => { + const WrappedPanel = (props: any) => { + const { isOpen, onClose, toolCalls, currentIndex, onNavigate, ...rest } = props; + + // Add a function to show debug info for the current tool call + const showDebugInfo = useCallback(() => { + if (toolCalls && toolCalls.length > 0 && currentIndex >= 0 && currentIndex < toolCalls.length) { + const tool = toolCalls[currentIndex]; + console.log('Current tool call debug info:', { + name: tool.assistantCall?.name, + content: tool.assistantCall?.content, + messageIdMatches: tool.assistantCall?.content?.match(//), + toolResult: tool.toolResult + }); + } + }, [toolCalls, currentIndex]); + + return ( +
+ + + {/* Add debug button */} + {isOpen && toolCalls && toolCalls.length > 0 && ( +
+ +
+ )} +
+ ); + }; + + return WrappedPanel; + }, []); + + if (isLoading && !initialLoadCompleted.current) { + return ( +
+
+ {/* Skeleton Header */} +
+
+
+
+ + +
+
+
+ + +
+
+
+ + {/* Skeleton Chat Messages */} +
+
+ {/* User message */} +
+
+
+ + +
+
+
+ + {/* Assistant response with tool usage */} +
+
+ +
+
+
+
+ + + +
+ + {/* Tool call button skeleton */} +
+ +
+ +
+ + +
+
+
+
+
+
+
+
+
+ + {/* Skeleton Side Panel (closed state) */} +
+
+
+ + + +
+
+
+
+ ); + } + + if (error) { + return ( +
+
+
+
+
+ Shared Conversation +
+
+
+
+
+

Error

+

{error}

+ +
+
+
+
+ ); + } + return (
@@ -1526,8 +1754,8 @@ export default function ThreadPage({ params }: { params: Promise } )} - {/* Tool calls side panel */} - setIsSidePanelOpen(false)} toolCalls={toolCalls} @@ -1550,4 +1778,4 @@ export default function ThreadPage({ params }: { params: Promise } />
); -} +} \ No newline at end of file diff --git a/frontend/src/components/thread/tool-views/FileOperationToolView.tsx b/frontend/src/components/thread/tool-views/FileOperationToolView.tsx index 27154aed..d2d22087 100644 --- a/frontend/src/components/thread/tool-views/FileOperationToolView.tsx +++ b/frontend/src/components/thread/tool-views/FileOperationToolView.tsx @@ -144,11 +144,7 @@ export function FileOperationToolView({ ? `${project.sandbox.sandbox_url}/${processedFilePath}` : undefined; - // Only log HTML preview URL when it exists - if (htmlPreviewUrl) { - console.log('HTML Preview URL:', htmlPreviewUrl); - } - + // console.log('HTML Preview URL:', htmlPreviewUrl); // Add state for view mode toggle (code or preview) const [viewMode, setViewMode] = useState<'code' | 'preview'>(isHtml || isMarkdown || isCsv ? 'preview' : 'code');