98 lines
2.7 KiB
TypeScript
98 lines
2.7 KiB
TypeScript
// 技能库纯逻辑:按 scope 分组、档位文案。
|
||
// 对齐 C3 扩(GET /skills,只读注册表视图)。纯逻辑,便于 node 环境单测。
|
||
|
||
import type { SkillView } from "@/lib/api/types";
|
||
|
||
export type SkillScope = "builtin" | "custom" | "community";
|
||
|
||
export const SCOPE_LABELS: Record<string, string> = {
|
||
builtin: "内置",
|
||
custom: "自定义",
|
||
community: "社区",
|
||
};
|
||
|
||
export const TIER_LABELS: Record<string, string> = {
|
||
writer: "写手",
|
||
analyst: "分析",
|
||
light: "轻量",
|
||
};
|
||
|
||
export function scopeLabel(scope: string): string {
|
||
return SCOPE_LABELS[scope] ?? scope;
|
||
}
|
||
|
||
export function tierLabel(tier: string): string {
|
||
return TIER_LABELS[tier] ?? tier;
|
||
}
|
||
|
||
export type SkillGroups = { scope: string; skills: SkillView[] }[];
|
||
|
||
export interface SkillCount {
|
||
key: string;
|
||
count: number;
|
||
}
|
||
|
||
export interface SkillsSummary {
|
||
total: number;
|
||
readonlyCount: number;
|
||
writableCount: number;
|
||
scopes: SkillCount[];
|
||
tiers: SkillCount[];
|
||
readableTables: string[];
|
||
writableTables: string[];
|
||
}
|
||
|
||
// 按 scope 分组(保持服务端 name 升序);scope 顺序按首次出现。
|
||
export function groupByScope(
|
||
skills: readonly SkillView[] | undefined,
|
||
): SkillGroups {
|
||
const order: string[] = [];
|
||
const map = new Map<string, SkillView[]>();
|
||
for (const skill of skills ?? []) {
|
||
if (!map.has(skill.scope)) {
|
||
order.push(skill.scope);
|
||
map.set(skill.scope, []);
|
||
}
|
||
map.get(skill.scope)?.push(skill);
|
||
}
|
||
return order.map((scope) => ({ scope, skills: map.get(scope) ?? [] }));
|
||
}
|
||
|
||
export function summarizeSkills(
|
||
skills: readonly SkillView[] | undefined,
|
||
): SkillsSummary {
|
||
const scopeCounts = new Map<string, number>();
|
||
const tierCounts = new Map<string, number>();
|
||
const readableTables = new Set<string>();
|
||
const writableTables = new Set<string>();
|
||
let writableCount = 0;
|
||
|
||
for (const skill of skills ?? []) {
|
||
scopeCounts.set(skill.scope, (scopeCounts.get(skill.scope) ?? 0) + 1);
|
||
tierCounts.set(skill.tier, (tierCounts.get(skill.tier) ?? 0) + 1);
|
||
for (const table of skill.reads ?? []) {
|
||
readableTables.add(table);
|
||
}
|
||
if (skill.writes && skill.writes.length > 0) {
|
||
writableCount += 1;
|
||
for (const table of skill.writes) {
|
||
writableTables.add(table);
|
||
}
|
||
}
|
||
}
|
||
|
||
return {
|
||
total: skills?.length ?? 0,
|
||
readonlyCount: (skills?.length ?? 0) - writableCount,
|
||
writableCount,
|
||
scopes: countsFromMap(scopeCounts),
|
||
tiers: countsFromMap(tierCounts),
|
||
readableTables: [...readableTables].sort(),
|
||
writableTables: [...writableTables].sort(),
|
||
};
|
||
}
|
||
|
||
function countsFromMap(map: Map<string, number>): SkillCount[] {
|
||
return [...map.entries()].map(([key, count]) => ({ key, count }));
|
||
}
|