chore: snapshot workflow migration progress

This commit is contained in:
2026-04-12 11:49:04 +02:00
parent 0cd02513d5
commit 3e810c74a3
163 changed files with 31774 additions and 2753 deletions
@@ -34,6 +34,32 @@ const EMPTY_FORM = {
lighting_only: false,
shadow_catcher_enabled: false,
camera_orbit: true,
workflow_input_schema_text: '[]',
}
function stringifyWorkflowInputSchema(value: unknown): string {
try {
return JSON.stringify(Array.isArray(value) ? value : [], null, 2)
} catch {
return '[]'
}
}
function parseWorkflowInputSchemaText(rawValue: unknown): unknown[] {
const text = typeof rawValue === 'string' ? rawValue.trim() : ''
if (!text) return []
let parsed: unknown
try {
parsed = JSON.parse(text)
} catch {
throw new Error('Workflow input schema must be valid JSON')
}
if (!Array.isArray(parsed)) {
throw new Error('Workflow input schema must be a JSON array')
}
return parsed
}
export default function RenderTemplateTable() {
@@ -43,7 +69,7 @@ export default function RenderTemplateTable() {
const [addFile, setAddFile] = useState<File | null>(null)
const [cloneBlendFrom, setCloneBlendFrom] = useState<string>('')
const [editingId, setEditingId] = useState<string | null>(null)
const [editDraft, setEditDraft] = useState<Partial<RenderTemplate>>({})
const [editDraft, setEditDraft] = useState<(Partial<RenderTemplate> & { workflow_input_schema_text?: string })>({})
const fileInputRef = useRef<HTMLInputElement>(null)
const reuploadRef = useRef<HTMLInputElement>(null)
const [reuploadId, setReuploadId] = useState<string | null>(null)
@@ -75,6 +101,7 @@ export default function RenderTemplateTable() {
fd.append('lighting_only', String(form.lighting_only))
fd.append('shadow_catcher_enabled', String(form.shadow_catcher_enabled))
fd.append('camera_orbit', String(form.camera_orbit))
fd.append('workflow_input_schema', JSON.stringify(parseWorkflowInputSchemaText(form.workflow_input_schema_text)))
return createRenderTemplate(fd)
},
onSuccess: () => {
@@ -85,7 +112,7 @@ export default function RenderTemplateTable() {
setCloneBlendFrom('')
setShowAdd(false)
},
onError: (e: any) => toast.error(e.response?.data?.detail || 'Failed to create template'),
onError: (e: any) => toast.error(e.response?.data?.detail || e.message || 'Failed to create template'),
})
const updateMut = useMutation({
@@ -96,7 +123,7 @@ export default function RenderTemplateTable() {
qc.invalidateQueries({ queryKey: ['render-templates'] })
setEditingId(null)
},
onError: (e: any) => toast.error(e.response?.data?.detail || 'Failed to update'),
onError: (e: any) => toast.error(e.response?.data?.detail || e.message || 'Failed to update'),
})
const deleteMut = useMutation({
@@ -128,6 +155,7 @@ export default function RenderTemplateTable() {
shadow_catcher_enabled: t.shadow_catcher_enabled,
camera_orbit: t.camera_orbit,
output_type_ids: t.output_type_ids ?? [],
workflow_input_schema: t.workflow_input_schema ?? [],
}),
onSuccess: () => {
toast.success('Template duplicated')
@@ -147,13 +175,19 @@ export default function RenderTemplateTable() {
lighting_only: t.lighting_only,
shadow_catcher_enabled: t.shadow_catcher_enabled,
camera_orbit: t.camera_orbit,
workflow_input_schema_text: stringifyWorkflowInputSchema(t.workflow_input_schema),
is_active: t.is_active,
})
}
function saveEdit() {
if (!editingId) return
updateMut.mutate({ id: editingId, data: editDraft as Record<string, unknown> })
const data: Record<string, unknown> = { ...editDraft }
if (Object.prototype.hasOwnProperty.call(editDraft, 'workflow_input_schema_text')) {
data.workflow_input_schema = parseWorkflowInputSchemaText(editDraft.workflow_input_schema_text)
delete data.workflow_input_schema_text
}
updateMut.mutate({ id: editingId, data })
}
// Render the edit form grid (shared between edit-row and add-row)
@@ -174,6 +208,9 @@ export default function RenderTemplateTable() {
if (field === 'lighting_only') return editDraft.lighting_only ?? t!.lighting_only
if (field === 'shadow_catcher_enabled') return editDraft.shadow_catcher_enabled ?? t!.shadow_catcher_enabled
if (field === 'camera_orbit') return editDraft.camera_orbit ?? t!.camera_orbit
if (field === 'workflow_input_schema_text') {
return editDraft.workflow_input_schema_text ?? stringifyWorkflowInputSchema(t!.workflow_input_schema)
}
if (field === 'is_active') return editDraft.is_active ?? t!.is_active
return (editDraft as any)[field] ?? (t as any)[field]
}
@@ -381,7 +418,27 @@ export default function RenderTemplateTable() {
)}
</div>
{/* Row 4: Active + Save/Cancel */}
<div className="mt-4">
<label className="block text-xs font-medium text-content-muted mb-1">
Workflow Input Schema (JSON)
</label>
<textarea
className="input-sm w-full min-h-36 font-mono text-xs"
value={String(val('workflow_input_schema_text') ?? '[]')}
onChange={(e) => set('workflow_input_schema_text', e.target.value)}
placeholder='[{"key":"studio_variant","label":"Studio Variant","type":"select","options":[{"value":"default","label":"Default"}]}]'
/>
<p className="mt-1 text-xs text-content-muted">
Defines additional `resolve_template` node inputs for this .blend template.
</p>
<p className="mt-1 text-xs text-content-muted">
Matching variants can be bound inside the template via markers like
`template-input:studio_variant=warm` or a `template_input=studio_variant=warm`
custom property on collections, objects, or worlds.
</p>
</div>
{/* Row 5: Active + Save/Cancel */}
<div className="flex items-center justify-between mt-4 pt-3 border-t border-border-light">
{isEdit ? (
<label className="flex items-center gap-2">