feat: expose workflow execution modes in editor
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user