mirror of
https://github.com/DayuanJiang/next-ai-draw-io.git
synced 2026-01-02 22:32:27 +08:00
feat: enhance chat functionality with flowchart tools integration and improved UI interactions
This commit is contained in:
@@ -1,13 +1,40 @@
|
|||||||
import { google } from "@ai-sdk/google";
|
import { google } from "@ai-sdk/google";
|
||||||
|
import { Message } from "ai/react";
|
||||||
import { streamText } from "ai";
|
import { streamText } from "ai";
|
||||||
|
import { z } from "zod";
|
||||||
export const maxDuration = 30;
|
export const maxDuration = 30;
|
||||||
|
|
||||||
|
// Define tool interfaces
|
||||||
|
interface DisplayFlowChartArgs {
|
||||||
|
xml: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ToolContext {
|
||||||
|
getCurrentXML: () => string;
|
||||||
|
displayChart: (xml: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
export async function POST(req: Request) {
|
export async function POST(req: Request) {
|
||||||
const { messages } = await req.json();
|
const { messages } = await req.json();
|
||||||
const result = streamText({
|
const response = streamText({
|
||||||
model: google("gemini-2.0-flash"),
|
model: google("gemini-2.0-flash"),
|
||||||
messages,
|
messages,
|
||||||
|
tools: {
|
||||||
|
display_flow_chart: {
|
||||||
|
description: "Display a flowchart on draw.io",
|
||||||
|
parameters: z.object(
|
||||||
|
{
|
||||||
|
xml: z.string().describe("XML string to be displayed on draw.io"),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
},
|
||||||
|
fetch_flow_chart: {
|
||||||
|
description: "Get the current flowchart XML from draw.io",
|
||||||
|
parameters: z.object({}),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
temperature: 0,
|
||||||
});
|
});
|
||||||
return result.toDataStreamResponse();
|
|
||||||
|
return response.toDataStreamResponse();
|
||||||
}
|
}
|
||||||
18
app/page.tsx
18
app/page.tsx
@@ -9,24 +9,23 @@ import ChatPanel from "@/components/chatPanel";
|
|||||||
export default function Home() {
|
export default function Home() {
|
||||||
const drawioRef = useRef<DrawIoEmbedRef>(null);
|
const drawioRef = useRef<DrawIoEmbedRef>(null);
|
||||||
const [chartXML, setChartXML] = useState<string>("");
|
const [chartXML, setChartXML] = useState<string>("");
|
||||||
const [diagram, setDiagram] = useState<string>("");
|
|
||||||
// const handleExport = () => {};
|
|
||||||
const handleExport = () => {
|
const handleExport = () => {
|
||||||
// use this function to export the diagramxml from the drawio editor
|
|
||||||
if (drawioRef.current) {
|
if (drawioRef.current) {
|
||||||
drawioRef.current.exportDiagram({
|
drawioRef.current.exportDiagram({
|
||||||
format: "xmlsvg",
|
format: "xmlsvg",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadDiagram = (chart: string) => {
|
const loadDiagram = (chart: string) => {
|
||||||
// use this function to display the diagramxml in the drawio editor
|
|
||||||
if (drawioRef.current) {
|
if (drawioRef.current) {
|
||||||
drawioRef.current.load({
|
drawioRef.current.load({
|
||||||
xml: chart,
|
xml: chart,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-screen bg-gray-100">
|
<div className="flex h-screen bg-gray-100">
|
||||||
<div className="w-2/3 p-1">
|
<div className="w-2/3 p-1">
|
||||||
@@ -34,7 +33,6 @@ export default function Home() {
|
|||||||
ref={drawioRef}
|
ref={drawioRef}
|
||||||
onExport={(data) => setChartXML(extractDiagramXML(data.data))}
|
onExport={(data) => setChartXML(extractDiagramXML(data.data))}
|
||||||
urlParameters={{
|
urlParameters={{
|
||||||
// ui: "kennedy",
|
|
||||||
spin: true,
|
spin: true,
|
||||||
libraries: false,
|
libraries: false,
|
||||||
saveAndExit: false,
|
saveAndExit: false,
|
||||||
@@ -42,8 +40,14 @@ export default function Home() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-1/3 p-1 border-gray-300 ">
|
<div className="w-1/3 p-1 border-gray-300">
|
||||||
<ChatPanel />
|
<ChatPanel
|
||||||
|
onDisplayChart={(xml) => loadDiagram(xml)}
|
||||||
|
onFetchChart={() => {
|
||||||
|
handleExport();
|
||||||
|
return chartXML;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,20 +1,41 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import type React from "react"
|
import type React from "react"
|
||||||
|
|
||||||
import { useRef, useEffect } from "react"
|
import { useRef, useEffect } from "react"
|
||||||
import { useChat } from "@ai-sdk/react"
|
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import { Input } from "@/components/ui/input"
|
import { Input } from "@/components/ui/input"
|
||||||
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"
|
||||||
import { Loader2, Send } from "lucide-react"
|
import { Loader2, Send } from "lucide-react"
|
||||||
|
import { useChat } from '@ai-sdk/react';
|
||||||
|
import { ToolInvocation } from 'ai';
|
||||||
|
|
||||||
|
|
||||||
export default function ChatPanel() {
|
interface ChatPanelProps {
|
||||||
const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat({
|
onDisplayChart: (xml: string) => void;
|
||||||
api: "/api/chat",
|
onFetchChart: () => string;
|
||||||
maxSteps: 5, // Allow multiple steps for complex diagram generation
|
}
|
||||||
|
|
||||||
|
export default function ChatPanel({ onDisplayChart, onFetchChart }: ChatPanelProps) {
|
||||||
|
const { messages, input, handleInputChange, handleSubmit, isLoading, error, addToolResult } = useChat({
|
||||||
|
async onToolCall({ toolCall }) {
|
||||||
|
console.log("Tool call:", toolCall);
|
||||||
|
if (toolCall.toolName === "display_flow_chart") {
|
||||||
|
const { xml } = toolCall.args as { xml: string };
|
||||||
|
onDisplayChart(xml);
|
||||||
|
} else if (toolCall.toolName === "fetch_flow_chart") {
|
||||||
|
const currentXML = onFetchChart();
|
||||||
|
console.log("Current XML:", currentXML);
|
||||||
|
addToolResult({
|
||||||
|
toolCallId: toolCall.toolCallId,
|
||||||
|
result: currentXML
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
console.error("Chat error:", error);
|
||||||
|
}
|
||||||
})
|
})
|
||||||
const messagesEndRef = useRef<HTMLDivElement>(null)
|
const messagesEndRef = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
@@ -25,10 +46,9 @@ export default function ChatPanel() {
|
|||||||
}
|
}
|
||||||
}, [messages])
|
}, [messages])
|
||||||
|
|
||||||
|
|
||||||
const onFormSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
const onFormSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
if (input.trim()) {
|
if (input.trim() && !isLoading) {
|
||||||
handleSubmit(e)
|
handleSubmit(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -42,7 +62,7 @@ export default function ChatPanel() {
|
|||||||
<ScrollArea className="h-full pr-4">
|
<ScrollArea className="h-full pr-4">
|
||||||
{messages.length === 0 ? (
|
{messages.length === 0 ? (
|
||||||
<div className="text-center text-gray-500 mt-8">
|
<div className="text-center text-gray-500 mt-8">
|
||||||
<p>Start a conversation to generate a diagram.</p>
|
<p>Start a conversation to generate or modify diagrams.</p>
|
||||||
<p className="text-sm mt-2">Try: "Create a flowchart for user authentication"</p>
|
<p className="text-sm mt-2">Try: "Create a flowchart for user authentication"</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
@@ -54,16 +74,21 @@ export default function ChatPanel() {
|
|||||||
>
|
>
|
||||||
{message.content}
|
{message.content}
|
||||||
</div>
|
</div>
|
||||||
{message.toolInvocations?.map((tool, index) => (
|
{(message as any).function_call && (
|
||||||
<div key={index} className="mt-2 text-left">
|
<div className="mt-2 text-left">
|
||||||
<div className="text-xs text-gray-500">
|
<div className="text-xs text-gray-500">
|
||||||
{tool.state === "call" ? "Generating diagram..." : "Diagram generated"}
|
Using tool: {(message as any).function_call.name}...
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
)}
|
||||||
</div>
|
</div>
|
||||||
))
|
))
|
||||||
)}
|
)}
|
||||||
|
{error && (
|
||||||
|
<div className="text-red-500 text-sm mt-2">
|
||||||
|
Error: {error.message}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div ref={messagesEndRef} />
|
<div ref={messagesEndRef} />
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
@@ -72,7 +97,7 @@ export default function ChatPanel() {
|
|||||||
<Input
|
<Input
|
||||||
value={input}
|
value={input}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
placeholder="Describe the diagram you want to create..."
|
placeholder="Describe what changes you want to make to the diagram..."
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
className="flex-grow"
|
className="flex-grow"
|
||||||
/>
|
/>
|
||||||
@@ -84,4 +109,3 @@ export default function ChatPanel() {
|
|||||||
</Card>
|
</Card>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user