Files
writer-work-flow/apps/web/lib/skills/skills.ts
2026-06-28 07:31:20 +02:00

98 lines
2.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 技能库纯逻辑:按 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 }));
}