'use client'; import { Spinner } from "@heroui/react"; import { useMemo, useState } from "react"; import z from "zod"; import { Workflow } from "@/app/lib/types/workflow_types"; import { WorkflowTool } from "@/app/lib/types/workflow_types"; import MarkdownContent from "@/app/lib/components/markdown-content"; import { ChevronRightIcon, ChevronDownIcon, ChevronUpIcon, CodeIcon, CheckCircleIcon, FileTextIcon, EyeIcon, EyeOffIcon, WrapTextIcon, ArrowRightFromLineIcon, BracesIcon, TextIcon } from "lucide-react"; import { TestProfile } from "@/app/lib/types/testing_types"; import { ProfileContextBox } from "./profile-context-box"; import { Message, ToolMessage, AssistantMessageWithToolCalls } from "@/app/lib/types/types"; function UserMessage({ content }: { content: string }) { return (
User
); } function InternalAssistantMessage({ content, sender, latency, delta, showJsonMode = false }: { content: string, sender: string | null | undefined, latency: number, delta: number, showJsonMode?: boolean }) { const isJsonContent = useMemo(() => { try { JSON.parse(content); return true; } catch { return false; } }, [content]); const hasResponseKey = useMemo(() => { if (!isJsonContent) return false; try { const parsed = JSON.parse(content); return parsed && typeof parsed === 'object' && 'response' in parsed; } catch { return false; } }, [content, isJsonContent]); const [jsonMode, setJsonMode] = useState(false); const [wrapText, setWrapText] = useState(true); // Show plus icon and duration const deltaDisplay = ( +{Math.round(delta / 1000)}s ); // Extract response content for display const displayContent = useMemo(() => { if (!isJsonContent || !hasResponseKey) return content; try { const parsed = JSON.parse(content); return parsed.response || content; } catch { return content; } }, [content, isJsonContent, hasResponseKey]); // Format JSON content const formattedJson = useMemo(() => { if (!isJsonContent) return content; try { return JSON.stringify(JSON.parse(content), null, 2); } catch { return content; } }, [content, isJsonContent]); return (
{sender ?? 'Assistant'}
{isJsonContent && hasResponseKey && (
{jsonMode && ( )}
)} {isJsonContent && hasResponseKey && jsonMode ? (
                                {formattedJson}
                            
) : ( )}
{deltaDisplay}
); } function AssistantMessage({ content, sender, latency }: { content: string, sender: string | null | undefined, latency: number }) { return (
{sender ?? 'Assistant'}
{latency > 0 &&
{Math.round(latency / 1000)}s
}
); } function AssistantMessageLoading() { return (
); } function ToolCalls({ toolCalls, results, projectId, messages, sender, workflow, testProfile = null, systemMessage, delta }: { toolCalls: z.infer['toolCalls']; results: Record>; projectId: string; messages: z.infer[]; sender: string | null | undefined; workflow: z.infer; testProfile: z.infer | null; systemMessage: string | undefined; delta: number; }) { return
{toolCalls.map(toolCall => { return })}
; } function ToolCall({ toolCall, result, sender, workflow, delta }: { toolCall: z.infer['toolCalls'][number]; result: z.infer | undefined; sender: string | null | undefined; workflow: z.infer; delta: number; }) { let matchingWorkflowTool: z.infer | undefined; for (const tool of workflow.tools) { if (tool.name === toolCall.function.name) { matchingWorkflowTool = tool; break; } } if (toolCall.function.name.startsWith('transfer_to_')) { return ; } return ; } function TransferToAgentToolCall({ result: availableResult, sender, delta }: { result: z.infer | undefined; sender: string | null | undefined; delta: number; }) { const typedResult = availableResult ? JSON.parse(availableResult.content) as { assistant: string } : undefined; if (!typedResult) { return <>; } const deltaDisplay = ( +{Math.round(delta / 1000)}s ); return (
{sender} {typedResult.assistant} {deltaDisplay}
); } function ClientToolCall({ toolCall, result: availableResult, sender, workflow, delta }: { toolCall: z.infer['toolCalls'][number]; result: z.infer | undefined; sender: string | null | undefined; workflow: z.infer; delta: number; }) { const [wrapText, setWrapText] = useState(true); const [paramsExpanded, setParamsExpanded] = useState(false); const [resultsExpanded, setResultsExpanded] = useState(false); const hasExpandedContent = paramsExpanded || resultsExpanded; const isCompressed = !paramsExpanded && !resultsExpanded; // Compressed state: stretch header, no wrapping if (isCompressed) { return (
{sender && (
{sender}
)}
{!availableResult && } {availableResult && }
Invoked Tool: {toolCall.function.name}
{hasExpandedContent && (
)}
} wrapText={wrapText} onExpandedChange={setParamsExpanded} /> {availableResult && (
} wrapText={wrapText} onExpandedChange={setResultsExpanded} />
)}
); } // Expanded state: respect 85% max width, prevent overshoot return (
{sender && (
{sender}
)}
{!availableResult && } {availableResult && }
Invoked Tool: {toolCall.function.name}
{hasExpandedContent && (
)}
} wrapText={wrapText} onExpandedChange={setParamsExpanded} /> {availableResult && (
} wrapText={wrapText} onExpandedChange={setResultsExpanded} />
)}
); } function ExpandableContent({ label, content, expanded = false, icon, wrapText = false, onExpandedChange, rightButton }: { label: string, content: string | object | undefined, expanded?: boolean, icon?: React.ReactNode, wrapText?: boolean, onExpandedChange?: (expanded: boolean) => void, rightButton?: React.ReactNode }) { const [isExpanded, setIsExpanded] = useState(expanded); const formattedContent = useMemo(() => { if (typeof content === 'string') { try { const parsed = JSON.parse(content); return JSON.stringify(parsed, null, 2); } catch (e) { // If it's not JSON, return the string as-is return content; } } if (typeof content === 'object') { return JSON.stringify(content, null, 2); } return 'undefined'; }, [content]); function toggleExpanded() { const newExpanded = !isExpanded; setIsExpanded(newExpanded); onExpandedChange?.(newExpanded); } const isMarkdown = label === 'Result' && typeof content === 'string' && !content.startsWith('{'); return
{!isExpanded && } {isExpanded && } {icon && {icon}}
{label}
{rightButton && {rightButton}}
{isExpanded && ( isMarkdown ? (
) : (
                    {formattedContent}
                
) )}
; } export function Messages({ projectId, messages, toolCallResults, loadingAssistantResponse, workflow, testProfile = null, systemMessage, onSystemMessageChange, showSystemMessage, showDebugMessages = true, showJsonMode = false, }: { projectId: string; messages: z.infer[]; toolCallResults: Record>; loadingAssistantResponse: boolean; workflow: z.infer; testProfile: z.infer | null; systemMessage: string | undefined; onSystemMessageChange: (message: string) => void; showSystemMessage: boolean; showDebugMessages?: boolean; showJsonMode?: boolean; }) { // Remove scroll/auto-scroll state and logic // const scrollContainerRef = useRef(null); // const [autoScroll, setAutoScroll] = useState(true); // const [showUnreadBubble, setShowUnreadBubble] = useState(false); // Remove handleScroll and useEffect for scroll const renderMessage = (message: z.infer, index: number) => { if (message.role === 'assistant') { // TODO: add latency support // let latency = new Date(message.createdAt).getTime() - lastUserMessageTimestamp; // if (!userMessageSeen) { // latency = 0; // } let latency = 0; // First check for tool calls if ('toolCalls' in message) { // Skip tool calls if debug mode is off if (!showDebugMessages) { return null; } return ( ); } // Then check for internal messages if (message.content && message.responseType === 'internal') { // Skip internal messages if debug mode is off if (!showDebugMessages) { return null; } return ( ); } // Finally, regular assistant messages return ( ); } if (message.role === 'user') { // TODO: add latency support // lastUserMessageTimestamp = new Date(message.createdAt).getTime(); // userMessageSeen = true; return ; } return null; }; const isAgentTransition = (message: z.infer) => { return message.role === 'assistant' && 'toolCalls' in message && Array.isArray(message.toolCalls) && message.toolCalls.some(tc => tc.function.name.startsWith('transfer_to_')); }; const isAssistantMessage = (message: z.infer) => { return message.role === 'assistant' && (!('toolCalls' in message) || !Array.isArray(message.toolCalls) || !message.toolCalls.some(tc => tc.function.name.startsWith('transfer_to_'))); }; if (showSystemMessage) { return ( ); } // Just render the messages, no scroll container or unread bubble return (
{messages.map((message, index) => { const renderedMessage = renderMessage(message, index); if (renderedMessage) { return (
{renderedMessage}
); } return null; })} {loadingAssistantResponse && }
); }