feat(webui): add skills marketplace

This commit is contained in:
Xubin Ren
2026-07-30 01:13:37 +08:00
parent 129b74b4cf
commit ba0ba4749d
23 changed files with 3042 additions and 246 deletions
+86
View File
@@ -26,7 +26,12 @@ import type {
SettingsUpdate,
SidebarStatePayload,
SkillDetail,
SkillActionPayload,
SkillInstallPayload,
SkillsPayload,
SkillsSearchPayload,
SkillsTrendsPayload,
SkillsTrendingPayload,
SlashCommand,
SlashCommandLifecycle,
TranscriptionSettingsUpdate,
@@ -310,6 +315,87 @@ export async function fetchSkillDetail(
);
}
export async function updateSkillEnabled(
token: string,
name: string,
enabled: boolean,
base: string = "",
): Promise<SkillActionPayload> {
const params = new URLSearchParams({ name, enabled: String(enabled) });
return request<SkillActionPayload>(
`${base}/api/webui/skills/update?${params}`,
token,
);
}
export async function deleteSkill(
token: string,
name: string,
base: string = "",
): Promise<SkillActionPayload> {
const params = new URLSearchParams({ name });
return request<SkillActionPayload>(
`${base}/api/webui/skills/delete?${params}`,
token,
);
}
export async function searchMarketplaceSkills(
token: string,
query: string,
base: string = "",
): Promise<SkillsSearchPayload> {
const params = new URLSearchParams({ q: query });
return request<SkillsSearchPayload>(
`${base}/api/webui/skills/search?${params}`,
token,
undefined,
API_READ_TIMEOUT_MS,
);
}
export async function fetchTrendingMarketplaceSkills(
token: string,
base: string = "",
): Promise<SkillsTrendingPayload> {
return request<SkillsTrendingPayload>(
`${base}/api/webui/skills/trending`,
token,
undefined,
API_READ_TIMEOUT_MS,
);
}
export async function fetchMarketplaceSkillTrends(
token: string,
skillIds: string[],
base: string = "",
): Promise<SkillsTrendsPayload> {
const params = new URLSearchParams();
skillIds.forEach((id) => params.append("id", id));
return request<SkillsTrendsPayload>(
`${base}/api/webui/skills/trends?${params}`,
token,
undefined,
API_READ_TIMEOUT_MS,
);
}
export async function installMarketplaceSkill(
token: string,
source: string,
skill: string,
base: string = "",
): Promise<SkillInstallPayload> {
const params = new URLSearchParams({ source, skill });
return request<SkillInstallPayload>(
`${base}/api/webui/skills/install?${params}`,
token,
undefined,
150_000,
);
}
export async function deleteSession(
token: string,
key: string,
+18
View File
@@ -0,0 +1,18 @@
import type { SkillsPayload } from "@/lib/types";
export const SKILLS_CHANGED_EVENT = "nanobot:skills-changed";
export function isSkillsPayload(value: unknown): value is SkillsPayload {
return (
!!value
&& typeof value === "object"
&& Array.isArray((value as { skills?: unknown }).skills)
);
}
export function notifySkillsChanged(payload: SkillsPayload): void {
if (typeof window === "undefined") return;
window.dispatchEvent(new CustomEvent<SkillsPayload>(SKILLS_CHANGED_EVENT, {
detail: payload,
}));
}
+53
View File
@@ -178,6 +178,8 @@ export interface SkillSummary {
name: string;
description: string;
source: "workspace" | "builtin" | string;
enabled?: boolean;
deletable?: boolean;
available: boolean;
unavailable_reason?: string;
}
@@ -189,13 +191,64 @@ export interface SkillRequirements {
missing_env: string[];
}
export interface SkillInstallOption {
id: string;
kind: string;
label: string;
command: string;
}
export interface SkillDetail extends SkillSummary {
requirements: SkillRequirements;
install_options?: SkillInstallOption[];
raw_markdown: string;
}
export interface SkillsPayload { skills: SkillSummary[]; }
export interface SkillActionPayload extends SkillsPayload {
last_action: {
name: string;
enabled: boolean;
deleted: boolean;
};
}
export interface MarketplaceSkillSummary {
id: string;
skill_id: string;
name: string;
source: string;
installs: number;
url: string;
installed: boolean;
rank?: number;
}
export interface SkillsSearchPayload {
query: string;
skills: MarketplaceSkillSummary[];
install_supported: boolean;
}
export interface SkillsTrendingPayload {
skills: MarketplaceSkillSummary[];
period: "24h";
install_supported: boolean;
}
export interface SkillsTrendsPayload {
trends: Record<string, number[]>;
}
export interface SkillInstallPayload extends SkillsPayload {
last_action: {
installed: boolean;
already_installed: boolean;
name: string;
};
}
/** Structured UI blob on ``progress`` WS frames; channels may add more ``kind`` values later. */
export interface AgentUIBlob {
kind: string;