Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
refactor: 统一Hands系统常量到单个源文件 refactor: 更新Hands中文名称和描述 fix: 修复技能市场在连接状态变化时重新加载 fix: 修复身份变更提案的错误处理逻辑 docs: 更新多个功能文档的验证状态和实现位置 docs: 更新Hands系统文档 test: 添加测试文件验证工作区路径
216 lines
5.8 KiB
TypeScript
216 lines
5.8 KiB
TypeScript
/**
|
|
* Skill Adapter - Converts between configStore and UI skill formats
|
|
*
|
|
* Bridges the gap between:
|
|
* - configStore.SkillInfo (backend/Gateway format)
|
|
* - SkillMarket UI format (based on skill-discovery types)
|
|
*
|
|
* Part of Phase 1: Skill Market Store Unification
|
|
*/
|
|
|
|
import type { SkillInfo as ConfigSkillInfo } from '../store/configStore';
|
|
|
|
// === UI Skill Types (aligned with SkillMarket expectations) ===
|
|
|
|
export interface UISkillInfo {
|
|
id: string;
|
|
name: string;
|
|
description: string;
|
|
triggers: string[];
|
|
capabilities: string[];
|
|
toolDeps: string[];
|
|
installed: boolean;
|
|
category?: string;
|
|
path?: string;
|
|
source?: 'builtin' | 'extra';
|
|
}
|
|
|
|
// Category mapping based on skill keywords
|
|
const CATEGORY_KEYWORDS: Record<string, string[]> = {
|
|
development: ['code', 'git', 'frontend', 'backend', 'react', 'vue', 'api', 'typescript', 'javascript'],
|
|
security: ['security', 'audit', 'vulnerability', 'pentest', 'auth'],
|
|
analytics: ['data', 'analysis', 'analytics', 'visualization', 'report'],
|
|
content: ['writing', 'content', 'article', 'copy', 'chinese'],
|
|
ops: ['devops', 'docker', 'k8s', 'deploy', 'ci', 'cd', 'automation'],
|
|
management: ['pm', 'project', 'requirement', 'planning', 'prd'],
|
|
testing: ['test', 'api test', 'e2e', 'unit'],
|
|
business: ['finance', 'budget', 'expense', 'accounting'],
|
|
marketing: ['social', 'media', 'marketing', 'campaign', 'operation'],
|
|
};
|
|
|
|
/**
|
|
* Infer category from skill name and description
|
|
*/
|
|
function inferCategory(skill: ConfigSkillInfo): string | undefined {
|
|
const text = `${skill.name} ${skill.description || ''}`.toLowerCase();
|
|
|
|
for (const [category, keywords] of Object.entries(CATEGORY_KEYWORDS)) {
|
|
if (keywords.some(keyword => text.includes(keyword))) {
|
|
return category;
|
|
}
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
/**
|
|
* Extract trigger patterns from config format
|
|
*/
|
|
function extractTriggers(triggers?: ConfigSkillInfo['triggers']): string[] {
|
|
if (!triggers) return [];
|
|
|
|
return triggers
|
|
.map(t => t.pattern || t.type)
|
|
.filter((p): p is string => Boolean(p));
|
|
}
|
|
|
|
/**
|
|
* Extract capabilities from actions or capabilities field
|
|
*/
|
|
function extractCapabilities(skill: ConfigSkillInfo): string[] {
|
|
// Prefer explicit capabilities field if available
|
|
if (skill.capabilities && skill.capabilities.length > 0) {
|
|
return skill.capabilities;
|
|
}
|
|
|
|
// Fall back to extracting from actions
|
|
if (skill.actions) {
|
|
return skill.actions
|
|
.map(a => a.type)
|
|
.filter((t): t is string => Boolean(t));
|
|
}
|
|
|
|
return [];
|
|
}
|
|
|
|
/**
|
|
* Extract tool dependencies from actions params
|
|
*/
|
|
function extractToolDeps(actions?: ConfigSkillInfo['actions']): string[] {
|
|
if (!actions) return [];
|
|
|
|
const deps = new Set<string>();
|
|
|
|
for (const action of actions) {
|
|
if (action.params?.tools && Array.isArray(action.params.tools)) {
|
|
for (const tool of action.params.tools) {
|
|
if (typeof tool === 'string') {
|
|
deps.add(tool);
|
|
}
|
|
}
|
|
}
|
|
if (action.params?.toolDeps && Array.isArray(action.params.toolDeps)) {
|
|
for (const dep of action.params.toolDeps) {
|
|
if (typeof dep === 'string') {
|
|
deps.add(dep);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return Array.from(deps);
|
|
}
|
|
|
|
/**
|
|
* Adapt a single skill from configStore format to UI format
|
|
*/
|
|
export function adaptSkillInfo(skill: ConfigSkillInfo): UISkillInfo {
|
|
return {
|
|
id: skill.id,
|
|
name: skill.name,
|
|
description: skill.description || '',
|
|
triggers: extractTriggers(skill.triggers),
|
|
capabilities: extractCapabilities(skill),
|
|
toolDeps: extractToolDeps(skill.actions),
|
|
installed: skill.enabled ?? false,
|
|
category: inferCategory(skill),
|
|
path: skill.path,
|
|
source: skill.source,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Adapt an array of skills from configStore format to UI format
|
|
*/
|
|
export function adaptSkills(skills: ConfigSkillInfo[]): UISkillInfo[] {
|
|
return skills.map(adaptSkillInfo);
|
|
}
|
|
|
|
/**
|
|
* Search skills by query string
|
|
*/
|
|
export function searchSkills(skills: UISkillInfo[], query: string): UISkillInfo[] {
|
|
const q = query.toLowerCase().trim();
|
|
if (!q) return skills;
|
|
|
|
const tokens = q.split(/[\s,;.!?.,;!?]+/).filter(t => t.length > 0);
|
|
|
|
const scored = skills.map(skill => {
|
|
let score = 0;
|
|
|
|
// Name match (highest weight)
|
|
if (skill.name.toLowerCase().includes(q)) score += 10;
|
|
|
|
// Description match
|
|
if (skill.description.toLowerCase().includes(q)) score += 5;
|
|
|
|
// Trigger match
|
|
for (const trigger of skill.triggers) {
|
|
const tLower = trigger.toLowerCase();
|
|
if (tLower === q) { score += 15; break; }
|
|
if (tLower.includes(q) || q.includes(tLower)) score += 8;
|
|
}
|
|
|
|
// Capability match
|
|
for (const cap of skill.capabilities) {
|
|
if (cap.toLowerCase().includes(q)) score += 4;
|
|
}
|
|
|
|
// Token-level matching
|
|
for (const token of tokens) {
|
|
if (skill.name.toLowerCase().includes(token)) score += 2;
|
|
if (skill.description.toLowerCase().includes(token)) score += 1;
|
|
for (const trigger of skill.triggers) {
|
|
if (trigger.toLowerCase().includes(token)) score += 3;
|
|
}
|
|
}
|
|
|
|
// Category match
|
|
if (skill.category && skill.category.toLowerCase().includes(q)) score += 3;
|
|
|
|
return { skill, score };
|
|
});
|
|
|
|
return scored
|
|
.filter(s => s.score > 0)
|
|
.sort((a, b) => b.score - a.score)
|
|
.map(s => s.skill);
|
|
}
|
|
|
|
/**
|
|
* Get unique categories from skills
|
|
*/
|
|
export function getCategories(skills: UISkillInfo[]): string[] {
|
|
const categories = new Set<string>();
|
|
for (const skill of skills) {
|
|
if (skill.category) {
|
|
categories.add(skill.category);
|
|
}
|
|
}
|
|
return Array.from(categories);
|
|
}
|
|
|
|
// === Aliases for backward compatibility ===
|
|
|
|
/**
|
|
* Alias for UISkillInfo for backward compatibility
|
|
*/
|
|
export type SkillDisplay = UISkillInfo;
|
|
|
|
/**
|
|
* Alias for adaptSkills for catalog adaptation
|
|
*/
|
|
export function adaptSkillsCatalog(skills: ConfigSkillInfo[]): UISkillInfo[] {
|
|
return adaptSkills(skills);
|
|
}
|