"use client"; import type React from "react"; import { useRef, useEffect, useState } from "react"; import { FaGithub } from "react-icons/fa"; import { PanelRightClose, PanelRightOpen } from "lucide-react"; import Link from "next/link"; import Image from "next/image"; import { useChat } from "@ai-sdk/react"; import { DefaultChatTransport } from "ai"; import { ChatInput } from "@/components/chat-input"; import { ChatMessageDisplay } from "./chat-message-display"; import { useDiagram } from "@/contexts/diagram-context"; import { replaceNodes, formatXML, validateMxCellStructure } from "@/lib/utils"; import { ButtonWithTooltip } from "@/components/button-with-tooltip"; interface ChatPanelProps { isVisible: boolean; onToggleVisibility: () => void; } export default function ChatPanel({ isVisible, onToggleVisibility, }: ChatPanelProps) { const { loadDiagram: onDisplayChart, handleExport: onExport, resolverRef, chartXML, clearDiagram, } = useDiagram(); const onFetchChart = () => { return Promise.race([ new Promise((resolve) => { if (resolverRef && "current" in resolverRef) { resolverRef.current = resolve; } onExport(); }), new Promise((_, reject) => setTimeout( () => reject( new Error("Chart export timed out after 10 seconds") ), 10000 ) ), ]); }; const [files, setFiles] = useState([]); const [showHistory, setShowHistory] = useState(false); const [input, setInput] = useState(""); const { messages, sendMessage, addToolResult, status, error, setMessages } = useChat({ transport: new DefaultChatTransport({ api: "/api/chat", }), async onToolCall({ toolCall }) { if (toolCall.toolName === "display_diagram") { const { xml } = toolCall.input as { xml: string }; const validationError = validateMxCellStructure(xml); if (validationError) { addToolResult({ tool: "display_diagram", toolCallId: toolCall.toolCallId, output: validationError, }); } else { addToolResult({ tool: "display_diagram", toolCallId: toolCall.toolCallId, output: "Successfully displayed the diagram.", }); } } else if (toolCall.toolName === "edit_diagram") { const { edits } = toolCall.input as { edits: Array<{ search: string; replace: string }>; }; let currentXml = ""; try { currentXml = await onFetchChart(); const { replaceXMLParts } = await import("@/lib/utils"); const editedXml = replaceXMLParts(currentXml, edits); onDisplayChart(editedXml); addToolResult({ tool: "edit_diagram", toolCallId: toolCall.toolCallId, output: `Successfully applied ${edits.length} edit(s) to the diagram.`, }); } catch (error) { console.error("Edit diagram failed:", error); const errorMessage = error instanceof Error ? error.message : String(error); addToolResult({ tool: "edit_diagram", toolCallId: toolCall.toolCallId, output: `Edit failed: ${errorMessage} Current diagram XML: \`\`\`xml ${currentXml} \`\`\` Please retry with an adjusted search pattern or use display_diagram if retries are exhausted.`, }); } } }, onError: (error) => { console.error("Chat error:", error); }, }); const messagesEndRef = useRef(null); useEffect(() => { if (messagesEndRef.current) { messagesEndRef.current.scrollIntoView({ behavior: "smooth" }); } }, [messages]); useEffect(() => { console.log("[ChatPanel] Status changed to:", status); }, [status]); const onFormSubmit = async (e: React.FormEvent) => { e.preventDefault(); const isProcessing = status === "streaming" || status === "submitted"; if (input.trim() && !isProcessing) { try { let chartXml = await onFetchChart(); chartXml = formatXML(chartXml); const parts: any[] = [{ type: "text", text: input }]; if (files.length > 0) { for (const file of files) { const reader = new FileReader(); const dataUrl = await new Promise((resolve) => { reader.onload = () => resolve(reader.result as string); reader.readAsDataURL(file); }); parts.push({ type: "file", url: dataUrl, mediaType: file.type, }); } } sendMessage( { parts }, { body: { xml: chartXml, }, } ); setInput(""); setFiles([]); } catch (error) { console.error("Error fetching chart data:", error); } } }; const handleInputChange = ( e: React.ChangeEvent ) => { setInput(e.target.value); }; const handleFileChange = (newFiles: File[]) => { setFiles(newFiles); }; // Collapsed view if (!isVisible) { return (
AI Chat
); } // Full view return (
{/* Header */}
Next AI Drawio

Next AI Drawio

About
{/* Messages */}
{/* Input */}
); }