mirror of
https://github.com/basketikun/infinite-canvas.git
synced 2026-08-05 17:04:27 +08:00
129 lines
5.7 KiB
TypeScript
129 lines
5.7 KiB
TypeScript
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<CanvasResourceKind, number> = { 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<CanvasResourceKind, number> = { 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;
|
|
}
|