Add skill enable controls and data factory SQL skill

This commit is contained in:
武阳
2026-05-08 14:56:32 +08:00
parent 046b0eeab0
commit bd2f260b9f
16 changed files with 1167 additions and 25 deletions
+27
View File
@@ -20,3 +20,30 @@ export async function GET() {
},
});
}
export async function PATCH(request: Request) {
const account = await getCurrentAccount();
if (!account)
return Response.json({ error: "unauthorized" }, { status: 401 });
const body = (await request.json()) as Record<string, unknown>;
const response = await fetch(`${CLAW_API_URL}/api/skills`, {
method: "PATCH",
headers: {
"content-type": "application/json",
},
body: JSON.stringify({
...body,
account_id: account.id,
}),
cache: "no-store",
});
const payload = await response.text();
return new Response(payload, {
status: response.status,
headers: {
"content-type":
response.headers.get("content-type") ?? "application/json",
},
});
}
+72 -16
View File
@@ -72,6 +72,8 @@ type SkillSummary = {
name: string;
description?: string;
when_to_use?: string;
enabled?: boolean;
configurable?: boolean;
};
type SlashCommandSummary = {
@@ -1091,6 +1093,7 @@ function SkillInsertDialog() {
const [open, setOpen] = useState(false);
const [skills, setSkills] = useState<SkillSummary[]>([]);
const [status, setStatus] = useState("点击后读取当前后端 Skill 列表。");
const [updatingSkill, setUpdatingSkill] = useState<string | null>(null);
async function loadSkills() {
setStatus("正在读取 Skill 列表...");
@@ -1111,6 +1114,43 @@ function SkillInsertDialog() {
}
}
async function setSkillEnabled(skill: SkillSummary, enabled: boolean) {
if (skill.configurable === false) return;
const previous = skills;
setUpdatingSkill(skill.name);
setSkills((items) =>
items.map((item) =>
item.name === skill.name ? { ...item, enabled } : item,
),
);
try {
const response = await fetch("/api/claw/skills", {
method: "PATCH",
headers: {
"content-type": "application/json",
},
body: JSON.stringify({ skill: skill.name, 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.name}` : `已停用 ${skill.name}`);
} catch (err) {
setSkills(previous);
setStatus(err instanceof Error ? err.message : "更新 Skill 失败");
} finally {
setUpdatingSkill(null);
}
}
return (
<Dialog
open={open}
@@ -1134,7 +1174,7 @@ function SkillInsertDialog() {
<DialogHeader>
<DialogTitle>Skills</DialogTitle>
<DialogDescription>
Skill 便
Skill
</DialogDescription>
</DialogHeader>
{status ? (
@@ -1142,23 +1182,39 @@ function SkillInsertDialog() {
) : null}
<div className="grid max-h-96 gap-2 overflow-y-auto">
{skills.map((skill) => (
<button
<div
key={skill.name}
type="button"
className="rounded-lg border px-3 py-2 text-left transition-colors hover:bg-muted"
onClick={() => {
insertComposerText(`Use the ${skill.name} skill.\n\n`);
setOpen(false);
}}
className="flex items-start gap-3 rounded-lg border px-3 py-2 transition-colors hover:bg-muted"
>
<div className="flex items-center gap-2 font-medium text-sm">
<ListIcon className="size-3.5 text-muted-foreground" />
{skill.name}
</div>
<div className="mt-1 line-clamp-2 text-muted-foreground text-xs">
{skill.description || skill.when_to_use || "暂无说明"}
</div>
</button>
<input
type="checkbox"
checked={skill.enabled !== false}
disabled={
skill.configurable === false || updatingSkill === skill.name
}
aria-label={`启用 ${skill.name}`}
className="mt-1 size-4 accent-primary"
onChange={(event) =>
setSkillEnabled(skill, event.currentTarget.checked)
}
/>
<button
type="button"
className="min-w-0 flex-1 text-left"
onClick={() => {
insertComposerText(`Use the ${skill.name} skill.\n\n`);
setOpen(false);
}}
>
<div className="flex items-center gap-2 font-medium text-sm">
<ListIcon className="size-3.5 shrink-0 text-muted-foreground" />
<span className="truncate">{skill.name}</span>
</div>
<div className="mt-1 line-clamp-2 text-muted-foreground text-xs">
{skill.description || skill.when_to_use || "暂无说明"}
</div>
</button>
</div>
))}
</div>
</DialogContent>