import { createEmptyAgentModeWorkspaceState, normalizeAgentModeWorkspaceState, } from "@/lib/agents/mode-workspace-state"; import { createEmptyAgentWorkflowMaterialSelections, normalizeAgentWorkflowMaterialSelections, } from "@/lib/agents/workflow-material-config"; import type { AgentGenerationProjectPersistedState, AgentGenerationProjectResultCollections, AgentGenerationResultModeType, AgentGenerationProjectWorkspaceState, AgentWorkflowPersistedState, } from "@/types/agent"; export function createEmptyAgentGenerationProjectWorkspaceState(): AgentGenerationProjectWorkspaceState { return { input: "", attachments: [], selectedModelId: "auto", imageParams: { aspectRatio: "auto", imageSize: "2K", imageCount: 1, }, }; } export function createEmptyAgentWorkflowPersistedState(): AgentWorkflowPersistedState { return { inputs: { primarySku: "", secondarySku: "", brand: "", store: "", officialSecondCategory: "", officialThirdCategory: "", thirdCategory: "", fourthCategory: "", fifthCategory: "", priceBand: "", }, materials: createEmptyAgentWorkflowMaterialSelections(), appearanceRecognitionEnabled: false, selections: {}, selectedBrandModel: null, selectedAgentPresetId: null, currentStage: "workflow", agentModeState: createEmptyAgentModeWorkspaceState(), }; } export function createEmptyAgentGenerationProjectPersistedState(): AgentGenerationProjectPersistedState { return { workflowState: createEmptyAgentWorkflowPersistedState(), workspaceState: createEmptyAgentGenerationProjectWorkspaceState(), resultImageUrls: [], resultCollections: {}, deletedResultCollections: {}, }; } function normalizeResultCollections(value: unknown): AgentGenerationProjectResultCollections { const raw = value && typeof value === "object" && !Array.isArray(value) ? (value as Record) : {}; const normalizeUrlList = (input: unknown) => Array.isArray(input) ? Array.from( new Set( input .map((item) => String(item || "").trim()) .filter((item) => /^https?:\/\//i.test(item)) ) ).slice(0, 128) : []; return { scene_generation: normalizeUrlList(raw.scene_generation), detail_generation: normalizeUrlList(raw.detail_generation), detail_replacement: normalizeUrlList(raw.detail_replacement), sku_replacement: normalizeUrlList(raw.sku_replacement), }; } function inferLegacyResultModeType( workflowState: AgentWorkflowPersistedState, resultUrls: string[] ): AgentGenerationResultModeType { const detailReferenceCount = workflowState.agentModeState?.detailReplacement?.references?.length || 0; const skuReferenceCount = workflowState.agentModeState?.skuReplacement?.references?.length || 0; const resultCount = resultUrls.length; if (detailReferenceCount > 0 && skuReferenceCount === 0) { return "detail_replacement"; } if (skuReferenceCount > 0 && detailReferenceCount === 0) { return "sku_replacement"; } if (detailReferenceCount > 0 || skuReferenceCount > 0) { const detailGap = Math.abs(resultCount - detailReferenceCount); const skuGap = Math.abs(resultCount - skuReferenceCount); return detailGap <= skuGap ? "detail_replacement" : "sku_replacement"; } return "scene_generation"; } function normalizeMigratedResultCollections(params: { workflowState: AgentWorkflowPersistedState; resultImageUrls: string[]; resultCollections: AgentGenerationProjectResultCollections; }) { const normalizedSceneUrls = params.resultCollections.scene_generation || []; const normalizedDetailGenerationUrls = params.resultCollections.detail_generation || []; const normalizedDetailUrls = params.resultCollections.detail_replacement || []; const normalizedSkuUrls = params.resultCollections.sku_replacement || []; const hasExplicitBuckets = normalizedSceneUrls.length > 0 || normalizedDetailGenerationUrls.length > 0 || normalizedDetailUrls.length > 0 || normalizedSkuUrls.length > 0; if (!hasExplicitBuckets && params.resultImageUrls.length > 0) { const inferredModeType = inferLegacyResultModeType( params.workflowState, params.resultImageUrls ); return { scene_generation: inferredModeType === "scene_generation" ? params.resultImageUrls : [], detail_replacement: inferredModeType === "detail_replacement" ? params.resultImageUrls : [], sku_replacement: inferredModeType === "sku_replacement" ? params.resultImageUrls : [], } satisfies AgentGenerationProjectResultCollections; } const detailReferenceCount = params.workflowState.agentModeState?.detailReplacement?.references?.length || 0; const skuReferenceCount = params.workflowState.agentModeState?.skuReplacement?.references?.length || 0; if ( normalizedSceneUrls.length > 0 && normalizedDetailUrls.length === 0 && normalizedSkuUrls.length === 0 ) { if ( detailReferenceCount > 0 && normalizedSceneUrls.length >= Math.max(4, Math.floor(detailReferenceCount * 0.6)) ) { return { scene_generation: [], detail_generation: [], detail_replacement: normalizedSceneUrls, sku_replacement: [], } satisfies AgentGenerationProjectResultCollections; } if ( skuReferenceCount > 0 && normalizedSceneUrls.length >= Math.max(4, Math.floor(skuReferenceCount * 0.6)) ) { return { scene_generation: [], detail_generation: [], detail_replacement: [], sku_replacement: normalizedSceneUrls, } satisfies AgentGenerationProjectResultCollections; } } return params.resultCollections; } function normalizeWorkspaceState(value: unknown): AgentGenerationProjectWorkspaceState { const raw = value && typeof value === "object" && !Array.isArray(value) ? (value as Record) : {}; const attachments = Array.isArray(raw.attachments) ? raw.attachments .map((item) => { if (!item || typeof item !== "object" || Array.isArray(item)) { return null; } const record = item as Record; const url = String(record.url || "").trim(); if (!url) return null; return { url, name: String(record.name || "").trim() || null, contentType: String(record.contentType || "").trim() || null, assetId: String(record.assetId || "").trim() || null, }; }) .slice(0, 16) : []; const rawImageParams = raw.imageParams && typeof raw.imageParams === "object" && !Array.isArray(raw.imageParams) ? (raw.imageParams as Record) : {}; const imageCount = Number(rawImageParams.imageCount); return { input: String(raw.input || "").trim(), attachments, selectedModelId: String(raw.selectedModelId || "").trim() || "auto", imageParams: { aspectRatio: String(rawImageParams.aspectRatio || "").trim() || "auto", imageSize: String(rawImageParams.imageSize || "").trim() || "2K", imageCount: Number.isFinite(imageCount) ? Math.max(1, Math.min(8, Math.round(imageCount))) : 1, }, }; } function normalizeWorkflowState(value: unknown): AgentWorkflowPersistedState { const raw = value && typeof value === "object" && !Array.isArray(value) ? (value as Record) : {}; return { inputs: { primarySku: String(raw.inputs && typeof raw.inputs === "object" ? (raw.inputs as Record).primarySku || "" : "").trim(), secondarySku: String(raw.inputs && typeof raw.inputs === "object" ? (raw.inputs as Record).secondarySku || "" : "").trim(), brand: String(raw.inputs && typeof raw.inputs === "object" ? (raw.inputs as Record).brand || "" : "").trim(), store: String(raw.inputs && typeof raw.inputs === "object" ? (raw.inputs as Record).store || "" : "").trim(), officialSecondCategory: String(raw.inputs && typeof raw.inputs === "object" ? (raw.inputs as Record).officialSecondCategory || "" : "").trim(), officialThirdCategory: String(raw.inputs && typeof raw.inputs === "object" ? (raw.inputs as Record).officialThirdCategory || "" : "").trim(), thirdCategory: String(raw.inputs && typeof raw.inputs === "object" ? (raw.inputs as Record).thirdCategory || "" : "").trim(), fourthCategory: String(raw.inputs && typeof raw.inputs === "object" ? (raw.inputs as Record).fourthCategory || "" : "").trim(), fifthCategory: String(raw.inputs && typeof raw.inputs === "object" ? (raw.inputs as Record).fifthCategory || "" : "").trim(), priceBand: String(raw.inputs && typeof raw.inputs === "object" ? (raw.inputs as Record).priceBand || "" : "").trim(), }, materials: normalizeAgentWorkflowMaterialSelections(raw.materials), appearanceRecognitionEnabled: raw.appearanceRecognitionEnabled === true, selections: raw.selections && typeof raw.selections === "object" && !Array.isArray(raw.selections) ? (raw.selections as Record) : {}, selectedBrandModel: raw.selectedBrandModel && typeof raw.selectedBrandModel === "object" && !Array.isArray(raw.selectedBrandModel) ? { ...(raw.selectedBrandModel as Record), brandName: String((raw.selectedBrandModel as Record).brandName || "").trim(), id: String((raw.selectedBrandModel as Record).id || "").trim(), imageUrl: String((raw.selectedBrandModel as Record).imageUrl || "").trim(), assetId: String((raw.selectedBrandModel as Record).assetId || "").trim() || null, displayName: String((raw.selectedBrandModel as Record).displayName || "").trim() || null, mimeType: String((raw.selectedBrandModel as Record).mimeType || "").trim() || null, sortOrder: Number.isFinite(Number((raw.selectedBrandModel as Record).sortOrder)) ? Math.max(0, Math.min(999, Math.round(Number((raw.selectedBrandModel as Record).sortOrder)))) : 0, } : null, selectedAgentPresetId: String(raw.selectedAgentPresetId || "").trim() || null, currentStage: raw.currentStage === "mode" ? "mode" : "workflow", agentModeState: normalizeAgentModeWorkspaceState(raw.agentModeState), }; } export function normalizeAgentGenerationProjectPersistedState( value: unknown ): AgentGenerationProjectPersistedState { const raw = value && typeof value === "object" && !Array.isArray(value) ? (value as Record) : {}; const resultImageUrls = Array.isArray(raw.resultImageUrls) ? Array.from( new Set( raw.resultImageUrls .map((item) => String(item || "").trim()) .filter((item) => /^https?:\/\//i.test(item)) ) ).slice(0, 64) : []; const workflowState = normalizeWorkflowState(raw.workflowState); const resultCollections = normalizeMigratedResultCollections({ workflowState, resultImageUrls, resultCollections: normalizeResultCollections(raw.resultCollections), }); const deletedResultCollections = normalizeResultCollections(raw.deletedResultCollections); return { workflowState, workspaceState: normalizeWorkspaceState(raw.workspaceState), resultImageUrls, resultCollections, deletedResultCollections, }; }