107 lines
3.2 KiB
TypeScript

import { and, eq } from "drizzle-orm";
import type { CanvasCapabilityClientCommand } from "@/lib/canvas/capabilities/client-command";
import { db } from "@/lib/db";
import { canvasProject } from "@/lib/db/schema";
export class CanvasCapabilityRuntimeError extends Error {
status: number;
code: string;
constructor(message: string, status = 400, code = "CANVAS_CAPABILITY_ERROR") {
super(message);
this.name = "CanvasCapabilityRuntimeError";
this.status = status;
this.code = code;
}
}
export type CanvasCapabilityTarget = {
node: Record<string, any>;
nodeId: string;
sourceImageUrl: string;
assetId?: string;
title: string;
};
export async function resolveCanvasCapabilityContext(params: {
command: CanvasCapabilityClientCommand;
projectId: string;
requiresRemoteImage?: boolean;
allowUnsavedDownloadTargets?: boolean;
userId: string;
}) {
const [project] = await db
.select()
.from(canvasProject)
.where(
and(
eq(canvasProject.id, params.projectId),
eq(canvasProject.userId, params.userId)
)
)
.limit(1);
if (!project) {
throw new CanvasCapabilityRuntimeError(
"画布不存在或无权访问",
404,
"CANVAS_CAPABILITY_PROJECT_NOT_FOUND"
);
}
const canvasData =
project.canvasData && typeof project.canvasData === "object"
? (project.canvasData as Record<string, any>)
: {};
const nodes = Array.isArray(canvasData.nodes) ? canvasData.nodes : [];
const nodeById = new Map<string, Record<string, any>>(
nodes.map((node: Record<string, any>) => [String(node?.id || ""), node])
);
const targets = params.command.nodeIds.map((nodeId): CanvasCapabilityTarget | null => {
const node = nodeById.get(nodeId);
if (!node) {
// Downloading uses the browser's current image URL. A freshly generated
// node can exist there before autosave; it does not need a server target.
if (params.allowUnsavedDownloadTargets && params.command.capabilityId === "image.download") {
return null;
}
throw new CanvasCapabilityRuntimeError(
`选中节点不存在:${nodeId}`,
404,
"CANVAS_CAPABILITY_NODE_NOT_FOUND"
);
}
if (node.type !== "image") {
throw new CanvasCapabilityRuntimeError(
`能力仅支持图片节点:${nodeId}`,
400,
"CANVAS_CAPABILITY_INVALID_NODE_TYPE"
);
}
const data =
node.data && typeof node.data === "object" ? node.data : {};
const sourceImageUrl = String(data.src || data.url || "").trim();
if (params.requiresRemoteImage !== false && !/^https?:\/\//i.test(sourceImageUrl)) {
throw new CanvasCapabilityRuntimeError(
`图片尚未上传完成:${nodeId}`,
409,
"CANVAS_CAPABILITY_IMAGE_NOT_REMOTE"
);
}
return {
node,
nodeId,
sourceImageUrl,
assetId: String(data.assetId || "").trim() || undefined,
title: String(data.title || data.prompt || "图片").trim() || "图片",
} satisfies CanvasCapabilityTarget;
});
return {
project,
canvasData,
targets: targets.filter((target): target is CanvasCapabilityTarget => target !== null),
};
}