fix: fail unsupported psb templates fast
This commit is contained in:
parent
faa70a315c
commit
c8073c6c46
@ -751,7 +751,7 @@ export function PsdTemplatesSettingsClient({
|
||||
async function handleCreateTemplate(options?: { parseAfterUpload?: boolean }) {
|
||||
const parseAfterUpload = options?.parseAfterUpload === true;
|
||||
if (!uploadFile) {
|
||||
toast.error("请先选择 PSD / PSB 文件");
|
||||
toast.error("请先选择 PSD 文件");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -2581,7 +2581,10 @@ export function PsdTemplatesSettingsClient({
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-base">上传新模板</CardTitle>
|
||||
<CardDescription>支持 PSD / PSB,建议图层命名清晰,便于后台配置图片位和文字位。</CardDescription>
|
||||
<CardDescription>
|
||||
支持 Photoshop PSD(.psd),建议图层命名清晰,便于后台配置图片位和文字位。PSB
|
||||
请先在 Photoshop 中另存为 PSD 后上传。
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
@ -2742,11 +2745,11 @@ export function PsdTemplatesSettingsClient({
|
||||
<Input
|
||||
id="psdTemplateFile"
|
||||
type="file"
|
||||
accept=".psd,.psb"
|
||||
accept=".psd"
|
||||
onChange={handleTemplateFileChange}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{uploadFile ? `已选择:${uploadFile.name}` : "支持 80MB 以内 PSD / PSB 文件"}
|
||||
{uploadFile ? `已选择:${uploadFile.name}` : "支持 80MB 以内 PSD 文件"}
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid gap-3 sm:grid-cols-2">
|
||||
|
||||
@ -12,6 +12,10 @@ import {
|
||||
} from "@/lib/agents/psd-template-parser";
|
||||
import { pickPrimaryPsdImageLayerId } from "@/lib/agents/psd-template-image-slot-selection";
|
||||
import { schedulePsdTemplateAssetBackfill } from "@/lib/agents/psd-template-asset-backfill";
|
||||
import {
|
||||
AGENT_PSD_TEMPLATE_UNSUPPORTED_PSB_MESSAGE,
|
||||
assertSupportedAgentPsdTemplateFile,
|
||||
} from "@/lib/agents/psd-template-format";
|
||||
import {
|
||||
createAgentPsdTemplateParseHeartbeat,
|
||||
updateAgentPsdTemplateById,
|
||||
@ -181,6 +185,11 @@ export async function POST(
|
||||
}
|
||||
|
||||
const { id } = await context.params;
|
||||
let latestProgress = {
|
||||
progress: 0,
|
||||
stage: "准备解析 PSD",
|
||||
error: undefined as string | undefined,
|
||||
};
|
||||
|
||||
try {
|
||||
const { templates, timedOutTemplates } = await failTimedOutAgentPsdTemplates({
|
||||
@ -204,10 +213,10 @@ export async function POST(
|
||||
return jsonError(409, "该模板正在解析中,请稍后刷新");
|
||||
}
|
||||
|
||||
let latestProgress = {
|
||||
latestProgress = {
|
||||
progress: 5,
|
||||
stage: "准备解析 PSD",
|
||||
error: undefined as string | undefined,
|
||||
error: undefined,
|
||||
};
|
||||
const setParseProgress = async (progress: number, stage: string, error?: string) => {
|
||||
latestProgress = {
|
||||
@ -279,6 +288,9 @@ export async function POST(
|
||||
});
|
||||
}
|
||||
|
||||
await setParseProgress(22, "正在校验 PSD 文件格式");
|
||||
assertSupportedAgentPsdTemplateFile(fileBuffer);
|
||||
|
||||
await setParseProgress(24, "正在解析 PSD 图层结构与预览");
|
||||
const parsedTemplate = await parsePsdTemplateFile(fileBuffer, {
|
||||
onProgress: async (event) => {
|
||||
@ -353,10 +365,12 @@ export async function POST(
|
||||
const existingTemplate = (await readAgentPsdTemplates()).find((entry) => entry.id === id);
|
||||
|
||||
if (existingTemplate) {
|
||||
const fallbackStatus =
|
||||
existingTemplate.parseStatus === "completed" && existingTemplate.layers.length > 0
|
||||
? "completed"
|
||||
: "failed";
|
||||
const hasUsablePreviousParse =
|
||||
existingTemplate.layers.length > 0 &&
|
||||
existingTemplate.width > 1 &&
|
||||
existingTemplate.height > 1 &&
|
||||
Boolean(existingTemplate.previewUrl);
|
||||
const fallbackStatus = hasUsablePreviousParse ? "completed" : "failed";
|
||||
|
||||
await updateAgentPsdTemplateById({
|
||||
templateId: id,
|
||||
@ -364,9 +378,14 @@ export async function POST(
|
||||
updater: (template) => ({
|
||||
...template,
|
||||
parseStatus: fallbackStatus,
|
||||
parseProgress: fallbackStatus === "completed" ? 100 : 0,
|
||||
parseProgress:
|
||||
fallbackStatus === "completed"
|
||||
? 100
|
||||
: Math.max(0, Math.round(Number(latestProgress.progress || 0))),
|
||||
parseStage:
|
||||
fallbackStatus === "completed" ? "沿用上次成功解析结果" : undefined,
|
||||
fallbackStatus === "completed"
|
||||
? "沿用上次成功解析结果"
|
||||
: latestProgress.stage || "PSD 解析失败",
|
||||
parseError:
|
||||
fallbackStatus === "completed"
|
||||
? `重新解析失败:${message}`
|
||||
@ -376,7 +395,19 @@ export async function POST(
|
||||
}).catch(() => undefined);
|
||||
}
|
||||
|
||||
console.error("[PSD Templates][Admin] parse failed:", error);
|
||||
return jsonError(502, message);
|
||||
console.error("[PSD Templates][Admin] parse failed:", {
|
||||
templateId: id,
|
||||
fileName: existingTemplate?.fileName,
|
||||
progress: latestProgress.progress,
|
||||
stage: latestProgress.stage,
|
||||
error,
|
||||
});
|
||||
return jsonError(
|
||||
message === AGENT_PSD_TEMPLATE_UNSUPPORTED_PSB_MESSAGE ||
|
||||
message.startsWith("模板文件格式不正确")
|
||||
? 400
|
||||
: 502,
|
||||
message
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,6 +15,10 @@ import {
|
||||
type AgentPsdTemplateMutationResponse,
|
||||
} from "@/lib/agents/psd-template-config";
|
||||
import { normalizeAgentPsdTemplateCategoryTags } from "@/lib/agents/psd-template-category-tag-config";
|
||||
import {
|
||||
AGENT_PSD_TEMPLATE_UNSUPPORTED_PSB_MESSAGE,
|
||||
assertSupportedAgentPsdTemplateFile,
|
||||
} from "@/lib/agents/psd-template-format";
|
||||
import { cacheAgentPsdSourceLocally } from "@/lib/agents/psd-local-cache";
|
||||
import { readAgentPsdTemplates } from "@/lib/agents/psd-template-settings";
|
||||
import { failTimedOutAgentPsdTemplates } from "@/lib/agents/psd-template-timeout";
|
||||
@ -22,7 +26,7 @@ import { failTimedOutAgentPsdTemplates } from "@/lib/agents/psd-template-timeout
|
||||
export const runtime = "nodejs";
|
||||
|
||||
const MAX_TEMPLATE_FILE_SIZE = 80 * 1024 * 1024;
|
||||
const ALLOWED_TEMPLATE_EXTENSIONS = new Set(["psd", "psb"]);
|
||||
const ALLOWED_TEMPLATE_EXTENSIONS = new Set(["psd"]);
|
||||
const PLACEHOLDER_PREVIEW_PNG = Buffer.from(
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO2+p4kAAAAASUVORK5CYII=",
|
||||
"base64"
|
||||
@ -222,7 +226,12 @@ export async function POST(request: NextRequest) {
|
||||
|
||||
const extension = getFileExtension(file.name);
|
||||
if (!ALLOWED_TEMPLATE_EXTENSIONS.has(extension)) {
|
||||
return jsonMutationError(400, "模板文件仅支持 PSD / PSB");
|
||||
return jsonMutationError(
|
||||
400,
|
||||
extension === "psb"
|
||||
? AGENT_PSD_TEMPLATE_UNSUPPORTED_PSB_MESSAGE
|
||||
: "模板文件仅支持 Photoshop PSD(.psd)"
|
||||
);
|
||||
}
|
||||
|
||||
if (file.size <= 0) {
|
||||
@ -235,11 +244,15 @@ export async function POST(request: NextRequest) {
|
||||
|
||||
try {
|
||||
const fileBuffer = await file.arrayBuffer();
|
||||
const fileInspection = assertSupportedAgentPsdTemplateFile(fileBuffer);
|
||||
const templateId = randomUUID();
|
||||
console.log("[PSD Templates][Admin] received PSD template upload:", {
|
||||
templateId,
|
||||
fileName: file.name,
|
||||
size: file.size,
|
||||
fileFormat: fileInspection.format,
|
||||
width: fileInspection.width,
|
||||
height: fileInspection.height,
|
||||
});
|
||||
await cacheAgentPsdSourceLocally({
|
||||
templateId,
|
||||
@ -311,6 +324,14 @@ export async function POST(request: NextRequest) {
|
||||
} satisfies AgentPsdTemplateMutationResponse);
|
||||
} catch (error) {
|
||||
console.error("[PSD Templates][Admin] create failed:", error);
|
||||
if (error instanceof Error && error.message.trim()) {
|
||||
if (
|
||||
error.message === AGENT_PSD_TEMPLATE_UNSUPPORTED_PSB_MESSAGE ||
|
||||
error.message.startsWith("模板文件格式不正确")
|
||||
) {
|
||||
return jsonMutationError(400, error.message);
|
||||
}
|
||||
}
|
||||
return jsonMutationError(502, "PSD 模板上传或解析失败");
|
||||
}
|
||||
}
|
||||
|
||||
74
lib/agents/psd-template-format.ts
Normal file
74
lib/agents/psd-template-format.ts
Normal file
@ -0,0 +1,74 @@
|
||||
import "server-only";
|
||||
|
||||
export type AgentPsdTemplateFileFormat = "psd" | "psb" | "unknown";
|
||||
|
||||
export interface AgentPsdTemplateFileInspection {
|
||||
signature: string;
|
||||
version: number;
|
||||
format: AgentPsdTemplateFileFormat;
|
||||
channels: number;
|
||||
height: number;
|
||||
width: number;
|
||||
depth: number;
|
||||
colorMode: number;
|
||||
}
|
||||
|
||||
export const AGENT_PSD_TEMPLATE_UNSUPPORTED_PSB_MESSAGE =
|
||||
"当前生产模板解析仅支持 Photoshop PSD(.psd)。检测到 PSB / Large Document 格式,请在 Photoshop 中另存为 PSD 后再上传解析。";
|
||||
|
||||
export function inspectAgentPsdTemplateFile(
|
||||
value: ArrayBuffer | Buffer
|
||||
): AgentPsdTemplateFileInspection {
|
||||
const buffer = Buffer.isBuffer(value)
|
||||
? value
|
||||
: Buffer.from(value);
|
||||
|
||||
if (buffer.byteLength < 26) {
|
||||
return {
|
||||
signature: "",
|
||||
version: 0,
|
||||
format: "unknown",
|
||||
channels: 0,
|
||||
height: 0,
|
||||
width: 0,
|
||||
depth: 0,
|
||||
colorMode: 0,
|
||||
};
|
||||
}
|
||||
|
||||
const signature = buffer.subarray(0, 4).toString("ascii");
|
||||
const version = buffer.readUInt16BE(4);
|
||||
const format =
|
||||
signature === "8BPS" && version === 1
|
||||
? "psd"
|
||||
: signature === "8BPS" && version === 2
|
||||
? "psb"
|
||||
: "unknown";
|
||||
|
||||
return {
|
||||
signature,
|
||||
version,
|
||||
format,
|
||||
channels: buffer.readUInt16BE(12),
|
||||
height: buffer.readUInt32BE(14),
|
||||
width: buffer.readUInt32BE(18),
|
||||
depth: buffer.readUInt16BE(22),
|
||||
colorMode: buffer.readUInt16BE(24),
|
||||
};
|
||||
}
|
||||
|
||||
export function assertSupportedAgentPsdTemplateFile(
|
||||
value: ArrayBuffer | Buffer
|
||||
) {
|
||||
const inspection = inspectAgentPsdTemplateFile(value);
|
||||
|
||||
if (inspection.format === "psd") {
|
||||
return inspection;
|
||||
}
|
||||
|
||||
if (inspection.format === "psb") {
|
||||
throw new Error(AGENT_PSD_TEMPLATE_UNSUPPORTED_PSB_MESSAGE);
|
||||
}
|
||||
|
||||
throw new Error("模板文件格式不正确,请上传 Photoshop PSD(.psd)文件。");
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user