diff --git a/apps/dashboard/src/components/workflow/DescribeWorkflow.tsx b/apps/dashboard/src/components/workflow/DescribeWorkflow.tsx new file mode 100644 index 00000000..82763343 --- /dev/null +++ b/apps/dashboard/src/components/workflow/DescribeWorkflow.tsx @@ -0,0 +1,98 @@ +import { useState } from "react"; +import { Loader2, Sparkles } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import type { Deployment } from "@/hooks/useReadyDeployments"; +import { INFERENCE_URL } from "@/lib/api"; +import { getToken } from "@/lib/tokenStore"; +import { generateWorkflow, type PlanSource } from "@/lib/workflow/generate"; +import { NODE_KINDS } from "@/lib/workflow/nodeKinds"; +import type { Workflow } from "@/lib/workflow/types"; +import { cn } from "@/lib/utils"; + +const inputClass = + "w-full rounded-md border bg-background px-2 py-1.5 text-xs outline-none focus:ring-1 focus:ring-ember-500"; + +// The planner call goes through the LLM node itself so it shares the sandbox auth and reasoning stripping. +function complete(model: string) { + return async (system: string, user: string) => { + const out = await NODE_KINDS.llm.run( + { type: "text", text: user }, + { id: "planner", kind: "llm", position: { x: 0, y: 0 }, config: { model, systemPrompt: system } }, + { baseUrl: INFERENCE_URL, token: getToken(), fetch: window.fetch.bind(window), audioInputs: {} }, + ); + return out.type === "text" ? out.text : ""; + }; +} + +export function DescribeWorkflow({ + deployments, + onGenerated, +}: { + deployments: Deployment[]; + onGenerated: (workflow: Workflow) => void; +}) { + const models = deployments.filter((d) => d.model_type === "inference" && d.modelName !== "-").map((d) => d.modelName); + const [description, setDescription] = useState(""); + const [picked, setPicked] = useState(""); + const [busy, setBusy] = useState(false); + const [source, setSource] = useState(null); + const model = models.includes(picked) ? picked : models[0]; + + const submit = async () => { + if (!description.trim() || busy) return; + setBusy(true); + try { + const result = await generateWorkflow(description, model, model ? complete(model) : async () => ""); + setSource(result.source); + onGenerated(result.workflow); + } finally { + setBusy(false); + } + }; + + return ( +
+

Describe a workflow

+