Add skill sync action

This commit is contained in:
wuyang6
2026-05-11 09:59:34 +08:00
parent 010dc1a88d
commit b4bd7698a4
3 changed files with 162 additions and 0 deletions
+25
View File
@@ -47,3 +47,28 @@ export async function PATCH(request: Request) {
},
});
}
export async function POST() {
const account = await getCurrentAccount();
if (!account)
return Response.json({ error: "unauthorized" }, { status: 401 });
const response = await fetch(`${CLAW_API_URL}/api/skills/sync`, {
method: "POST",
headers: {
"content-type": "application/json",
},
body: JSON.stringify({
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",
},
});
}
@@ -76,6 +76,17 @@ type SkillSummary = {
configurable?: boolean;
};
type SkillSyncResponse = {
skills?: SkillSummary[];
updated?: boolean;
before?: string;
after?: string;
changed_files?: string[];
message?: string;
error?: string;
detail?: string;
};
type SlashCommandSummary = {
names: string[];
primary: string;
@@ -1099,6 +1110,7 @@ function SkillInsertDialog() {
const [skills, setSkills] = useState<SkillSummary[]>([]);
const [status, setStatus] = useState("点击后读取当前后端 Skill 列表。");
const [updatingSkill, setUpdatingSkill] = useState<string | null>(null);
const [syncingSkills, setSyncingSkills] = useState(false);
async function loadSkills() {
setStatus("正在读取 Skill 列表...");
@@ -1119,6 +1131,33 @@ function SkillInsertDialog() {
}
}
async function syncSkills() {
setSyncingSkills(true);
setStatus("正在从 git 同步 Skill...");
try {
const response = await fetch("/api/claw/skills", {
method: "POST",
cache: "no-store",
});
const payload = (await response.json()) as SkillSyncResponse;
if (!response.ok || !Array.isArray(payload.skills)) {
throw new Error(payload.detail ?? payload.error ?? "同步 Skill 失败");
}
setSkills(payload.skills);
const changedCount = payload.changed_files?.length ?? 0;
if (payload.updated) {
const shortHash = payload.after ? payload.after.slice(0, 7) : "最新提交";
setStatus(`Skill 已更新到 ${shortHash},变更 ${changedCount} 个文件。`);
} else {
setStatus("Skill 已是最新。");
}
} catch (err) {
setStatus(err instanceof Error ? err.message : "同步 Skill 失败");
} finally {
setSyncingSkills(false);
}
}
async function setSkillEnabled(skill: SkillSummary, enabled: boolean) {
if (skill.configurable === false) return;
const previous = skills;
@@ -1182,6 +1221,24 @@ function SkillInsertDialog() {
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>
{status ? (
<p className="text-muted-foreground text-sm">{status}</p>
) : null}