|
|
@ -641,14 +641,22 @@ function buildReferenceSubjectNodePatch( |
|
|
node: WorkflowNode, |
|
|
node: WorkflowNode, |
|
|
beforeMaterials: ComposerReferenceMaterial[], |
|
|
beforeMaterials: ComposerReferenceMaterial[], |
|
|
afterMaterials: ComposerReferenceMaterial[], |
|
|
afterMaterials: ComposerReferenceMaterial[], |
|
|
options: { syncPrompt?: boolean } = {} |
|
|
options: { |
|
|
|
|
|
syncPrompt?: boolean; |
|
|
|
|
|
promptBeforeMaterials?: ComposerReferenceMaterial[]; |
|
|
|
|
|
promptAfterMaterials?: ComposerReferenceMaterial[]; |
|
|
|
|
|
} = {} |
|
|
): Partial<WorkflowNodeData> | null { |
|
|
): Partial<WorkflowNodeData> | null { |
|
|
const data = node.data as { parameters?: Record<string, unknown>; inputPrompt?: unknown; prompt?: unknown }; |
|
|
const data = node.data as { parameters?: Record<string, unknown>; inputPrompt?: unknown; prompt?: unknown }; |
|
|
const previousSubjects = readNodeReferenceSubjects(node); |
|
|
const previousSubjects = readNodeReferenceSubjects(node); |
|
|
const nextSubjects = buildReferenceSubjectListParameter(afterMaterials); |
|
|
const nextSubjects = buildReferenceSubjectListParameter(afterMaterials); |
|
|
const previousPrompt = readNodeInputPrompt(node); |
|
|
const previousPrompt = readNodeInputPrompt(node); |
|
|
const nextPrompt = options.syncPrompt |
|
|
const nextPrompt = options.syncPrompt |
|
|
? syncPromptReferenceMaterials(previousPrompt, beforeMaterials, afterMaterials) |
|
|
? syncPromptReferenceMaterials( |
|
|
|
|
|
previousPrompt, |
|
|
|
|
|
options.promptBeforeMaterials ?? beforeMaterials, |
|
|
|
|
|
options.promptAfterMaterials ?? afterMaterials |
|
|
|
|
|
) |
|
|
: previousPrompt; |
|
|
: previousPrompt; |
|
|
const nextSubType = isVideoGenerationNode(node) |
|
|
const nextSubType = isVideoGenerationNode(node) |
|
|
? normalizeVideoGenerationSubType( |
|
|
? normalizeVideoGenerationSubType( |
|
|
@ -803,11 +811,48 @@ function applyReferenceSubjectListPatch( |
|
|
const orderedAfterMaterials = options.preserveMaterialOrder |
|
|
const orderedAfterMaterials = options.preserveMaterialOrder |
|
|
? afterMaterials |
|
|
? afterMaterials |
|
|
: orderReferenceMaterials(afterMaterials, previousOrder); |
|
|
: orderReferenceMaterials(afterMaterials, previousOrder); |
|
|
const patch = buildReferenceSubjectNodePatch(node, orderedBeforeMaterials, orderedAfterMaterials, options); |
|
|
const patch = buildReferenceSubjectNodePatch(node, orderedBeforeMaterials, orderedAfterMaterials, { |
|
|
|
|
|
...options, |
|
|
|
|
|
promptBeforeMaterials: beforeMaterials, |
|
|
|
|
|
promptAfterMaterials: afterMaterials, |
|
|
|
|
|
}); |
|
|
if (!patch) return; |
|
|
if (!patch) return; |
|
|
get().updateNodeData(nodeId, patch); |
|
|
get().updateNodeData(nodeId, patch); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
function collectReferenceMaterialsBeforeRemovingSourceNodes( |
|
|
|
|
|
removedNodeIds: Set<string>, |
|
|
|
|
|
get: () => WorkflowStore |
|
|
|
|
|
): { beforeMaterialsByTarget: Map<string, ComposerReferenceMaterial[]>; removedEdges: WorkflowEdge[] } { |
|
|
|
|
|
const removedEdges = get().edges.filter( |
|
|
|
|
|
(edge) => removedNodeIds.has(edge.source) || removedNodeIds.has(edge.target) |
|
|
|
|
|
); |
|
|
|
|
|
const beforeMaterialsByTarget = new Map<string, ComposerReferenceMaterial[]>(); |
|
|
|
|
|
|
|
|
|
|
|
removedEdges.forEach((edge) => { |
|
|
|
|
|
if (!removedNodeIds.has(edge.source) || removedNodeIds.has(edge.target)) return; |
|
|
|
|
|
if (beforeMaterialsByTarget.has(edge.target)) return; |
|
|
|
|
|
beforeMaterialsByTarget.set(edge.target, buildNodeReferenceMaterials(edge.target, get)); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
return { beforeMaterialsByTarget, removedEdges }; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
function applyReferenceMaterialChangesAfterRemovingSourceNodes( |
|
|
|
|
|
beforeMaterialsByTarget: Map<string, ComposerReferenceMaterial[]>, |
|
|
|
|
|
get: () => WorkflowStore |
|
|
|
|
|
): void { |
|
|
|
|
|
beforeMaterialsByTarget.forEach((beforeMaterials, targetNodeId) => { |
|
|
|
|
|
if (!get().nodes.some((node) => node.id === targetNodeId)) return; |
|
|
|
|
|
get().applyNodeReferenceMaterialsChange( |
|
|
|
|
|
targetNodeId, |
|
|
|
|
|
beforeMaterials, |
|
|
|
|
|
buildNodeReferenceMaterials(targetNodeId, get), |
|
|
|
|
|
{ syncPrompt: true } |
|
|
|
|
|
); |
|
|
|
|
|
}); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
/** Capture current undoable state as a deep-cloned snapshot */ |
|
|
/** Capture current undoable state as a deep-cloned snapshot */ |
|
|
function captureUndoSnapshot(state: WorkflowStore): UndoSnapshot { |
|
|
function captureUndoSnapshot(state: WorkflowStore): UndoSnapshot { |
|
|
const cloned = clonePreservingStrings({ |
|
|
const cloned = clonePreservingStrings({ |
|
|
@ -1490,6 +1535,10 @@ const workflowStoreImpl: StateCreator<WorkflowStore> = (set, get) => ({ |
|
|
|
|
|
|
|
|
removeNode: (nodeId: string) => { |
|
|
removeNode: (nodeId: string) => { |
|
|
pushUndoCheckpoint(get, set); |
|
|
pushUndoCheckpoint(get, set); |
|
|
|
|
|
const { beforeMaterialsByTarget, removedEdges } = collectReferenceMaterialsBeforeRemovingSourceNodes( |
|
|
|
|
|
new Set([nodeId]), |
|
|
|
|
|
get |
|
|
|
|
|
); |
|
|
set((state) => ({ |
|
|
set((state) => ({ |
|
|
nodes: state.nodes.filter((node) => node.id !== nodeId), |
|
|
nodes: state.nodes.filter((node) => node.id !== nodeId), |
|
|
edges: state.edges.filter( |
|
|
edges: state.edges.filter( |
|
|
@ -1497,6 +1546,15 @@ const workflowStoreImpl: StateCreator<WorkflowStore> = (set, get) => ({ |
|
|
), |
|
|
), |
|
|
hasUnsavedChanges: true, |
|
|
hasUnsavedChanges: true, |
|
|
})); |
|
|
})); |
|
|
|
|
|
if (removedEdges.length > 0) { |
|
|
|
|
|
deleteCheckpointActive = true; |
|
|
|
|
|
try { |
|
|
|
|
|
clearStaleInputImages(removedEdges, get); |
|
|
|
|
|
applyReferenceMaterialChangesAfterRemovingSourceNodes(beforeMaterialsByTarget, get); |
|
|
|
|
|
} finally { |
|
|
|
|
|
deleteCheckpointActive = false; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
get().incrementManualChangeCount(); |
|
|
get().incrementManualChangeCount(); |
|
|
}, |
|
|
}, |
|
|
|
|
|
|
|
|
@ -1562,6 +1620,13 @@ const workflowStoreImpl: StateCreator<WorkflowStore> = (set, get) => ({ |
|
|
setTimeout(() => { deleteCheckpointActive = false; }, 0); |
|
|
setTimeout(() => { deleteCheckpointActive = false; }, 0); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const removedNodeIds = new Set( |
|
|
|
|
|
effectiveChanges.filter((change) => change.type === "remove").map((change) => change.id) |
|
|
|
|
|
); |
|
|
|
|
|
const { beforeMaterialsByTarget, removedEdges } = hasRemoveChange |
|
|
|
|
|
? collectReferenceMaterialsBeforeRemovingSourceNodes(removedNodeIds, get) |
|
|
|
|
|
: { beforeMaterialsByTarget: new Map<string, ComposerReferenceMaterial[]>(), removedEdges: [] }; |
|
|
|
|
|
|
|
|
set((state) => { |
|
|
set((state) => { |
|
|
const removedGroupIds = new Set<string>(); |
|
|
const removedGroupIds = new Set<string>(); |
|
|
for (const change of effectiveChanges) { |
|
|
for (const change of effectiveChanges) { |
|
|
@ -1647,6 +1712,8 @@ const workflowStoreImpl: StateCreator<WorkflowStore> = (set, get) => ({ |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
if (hasRemoveChange) { |
|
|
if (hasRemoveChange) { |
|
|
|
|
|
clearStaleInputImages(removedEdges, get); |
|
|
|
|
|
applyReferenceMaterialChangesAfterRemovingSourceNodes(beforeMaterialsByTarget, get); |
|
|
get().incrementManualChangeCount(); |
|
|
get().incrementManualChangeCount(); |
|
|
} |
|
|
} |
|
|
}, |
|
|
}, |
|
|
|