83 lines
4.6 KiB
TypeScript
83 lines
4.6 KiB
TypeScript
import { nanoid } from "nanoid";
|
|
|
|
import { storyboardAssetRoleForNode } from "@/lib/canvas/canvas-storyboard-assets";
|
|
import { buildOrderedCanvasResourceReferences, canvasResourceMentionToken } from "@/lib/canvas/canvas-resource-references";
|
|
import { CanvasNodeType, type CanvasConnection, type CanvasNodeData, type StoryboardAssetBinding, type StoryboardRow } from "@/types/canvas";
|
|
|
|
export function storyboardRowReferenceNodeIds(
|
|
scriptNode: CanvasNodeData,
|
|
row: StoryboardRow,
|
|
nodes: CanvasNodeData[],
|
|
connections: CanvasConnection[],
|
|
includeFirstFrame: boolean,
|
|
targetNodeId?: string,
|
|
) {
|
|
const characterAssetIds = new Set((row.characters || []).map((character) => character.characterAssetId).filter((assetId): assetId is string => Boolean(assetId)));
|
|
const characterNodeIds = nodes
|
|
.filter((node) => node.metadata?.workflowKind === "character" && Boolean(node.metadata.characterAssetId) && characterAssetIds.has(node.metadata.characterAssetId!))
|
|
.map((node) => node.id);
|
|
const referenceIds = new Set([
|
|
...(scriptNode.metadata?.storyboard?.referenceNodeIds || []),
|
|
...(row.assetBindings || []).map((binding) => binding.nodeId),
|
|
...characterNodeIds,
|
|
...connections.filter((connection) => connection.toNodeId === scriptNode.id && connection.toHandleId === `row:${row.id}`).map((connection) => connection.fromNodeId),
|
|
...(targetNodeId ? connections.filter((connection) => !connection.relation && connection.toNodeId === targetNodeId).map((connection) => connection.fromNodeId) : []),
|
|
...(includeFirstFrame && row.imageNodeId ? [row.imageNodeId] : []),
|
|
]);
|
|
if (!includeFirstFrame && row.imageNodeId) referenceIds.delete(row.imageNodeId);
|
|
referenceIds.delete(scriptNode.id);
|
|
return Array.from(referenceIds).filter((nodeId) => nodes.some((node) => node.id === nodeId));
|
|
}
|
|
|
|
export function storyboardComposerContent(prompt: string, referenceNodeIds: string[], nodes: CanvasNodeData[]) {
|
|
const referenceIds = Array.from(new Set(referenceNodeIds));
|
|
const referenceNodes = referenceIds.flatMap((nodeId) => {
|
|
const node = nodes.find((candidate) => candidate.id === nodeId);
|
|
return node ? [node] : [];
|
|
});
|
|
const mentions = buildOrderedCanvasResourceReferences(referenceNodes).map(canvasResourceMentionToken);
|
|
return mentions.length ? [`参考资产:${mentions.join(" ")}`, prompt.trim()].filter(Boolean).join("\n") : prompt.trim();
|
|
}
|
|
|
|
export function reconcileStoryboardTargetConnections(
|
|
connections: CanvasConnection[],
|
|
scriptNode: CanvasNodeData,
|
|
row: StoryboardRow,
|
|
targetNodeId: string,
|
|
referenceNodeIds: string[],
|
|
) {
|
|
const desired = new Set(referenceNodeIds.filter((nodeId) => nodeId !== targetNodeId && nodeId !== scriptNode.id));
|
|
const next = connections.filter((connection) => {
|
|
if (connection.storyboardRowId !== row.id || connection.toNodeId !== targetNodeId) return true;
|
|
if (connection.relation === "storyboard-output") return connection.fromNodeId === scriptNode.id;
|
|
if (connection.relation === "storyboard-asset-reference") return desired.has(connection.fromNodeId);
|
|
return true;
|
|
});
|
|
if (!next.some((connection) => connection.fromNodeId === scriptNode.id && connection.toNodeId === targetNodeId && connection.fromHandleId === `row:${row.id}`)) {
|
|
next.push({ id: nanoid(), fromNodeId: scriptNode.id, toNodeId: targetNodeId, fromHandleId: `row:${row.id}`, relation: "storyboard-output", storyboardRowId: row.id });
|
|
}
|
|
desired.forEach((fromNodeId) => {
|
|
if (!next.some((connection) => connection.fromNodeId === fromNodeId && connection.toNodeId === targetNodeId)) {
|
|
next.push({ id: nanoid(), fromNodeId, toNodeId: targetNodeId, relation: "storyboard-asset-reference", storyboardRowId: row.id });
|
|
}
|
|
});
|
|
return next;
|
|
}
|
|
|
|
export function bindingForConnectedNode(node: CanvasNodeData): StoryboardAssetBinding | null {
|
|
const role = storyboardAssetRoleForNode(node);
|
|
return role ? { nodeId: node.id, role, priority: defaultRolePriority(role) } : null;
|
|
}
|
|
|
|
export function isStoryboardPreviewAsset(node: CanvasNodeData) {
|
|
return node.type === CanvasNodeType.Image || node.type === CanvasNodeType.Drawing || node.type === CanvasNodeType.Video || node.type === CanvasNodeType.Audio || node.metadata?.workflowKind === "character";
|
|
}
|
|
|
|
function defaultRolePriority(role: StoryboardAssetBinding["role"]) {
|
|
if (role === "character") return 100;
|
|
if (role === "environment") return 90;
|
|
if (role === "prop" || role === "weapon" || role === "wardrobe") return 80;
|
|
if (role === "motion" || role === "audio") return 70;
|
|
return 60;
|
|
}
|