Add skill sync action
This commit is contained in:
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user