feat: split PSD parsing into fast parse and async backfill
This commit is contained in:
parent
8c8b1794e4
commit
e84bfd94ec
@ -182,16 +182,25 @@ function countEditableTextLayers(template: AgentPsdTemplate | null) {
|
||||
);
|
||||
}
|
||||
|
||||
function hasTemplateParsedCanvas(template: AgentPsdTemplate | null) {
|
||||
return Boolean(
|
||||
template &&
|
||||
Number(template.width || 0) > 1 &&
|
||||
Number(template.height || 0) > 1 &&
|
||||
String(template.previewUrl || "").trim()
|
||||
);
|
||||
}
|
||||
|
||||
function getTemplateParseLabel(template: AgentPsdTemplate | null) {
|
||||
if (!template) return "未解析";
|
||||
|
||||
switch (template.parseStatus) {
|
||||
case "processing":
|
||||
return "解析中";
|
||||
return hasTemplateParsedCanvas(template) ? "补全中" : "解析中";
|
||||
case "completed":
|
||||
return "已解析";
|
||||
case "failed":
|
||||
return "解析失败";
|
||||
return hasTemplateParsedCanvas(template) ? "补全失败" : "解析失败";
|
||||
default:
|
||||
return "待解析";
|
||||
}
|
||||
@ -219,6 +228,9 @@ function getTemplateParseProgress(template: AgentPsdTemplate | null) {
|
||||
}
|
||||
|
||||
function getTemplateParseActionLabel(template: AgentPsdTemplate | null) {
|
||||
if (template?.parseStatus === "processing") {
|
||||
return "补全中";
|
||||
}
|
||||
return template?.parseStatus === "completed" ? "重新解析" : "解析 PSD";
|
||||
}
|
||||
|
||||
@ -775,8 +787,12 @@ export function PsdTemplatesSettingsClient({
|
||||
onStart: () => {
|
||||
setTemplateUploadStatus("正在解析 PSD,请稍候...");
|
||||
},
|
||||
onDone: () => {
|
||||
setTemplateUploadStatus("解析完成");
|
||||
onDone: (template) => {
|
||||
setTemplateUploadStatus(
|
||||
template.parseStatus === "completed"
|
||||
? "解析完成"
|
||||
: "快速解析完成,后台继续补全资产..."
|
||||
);
|
||||
},
|
||||
onError: (message) => {
|
||||
setTemplateUploadStatus(`解析失败:${message}`);
|
||||
@ -1035,7 +1051,7 @@ export function PsdTemplatesSettingsClient({
|
||||
options?: {
|
||||
skipCompletedConfirm?: boolean;
|
||||
onStart?: () => void;
|
||||
onDone?: () => void;
|
||||
onDone?: (template: AgentPsdTemplate) => void;
|
||||
onError?: (message: string) => void;
|
||||
}
|
||||
) {
|
||||
@ -1099,11 +1115,15 @@ export function PsdTemplatesSettingsClient({
|
||||
throw new Error(payload.error || "PSD 解析失败");
|
||||
}
|
||||
|
||||
toast.success(`模板“${payload.template.name}”解析完成`);
|
||||
toast.success(
|
||||
payload.template.parseStatus === "completed"
|
||||
? `模板“${payload.template.name}”解析完成`
|
||||
: `模板“${payload.template.name}”已完成快速解析,后台继续补全资产`
|
||||
);
|
||||
setDraftTemplate(cloneTemplate(payload.template));
|
||||
await mutate();
|
||||
setSelectedTemplateId(payload.template.id);
|
||||
options?.onDone?.();
|
||||
options?.onDone?.(payload.template);
|
||||
} catch (error) {
|
||||
await mutate();
|
||||
const message = error instanceof Error ? error.message : "PSD 解析失败";
|
||||
@ -1184,7 +1204,7 @@ export function PsdTemplatesSettingsClient({
|
||||
{getTemplateParseLabel(draftTemplate)}
|
||||
</Badge>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{draftTemplate.parseStatus === "completed"
|
||||
{hasTemplateParsedCanvas(draftTemplate)
|
||||
? `${draftTemplate.width} x ${draftTemplate.height}`
|
||||
: "等待解析后显示尺寸"}
|
||||
</span>
|
||||
@ -1851,7 +1871,7 @@ export function PsdTemplatesSettingsClient({
|
||||
<div className="min-w-0">
|
||||
<div className="truncate text-sm font-medium">{template.name}</div>
|
||||
<div className="mt-1 text-xs text-muted-foreground">
|
||||
{template.parseStatus === "completed"
|
||||
{hasTemplateParsedCanvas(template)
|
||||
? `${template.width} x ${template.height} · ${template.layerCount} 图层`
|
||||
: "待解析完成后生成尺寸和图层"}
|
||||
</div>
|
||||
@ -2031,12 +2051,12 @@ export function PsdTemplatesSettingsClient({
|
||||
className="flex w-full items-center justify-center bg-black/5"
|
||||
style={{
|
||||
aspectRatio:
|
||||
draftTemplate.parseStatus === "completed"
|
||||
hasTemplateParsedCanvas(draftTemplate)
|
||||
? `${Math.max(1, draftTemplate.width)} / ${Math.max(1, draftTemplate.height)}`
|
||||
: "1 / 1",
|
||||
}}
|
||||
>
|
||||
{draftTemplate.parseStatus === "completed" ? (
|
||||
{hasTemplateParsedCanvas(draftTemplate) ? (
|
||||
<img
|
||||
src={draftTemplate.previewUrl}
|
||||
alt={draftTemplate.name}
|
||||
@ -2082,7 +2102,9 @@ export function PsdTemplatesSettingsClient({
|
||||
{draftTemplate.parseStage ||
|
||||
(draftTemplate.parseStatus === "completed"
|
||||
? "解析完成,可开始配置槽位"
|
||||
: "上传完成后需要手动解析,适合批量上传多个 PSD 再逐个处理")}
|
||||
: hasTemplateParsedCanvas(draftTemplate)
|
||||
? "快速解析已完成,后台正在补全图层缩略图、源图和模板底图"
|
||||
: "上传完成后需要手动解析,适合批量上传多个 PSD 再逐个处理")}
|
||||
</div>
|
||||
{draftTemplate.parseStatus === "processing" ? (
|
||||
<div className="space-y-2">
|
||||
@ -2103,7 +2125,7 @@ export function PsdTemplatesSettingsClient({
|
||||
<div className="rounded-xl border p-3">
|
||||
<div className="text-xs text-muted-foreground">画布尺寸</div>
|
||||
<div className="mt-1 text-sm font-medium">
|
||||
{draftTemplate.parseStatus === "completed"
|
||||
{hasTemplateParsedCanvas(draftTemplate)
|
||||
? `${draftTemplate.width} x ${draftTemplate.height}`
|
||||
: "待解析"}
|
||||
</div>
|
||||
@ -2111,7 +2133,7 @@ export function PsdTemplatesSettingsClient({
|
||||
<div className="rounded-xl border p-3">
|
||||
<div className="text-xs text-muted-foreground">当前槽位</div>
|
||||
<div className="mt-1 text-sm font-medium">
|
||||
{draftTemplate.parseStatus === "completed"
|
||||
{hasTemplateParsedCanvas(draftTemplate)
|
||||
? `${countEditableLayers(draftTemplate)} / ${draftTemplate.layers.length}`
|
||||
: "待解析"}
|
||||
</div>
|
||||
@ -2388,7 +2410,7 @@ export function PsdTemplatesSettingsClient({
|
||||
</Badge>
|
||||
<Badge variant="secondary">
|
||||
当前图层数:
|
||||
{draftTemplate.parseStatus === "completed"
|
||||
{hasTemplateParsedCanvas(draftTemplate)
|
||||
? draftTemplate.layers.length
|
||||
: "待解析"}
|
||||
</Badge>
|
||||
@ -2438,7 +2460,9 @@ export function PsdTemplatesSettingsClient({
|
||||
</div>
|
||||
<div className="mt-4 text-base font-medium">先解析 PSD,再配置槽位</div>
|
||||
<div className="mt-2 text-sm text-muted-foreground">
|
||||
当前模板还没有生成图层结构、缩略图和字体识别结果,完成解析后这里会自动显示可编辑配置项。
|
||||
{hasTemplateParsedCanvas(draftTemplate)
|
||||
? "快速解析已经返回图层结构,后台正在补全缩略图、源图和底图,完成后这里会自动开放完整配置。"
|
||||
: "当前模板还没有生成图层结构、缩略图和字体识别结果,完成解析后这里会自动显示可编辑配置项。"}
|
||||
</div>
|
||||
<div className="mt-4 flex justify-center">
|
||||
<Button
|
||||
|
||||
@ -5,7 +5,6 @@ import { findBestMatchingPsdFont } from "@/lib/agents/psd-font-matching";
|
||||
import { readAgentPsdFonts } from "@/lib/agents/psd-font-settings";
|
||||
import {
|
||||
AGENT_PSD_TEMPLATES_KEY,
|
||||
AGENT_PSD_TEMPLATE_ASSET_VERSION,
|
||||
type AgentPsdTemplate,
|
||||
type AgentPsdTemplateLayerSlot,
|
||||
type AgentPsdTemplateMutationResponse,
|
||||
@ -13,14 +12,13 @@ import {
|
||||
import {
|
||||
type ParsedPsdTemplateLayer,
|
||||
parsePsdTemplateFile,
|
||||
preparePsdTemplateRenderAssets,
|
||||
} 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 {
|
||||
cacheAgentPsdSourceLocally,
|
||||
hasAgentPsdLocalCache,
|
||||
readAgentPsdSourceFromLocalCache,
|
||||
resolveAgentPsdLocalCachePath,
|
||||
} from "@/lib/agents/psd-local-cache";
|
||||
import { readAgentPsdTemplates } from "@/lib/agents/psd-template-settings";
|
||||
import { uploadFile } from "@/lib/storage/service";
|
||||
@ -229,11 +227,6 @@ export async function POST(
|
||||
error: undefined,
|
||||
});
|
||||
|
||||
const localFilePath = resolveAgentPsdLocalCachePath({
|
||||
templateId: id,
|
||||
fileName: currentTemplate.fileName,
|
||||
});
|
||||
|
||||
let fileBuffer: ArrayBuffer;
|
||||
if (
|
||||
await hasAgentPsdLocalCache({
|
||||
@ -322,57 +315,17 @@ export async function POST(
|
||||
canvasHeight: parsedTemplate.height,
|
||||
});
|
||||
|
||||
await updateParseProgress({
|
||||
templateId: id,
|
||||
updatedBy: session.user.id,
|
||||
progress: 78,
|
||||
stage: "正在生成图层预览和遮罩",
|
||||
error: undefined,
|
||||
});
|
||||
|
||||
const preparedAssets = await preparePsdTemplateRenderAssets({
|
||||
templateId: id,
|
||||
fileUrl: currentTemplate.fileUrl,
|
||||
filePath: localFilePath,
|
||||
fileBuffer,
|
||||
layers: initialLayers,
|
||||
});
|
||||
|
||||
await updateParseProgress({
|
||||
templateId: id,
|
||||
updatedBy: session.user.id,
|
||||
progress: 92,
|
||||
stage: "正在生成模板底图",
|
||||
error: undefined,
|
||||
});
|
||||
|
||||
const uploadedRenderBase = await uploadFile(
|
||||
`agent-psd-base-${id}-${Date.now()}.png`,
|
||||
toArrayBuffer(preparedAssets.renderBaseBuffer)
|
||||
);
|
||||
|
||||
const hasEditableLayers = preparedAssets.layers.some((layer) => layer.isEditable);
|
||||
const nextStatus =
|
||||
currentTemplate.status === "active" ||
|
||||
(currentTemplate.layers.length === 0 && hasEditableLayers)
|
||||
? "active"
|
||||
: currentTemplate.status;
|
||||
|
||||
const updatedTemplate = await updateTemplate(id, session.user.id, (template) => ({
|
||||
...template,
|
||||
previewUrl: uploadedPreview.url,
|
||||
previewPath: uploadedPreview.pathname,
|
||||
renderBaseUrl: uploadedRenderBase.url,
|
||||
renderBasePath: uploadedRenderBase.pathname,
|
||||
width: parsedTemplate.width,
|
||||
height: parsedTemplate.height,
|
||||
layerCount: parsedTemplate.layers.length,
|
||||
layers: preparedAssets.layers,
|
||||
assetVersion: AGENT_PSD_TEMPLATE_ASSET_VERSION,
|
||||
status: nextStatus,
|
||||
parseStatus: "completed",
|
||||
parseProgress: 100,
|
||||
parseStage: "解析完成",
|
||||
layers: initialLayers,
|
||||
parseStatus: "processing",
|
||||
parseProgress: 48,
|
||||
parseStage: "快速解析完成,正在后台补全图层资源和模板底图",
|
||||
parseError: undefined,
|
||||
updatedAt: new Date().toISOString(),
|
||||
}));
|
||||
@ -381,6 +334,11 @@ export async function POST(
|
||||
return jsonError(404, "未找到 PSD 模板");
|
||||
}
|
||||
|
||||
schedulePsdTemplateAssetBackfill({
|
||||
templateId: id,
|
||||
updatedBy: session.user.id,
|
||||
});
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
template: updatedTemplate,
|
||||
|
||||
267
lib/agents/psd-template-asset-backfill.ts
Normal file
267
lib/agents/psd-template-asset-backfill.ts
Normal file
@ -0,0 +1,267 @@
|
||||
import "server-only";
|
||||
|
||||
import { upsertSystemSetting } from "@/lib/db/queries";
|
||||
import {
|
||||
AGENT_PSD_TEMPLATES_KEY,
|
||||
AGENT_PSD_TEMPLATE_ASSET_VERSION,
|
||||
isAgentPsdImageLayerSlot,
|
||||
isAgentPsdTextLayerSlot,
|
||||
type AgentPsdTemplate,
|
||||
type AgentPsdTemplateLayerSlot,
|
||||
} from "@/lib/agents/psd-template-config";
|
||||
import { resolveAgentPsdLocalCachePath } from "@/lib/agents/psd-local-cache";
|
||||
import { preparePsdTemplateRenderAssets } from "@/lib/agents/psd-template-parser";
|
||||
import { readAgentPsdTemplates } from "@/lib/agents/psd-template-settings";
|
||||
import { uploadFile } from "@/lib/storage/service";
|
||||
|
||||
type PsdTemplateAssetBackfillJobState = {
|
||||
running: boolean;
|
||||
rerun: boolean;
|
||||
};
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line no-var
|
||||
var __agentPsdTemplateAssetBackfillJobs:
|
||||
| Map<string, PsdTemplateAssetBackfillJobState>
|
||||
| undefined;
|
||||
}
|
||||
|
||||
function getJobMap() {
|
||||
if (!globalThis.__agentPsdTemplateAssetBackfillJobs) {
|
||||
globalThis.__agentPsdTemplateAssetBackfillJobs = new Map();
|
||||
}
|
||||
|
||||
return globalThis.__agentPsdTemplateAssetBackfillJobs;
|
||||
}
|
||||
|
||||
function toArrayBuffer(buffer: Buffer) {
|
||||
return buffer.buffer.slice(
|
||||
buffer.byteOffset,
|
||||
buffer.byteOffset + buffer.byteLength
|
||||
) as ArrayBuffer;
|
||||
}
|
||||
|
||||
function getErrorMessage(error: unknown) {
|
||||
if (error instanceof Error && error.message.trim()) {
|
||||
return error.message.trim();
|
||||
}
|
||||
return "PSD 后台资产补全失败";
|
||||
}
|
||||
|
||||
async function updateTemplateById(params: {
|
||||
templateId: string;
|
||||
updatedBy: string;
|
||||
updater: (template: AgentPsdTemplate) => AgentPsdTemplate;
|
||||
}) {
|
||||
const templates = await readAgentPsdTemplates();
|
||||
const currentTemplate = templates.find((entry) => entry.id === params.templateId);
|
||||
|
||||
if (!currentTemplate) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const nextTemplate = params.updater(currentTemplate);
|
||||
await upsertSystemSetting({
|
||||
key: AGENT_PSD_TEMPLATES_KEY,
|
||||
value: templates.map((entry) => (entry.id === params.templateId ? nextTemplate : entry)),
|
||||
updatedBy: params.updatedBy,
|
||||
});
|
||||
|
||||
return nextTemplate;
|
||||
}
|
||||
|
||||
function mergePreparedLayerAssets(params: {
|
||||
currentLayers: AgentPsdTemplateLayerSlot[];
|
||||
preparedLayers: AgentPsdTemplateLayerSlot[];
|
||||
}) {
|
||||
const preparedLayerMap = new Map(
|
||||
params.preparedLayers.map((layer) => [layer.id, layer] as const)
|
||||
);
|
||||
|
||||
return params.currentLayers.map((layer) => {
|
||||
const preparedLayer = preparedLayerMap.get(layer.id);
|
||||
if (!preparedLayer) {
|
||||
return layer;
|
||||
}
|
||||
|
||||
if (isAgentPsdImageLayerSlot(layer) && isAgentPsdImageLayerSlot(preparedLayer)) {
|
||||
return {
|
||||
...layer,
|
||||
previewUrl: preparedLayer.previewUrl || layer.previewUrl,
|
||||
previewPath: preparedLayer.previewPath || layer.previewPath,
|
||||
sourceUrl: preparedLayer.sourceUrl || layer.sourceUrl,
|
||||
sourcePath: preparedLayer.sourcePath || layer.sourcePath,
|
||||
contentBounds: preparedLayer.contentBounds || layer.contentBounds,
|
||||
maskUrl: preparedLayer.maskUrl,
|
||||
maskPath: preparedLayer.maskPath,
|
||||
} satisfies AgentPsdTemplateLayerSlot;
|
||||
}
|
||||
|
||||
if (isAgentPsdTextLayerSlot(layer) && isAgentPsdTextLayerSlot(preparedLayer)) {
|
||||
let nextLayer: AgentPsdTemplateLayerSlot = {
|
||||
...layer,
|
||||
previewUrl: preparedLayer.previewUrl || layer.previewUrl,
|
||||
previewPath: preparedLayer.previewPath || layer.previewPath,
|
||||
sourceUrl: preparedLayer.sourceUrl || layer.sourceUrl,
|
||||
sourcePath: preparedLayer.sourcePath || layer.sourcePath,
|
||||
};
|
||||
|
||||
if (layer.styleSource !== "custom") {
|
||||
nextLayer = {
|
||||
...nextLayer,
|
||||
textColor:
|
||||
layer.textColor === "#111111" && preparedLayer.textColor
|
||||
? preparedLayer.textColor
|
||||
: layer.textColor,
|
||||
textAlign:
|
||||
layer.textAlign === "left" && preparedLayer.textAlign !== "left"
|
||||
? preparedLayer.textAlign
|
||||
: layer.textAlign,
|
||||
paddingX: layer.paddingX > 0 ? layer.paddingX : preparedLayer.paddingX,
|
||||
paddingY: layer.paddingY > 0 ? layer.paddingY : preparedLayer.paddingY,
|
||||
fontId: layer.fontId || preparedLayer.fontId,
|
||||
fontFamily: layer.fontFamily || preparedLayer.fontFamily,
|
||||
fontLabel: layer.fontLabel || preparedLayer.fontLabel,
|
||||
fontFileUrl: layer.fontFileUrl || preparedLayer.fontFileUrl,
|
||||
fontContentType: layer.fontContentType || preparedLayer.fontContentType,
|
||||
} satisfies AgentPsdTemplateLayerSlot;
|
||||
}
|
||||
|
||||
return nextLayer;
|
||||
}
|
||||
|
||||
return layer;
|
||||
});
|
||||
}
|
||||
|
||||
async function backfillTemplateAssetsOnce(params: {
|
||||
templateId: string;
|
||||
updatedBy: string;
|
||||
}) {
|
||||
const templates = await readAgentPsdTemplates();
|
||||
const template = templates.find((entry) => entry.id === params.templateId);
|
||||
|
||||
if (!template || template.parseStatus !== "processing") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (template.layers.length === 0 || template.width <= 1 || template.height <= 1) {
|
||||
throw new Error("PSD 模板尚未完成快速解析");
|
||||
}
|
||||
|
||||
await updateTemplateById({
|
||||
templateId: template.id,
|
||||
updatedBy: params.updatedBy,
|
||||
updater: (currentTemplate) => ({
|
||||
...currentTemplate,
|
||||
parseStatus: "processing",
|
||||
parseProgress: Math.max(62, Number(currentTemplate.parseProgress || 0)),
|
||||
parseStage: "快速解析完成,正在后台补全图层资源",
|
||||
parseError: undefined,
|
||||
updatedAt: new Date().toISOString(),
|
||||
}),
|
||||
});
|
||||
|
||||
const preparedAssets = await preparePsdTemplateRenderAssets({
|
||||
templateId: template.id,
|
||||
fileUrl: template.fileUrl,
|
||||
filePath: resolveAgentPsdLocalCachePath({
|
||||
templateId: template.id,
|
||||
fileName: template.fileName,
|
||||
}),
|
||||
layers: template.layers,
|
||||
});
|
||||
|
||||
await updateTemplateById({
|
||||
templateId: template.id,
|
||||
updatedBy: params.updatedBy,
|
||||
updater: (currentTemplate) => ({
|
||||
...currentTemplate,
|
||||
parseStatus: "processing",
|
||||
parseProgress: Math.max(88, Number(currentTemplate.parseProgress || 0)),
|
||||
parseStage: "正在后台生成模板底图",
|
||||
parseError: undefined,
|
||||
updatedAt: new Date().toISOString(),
|
||||
}),
|
||||
});
|
||||
|
||||
const uploadedRenderBase = await uploadFile(
|
||||
`agent-psd-base-${template.id}-${Date.now()}.png`,
|
||||
toArrayBuffer(preparedAssets.renderBaseBuffer)
|
||||
);
|
||||
|
||||
await updateTemplateById({
|
||||
templateId: template.id,
|
||||
updatedBy: params.updatedBy,
|
||||
updater: (currentTemplate) => {
|
||||
const mergedLayers = mergePreparedLayerAssets({
|
||||
currentLayers: currentTemplate.layers,
|
||||
preparedLayers: preparedAssets.layers,
|
||||
});
|
||||
|
||||
return {
|
||||
...currentTemplate,
|
||||
renderBaseUrl: uploadedRenderBase.url,
|
||||
renderBasePath: uploadedRenderBase.pathname,
|
||||
layers: mergedLayers,
|
||||
layerCount: mergedLayers.length,
|
||||
assetVersion: AGENT_PSD_TEMPLATE_ASSET_VERSION,
|
||||
parseStatus: "completed",
|
||||
parseProgress: 100,
|
||||
parseStage: "快速解析和后台资产补全已完成",
|
||||
parseError: undefined,
|
||||
updatedAt: new Date().toISOString(),
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function schedulePsdTemplateAssetBackfill(params: {
|
||||
templateId: string;
|
||||
updatedBy: string;
|
||||
}) {
|
||||
const jobs = getJobMap();
|
||||
const existingJob = jobs.get(params.templateId);
|
||||
if (existingJob?.running) {
|
||||
existingJob.rerun = true;
|
||||
return;
|
||||
}
|
||||
|
||||
const jobState: PsdTemplateAssetBackfillJobState = {
|
||||
running: true,
|
||||
rerun: false,
|
||||
};
|
||||
jobs.set(params.templateId, jobState);
|
||||
|
||||
queueMicrotask(() => {
|
||||
void (async () => {
|
||||
try {
|
||||
do {
|
||||
jobState.rerun = false;
|
||||
await backfillTemplateAssetsOnce(params);
|
||||
} while (jobState.rerun);
|
||||
} catch (error) {
|
||||
const message = getErrorMessage(error);
|
||||
await updateTemplateById({
|
||||
templateId: params.templateId,
|
||||
updatedBy: params.updatedBy,
|
||||
updater: (currentTemplate) => ({
|
||||
...currentTemplate,
|
||||
parseStatus: "failed",
|
||||
parseProgress: Math.min(95, Math.max(45, Number(currentTemplate.parseProgress || 0))),
|
||||
parseStage: "快速解析已完成,但后台补全资产失败",
|
||||
parseError: message,
|
||||
updatedAt: new Date().toISOString(),
|
||||
}),
|
||||
}).catch(() => undefined);
|
||||
|
||||
console.error("[PSD Template] background asset backfill failed:", {
|
||||
templateId: params.templateId,
|
||||
error,
|
||||
});
|
||||
} finally {
|
||||
jobs.delete(params.templateId);
|
||||
}
|
||||
})();
|
||||
});
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user