- 智能体支持从广场收藏到「我的」(新增 agent_favorites 表与收藏/取消收藏接口) - 定时任务新增「必须生成Markdown文件」开关:注入提示词并校验生成的 .md 文件且正文超过100字,否则记为失败(存于 execution_context,无需迁移) - 聊天问答展示时间(年月日 时分):问题在右下角、回答在左下角 - 常用提示词改为与账号绑定(新增 user_custom_prompts 表与 /api/user-prompts 接口),仅在首页输入框上方以居中标签 + 轮播展示,齿轮置于最右侧管理 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
252 lines
7.9 KiB
TypeScript
252 lines
7.9 KiB
TypeScript
import { getBackendBaseURL } from "@/core/config";
|
|
import { apiFetch } from "@/core/api";
|
|
|
|
import type {
|
|
Agent,
|
|
AgentSelectorResponse,
|
|
CreateAgentRequest,
|
|
UpdateAgentRequest,
|
|
} from "./types";
|
|
|
|
const BACKEND_UNAVAILABLE_STATUSES = new Set([502, 503, 504]);
|
|
|
|
export class AgentNameCheckError extends Error {
|
|
constructor(
|
|
message: string,
|
|
public readonly reason: "backend_unreachable" | "request_failed",
|
|
) {
|
|
super(message);
|
|
this.name = "AgentNameCheckError";
|
|
}
|
|
}
|
|
|
|
export interface ListAgentsParams {
|
|
search?: string;
|
|
tagIds?: string[];
|
|
}
|
|
|
|
export async function listAgents(params: ListAgentsParams = {}): Promise<Agent[]> {
|
|
const qs = new URLSearchParams();
|
|
if (params.search) qs.set("search", params.search);
|
|
for (const id of params.tagIds ?? []) qs.append("tag_ids", id);
|
|
const query = qs.toString();
|
|
const res = await apiFetch(
|
|
`${getBackendBaseURL()}/api/agents${query ? `?${query}` : ""}`,
|
|
);
|
|
if (!res.ok) throw new Error(`Failed to load agents: ${res.statusText}`);
|
|
const data = (await res.json()) as { agents: Agent[] };
|
|
return data.agents;
|
|
}
|
|
|
|
export async function listAllAgentsAdmin(): Promise<Agent[]> {
|
|
const res = await apiFetch(`${getBackendBaseURL()}/api/agents?admin=true`);
|
|
if (!res.ok) {
|
|
const err = (await res.json().catch(() => ({}))) as { detail?: string };
|
|
throw new Error(err.detail ?? `Failed to load agents: ${res.statusText}`);
|
|
}
|
|
const data = (await res.json()) as { agents: Agent[] };
|
|
return data.agents;
|
|
}
|
|
|
|
export async function getAgent(id: string): Promise<Agent> {
|
|
const res = await apiFetch(`${getBackendBaseURL()}/api/agents/${id}`);
|
|
if (!res.ok) throw new Error(`Agent '${id}' not found`);
|
|
return res.json() as Promise<Agent>;
|
|
}
|
|
|
|
export async function createAgent(request: CreateAgentRequest): Promise<Agent> {
|
|
const res = await apiFetch(`${getBackendBaseURL()}/api/agents`, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify(request),
|
|
});
|
|
if (!res.ok) {
|
|
const err = (await res.json().catch(() => ({}))) as { detail?: string };
|
|
throw new Error(err.detail ?? `Failed to create agent: ${res.statusText}`);
|
|
}
|
|
return res.json() as Promise<Agent>;
|
|
}
|
|
|
|
export async function updateAgent(
|
|
id: string,
|
|
request: UpdateAgentRequest,
|
|
): Promise<Agent> {
|
|
const res = await apiFetch(`${getBackendBaseURL()}/api/agents/${id}`, {
|
|
method: "PUT",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify(request),
|
|
});
|
|
if (!res.ok) {
|
|
const err = (await res.json().catch(() => ({}))) as { detail?: string };
|
|
throw new Error(err.detail ?? `Failed to update agent: ${res.statusText}`);
|
|
}
|
|
return res.json() as Promise<Agent>;
|
|
}
|
|
|
|
export async function deleteAgent(id: string): Promise<void> {
|
|
const res = await apiFetch(`${getBackendBaseURL()}/api/agents/${id}`, {
|
|
method: "DELETE",
|
|
});
|
|
if (!res.ok) throw new Error(`Failed to delete agent: ${res.statusText}`);
|
|
}
|
|
|
|
export async function getAgentSelector(): Promise<AgentSelectorResponse> {
|
|
const res = await apiFetch(`${getBackendBaseURL()}/api/agents/selector`);
|
|
if (!res.ok) throw new Error(`Failed to load agent selector: ${res.statusText}`);
|
|
return res.json() as Promise<AgentSelectorResponse>;
|
|
}
|
|
|
|
export async function favoriteAgent(id: string): Promise<void> {
|
|
const res = await apiFetch(`${getBackendBaseURL()}/api/agents/${id}/favorite`, {
|
|
method: "POST",
|
|
});
|
|
if (!res.ok) {
|
|
const err = (await res.json().catch(() => ({}))) as { detail?: string };
|
|
throw new Error(err.detail ?? `Failed to favorite agent: ${res.statusText}`);
|
|
}
|
|
}
|
|
|
|
export async function unfavoriteAgent(id: string): Promise<void> {
|
|
const res = await apiFetch(`${getBackendBaseURL()}/api/agents/${id}/favorite`, {
|
|
method: "DELETE",
|
|
});
|
|
if (!res.ok) {
|
|
const err = (await res.json().catch(() => ({}))) as { detail?: string };
|
|
throw new Error(err.detail ?? `Failed to unfavorite agent: ${res.statusText}`);
|
|
}
|
|
}
|
|
|
|
export async function reorderFeaturedAgents(agentIds: string[]): Promise<void> {
|
|
const res = await apiFetch(
|
|
`${getBackendBaseURL()}/api/agents/featured/order`,
|
|
{
|
|
method: "PUT",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ agent_ids: agentIds }),
|
|
},
|
|
);
|
|
if (!res.ok) {
|
|
const err = (await res.json().catch(() => ({}))) as { detail?: string };
|
|
throw new Error(
|
|
err.detail ?? `Failed to reorder featured agents: ${res.statusText}`,
|
|
);
|
|
}
|
|
}
|
|
|
|
export async function listPrivateAgents(password: string): Promise<Agent[]> {
|
|
const res = await apiFetch(
|
|
`${getBackendBaseURL()}/api/agents/private-square/list`,
|
|
{
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ password }),
|
|
},
|
|
);
|
|
if (!res.ok) {
|
|
const err = (await res.json().catch(() => ({}))) as { detail?: string };
|
|
throw new Error(
|
|
err.detail ?? `Failed to load private agents: ${res.statusText}`,
|
|
);
|
|
}
|
|
const data = (await res.json()) as { agents: Agent[] };
|
|
return data.agents;
|
|
}
|
|
|
|
export async function setAgentPrivate(
|
|
id: string,
|
|
isPrivate: boolean,
|
|
): Promise<Agent> {
|
|
const res = await apiFetch(
|
|
`${getBackendBaseURL()}/api/agents/${id}/private`,
|
|
{
|
|
method: "PUT",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ is_private: isPrivate }),
|
|
},
|
|
);
|
|
if (!res.ok) {
|
|
const err = (await res.json().catch(() => ({}))) as { detail?: string };
|
|
throw new Error(
|
|
err.detail ?? `Failed to update agent privacy: ${res.statusText}`,
|
|
);
|
|
}
|
|
return res.json() as Promise<Agent>;
|
|
}
|
|
|
|
export async function setAgentFeatured(
|
|
id: string,
|
|
order: number | null,
|
|
): Promise<Agent> {
|
|
const res = await apiFetch(
|
|
`${getBackendBaseURL()}/api/agents/${id}/featured`,
|
|
{
|
|
method: "PUT",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ order }),
|
|
},
|
|
);
|
|
if (!res.ok) {
|
|
const err = (await res.json().catch(() => ({}))) as { detail?: string };
|
|
throw new Error(
|
|
err.detail ?? `Failed to set featured: ${res.statusText}`,
|
|
);
|
|
}
|
|
return res.json() as Promise<Agent>;
|
|
}
|
|
|
|
export interface AgentSkillsSyncRemoval {
|
|
agent_id: string;
|
|
agent_name: string | null;
|
|
user_id: string | null;
|
|
removed_skills: string[];
|
|
}
|
|
|
|
export interface AgentSkillsSyncResult {
|
|
checked: number;
|
|
modified: number;
|
|
removals: AgentSkillsSyncRemoval[];
|
|
}
|
|
|
|
export async function adminSyncAgentSkills(): Promise<AgentSkillsSyncResult> {
|
|
const res = await apiFetch(
|
|
`${getBackendBaseURL()}/api/agents/admin/sync-skills`,
|
|
{ method: "POST" },
|
|
);
|
|
if (!res.ok) {
|
|
const err = (await res.json().catch(() => ({}))) as { detail?: string };
|
|
throw new Error(err.detail ?? `Failed to sync agent skills: ${res.statusText}`);
|
|
}
|
|
return res.json() as Promise<AgentSkillsSyncResult>;
|
|
}
|
|
|
|
export async function checkAgentName(
|
|
name: string,
|
|
): Promise<{ available: boolean; name: string }> {
|
|
let res: Response;
|
|
try {
|
|
res = await apiFetch(
|
|
`${getBackendBaseURL()}/api/agents/check?name=${encodeURIComponent(name)}`,
|
|
);
|
|
} catch {
|
|
throw new AgentNameCheckError(
|
|
"Could not reach the CM Assistant backend.",
|
|
"backend_unreachable",
|
|
);
|
|
}
|
|
|
|
if (!res.ok) {
|
|
const err = (await res.json().catch(() => ({}))) as { detail?: string };
|
|
if (BACKEND_UNAVAILABLE_STATUSES.has(res.status)) {
|
|
throw new AgentNameCheckError(
|
|
"Could not reach the CM Assistant backend.",
|
|
"backend_unreachable",
|
|
);
|
|
}
|
|
throw new AgentNameCheckError(
|
|
err.detail ?? `Failed to check agent name: ${res.statusText}`,
|
|
"request_failed",
|
|
);
|
|
}
|
|
return res.json() as Promise<{ available: boolean; name: string }>;
|
|
}
|