feat(canvas): 画布工作台 - 新增生成副本并修复大型画布性能、素材同步与删除校验

统一图片与视频副本的生成落点并保留参考连接。\n优化大型画布节点虚拟化、媒体懒加载、拖拽与详情进入路径。\n修复素材主版本 ID 同步及删除时的画布独立引用校验。
This commit is contained in:
ddCat committed 2026-09-01 14:57:05 +08:00
1 parent 434f63e153
commit 3f4a9ea3d0
45 files changed
+1348 -286

No files matched your search

@@ -74,17 +74,56 @@ export function buildAssetMentionReferences(assets: Asset[]): CanvasResourceRefe
});
}
export function buildCanvasResourceReferences(nodes: CanvasNodeData[], connections: CanvasConnection[], contextNodeId?: string | null) {
export function buildCanvasResourceReferences(nodes: CanvasNodeData[], connections: CanvasConnection[], contextNodeId?: string | null, targetNodes?: CanvasNodeData[]) {
const contextNodes = contextNodeId ? getMentionResourceNodes(contextNodeId, nodes, connections) : [];
const globalReferences = labelResourceNodes(nodes.filter(isResourceNode), false);
const sourceNodes = targetNodes ? uniqueCanvasNodes([...targetNodes, ...contextNodes]) : nodes;
const globalReferences = labelResourceNodes(sourceNodes.filter(isResourceNode), false);
const activeByNodeId = new Map(labelResourceNodes(contextNodes, true).map((reference) => [reference.nodeId, reference]));
return globalReferences.map((reference) => activeByNodeId.get(reference.nodeId) || reference);
}
function uniqueCanvasNodes(nodes: CanvasNodeData[]) {
const seen = new Set<string>();
return nodes.filter((node) => {
if (seen.has(node.id)) return false;
seen.add(node.id);
return true;
});
}
export function buildNodeMentionReferences(node: CanvasNodeData, nodes: CanvasNodeData[], connections: CanvasConnection[]) {
return labelResourceNodes(getMentionResourceNodes(node.id, nodes, connections), true);
}
export function buildCanvasNodeMentionReferenceMap(nodes: CanvasNodeData[], connections: CanvasConnection[], targetNodes: CanvasNodeData[] = nodes) {
const nodeById = new Map(nodes.map((node) => [node.id, node]));
const resourceInputsByTargetId = new Map<string, CanvasNodeData[]>();
const configTargetBySourceId = new Map<string, string>();
for (const connection of connections) {
const source = nodeById.get(connection.fromNodeId);
const target = nodeById.get(connection.toNodeId);
if (!source || !target) continue;
if (isResourceNode(source)) {
const inputs = resourceInputsByTargetId.get(target.id) || [];
inputs.push(source);
resourceInputsByTargetId.set(target.id, inputs);
}
if (target.type === CanvasNodeType.Config && !configTargetBySourceId.has(source.id)) {
configTargetBySourceId.set(source.id, target.id);
}
}
const referencesByNodeId = new Map<string, CanvasResourceReference[]>();
for (const node of targetNodes) {
const configTargetId = configTargetBySourceId.get(node.id);
const configInputs = configTargetId ? (resourceInputsByTargetId.get(configTargetId) || []).filter((input) => input.id !== node.id) : [];
const ownInputs = resourceInputsByTargetId.get(node.id) || [];
const inputs = configInputs.length ? configInputs : ownInputs.length ? ownInputs : isResourceNode(node) ? [node] : [];
referencesByNodeId.set(node.id, labelResourceNodes(inputs, true));
}
return referencesByNodeId;
}
export function buildOrderedCanvasResourceReferences(nodes: CanvasNodeData[], active = true) {
return labelResourceNodes(nodes.filter(isResourceNode), active);
}