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; nodeId: string; sourceImageUrl: string; assetId?: string; title: string; }; export async function resolveCanvasCapabilityContext(params: { command: CanvasCapabilityClientCommand; projectId: string; requiresRemoteImage?: 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) : {}; const nodes = Array.isArray(canvasData.nodes) ? canvasData.nodes : []; const nodeById = new Map>( nodes.map((node: Record) => [String(node?.id || ""), node]) ); const targets = params.command.nodeIds.map((nodeId) => { const node = nodeById.get(nodeId); if (!node) { 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, }; }