mirror of
https://github.com/DayuanJiang/next-ai-draw-io.git
synced 2026-01-02 22:32:27 +08:00
feat: add XML handling in ChatPanel and utility function for legal XML conversion
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import type React from "react"
|
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 { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
|
||||||
import { ScrollArea } from "@/components/ui/scroll-area"
|
import { ScrollArea } from "@/components/ui/scroll-area"
|
||||||
@@ -15,6 +15,7 @@ interface ChatPanelProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function ChatPanel({ onDisplayChart, onFetchChart }: ChatPanelProps) {
|
export default function ChatPanel({ onDisplayChart, onFetchChart }: ChatPanelProps) {
|
||||||
|
const [previousXml, setPreviousXml] = useState<string>("");
|
||||||
const { messages, input, handleInputChange, handleSubmit, status, error, setInput, setMessages, data } = useChat({
|
const { messages, input, handleInputChange, handleSubmit, status, error, setInput, setMessages, data } = useChat({
|
||||||
maxSteps: 5,
|
maxSteps: 5,
|
||||||
async onToolCall({ toolCall }) {
|
async onToolCall({ toolCall }) {
|
||||||
@@ -59,6 +60,10 @@ export default function ChatPanel({ onDisplayChart, onFetchChart }: ChatPanelPro
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Helper function to render tool invocations
|
// Helper function to render tool invocations
|
||||||
const renderToolInvocation = (toolInvocation: any) => {
|
const renderToolInvocation = (toolInvocation: any) => {
|
||||||
const callId = toolInvocation.toolCallId;
|
const callId = toolInvocation.toolCallId;
|
||||||
@@ -66,13 +71,39 @@ export default function ChatPanel({ onDisplayChart, onFetchChart }: ChatPanelPro
|
|||||||
switch (toolInvocation.toolName) {
|
switch (toolInvocation.toolName) {
|
||||||
case 'display_diagram': {
|
case 'display_diagram': {
|
||||||
switch (toolInvocation.state) {
|
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 (
|
||||||
|
<div key={callId} className="mt-2 text-sm bg-blue-50 p-2 rounded border border-blue-200">
|
||||||
|
<div className="font-medium">Preparing to display diagram...</div>
|
||||||
|
<div className="text-xs text-gray-500 mt-1">
|
||||||
|
Tool: display_diagram
|
||||||
|
{shouldShowPartialArgs && toolInvocation.args && (
|
||||||
|
<div className="mt-1 font-mono text-xs">
|
||||||
|
Partial args: {JSON.stringify(toolInvocation.args, null, 2)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
case 'call':
|
case 'call':
|
||||||
case 'partial-call':
|
|
||||||
return (
|
return (
|
||||||
<div key={callId} className="mt-2 text-sm bg-yellow-50 p-2 rounded border border-yellow-200">
|
<div key={callId} className="mt-2 text-sm bg-yellow-50 p-2 rounded border border-yellow-200">
|
||||||
<div className="font-medium">Displaying diagram...</div>
|
<div className="font-medium">Displaying diagram...</div>
|
||||||
<div className="text-xs text-gray-500 mt-1">
|
<div className="text-xs text-gray-500 mt-1">
|
||||||
Tool: display_diagram
|
Tool: display_diagram
|
||||||
|
<div className="mt-1 font-mono text-xs">
|
||||||
|
Args: {JSON.stringify(toolInvocation.args, null, 2)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
52
lib/utils.ts
52
lib/utils.ts
@@ -4,3 +4,55 @@ import { twMerge } from "tailwind-merge"
|
|||||||
export function cn(...inputs: ClassValue[]) {
|
export function cn(...inputs: ClassValue[]) {
|
||||||
return twMerge(clsx(inputs))
|
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 += `</${stack[j]}>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user