133 lines
5.3 KiB
TypeScript
133 lines
5.3 KiB
TypeScript
import { getDataUrlByteSize } from "@/lib/image-utils";
|
|
import type { Asset, AssetCategory, NewAsset } from "@/stores/use-asset-store";
|
|
import { CanvasNodeType, type CanvasNodeData } from "@/types/canvas";
|
|
|
|
export type CanvasAssetSource = "canvas-generation" | "canvas-upload" | "canvas-manual";
|
|
|
|
type CanvasNodeAssetOptions = {
|
|
canvasId: string;
|
|
source: CanvasAssetSource;
|
|
taskId?: string;
|
|
};
|
|
|
|
export function canvasNodeToAsset(node: CanvasNodeData, options: CanvasNodeAssetOptions): NewAsset | null {
|
|
const content = node.metadata?.content?.trim();
|
|
if (!content) return null;
|
|
const title = node.metadata?.prompt?.slice(0, 24) || node.title || canvasAssetFallbackTitle(node.type);
|
|
const metadata = {
|
|
source: options.source,
|
|
canvasId: options.canvasId,
|
|
nodeId: node.id,
|
|
taskId: options.taskId || node.metadata?.taskId,
|
|
prompt: node.metadata?.prompt,
|
|
resourceKey: node.metadata?.storageKey,
|
|
};
|
|
const base = {
|
|
title,
|
|
coverUrl: node.type === CanvasNodeType.Image ? content : "",
|
|
tags: node.metadata?.assetTags || [],
|
|
category: canvasNodeAssetCategory(node),
|
|
status: "confirmed" as const,
|
|
source: "Canvas",
|
|
metadata,
|
|
};
|
|
|
|
if (node.type === CanvasNodeType.Text) {
|
|
return { ...base, kind: "text", data: { content } };
|
|
}
|
|
if (node.type === CanvasNodeType.Image) {
|
|
const dataUrl = node.metadata?.storageKey ? "" : content;
|
|
return {
|
|
...base,
|
|
kind: "image",
|
|
data: {
|
|
dataUrl,
|
|
storageKey: node.metadata?.storageKey,
|
|
width: node.metadata?.naturalWidth || node.width,
|
|
height: node.metadata?.naturalHeight || node.height,
|
|
bytes: node.metadata?.bytes || getDataUrlByteSize(dataUrl),
|
|
mimeType: node.metadata?.mimeType || "image/png",
|
|
},
|
|
};
|
|
}
|
|
if (node.type === CanvasNodeType.Video) {
|
|
return {
|
|
...base,
|
|
kind: "video",
|
|
data: {
|
|
url: content,
|
|
storageKey: node.metadata?.storageKey,
|
|
width: node.metadata?.naturalWidth || node.width,
|
|
height: node.metadata?.naturalHeight || node.height,
|
|
durationMs: node.metadata?.durationMs,
|
|
bytes: node.metadata?.bytes || 0,
|
|
mimeType: node.metadata?.mimeType || "video/mp4",
|
|
},
|
|
};
|
|
}
|
|
if (node.type === CanvasNodeType.Audio) {
|
|
return {
|
|
...base,
|
|
kind: "audio",
|
|
data: {
|
|
url: content,
|
|
storageKey: node.metadata?.storageKey,
|
|
durationMs: node.metadata?.durationMs,
|
|
bytes: node.metadata?.bytes || 0,
|
|
mimeType: node.metadata?.mimeType || "audio/mpeg",
|
|
},
|
|
};
|
|
}
|
|
return null;
|
|
}
|
|
|
|
export function findCanvasNodeAsset(assets: Asset[], node: CanvasNodeData, canvasId: string, taskId?: string) {
|
|
const explicitId = node.metadata?.assetId;
|
|
if (explicitId) {
|
|
const explicit = assets.find((asset) => asset.id === explicitId);
|
|
if (explicit && sameAssetResource(explicit, node)) return explicit;
|
|
}
|
|
const generationTaskId = taskId || node.metadata?.taskId;
|
|
return assets.find((asset) => {
|
|
if (generationTaskId && asset.metadata?.taskId === generationTaskId && asset.metadata?.nodeId === node.id) return true;
|
|
if (asset.metadata?.nodeId !== node.id) return false;
|
|
const sourceCanvasId = asset.metadata?.canvasId;
|
|
if (sourceCanvasId !== canvasId && (sourceCanvasId || asset.metadata?.source !== "canvas-generation")) return false;
|
|
const nodeResourceKey = node.metadata?.storageKey;
|
|
const assetResourceKey = typeof asset.metadata?.resourceKey === "string" ? asset.metadata.resourceKey : assetStorageKey(asset);
|
|
return !nodeResourceKey || !assetResourceKey || nodeResourceKey === assetResourceKey;
|
|
});
|
|
}
|
|
|
|
function sameAssetResource(asset: Asset, node: CanvasNodeData) {
|
|
const nodeResourceKey = node.metadata?.storageKey;
|
|
const assetResourceKey = typeof asset.metadata?.resourceKey === "string" ? asset.metadata.resourceKey : assetStorageKey(asset);
|
|
return !nodeResourceKey || !assetResourceKey || nodeResourceKey === assetResourceKey;
|
|
}
|
|
|
|
function assetStorageKey(asset: Asset) {
|
|
if (asset.kind === "text" || asset.kind === "entity") return undefined;
|
|
return asset.data.storageKey;
|
|
}
|
|
|
|
export function declaredCanvasNodeAssetCategory(node: CanvasNodeData): AssetCategory | undefined {
|
|
if (node.metadata?.assetCategory) return node.metadata.assetCategory;
|
|
if (node.metadata?.workflowKind === "character") return "character";
|
|
if (node.metadata?.workflowKind === "scene") return "environment";
|
|
if (node.metadata?.workflowKind === "styleboard") return "style";
|
|
return undefined;
|
|
}
|
|
|
|
export function canvasNodeAssetCategory(node: CanvasNodeData): AssetCategory {
|
|
const declared = declaredCanvasNodeAssetCategory(node);
|
|
if (declared) return declared;
|
|
return "other";
|
|
}
|
|
|
|
function canvasAssetFallbackTitle(type: CanvasNodeType) {
|
|
if (type === CanvasNodeType.Image) return "画布图片";
|
|
if (type === CanvasNodeType.Video) return "画布视频";
|
|
if (type === CanvasNodeType.Audio) return "画布音频";
|
|
return "画布文本";
|
|
}
|