From 825fe36967fffe22627640b250522d36fdff6201 Mon Sep 17 00:00:00 2001 From: duanshuwen Date: Fri, 10 Apr 2026 23:03:56 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E6=8A=80=E8=83=BD?= =?UTF-8?q?=E7=9B=B8=E5=85=B3=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Skills-Development-Plan.md | 397 ++++++++++++++++++ Skills-implementation-referrnce.md | 291 +++++++++++++ src/i18n/constants.ts | 1 + src/i18n/locales/en/skills.json | 112 +++++ src/i18n/locales/ja/skills.json | 112 +++++ src/i18n/locales/zh/skills.json | 112 +++++ src/lib/skills-api.ts | 174 ++++++++ src/lib/skills-types.ts | 68 +++ src/lib/skills-utils.ts | 19 + .../components/EventManager/addDialog.vue | 39 +- src/pages/login/index.vue | 2 +- src/pages/skills/components/EnvVarManager.vue | 77 ++++ .../skills/components/MarketplaceDrawer.vue | 140 ++++++ .../skills/components/SkillDetailDrawer.vue | 250 +++++++++++ src/pages/skills/components/SkillListItem.vue | 61 +++ src/pages/skills/index.vue | 344 ++++++++++++++- src/store/skills.ts | 123 ++++++ 17 files changed, 2295 insertions(+), 27 deletions(-) create mode 100644 Skills-Development-Plan.md create mode 100644 Skills-implementation-referrnce.md create mode 100644 src/i18n/locales/en/skills.json create mode 100644 src/i18n/locales/ja/skills.json create mode 100644 src/i18n/locales/zh/skills.json create mode 100644 src/lib/skills-api.ts create mode 100644 src/lib/skills-types.ts create mode 100644 src/lib/skills-utils.ts create mode 100644 src/pages/skills/components/EnvVarManager.vue create mode 100644 src/pages/skills/components/MarketplaceDrawer.vue create mode 100644 src/pages/skills/components/SkillDetailDrawer.vue create mode 100644 src/pages/skills/components/SkillListItem.vue create mode 100644 src/store/skills.ts diff --git a/Skills-Development-Plan.md b/Skills-Development-Plan.md new file mode 100644 index 0000000..3555c0f --- /dev/null +++ b/Skills-Development-Plan.md @@ -0,0 +1,397 @@ +# zn-ai 技能功能开发计划 + +## 项目概述 + +本计划旨在将 ClawX 项目的 Skills(技能)功能完整迁移到 zn-ai 项目中,并保持功能一致性、代码完整性和用户体验对齐。基于对 ClawX Skills 功能的深入分析,制定本四阶段开发计划。 + +## 技术栈映射与适配策略 + +### 技术栈差异 +| 领域 | ClawX (源项目) | zn-ai (目标项目) | 适配策略 | +|------|----------------|------------------|----------| +| 前端框架 | React 18 + TypeScript | Vue 3 + TypeScript | React组件转换为Vue组件(Composition API) | +| 状态管理 | Zustand | Pinia | Zustand Store转换为Pinia Store | +| UI组件库 | Shadcn/ui (基于Radix) | Element Plus + Tailwind CSS | 使用Element Plus组件+自定义样式实现相似UI | +| API通信 | 自定义hostApiFetch | 现有axios/request封装 | 适配现有API调用方式 | +| 国际化 | react-i18next | vue-i18n | 翻译键迁移,保持相同结构 | +| 构建工具 | Vite | Vite | 直接兼容 | + +### 核心挑战与解决方案 +1. **UI一致性**:通过Element Plus组件+Tailwind CSS自定义样式实现类似视觉效果 +2. **状态管理**:Pinia的模块化store结构与Zustand的函数式风格适配 +3. **组件交互**:Vue的事件系统与React的props/callback模式转换 +4. **响应式系统**:利用Vue的ref/reactive替代React的useState + +## 阶段一:基础架构与类型定义 (预计:1-2天) + +### 目标 +建立技能功能的基础架构,包括类型定义、状态管理骨架和核心API接口。 + +### 具体任务 + +#### 1.1 类型定义创建 +**文件**: `src/lib/skills-types.ts` +```typescript +// 复用ClawX的Skill和MarketplaceSkill类型定义 +// 根据zn-ai的命名规范进行适当调整 +``` + +**文件**: `src/lib/skill-config.ts` +```typescript +// 技能配置相关的类型和工具函数 +// 包括配置schema、验证逻辑等 +``` + +#### 1.2 状态管理Store +**文件**: `src/store/skills.ts` +```typescript +// Pinia Store定义,包含: +// - 技能列表、搜索结果、加载状态等核心状态 +// - fetchSkills、searchSkills、installSkill等核心actions +// - 错误处理逻辑 +``` + +#### 1.3 API客户端适配 +**文件**: `src/lib/skills-api.ts` +```typescript +// 封装技能相关的API调用 +// 与现有的host-api.ts或axios封装集成 +// 包含:获取技能列表、搜索市场、安装/卸载等接口 +``` + +#### 1.4 国际化资源 +**文件**: `src/i18n/locales/zh/skills.json` +**文件**: `src/i18n/locales/en/skills.json` +**文件**: `src/i18n/locales/ja/skills.json` +```json +// 从ClawX项目迁移翻译键 +// 保持相同的键结构,确保功能一致性 +``` + +**更新**: `src/i18n/constants.ts` +```typescript +// 添加'skills'到NAMESPACES数组 +``` + +### 交付成果 +- 完整的TypeScript类型定义 +- Pinia Store骨架(可编译通过) +- API客户端接口定义 +- 多语言资源文件 +- 无TypeScript编译错误 + +## 阶段二:核心UI组件开发 (预计:2-3天) + +### 目标 +开发技能功能的主要UI组件,包括技能列表、详情面板和市场搜索界面。 + +### 具体任务 + +#### 2.1 主页面组件 +**文件**: `src/pages/skills/index.vue` +```vue + + + +``` + +#### 2.2 技能列表项组件 +**文件**: `src/pages/skills/components/SkillListItem.vue` +```vue + + +``` + +#### 2.3 技能详情弹窗组件 +**文件**: `src/pages/skills/components/SkillDetailDialog.vue` +```vue + + +``` + +#### 2.4 市场搜索弹窗组件 +**文件**: `src/pages/skills/components/MarketplaceDialog.vue` +```vue + + +``` + +#### 2.5 环境变量管理组件 +**文件**: `src/pages/skills/components/EnvVarManager.vue` +```vue + + +``` + +### UI/UX设计要点 +1. **视觉风格**:使用Element Plus的Dialog、Drawer、Card等组件,配合Tailwind CSS实现类似ClawX的视觉效果 +2. **交互设计**: + - 点击技能卡片打开详情 + - 状态开关的即时反馈 + - 搜索输入的防抖处理(300ms) + - 批量操作的成功/失败提示 +3. **响应式设计**:确保桌面端良好的显示效果 + +### 交付成果 +- 完整的技能管理页面(可交互但数据为模拟) +- 详情弹窗组件(包含配置表单) +- 市场搜索界面 +- 基本的样式和交互效果 + +## 阶段三:功能集成与数据绑定 (预计:2-3天) + +### 目标 +将UI组件与状态管理和后端API进行集成,实现完整的数据流和业务逻辑。 + +### 具体任务 + +#### 3.1 Store功能实现 +**完善**: `src/store/skills.ts` +```typescript +// 实现所有核心actions: +// - fetchSkills(): 集成真实API调用 +// - searchSkills(): 连接市场搜索API +// - installSkill()/uninstallSkill(): 安装/卸载逻辑 +// - enableSkill()/disableSkill(): 启用/禁用技能 +// - 错误处理:超时、频率限制等 +``` + +#### 3.2 API集成 +**完善**: `src/lib/skills-api.ts` +```typescript +// 实现所有API调用: +// - GET /api/skills/configs +// - PUT /api/skills/config +// - POST /api/clawhub/search +// - POST /api/clawhub/install +// - POST /api/clawhub/uninstall +// - GET /api/clawhub/list +// - 等所有必要的接口 +``` + +#### 3.3 组件数据绑定 +**更新各Vue组件**: +- 将UI组件连接到Pinia Store +- 实现响应式数据更新 +- 添加加载状态和错误处理 +- 实现表单验证和提交逻辑 + +#### 3.4 文件系统集成 +**功能实现**: +- 打开技能文件夹功能 +- 复制技能路径到剪贴板 +- 打开技能手册/编辑器 +- 跨平台文件路径处理 + +#### 3.5 错误处理与用户反馈 +**实现**: +- Toast通知系统集成 +- 错误消息的友好显示 +- 网络错误的重试机制 +- 操作成功/失败的明确反馈 + +### 交付成果 +- 完整的数据流:UI ↔ Store ↔ API +- 真实的技能管理功能(安装、卸载、配置) +- 完整的错误处理和用户反馈 +- 文件系统操作功能 + +## 阶段四:测试、优化与完善 (预计:1-2天) + +### 目标 +进行功能测试、性能优化、国际化完善和代码质量提升。 + +### 具体任务 + +#### 4.1 功能测试 +**测试范围**: +- 技能列表加载和显示 +- 搜索和筛选功能 +- 启用/禁用技能 +- 安装/卸载技能 +- 配置保存和加载 +- 文件系统操作 +- 错误场景处理 + +**测试方法**: +- 手动功能测试 +- 关键路径的单元测试 +- 模拟API响应的集成测试 + +#### 4.2 性能优化 +**优化点**: +- 技能列表的虚拟滚动(如列表过长) +- 图片/图标的懒加载 +- API响应的缓存策略 +- 搜索防抖的优化调整 + +#### 4.3 国际化完善 +**完善内容**: +- 验证所有翻译键的正确使用 +- 动态内容(如技能来源)的本地化 +- 错误消息的多语言支持 +- 日期、数字等格式的本地化 + +#### 4.4 代码质量 +**改进项**: +- TypeScript类型定义的完善 +- 代码注释和文档 +- 组件和函数的单一职责原则 +- 错误处理的统一模式 + +#### 4.5 UI/UX优化 +**优化内容**: +- 响应式设计的进一步完善 +- 动画和过渡效果的优化 +- 无障碍访问支持 +- 深色/浅色主题的适配 + +### 交付成果 +- 经过充分测试的稳定功能 +- 良好的性能表现 +- 完整的国际化支持 +- 高质量的代码实现 +- 优秀的用户体验 + +## 文件结构规划 + +``` +src/ +├── lib/ +│ ├── skills-types.ts # 技能相关类型定义 +│ ├── skills-api.ts # 技能API客户端 +│ └── skill-config.ts # 技能配置工具 +├── store/ +│ └── skills.ts # Pinia技能Store +├── pages/ +│ └── skills/ +│ ├── index.vue # 技能主页面 +│ └── components/ +│ ├── SkillListItem.vue # 技能列表项 +│ ├── SkillDetailDialog.vue # 技能详情弹窗 +│ ├── MarketplaceDialog.vue # 市场搜索弹窗 +│ ├── EnvVarManager.vue # 环境变量管理 +│ └── SkillCard.vue # 技能卡片(可选) +└── i18n/ + └── locales/ + ├── zh/ + │ └── skills.json # 中文翻译 + ├── en/ + │ └── skills.json # 英文翻译 + └── ja/ + └── skills.json # 日文翻译 +``` + +## 后端集成考虑 + +### API接口对齐 +zn-ai项目需要实现以下后端API接口以支持完整功能: +``` +GET /api/skills/configs # 获取所有技能配置 +PUT /api/skills/config # 更新技能配置 +POST /api/clawhub/search # 搜索技能市场 +POST /api/clawhub/install # 安装技能 +POST /api/clawhub/uninstall # 卸载技能 +GET /api/clawhub/list # 列出已安装技能 +POST /api/clawhub/open-readme # 打开技能手册 +POST /api/clawhub/open-path # 打开技能目录 +``` + +### 网关状态检测 +如果zn-ai有类似的网关架构,需要集成网关状态检测功能。如无,可简化或省略相关逻辑。 + +## 风险评估与缓解策略 + +### 技术风险 +1. **网关架构差异**:zn-ai可能没有ClawX的网关架构 + - **缓解**:简化网关相关逻辑,或实现兼容层 + +2. **ClawHub服务依赖**:技能市场服务可能不可用 + - **缓解**:实现降级方案,支持本地技能安装 + +3. **文件系统兼容性**:跨平台路径处理问题 + - **缓解**:使用Node.js的path模块进行规范化处理 + +### 实施风险 +1. **开发工作量超出预期** + - **缓解**:优先实现核心功能,增强功能后续迭代 + +2. **UI一致性难以保证** + - **缓解**:使用设计系统规范,确保与zn-ai现有UI风格协调 + +### 兼容性风险 +1. **技能包格式兼容性** + - **缓解**:实现格式转换层,或要求技能包符合zn-ai标准 + +## 成功标准 + +### 功能完整性 +- [ ] 技能列表展示和筛选 +- [ ] 技能启用/禁用功能 +- [ ] 技能详情查看和配置 +- [ ] 技能市场搜索和安装 +- [ ] 批量操作支持 +- [ ] 文件系统集成 +- [ ] 完整的错误处理 + +### 技术质量 +- [ ] 无TypeScript编译错误 +- [ ] 通过ESLint检查 +- [ ] 代码覆盖率达标(如有时) +- [ ] 性能指标满足要求 + +### 用户体验 +- [ ] 界面响应迅速 +- [ ] 操作反馈明确 +- [ ] 错误提示友好 +- [ ] 多语言支持完整 + +## 后续迭代建议 + +### 短期优化(1-2周内) +1. 技能分类和标签系统 +2. 技能依赖关系管理 +3. 技能版本更新检查 +4. 技能使用统计 + +### 中期增强(1-2月内) +1. 技能开发工具包 +2. 技能市场用户评价系统 +3. 技能自动更新机制 +4. 技能组合和模板 + +### 长期愿景(3-6月内) +1. 技能可视化编排 +2. AI辅助技能创建 +3. 技能商店和分发平台 +4. 社区技能分享生态 + +## 附录 + +### 参考资源 +1. ClawX Skills源代码:`/Users/duanshuwen/Documents/workspace/electron/ClawX/src/pages/Skills/` +2. ClawX技能Store:`/Users/duanshuwen/Documents/workspace/electron/ClawX/src/stores/skills.ts` +3. ClawX技能类型:`/Users/duanshuwen/Documents/workspace/electron/ClawX/src/types/skill.ts` +4. ClawX技能API:`/Users/duanshuwen/Documents/workspace/electron/ClawX/electron/api/routes/skills.ts` + +### 关键决策点 +1. **UI框架选择**:坚持使用Element Plus,通过自定义样式达到视觉一致性 +2. **状态管理**:使用Pinia,保持与zn-ai其他模块的一致性 +3. **API设计**:尽可能保持与ClawX的API兼容,便于后续维护 +4. **国际化策略**:完全复用ClawX的翻译键结构,确保功能对齐 + +--- +*计划制定时间:2026-04-10* +*基于Skills功能分析报告制定* \ No newline at end of file diff --git a/Skills-implementation-referrnce.md b/Skills-implementation-referrnce.md new file mode 100644 index 0000000..ec56c9d --- /dev/null +++ b/Skills-implementation-referrnce.md @@ -0,0 +1,291 @@ +# Skills 功能实现分析报告 + +## 1. 功能概述 + +Skills(技能)功能是AI助手应用的核心模块,用于管理AI技能/插件。该功能允许用户: +- 浏览已安装的技能(内置、用户安装) +- 搜索和安装新的技能从市场(ClawHub) +- 启用/禁用技能 +- 配置技能的API密钥和环境变量 +- 批量操作(启用/禁用所有可见技能) +- 查看技能详情和配置 +- 打开技能目录和编辑器 + +## 2. 核心功能特性 + +### 2.1 技能管理 +- **技能列表展示**:分类显示内置技能、用户安装技能 +- **技能筛选**:按来源(全部、内置、市场)和搜索关键词过滤 +- **技能状态**:显示启用/禁用状态,支持快速切换 +- **批量操作**:一键启用/禁用所有可见非核心技能 + +### 2.2 技能详情与配置 +- **详情面板**:显示技能名称、描述、版本、图标、来源等信息 +- **API密钥配置**:为技能配置API密钥(密码输入框) +- **环境变量管理**:动态添加/编辑/删除环境变量 +- **路径操作**:复制技能路径、打开技能目录 +- **外部链接**:跳转到ClawHub技能页面、打开技能手册 + +### 2.3 技能市场 +- **搜索功能**:实时搜索技能市场 +- **安装/卸载**:安装新技能、卸载用户安装的技能 +- **状态显示**:显示已安装状态、安装进度 + +### 2.4 系统集成 +- **网关状态检测**:显示网关运行状态警告 +- **文件系统集成**:打开技能文件夹、编辑器 +- **国际化**:完整的多语言支持 +- **错误处理**:网络超时、频率限制等错误处理 + +## 3. 技术架构分析 + +### 3.1 前端架构(ClawX - React) +``` +src/pages/Skills/index.tsx # 主页面组件 +src/stores/skills.ts # Zustand状态管理 +src/types/skill.ts # 类型定义 +src/lib/host-api.ts # API客户端 +src/components/ui/ # UI组件库(Shadcn/ui) +``` + +### 3.2 后端架构(ClawX - Electron) +``` +electron/api/routes/skills.ts # 技能API路由 +electron/utils/skill-config.ts # 技能配置管理 +electron/services/ # 服务层(ClawHub服务等) +``` + +### 3.3 数据流 +``` +前端组件 → Zustand Store → Host API → 后端路由 → 服务层 → 文件系统/网关 +``` + +## 4. 关键组件分析 + +### 4.1 状态管理(stores/skills.ts) +**核心状态**: +```typescript +interface SkillsState { + skills: Skill[]; // 技能列表 + searchResults: MarketplaceSkill[]; // 市场搜索结果 + loading: boolean; // 加载状态 + searching: boolean; // 搜索中状态 + searchError: string | null; // 搜索错误 + installing: Record;// 安装状态(slug -> 是否安装中) + error: string | null; // 通用错误 +} +``` + +**核心方法**: +- `fetchSkills()`: 从网关和ClawHub获取技能列表 +- `searchSkills(query)`: 搜索技能市场 +- `installSkill(slug)`: 安装技能 +- `uninstallSkill(slug)`: 卸载技能 +- `enableSkill(skillId)`: 启用技能 +- `disableSkill(skillId)`: 禁用技能 + +### 4.2 页面组件(Skills/index.tsx) +**主要UI结构**: +1. **头部区域**:标题、副标题、打开文件夹按钮 +2. **网关状态警告**:网关未运行时的警告提示 +3. **导航与筛选**:搜索框、来源筛选(全部/内置/市场) +4. **批量操作按钮**:启用/禁用所有可见技能、安装技能按钮、刷新按钮 +5. **技能列表**:技能卡片列表,包含图标、名称、描述、状态开关 +6. **安装弹窗**:搜索和安装新技能的市场界面 +7. **详情弹窗**:技能详情和配置面板 + +**交互细节**: +- 实时搜索防抖(300ms延迟) +- 批量操作时的进度反馈 +- 技能切换时的即时状态更新 +- 错误处理的用户友好提示 + +### 4.3 详情弹窗组件(SkillDetailDialog) +**配置部分**: +- API密钥配置(密码输入框) +- 环境变量管理(动态表单) +- 路径操作(复制、打开) +- 外部链接(ClawHub、手册) + +**保存机制**: +- 配置保存到本地文件 +- 成功后刷新技能列表 +- 错误处理的toast通知 + +## 5. API接口分析 + +### 5.1 后端API路由(/api/skills/*) +``` +GET /api/skills/configs # 获取所有技能配置 +PUT /api/skills/config # 更新技能配置 +POST /api/clawhub/search # 搜索技能市场 +POST /api/clawhub/install # 安装技能 +POST /api/clawhub/uninstall # 卸载技能 +GET /api/clawhub/list # 列出已安装技能 +POST /api/clawhub/open-readme # 打开技能手册 +POST /api/clawhub/open-path # 打开技能目录 +``` + +### 5.2 前端API调用 +通过`hostApiFetch`封装调用后端API,支持: +- JSON请求/响应处理 +- 错误规范化 +- 超时和频率限制处理 + +## 6. 数据类型定义 + +### 6.1 技能类型(Skill) +```typescript +interface Skill { + id: string; // 技能标识 + slug?: string; // 技能slug(市场标识) + name: string; // 技能名称 + description: string; // 技能描述 + enabled: boolean; // 是否启用 + icon?: string; // 技能图标(emoji或URL) + version?: string; // 版本号 + author?: string; // 作者 + configurable?: boolean; // 是否可配置 + config?: Record; // 配置数据 + isCore?: boolean; // 是否核心技能(不可禁用) + isBundled?: boolean; // 是否内置技能 + source?: string; // 来源(openclaw-bundled等) + baseDir?: string; // 基础目录 + filePath?: string; // 文件路径 +} +``` + +### 6.2 市场技能类型(MarketplaceSkill) +```typescript +interface MarketplaceSkill { + slug: string; // 市场slug + name: string; // 技能名称 + description: string; // 技能描述 + version: string; // 版本号 + author?: string; // 作者 + downloads?: number; // 下载量 + stars?: number; // 星标数 +} +``` + +## 7. 错误处理机制 + +### 7.1 错误类型 +- **超时错误**:`fetchTimeoutError`、`searchTimeoutError`、`installTimeoutError` +- **频率限制错误**:`fetchRateLimitError`、`searchRateLimitError`、`installRateLimitError` +- **网关错误**:网关未运行时的友好提示 +- **文件系统错误**:目录不存在、权限问题等 + +### 7.2 错误处理策略 +- 错误代码到用户友好消息的映射 +- 网络错误时的重试机制 +- 部分失败时的批量操作反馈 +- Toast通知的用户界面反馈 + +## 8. 国际化支持 + +### 8.1 翻译键示例 +- `skills.title`: "Skills" +- `skills.subtitle`: "Browse and manage AI skills" +- `skills.search`: "Search skills..." +- `skills.filter.all`: "All ({count})" +- `skills.filter.builtIn`: "Built-in ({count})" +- `skills.filter.marketplace`: "Marketplace ({count})" +- `skills.actions.enableVisible`: "Enable visible" +- `skills.actions.disableVisible`: "Disable visible" +- `skills.actions.installSkill`: "Install skill" + +### 8.2 动态内容翻译 +- 技能来源标签的本地化 +- 错误消息的本地化 +- 操作成功/失败的Toast消息 + +## 9. UI/UX设计分析 + +### 9.1 视觉风格 +- 卡片式列表布局 +- 右侧滑出式详情面板 +- 现代化的输入和按钮样式 +- 深色/浅色主题支持 + +### 9.2 交互设计 +- 点击技能卡片打开详情 +- 悬停效果和状态反馈 +- 批量操作的确认反馈 +- 安装过程的进度指示 + +### 9.3 响应式设计 +- 桌面优化的布局 +- 弹窗的自适应宽度 +- 移动端友好的触摸目标 + +## 10. 与zn-ai项目的技术栈映射 + +### 10.1 技术栈差异 +| 组件 | ClawX (源) | zn-ai (目标) | +|------|------------|--------------| +| 前端框架 | React 18 | Vue 3 | +| 状态管理 | Zustand | Pinia | +| UI组件库 | Shadcn/ui (基于Radix) | Element Plus + Tailwind CSS | +| 构建工具 | Vite | Vite | +| 类型系统 | TypeScript | TypeScript | + +### 10.2 适配策略 +1. **组件迁移**:React组件 → Vue 3组件(Composition API) +2. **状态管理**:Zustand Store → Pinia Store +3. **UI组件**:Shadcn/ui → Element Plus + 自定义样式 +4. **API层**:保留相同的API接口,适配Vue调用方式 +5. **类型系统**:直接复用TypeScript类型定义 + +### 10.3 关键挑战 +1. **UI一致性**:在Element Plus基础上实现类似Shadcn/ui的视觉效果 +2. **状态管理模式**:从Zustand的函数式风格到Pinia的类Vuex风格 +3. **组件交互**:Vue的事件系统与React的props/callback差异 +4. **响应式系统**:Vue的响应式与React的状态更新机制差异 + +## 11. 实现优先级建议 + +### 高优先级(核心功能) +1. 技能列表展示和基本筛选 +2. 技能启用/禁用切换 +3. 技能详情查看 +4. 基本的API密钥配置 + +### 中优先级(增强功能) +1. 环境变量管理 +2. 技能市场搜索和安装 +3. 批量操作功能 +4. 文件夹和路径操作 + +### 低优先级(优化功能) +1. 高级错误处理和重试 +2. 性能优化(虚拟滚动等) +3. 离线模式支持 +4. 技能导入/导出 + +## 12. 风险评估 + +### 技术风险 +- **网关集成**:zn-ai可能没有相同的网关架构 +- **文件系统操作**:跨平台文件路径处理的兼容性 +- **技能市场依赖**:ClawHub服务的可用性和API稳定性 + +### 兼容性风险 +- **技能格式兼容**:技能包格式和配置schema的兼容性 +- **升级路径**:未来技能格式变更的向后兼容 + +### 实施风险 +- **开发工作量**:从React到Vue的完整重写工作量较大 +- **测试覆盖**:需要完整的端到端测试确保功能稳定 + +## 13. 后续步骤建议 + +1. **详细设计阶段**:基于本分析完成Vue组件的详细设计 +2. **原型开发**:开发核心功能的可交互原型 +3. **迭代开发**:按照优先级分批次实现功能 +4. **集成测试**:与现有zn-ai系统进行集成测试 +5. **用户测试**:获取真实用户反馈并进行优化 + +--- +*分析完成时间:2026-04-10* +*分析基于ClawX项目commit: 5f54271 (feat(models): add models configuration page...)* \ No newline at end of file diff --git a/src/i18n/constants.ts b/src/i18n/constants.ts index 72b15fb..5e4c0b6 100644 --- a/src/i18n/constants.ts +++ b/src/i18n/constants.ts @@ -20,5 +20,6 @@ export const NAMESPACES = [ 'knowledge', 'component', 'models', + 'skills', ] as const; export type Namespace = (typeof NAMESPACES)[number]; \ No newline at end of file diff --git a/src/i18n/locales/en/skills.json b/src/i18n/locales/en/skills.json new file mode 100644 index 0000000..585fd0a --- /dev/null +++ b/src/i18n/locales/en/skills.json @@ -0,0 +1,112 @@ +{ + "title": "Skills", + "subtitle": "Browse and manage AI capabilities", + "refresh": "Refresh", + "openFolder": "Open Skills Folder", + "gatewayWarning": "Gateway is not running. Skills cannot be loaded without an active Gateway.", + "tabs": { + "installed": "Installed", + "marketplace": "Marketplace" + }, + "filter": { + "all": "All ({{count}})", + "builtIn": "Built-in ({{count}})", + "marketplace": "Marketplace ({{count}})" + }, + "search": "Search skills...", + "searchMarketplace": "Search marketplace...", + "searchButton": "Search", + "actions": { + "enableVisible": "Enable Visible", + "disableVisible": "Disable Visible", + "installSkill": "Install Skills" + }, + "noSkills": "No skills found", + "noSkillsSearch": "Try a different search term", + "noSkillsAvailable": "No skills available", + "detail": { + "info": "Information", + "config": "Configuration", + "description": "Description", + "version": "Version", + "author": "Author", + "source": "Source", + "coreSystem": "Core System", + "bundled": "Bundled", + "userInstalled": "User Installed", + "enabled": "Enabled", + "disabled": "Disabled", + "apiKey": "API Key", + "apiKeyPlaceholder": "Enter API Key (optional)", + "apiKeyDesc": "The primary API key for this skill. Leave blank if not required or configured elsewhere.", + "envVars": "Environment Variables", + "addVariable": "Add Variable", + "noEnvVars": "No environment variables configured.", + "keyPlaceholder": "KEY (e.g. BASE_URL)", + "valuePlaceholder": "VALUE", + "envNote": "Note: Rows with empty keys will be automatically removed during save.", + "saving": "Saving...", + "saveConfig": "Save Configuration", + "configSaved": "Configuration saved", + "openManual": "Open Manual", + "openActualFolder": "Open Actual Folder", + "copyPath": "Copy path", + "pathUnavailable": "Path not available", + "configurable": "Configurable", + "uninstall": "Uninstall", + "enable": "Enable", + "disable": "Disable" + }, + "source": { + "badge": { + "bundled": "Bundled", + "managed": "Managed", + "workspace": "Workspace", + "extra": "Extra dirs", + "agentsPersonal": "Personal .agents", + "agentsProject": "Project .agents", + "unknown": "Unknown source" + } + }, + "toast": { + "enabled": "Skill enabled", + "disabled": "Skill disabled", + "installed": "Skill installed and enabled", + "uninstalled": "Skill uninstalled successfully", + "openedEditor": "Opened in editor", + "failedEditor": "Failed to open editor", + "failedSave": "Failed to save configuration", + "failedOpenFolder": "Failed to open skills folder", + "failedInstall": "Failed to install", + "failedUninstall": "Failed to uninstall", + "failedFolderNotFound": "Skills folder does not exist yet. Install a skill first.", + "copiedPath": "Path copied", + "failedCopyPath": "Failed to copy path", + "failedOpenActualFolder": "Failed to open actual skill folder", + "searchTimeoutError": "Search timed out, check network. You can also search on ClawHub.ai, download the ZIP, and extract it to \"{{path}}\"", + "installTimeoutError": "Installation timed out, check network. You can also download the ZIP from ClawHub.ai and extract it to \"{{path}}\"", + "searchRateLimitError": "Search rate limit exceeded. You can also search on ClawHub.ai, download the ZIP, and extract it to \"{{path}}\"", + "installRateLimitError": "Installation rate limit exceeded. You can also download the ZIP from ClawHub.ai and extract it to \"{{path}}\"", + "fetchTimeoutError": "Fetching skills timed out, please check your network connection.", + "fetchRateLimitError": "Fetching skills rate limit exceeded, please try again later.", + "noBatchEnableTargets": "All visible skills are already enabled.", + "noBatchDisableTargets": "All visible skills are already disabled.", + "batchEnabled": "{{count}} skills enabled.", + "batchDisabled": "{{count}} skills disabled.", + "batchPartial": "Updated {{success}} / {{total}} skills. Some items failed." + }, + "marketplace": { + "title": "Marketplace", + "installDialogTitle": "Install Skills", + "installDialogSubtitle": "Browse Explore by default, or enter keywords to search.", + "sourceLabel": "Source", + "sourceClawHub": "ClawHub", + "securityNote": "Click skill card to view its documentation and security information on ClawHub before installation.", + "manualInstallHint": "Network issues? You can always download skill ZIP archives from ClawHub.ai and extract them manually into \"{{path}}\".", + "searching": "Searching ClawHub...", + "noResults": "No skills found matching your search.", + "emptyPrompt": "Search for new skills to expand your capabilities.", + "searchError": "ClawHub search failed. Check your connection or installation.", + "install": "Install" + } +} diff --git a/src/i18n/locales/ja/skills.json b/src/i18n/locales/ja/skills.json new file mode 100644 index 0000000..8cc7a14 --- /dev/null +++ b/src/i18n/locales/ja/skills.json @@ -0,0 +1,112 @@ +{ + "title": "スキル", + "subtitle": "AI機能の閲覧と管理", + "refresh": "更新", + "openFolder": "スキルフォルダを開く", + "gatewayWarning": "ゲートウェイが稼働していません。アクティブなゲートウェイがないとスキルを読み込めません。", + "tabs": { + "installed": "インストール済み", + "marketplace": "マーケットプレイス" + }, + "filter": { + "all": "すべて ({{count}})", + "builtIn": "内蔵 ({{count}})", + "marketplace": "マーケットプレイス ({{count}})" + }, + "search": "スキルを検索...", + "searchMarketplace": "マーケットプレイスを検索...", + "searchButton": "検索", + "actions": { + "enableVisible": "表示中を一括有効化", + "disableVisible": "表示中を一括無効化", + "installSkill": "スキルをインストール" + }, + "noSkills": "スキルが見つかりません", + "noSkillsSearch": "別の検索語をお試しください", + "noSkillsAvailable": "利用可能なスキルがありません", + "detail": { + "info": "情報", + "config": "設定", + "description": "説明", + "version": "バージョン", + "author": "作者", + "source": "ソース", + "coreSystem": "コアシステム", + "bundled": "内蔵", + "userInstalled": "ユーザーインストール", + "enabled": "有効", + "disabled": "無効", + "apiKey": "APIキー", + "apiKeyPlaceholder": "APIキーを入力(任意)", + "apiKeyDesc": "このスキルの主要なAPIキーです。不要な場合または別の場所で設定している場合は空白のままにしてください。", + "envVars": "環境変数", + "addVariable": "変数を追加", + "noEnvVars": "環境変数が設定されていません。", + "keyPlaceholder": "キー(例:BASE_URL)", + "valuePlaceholder": "値", + "envNote": "注意:キーが空の行は保存時に自動的に削除されます。", + "saving": "保存中...", + "saveConfig": "設定を保存", + "configSaved": "設定を保存しました", + "openManual": "マニュアルを開く", + "openActualFolder": "実際のフォルダを開く", + "copyPath": "パスをコピー", + "pathUnavailable": "パスを取得できません", + "configurable": "設定可能", + "uninstall": "アンインストール", + "enable": "有効化", + "disable": "無効化" + }, + "source": { + "badge": { + "bundled": "内蔵", + "managed": "管理ディレクトリ", + "workspace": "ワークスペース", + "extra": "追加ディレクトリ", + "agentsPersonal": "個人 .agents", + "agentsProject": "プロジェクト .agents", + "unknown": "不明なソース" + } + }, + "toast": { + "enabled": "スキルを有効にしました", + "disabled": "スキルを無効にしました", + "installed": "スキルをインストールして有効にしました", + "uninstalled": "スキルのアンインストールに成功しました", + "openedEditor": "エディターで開きました", + "failedEditor": "エディターを開けませんでした", + "failedSave": "設定の保存に失敗しました", + "failedOpenFolder": "スキルフォルダを開けませんでした", + "failedInstall": "インストールに失敗しました", + "failedUninstall": "アンインストールに失敗しました", + "failedFolderNotFound": "スキルフォルダがまだ存在しません。先にスキルをインストールしてください。", + "copiedPath": "パスをコピーしました", + "failedCopyPath": "パスのコピーに失敗しました", + "failedOpenActualFolder": "スキルの実際のフォルダを開けませんでした", + "searchTimeoutError": "検索がタイムアウトしました。ClawHub.aiで検索してZIPをダウンロードし、\"{{path}}\" に展開することも可能です", + "installTimeoutError": "インストールがタイムアウトしました。ClawHub.aiでZIPをダウンロードし、\"{{path}}\" に展開することも可能です", + "searchRateLimitError": "検索リクエストの制限を超過しました。ClawHub.aiで検索してZIPをダウンロードし、\"{{path}}\" に展開することも可能です", + "installRateLimitError": "インストールリクエストの制限を超過しました。ClawHub.aiからZIPをダウンロードし、\"{{path}}\" に展開することも可能です", + "fetchTimeoutError": "スキルリストの取得がタイムアウトしました。ネットワークを確認してください。", + "fetchRateLimitError": "スキルリスト取得のリクエスト制限を超過しました。後でお試しください。", + "noBatchEnableTargets": "表示中のスキルはすべて有効です。", + "noBatchDisableTargets": "表示中のスキルはすべて無効です。", + "batchEnabled": "{{count}} 件のスキルを有効化しました。", + "batchDisabled": "{{count}} 件のスキルを無効化しました。", + "batchPartial": "{{success}} / {{total}} 件を更新しました。一部失敗しています。" + }, + "marketplace": { + "title": "マーケットプレイス", + "installDialogTitle": "スキルをインストール", + "installDialogSubtitle": "初期表示は Explore、キーワード入力時は検索します。", + "sourceLabel": "ソース", + "sourceClawHub": "ClawHub", + "securityNote": "インストール前にスキルカードをクリックして、ClawHubでドキュメントとセキュリティ情報を確認してください。", + "manualInstallHint": "ネットワークに問題がありますか?いつでもClawHub.aiからスキルのZIPをダウンロードし、手動で \"{{path}}\" に展開してインストールできます。", + "searching": "ClawHubを検索中...", + "noResults": "検索に一致するスキルが見つかりません。", + "emptyPrompt": "新しいスキルを検索して機能を拡張しましょう。", + "searchError": "ClawHub検索に失敗しました。接続またはインストールを確認してください。", + "install": "インストール" + } +} diff --git a/src/i18n/locales/zh/skills.json b/src/i18n/locales/zh/skills.json new file mode 100644 index 0000000..59f4daf --- /dev/null +++ b/src/i18n/locales/zh/skills.json @@ -0,0 +1,112 @@ +{ + "title": "技能", + "subtitle": "浏览和管理 AI 能力", + "refresh": "刷新", + "openFolder": "打开技能文件夹", + "gatewayWarning": "网关未运行。没有活跃的网关,无法加载技能。", + "tabs": { + "installed": "已安装", + "marketplace": "市场" + }, + "filter": { + "all": "全部 ({{count}})", + "builtIn": "内置 ({{count}})", + "marketplace": "市场 ({{count}})" + }, + "search": "搜索技能...", + "searchMarketplace": "搜索市场...", + "searchButton": "搜索", + "actions": { + "enableVisible": "批量启用可见项", + "disableVisible": "批量禁用可见项", + "installSkill": "安装技能" + }, + "noSkills": "未找到技能", + "noSkillsSearch": "尝试不同的搜索词", + "noSkillsAvailable": "暂无可用技能", + "detail": { + "info": "信息", + "config": "配置", + "description": "描述", + "version": "版本", + "author": "作者", + "source": "来源", + "coreSystem": "核心系统", + "bundled": "内置", + "userInstalled": "用户安装", + "enabled": "已启用", + "disabled": "已禁用", + "apiKey": "API 密钥", + "apiKeyPlaceholder": "输入 API 密钥(可选)", + "apiKeyDesc": "此技能的主要 API 密钥。如果不需要或在别处配置,请留空。", + "envVars": "环境变量", + "addVariable": "添加变量", + "noEnvVars": "未配置环境变量。", + "keyPlaceholder": "键名 (例如 BASE_URL)", + "valuePlaceholder": "值", + "envNote": "注意:键名为空的行将在保存时自动移除。", + "saving": "保存中...", + "saveConfig": "保存配置", + "configSaved": "配置已保存", + "openManual": "打开手册", + "openActualFolder": "打开实际目录", + "copyPath": "复制路径", + "pathUnavailable": "路径不可用", + "configurable": "可配置", + "uninstall": "卸载", + "enable": "启用", + "disable": "禁用" + }, + "source": { + "badge": { + "bundled": "内置", + "managed": "托管目录", + "workspace": "工作区", + "extra": "额外目录", + "agentsPersonal": "个人 .agents", + "agentsProject": "项目 .agents", + "unknown": "未知来源" + } + }, + "toast": { + "enabled": "技能已启用", + "disabled": "技能已禁用", + "installed": "技能已安装并启用", + "uninstalled": "技能已成功卸载", + "openedEditor": "已在编辑器中打开", + "failedEditor": "无法打开编辑器", + "failedSave": "保存配置失败", + "failedOpenFolder": "无法打开技能文件夹", + "failedInstall": "安装失败", + "failedUninstall": "卸载失败", + "failedFolderNotFound": "技能文件夹尚不存在,请先安装一个技能。", + "copiedPath": "路径已复制", + "failedCopyPath": "复制路径失败", + "failedOpenActualFolder": "打开技能实际目录失败", + "searchTimeoutError": "搜索超时,请检查网络。您也可访问 ClawHub.ai 搜索并下载压缩包,解压到 \"{{path}}\"", + "installTimeoutError": "安装超时,请检查网络。您也可在 ClawHub.ai 下载该技能压缩包,解压到 \"{{path}}\"", + "searchRateLimitError": "搜索请求过于频繁。您也可访问 ClawHub.ai 搜索并下载压缩包,解压到 \"{{path}}\"", + "installRateLimitError": "安装请求过于频繁。您也可在 ClawHub.ai 下载该技能压缩包,解压到 \"{{path}}\"", + "fetchTimeoutError": "获取技能列表超时,请检查网络。", + "fetchRateLimitError": "获取技能列表请求过于频繁,请稍后再试。", + "noBatchEnableTargets": "当前可见技能都已启用。", + "noBatchDisableTargets": "当前可见技能都已禁用。", + "batchEnabled": "已启用 {{count}} 个技能。", + "batchDisabled": "已禁用 {{count}} 个技能。", + "batchPartial": "已更新 {{success}} / {{total}} 个技能,部分操作失败。" + }, + "marketplace": { + "title": "市场", + "installDialogTitle": "安装技能", + "installDialogSubtitle": "默认展示 Explore,输入关键词后执行搜索。", + "sourceLabel": "来源", + "sourceClawHub": "ClawHub", + "securityNote": "安装前请点击技能卡片,在 ClawHub 上查看其文档和安全信息。", + "manualInstallHint": "遇到网络问题?您可以随时从 ClawHub.ai 下载技能压缩包,并将其解压至 \"{{path}}\" 目录来完成手动安装。", + "searching": "正在搜索 ClawHub...", + "noResults": "未找到匹配的技能。", + "emptyPrompt": "搜索新技能以扩展您的能力。", + "searchError": "ClawHub 搜索失败。请检查您的连接或安装。", + "install": "安装" + } +} diff --git a/src/lib/skills-api.ts b/src/lib/skills-api.ts new file mode 100644 index 0000000..65a7df7 --- /dev/null +++ b/src/lib/skills-api.ts @@ -0,0 +1,174 @@ +import { hostApiFetch } from './host-api'; +import type { Skill, MarketplaceSkill } from './skills-types'; + +// Mock data for UI development when backend is not ready +const MOCK_SKILLS: Skill[] = [ + { id: '1password', slug: '1password', name: '1password', description: 'Set up and use 1Password CLI (op). Use when installing the CLI, enabling desktop app integration, signing in...', enabled: true, icon: '🔐', version: '1.0.0', isCore: false, isBundled: true, source: 'openclaw-bundled', baseDir: '/Applications/ClawX.app/Contents/Resources/openclaw/skills/1password' }, + { id: 'apple-notes', slug: 'apple-notes', name: 'apple-notes', description: 'Manage Apple Notes via the `memo` CLI on macOS (create, view, edit, delete, search, move, and export notes...', enabled: true, icon: '📝', version: '1.0.0', isCore: false, isBundled: true, source: 'openclaw-bundled', baseDir: '/Applications/ClawX.app/Contents/Resources/openclaw/skills/apple-notes' }, + { id: 'apple-reminders', slug: 'apple-reminders', name: 'apple-reminders', description: 'Manage Apple Reminders via remindctl CLI (list, add, edit, complete, delete). Supports lists, date filters, and...', enabled: true, icon: '⏰', version: '1.0.0', isCore: false, isBundled: true, source: 'openclaw-bundled', baseDir: '/Applications/ClawX.app/Contents/Resources/openclaw/skills/apple-reminders' }, + { id: 'bear-notes', slug: 'bear-notes', name: 'bear-notes', description: 'Create, search, and manage Bear notes via grizzly CLI.', enabled: true, icon: '🐻', version: '1.0.0', isCore: false, isBundled: true, source: 'openclaw-bundled', baseDir: '/Applications/ClawX.app/Contents/Resources/openclaw/skills/bear-notes' }, + { id: 'fetch-hacker-news', slug: 'fetch-hacker-news', name: 'fetch-hacker-news', description: 'Fetch top stories from Hacker News.', enabled: false, icon: '📰', version: '1.0.0', isCore: false, isBundled: false, source: 'openclaw-managed', baseDir: '~/.openclaw/skills/fetch-hacker-news' }, + { id: 'github', slug: 'github', name: 'github', description: 'Interact with GitHub issues, pull requests, repositories, and more.', enabled: true, icon: '🐙', version: '1.2.0', isCore: false, isBundled: false, source: 'openclaw-managed', baseDir: '~/.openclaw/skills/github' }, + { id: 'todoist', slug: 'todoist', name: 'todoist', description: 'Manage Todoist tasks and projects.', enabled: true, icon: '✅', version: '1.0.0', isCore: false, isBundled: true, source: 'openclaw-bundled', baseDir: '/Applications/ClawX.app/Contents/Resources/openclaw/skills/todoist' }, + { id: 'zotero', slug: 'zotero', name: 'zotero', description: 'Search and manage Zotero references.', enabled: false, icon: '📚', version: '1.0.0', isCore: false, isBundled: true, source: 'openclaw-bundled', baseDir: '/Applications/ClawX.app/Contents/Resources/openclaw/skills/zotero' }, + { id: 'browser-use', slug: 'browser-use', name: 'browser-use', description: 'Use a browser to search and navigate the web.', enabled: true, icon: '🌐', version: '2.0.0', isCore: true, isBundled: true, source: 'openclaw-bundled', baseDir: '/Applications/ClawX.app/Contents/Resources/openclaw/skills/browser-use' }, + { id: 'memory', slug: 'memory', name: 'memory', description: 'Store and retrieve long-term memories.', enabled: true, icon: '🧠', version: '1.0.0', isCore: true, isBundled: true, source: 'openclaw-bundled', baseDir: '/Applications/ClawX.app/Contents/Resources/openclaw/skills/memory' }, +]; + +const MOCK_MARKETPLACE: MarketplaceSkill[] = [ + { slug: 'notion', name: 'notion', description: 'Read and write pages in Notion workspaces.', version: '1.0.0', author: 'clawhub' }, + { slug: 'linear', name: 'linear', description: 'Manage Linear issues and projects.', version: '1.0.0', author: 'clawhub' }, + { slug: 'slack', name: 'slack', description: 'Send messages and search channels in Slack.', version: '1.0.0', author: 'clawhub' }, + { slug: 'discord', name: 'discord', description: 'Send messages and manage Discord servers.', version: '1.0.0', author: 'clawhub' }, + { slug: 'figma', name: 'figma', description: 'Search and manage Figma files and comments.', version: '1.0.0', author: 'clawhub' }, + { slug: 'spotify', name: 'spotify', description: 'Control Spotify playback and manage playlists.', version: '1.0.0', author: 'clawhub' }, +]; + +export async function apiFetchSkills(): Promise { + try { + const configs = await hostApiFetch>('/api/skills/configs'); + const listResult = await hostApiFetch<{ success: boolean; results?: any[]; error?: string }>('/api/clawhub/list'); + + let skills: Skill[] = []; + + if (configs && typeof configs === 'object' && Object.keys(configs).length > 0) { + // Support extended config format that includes metadata (for zn-ai without gateway) + skills = Object.entries(configs).map(([key, cfg]: [string, any]) => ({ + id: key, + slug: cfg.slug || key, + name: cfg.name || key, + description: cfg.description || '', + enabled: cfg.enabled !== false, + icon: cfg.icon || '📦', + version: cfg.version || '1.0.0', + author: cfg.author, + config: cfg.config || { apiKey: cfg.apiKey, env: cfg.env }, + isCore: cfg.isCore || false, + isBundled: cfg.isBundled ?? true, + source: cfg.source, + baseDir: cfg.baseDir, + filePath: cfg.filePath, + })); + } else if (listResult?.success && listResult.results && listResult.results.length > 0) { + skills = listResult.results.map((item: any) => ({ + id: item.slug, + slug: item.slug, + name: item.slug, + description: 'Recently installed, initializing...', + enabled: false, + icon: '⌛', + version: item.version || 'unknown', + source: item.source || 'openclaw-managed', + baseDir: item.baseDir, + isCore: false, + isBundled: false, + })); + } + + if (skills.length === 0) { + return MOCK_SKILLS; + } + return skills; + } catch (error) { + console.error('Failed to fetch skills:', error); + return MOCK_SKILLS; + } +} + +export async function apiSearchSkills(query: string): Promise { + try { + const result = await hostApiFetch<{ success: boolean; results?: MarketplaceSkill[]; error?: string }>('/api/clawhub/search', { + method: 'POST', + body: JSON.stringify({ query }), + }); + if (result?.success && result.results && result.results.length > 0) { + return result.results; + } + if (!query) return MOCK_MARKETPLACE; + const q = query.toLowerCase(); + return MOCK_MARKETPLACE.filter(s => s.name.toLowerCase().includes(q) || s.description.toLowerCase().includes(q)); + } catch (error) { + console.error('Search error:', error); + if (!query) return MOCK_MARKETPLACE; + const q = query.toLowerCase(); + return MOCK_MARKETPLACE.filter(s => s.name.toLowerCase().includes(q) || s.description.toLowerCase().includes(q)); + } +} + +export async function apiInstallSkill(slug: string, version?: string): Promise { + const result = await hostApiFetch<{ success: boolean; error?: string }>('/api/clawhub/install', { + method: 'POST', + body: JSON.stringify({ slug, version }), + }); + if (!result?.success) { + throw new Error(result?.error || 'Install failed'); + } +} + +export async function apiUninstallSkill(slug: string): Promise { + const result = await hostApiFetch<{ success: boolean; error?: string }>('/api/clawhub/uninstall', { + method: 'POST', + body: JSON.stringify({ slug }), + }); + if (!result?.success) { + throw new Error(result?.error || 'Uninstall failed'); + } +} + +export async function apiUpdateSkillConfig( + skillKey: string, + config: { apiKey?: string; env?: Record; enabled?: boolean } +): Promise { + const result = await hostApiFetch<{ success: boolean; error?: string }>('/api/skills/config', { + method: 'PUT', + body: JSON.stringify({ skillKey, ...config }), + }); + if (!result?.success) { + throw new Error(result?.error || 'Save failed'); + } +} + +export async function apiOpenSkillPath(skillKey: string, slug?: string, baseDir?: string): Promise { + const result = await hostApiFetch<{ success: boolean; error?: string }>('/api/clawhub/open-path', { + method: 'POST', + body: JSON.stringify({ skillKey, slug, baseDir }), + }); + if (!result?.success) { + throw new Error(result?.error || 'Open path failed'); + } +} + +export async function apiOpenSkillReadme(skillKey: string, slug?: string, baseDir?: string): Promise { + const result = await hostApiFetch<{ success: boolean; error?: string }>('/api/clawhub/open-readme', { + method: 'POST', + body: JSON.stringify({ skillKey, slug, baseDir }), + }); + if (!result?.success) { + throw new Error(result?.error || 'Open readme failed'); + } +} + +export async function apiGetSkillsDir(): Promise { + try { + const result = await hostApiFetch<{ success: boolean; dir?: string; error?: string }>('/api/clawhub/skills-dir'); + if (result?.success && result.dir) return result.dir; + } catch { + // fallback + } + return '~/.zn-ai/skills'; +} + +export async function apiOpenSkillsDir(): Promise { + const dir = await apiGetSkillsDir(); + try { + const result = await hostApiFetch<{ success: boolean; error?: string }>('/api/clawhub/open-skills-dir', { + method: 'POST', + body: JSON.stringify({ dir }), + }); + if (result?.success) return; + throw new Error(result?.error || 'Open failed'); + } catch (error) { + await navigator.clipboard.writeText(dir); + throw new Error('Path copied to clipboard: ' + dir); + } +} diff --git a/src/lib/skills-types.ts b/src/lib/skills-types.ts new file mode 100644 index 0000000..805f51f --- /dev/null +++ b/src/lib/skills-types.ts @@ -0,0 +1,68 @@ +/** + * Skill Type Definitions + * Types for skills/plugins + */ + +/** + * Skill data structure + */ +export interface Skill { + id: string; + slug?: string; + name: string; + description: string; + enabled: boolean; + icon?: string; + version?: string; + author?: string; + configurable?: boolean; + config?: Record; + isCore?: boolean; + isBundled?: boolean; + dependencies?: string[]; + source?: string; + baseDir?: string; + filePath?: string; +} + +/** + * Skill bundle (preset skill collection) + */ +export interface SkillBundle { + id: string; + name: string; + nameZh: string; + description: string; + descriptionZh: string; + icon: string; + skills: string[]; + recommended?: boolean; +} + +/** + * Marketplace skill data + */ +export interface MarketplaceSkill { + slug: string; + name: string; + description: string; + version: string; + author?: string; + downloads?: number; + stars?: number; +} + +/** + * Skill configuration schema + */ +export interface SkillConfigSchema { + type: 'object'; + properties: Record; + required?: string[]; +} diff --git a/src/lib/skills-utils.ts b/src/lib/skills-utils.ts new file mode 100644 index 0000000..a304f19 --- /dev/null +++ b/src/lib/skills-utils.ts @@ -0,0 +1,19 @@ +import type { Skill } from './skills-types'; + +export function resolveSkillSourceLabel( + skill: Skill, + t: (key: string, defaultValue?: string) => string +): string { + const source = (skill.source || '').trim().toLowerCase(); + if (!source) { + if (skill.isBundled) return t('skills.source.badge.bundled', 'Built-in'); + return t('skills.source.badge.unknown', 'Unknown source'); + } + if (source === 'openclaw-bundled') return t('skills.source.badge.bundled', 'Built-in'); + if (source === 'openclaw-managed') return t('skills.source.badge.managed', 'Managed'); + if (source === 'openclaw-workspace') return t('skills.source.badge.workspace', 'Workspace'); + if (source === 'openclaw-extra') return t('skills.source.badge.extra', 'Extra dirs'); + if (source === 'agents-skills-personal') return t('skills.source.badge.agentsPersonal', 'Personal .agents'); + if (source === 'agents-skills-project') return t('skills.source.badge.agentsProject', 'Project .agents'); + return source; +} diff --git a/src/pages/knowledge/components/EventManager/addDialog.vue b/src/pages/knowledge/components/EventManager/addDialog.vue index 665c708..e0f451f 100644 --- a/src/pages/knowledge/components/EventManager/addDialog.vue +++ b/src/pages/knowledge/components/EventManager/addDialog.vue @@ -1,18 +1,18 @@