224 lines
13 KiB
TypeScript
224 lines
13 KiB
TypeScript
import type {
|
||
AdminProfile,
|
||
AdminUser,
|
||
AgentDebugResult,
|
||
AgentGenerationConfig,
|
||
AgentRuntimeConfig,
|
||
AiLogRecord,
|
||
ApiResponse,
|
||
ChatDetail,
|
||
ChatRecord,
|
||
ChatRecordQuery,
|
||
DashboardStats,
|
||
KnowledgeItem,
|
||
KnowledgeDetail,
|
||
KnowledgeSyncJob,
|
||
KnowledgeVersion,
|
||
KnowledgeVersionContent,
|
||
MigrationSummary,
|
||
KnowledgeBatchUpdateResult,
|
||
RetrievalLogItem,
|
||
AttentionItem,
|
||
ModelItem,
|
||
SystemConfigItem,
|
||
UserImportResult,
|
||
PageResult,
|
||
PromptDetail,
|
||
PromptHistoryItem,
|
||
} from "../types/api";
|
||
|
||
const API_BASE = import.meta.env.VITE_API_BASE_URL ?? "/api";
|
||
const TOKEN_KEY = "ai-kb-admin-token";
|
||
|
||
export function getToken() {
|
||
return window.localStorage.getItem(TOKEN_KEY);
|
||
}
|
||
|
||
export function saveToken(token: string) {
|
||
window.localStorage.setItem(TOKEN_KEY, token);
|
||
}
|
||
|
||
export function clearToken() {
|
||
window.localStorage.removeItem(TOKEN_KEY);
|
||
}
|
||
|
||
async function request<T>(path: string, init: RequestInit = {}): Promise<T> {
|
||
const headers = new Headers(init.headers);
|
||
headers.set("Content-Type", "application/json");
|
||
const token = getToken();
|
||
if (token) headers.set("Authorization", `Bearer ${token}`);
|
||
const response = await fetch(`${API_BASE}${path}`, { ...init, headers });
|
||
const body = await readApiResponse<T>(response);
|
||
if (!response.ok || body.code !== 0) {
|
||
throw new Error(body.detail || body.message || `${response.status} ${response.statusText}`);
|
||
}
|
||
return body.data;
|
||
}
|
||
|
||
async function readApiResponse<T>(response: Response): Promise<ApiResponse<T> & { detail?: string }> {
|
||
const text = await response.text();
|
||
if (!text.trim()) {
|
||
throw new Error(response.ok ? "接口返回为空" : `服务暂时不可用(${response.status})`);
|
||
}
|
||
try {
|
||
return JSON.parse(text) as ApiResponse<T> & { detail?: string };
|
||
} catch {
|
||
throw new Error(response.ok ? "接口返回格式不正确" : `服务暂时不可用(${response.status})`);
|
||
}
|
||
}
|
||
|
||
async function download(path: string, filename: string) {
|
||
const headers = new Headers();
|
||
const token = getToken();
|
||
if (token) headers.set("Authorization", `Bearer ${token}`);
|
||
const response = await fetch(`${API_BASE}${path}`, { headers });
|
||
if (!response.ok) throw new Error("导出失败");
|
||
const blob = await response.blob();
|
||
const url = window.URL.createObjectURL(blob);
|
||
const anchor = document.createElement("a");
|
||
anchor.href = url;
|
||
anchor.download = filename;
|
||
anchor.click();
|
||
window.URL.revokeObjectURL(url);
|
||
}
|
||
|
||
async function upload<T>(path: string, formData: FormData): Promise<T> {
|
||
const headers = new Headers();
|
||
const token = getToken();
|
||
if (token) headers.set("Authorization", `Bearer ${token}`);
|
||
const response = await fetch(`${API_BASE}${path}`, { method: "POST", body: formData, headers });
|
||
const body = await readApiResponse<T>(response);
|
||
if (!response.ok || body.code !== 0) {
|
||
throw new Error(body.message || body.detail || `${response.status} ${response.statusText}`);
|
||
}
|
||
return body.data;
|
||
}
|
||
|
||
function queryString(query: object) {
|
||
const params = new URLSearchParams();
|
||
Object.entries(query).forEach(([key, value]) => {
|
||
if (value !== undefined && value !== null && value !== "") params.set(key, String(value));
|
||
});
|
||
const text = params.toString();
|
||
return text ? `?${text}` : "";
|
||
}
|
||
|
||
export const api = {
|
||
login: (username: string, password: string) =>
|
||
request<{ token: string; admin: AdminProfile }>("/admin/login", {
|
||
method: "POST",
|
||
body: JSON.stringify({ username, password }),
|
||
}),
|
||
logout: () => request<null>("/admin/logout", { method: "POST", body: "{}" }),
|
||
profile: () => request<AdminProfile>("/admin/profile"),
|
||
dashboard: (start?: string, end?: string) => {
|
||
const params = new URLSearchParams();
|
||
if (start) params.set("start", start);
|
||
if (end) params.set("end", end);
|
||
const qs = params.toString();
|
||
return request<DashboardStats>(`/admin/dashboard${qs ? "?" + qs : ""}`);
|
||
},
|
||
users: (query: { keyword?: string; page?: number; pageSize?: number } = {}) => request<PageResult<AdminUser>>(`/admin/user/list${queryString(query)}`),
|
||
createUser: (payload: Record<string, unknown>) =>
|
||
request<AdminUser>("/admin/user", { method: "POST", body: JSON.stringify(payload) }),
|
||
importUsers: (students: Record<string, unknown>[]) =>
|
||
request<UserImportResult>("/admin/user/import", { method: "POST", body: JSON.stringify({ students }) }),
|
||
downloadStudentTemplate: () => download("/admin/user/import/template", "学员导入模板.xlsx"),
|
||
importUsersExcel: (file: File) => {
|
||
const formData = new FormData();
|
||
formData.append("file", file);
|
||
return upload<UserImportResult>("/admin/user/import/excel", formData);
|
||
},
|
||
updateUser: (id: number, payload: Record<string, unknown>) =>
|
||
request<AdminUser>(`/admin/user/${id}`, { method: "PUT", body: JSON.stringify(payload) }),
|
||
deleteUser: (id: number) => request<null>(`/admin/user/${id}`, { method: "DELETE" }),
|
||
knowledge: (query: Record<string, unknown> = {}) => request<PageResult<KnowledgeItem>>(`/admin/knowledge/list${queryString(query)}`),
|
||
knowledgeOptions: () => request<KnowledgeItem[]>("/admin/knowledge/options"),
|
||
resolveKnowledgeNode: (nodeId: string) => request<{ nodeId: string; spaceId: string; sourceTitle: string; name: string; remark: string }>("/admin/knowledge/resolve-node", { method: "POST", body: JSON.stringify({ nodeId }) }),
|
||
createKnowledge: (payload: Record<string, unknown>) =>
|
||
request<KnowledgeItem>("/admin/knowledge", { method: "POST", body: JSON.stringify(payload) }),
|
||
updateKnowledge: (id: number, payload: Record<string, unknown>) =>
|
||
request<KnowledgeItem>(`/admin/knowledge/${id}`, { method: "PUT", body: JSON.stringify(payload) }),
|
||
updateKnowledgeMetadata: (id: number, payload: { name: string; knowledgeType: string; reviewMode: string }) =>
|
||
request<KnowledgeItem>(`/admin/knowledge/${id}/metadata`, { method: "PATCH", body: JSON.stringify(payload) }),
|
||
batchUpdateKnowledgeMetadata: (payload: { knowledgeIds: number[]; knowledgeType?: string; reviewMode?: string }) =>
|
||
request<KnowledgeBatchUpdateResult>("/admin/knowledge/batch/metadata", { method: "PATCH", body: JSON.stringify(payload) }),
|
||
deleteKnowledge: (id: number) => request<null>(`/admin/knowledge/${id}`, { method: "DELETE" }),
|
||
importKnowledgeFromSpace: (body: { node_token: string }) =>
|
||
request<{ imported: number; skipped: number; id: number; name: string }>(
|
||
"/admin/knowledge/import",
|
||
{ method: "POST", body: JSON.stringify(body) },
|
||
),
|
||
knowledgeDetail: (id: number) => request<KnowledgeDetail>(`/admin/knowledge/${id}/detail`),
|
||
knowledgeVersions: (id: number) => request<KnowledgeVersion[]>(`/admin/knowledge/${id}/versions`),
|
||
knowledgeVersionContent: (versionId: number) => request<KnowledgeVersionContent>(`/admin/knowledge/version/${versionId}/content`),
|
||
knowledgeSyncJobs: (id: number) => request<KnowledgeSyncJob[]>(`/admin/knowledge/${id}/sync-jobs`),
|
||
syncKnowledge: (id: number, mode: "review" | "replace") =>
|
||
request<KnowledgeSyncJob>(`/admin/knowledge/${id}/sync`, { method: "POST", body: JSON.stringify({ mode }) }),
|
||
confirmKnowledgeManifest: (id: number, versionId: number) =>
|
||
request<null>(`/admin/knowledge/${id}/manifest/${versionId}/confirm`, { method: "POST", body: "{}" }),
|
||
reviewKnowledgeVersion: (id: number, versionId: number, action: "approve" | "reject", reason?: string) =>
|
||
request<null>(`/admin/knowledge/${id}/version/${versionId}/review`, { method: "POST", body: JSON.stringify({ action, reason }) }),
|
||
publishKnowledgeVersion: (id: number, versionId: number, keepOpen: boolean) =>
|
||
request<KnowledgeItem>(`/admin/knowledge/${id}/version/${versionId}/publish`, { method: "POST", body: JSON.stringify({ keepOpen }) }),
|
||
rollbackKnowledgeVersion: (id: number, versionId: number, keepOpen: boolean) =>
|
||
request<KnowledgeItem>(`/admin/knowledge/${id}/version/${versionId}/rollback`, { method: "POST", body: JSON.stringify({ keepOpen }) }),
|
||
setKnowledgeOpen: (id: number, open: boolean) =>
|
||
request<KnowledgeItem>(`/admin/knowledge/${id}/open-status`, { method: "PUT", body: JSON.stringify({ open }) }),
|
||
setKnowledgeLifecycle: (id: number, status: "active" | "archived") =>
|
||
request<KnowledgeItem>(`/admin/knowledge/${id}/lifecycle`, { method: "PUT", body: JSON.stringify({ status }) }),
|
||
migrationSummary: () => request<MigrationSummary>("/admin/knowledge-migration/summary"),
|
||
runKnowledgeMigration: (knowledgeId?: number) =>
|
||
request<{ total: number; success: number; results: Record<string, unknown>[] }>(`/admin/knowledge-migration/run${knowledgeId ? `?knowledgeId=${knowledgeId}` : ""}`, { method: "POST", body: "{}" }),
|
||
prompt: () => request<PromptDetail>("/admin/prompt"),
|
||
savePrompt: (promptContent: string) =>
|
||
request<PromptDetail>("/admin/prompt", { method: "PUT", body: JSON.stringify({ promptContent }) }),
|
||
resetPrompt: () => request<PromptDetail>("/admin/prompt/reset", { method: "POST", body: "{}" }),
|
||
promptHistory: (query: { page?: number; pageSize?: number } = {}) =>
|
||
request<PageResult<PromptHistoryItem>>(`/admin/prompt/history${queryString(query)}`),
|
||
promptHistoryDetail: (id: number) => request<PromptDetail>(`/admin/prompt/history/${id}`),
|
||
restorePrompt: (id: number) => request<PromptDetail>(`/admin/prompt/history/${id}/restore`, { method: "POST", body: "{}" }),
|
||
debugAgent: (payload: Record<string, unknown>) =>
|
||
request<AgentDebugResult>("/admin/agent/debug", { method: "POST", body: JSON.stringify(payload) }),
|
||
agentRuntimeConfig: () => request<AgentRuntimeConfig>("/admin/agent/runtime-config"),
|
||
saveAgentRuntimeConfig: (payload: AgentGenerationConfig) =>
|
||
request<AgentRuntimeConfig>("/admin/agent/runtime-config", { method: "PUT", body: JSON.stringify(payload) }),
|
||
models: () => request<ModelItem[]>("/admin/model/list"),
|
||
createModel: (payload: Record<string, unknown>) =>
|
||
request<ModelItem>("/admin/model", { method: "POST", body: JSON.stringify(payload) }),
|
||
updateModel: (id: number, payload: Record<string, unknown>) =>
|
||
request<ModelItem>(`/admin/model/${id}`, { method: "PUT", body: JSON.stringify(payload) }),
|
||
enableModel: (modelId: number) =>
|
||
request<null>("/admin/model/enable", { method: "POST", body: JSON.stringify({ modelId }) }),
|
||
deleteModel: (modelId: number) =>
|
||
request<null>(`/admin/model/${modelId}`, { method: "DELETE" }),
|
||
testModel: (modelId: number) =>
|
||
request<{ ok: boolean; message: string; answer: string }>(`/admin/model/${modelId}/test`, {
|
||
method: "POST",
|
||
body: "{}",
|
||
}),
|
||
configs: () => request<SystemConfigItem[]>("/admin/config"),
|
||
saveConfig: (payload: Record<string, unknown>) =>
|
||
request<SystemConfigItem>("/admin/config", { method: "PUT", body: JSON.stringify(payload) }),
|
||
chats: (query: ChatRecordQuery = {}) => request<PageResult<ChatRecord>>(`/admin/chat/list${queryString(query)}`),
|
||
chatDetail: (sessionId: number) => request<ChatDetail>(`/admin/chat/${sessionId}`),
|
||
exportChats: (query: ChatRecordQuery = {}) => download(`/admin/chat/export${queryString(query)}`, "chat_records.csv"),
|
||
aiLogs: (query: { sessionId?: number; userId?: number; status?: string; page?: number; pageSize?: number } = {}) =>
|
||
request<PageResult<AiLogRecord>>(`/admin/ai-log/list${queryString(query)}`),
|
||
aiLogDetail: (id: number) => request<AiLogRecord>(`/admin/ai-log/${id}`),
|
||
operationLogs: (query: { module?: string; page?: number; pageSize?: number } = {}) => request<PageResult<Record<string, unknown>>>(`/admin/log/list${queryString(query)}`),
|
||
retrievalLogs: (query: { page?: number; pageSize?: number } = {}) => request<PageResult<RetrievalLogItem>>(`/admin/retrieval-log/list${queryString(query)}`),
|
||
estimateRetrievalCleanup: (before: string) => request<{ before: string; estimatedCount: number }>("/admin/retrieval-log/cleanup/estimate", { method: "POST", body: JSON.stringify({ before }) }),
|
||
cleanupRetrievalLogs: (before: string) => request<{ before: string; deleted: number }>("/admin/retrieval-log/cleanup", { method: "POST", body: JSON.stringify({ before }) }),
|
||
retrievalRetention: () => request<{ enabled: boolean; retentionDays: number | null; lastRunAt: string | null; lastResult: string | null }>("/admin/retrieval-log/retention"),
|
||
saveRetrievalRetention: (retentionDays: number | null) => request<{ enabled: boolean; retentionDays: number | null }>("/admin/retrieval-log/retention", { method: "PUT", body: JSON.stringify({ retentionDays }) }),
|
||
storageStats: (refresh = false) => request<Record<string, any>>(`/admin/dashboard/storage${refresh ? "?refresh=true" : ""}`),
|
||
retrievalLogDetail: (id: number) => request<Record<string, unknown>>(`/admin/retrieval-log/${id}`),
|
||
attentionList: (query: { page?: number; pageSize?: number } = {}) => request<PageResult<AttentionItem>>(`/admin/attention/list${queryString(query)}`),
|
||
updateAttention: (id: number, status: string, note?: string) =>
|
||
request<AttentionItem>(`/admin/attention/${id}`, { method: "PUT", body: JSON.stringify({ status, note }) }),
|
||
deleteAttention: (id: number) => request<null>(`/admin/attention/${id}`, { method: "DELETE" }),
|
||
clearFeishuCache: () =>
|
||
request<{ cleared: number; message: string }>("/admin/feishu/cache/clear", { method: "POST", body: "{}" }),
|
||
};
|