feat(client): enrich digital skill catalog

This commit is contained in:
baiyanyun
2026-06-07 20:04:38 +08:00
parent 0f0bffe695
commit ee51fcdbeb
12 changed files with 643 additions and 263 deletions

View File

@@ -30,6 +30,13 @@ const cadenceLabels: Record<TaskCadence, string> = {
adhoc: '临时',
};
const policyLabels: Record<NonNullable<SkillSummary['governancePolicy']>, string> = {
allow: '白名单',
deny: '黑名单',
open: '开放',
disabled: '停用',
};
function getCadence(skill: SkillSummary): TaskCadence {
const text = `${skill.id} ${skill.name} ${skill.description} ${skill.category}`.toLowerCase();
if (/weekly|week|周/.test(text)) return 'weekly';
@@ -71,6 +78,26 @@ function learningStatus(skill: SkillSummary, recommended: boolean, advanced: boo
return '可学习';
}
function skillMetaBadges(skill: SkillSummary): string[] {
const badges: string[] = [];
if (skill.serverId) badges.push(skill.serverId);
if (skill.running !== null && skill.running !== undefined) badges.push(skill.running ? '运行中' : '未运行');
if (skill.governancePolicy) badges.push(policyLabels[skill.governancePolicy]);
if (typeof skill.toolCount === 'number') badges.push(`${skill.toolCount} 工具`);
if (skill.allowTools?.length) badges.push(`allow ${skill.allowTools.length}`);
if (skill.denyTools?.length) badges.push(`deny ${skill.denyTools.length}`);
return badges.slice(0, 5);
}
function recentCallText(skill: SkillSummary): string | null {
const call = skill.recentCalls?.[0];
if (!call) return typeof skill.callCount === 'number' && skill.callCount > 0
? `${skill.callCount} 次调用`
: null;
const tool = call.toolName ? `${call.toolName} · ` : '';
return `${tool}${call.message || call.status || '最近调用'}`;
}
function SkillCard({
skill,
recommended = false,
@@ -90,6 +117,8 @@ function SkillCard({
}) {
const cadence = getCadence(skill);
const status = learningStatus(skill, recommended, advanced);
const metaBadges = skillMetaBadges(skill);
const recentCall = recentCallText(skill);
return (
<div
@@ -112,6 +141,12 @@ function SkillCard({
</span>
</div>
<p className="de-skill-desc">{getOutcome(skill)}</p>
{metaBadges.length > 0 && (
<div className="de-skill-meta-row">
{metaBadges.map((badge) => <span key={badge} className="de-skill-meta-chip">{badge}</span>)}
</div>
)}
{recentCall && <p className="de-skill-recent">{recentCall}</p>}
<div className="de-skill-card-foot">
<span className="de-skill-version">
{cadenceLabels[cadence]}
@@ -205,7 +240,19 @@ export default function SkillLibrary({ focusSkillId }: { focusSkillId?: string |
const filtered = skills
.filter(s => cadenceFilter === 'all' || getCadence(s) === cadenceFilter)
.filter(s => !search || s.name.includes(search) || s.description.includes(search) || s.category.includes(search));
.filter(s => {
if (!search) return true;
const haystack = [
s.name,
s.description,
s.category,
s.serverId,
...(s.tools ?? []),
...(s.allowTools ?? []),
...(s.denyTools ?? []),
].filter(Boolean).join(' ');
return haystack.includes(search);
});
const advancedSkills = filtered.filter(isAdvancedSkill);
const advancedIds = new Set(advancedSkills.map(s => s.id));