diff --git a/app/api/psd-templates/render/route.ts b/app/api/psd-templates/render/route.ts index b6dd6e86a..0e5087e68 100644 --- a/app/api/psd-templates/render/route.ts +++ b/app/api/psd-templates/render/route.ts @@ -1,11 +1,11 @@ import { NextRequest, NextResponse } from "next/server"; import { z } from "zod"; import { auth } from "@/app/(auth)/auth"; -import { upsertSystemSetting } from "@/lib/db/queries"; import { - AGENT_PSD_TEMPLATES_KEY, getEditableAgentPsdTemplateLayers, + getAgentPsdTemplateRuntimeIssue, isAgentPsdImageLayerSlot, + isAgentPsdTemplateRuntimeReady, isAgentPsdTemplateReady, isAgentPsdTextLayerSlot, type AgentPsdTemplateRenderResponse, @@ -13,9 +13,8 @@ import { import { readAgentPsdFonts } from "@/lib/agents/psd-font-settings"; import { normalizeAgentPsdImagePlacement } from "@/lib/agents/psd-image-placement"; import { describeRemoteAssetLocation } from "@/lib/agents/psd-remote-asset"; -import { repairAgentPsdTemplateAssetsIfNeeded } from "@/lib/agents/psd-template-repair"; +import { repairAgentPsdTemplateFontBindings } from "@/lib/agents/psd-template-repair"; import { renderAgentPsdTemplate } from "@/lib/agents/psd-template-renderer"; -import { resolveAgentPsdTemplateTextStyles } from "@/lib/agents/psd-template-style-resolver"; import { normalizeHexColor } from "@/lib/agents/psd-text-layout"; import { readAgentPsdTemplates } from "@/lib/agents/psd-template-settings"; @@ -62,43 +61,32 @@ export async function POST(request: NextRequest) { try { const templates = await readAgentPsdTemplates(); - const templateIndex = templates.findIndex( + const template = templates.find( (entry) => entry.id === body.templateId && entry.status === "active" && isAgentPsdTemplateReady(entry) ); - const template = templateIndex >= 0 ? templates[templateIndex] : undefined; if (!template) { return jsonError(404, "PSD 模板不存在或未启用"); } - const repairedTemplate = await repairAgentPsdTemplateAssetsIfNeeded(template); - if (repairedTemplate !== template) { - await upsertSystemSetting({ - key: AGENT_PSD_TEMPLATES_KEY, - value: templates.map((entry, index) => - index === templateIndex ? repairedTemplate : entry - ), - updatedBy: session.user.id, - }).catch((error) => { - console.warn("[PSD Templates] failed to persist repaired template before render:", { - templateId: template.id, - error, - }); - }); + const fonts = await readAgentPsdFonts(); + const resolvedTemplate = repairAgentPsdTemplateFontBindings(template, fonts); + if (!isAgentPsdTemplateRuntimeReady(resolvedTemplate)) { + return jsonError( + 409, + getAgentPsdTemplateRuntimeIssue(resolvedTemplate) || + "PSD 模板资源不完整,请在后台重新解析后再导出" + ); } - const resolvedTemplate = await resolveAgentPsdTemplateTextStyles(repairedTemplate).catch( - () => repairedTemplate - ); const editableLayers = getEditableAgentPsdTemplateLayers(resolvedTemplate); if (editableLayers.length === 0) { return jsonError(400, "当前模板未配置可编辑图层"); } - const fonts = await readAgentPsdFonts(); const fontMap = new Map(fonts.map((font) => [font.id, font])); const missingFontLayer = editableLayers.find( (layer) => isAgentPsdTextLayerSlot(layer) && layer.fontId && !fontMap.has(layer.fontId) diff --git a/app/api/psd-templates/route.ts b/app/api/psd-templates/route.ts index cbff9cb18..8986c7c01 100644 --- a/app/api/psd-templates/route.ts +++ b/app/api/psd-templates/route.ts @@ -7,6 +7,7 @@ import { getEditableAgentPsdTemplateLayers, isAgentPsdTextLayerSlot, isAgentPsdTemplateReady, + isAgentPsdTemplateRuntimeReady, normalizeAgentPsdTemplateMode, type AgentPsdTemplateListResponse, } from "@/lib/agents/psd-template-config"; @@ -92,6 +93,7 @@ export async function GET(request: NextRequest) { requestedBrand ? template.brandName === requestedBrand : true ) .filter((template) => (requestedMode ? template.mode === requestedMode : true)) + .filter(isAgentPsdTemplateRuntimeReady) .filter((template) => getEditableAgentPsdTemplateLayers(template).length > 0) .map((template) => repairAgentPsdTemplateFontBindings(template, fonts)) .map((template) => ({ diff --git a/lib/agents/psd-template-config.ts b/lib/agents/psd-template-config.ts index 52dd4ea1d..e73db32dc 100644 --- a/lib/agents/psd-template-config.ts +++ b/lib/agents/psd-template-config.ts @@ -503,6 +503,58 @@ export function isAgentPsdTemplateReady(template: AgentPsdTemplate) { return template.parseStatus === "completed" && template.layers.length > 0; } +export function hasAgentPsdTemplateStoredLayerSource( + layer: Pick +) { + return Boolean( + String(layer.sourcePath || "").trim() || String(layer.sourceUrl || "").trim() + ); +} + +export function hasAgentPsdTemplateDedicatedRenderBase( + template: Pick +) { + const renderBaseUrl = String(template.renderBaseUrl || "").trim(); + const previewUrl = String(template.previewUrl || "").trim(); + return Boolean(String(template.renderBasePath || "").trim()) || ( + Boolean(renderBaseUrl) && + renderBaseUrl !== previewUrl + ); +} + +export function getAgentPsdTemplateRuntimeMissingLayers(template: AgentPsdTemplate) { + return template.layers.filter( + (layer) => !hasAgentPsdTemplateStoredLayerSource(layer) + ); +} + +export function getAgentPsdTemplateRuntimeIssue(template: AgentPsdTemplate) { + if (!isAgentPsdTemplateReady(template)) { + return "PSD 模板尚未完成解析,请先在后台重新解析后再使用"; + } + + const missingLayers = getAgentPsdTemplateRuntimeMissingLayers(template); + if (missingLayers.length === 0) { + return null; + } + + const layerNames = Array.from( + new Set(missingLayers.map((layer) => String(layer.layerName || "").trim()).filter(Boolean)) + ); + const summarizedLayerNames = + layerNames.length <= 3 + ? layerNames.join("、") + : `${layerNames.slice(0, 3).join("、")} 等 ${layerNames.length} 个图层`; + + return summarizedLayerNames + ? `PSD 模板图层资源不完整(${summarizedLayerNames}),请在后台重新解析该模板后再导出` + : "PSD 模板图层资源不完整,请在后台重新解析该模板后再导出"; +} + +export function isAgentPsdTemplateRuntimeReady(template: AgentPsdTemplate) { + return getAgentPsdTemplateRuntimeIssue(template) === null; +} + export function getEditableAgentPsdTemplateLayers(template: AgentPsdTemplate) { return template.layers.filter((layer) => layer.isEditable); } diff --git a/lib/agents/psd-template-renderer.ts b/lib/agents/psd-template-renderer.ts index 0a25bb423..4a696b6fb 100644 --- a/lib/agents/psd-template-renderer.ts +++ b/lib/agents/psd-template-renderer.ts @@ -2,8 +2,8 @@ import "server-only"; import { uploadFile } from "@/lib/storage/service"; import { fetchRemoteAssetBuffer } from "@/lib/agents/psd-remote-asset"; -import { loadPsdTemplateSourceFromUrl } from "@/lib/agents/psd-template-parser"; import { + hasAgentPsdTemplateStoredLayerSource, isAgentPsdImageLayerSlot, isAgentPsdTextLayerSlot, type AgentPsdTemplate, @@ -18,19 +18,6 @@ async function getSharp() { return sharp.default; } -async function rgbaToPngBuffer(rgba: Uint8ClampedArray, width: number, height: number) { - const sharp = await getSharp(); - return sharp(Buffer.from(rgba), { - raw: { - width, - height, - channels: 4, - }, - }) - .png() - .toBuffer(); -} - async function fitOverlayBufferIntoCanvas(params: { input: Buffer; left: number; @@ -341,9 +328,6 @@ export async function renderAgentPsdTemplate(params: { const remoteBufferCache = new Map(); const fontMap = new Map(fonts.map((font) => [font.id, font])); const shouldUseDedicatedRenderBase = hasDedicatedRenderBase(template); - let sourceLayerMapPromise: - | Promise>["layers"][number]>> - | null = null; async function readCachedBuffer(url: string, label = "PSD 模板资源") { const normalizedUrl = String(url || "").trim(); @@ -365,16 +349,6 @@ export async function renderAgentPsdTemplate(params: { return remoteBufferCache.get(normalizedUrl)!; } - async function getSourceLayerMap() { - if (!sourceLayerMapPromise) { - sourceLayerMapPromise = loadPsdTemplateSourceFromUrl(template.fileUrl).then( - (source) => new Map(source.layers.map((entry) => [entry.layer.layerId, entry])) - ); - } - - return sourceLayerMapPromise; - } - const compositeInputs: Array<{ input: Buffer; left: number; top: number }> = []; let didAppendDedicatedRenderBase = false; @@ -402,32 +376,14 @@ export async function renderAgentPsdTemplate(params: { return true; } - async function appendOriginalLayer(layerId: string) { - const sourceLayerMap = await getSourceLayerMap(); - const sourceLayer = sourceLayerMap.get(layerId); - if (!sourceLayer) return; - const rgba = await sourceLayer.composite(); - const overlayBuffer = await rgbaToPngBuffer( - rgba, - sourceLayer.layer.width, - sourceLayer.layer.height - ); - await appendBufferOverlay({ - input: overlayBuffer, - left: sourceLayer.layer.x, - top: sourceLayer.layer.y, - width: sourceLayer.layer.width, - height: sourceLayer.layer.height, - }); - } - async function appendStoredLayer(layer: AgentPsdTemplate["layers"][number]) { - const sourceUrl = String(layer.sourceUrl || "").trim(); - if (!sourceUrl) { - await appendOriginalLayer(layer.layerId); - return; + if (!hasAgentPsdTemplateStoredLayerSource(layer)) { + throw new Error( + `PSD 模板图层“${layer.layerName}”缺少已打包资源,请在后台重新解析该模板` + ); } + const sourceUrl = String(layer.sourceUrl || "").trim(); try { const overlayBuffer = await readCachedBuffer(sourceUrl, "PSD 图层素材"); const didAppend = await appendBufferOverlay({ @@ -446,9 +402,10 @@ export async function renderAgentPsdTemplate(params: { layerId: layer.layerId, error, }); + throw new Error( + `PSD 模板图层“${layer.layerName}”资源加载失败,请稍后重试或在后台重新解析该模板` + ); } - - await appendOriginalLayer(layer.layerId); } if (shouldUseDedicatedRenderBase && template.renderBaseUrl) {