# Conflicts: # frontend-web/src/components/workspace/agents/agent-gallery.tsx # frontend-web/src/components/workspace/settings/skill-settings-page.tsx
215 lines
6.3 KiB
TypeScript
215 lines
6.3 KiB
TypeScript
import { getBackendBaseURL } from "@/core/config";
|
|
import { apiFetch } from "@/core/api";
|
|
|
|
import type { Skill, SkillState } from "./type";
|
|
|
|
export interface ListSkillsParams {
|
|
search?: string;
|
|
tagId?: string | null;
|
|
}
|
|
|
|
export async function loadSkills(params: ListSkillsParams = {}) {
|
|
const qs = new URLSearchParams();
|
|
if (params.search) qs.set("search", params.search);
|
|
if (params.tagId) qs.set("tag_id", params.tagId);
|
|
const query = qs.toString();
|
|
const skills = await apiFetch(
|
|
`${getBackendBaseURL()}/api/skills${query ? `?${query}` : ""}`,
|
|
);
|
|
const json = await skills.json();
|
|
return json.skills as Skill[];
|
|
}
|
|
|
|
export async function enableSkill(skillName: string, enabled: boolean) {
|
|
const response = await apiFetch(
|
|
`${getBackendBaseURL()}/api/skills/${skillName}`,
|
|
{
|
|
method: "PUT",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify({
|
|
enabled,
|
|
}),
|
|
},
|
|
);
|
|
return response.json();
|
|
}
|
|
|
|
export interface InstallSkillRequest {
|
|
thread_id: string;
|
|
path: string;
|
|
}
|
|
|
|
export interface InstallSkillResponse {
|
|
success: boolean;
|
|
skill_name: string;
|
|
message: string;
|
|
overwritten?: boolean;
|
|
}
|
|
|
|
export interface ValidateSkillUploadResponse {
|
|
ok: boolean;
|
|
skill_name: string;
|
|
description: string;
|
|
license: string | null;
|
|
allowed_tools: string[];
|
|
conflict: boolean;
|
|
conflict_kind: "public" | "custom" | null;
|
|
warnings: string[];
|
|
}
|
|
|
|
export async function installSkill(
|
|
request: InstallSkillRequest,
|
|
): Promise<InstallSkillResponse> {
|
|
const response = await apiFetch(`${getBackendBaseURL()}/api/skills/install`, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify(request),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
// Handle HTTP error responses (4xx, 5xx)
|
|
const errorData = await response.json().catch(() => ({}));
|
|
const errorMessage =
|
|
errorData.detail ?? `HTTP ${response.status}: ${response.statusText}`;
|
|
return {
|
|
success: false,
|
|
skill_name: "",
|
|
message: errorMessage,
|
|
};
|
|
}
|
|
|
|
return response.json();
|
|
}
|
|
|
|
async function parseDetailedError(response: Response, fallback: string): Promise<Error> {
|
|
const data = await response.json().catch(() => ({}));
|
|
const detail = (data as { detail?: unknown }).detail;
|
|
const message = typeof detail === "string" && detail ? detail : `${fallback} (HTTP ${response.status})`;
|
|
return new Error(message);
|
|
}
|
|
|
|
export async function validateSkillUpload(file: File): Promise<ValidateSkillUploadResponse> {
|
|
const form = new FormData();
|
|
form.append("file", file, file.name);
|
|
const response = await apiFetch(`${getBackendBaseURL()}/api/skills/validate`, {
|
|
method: "POST",
|
|
body: form,
|
|
});
|
|
if (!response.ok) {
|
|
throw await parseDetailedError(response, "Validation failed");
|
|
}
|
|
return response.json() as Promise<ValidateSkillUploadResponse>;
|
|
}
|
|
|
|
export async function installSkillUpload(
|
|
file: File,
|
|
options: { overwrite?: boolean; targetCategory?: "public" | "custom" } = {},
|
|
): Promise<InstallSkillResponse> {
|
|
const form = new FormData();
|
|
form.append("file", file, file.name);
|
|
const params = new URLSearchParams();
|
|
if (options.overwrite) params.set("overwrite", "true");
|
|
if (options.targetCategory) params.set("target_category", options.targetCategory);
|
|
const query = params.size > 0 ? `?${params.toString()}` : "";
|
|
const response = await apiFetch(
|
|
`${getBackendBaseURL()}/api/skills/install-upload${query}`,
|
|
{ method: "POST", body: form },
|
|
);
|
|
if (!response.ok) {
|
|
throw await parseDetailedError(response, "Install failed");
|
|
}
|
|
return response.json() as Promise<InstallSkillResponse>;
|
|
}
|
|
|
|
export interface SetSkillPublishedResponse {
|
|
name: string;
|
|
owner_user_id: string | null;
|
|
published: boolean;
|
|
}
|
|
|
|
export async function setSkillPublished(
|
|
skillName: string,
|
|
published: boolean,
|
|
): Promise<SetSkillPublishedResponse> {
|
|
const response = await apiFetch(
|
|
`${getBackendBaseURL()}/api/skills/custom/${encodeURIComponent(skillName)}/published`,
|
|
{
|
|
method: "PUT",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ published }),
|
|
},
|
|
);
|
|
if (!response.ok) {
|
|
throw await parseDetailedError(response, "Failed to update published state");
|
|
}
|
|
return response.json() as Promise<SetSkillPublishedResponse>;
|
|
}
|
|
|
|
export async function setSkillState(
|
|
skillName: string,
|
|
state: SkillState,
|
|
): Promise<Skill> {
|
|
const response = await apiFetch(
|
|
`${getBackendBaseURL()}/api/skills/custom/${encodeURIComponent(skillName)}/state`,
|
|
{
|
|
method: "PUT",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ state }),
|
|
},
|
|
);
|
|
if (!response.ok) {
|
|
throw await parseDetailedError(response, "Failed to update skill state");
|
|
}
|
|
return response.json() as Promise<Skill>;
|
|
}
|
|
|
|
export async function setSkillPinned(
|
|
skillName: string,
|
|
pinned: boolean,
|
|
): Promise<Skill> {
|
|
const response = await apiFetch(
|
|
`${getBackendBaseURL()}/api/skills/custom/${encodeURIComponent(skillName)}/pinned`,
|
|
{
|
|
method: "PUT",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ pinned }),
|
|
},
|
|
);
|
|
if (!response.ok) {
|
|
throw await parseDetailedError(response, "Failed to update skill pinned state");
|
|
}
|
|
return response.json() as Promise<Skill>;
|
|
}
|
|
|
|
export interface SkillContentResponse {
|
|
name: string;
|
|
category: string;
|
|
content: string;
|
|
}
|
|
|
|
export async function getSkillContent(
|
|
skillName: string,
|
|
): Promise<SkillContentResponse> {
|
|
const response = await apiFetch(
|
|
`${getBackendBaseURL()}/api/skills/${encodeURIComponent(skillName)}/content`,
|
|
);
|
|
if (!response.ok) {
|
|
throw await parseDetailedError(response, "Failed to load skill content");
|
|
}
|
|
return response.json() as Promise<SkillContentResponse>;
|
|
}
|
|
|
|
export async function deleteCustomSkill(skillName: string): Promise<void> {
|
|
const response = await apiFetch(
|
|
`${getBackendBaseURL()}/api/skills/custom/${encodeURIComponent(skillName)}`,
|
|
{ method: "DELETE" },
|
|
);
|
|
if (!response.ok) {
|
|
throw await parseDetailedError(response, "Failed to delete skill");
|
|
}
|
|
}
|