Initial commit: OmniAI Web Frontend
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
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"));
|
||||
let model = resolveVideoRequestModel({ model: displayModel, referenceUrls });
|
||||
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,
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user