Files
omniai-web/src/features/canvas/canvasWorkflowSchema.ts
T
2026-06-02 12:38:01 +08:00

203 lines
6.8 KiB
TypeScript

import type {
WebCanvasWorkflow,
WebCanvasWorkflowAssetRef,
WebCanvasWorkflowEdge,
WebCanvasWorkflowNode,
WebCanvasWorkflowPort,
WebCanvasWorkflowTaskRef,
} from "../../types";
export const CANVAS_WORKFLOW_SCHEMA_VERSION = 2;
type LegacyNodeKind = WebCanvasWorkflowNode["kind"];
export type CanvasWorkflowNodeKind = "text" | "image" | "video";
export interface CanvasTaskResultInput {
taskId: string;
status: WebCanvasWorkflowTaskRef["status"];
resultUrl?: string | null;
ossKey?: string | null;
mediaType?: string | null;
originalUrl?: string | null;
expiresAt?: string | null;
}
function isRecord(value: unknown): value is Record<string, unknown> {
return Boolean(value && typeof value === "object" && !Array.isArray(value));
}
function toStringValue(value: unknown, fallback = ""): string {
return typeof value === "string" ? value : fallback;
}
function normalizeNodeKind(kind: LegacyNodeKind): CanvasWorkflowNodeKind {
if (kind === "image") return "image";
if (kind === "video") return "video";
return "text";
}
export function getCanvasWorkflowNodeOutputType(kind: CanvasWorkflowNodeKind): WebCanvasWorkflowPort["type"] {
if (kind === "image") return "image";
if (kind === "video") return "video";
return "text";
}
export function getCanvasWorkflowNodeInputType(kind: CanvasWorkflowNodeKind): WebCanvasWorkflowPort["type"] {
if (kind === "image" || kind === "video") return "image";
return "text";
}
export function createCanvasWorkflowPort(
direction: "input" | "output",
kind: CanvasWorkflowNodeKind,
): WebCanvasWorkflowPort {
return {
id: direction === "output" ? "out" : "in",
type: direction === "output" ? getCanvasWorkflowNodeOutputType(kind) : getCanvasWorkflowNodeInputType(kind),
};
}
function inferMediaTypeFromNode(kind: CanvasWorkflowNodeKind): string {
if (kind === "image") return "image";
if (kind === "video") return "video";
return "text";
}
function normalizeAssetRef(node: WebCanvasWorkflowNode, normalizedKind: CanvasWorkflowNodeKind): WebCanvasWorkflowAssetRef | null {
const metadata = isRecord(node.metadata) ? node.metadata : {};
const candidate = isRecord(node.assetRef)
? node.assetRef
: isRecord(metadata.assetRef)
? metadata.assetRef
: null;
const url = toStringValue(candidate?.url, node.previewUrl || "");
if (!url) return null;
return {
url,
ossKey: toStringValue(candidate?.ossKey) || null,
mediaType: toStringValue(candidate?.mediaType, inferMediaTypeFromNode(normalizedKind)),
sourceTaskId: toStringValue(candidate?.sourceTaskId, toStringValue(metadata.sourceTaskId)) || null,
originalUrl: toStringValue(candidate?.originalUrl) || null,
expiresAt: toStringValue(candidate?.expiresAt) || null,
};
}
function normalizeTaskRef(node: WebCanvasWorkflowNode, assetRef: WebCanvasWorkflowAssetRef | null): WebCanvasWorkflowTaskRef | null {
const metadata = isRecord(node.metadata) ? node.metadata : {};
const candidate = isRecord(node.taskRef)
? node.taskRef
: isRecord(metadata.taskRef)
? metadata.taskRef
: null;
const taskId = toStringValue(candidate?.taskId, assetRef?.sourceTaskId || toStringValue(metadata.sourceTaskId));
if (!taskId) return null;
const status = toStringValue(candidate?.status, "completed") as WebCanvasWorkflowTaskRef["status"];
return {
taskId,
status,
resultUrl: toStringValue(candidate?.resultUrl, assetRef?.url || node.previewUrl || "") || null,
updatedAt: toStringValue(candidate?.updatedAt) || null,
};
}
function normalizeNodeParams(node: WebCanvasWorkflowNode): Record<string, unknown> {
const metadata = isRecord(node.metadata) ? node.metadata : {};
const params = isRecord(node.params) ? node.params : {};
const reserved = new Set(["assetRef", "taskRef", "sourceTaskId"]);
const metadataParams = Object.fromEntries(Object.entries(metadata).filter(([key]) => !reserved.has(key)));
return {
...metadataParams,
...params,
};
}
export function normalizeCanvasWorkflowNode(node: WebCanvasWorkflowNode): WebCanvasWorkflowNode {
const kind = normalizeNodeKind(node.kind);
const assetRef = normalizeAssetRef(node, kind);
const taskRef = normalizeTaskRef(node, assetRef);
const inputs = Array.isArray(node.inputs) && node.inputs.length ? node.inputs : [createCanvasWorkflowPort("input", kind)];
const outputs = Array.isArray(node.outputs) && node.outputs.length ? node.outputs : [createCanvasWorkflowPort("output", kind)];
return {
...node,
kind,
inputs,
outputs,
assetRef,
taskRef,
params: normalizeNodeParams(node),
previewUrl: assetRef?.url || node.previewUrl,
};
}
export function normalizeCanvasWorkflowEdge(
edge: WebCanvasWorkflowEdge,
nodeById: Map<string, WebCanvasWorkflowNode>,
): WebCanvasWorkflowEdge {
const sourceNode = nodeById.get(edge.source);
const targetNode = nodeById.get(edge.target);
const sourcePort = edge.sourcePort || sourceNode?.outputs?.[0] || createCanvasWorkflowPort("output", "text");
const targetPort = edge.targetPort || targetNode?.inputs?.[0] || createCanvasWorkflowPort("input", "text");
return {
...edge,
sourcePort,
targetPort,
};
}
export function normalizeCanvasWorkflowSchema(workflow: WebCanvasWorkflow): WebCanvasWorkflow {
const nodes = workflow.nodes.map(normalizeCanvasWorkflowNode);
const nodeById = new Map(nodes.map((node) => [node.id, node]));
return {
...workflow,
schemaVersion: CANVAS_WORKFLOW_SCHEMA_VERSION,
nodes,
edges: workflow.edges.map((edge) => normalizeCanvasWorkflowEdge(edge, nodeById)),
};
}
export function createCanvasAssetRefFromTaskResult(
kind: CanvasWorkflowNodeKind,
result: CanvasTaskResultInput,
): WebCanvasWorkflowAssetRef | null {
const url = String(result.resultUrl || "").trim();
if (!url) return null;
return {
url,
ossKey: result.ossKey || null,
mediaType: result.mediaType || inferMediaTypeFromNode(kind),
sourceTaskId: result.taskId,
originalUrl: result.originalUrl || null,
expiresAt: result.expiresAt || null,
};
}
export function applyCanvasNodeTaskResult(
workflow: WebCanvasWorkflow,
nodeId: string,
result: CanvasTaskResultInput,
): WebCanvasWorkflow {
const normalized = normalizeCanvasWorkflowSchema(workflow);
return {
...normalized,
nodes: normalized.nodes.map((node) => {
if (node.id !== nodeId) return node;
const kind = normalizeNodeKind(node.kind);
const assetRef = createCanvasAssetRefFromTaskResult(kind, result);
return {
...node,
assetRef: assetRef || node.assetRef || null,
taskRef: {
taskId: result.taskId,
status: result.status,
resultUrl: result.resultUrl || assetRef?.url || node.taskRef?.resultUrl || null,
updatedAt: new Date().toISOString(),
},
previewUrl: assetRef?.url || result.resultUrl || node.previewUrl,
};
}),
};
}