chore: snapshot workflow migration progress
This commit is contained in:
@@ -0,0 +1,296 @@
|
||||
import type { WorkflowNodeDefinition } from '../../api/workflows'
|
||||
import type { WorkflowGraphFamily } from './workflowNodeLibrary'
|
||||
import { getWorkflowModuleBundles, type WorkflowModuleBundleDefinition, type WorkflowModuleBundleId } from './workflowModuleBundles'
|
||||
import {
|
||||
getWorkflowReferenceBundles,
|
||||
type WorkflowReferenceBundleDefinition,
|
||||
type WorkflowReferenceBundleId,
|
||||
} from './workflowReferenceBundles'
|
||||
|
||||
export const STARTER_NODE_STEP_ORDER: Record<WorkflowGraphFamily, string[]> = {
|
||||
cad_file: [
|
||||
'resolve_step_path',
|
||||
'occ_object_extract',
|
||||
'occ_glb_export',
|
||||
'stl_cache_generate',
|
||||
'thumbnail_save',
|
||||
],
|
||||
order_line: [
|
||||
'order_line_setup',
|
||||
'resolve_template',
|
||||
'auto_populate_materials',
|
||||
'glb_bbox',
|
||||
'material_map_resolve',
|
||||
'blender_still',
|
||||
'output_save',
|
||||
'notify',
|
||||
],
|
||||
mixed: [],
|
||||
}
|
||||
|
||||
export const STARTER_PATH_TITLES: Record<WorkflowGraphFamily, string> = {
|
||||
cad_file: 'CAD intake assembly',
|
||||
order_line: 'Still-render assembly',
|
||||
mixed: 'Starter path',
|
||||
}
|
||||
|
||||
export const STARTER_PATH_DESCRIPTIONS: Record<WorkflowGraphFamily, string> = {
|
||||
cad_file: 'Use this module sequence to take a CAD file through extraction, preview generation, and published output.',
|
||||
order_line: 'Use this module sequence to keep the non-legacy still graph parallel to the legacy render path.',
|
||||
mixed: 'Reference sequence for assembling a workflow graph.',
|
||||
}
|
||||
|
||||
export type WorkflowAuthoringPriority = {
|
||||
title: string
|
||||
description: string
|
||||
}
|
||||
|
||||
export type WorkflowAuthoringFlowStep = {
|
||||
index: number
|
||||
title: string
|
||||
description: string
|
||||
}
|
||||
|
||||
export type WorkflowAuthoringBundleStatus<TBundle> = TBundle & {
|
||||
presentCount: number
|
||||
totalCount: number
|
||||
}
|
||||
|
||||
export type WorkflowStarterPathItem = {
|
||||
index: number
|
||||
definition: WorkflowNodeDefinition
|
||||
isPresent: boolean
|
||||
}
|
||||
|
||||
export type WorkflowAuthoringStageProgress = {
|
||||
id: string
|
||||
title: string
|
||||
description: string
|
||||
present: number
|
||||
total: number
|
||||
actionLabel: string
|
||||
actionKind: 'reference' | 'module' | 'step'
|
||||
bundleId?: WorkflowReferenceBundleId | WorkflowModuleBundleId
|
||||
step?: string
|
||||
}
|
||||
|
||||
export type WorkflowAuthoringPlan = {
|
||||
title: string
|
||||
description: string
|
||||
priorities: WorkflowAuthoringPriority[]
|
||||
authoringFlow: WorkflowAuthoringFlowStep[]
|
||||
referenceBundles: WorkflowAuthoringBundleStatus<WorkflowReferenceBundleDefinition>[]
|
||||
moduleBundles: WorkflowAuthoringBundleStatus<WorkflowModuleBundleDefinition>[]
|
||||
starterTitle: string
|
||||
starterDescription: string
|
||||
starterItems: WorkflowStarterPathItem[]
|
||||
starterCompletedCount: number
|
||||
stageProgress: WorkflowAuthoringStageProgress[]
|
||||
gapFillDefinitions: WorkflowNodeDefinition[]
|
||||
}
|
||||
|
||||
function haveSameStepSet(left: string[], right: string[]) {
|
||||
if (left.length !== right.length) return false
|
||||
const rightSet = new Set(right)
|
||||
return left.every(step => rightSet.has(step))
|
||||
}
|
||||
|
||||
function getAuthoringPriorities(graphFamily: WorkflowGraphFamily): WorkflowAuthoringPriority[] {
|
||||
if (graphFamily === 'order_line') {
|
||||
return [
|
||||
{
|
||||
title: 'Start with the Still Render Reference path',
|
||||
description: 'Insert the full non-legacy still production baseline first, then tune modules or individual nodes.',
|
||||
},
|
||||
{
|
||||
title: 'Swap stages with production modules',
|
||||
description: 'Use render and publish bundles to change whole stages without breaking graph-safe sequencing.',
|
||||
},
|
||||
{
|
||||
title: 'Use raw nodes last',
|
||||
description: 'Drop to legacy, bridge, or native graph nodes only after the production path already exists on canvas.',
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
if (graphFamily === 'cad_file') {
|
||||
return [
|
||||
{
|
||||
title: 'Start with the CAD intake assembly',
|
||||
description: 'Build the import and preview chain first so downstream consumers always receive consistent artifacts.',
|
||||
},
|
||||
{
|
||||
title: 'Extend with stage bundles',
|
||||
description: 'Use reusable intake modules before inserting isolated conversion or utility nodes.',
|
||||
},
|
||||
{
|
||||
title: 'Use raw nodes last',
|
||||
description: 'Only drop to individual nodes for edge cases or targeted debugging once the intake baseline is present.',
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
title: 'Split the graph by responsibility',
|
||||
description: 'Keep mixed graphs organized by building complete stages first, then add isolated nodes only for orchestration glue.',
|
||||
},
|
||||
{
|
||||
title: 'Prefer reusable modules over hand-wiring',
|
||||
description: 'Use grouped bundles whenever a stage can be expressed as a reusable production slice.',
|
||||
},
|
||||
{
|
||||
title: 'Use raw nodes last',
|
||||
description: 'Reach for the full catalog only after the baseline path is readable and stable.',
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
export function getWorkflowAuthoringFlow(graphFamily: WorkflowGraphFamily): WorkflowAuthoringFlowStep[] {
|
||||
const starterPathLabel = graphFamily === 'mixed' ? 'Starter Path' : STARTER_PATH_TITLES[graphFamily]
|
||||
|
||||
return [
|
||||
{
|
||||
index: 1,
|
||||
title: 'Reference Path',
|
||||
description:
|
||||
graphFamily === 'order_line'
|
||||
? 'Start from the canonical non-legacy still graph before changing individual stages.'
|
||||
: 'Start from the canonical path when you want a full baseline instead of assembling from scratch.',
|
||||
},
|
||||
{
|
||||
index: 2,
|
||||
title: 'Production Modules',
|
||||
description: 'Swap or extend whole production stages with reusable graph-safe bundles.',
|
||||
},
|
||||
{
|
||||
index: 3,
|
||||
title: starterPathLabel,
|
||||
description: 'Fill gaps one required step at a time and verify the minimum viable chain stays intact.',
|
||||
},
|
||||
{
|
||||
index: 4,
|
||||
title: 'Raw Node Catalog',
|
||||
description: 'Use individual nodes only for advanced authoring, experiments, or edge-case overrides.',
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
export function getWorkflowAuthoringPlan(
|
||||
definitions: WorkflowNodeDefinition[],
|
||||
graphFamily: WorkflowGraphFamily,
|
||||
activeSteps: string[],
|
||||
): WorkflowAuthoringPlan {
|
||||
const activeStepSet = new Set(activeSteps)
|
||||
const definitionsByStep = new Map(definitions.map(definition => [definition.step, definition]))
|
||||
const title = graphFamily === 'mixed' ? 'Guided Authoring' : STARTER_PATH_TITLES[graphFamily]
|
||||
const description =
|
||||
graphFamily === 'mixed'
|
||||
? 'Keep the graph readable by preferring complete paths and bundles before raw node-level edits.'
|
||||
: STARTER_PATH_DESCRIPTIONS[graphFamily]
|
||||
const referenceBundles = getWorkflowReferenceBundles(definitions, graphFamily)
|
||||
.map(bundle => ({
|
||||
...bundle,
|
||||
presentCount: bundle.stepIds.filter(step => activeStepSet.has(step)).length,
|
||||
totalCount: bundle.stepIds.length,
|
||||
}))
|
||||
const moduleBundles = getWorkflowModuleBundles(definitions, graphFamily)
|
||||
.map(bundle => ({
|
||||
...bundle,
|
||||
presentCount: bundle.stepIds.filter(step => activeStepSet.has(step)).length,
|
||||
totalCount: bundle.stepIds.length,
|
||||
}))
|
||||
|
||||
const starterDefinitions =
|
||||
graphFamily === 'mixed'
|
||||
? []
|
||||
: STARTER_NODE_STEP_ORDER[graphFamily]
|
||||
.map(step => definitionsByStep.get(step))
|
||||
.filter((definition): definition is WorkflowNodeDefinition => Boolean(definition))
|
||||
|
||||
const starterItems = starterDefinitions.map((definition, index) => ({
|
||||
index: index + 1,
|
||||
definition,
|
||||
isPresent: activeStepSet.has(definition.step),
|
||||
}))
|
||||
|
||||
const starterCompletedCount = starterItems.filter(item => item.isPresent).length
|
||||
const gapFillDefinitions = starterItems
|
||||
.filter(item => !item.isPresent)
|
||||
.map(item => item.definition)
|
||||
.slice(0, 3)
|
||||
|
||||
const stageProgress = (() => {
|
||||
if (graphFamily === 'mixed') return [] as WorkflowAuthoringStageProgress[]
|
||||
|
||||
const stages: WorkflowAuthoringStageProgress[] = []
|
||||
const referenceBundle = referenceBundles[0]
|
||||
if (referenceBundle) {
|
||||
stages.push({
|
||||
id: referenceBundle.id,
|
||||
title: referenceBundle.label,
|
||||
description: referenceBundle.description,
|
||||
present: referenceBundle.presentCount,
|
||||
total: referenceBundle.totalCount,
|
||||
actionLabel:
|
||||
referenceBundle.presentCount === 0
|
||||
? `Insert ${referenceBundle.shortLabel}`
|
||||
: `Reapply ${referenceBundle.shortLabel}`,
|
||||
actionKind: 'reference',
|
||||
bundleId: referenceBundle.id,
|
||||
})
|
||||
}
|
||||
|
||||
for (const bundle of moduleBundles) {
|
||||
if (referenceBundle && haveSameStepSet(bundle.stepIds, referenceBundle.stepIds)) {
|
||||
continue
|
||||
}
|
||||
if (bundle.presentCount === bundle.totalCount) continue
|
||||
|
||||
stages.push({
|
||||
id: bundle.id,
|
||||
title: bundle.label,
|
||||
description: bundle.description,
|
||||
present: bundle.presentCount,
|
||||
total: bundle.totalCount,
|
||||
actionLabel:
|
||||
bundle.presentCount === 0
|
||||
? `Insert ${bundle.shortLabel}`
|
||||
: `Complete ${bundle.shortLabel}`,
|
||||
actionKind: 'module',
|
||||
bundleId: bundle.id,
|
||||
})
|
||||
}
|
||||
|
||||
const nextMissingStarterDefinition = starterItems.find(item => !item.isPresent)?.definition
|
||||
if (nextMissingStarterDefinition) {
|
||||
stages.push({
|
||||
id: nextMissingStarterDefinition.step,
|
||||
title: `Next missing step: ${nextMissingStarterDefinition.label}`,
|
||||
description: 'Use a single-step insert only when you need to patch a gap without dropping an entire stage bundle.',
|
||||
present: 0,
|
||||
total: 1,
|
||||
actionLabel: `Add ${nextMissingStarterDefinition.label}`,
|
||||
actionKind: 'step',
|
||||
step: nextMissingStarterDefinition.step,
|
||||
})
|
||||
}
|
||||
|
||||
return stages
|
||||
})()
|
||||
|
||||
return {
|
||||
title,
|
||||
description,
|
||||
priorities: getAuthoringPriorities(graphFamily),
|
||||
authoringFlow: getWorkflowAuthoringFlow(graphFamily),
|
||||
referenceBundles,
|
||||
moduleBundles,
|
||||
starterTitle: STARTER_PATH_TITLES[graphFamily],
|
||||
starterDescription: STARTER_PATH_DESCRIPTIONS[graphFamily],
|
||||
starterItems,
|
||||
starterCompletedCount,
|
||||
stageProgress,
|
||||
gapFillDefinitions,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user