343 lines
11 KiB
TypeScript
343 lines
11 KiB
TypeScript
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<string>
|
|
): 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<string, unknown>)
|
|
: {};
|
|
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<string, unknown>)
|
|
: {};
|
|
const hasRawSections = Array.isArray(raw.sections);
|
|
const rawSections: unknown[] = hasRawSections
|
|
? (raw.sections as unknown[])
|
|
: defaultAgentPresetLibraryConfig.sections;
|
|
const usedSectionIds = new Set<string>();
|
|
|
|
const sections = rawSections
|
|
.map((item: unknown, index: number) => {
|
|
const record =
|
|
item && typeof item === "object" && !Array.isArray(item)
|
|
? (item as Record<string, unknown>)
|
|
: {};
|
|
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<string>();
|
|
|
|
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<string, unknown>)
|
|
: {};
|
|
|
|
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;
|
|
}
|