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 { isPsdAdjustmentLikeImageLayer, pickPrimaryPsdImageLayerId, } from "@/lib/agents/psd-template-image-slot-selection"; 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() ); } 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 repairEditableImageLayerSelection(template: AgentPsdTemplate) { const imageLayers = template.layers.filter(isAgentPsdImageLayerSlot); if (imageLayers.length === 0) { return template.layers; } const editableImageLayers = imageLayers.filter((layer) => layer.isEditable); const defaultImageLayer = imageLayers.find((layer) => layer.isEditable && layer.isDefaultResultSource); const shouldAutoRepairSelection = editableImageLayers.length === 0 || !defaultImageLayer || (editableImageLayers.length === 1 && isPsdAdjustmentLikeImageLayer(editableImageLayers[0])); if (!shouldAutoRepairSelection) { return template.layers; } const nextPrimaryLayerId = pickPrimaryPsdImageLayerId({ imageLayers, canvasWidth: template.width, canvasHeight: template.height, }); if (!nextPrimaryLayerId) { return template.layers; } const currentPrimaryLayerId = defaultImageLayer?.id || editableImageLayers[0]?.id || ""; if (currentPrimaryLayerId === nextPrimaryLayerId) { return template.layers; } return template.layers.map((layer) => { if (!isAgentPsdImageLayerSlot(layer)) { return layer; } if (layer.id === nextPrimaryLayerId) { return { ...layer, isEditable: true, isDefaultResultSource: true, allowUserUploadOverride: true, }; } if (layer.id === currentPrimaryLayerId) { return { ...layer, isEditable: false, isDefaultResultSource: false, allowUserUploadOverride: false, }; } return layer; }); } 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 = repairEditableImageLayerSelection(template); 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; } }