import type { AgentWorkflowMaterialApiConfig, AgentWorkflowMaterialImage, AgentWorkflowMaterialSkuOption, AgentWorkflowMaterialSearchResult, AgentWorkflowMaterialSelectionState, AgentWorkflowMaterialSelections, } from "@/types/agent"; export const AGENT_WORKFLOW_MATERIAL_API_CONFIG_KEY = "agent_workflow_material_api_config"; export const defaultAgentWorkflowMaterialApiConfig: AgentWorkflowMaterialApiConfig = { enabled: false, baseUrl: "https://sc.jiansun.vip/api/external", apiKey: "", }; function sanitizeText(value: unknown, maxLength: number) { return String(value || "").trim().slice(0, maxLength); } function normalizeMaterialImage( value: unknown, index: number ): AgentWorkflowMaterialImage | null { if (!value || typeof value !== "object" || Array.isArray(value)) { return null; } const raw = value as Record; const imageUrl = sanitizeText(raw.imageUrl, 2000); if (!imageUrl) return null; const id = sanitizeText(raw.id, 128) || `image-${index + 1}`; const group = raw.group === "other" ? "other" : "fixed"; return { id, key: sanitizeText(raw.key, 64) || id, label: sanitizeText(raw.label, 128) || `图片${index + 1}`, fileName: sanitizeText(raw.fileName, 255) || `image-${index + 1}`, imageUrl, assetId: sanitizeText(raw.assetId, 128) || null, mimeType: sanitizeText(raw.mimeType, 128) || null, appearanceSummary: sanitizeText(raw.appearanceSummary, 255) || null, sourceType: raw.sourceType === "upload" ? "upload" : "search", group, sortOrder: Number.isFinite(Number(raw.sortOrder)) ? Math.max(0, Math.min(999, Math.round(Number(raw.sortOrder)))) : index, }; } function normalizeMaterialSearchResult(value: unknown): AgentWorkflowMaterialSearchResult | null { if (!value || typeof value !== "object" || Array.isArray(value)) { return null; } const raw = value as Record; const skuOptions: AgentWorkflowMaterialSkuOption[] = Array.isArray(raw.skuOptions) ? raw.skuOptions .map((item) => { if (!item || typeof item !== "object" || Array.isArray(item)) { return null; } const skuRaw = item as Record; const skuName = sanitizeText(skuRaw.skuName, 64); if (!skuName) return null; const normalizedOption: AgentWorkflowMaterialSkuOption = { skuName, skuId: sanitizeText(skuRaw.skuId, 128) || null, hasWhiteBackground: skuRaw.hasWhiteBackground === true, whiteBackgroundCount: Number.isFinite(Number(skuRaw.whiteBackgroundCount)) ? Math.max(0, Math.min(999, Math.round(Number(skuRaw.whiteBackgroundCount)))) : 0, previewImageUrl: sanitizeText(skuRaw.previewImageUrl, 2000) || null, previewFileName: sanitizeText(skuRaw.previewFileName, 255) || null, }; return normalizedOption; }) .filter((item): item is AgentWorkflowMaterialSkuOption => item !== null) .slice(0, 100) : []; const images = Array.isArray(raw.images) ? raw.images .map((item, index) => normalizeMaterialImage(item, index)) .filter((item): item is AgentWorkflowMaterialImage => Boolean(item)) .slice(0, 32) : []; if (images.length === 0 && skuOptions.length === 0) { return null; } return { query: sanitizeText(raw.query, 64), queryType: raw.queryType === "spu" ? "spu" : "sku", matchedSku: sanitizeText(raw.matchedSku, 64), matchedSpu: sanitizeText(raw.matchedSpu, 64) || null, materialId: sanitizeText(raw.materialId, 128) || null, productModel: sanitizeText(raw.productModel, 128) || null, zipUrl: sanitizeText(raw.zipUrl, 2000) || null, fixedCount: Number.isFinite(Number(raw.fixedCount)) ? Math.max(0, Math.min(999, Math.round(Number(raw.fixedCount)))) : images.filter((item) => item.group === "fixed").length, otherCount: Number.isFinite(Number(raw.otherCount)) ? Math.max(0, Math.min(999, Math.round(Number(raw.otherCount)))) : images.filter((item) => item.group === "other").length, totalCount: Number.isFinite(Number(raw.totalCount)) ? Math.max(0, Math.min(999, Math.round(Number(raw.totalCount)))) : images.length, images: images .slice() .sort((left, right) => left.sortOrder - right.sortOrder), recommendedImageIds: Array.isArray(raw.recommendedImageIds) ? Array.from( new Set( raw.recommendedImageIds .map((item) => sanitizeText(item, 128)) .filter((item) => item && images.some((image) => image.id === item)) .slice(0, 16) ) ) : [], skuOptions, goodsInfo: raw.goodsInfo && typeof raw.goodsInfo === "object" && !Array.isArray(raw.goodsInfo) ? { productName: sanitizeText((raw.goodsInfo as Record).productName, 255) || null, sizeInfo: sanitizeText((raw.goodsInfo as Record).sizeInfo, 255) || null, materialInfo: sanitizeText((raw.goodsInfo as Record).materialInfo, 255) || null, designer: sanitizeText((raw.goodsInfo as Record).designer, 128) || null, buyer: sanitizeText((raw.goodsInfo as Record).buyer, 128) || null, boardType: sanitizeText((raw.goodsInfo as Record).boardType, 128) || null, appearanceSummary: sanitizeText((raw.goodsInfo as Record).appearanceSummary, 255) || null, } : null, }; } export function createEmptyAgentWorkflowMaterialSelection(): AgentWorkflowMaterialSelectionState { return { result: null, selectedImageIds: [], }; } export function createEmptyAgentWorkflowMaterialSelections(): AgentWorkflowMaterialSelections { return { primary: createEmptyAgentWorkflowMaterialSelection(), secondary: createEmptyAgentWorkflowMaterialSelection(), }; } function normalizeMaterialSelectionState(value: unknown): AgentWorkflowMaterialSelectionState { const raw = value && typeof value === "object" && !Array.isArray(value) ? (value as Record) : {}; const result = normalizeMaterialSearchResult(raw.result); const validIds = new Set((result?.images || []).map((item) => item.id)); const selectedImageIds = Array.isArray(raw.selectedImageIds) ? Array.from( new Set( raw.selectedImageIds .map((item) => sanitizeText(item, 128)) .filter((item) => item && validIds.has(item)) .slice(0, 32) ) ) : []; return { result, selectedImageIds, }; } export function normalizeAgentWorkflowMaterialSelections( value: unknown ): AgentWorkflowMaterialSelections { const raw = value && typeof value === "object" && !Array.isArray(value) ? (value as Record) : {}; return { primary: normalizeMaterialSelectionState(raw.primary), secondary: normalizeMaterialSelectionState(raw.secondary), }; } export function normalizeAgentWorkflowMaterialApiConfig( value: unknown ): AgentWorkflowMaterialApiConfig { if (!value || typeof value !== "object" || Array.isArray(value)) { return defaultAgentWorkflowMaterialApiConfig; } const raw = value as Partial; return { enabled: raw.enabled === true, baseUrl: sanitizeText(raw.baseUrl, 255) || defaultAgentWorkflowMaterialApiConfig.baseUrl, apiKey: sanitizeText(raw.apiKey, 500), }; } export function normalizeAgentWorkflowMaterialApiConfigForPublic( value: unknown ): AgentWorkflowMaterialApiConfig { const config = normalizeAgentWorkflowMaterialApiConfig(value); return { ...config, apiKey: "", }; }