220 lines
6.5 KiB
TypeScript
220 lines
6.5 KiB
TypeScript
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;
|
|
}
|
|
}
|