'use client'; import { Project } from "@/lib/api"; import { getToolIcon } from "@/components/thread/utils"; import React from "react"; import { Slider } from "@/components/ui/slider"; import { ApiMessageType } from '@/components/thread/types'; import { CircleDashed, X, ChevronLeft, ChevronRight } from "lucide-react"; import { cn } from "@/lib/utils"; import { useIsMobile } from "@/hooks/use-mobile"; import { Button } from "@/components/ui/button"; // Import tool view components from the tool-views directory import { CommandToolView } from "./tool-views/CommandToolView"; import { StrReplaceToolView } from "./tool-views/StrReplaceToolView"; import { GenericToolView } from "./tool-views/GenericToolView"; import { FileOperationToolView } from "./tool-views/FileOperationToolView"; import { BrowserToolView } from "./tool-views/BrowserToolView"; import { WebSearchToolView } from "./tool-views/WebSearchToolView"; import { WebCrawlToolView } from "./tool-views/WebCrawlToolView"; import { WebScrapeToolView } from "./tool-views/WebScrapeToolView"; import { DataProviderToolView } from "./tool-views/DataProviderToolView"; import { ExposePortToolView } from "./tool-views/ExposePortToolView"; // Simple input interface export interface ToolCallInput { assistantCall: { content?: string; name?: string; timestamp?: string; }; toolResult?: { content?: string; isSuccess?: boolean; timestamp?: string; }; messages?: ApiMessageType[]; } // Get the specialized tool view component based on the tool name function getToolView( toolName: string | undefined, assistantContent: string | undefined, toolContent: string | undefined, assistantTimestamp: string | undefined, toolTimestamp: string | undefined, isSuccess: boolean = true, project?: Project, messages?: ApiMessageType[], agentStatus?: string, currentIndex?: number, totalCalls?: number, isStreaming?: boolean ) { if (!toolName) return null; const normalizedToolName = toolName.toLowerCase(); switch (normalizedToolName) { case 'execute-command': return ( ); case 'str-replace': return ( ); case 'expose-port': return ( ); case 'create-file': case 'full-file-rewrite': case 'delete-file': return ( ); case 'browser-navigate': case 'browser-click': case 'browser-extract': case 'browser-fill': case 'browser-wait': return ( ); case 'web-search': return ( ); case 'crawl-webpage': return ( ); case 'scrape-webpage': return ( ); case 'execute-data-provider-call': case 'get-data-provider-endpoints': return ( ); default: // Check if it's a browser operation if (normalizedToolName.startsWith('browser-')) { return ( ); } // Fallback to generic view return ( ); } } interface ToolCallSidePanelProps { isOpen: boolean; onClose: () => void; toolCalls: ToolCallInput[]; currentIndex: number; onNavigate: (newIndex: number) => void; messages?: ApiMessageType[]; agentStatus: string; project?: Project; renderAssistantMessage?: (assistantContent?: string, toolContent?: string) => React.ReactNode; renderToolResult?: (toolContent?: string, isSuccess?: boolean) => React.ReactNode; } export function ToolCallSidePanel({ isOpen, onClose, toolCalls, currentIndex, onNavigate, messages, agentStatus, project, renderAssistantMessage, renderToolResult }: ToolCallSidePanelProps) { // Move hooks outside of conditional const [dots, setDots] = React.useState(''); const currentToolCall = toolCalls[currentIndex]; const totalCalls = toolCalls.length; const currentToolName = currentToolCall?.assistantCall?.name || 'Tool Call'; const CurrentToolIcon = getToolIcon(currentToolName === 'Tool Call' ? 'unknown' : currentToolName); const isStreaming = currentToolCall?.toolResult?.content === "STREAMING"; const isSuccess = currentToolCall?.toolResult?.isSuccess ?? true; const isMobile = useIsMobile(); // Add keyboard shortcut for CMD+I to close panel React.useEffect(() => { if (!isOpen) return; const handleKeyDown = (event: KeyboardEvent) => { if ((event.metaKey || event.ctrlKey) && event.key === 'i') { event.preventDefault(); onClose(); } }; window.addEventListener('keydown', handleKeyDown); return () => window.removeEventListener('keydown', handleKeyDown); }, [isOpen, onClose]); // Listen for sidebar toggle events React.useEffect(() => { if (!isOpen) return; const handleSidebarToggle = (event: CustomEvent) => { if (event.detail.expanded) { onClose(); } }; window.addEventListener('sidebar-left-toggled', handleSidebarToggle as EventListener); return () => window.removeEventListener('sidebar-left-toggled', handleSidebarToggle as EventListener); }, [isOpen, onClose]); React.useEffect(() => { if (!isStreaming) return; // Create a loading animation with dots const interval = setInterval(() => { setDots(prev => { if (prev === '...') return ''; return prev + '.'; }); }, 500); return () => clearInterval(interval); }, [isStreaming]); // Handle navigation with safety checks const navigateToPrevious = React.useCallback(() => { if (currentIndex > 0) { onNavigate(currentIndex - 1); } }, [currentIndex, onNavigate]); const navigateToNext = React.useCallback(() => { if (currentIndex < totalCalls - 1) { onNavigate(currentIndex + 1); } }, [currentIndex, totalCalls, onNavigate]); if (!isOpen) return null; const renderContent = () => { if (!currentToolCall) { return (
{/* Always show header with close button for empty state */}

Suna's Computer

{/* Empty state message */}

No tool call details available.

); } const toolView = getToolView( currentToolCall.assistantCall.name, currentToolCall.assistantCall.content, currentToolCall.toolResult?.content, currentToolCall.assistantCall.timestamp, currentToolCall.toolResult?.timestamp, isStreaming ? true : (currentToolCall.toolResult?.isSuccess ?? true), project, messages, agentStatus, currentIndex, totalCalls, isStreaming ); if (!toolView) { return (
{/* Header with close button even when no tool view */}

Suna's Computer

{/* Error state message */}

Unable to display tool details.

); } return (

Suna's Computer

{currentToolCall.toolResult?.content && !isStreaming && (
{currentToolName}
{isSuccess ? 'Success' : 'Failed'}
{/* Add close button */}
)} {isStreaming && (
Running
{/* Add close button */}
)} {/* Show close button when no status is available */} {!currentToolCall.toolResult?.content && !isStreaming && ( )}
{/* Content area */}
{toolView}
); }; return (
{renderContent()}
{/* Navigation controls */} {totalCalls > 1 && (
{!isMobile && (
{currentToolName} {isStreaming && `(Running${dots})`}
Step {currentIndex + 1} of {totalCalls}
)} {isMobile ? (
{currentIndex + 1} / {totalCalls}
) : (
onNavigate(newValue)} className="w-full [&>span:first-child]:h-1 [&>span:first-child]:bg-zinc-200 dark:[&>span:first-child]:bg-zinc-800 [&>span:first-child>span]:bg-zinc-500 dark:[&>span:first-child>span]:bg-zinc-400 [&>span:first-child>span]:h-1" />
)}
)}
); }