diff --git a/components/chatPanel.tsx b/components/chatPanel.tsx index e26e34b..13a353a 100644 --- a/components/chatPanel.tsx +++ b/components/chatPanel.tsx @@ -1,7 +1,7 @@ "use client" import type React from "react" -import { useRef, useEffect } from "react" +import { useRef, useEffect, useState } from "react" import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@/components/ui/card" import { ScrollArea } from "@/components/ui/scroll-area" @@ -15,6 +15,7 @@ interface ChatPanelProps { } export default function ChatPanel({ onDisplayChart, onFetchChart }: ChatPanelProps) { + const [previousXml, setPreviousXml] = useState(""); const { messages, input, handleInputChange, handleSubmit, status, error, setInput, setMessages, data } = useChat({ maxSteps: 5, async onToolCall({ toolCall }) { @@ -59,6 +60,10 @@ export default function ChatPanel({ onDisplayChart, onFetchChart }: ChatPanelPro } } + + + + // Helper function to render tool invocations const renderToolInvocation = (toolInvocation: any) => { const callId = toolInvocation.toolCallId; @@ -66,13 +71,39 @@ export default function ChatPanel({ onDisplayChart, onFetchChart }: ChatPanelPro switch (toolInvocation.toolName) { case 'display_diagram': { switch (toolInvocation.state) { + case 'partial-call': { + const currentXml = toolInvocation.args?.xml || ""; + const shouldShowPartialArgs = currentXml && + (!previousXml || Math.abs(currentXml.length - previousXml.length) > 50); + + // Update previous XML for next comparison + if (currentXml) { + setPreviousXml(currentXml); + } + + return ( +
+
Preparing to display diagram...
+
+ Tool: display_diagram + {shouldShowPartialArgs && toolInvocation.args && ( +
+ Partial args: {JSON.stringify(toolInvocation.args, null, 2)} +
+ )} +
+
+ ); + } case 'call': - case 'partial-call': return (
Displaying diagram...
Tool: display_diagram +
+ Args: {JSON.stringify(toolInvocation.args, null, 2)} +
); diff --git a/lib/utils.ts b/lib/utils.ts index bd0c391..c848602 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -4,3 +4,55 @@ import { twMerge } from "tailwind-merge" export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)) } + +/** + * Efficiently converts a potentially incomplete XML string to a legal XML string + * by closing any open tags properly. + * + * @param xmlString The potentially incomplete XML string + * @returns A legal XML string with properly closed tags + */ +export function convertToLegalXml(xmlString: string): string { + const stack: string[] = []; + let result = ''; + let tagStart = -1; + + for (let i = 0; i < xmlString.length; i++) { + const char = xmlString[i]; + result += char; + + if (char === '<' && tagStart === -1) { + // Start of a new tag + tagStart = i; + } else if (char === '>' && tagStart !== -1) { + // End of a tag + const tagContent = xmlString.substring(tagStart + 1, i); + + if (tagContent.startsWith('/')) { + // Closing tag + const tagName = tagContent.substring(1).trim().split(/\s+/)[0]; + if (stack.length && stack[stack.length - 1] === tagName) { + stack.pop(); + } + } else if (!tagContent.endsWith('/') && !tagContent.startsWith('?') && !tagContent.startsWith('!')) { + // Opening tag (not self-closing, processing instruction, or comment) + const tagName = tagContent.trim().split(/\s+/)[0]; + stack.push(tagName); + } + + tagStart = -1; + } + } + + // If we have an incomplete tag at the end, don't include it in the result + if (tagStart !== -1) { + result = result.substring(0, tagStart); + } + + // Close all remaining open tags + for (let j = stack.length - 1; j >= 0; j--) { + result += ``; + } + + return result; +} \ No newline at end of file