diff --git a/backend/api/server.py b/backend/api/server.py index 3349ace..7055000 100644 --- a/backend/api/server.py +++ b/backend/api/server.py @@ -633,6 +633,27 @@ class AgentState: if key.startswith(account_prefix): self._agents.pop(key, None) + def set_all_skills_enabled( + self, + account_id: str | None, + enabled: bool, + ) -> None: + with self._lock: + config = self._config_for(account_id) + configurable_skill_names = { + skill.name.lower() + for skill in get_bundled_skills(config.cwd) + if skill.user_invocable + and skill.name.lower() not in ALWAYS_ENABLED_HIDDEN_SKILL_NAMES + } + disabled = self._load_disabled_skill_names(account_id) + if enabled: + disabled.difference_update(configurable_skill_names) + else: + disabled.update(configurable_skill_names) + self._save_disabled_skill_names(account_id, disabled) + self._clear_agents_for_account(account_id) + def _clear_agents_for_account(self, account_id: str | None) -> None: account_prefix = f'{self._account_key(account_id)}:' for key in list(self._agents): @@ -856,8 +877,9 @@ class ModelListRequest(BaseModel): class SkillPreferenceUpdate(BaseModel): - skill: str = Field(min_length=1) + skill: str | None = None enabled: bool + apply_all: bool = False account_id: str | None = None @@ -942,7 +964,12 @@ def create_app(state: AgentState) -> FastAPI: @app.patch('/api/skills') async def update_skill_preference(payload: SkillPreferenceUpdate) -> list[dict[str, Any]]: try: - state.set_skill_enabled(payload.account_id, payload.skill, payload.enabled) + if payload.apply_all: + state.set_all_skills_enabled(payload.account_id, payload.enabled) + elif payload.skill: + state.set_skill_enabled(payload.account_id, payload.skill, payload.enabled) + else: + raise ValueError('skill must be provided unless apply_all is true') except ValueError as exc: raise HTTPException(status_code=400, detail=str(exc)) return await list_skills(payload.account_id) diff --git a/frontend/app/components/assistant-ui/thread.tsx b/frontend/app/components/assistant-ui/thread.tsx index dacb72d..9bad58b 100644 --- a/frontend/app/components/assistant-ui/thread.tsx +++ b/frontend/app/components/assistant-ui/thread.tsx @@ -1111,6 +1111,13 @@ function SkillInsertDialog() { const [status, setStatus] = useState("点击后读取当前后端 Skill 列表。"); const [updatingSkill, setUpdatingSkill] = useState(null); const [syncingSkills, setSyncingSkills] = useState(false); + const [bulkUpdatingSkills, setBulkUpdatingSkills] = useState(false); + const configurableSkills = skills.filter( + (skill) => skill.configurable !== false, + ); + const allSkillsEnabled = + configurableSkills.length > 0 && + configurableSkills.every((skill) => skill.enabled !== false); async function loadSkills() { setStatus("正在读取 Skill 列表..."); @@ -1195,6 +1202,43 @@ function SkillInsertDialog() { } } + async function setAllSkillsEnabled(enabled: boolean) { + if (!configurableSkills.length) return; + const previous = skills; + setBulkUpdatingSkills(true); + setSkills((items) => + items.map((item) => + item.configurable === false ? item : { ...item, enabled }, + ), + ); + try { + const response = await fetch("/api/claw/skills", { + method: "PATCH", + headers: { + "content-type": "application/json", + }, + body: JSON.stringify({ apply_all: true, enabled }), + }); + const payload = (await response.json()) as + | SkillSummary[] + | { error?: string; detail?: string }; + if (!response.ok || !Array.isArray(payload)) { + throw new Error( + Array.isArray(payload) + ? "更新失败" + : (payload.detail ?? payload.error ?? "更新失败"), + ); + } + setSkills(payload); + setStatus(enabled ? "已启用全部 Skill。" : "已停用全部可选 Skill。"); + } catch (err) { + setSkills(previous); + setStatus(err instanceof Error ? err.message : "批量更新 Skill 失败"); + } finally { + setBulkUpdatingSkills(false); + } + } + return ( - - + + Skills 勾选的 Skill 会进入模型可见列表;点击名称可插入显式调用。 -
- -

- 从当前 git 分支拉取最新 skill 文件 -

+
+
+ + +
+ {status ? ( +

+ {status} +

+ ) : null}
- {status ? ( -

{status}

- ) : null} -
+
{skills.map((skill) => (