import type { AdminProfile, AdminUser, AdminUserDetail, AgentDebugResult, AgentDebugStreamComplete, AgentGenerationConfig, AgentRuntimeConfig, AiLogRecord, ApiResponse, ChatDetail, ChatMessageRecord, ChatRecord, ChatRecordQuery, DashboardStats, EntitlementPlan, EntitlementBatchRenewResult, KnowledgeItem, KnowledgeContentSearchItem, KnowledgeDetail, KnowledgeSyncJob, KnowledgeVersionContent, MigrationSummary, KnowledgeBatchSyncResult, KnowledgeBatchUpdateResult, RetrievalLogItem, AttentionItem, ModelItem, SystemConfigItem, UserImportResult, UserEntitlementSummary, PageResult, PeriodicReportRecord, PromptDetail, PromptHistoryItem, QuestionInsightRefreshResult, QuestionInsightSummary, TopicSessionRecord, TopicSummaryRecord, } 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(path: string, init: RequestInit = {}): Promise { 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(response); if (!response.ok || body.code !== 0) { throw new Error(body.detail || body.message || `${response.status} ${response.statusText}`); } return body.data; } async function readApiResponse(response: Response): Promise & { detail?: string }> { const text = await response.text(); if (!text.trim()) { throw new Error(response.ok ? "接口返回为空" : `服务暂时不可用(${response.status})`); } try { return JSON.parse(text) as ApiResponse & { 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(path: string, formData: FormData): Promise { 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(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("/admin/logout", { method: "POST", body: "{}" }), profile: () => request("/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(`/admin/dashboard${qs ? "?" + qs : ""}`); }, users: (query: { keyword?: string; planId?: number; entitlementStatus?: string; page?: number; pageSize?: number } = {}) => request>(`/admin/user/list${queryString(query)}`), userDetail: (id: number) => request(`/admin/user/${id}/detail`), userTopics: (id: number, query: { keyword?: string; page?: number; pageSize?: number } = {}) => request>(`/admin/user/${id}/topics${queryString(query)}`), userReports: (id: number, limit = 20) => request(`/admin/user/${id}/reports${queryString({ limit })}`), generateUserReport: (id: number, payload: { reportType: string; periodStart?: string | null; periodEnd?: string | null }) => request(`/admin/user/${id}/reports/generate`, { method: "POST", body: JSON.stringify(payload) }), retryTopicSettlement: (userId: number, topicId: number) => request(`/admin/user/${userId}/topic/${topicId}/settlement/retry`, { method: "POST", body: "{}" }), createUser: (payload: Record) => request("/admin/user", { method: "POST", body: JSON.stringify(payload) }), importUsers: (students: Record[]) => request("/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("/admin/user/import/excel", formData); }, updateUser: (id: number, payload: Record) => request(`/admin/user/${id}`, { method: "PUT", body: JSON.stringify(payload) }), deleteUser: (id: number) => request(`/admin/user/${id}`, { method: "DELETE" }), entitlementPlans: (includeDisabled = true) => request(`/admin/entitlement/plan/list${queryString({ includeDisabled })}`), createEntitlementPlan: (payload: Record) => request("/admin/entitlement/plan", { method: "POST", body: JSON.stringify(payload) }), updateEntitlementPlan: (id: number, payload: Record) => request(`/admin/entitlement/plan/${id}`, { method: "PUT", body: JSON.stringify(payload) }), assignUserEntitlement: (userId: number, payload: { planId: number; effectiveAt?: string | null; expiredAt?: string | null; remark?: string | null }) => request(`/admin/user/${userId}/entitlement`, { method: "POST", body: JSON.stringify(payload) }), renewUserEntitlement: (userId: number, payload: { extensionDays: number; idempotencyKey: string; remark?: string | null }) => request(`/admin/user/${userId}/entitlement/renew`, { method: "POST", body: JSON.stringify(payload) }), batchRenewUserEntitlement: (payload: { userIds: number[]; extensionDays: number; idempotencyKey: string; remark?: string | null }) => request("/admin/user/entitlement/batch-renew", { method: "POST", body: JSON.stringify(payload) }), knowledge: (query: Record = {}) => request>(`/admin/knowledge/list${queryString(query)}`), knowledgeOptions: () => request("/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) => request("/admin/knowledge", { method: "POST", body: JSON.stringify(payload) }), updateKnowledge: (id: number, payload: Record) => request(`/admin/knowledge/${id}`, { method: "PUT", body: JSON.stringify(payload) }), updateKnowledgeMetadata: (id: number, payload: { name: string; knowledgeType: string }) => request(`/admin/knowledge/${id}/metadata`, { method: "PATCH", body: JSON.stringify(payload) }), batchUpdateKnowledgeMetadata: (payload: { knowledgeIds: number[]; knowledgeType?: string }) => request("/admin/knowledge/batch/metadata", { method: "PATCH", body: JSON.stringify(payload) }), batchSyncKnowledge: (knowledgeIds: number[]) => request("/admin/knowledge/batch/sync", { method: "POST", body: JSON.stringify({ knowledgeIds }), }), deleteKnowledge: (id: number) => request(`/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(`/admin/knowledge/${id}/detail`), knowledgeContentSearch: (query: { keyword: string; includeClosed?: boolean; page?: number; pageSize?: number }) => request>(`/admin/knowledge/content-search${queryString(query)}`), knowledgeVersionContent: (versionId: number) => request(`/admin/knowledge/version/${versionId}/content`), knowledgeSyncJobs: (id: number) => request(`/admin/knowledge/${id}/sync-jobs`), syncKnowledge: (id: number) => request(`/admin/knowledge/${id}/sync`, { method: "POST" }), setKnowledgeOpen: (id: number, open: boolean) => request(`/admin/knowledge/${id}/open-status`, { method: "PUT", body: JSON.stringify({ open }) }), setKnowledgeLifecycle: (id: number, status: "active" | "archived") => request(`/admin/knowledge/${id}/lifecycle`, { method: "PUT", body: JSON.stringify({ status }) }), migrationSummary: () => request("/admin/knowledge-migration/summary"), runKnowledgeMigration: (knowledgeId?: number) => request<{ total: number; success: number; results: Record[] }>(`/admin/knowledge-migration/run${knowledgeId ? `?knowledgeId=${knowledgeId}` : ""}`, { method: "POST", body: "{}" }), prompt: () => request("/admin/prompt"), savePrompt: (promptContent: string) => request("/admin/prompt", { method: "PUT", body: JSON.stringify({ promptContent }) }), resetPrompt: () => request("/admin/prompt/reset", { method: "POST", body: "{}" }), promptHistory: (query: { page?: number; pageSize?: number } = {}) => request>(`/admin/prompt/history${queryString(query)}`), promptHistoryDetail: (id: number) => request(`/admin/prompt/history/${id}`), restorePrompt: (id: number) => request(`/admin/prompt/history/${id}/restore`, { method: "POST", body: "{}" }), debugAgent: (payload: Record) => request("/admin/agent/debug", { method: "POST", body: JSON.stringify(payload) }), agentRuntimeConfig: () => request("/admin/agent/runtime-config"), saveAgentRuntimeConfig: (payload: AgentGenerationConfig) => request("/admin/agent/runtime-config", { method: "PUT", body: JSON.stringify(payload) }), models: () => request("/admin/model/list"), createModel: (payload: Record) => request("/admin/model", { method: "POST", body: JSON.stringify(payload) }), updateModel: (id: number, payload: Record) => request(`/admin/model/${id}`, { method: "PUT", body: JSON.stringify(payload) }), setModelAvailability: (modelId: number, enabled: number) => request("/admin/model/enable", { method: "POST", body: JSON.stringify({ modelId, enabled }) }), setDefaultModel: (modelId: number) => request("/admin/model/default", { method: "POST", body: JSON.stringify({ modelId }) }), deleteModel: (modelId: number) => request(`/admin/model/${modelId}`, { method: "DELETE" }), testModel: (modelId: number) => request<{ ok: boolean; message: string; answer: string }>(`/admin/model/${modelId}/test`, { method: "POST", body: "{}", }), configs: () => request("/admin/config"), saveConfig: (payload: Record) => request("/admin/config", { method: "PUT", body: JSON.stringify(payload) }), chats: (query: ChatRecordQuery = {}) => request>(`/admin/chat/list${queryString(query)}`), chatDetail: (sessionId: number, query: { messagePage?: number; messagePageSize?: number } = {}) => request(`/admin/chat/${sessionId}${queryString(query)}`), chatMessages: (sessionId: number, query: { page?: number; pageSize?: number } = {}) => request>(`/admin/chat/${sessionId}/messages${queryString(query)}`), 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>(`/admin/ai-log/list${queryString(query)}`), aiLogDetail: (id: number) => request(`/admin/ai-log/${id}`), operationLogs: (query: { module?: string; page?: number; pageSize?: number } = {}) => request>>(`/admin/log/list${queryString(query)}`), questionInsights: (query: { dateFrom?: string; dateTo?: string; minCount?: number; maxMessages?: number; page?: number; pageSize?: number } = {}) => request(`/admin/question-insights/summary${queryString(query)}`), refreshQuestionInsights: (query: { dateFrom?: string; dateTo?: string; maxMessages?: number } = {}) => request(`/admin/question-insights/refresh${queryString(query)}`, { method: "POST", }), retrievalLogs: (query: { page?: number; pageSize?: number } = {}) => request>(`/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>(`/admin/dashboard/storage${refresh ? "?refresh=true" : ""}`), retrievalLogDetail: (id: number) => request>(`/admin/retrieval-log/${id}`), attentionList: (query: { page?: number; pageSize?: number } = {}) => request>(`/admin/attention/list${queryString(query)}`), updateAttention: (id: number, status: string, note?: string) => request(`/admin/attention/${id}`, { method: "PUT", body: JSON.stringify({ status, note }) }), deleteAttention: (id: number) => request(`/admin/attention/${id}`, { method: "DELETE" }), clearFeishuCache: () => request<{ cleared: number; message: string }>("/admin/feishu/cache/clear", { method: "POST", body: "{}" }), }; export async function streamDebugAgent( payload: Record, onChunk: (chunk: string) => void, onReasoning: (chunk: string) => void, onStatus: (status: { message: string; reasoningVisible: boolean }) => void, onComplete: (result: AgentDebugStreamComplete) => void, signal?: AbortSignal, ) { const token = getToken(); const response = await fetch(`${API_BASE}/admin/agent/debug/stream`, { method: "POST", headers: { "Content-Type": "application/json", ...(token ? { Authorization: `Bearer ${token}` } : {}), }, body: JSON.stringify(payload), signal, }); if (!response.ok || !response.body) { const body = await readApiResponse(response); throw new Error(body.detail || body.message || "Agent 调试连接失败"); } const reader = response.body.getReader(); const decoder = new TextDecoder("utf-8"); let buffer = ""; while (true) { const { done, value } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); const events = buffer.split(/\r?\n\r?\n/); buffer = events.pop() ?? ""; for (const event of events) { const dataLines = event.split(/\r?\n/).filter((line) => line.startsWith("data:")); if (!dataLines.length) continue; const data = dataLines.map((line) => line.slice(5).trimStart()).join("\n").trim(); if (data === "[DONE]") return; const parsed = JSON.parse(data) as AgentDebugStreamComplete & { type?: string; content?: string; reasoningVisible?: boolean; }; if (parsed.type === "error") throw new Error(parsed.message || "Agent 调试失败"); if (parsed.type === "content" && parsed.content) onChunk(parsed.content); if (parsed.type === "reasoning" && parsed.content) onReasoning(parsed.content); if (parsed.type === "status") { onStatus({ message: parsed.message || "思考中", reasoningVisible: Boolean(parsed.reasoningVisible) }); } if (parsed.type === "complete") onComplete(parsed); } } }