fix: remove psd runtime source fallback
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled

This commit is contained in:
Your Name 2026-04-10 20:59:43 +08:00
parent ebbab98a41
commit e74586c8a0
4 changed files with 75 additions and 76 deletions

View File

@ -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)

View File

@ -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) => ({

View File

@ -503,6 +503,58 @@ export function isAgentPsdTemplateReady(template: AgentPsdTemplate) {
return template.parseStatus === "completed" && template.layers.length > 0;
}
export function hasAgentPsdTemplateStoredLayerSource(
layer: Pick<AgentPsdTemplateLayerSlot, "sourceUrl" | "sourcePath">
) {
return Boolean(
String(layer.sourcePath || "").trim() || String(layer.sourceUrl || "").trim()
);
}
export function hasAgentPsdTemplateDedicatedRenderBase(
template: Pick<AgentPsdTemplate, "renderBaseUrl" | "renderBasePath" | "previewUrl">
) {
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);
}

View File

@ -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<string, Buffer>();
const fontMap = new Map(fonts.map((font) => [font.id, font]));
const shouldUseDedicatedRenderBase = hasDedicatedRenderBase(template);
let sourceLayerMapPromise:
| Promise<Map<string, Awaited<ReturnType<typeof loadPsdTemplateSourceFromUrl>>["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) {