import type { AgentCategoryPath, AgentPresetLibraryCategoryNode, AgentPresetLibraryConfig, AgentPresetLibrarySection, } from "@/types/agent"; export const AGENT_PRESET_LIBRARY_CONFIG_KEY = "agent_preset_library_config"; export const AGENT_PRESET_LIBRARY_MAX_DEPTH = 3; export const AGENT_PRESET_LIBRARY_MY_WORKS_ID = "my-works"; export const AGENT_PRESET_LIBRARY_DEFAULT_SECTION_ID = "cases"; function normalizeText(value: unknown, maxLength: number) { return String(value || "").trim().slice(0, maxLength); } function normalizeBoolean(value: unknown, fallback: boolean) { return typeof value === "boolean" ? value : fallback; } function normalizeNumber(value: unknown, fallback: number, max = 999) { const parsed = Number(value); if (!Number.isFinite(parsed)) return fallback; return Math.max(0, Math.min(max, Math.round(parsed))); } function normalizeId(value: unknown, fallback: string) { const normalized = String(value || "") .trim() .slice(0, 64) .replace(/\s+/g, "_") .replace(/[^\w-]/g, "_") .replace(/_+/g, "_") .replace(/^_+|_+$/g, "") .toLowerCase(); return normalized || fallback; } function createDefaultSection( id: string, label: string, sortOrder: number ): AgentPresetLibrarySection { return { id, label, description: "", enabled: true, sortOrder, categories: [], }; } export const defaultAgentPresetLibraryConfig: AgentPresetLibraryConfig = { sections: [ createDefaultSection("cases", "案例广场", 0), createDefaultSection("personal-assets", "个人资产库", 1), createDefaultSection("company-assets", "公司资产库", 2), ], }; function normalizeNodeArray( input: unknown, depth: number, usedIds: Set ): AgentPresetLibraryCategoryNode[] { if (!Array.isArray(input) || depth > AGENT_PRESET_LIBRARY_MAX_DEPTH) { return []; } return input .map((item, index) => { const raw = item && typeof item === "object" && !Array.isArray(item) ? (item as Record) : {}; const name = normalizeText(raw.name, 64) || `分类${index + 1}`; const baseId = normalizeId(raw.id, `category_${depth}_${index + 1}`); let id = baseId; let duplicateIndex = 2; while (usedIds.has(id)) { id = `${baseId}_${duplicateIndex}`; duplicateIndex += 1; } usedIds.add(id); return { id, name, enabled: normalizeBoolean(raw.enabled, true), sortOrder: normalizeNumber(raw.sortOrder, index), children: depth < AGENT_PRESET_LIBRARY_MAX_DEPTH ? normalizeNodeArray(raw.children, depth + 1, usedIds) : [], }; }) .sort((left, right) => left.sortOrder - right.sortOrder); } export function normalizeAgentPresetLibraryConfig( value: unknown ): AgentPresetLibraryConfig { const raw = value && typeof value === "object" && !Array.isArray(value) ? (value as Record) : {}; const hasRawSections = Array.isArray(raw.sections); const rawSections: unknown[] = hasRawSections ? (raw.sections as unknown[]) : defaultAgentPresetLibraryConfig.sections; const usedSectionIds = new Set(); const sections = rawSections .map((item: unknown, index: number) => { const record = item && typeof item === "object" && !Array.isArray(item) ? (item as Record) : {}; const fallbackSection = defaultAgentPresetLibraryConfig.sections[index] || createDefaultSection(`section_${index + 1}`, `分类页${index + 1}`, index); const baseId = normalizeId(record.id, fallbackSection.id); let id = baseId; let duplicateIndex = 2; while (usedSectionIds.has(id)) { id = `${baseId}_${duplicateIndex}`; duplicateIndex += 1; } usedSectionIds.add(id); const usedNodeIds = new Set(); return { id, label: normalizeText(record.label, 64) || fallbackSection.label, description: normalizeText(record.description, 200) || "", enabled: normalizeBoolean(record.enabled, true), sortOrder: normalizeNumber(record.sortOrder, fallbackSection.sortOrder ?? index), categories: normalizeNodeArray(record.categories, 1, usedNodeIds), }; }) .sort( (left: AgentPresetLibrarySection, right: AgentPresetLibrarySection) => left.sortOrder - right.sortOrder ); return { sections: hasRawSections ? sections : sections.length > 0 ? sections : defaultAgentPresetLibraryConfig.sections, }; } export function getEnabledAgentPresetLibrarySections( config: AgentPresetLibraryConfig ) { return [...config.sections] .filter((section) => section.enabled !== false) .sort((left, right) => left.sortOrder - right.sortOrder); } export function getEnabledAgentPresetLibraryNodes( nodes: AgentPresetLibraryCategoryNode[] ): AgentPresetLibraryCategoryNode[] { return [...nodes] .filter((node) => node.enabled !== false) .sort((left, right) => left.sortOrder - right.sortOrder) .map((node) => ({ ...node, children: getEnabledAgentPresetLibraryNodes(node.children || []), })); } function findNodeByMatcher( nodes: AgentPresetLibraryCategoryNode[], matcher: (node: AgentPresetLibraryCategoryNode) => boolean ): AgentPresetLibraryCategoryNode | null { for (const node of nodes) { if (matcher(node)) { return node; } const foundChild = findNodeByMatcher(node.children || [], matcher); if (foundChild) { return foundChild; } } return null; } export function normalizeAgentCategoryPath(value: unknown): AgentCategoryPath { const raw = value && typeof value === "object" && !Array.isArray(value) ? (value as Record) : {}; return { sectionId: normalizeText(raw.sectionId, 64) || null, sectionName: normalizeText(raw.sectionName, 64) || null, level1Id: normalizeText(raw.level1Id, 64) || null, level1Name: normalizeText(raw.level1Name, 64) || normalizeText(raw.productCategory, 64) || null, level2Id: normalizeText(raw.level2Id, 64) || null, level2Name: normalizeText(raw.level2Name, 64) || normalizeText(raw.audienceTag, 64) || null, level3Id: normalizeText(raw.level3Id, 64) || null, level3Name: normalizeText(raw.level3Name, 64) || normalizeText(raw.usageScenario, 64) || null, productCategory: normalizeText(raw.productCategory, 64) || null, audienceTag: normalizeText(raw.audienceTag, 64) || null, usageScenario: normalizeText(raw.usageScenario, 64) || null, brand: normalizeText(raw.brand, 64) || null, }; } export interface ResolvedAgentPresetCategoryPath { section: AgentPresetLibrarySection | null; level1: AgentPresetLibraryCategoryNode | null; level2: AgentPresetLibraryCategoryNode | null; level3: AgentPresetLibraryCategoryNode | null; sectionId: string | null; sectionName: string | null; level1Id: string | null; level1Name: string | null; level2Id: string | null; level2Name: string | null; level3Id: string | null; level3Name: string | null; } export function resolveAgentPresetCategoryPath(params: { categoryPath?: AgentCategoryPath | null; config?: AgentPresetLibraryConfig | null; }) : ResolvedAgentPresetCategoryPath { const normalizedPath = normalizeAgentCategoryPath(params.categoryPath); const config = normalizeAgentPresetLibraryConfig( params.config || defaultAgentPresetLibraryConfig ); const sections = getEnabledAgentPresetLibrarySections(config); const fallbackSection = sections.find((section) => section.id === AGENT_PRESET_LIBRARY_DEFAULT_SECTION_ID) || sections[0] || null; const section = sections.find((item) => item.id === normalizedPath.sectionId) || sections.find((item) => item.label === normalizedPath.sectionName) || ((!normalizedPath.sectionId && !normalizedPath.sectionName && (normalizedPath.level1Name || normalizedPath.level2Name || normalizedPath.level3Name)) || normalizedPath.productCategory || normalizedPath.audienceTag || normalizedPath.usageScenario ? fallbackSection : null); const level1 = section ? findNodeByMatcher(getEnabledAgentPresetLibraryNodes(section.categories), (node) => { return ( node.id === normalizedPath.level1Id || node.name === normalizedPath.level1Name || node.name === normalizedPath.productCategory ); }) : null; const level2 = level1 ? findNodeByMatcher(getEnabledAgentPresetLibraryNodes(level1.children || []), (node) => { return ( node.id === normalizedPath.level2Id || node.name === normalizedPath.level2Name || node.name === normalizedPath.audienceTag ); }) : null; const level3 = level2 ? findNodeByMatcher(getEnabledAgentPresetLibraryNodes(level2.children || []), (node) => { return ( node.id === normalizedPath.level3Id || node.name === normalizedPath.level3Name || node.name === normalizedPath.usageScenario ); }) : null; return { section, level1, level2, level3, sectionId: section?.id || normalizedPath.sectionId || null, sectionName: section?.label || normalizedPath.sectionName || null, level1Id: level1?.id || normalizedPath.level1Id || null, level1Name: level1?.name || normalizedPath.level1Name || normalizedPath.productCategory || null, level2Id: level2?.id || normalizedPath.level2Id || null, level2Name: level2?.name || normalizedPath.level2Name || normalizedPath.audienceTag || null, level3Id: level3?.id || normalizedPath.level3Id || null, level3Name: level3?.name || normalizedPath.level3Name || normalizedPath.usageScenario || null, }; } export function buildAgentPresetCategoryPath(params: { section?: AgentPresetLibrarySection | null; level1?: AgentPresetLibraryCategoryNode | null; level2?: AgentPresetLibraryCategoryNode | null; level3?: AgentPresetLibraryCategoryNode | null; }): AgentCategoryPath | null { const section = params.section || null; const level1 = params.level1 || null; const level2 = params.level2 || null; const level3 = params.level3 || null; if (!section && !level1 && !level2 && !level3) { return null; } const nextPath: AgentCategoryPath = {}; if (section?.id) nextPath.sectionId = section.id; if (section?.label) nextPath.sectionName = section.label; if (level1?.id) nextPath.level1Id = level1.id; if (level1?.name) { nextPath.level1Name = level1.name; nextPath.productCategory = level1.name; } if (level2?.id) nextPath.level2Id = level2.id; if (level2?.name) { nextPath.level2Name = level2.name; nextPath.audienceTag = level2.name; } if (level3?.id) nextPath.level3Id = level3.id; if (level3?.name) { nextPath.level3Name = level3.name; nextPath.usageScenario = level3.name; } return Object.keys(nextPath).length > 0 ? nextPath : null; }