diff --git a/web/src/components/workflows/ActionPalette.tsx b/web/src/components/workflows/ActionPalette.tsx index 2aaf4fb..471588d 100644 --- a/web/src/components/workflows/ActionPalette.tsx +++ b/web/src/components/workflows/ActionPalette.tsx @@ -1,5 +1,11 @@ import { useState, useMemo } from "react"; -import { Search, X, ChevronDown, ChevronRight, GripVertical } from "lucide-react"; +import { + Search, + X, + ChevronDown, + ChevronRight, + GripVertical, +} from "lucide-react"; import type { PaletteAction } from "@/types/workflow"; interface ActionPaletteProps { @@ -24,7 +30,7 @@ export default function ActionPalette({ action.label?.toLowerCase().includes(query) || action.ref?.toLowerCase().includes(query) || action.description?.toLowerCase().includes(query) || - action.pack_ref?.toLowerCase().includes(query) + action.pack_ref?.toLowerCase().includes(query), ); }, [actions, searchQuery]); @@ -38,7 +44,7 @@ export default function ActionPalette({ grouped.get(packRef)!.push(action); }); return new Map( - [...grouped.entries()].sort((a, b) => a[0].localeCompare(b[0])) + [...grouped.entries()].sort((a, b) => a[0].localeCompare(b[0])), ); }, [filteredActions]); @@ -55,7 +61,7 @@ export default function ActionPalette({ }; return ( -
+

Action Palette @@ -93,7 +99,9 @@ export default function ActionPalette({

) : filteredActions.length === 0 ? (
-

No actions match your search

+

+ No actions match your search +

); - } + }, )}
)} diff --git a/web/src/components/workflows/WorkflowInputsPanel.tsx b/web/src/components/workflows/WorkflowInputsPanel.tsx new file mode 100644 index 0000000..eb8d2d1 --- /dev/null +++ b/web/src/components/workflows/WorkflowInputsPanel.tsx @@ -0,0 +1,246 @@ +import { useState } from "react"; +import { Pencil, Plus, X, LogIn, LogOut } from "lucide-react"; +import SchemaBuilder from "@/components/common/SchemaBuilder"; +import type { ParamDefinition } from "@/types/workflow"; + +interface WorkflowInputsPanelProps { + parameters: Record; + output: Record; + onParametersChange: (parameters: Record) => void; + onOutputChange: (output: Record) => void; +} + +type ModalTarget = "parameters" | "output" | null; + +function ParamSummaryList({ + schema, + emptyMessage, + onEdit, +}: { + schema: Record; + emptyMessage: string; + onEdit: () => void; +}) { + const entries = Object.entries(schema); + + if (entries.length === 0) { + return ( + + ); + } + + return ( +
+ {entries.map(([name, def]) => ( +
+
+
+ + {name} + + {def.required && ( + * + )} +
+
+ {def.type} + {def.secret && ( + secret + )} + {def.default !== undefined && ( + + = {JSON.stringify(def.default)} + + )} +
+
+
+ ))} + +
+ ); +} + +export default function WorkflowInputsPanel({ + parameters, + output, + onParametersChange, + onOutputChange, +}: WorkflowInputsPanelProps) { + const [modalTarget, setModalTarget] = useState(null); + + // Draft state for the modal so changes only apply on confirm + const [draftSchema, setDraftSchema] = useState< + Record + >({}); + + const openModal = (target: ModalTarget) => { + if (target === "parameters") { + setDraftSchema({ ...parameters }); + } else if (target === "output") { + setDraftSchema({ ...output }); + } + setModalTarget(target); + }; + + const handleConfirm = () => { + if (modalTarget === "parameters") { + onParametersChange(draftSchema); + } else if (modalTarget === "output") { + onOutputChange(draftSchema); + } + setModalTarget(null); + }; + + const handleCancel = () => { + setModalTarget(null); + }; + + const modalLabel = + modalTarget === "parameters" ? "Input Parameters" : "Output"; + + return ( + <> +
+
+ {/* Input Parameters */} +
+
+
+ +

+ Inputs +

+
+ {Object.keys(parameters).length > 0 && ( + + {Object.keys(parameters).length} + + )} +
+

+ Referenced via{" "} + + {"{{ params. }}"} + +

+ openModal("parameters")} + /> +
+ + {/* Output Schema */} +
+
+
+ +

+ Output +

+
+ {Object.keys(output).length > 0 && ( + + {Object.keys(output).length} + + )} +
+

+ Values this workflow produces on completion. +

+ openModal("output")} + /> +
+
+
+ + {/* Full-screen modal for SchemaBuilder editing */} + {modalTarget && ( +
+ {/* Backdrop */} +
+ {/* Modal */} +
+ {/* Header */} +
+
+

+ {modalLabel} +

+

+ {modalTarget === "parameters" + ? "Define the inputs this workflow accepts when executed." + : "Define the outputs this workflow produces upon completion."} +

+
+ +
+ + {/* Body */} +
+ + setDraftSchema(schema as Record) + } + placeholder={ + modalTarget === "parameters" + ? '{"message": {"type": "string", "required": true}}' + : '{"result": {"type": "string"}}' + } + /> +
+ + {/* Footer */} +
+ + +
+
+
+ )} + + ); +} diff --git a/web/src/pages/actions/WorkflowBuilderPage.tsx b/web/src/pages/actions/WorkflowBuilderPage.tsx index 2a14140..b69b835 100644 --- a/web/src/pages/actions/WorkflowBuilderPage.tsx +++ b/web/src/pages/actions/WorkflowBuilderPage.tsx @@ -9,10 +9,13 @@ import { Code, LayoutDashboard, X, + Zap, + Settings2, } from "lucide-react"; import yaml from "js-yaml"; import type { WorkflowYamlDefinition } from "@/types/workflow"; import ActionPalette from "@/components/workflows/ActionPalette"; +import WorkflowInputsPanel from "@/components/workflows/WorkflowInputsPanel"; import WorkflowCanvas from "@/components/workflows/WorkflowCanvas"; import type { EdgeHoverInfo } from "@/components/workflows/WorkflowEdges"; import TaskInspector from "@/components/workflows/TaskInspector"; @@ -83,6 +86,7 @@ export default function WorkflowBuilderPage() { const [saveSuccess, setSaveSuccess] = useState(false); const [initialized, setInitialized] = useState(false); const [showYamlPreview, setShowYamlPreview] = useState(false); + const [sidebarTab, setSidebarTab] = useState<"actions" | "inputs">("actions"); const [highlightedTransition, setHighlightedTransition] = useState<{ taskId: string; transitionIndex: number; @@ -747,12 +751,59 @@ export default function WorkflowBuilderPage() {
) : ( <> - {/* Left: Action Palette */} - + {/* Left sidebar: tabbed Actions / Inputs */} +
+ {/* Tab header */} +
+ + +
+ + {/* Tab content */} + {sidebarTab === "actions" ? ( + + ) : ( + + setState((prev) => ({ ...prev, parameters })) + } + onOutputChange={(output) => + setState((prev) => ({ ...prev, output })) + } + /> + )} +
{/* Center: Canvas */}