import "server-only"; import { findBestMatchingPsdFont } from "@/lib/agents/psd-font-matching"; import { readAgentPsdFonts } from "@/lib/agents/psd-font-settings"; import { AGENT_PSD_TEMPLATE_ASSET_VERSION, isAgentPsdImageLayerSlot, isAgentPsdTextLayerSlot, type AgentPsdTemplate, } from "@/lib/agents/psd-template-config"; import { parsePsdTemplateFileFromUrl, preparePsdTemplateRenderAssets, } from "@/lib/agents/psd-template-parser"; import { uploadFile } from "@/lib/storage/service"; function hasMissingLayerAssets(template: AgentPsdTemplate) { return template.layers.some( (layer) => !String(layer.previewUrl || "").trim() || !String(layer.sourceUrl || "").trim() || (isAgentPsdImageLayerSlot(layer) && layer.isEditable && !String(layer.maskUrl || "").trim()) ); } function needsTextMetadataRepair(template: AgentPsdTemplate) { return template.layers.some( (layer) => isAgentPsdTextLayerSlot(layer) && (!String(layer.detectedFontName || "").trim() || !Array.isArray(layer.detectedFontNames) || layer.detectedFontNames.length === 0) ); } function hasDedicatedRenderBase(template: AgentPsdTemplate) { const renderBaseUrl = String(template.renderBaseUrl || "").trim(); const previewUrl = String(template.previewUrl || "").trim(); return Boolean(String(template.renderBasePath || "").trim()) || ( Boolean(renderBaseUrl) && renderBaseUrl !== previewUrl ); } function toArrayBuffer(buffer: Buffer) { return buffer.buffer.slice( buffer.byteOffset, buffer.byteOffset + buffer.byteLength ) as ArrayBuffer; } export async function repairAgentPsdTemplateAssetsIfNeeded( template: AgentPsdTemplate ) { if (template.parseStatus !== "completed") { return template; } const needsAssetVersionRefresh = Number(template.assetVersion || 0) !== AGENT_PSD_TEMPLATE_ASSET_VERSION; const needsRepair = needsAssetVersionRefresh || hasMissingLayerAssets(template); const needsRenderBaseRepair = !hasDedicatedRenderBase(template); const fonts = await readAgentPsdFonts().catch(() => []); const needsFontDetection = needsTextMetadataRepair(template); let nextLayers = template.layers; if (needsFontDetection) { try { const parsedTemplate = await parsePsdTemplateFileFromUrl(template.fileUrl); const parsedTextLayerMap = new Map( parsedTemplate.layers .filter((layer) => layer.slotType === "text") .map((layer) => [layer.layerId, layer]) ); nextLayers = nextLayers.map((layer) => { if (!isAgentPsdTextLayerSlot(layer)) return layer; const parsedLayer = parsedTextLayerMap.get(layer.layerId); if (!parsedLayer) return layer; return { ...layer, detectedFontName: parsedLayer.detectedFontName || layer.detectedFontName, detectedFontNames: parsedLayer.detectedFontNames || layer.detectedFontNames, }; }); } catch (error) { console.warn("[PSD Template] failed to repair parsed text metadata:", { templateId: template.id, error, }); } } nextLayers = nextLayers.map((layer) => { if (!isAgentPsdTextLayerSlot(layer) || layer.fontId) { return layer; } const matchedFont = findBestMatchingPsdFont({ detectedFontNames: [ ...((layer.detectedFontNames || []).filter(Boolean)), ...(layer.detectedFontName ? [layer.detectedFontName] : []), ], fonts, }); if (!matchedFont) { return layer; } return { ...layer, fontId: matchedFont.font.id, fontFamily: matchedFont.font.family, fontLabel: matchedFont.font.name, fontFileUrl: matchedFont.font.fileUrl, fontContentType: matchedFont.font.contentType, }; }); const hasFontRepair = nextLayers.some( (layer, index) => JSON.stringify(layer) !== JSON.stringify(template.layers[index]) ); if (!needsRepair && !hasFontRepair && !needsRenderBaseRepair) { return template; } try { const preparedAssets = await preparePsdTemplateRenderAssets({ templateId: template.id, fileUrl: template.fileUrl, layers: nextLayers, forceRefreshLayerAssets: needsAssetVersionRefresh, }); const uploadedRenderBase = needsRenderBaseRepair ? await uploadFile( `agent-psd-base-${template.id}-${Date.now()}.png`, toArrayBuffer(preparedAssets.renderBaseBuffer) ) : null; return { ...template, renderBaseUrl: uploadedRenderBase?.url || template.renderBaseUrl, renderBasePath: uploadedRenderBase?.pathname || template.renderBasePath, layers: preparedAssets.layers, assetVersion: AGENT_PSD_TEMPLATE_ASSET_VERSION, updatedAt: new Date().toISOString(), } satisfies AgentPsdTemplate; } catch (error) { console.warn("[PSD Template] failed to repair template assets:", { templateId: template.id, error, }); return template; } }