"use client"; import type React from "react"; import { useRef, useEffect, useState } from "react"; import Image from "next/image"; import { Card, CardContent, CardFooter, CardHeader, CardTitle, } from "@/components/ui/card"; import { ScrollArea } from "@/components/ui/scroll-area"; import { Button } from "@/components/ui/button"; import { useChat } from "@ai-sdk/react"; import { ChatInput } from "@/components/chat-input"; import { convertToLegalXml } from "@/lib/utils"; interface ChatPanelProps { onDisplayChart: (xml: string) => void; onFetchChart: () => Promise; } export default function ChatPanel({ onDisplayChart, onFetchChart, }: ChatPanelProps) { // Add a step counter to track updates const stepCounterRef = useRef(0); // Add state for file attachments const [files, setFiles] = useState(undefined); // Add state to control visibility of prompt examples panel // Remove the currentXmlRef and related useEffect const { messages, input, handleInputChange, handleSubmit, status, error, setInput, setMessages, data, } = useChat({ maxSteps: 5, async onToolCall({ toolCall }) { console.log("Tool call:", toolCall); console.log("Tool call name:", toolCall.toolName); console.log("Tool call arguments:", toolCall.args); if (toolCall.toolName === "display_diagram") { const { xml } = toolCall.args as { xml: string }; onDisplayChart(xml); return "Successfully displayed the flowchart."; } }, onError: (error) => { console.error("Chat error:", error); }, }); const messagesEndRef = useRef(null); // Scroll to bottom when messages change useEffect(() => { if (messagesEndRef.current) { messagesEndRef.current.scrollIntoView({ behavior: "smooth" }); } console.log("Data updated:", data); }, [messages]); const onFormSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (input.trim() && status !== "streaming") { try { // Hide examples panel after sending a message // Fetch chart data before setting input const chartXml = await onFetchChart(); // Now use the fetched data to set input setInput( ` Current diagram XML: """xml ${chartXml} """ User input: """md ${input} """ ` ); handleSubmit(e, { experimental_attachments: files, }); // Clear files after submission setFiles(undefined); } catch (error) { console.error("Error fetching chart data:", error); } } }; // Helper function to handle file changes const handleFileChange = (newFiles: FileList | undefined) => { setFiles(newFiles); }; // New utility function to create a FileList from a File const createFileList = (file: File): FileList => { const dt = new DataTransfer(); dt.items.add(file); return dt.files; }; // New handler for the "Replicate this flowchart" button const handleReplicateFlowchart = async () => { setInput("Replicate this flowchart."); try { // Fetch the example image const response = await fetch("/example.png"); const blob = await response.blob(); const file = new File([blob], "example.png", { type: "image/png" }); // Set the file to the files state setFiles(createFileList(file)); } catch (error) { console.error("Error loading example image:", error); } }; // Helper function to render tool invocations const renderToolInvocation = (toolInvocation: any) => { const callId = toolInvocation.toolCallId; switch (toolInvocation.toolName) { case "display_diagram": { switch (toolInvocation.state) { case "partial-call": { const currentXml = toolInvocation.args?.xml || ""; // Increment the step counter stepCounterRef.current += 1; // Log the current step // Determine whether to show details based on a simple threshold // Rather than comparing lengths (which can cause re-renders) if ( stepCounterRef.current >= 50 && stepCounterRef.current % 20 === 0 ) { onDisplayChart(convertToLegalXml(currentXml)); } return (
Generating diagram...
Tool: display_diagram
onDisplayChart
); } case "call": return (
Displaying diagram...
Tool: display_diagram
Args:{" "} {JSON.stringify( toolInvocation.args, null, 2 )}
); case "result": return (
Diagram generated
Result: {toolInvocation.result}
); } break; } default: return null; } }; const examplePanel = (

{" "} Start a conversation to generate or modify diagrams.

{" "} You can also upload images to use as references.

Try these examples:

); return ( Next-AI-Drawio {messages.length === 0 ? examplePanel : messages.map((message) => (
{/* Render message content based on parts if available */} {message.parts ? message.parts.map((part, index) => { switch (part.type) { case "text": return (
{part.text}
); case "tool-invocation": return renderToolInvocation( part.toolInvocation ); default: return null; } }) : // Fallback to simple content for older format message.content}
{/* Display image attachments */} {message?.experimental_attachments ?.filter((attachment) => attachment?.contentType?.startsWith( "image/" ) ) .map((attachment, index) => (
{
))} {/* Legacy support for function_call format */} {(message as any).function_call && (
Using tool:{" "} { (message as any).function_call .name } ...
)}
))} {error && (
Error: {error.message}
)}
); }