Initial commit: OmniAI Web Frontend
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,202 @@
|
||||
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,
|
||||
};
|
||||
}),
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user