"use client"; import type React from "react"; import { useRef, useEffect, useState, useCallback } from "react"; import Image from "next/image"; import { ScrollArea } from "@/components/ui/scroll-area"; import ExamplePanel from "./chat-example-panel"; import { UIMessage } from "ai"; import { convertToLegalXml, replaceNodes } from "@/lib/utils"; import { useDiagram } from "@/contexts/diagram-context"; interface ChatMessageDisplayProps { messages: UIMessage[]; error?: Error | null; setInput: (input: string) => void; setFiles: (files: File[]) => void; } export function ChatMessageDisplay({ messages, error, setInput, setFiles, }: ChatMessageDisplayProps) { const { chartXML, loadDiagram: onDisplayChart } = useDiagram(); const messagesEndRef = useRef(null); const previousXML = useRef(""); const processedToolCalls = useRef>(new Set()); const [expandedTools, setExpandedTools] = useState>( {} ); const handleDisplayChart = useCallback( (xml: string) => { const currentXml = xml || ""; const convertedXml = convertToLegalXml(currentXml); if (convertedXml !== previousXML.current) { previousXML.current = convertedXml; const replacedXML = replaceNodes(chartXML, convertedXml); onDisplayChart(replacedXML); } }, [chartXML, onDisplayChart] ); useEffect(() => { if (messagesEndRef.current) { messagesEndRef.current.scrollIntoView({ behavior: "smooth" }); } }, [messages]); // Handle tool invocations and update diagram when needed useEffect(() => { messages.forEach((message) => { if (message.parts) { message.parts.forEach((part: any) => { if (part.type?.startsWith("tool-")) { const { toolCallId, state } = part; // Auto-collapse args when diagrams are generated if (state === "output-available") { setExpandedTools((prev) => ({ ...prev, [toolCallId]: false, })); } // Handle diagram updates for display_diagram tool if ( part.type === "tool-display_diagram" && part.input?.xml ) { // For streaming input, always update to show streaming if ( state === "input-streaming" || state === "input-available" ) { handleDisplayChart(part.input.xml); } // For completed calls, only update if not processed yet else if ( state === "output-available" && !processedToolCalls.current.has(toolCallId) ) { handleDisplayChart(part.input.xml); processedToolCalls.current.add(toolCallId); } } } }); } }); }, [messages, handleDisplayChart]); const renderToolPart = (part: any) => { const callId = part.toolCallId; const { state, input } = part; const isExpanded = expandedTools[callId] ?? true; const toolName = part.type?.replace("tool-", ""); const toggleExpanded = () => { setExpandedTools((prev) => ({ ...prev, [callId]: !isExpanded, })); }; return (
Tool: {toolName}
{input && Object.keys(input).length > 0 && ( )}
{input && isExpanded && (
{typeof input === "object" && Object.keys(input).length > 0 && `Input: ${JSON.stringify(input, null, 2)}`}
)}
{state === "input-streaming" ? (
) : state === "output-available" ? (
{toolName === "display_diagram" ? "Diagram generated" : toolName === "edit_diagram" ? "Diagram edited" : "Tool executed"}
) : state === "output-error" ? (
{toolName === "display_diagram" ? "Error generating diagram" : toolName === "edit_diagram" ? "Error editing diagram" : "Tool error"}
) : null}
); }; return ( {messages.length === 0 ? ( ) : ( messages.map((message) => (
{message.parts?.map((part: any, index: number) => { switch (part.type) { case "text": return (
{part.text}
); case "file": return (
{`file-${index}`}
); default: if (part.type?.startsWith("tool-")) { return renderToolPart(part); } return null; } })}
)) )} {error && (
Error: {error.message}
)}
); }