feat: expose workflow execution modes in editor

This commit is contained in:
2026-04-07 11:10:58 +02:00
parent f9d4da52b9
commit 26046fb2d6
7 changed files with 147 additions and 16 deletions
+62 -3
View File
@@ -28,6 +28,7 @@ import {
type WorkflowDefinition,
type WorkflowConfig,
type WorkflowEdge,
type WorkflowExecutionMode,
type WorkflowPresetType,
type WorkflowParams,
type StepCategory,
@@ -257,6 +258,24 @@ function inferStepFromNodeType(type?: string): string {
return 'blender_still'
}
const EXECUTION_MODE_LABELS: Record<WorkflowExecutionMode, string> = {
legacy: 'Legacy',
graph: 'Graph',
shadow: 'Shadow',
}
const EXECUTION_MODE_BADGE_STYLES: Record<WorkflowExecutionMode, string> = {
legacy: 'bg-slate-100 text-slate-700 dark:bg-slate-900/40 dark:text-slate-300',
graph: 'bg-emerald-100 text-emerald-700 dark:bg-emerald-900/40 dark:text-emerald-300',
shadow: 'bg-amber-100 text-amber-700 dark:bg-amber-900/40 dark:text-amber-300',
}
const EXECUTION_MODE_HINTS: Record<WorkflowExecutionMode, string> = {
legacy: 'Preset dispatcher remains authoritative for production runs.',
graph: 'Production dispatch uses graph runtime with hard fallback to legacy on failure.',
shadow: 'Currently stored and exposed, but production dispatch still falls back to legacy until shadow parity lands.',
}
function workflowToGraph(
config: WorkflowConfig,
nodeDefinitionsByStep: Record<string, WorkflowNodeDefinition>,
@@ -608,6 +627,7 @@ function FlowCanvas({ workflow, onSave, isSaving }: FlowCanvasProps) {
const [nodes, setNodes, onNodesChange] = useNodesState(initNodes)
const [edges, setEdges, onEdgesChange] = useEdgesState(initEdges)
const [selectedNodeId, setSelectedNodeId] = useState<string | null>(null)
const [executionMode, setExecutionMode] = useState<WorkflowExecutionMode>(workflow.config.ui?.execution_mode ?? 'legacy')
const reactFlowWrapper = useRef<HTMLDivElement>(null)
const [reactFlowInstance, setReactFlowInstance] = useState<any>(null)
@@ -616,6 +636,7 @@ function FlowCanvas({ workflow, onSave, isSaving }: FlowCanvasProps) {
setNodes(graph.nodes)
setEdges(graph.edges)
setSelectedNodeId(null)
setExecutionMode(workflow.config.ui?.execution_mode ?? 'legacy')
}, [nodeDefinitionsData, setEdges, setNodes, workflow.config])
const onConnect = useCallback(
@@ -709,7 +730,10 @@ function FlowCanvas({ workflow, onSave, isSaving }: FlowCanvasProps) {
const handleSave = () => {
const updatedConfig: WorkflowConfig = {
version: workflow.config.version ?? 1,
ui: workflow.config.ui,
ui: {
...(workflow.config.ui ?? {}),
execution_mode: executionMode,
},
nodes: nodes.map(node => ({
id: node.id,
step: ((node.data as any).step as string | undefined) ?? inferStepFromNodeType(node.type),
@@ -753,7 +777,21 @@ function FlowCanvas({ workflow, onSave, isSaving }: FlowCanvasProps) {
{definition.label}
</div>
))}
<div className="ml-auto">
<div className="ml-auto flex items-center gap-3">
<label className="flex items-center gap-2 text-xs text-content-secondary whitespace-nowrap">
<span>Execution Mode</span>
<select
value={executionMode}
onChange={event => setExecutionMode(event.target.value as WorkflowExecutionMode)}
className="border border-border-default rounded-lg px-2.5 py-1.5 text-sm bg-surface text-content focus:outline-none focus:ring-2 focus:ring-accent"
>
{(['legacy', 'graph', 'shadow'] as WorkflowExecutionMode[]).map(mode => (
<option key={mode} value={mode}>
{EXECUTION_MODE_LABELS[mode]}
</option>
))}
</select>
</label>
<button
onClick={handleSave}
disabled={isSaving}
@@ -765,6 +803,10 @@ function FlowCanvas({ workflow, onSave, isSaving }: FlowCanvasProps) {
</div>
</div>
<div className="px-4 py-2 border-b border-border-default bg-surface-hover/40">
<p className="text-xs text-content-muted">{EXECUTION_MODE_HINTS[executionMode]}</p>
</div>
{/* Canvas + Sidepanel */}
<div className="flex flex-1 min-h-0">
<div ref={reactFlowWrapper} className="flex-1" onDrop={onDrop} onDragOver={onDragOver}>
@@ -922,6 +964,7 @@ export default function WorkflowEditor() {
)}
{workflows.map(wf => {
const presetType = getWorkflowPresetType(wf.config)
const executionMode = wf.config.ui?.execution_mode ?? 'legacy'
return (
<button
key={wf.id}
@@ -962,6 +1005,13 @@ export default function WorkflowEditor() {
>
{typeLabel[presetType]}
</span>
<span
className={`inline-block mt-1 ml-1 text-xs px-1.5 py-0.5 rounded-full font-medium ${
EXECUTION_MODE_BADGE_STYLES[executionMode]
}`}
>
{EXECUTION_MODE_LABELS[executionMode]}
</span>
{!wf.is_active && (
<span className="ml-1 text-xs text-content-muted">(inactive)</span>
)}
@@ -978,7 +1028,16 @@ export default function WorkflowEditor() {
<div>
<h1 className="text-xl font-semibold text-content">Workflow Editor</h1>
{selectedWorkflow && (
<p className="text-sm text-content-muted mt-0.5">{selectedWorkflow.name}</p>
<div className="mt-0.5 flex items-center gap-2">
<p className="text-sm text-content-muted">{selectedWorkflow.name}</p>
<span
className={`inline-block text-xs px-1.5 py-0.5 rounded-full font-medium ${
EXECUTION_MODE_BADGE_STYLES[selectedWorkflow.config.ui?.execution_mode ?? 'legacy']
}`}
>
{EXECUTION_MODE_LABELS[selectedWorkflow.config.ui?.execution_mode ?? 'legacy']}
</span>
</div>
)}
</div>
<button