diff --git a/src/components/ChatPanel.tsx b/src/components/ChatPanel.tsx index 1e4658b8..9da30f22 100644 --- a/src/components/ChatPanel.tsx +++ b/src/components/ChatPanel.tsx @@ -22,7 +22,12 @@ export function ChatPanel({ isOpen, onClose, onBuildWorkflow, isBuildingWorkflow const messagesEndRef = useRef(null); const [input, setInput] = useState(""); - // Create a custom fetch function that includes workflowState + // Use a ref so the fetch closure always reads the latest workflowState + // without needing to re-create the transport + const workflowStateRef = useRef(workflowState); + workflowStateRef.current = workflowState; + + // Stable fetch function that reads workflowState from ref const customFetch = useCallback(async (input: RequestInfo | URL, init?: RequestInit) => { if (!init?.body) { return fetch(input, init); @@ -31,17 +36,19 @@ export function ChatPanel({ isOpen, onClose, onBuildWorkflow, isBuildingWorkflow const body = JSON.parse(init.body as string); const bodyWithWorkflow = { ...body, - workflowState, + workflowState: workflowStateRef.current, }; return fetch(input, { ...init, body: JSON.stringify(bodyWithWorkflow), }); - }, [workflowState]); + }, []); + + const [transport] = useState(() => new DefaultChatTransport({ api: "/api/chat", fetch: customFetch })); const { messages, sendMessage, setMessages, status } = useChat({ - transport: new DefaultChatTransport({ api: "/api/chat", fetch: customFetch }), + transport, }); const isLoading = status === "streaming" || status === "submitted"; @@ -57,22 +64,20 @@ export function ChatPanel({ isOpen, onClose, onBuildWorkflow, isBuildingWorkflow if (!("state" in part) || !("toolCallId" in part) || !("input" in part)) return; if (part.state !== "output-available") return; - const callId = part.toolCallId; + const callId = (part as Record).toolCallId as string; if (processedToolCalls.current.has(callId)) return; processedToolCalls.current.add(callId); const toolName = part.type.replace("tool-", ""); if (toolName === "createWorkflow" && onBuildWorkflow) { - // Extract description from tool args (input is the args) - const description = (part.input as { description?: string }).description; + const description = ((part as Record).input as { description?: string }).description; if (description) { onBuildWorkflow(description); } } if (toolName === "editWorkflow" && onApplyEdits) { - // Extract operations from tool args (input is the args) - const operations = (part.input as { operations?: EditOperation[] }).operations; + const operations = ((part as Record).input as { operations?: EditOperation[] }).operations; if (operations) { onApplyEdits(operations); } diff --git a/src/lib/chat/tools.ts b/src/lib/chat/tools.ts index eff3b522..b7e658b5 100644 --- a/src/lib/chat/tools.ts +++ b/src/lib/chat/tools.ts @@ -108,7 +108,18 @@ ${formatContextForPrompt(workflowContext)}`; - Use **editWorkflow** when the user wants to ADD, REMOVE, CHANGE, or MODIFY nodes/connections in the CURRENT workflow. - Always explain what you're about to do BEFORE calling a tool. - When editing, reference nodes by their ID from the current workflow state. -- After editing, summarize what changed.`; +- After editing, summarize what changed. + +## EDITABLE NODE PROPERTIES + +When using editWorkflow with updateNode, you MUST use these exact property names in the data object: + +- **prompt** node: \`{ "prompt": "the text" }\` +- **nanoBanana** (Generate Image) node: \`{ "resolution": "1K"|"2K"|"4K", "aspectRatio": "1:1"|"2:3"|"3:2"|"3:4"|"4:3"|"4:5"|"5:4"|"9:16"|"16:9"|"21:9", "useGoogleSearch": true|false }\` +- **llmGenerate** node: \`{ "temperature": 0-2, "maxTokens": 256-16384 }\` +- **Any node** title: \`{ "customTitle": "New Name" }\` + +Do NOT use "text", "content", or other guessed property names. Use ONLY the exact names listed above.`; return baseDomainExpertise + contextSection + toolUsageRules; } @@ -130,8 +141,7 @@ export function createChatTools(nodeIds: string[]) { .string() .describe("The helpful answer to the user question"), }), - // No execute function - this is a "generate" tool pattern - // The LLM provides the answer directly in the tool call result + execute: async ({ answer }) => ({ answer }), }), createWorkflow: tool({ @@ -142,7 +152,7 @@ export function createChatTools(nodeIds: string[]) { .string() .describe("Description of what the workflow should do"), }), - // No execute function - the client handles calling quickstart API + execute: async ({ description }) => ({ description }), }), editWorkflow: tool({ @@ -199,7 +209,7 @@ export function createChatTools(nodeIds: string[]) { "Brief explanation of what changes are being made and why" ), }), - // No execute function - results are returned to client for application + execute: async ({ operations, explanation }) => ({ operations, explanation }), }), }; }