feat(C3): add React Flow workflow editor

frontend/src/pages/WorkflowEditor.tsx: full React Flow editor with
custom nodes (Input/Convert/Render/FFmpeg/Output), config sidepanel,
node palette with drag-drop, new workflow dialog.
frontend/src/api/workflows.ts: workflow CRUD API client.
@xyflow/react added to package.json dependencies.
Route /workflows + sidebar link for admin+pm.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-06 17:06:10 +01:00
parent 5da90b5434
commit 217555025f
5 changed files with 896 additions and 1 deletions
+80
View File
@@ -0,0 +1,80 @@
import api from './client'
export interface WorkflowDefinition {
id: string
name: string
output_type_id: string | null
config: WorkflowConfig
is_active: boolean
created_at: string
}
export interface WorkflowConfig {
type: 'still' | 'turntable' | 'multi_angle' | 'custom'
params: WorkflowParams
nodes?: WorkflowNode[]
}
export interface WorkflowParams {
render_engine?: 'cycles' | 'eevee'
samples?: number
resolution?: [number, number]
fps?: number
duration_s?: number
angles?: number[]
}
export interface WorkflowNode {
id: string
type: string
position: { x: number; y: number }
data: Record<string, unknown>
}
export interface WorkflowCreate {
name: string
output_type_id?: string | null
config: WorkflowConfig
is_active?: boolean
}
export interface WorkflowRun {
id: string
workflow_def_id: string | null
order_line_id: string | null
celery_task_id: string | null
status: 'pending' | 'running' | 'completed' | 'failed'
started_at: string | null
completed_at: string | null
error_message: string | null
created_at: string
node_results: WorkflowNodeResult[]
}
export interface WorkflowNodeResult {
id: string
node_name: string
status: string
output: Record<string, unknown> | null
log: string | null
duration_s: number | null
created_at: string
}
export const getWorkflows = (): Promise<WorkflowDefinition[]> =>
api.get('/workflows').then(r => r.data)
export const getWorkflow = (id: string): Promise<WorkflowDefinition> =>
api.get(`/workflows/${id}`).then(r => r.data)
export const createWorkflow = (data: WorkflowCreate): Promise<WorkflowDefinition> =>
api.post('/workflows', data).then(r => r.data)
export const updateWorkflow = (id: string, data: Partial<WorkflowCreate>): Promise<WorkflowDefinition> =>
api.put(`/workflows/${id}`, data).then(r => r.data)
export const deleteWorkflow = (id: string): Promise<void> =>
api.delete(`/workflows/${id}`).then(() => undefined)
export const getWorkflowRuns = (workflowId: string): Promise<WorkflowRun[]> =>
api.get(`/workflows/${workflowId}/runs`).then(r => r.data)