ai-chatbot/lib/agents/preset-library-config.ts
Your Name 5c10380666
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled
feat: improve replacement workflow interactions
2026-03-21 18:36:12 +08:00

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;
}