Files
omniai-web/src/features/canvas/canvasWorkflowExecution.ts
T

129 lines
5.1 KiB
TypeScript
Raw Normal View History

2026-06-02 12:38:01 +08:00
import type { CreatePreviewTaskInput } from "../../api/webGenerationGateway";
import type { WebCanvasWorkflow, WebCanvasWorkflowNode, WebCanvasWorkflowTaskRef } from "../../types";
import { toHappyHorseDisplayModel } from "../../utils/happyHorseRouting";
import { resolveVideoRequestModel } from "../../utils/resolveVideoModel";
import { normalizeCanvasWorkflowSchema } from "./canvasWorkflowSchema";
export interface CanvasExecutionImageReference {
nodeId: string;
title: string;
url: string;
ossKey?: string | null;
}
export interface CanvasNodeExecutionContext {
node: WebCanvasWorkflowNode;
prompt: string;
imageReferences: CanvasExecutionImageReference[];
upstreamTaskRefs: WebCanvasWorkflowTaskRef[];
}
function getNodeText(node: WebCanvasWorkflowNode | undefined): string {
if (!node) return "";
return String(node.detail || node.params?.prompt || "").trim();
}
function getNodeAssetUrl(node: WebCanvasWorkflowNode | undefined): string {
return String(node?.assetRef?.url || node?.previewUrl || "").trim();
}
function incomingEdges(workflow: WebCanvasWorkflow, nodeId: string) {
return workflow.edges.filter((edge) => edge.target === nodeId);
}
export function resolveCanvasNodeExecutionContext(
workflowInput: WebCanvasWorkflow,
nodeId: string,
): CanvasNodeExecutionContext {
const workflow = normalizeCanvasWorkflowSchema(workflowInput);
const nodeById = new Map(workflow.nodes.map((node) => [node.id, node]));
const node = nodeById.get(nodeId);
if (!node) throw new Error(`Canvas node not found: ${nodeId}`);
const imageReferences: CanvasExecutionImageReference[] = [];
const upstreamPrompts: string[] = [];
const upstreamTaskRefs: WebCanvasWorkflowTaskRef[] = [];
const seenImages = new Set<string>();
incomingEdges(workflow, nodeId).forEach((edge) => {
const sourceNode = nodeById.get(edge.source);
if (!sourceNode) return;
if (sourceNode.taskRef) upstreamTaskRefs.push(sourceNode.taskRef);
const sourceKind = sourceNode.kind;
if (sourceKind === "image") {
const url = getNodeAssetUrl(sourceNode);
if (url && !seenImages.has(`${sourceNode.id}:${url}`)) {
seenImages.add(`${sourceNode.id}:${url}`);
imageReferences.push({
nodeId: sourceNode.id,
title: sourceNode.label,
url,
ossKey: sourceNode.assetRef?.ossKey || null,
});
}
return;
}
const text = getNodeText(sourceNode);
if (text) upstreamPrompts.push(text);
});
return {
node,
prompt: getNodeText(node) || upstreamPrompts.join("\n"),
imageReferences,
upstreamTaskRefs,
};
}
function normalizeDuration(value: unknown, fallback = 5): number {
const parsed = Number(String(value || "").replace(/s$/i, ""));
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
}
export function hasRunningCanvasTaskRef(node: WebCanvasWorkflowNode): boolean {
const status = node.taskRef?.status;
return status === "queued" || status === "pending" || status === "running";
}
export function buildCanvasVideoTaskInput(workflow: WebCanvasWorkflow, nodeId: string): CreatePreviewTaskInput {
const context = resolveCanvasNodeExecutionContext(workflow, nodeId);
const params = context.node.params || {};
const referenceUrls = context.imageReferences.map((item) => item.url);
const displayModel = toHappyHorseDisplayModel(String(params.model || workflow.settings.model || "happyhorse-1.0"));
2026-06-09 12:02:30 +08:00
const model = resolveVideoRequestModel({ model: displayModel, referenceUrls });
2026-06-02 12:38:01 +08:00
return {
title: context.node.label || "视频节点生成",
type: "video",
prompt: context.prompt || (referenceUrls.length ? "根据参考图片生成视频" : ""),
params: {
model,
ratio: String(params.aspectRatio || params.ratio || workflow.settings.ratio || "16:9"),
quality: String(params.resolution || workflow.settings.resolution || "720P"),
resolution: String(params.resolution || workflow.settings.resolution || "720P"),
duration: normalizeDuration(params.duration || workflow.settings.duration),
frameMode: params.videoMode === "firstlast" ? "start-end" : "omni",
referenceUrls: referenceUrls.length ? referenceUrls : undefined,
muted: false,
hasReferenceVideo: false,
},
};
}
export function buildCanvasImageTaskInput(workflow: WebCanvasWorkflow, nodeId: string): CreatePreviewTaskInput {
const context = resolveCanvasNodeExecutionContext(workflow, nodeId);
const params = context.node.params || {};
const referenceUrls = context.imageReferences.map((item) => item.url);
return {
title: context.node.label || "图片节点生成",
type: "image",
prompt: context.prompt || (referenceUrls.length ? "根据参考图片生成图片" : ""),
params: {
model: String(params.model || workflow.settings.model || "nano-banana-pro"),
ratio: String(params.aspectRatio || params.ratio || workflow.settings.ratio || "16:9"),
quality: String(params.imageSize || params.resolution || workflow.settings.resolution || "1K"),
gridMode: "single",
referenceUrls: referenceUrls.length ? referenceUrls : undefined,
},
};
}