Files
ttstd-infinite-canvas/web/src/lib/canvas/canvas-node-asset.ts

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 "画布文本";
}