feat: improve app ui and project metadata
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import type { SkillView } from "@/lib/api/types";
|
||||
import { groupByScope, scopeLabel, tierLabel } from "./skills";
|
||||
import { groupByScope, scopeLabel, summarizeSkills, tierLabel } from "./skills";
|
||||
|
||||
const skill = (over: Partial<SkillView>): SkillView => ({
|
||||
name: "x",
|
||||
@@ -36,3 +36,61 @@ describe("groupByScope", () => {
|
||||
expect(groupByScope(undefined)).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("summarizeSkills", () => {
|
||||
it("counts scopes, tiers, readonly skills and unique tables", () => {
|
||||
const summary = summarizeSkills([
|
||||
skill({
|
||||
name: "a",
|
||||
scope: "builtin",
|
||||
tier: "analyst",
|
||||
reads: ["projects", "outline"],
|
||||
writes: [],
|
||||
}),
|
||||
skill({
|
||||
name: "b",
|
||||
scope: "custom",
|
||||
tier: "writer",
|
||||
reads: ["projects"],
|
||||
writes: ["chapters"],
|
||||
}),
|
||||
skill({
|
||||
name: "c",
|
||||
scope: "builtin",
|
||||
tier: "analyst",
|
||||
reads: ["world_entities"],
|
||||
writes: ["outline", "chapters"],
|
||||
}),
|
||||
]);
|
||||
|
||||
expect(summary.total).toBe(3);
|
||||
expect(summary.readonlyCount).toBe(1);
|
||||
expect(summary.writableCount).toBe(2);
|
||||
expect(summary.scopes).toEqual([
|
||||
{ key: "builtin", count: 2 },
|
||||
{ key: "custom", count: 1 },
|
||||
]);
|
||||
expect(summary.tiers).toEqual([
|
||||
{ key: "analyst", count: 2 },
|
||||
{ key: "writer", count: 1 },
|
||||
]);
|
||||
expect(summary.readableTables).toEqual([
|
||||
"outline",
|
||||
"projects",
|
||||
"world_entities",
|
||||
]);
|
||||
expect(summary.writableTables).toEqual(["chapters", "outline"]);
|
||||
});
|
||||
|
||||
it("handles undefined", () => {
|
||||
expect(summarizeSkills(undefined)).toEqual({
|
||||
total: 0,
|
||||
readonlyCount: 0,
|
||||
writableCount: 0,
|
||||
scopes: [],
|
||||
tiers: [],
|
||||
readableTables: [],
|
||||
writableTables: [],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -27,6 +27,21 @@ export function tierLabel(tier: string): string {
|
||||
|
||||
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,
|
||||
@@ -42,3 +57,41 @@ export function groupByScope(
|
||||
}
|
||||
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 }));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user