diff --git a/desktop/src/renderer/src/i18n.ts b/desktop/src/renderer/src/i18n.ts index dd62c324..60426fc4 100644 --- a/desktop/src/renderer/src/i18n.ts +++ b/desktop/src/renderer/src/i18n.ts @@ -154,14 +154,17 @@ const translations: Record> = { models_search_bocha_key: '配置博查 API Key', models_search_bocha_hint: '前往博查开放平台创建 API Key', skills_title: '技能管理', - skills_desc: '查看、启用或禁用 Agent 技能', + skills_desc: '查看、启用或禁用 Agent 工具和技能', + skills_hub_btn: '探索技能广场', tools_section_title: '内置工具', - skills_section_title: '自定义技能', + skills_section_title: '技能', tools_loading: '加载工具中...', skills_loading: '加载技能中...', skills_loading_desc: '技能将在加载后显示', skill_enabled: '已启用', skill_disabled: '已禁用', + tools_empty: '暂无内置工具', + skills_empty: '暂无技能', memory_title: '记忆管理', memory_desc: '查看 Agent 记忆文件和内容', memory_tab_files: '记忆文件', @@ -418,7 +421,8 @@ const translations: Record> = { config_max_turns: 'Max Memory Turns', config_max_steps: 'Max Steps', skills_title: 'Skills', - skills_desc: 'View, enable, or disable agent skills', + skills_desc: 'View, enable, or disable agent tools and skills', + skills_hub_btn: 'Skill Hub', tools_section_title: 'Built-in Tools', skills_section_title: 'Skills', tools_loading: 'Loading tools...', @@ -426,6 +430,8 @@ const translations: Record> = { skills_loading_desc: 'Skills will be displayed here after loading', skill_enabled: 'Enabled', skill_disabled: 'Disabled', + tools_empty: 'No built-in tools', + skills_empty: 'No skills found', memory_title: 'Memory', memory_desc: 'View agent memory files and contents', memory_tab_files: 'Memory Files', diff --git a/desktop/src/renderer/src/pages/SkillsPage.tsx b/desktop/src/renderer/src/pages/SkillsPage.tsx index dae93a2c..11863ec8 100644 --- a/desktop/src/renderer/src/pages/SkillsPage.tsx +++ b/desktop/src/renderer/src/pages/SkillsPage.tsx @@ -1,22 +1,20 @@ -import React, { useState, useEffect } from 'react' +import React, { useEffect, useState } from 'react' +import { Loader2, Wrench, Zap, Puzzle } from 'lucide-react' import { t } from '../i18n' import apiClient from '../api/client' import type { ToolInfo, SkillInfo } from '../types' +import { Toggle } from './settings/primitives' interface SkillsPageProps { baseUrl: string } +const SKILL_HUB_URL = 'https://skills.cowagent.ai/' + const SkillsPage: React.FC = ({ baseUrl }) => { const [tools, setTools] = useState([]) const [skills, setSkills] = useState([]) const [loading, setLoading] = useState(true) - const [toggling, setToggling] = useState(null) - - useEffect(() => { - apiClient.setBaseUrl(baseUrl) - loadData() - }, [baseUrl]) const loadData = async () => { try { @@ -31,103 +29,92 @@ const SkillsPage: React.FC = ({ baseUrl }) => { } } - const handleToggle = async (skill: SkillInfo) => { - setToggling(skill.name) + useEffect(() => { + apiClient.setBaseUrl(baseUrl) + void loadData() + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [baseUrl]) + + const toggle = async (skill: SkillInfo, enabled: boolean) => { + // Optimistic flip; revert on failure. + setSkills((prev) => prev.map((s) => (s.name === skill.name ? { ...s, enabled } : s))) try { - await apiClient.toggleSkill(skill.name, skill.enabled ? 'close' : 'open') - setSkills((prev) => prev.map((s) => (s.name === skill.name ? { ...s, enabled: !s.enabled } : s))) - } catch (err) { - console.error('Toggle failed:', err) - } finally { - setToggling(null) + const res = await apiClient.toggleSkill(skill.name, enabled ? 'open' : 'close') + if (res.status !== 'success') throw new Error() + } catch { + setSkills((prev) => prev.map((s) => (s.name === skill.name ? { ...s, enabled: !enabled } : s))) } } return ( -
-
-
-
-

{t('skills_title')}

-

{t('skills_desc')}

-
-
- - {/* Built-in Tools */} -
-
- {t('tools_section_title')} - {tools.length > 0 && ( - {tools.length} - )} -
- {loading ? ( -
- - {t('tools_loading')} -
- ) : tools.length > 0 ? ( -
- {tools.map((tool) => ( -
-
- - {tool.name} -
-

{tool.description}

-
- ))} -
- ) : null} -
- - {/* Skills */} +
+
-
- {t('skills_section_title')} - {skills.length > 0 && ( - {skills.length} - )} -
+

{t('skills_title')}

+

{t('skills_desc')}

+
+ + + {t('skills_hub_btn')} + +
+ +
+
{loading ? ( -
-
- -
-

{t('skills_loading')}

-

{t('skills_loading_desc')}

-
- ) : skills.length > 0 ? ( -
- {skills.map((skill) => ( -
-
-
- - {skill.name} -
- -
-

{skill.description}

-
- ))} +
+ + {t('skills_loading')}
) : ( -
-
- -
-

{t('skills_loading')}

+
+
+ {tools.length === 0 ? ( + + ) : ( +
+ {tools.map((tool) => ( +
+
+ + {tool.name} +
+

{tool.description || '--'}

+
+ ))} +
+ )} +
+ +
+ {skills.length === 0 ? ( + + ) : ( +
+ {skills.map((skill) => ( +
+
+ +
+
+
+ + {skill.display_name || skill.name} + + toggle(skill, v)} /> +
+

{skill.description || '--'}

+
+
+ ))} +
+ )} +
)}
@@ -136,4 +123,20 @@ const SkillsPage: React.FC = ({ baseUrl }) => { ) } +const Section: React.FC<{ title: string; count: number; children: React.ReactNode }> = ({ title, count, children }) => ( +
+
+ {title} + {count > 0 && ( + {count} + )} +
+ {children} +
+) + +const Empty: React.FC<{ text: string }> = ({ text }) => ( +

{text}

+) + export default SkillsPage diff --git a/desktop/src/renderer/src/types.ts b/desktop/src/renderer/src/types.ts index f9e49afe..276b1d1d 100644 --- a/desktop/src/renderer/src/types.ts +++ b/desktop/src/renderer/src/types.ts @@ -346,6 +346,7 @@ export interface ToolInfo { export interface SkillInfo { name: string + display_name?: string description: string source?: string enabled: boolean