import { imageReferenceLabel } from "@/lib/image-reference-prompt"; import { seedanceReferenceLabel } from "@/lib/seedance-video"; import { CanvasNodeType, type CanvasConnection, type CanvasNodeData } from "../types"; export type CanvasResourceKind = "image" | "video" | "audio" | "text"; export type CanvasResourceReference = { id: string; nodeId: string; kind: CanvasResourceKind; label: string; title: string; previewUrl?: string; text?: string; active: boolean; }; type MentionInput = { nodeId: string; type: CanvasResourceKind; title: string; text?: string; image?: { dataUrl: string }; video?: { url: string }; audio?: { url: string }; }; export function buildCanvasResourceReferences(nodes: CanvasNodeData[], connections: CanvasConnection[], contextNodeId?: string | null) { const contextNodes = contextNodeId ? getMentionResourceNodes(contextNodeId, nodes, connections) : []; const globalReferences = labelResourceNodes(nodes.filter(isResourceNode), false); const activeByNodeId = new Map(labelResourceNodes(contextNodes, true).map((reference) => [reference.nodeId, reference])); return globalReferences.map((reference) => activeByNodeId.get(reference.nodeId) || reference); } export function buildNodeMentionReferences(node: CanvasNodeData, nodes: CanvasNodeData[], connections: CanvasConnection[]) { return labelResourceNodes(getMentionResourceNodes(node.id, nodes, connections), true); } export function buildInputMentionReferences(inputs: MentionInput[]) { const counts: Record = { image: 0, video: 0, audio: 0, text: 0 }; return inputs.map((input): CanvasResourceReference => { const index = counts[input.type]++; return { id: input.nodeId, nodeId: input.nodeId, kind: input.type, label: labelForKind(input.type, index), title: input.title || labelForKind(input.type, index), previewUrl: input.image?.dataUrl || input.video?.url || input.audio?.url, text: input.text, active: true, }; }); } export function getMentionResourceNodes(nodeId: string, nodes: CanvasNodeData[], connections: CanvasConnection[]) { const configInputs = getConnectedConfigResourceNodes(nodeId, nodes, connections); if (configInputs.length) return configInputs; const ownInputs = getContextResourceNodes(nodeId, nodes, connections); if (ownInputs.length) return ownInputs; const node = nodes.find((item) => item.id === nodeId); return node && isResourceNode(node) ? [node] : []; } export function getGenerationResourceNodes(nodeId: string, nodes: CanvasNodeData[], connections: CanvasConnection[]) { const configInputs = getConnectedConfigResourceNodes(nodeId, nodes, connections); if (configInputs.length) return configInputs; const ownInputs = getContextResourceNodes(nodeId, nodes, connections); if (ownInputs.length) return ownInputs; return []; } function getContextResourceNodes(nodeId: string, nodes: CanvasNodeData[], connections: CanvasConnection[]) { const target = nodes.find((node) => node.id === nodeId); const upstreamNodes = connections .filter((connection) => connection.toNodeId === nodeId) .map((connection) => nodes.find((node) => node.id === connection.fromNodeId)) .filter((node): node is CanvasNodeData => Boolean(node && isResourceNode(node))); const order = target?.metadata?.inputOrder || []; return [...order.map((id) => upstreamNodes.find((node) => node.id === id)).filter((node): node is CanvasNodeData => Boolean(node)), ...upstreamNodes.filter((node) => !order.includes(node.id))]; } function getConnectedConfigResourceNodes(nodeId: string, nodes: CanvasNodeData[], connections: CanvasConnection[]) { const configConnection = connections.find((connection) => connection.fromNodeId === nodeId && nodes.find((node) => node.id === connection.toNodeId)?.type === CanvasNodeType.Config); if (!configConnection) return []; return getContextResourceNodes(configConnection.toNodeId, nodes, connections).filter((node) => node.id !== nodeId); } function labelResourceNodes(nodes: CanvasNodeData[], active: boolean) { const counts: Record = { image: 0, video: 0, audio: 0, text: 0 }; return nodes.flatMap((node): CanvasResourceReference[] => { const kind = resourceKind(node); if (!kind) return []; const index = counts[kind]++; const label = labelForKind(kind, index); return [ { id: node.id, nodeId: node.id, kind, label, title: node.title || label, previewUrl: node.metadata?.content, text: node.type === CanvasNodeType.Text ? node.metadata?.content || node.metadata?.prompt : undefined, active, }, ]; }); } function labelForKind(kind: CanvasResourceKind, index: number) { if (kind === "image") return imageReferenceLabel(index); if (kind === "video") return seedanceReferenceLabel("video", index); if (kind === "audio") return seedanceReferenceLabel("audio", index); return `文本${index + 1}`; } function isResourceNode(node: CanvasNodeData) { return Boolean(resourceKind(node)); } function resourceKind(node: CanvasNodeData): CanvasResourceKind | null { if (node.type === CanvasNodeType.Image && node.metadata?.content) return "image"; if (node.type === CanvasNodeType.Video && node.metadata?.content) return "video"; if (node.type === CanvasNodeType.Audio && node.metadata?.content) return "audio"; if (node.type === CanvasNodeType.Text && (node.metadata?.content || node.metadata?.prompt)) return "text"; return null; }