diff --git a/package-lock.json b/package-lock.json index 00ca47dc..1506aa30 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.0", "dependencies": { "@ai-sdk/google": "^3.0.13", + "@ai-sdk/react": "^3.0.51", "@google/genai": "^1.30.0", "@tailwindcss/postcss": "^4.1.17", "@xyflow/react": "^12.9.3", @@ -115,6 +116,24 @@ "zod": "^3.25.76 || ^4.1.8" } }, + "node_modules/@ai-sdk/react": { + "version": "3.0.51", + "resolved": "https://registry.npmjs.org/@ai-sdk/react/-/react-3.0.51.tgz", + "integrity": "sha512-7nmCwEJM52NQZB4/ED8qJ4wbDg7EEWh94qJ7K9GSJxD6sWF3GOKrRZ5ivm4qNmKhY+JfCxCAxfghGY5mTKOsxw==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider-utils": "4.0.9", + "ai": "6.0.49", + "swr": "^2.2.5", + "throttleit": "2.1.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "react": "^18 || ~19.0.1 || ~19.1.2 || ^19.2.1" + } + }, "node_modules/@alloc/quick-lru": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", @@ -3458,7 +3477,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -4872,9 +4890,9 @@ } }, "node_modules/react": { - "version": "19.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", - "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", + "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -5387,6 +5405,19 @@ "node": ">=8" } }, + "node_modules/swr": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/swr/-/swr-2.3.8.tgz", + "integrity": "sha512-gaCPRVoMq8WGDcWj9p4YWzCMPHzE0WNl6W8ADIx9c3JBEIdMkJGMzW+uzXvxHMltwcYACr9jP+32H8/hgwMR7w==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3", + "use-sync-external-store": "^1.6.0" + }, + "peerDependencies": { + "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/symbol-tree": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", @@ -5413,6 +5444,18 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/throttleit": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-2.1.0.tgz", + "integrity": "sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/tinybench": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", diff --git a/package.json b/package.json index b4e5cb81..2481efa4 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ }, "dependencies": { "@ai-sdk/google": "^3.0.13", + "@ai-sdk/react": "^3.0.51", "@google/genai": "^1.30.0", "@tailwindcss/postcss": "^4.1.17", "@xyflow/react": "^12.9.3", diff --git a/src/components/ChatPanel.tsx b/src/components/ChatPanel.tsx new file mode 100644 index 00000000..f52b9e04 --- /dev/null +++ b/src/components/ChatPanel.tsx @@ -0,0 +1,128 @@ +"use client"; + +import { useRef, useEffect, useState } from "react"; +import { useChat } from "@ai-sdk/react"; +import { DefaultChatTransport } from "ai"; + +interface ChatPanelProps { + isOpen: boolean; + onClose: () => void; +} + +export function ChatPanel({ isOpen, onClose }: ChatPanelProps) { + const messagesEndRef = useRef(null); + const [input, setInput] = useState(""); + + const { messages, sendMessage, status } = useChat({ + transport: new DefaultChatTransport({ api: "/api/chat" }), + }); + + const isLoading = status === "streaming" || status === "submitted"; + + // Auto-scroll to bottom on new messages + useEffect(() => { + messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); + }, [messages]); + + if (!isOpen) return null; + + return ( +
+ {/* Header */} +
+

Workflow Assistant

+ +
+ + {/* Messages area */} +
+ {messages.length === 0 && ( +
+

Ask me anything about creating workflows!

+

e.g., "How do I create product photos with different backgrounds?"

+
+ )} + + {messages.map((message) => { + // Extract text from message parts + const textContent = message.parts + ?.filter((part): part is { type: "text"; text: string } => part.type === "text") + .map((part) => part.text) + .join("") || ""; + + return ( +
+
+

{textContent}

+
+
+ ); + })} + + {/* Loading indicator */} + {isLoading && ( +
+
+
+
+
+
+
+
+
+ )} + +
+
+ + {/* Input area */} +
{ + e.preventDefault(); + if (input.trim() && !isLoading) { + sendMessage({ text: input }); + setInput(""); + } + }} + className="p-3 border-t border-neutral-700" + > +
+ setInput(e.target.value)} + placeholder="Type a message..." + className="flex-1 bg-neutral-700 border border-neutral-600 rounded-lg px-3 py-2 text-sm text-neutral-200 placeholder-neutral-500 focus:outline-none focus:border-blue-500" + disabled={isLoading} + /> + +
+
+
+ ); +}