Polish skill sync controls

This commit is contained in:
wuyang6
2026-05-11 10:04:38 +08:00
parent b4bd7698a4
commit 119ac41916
2 changed files with 107 additions and 25 deletions
+78 -23
View File
@@ -1111,6 +1111,13 @@ function SkillInsertDialog() {
const [status, setStatus] = useState("点击后读取当前后端 Skill 列表。");
const [updatingSkill, setUpdatingSkill] = useState<string | null>(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 (
<Dialog
open={open}
@@ -1214,35 +1258,46 @@ function SkillInsertDialog() {
<BookOpenIcon className="size-4" />
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogContent className="gap-3 p-5">
<DialogHeader className="gap-1 pr-6">
<DialogTitle>Skills</DialogTitle>
<DialogDescription>
Skill
</DialogDescription>
</DialogHeader>
<div className="flex items-center justify-between gap-3">
<Button
type="button"
variant="outline"
size="sm"
className="h-8 gap-2"
disabled={syncingSkills}
onClick={syncSkills}
>
<RefreshCwIcon
className={cn("size-3.5", syncingSkills && "animate-spin")}
/>
Skill
</Button>
<p className="text-muted-foreground text-xs">
git skill
</p>
<div className="flex flex-wrap items-center gap-2">
<div className="flex items-center gap-2">
<Button
type="button"
variant="outline"
size="sm"
className="h-8 gap-2 px-2.5"
disabled={syncingSkills}
onClick={syncSkills}
>
<RefreshCwIcon
className={cn("size-3.5", syncingSkills && "animate-spin")}
/>
Skill
</Button>
<Button
type="button"
variant="ghost"
size="sm"
className="h-8 px-2.5"
disabled={!configurableSkills.length || bulkUpdatingSkills}
onClick={() => setAllSkillsEnabled(!allSkillsEnabled)}
>
{allSkillsEnabled ? "全取消" : "全选"}
</Button>
</div>
{status ? (
<p className="min-w-0 flex-1 truncate text-muted-foreground text-xs">
{status}
</p>
) : null}
</div>
{status ? (
<p className="text-muted-foreground text-sm">{status}</p>
) : null}
<div className="grid max-h-96 gap-2 overflow-y-auto">
<div className="grid max-h-96 gap-2 overflow-y-auto pt-1">
{skills.map((skill) => (
<div
key={skill.name}