s.runState.status)
- const activeNodeId = useWorkflowRunStore((s) => s.activeNodeId)
- const continueRun = useWorkflowRunStore((s) => s.continueRun)
- const isPaused = status === 'paused' && activeNodeId === nodeId
+ const { waitState, canContinue, isRunning, label, buttonClass, onContinue } = useWaitButton(nodeId)
return (
@@ -344,15 +346,29 @@ function WaitParamRow({ nodeId }: { nodeId: string }) {
Wait
- {isPaused ? (
+ {waitState ? (
) : (
@@ -442,7 +458,8 @@ function EmbeddedCanvas({ workflow, allExtensions }: {
))
}, [setNodes])
- const { setCurrentJob } = useAppStore()
+ const currentMeshUrl = useAppStore((s) => s.currentJob?.outputUrl)
+ const showToast = useAppStore((s) => s.showToast)
const { runState, run, cancel } = useWorkflowRunStore()
const isRunning = runState.status === 'running' || runState.status === 'paused'
@@ -453,33 +470,12 @@ function EmbeddedCanvas({ workflow, allExtensions }: {
if (out) updateNodeData(out.id, { params: { outputUrl: runState.outputUrl } })
}, [runState.status, runState.outputUrl])
- // Type mismatch detection β edge-based to support multi-input nodes
- const typeMismatch = useMemo(() => {
- // Build a map of what type each node produces
- const nodeOutput = new Map()
- for (const node of workflow.nodes) {
- if (node.type === 'imageNode') { nodeOutput.set(node.id, 'image'); continue }
- if (node.type === 'meshNode') { nodeOutput.set(node.id, 'mesh'); continue }
- if (node.type === 'textNode') { nodeOutput.set(node.id, 'text'); continue }
- if (node.type === 'extensionNode') {
- const ext = getWorkflowExtension(node.data.extensionId ?? '', allExtensions)
- if (ext) nodeOutput.set(node.id, ext.output)
- }
- }
- // For each extension node, check that every incoming edge carries an accepted type
- const extNodes = workflow.nodes.filter((n) => n.type === 'extensionNode')
- for (const node of extNodes) {
- const ext = getWorkflowExtension(node.data.extensionId ?? '', allExtensions)
- if (!ext) continue
- const accepted = ext.inputs ?? [ext.input]
- for (const edge of workflow.edges) {
- if (edge.target !== node.id) continue
- const srcType = nodeOutput.get(edge.source)
- if (srcType && !accepted.includes(srcType as any)) return true
- }
- }
- return false
- }, [workflow, allExtensions])
+ const preflightIssues = useMemo(() => {
+ const wf: Workflow = { ...workflow, nodes: nodes as WFNode[], edges: edges as WFEdge[] }
+ return validateWorkflowPreflight(wf, allExtensions, { currentMeshUrl })
+ }, [workflow, nodes, edges, allExtensions, currentMeshUrl])
+
+ const firstPreflightIssue = preflightIssues[0]?.message ?? null
// Ordered nodes for params list β only those marked showInGenerate
const sortedNodes = useMemo(
@@ -493,9 +489,13 @@ function EmbeddedCanvas({ workflow, allExtensions }: {
)
const handleGenerate = useCallback(() => {
+ if (firstPreflightIssue) {
+ showToast(firstPreflightIssue)
+ return
+ }
const wf: Workflow = { ...workflow, nodes: nodes as WFNode[], edges: edges as WFEdge[] }
run(wf, allExtensions)
- }, [nodes, edges, workflow, allExtensions, run])
+ }, [firstPreflightIssue, nodes, edges, workflow, allExtensions, run, showToast])
return (
@@ -542,13 +542,13 @@ function EmbeddedCanvas({ workflow, allExtensions }: {
{/* Footer */}
- {typeMismatch && !isRunning && (
-
-