Add skill enable controls and data factory SQL skill
This commit is contained in:
@@ -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,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>
|
||||
|
||||
Reference in New Issue
Block a user