import { z } from "zod"; import type { CourseModule, Book, Chapter, BookStructure, User, ForumCategory, ForumPostSummary, ForumPostDetail, ForumReply, } from "@/types"; // API基础配置 const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || (typeof window !== 'undefined' ? `${window.location.protocol}//${window.location.host}/api` : "http://127.0.0.1:8000"); // 请求拦截器 async function apiRequest( endpoint: string, options: RequestInit = {} ): Promise { const url = `${API_BASE_URL}${endpoint}`; const defaultHeaders: Record = { "Content-Type": "application/json", }; // 添加认证头 const token = localStorage.getItem("auth_token"); if (token) { defaultHeaders.Authorization = `Bearer ${token}`; } const config: RequestInit = { ...options, headers: { ...defaultHeaders, ...options.headers, }, }; try { const response = await fetch(url, config); if (!response.ok) { if (response.status === 401) { localStorage.removeItem("auth_token"); if (typeof window !== 'undefined' && !window.location.pathname.includes('/login') && !window.location.pathname.includes('/register')) { window.location.replace("/login"); } throw new Error("认证已过期,请重新登录"); } const errorData = await response.json().catch(() => ({})); throw new Error(errorData.detail || `HTTP ${response.status}`); } return await response.json(); } catch (error) { throw error; } } // 认证相关API export const authAPI = { async login(username: string, password: string) { return apiRequest<{ access_token: string; token_type: string; }>("/auth/login", { method: "POST", body: JSON.stringify({ username, password }), }); }, async register(userData: { username: string; email: string; password: string; full_name?: string; }) { return apiRequest("/auth/register", { method: "POST", body: JSON.stringify(userData), }); }, async getCurrentUser() { return apiRequest("/auth/me"); }, async updateUserInfo(data: { email?: string; full_name?: string }) { return apiRequest("/auth/me", { method: "PUT", body: JSON.stringify(data), }); }, async changePassword(data: { old_password: string; new_password: string }) { return apiRequest<{ message: string }>("/auth/change-password", { method: "POST", body: JSON.stringify(data), }); }, async verifyEmail(token: string) { return apiRequest<{ success: boolean; message: string; user?: User; }>("/auth/verify-email", { method: "POST", body: JSON.stringify({ token }), }); }, }; // 图像生成相关API export const imageAPI = { // 文生图 API async textToImage(request: { prompt: string; model?: string; template?: string; style?: string; size?: string; num_images?: number; }) { return apiRequest<{ images: Array<{ id: string; url: string; prompt: string; model: string; metadata: any; }>; total: number; }>("/image/text-to-image", { method: "POST", body: JSON.stringify(request), }); }, // 图生图 - 图像编辑 async imageEdit( image: File, prompt: string, mode: string = "optimize", mask?: File, strength: number = 0.8 ) { const formData = new FormData(); formData.append("image", image); formData.append("prompt", prompt); formData.append("mode", mode); formData.append("strength", strength.toString()); if (mask) { formData.append("mask", mask); } const token = localStorage.getItem("auth_token"); const response = await fetch(`${API_BASE_URL}/image/image-to-image/edit`, { method: "POST", headers: { ...(token && { Authorization: `Bearer ${token}` }), }, body: formData, }); if (!response.ok) { const errorData = await response.json().catch(() => ({})); throw new Error(errorData.detail || `HTTP ${response.status}`); } return await response.json(); }, // 图生图 - 生成变体 async imageVariations(image: File, num_variations: number = 3) { const formData = new FormData(); formData.append("image", image); formData.append("num_variations", num_variations.toString()); const token = localStorage.getItem("auth_token"); const response = await fetch(`${API_BASE_URL}/image/image-to-image/variations`, { method: "POST", headers: { ...(token && { Authorization: `Bearer ${token}` }), }, body: formData, }); if (!response.ok) { const errorData = await response.json().catch(() => ({})); throw new Error(errorData.detail || `HTTP ${response.status}`); } return await response.json(); }, // 图生图 - 风格转换 async styleTransfer(image: File, style_prompt: string, strength: number = 0.8) { const formData = new FormData(); formData.append("image", image); formData.append("style_prompt", style_prompt); formData.append("strength", strength.toString()); const token = localStorage.getItem("auth_token"); const response = await fetch(`${API_BASE_URL}/image/image-to-image/style-transfer`, { method: "POST", headers: { ...(token && { Authorization: `Bearer ${token}` }), }, body: formData, }); if (!response.ok) { const errorData = await response.json().catch(() => ({})); throw new Error(errorData.detail || `HTTP ${response.status}`); } return await response.json(); }, // 图生图 - 图像优化 async optimizeImage( image: File, optimization_prompt: string = "优化图像质量,增强细节,提高清晰度", strength: number = 0.6 ) { const formData = new FormData(); formData.append("image", image); formData.append("optimization_prompt", optimization_prompt); formData.append("strength", strength.toString()); const token = localStorage.getItem("auth_token"); const response = await fetch(`${API_BASE_URL}/image/image-to-image/optimize`, { method: "POST", headers: { ...(token && { Authorization: `Bearer ${token}` }), }, body: formData, }); if (!response.ok) { const errorData = await response.json().catch(() => ({})); throw new Error(errorData.detail || `HTTP ${response.status}`); } return await response.json(); }, // 图生图 - 图像扩展 async outpaintImage(image: File, expansion_prompt: string, strength: number = 0.7) { const formData = new FormData(); formData.append("image", image); formData.append("expansion_prompt", expansion_prompt); formData.append("strength", strength.toString()); const token = localStorage.getItem("auth_token"); const response = await fetch(`${API_BASE_URL}/image/image-to-image/outpaint`, { method: "POST", headers: { ...(token && { Authorization: `Bearer ${token}` }), }, body: formData, }); if (!response.ok) { const errorData = await response.json().catch(() => ({})); throw new Error(errorData.detail || `HTTP ${response.status}`); } return await response.json(); }, // 配置信息 API async getModels() { const response = await apiRequest<{ models: Array<{ id: string; name: string; description: string; }>; }>("/image/models", { method: "GET", }); return response.models; }, async getTemplates() { const response = await apiRequest<{ templates: Array<{ id: string; name: string; description: string; }>; }>("/image/templates", { method: "GET", }); return response.templates; }, async getStyles() { const response = await apiRequest<{ styles: Array<{ id: string; name: string; description: string; }>; }>("/image/styles", { method: "GET", }); return response.styles; }, async getSizes() { const response = await apiRequest<{ sizes: Array<{ id: string; name: string; description: string; }>; }>("/image/sizes", { method: "GET", }); return response.sizes; }, async getEditModes() { const response = await apiRequest<{ modes: Array<{ id: string; name: string; description: string; }>; }>("/image/edit-modes", { method: "GET", }); return response.modes; }, async getStylePresets() { const response = await apiRequest<{ presets: Array<{ id: string; name: string; description: string; }>; }>("/image/style-presets", { method: "GET", }); return response.presets; }, // 历史记录 API async getImageHistory(page: number = 1, page_size: number = 20) { return apiRequest<{ images: Array<{ id: string; url: string; prompt: string; style: string; size: string; created_at: string; metadata: any; }>; total: number; page: number; page_size: number; has_next: boolean; has_prev: boolean; }>(`/image/history?page=${page}&page_size=${page_size}`, { method: "GET", }); }, async deleteImage(imageId: string) { return apiRequest<{ message: string }>(`/image/${imageId}`, { method: "DELETE", }); }, }; // 聊天相关API export const chatAPI = { async createSession(title: string = "新对话") { return apiRequest<{ id: number; title: string; created_at: string; updated_at: string; message_count: number; }>("/chat/sessions", { method: "POST", body: JSON.stringify({ title }), }); }, async sendMessage(message: string, sessionId?: number) { return apiRequest<{ answer: string; sources: Array<{ title: string; filename: string; page: number; score: number; preview: string; }>; session_id: number; message_id: number; }>("/chat/send", { method: "POST", body: JSON.stringify({ message, session_id: sessionId }), }); }, async streamMessage( message: string, sessionId: number | undefined, mode: string, knowledgeBaseIds?: string[], signal?: AbortSignal, onChunk?: (chunk: string) => void, onComplete?: (sessionId: number, messageId?: number, userMessageId?: number) => void, onError?: (error: string) => void, onStatus?: (status: string) => void, onThinking?: (step: { step: string; message: string; details?: Record; timestamp?: string }) => void, onThinkingContent?: (content: string) => void, model?: string ) { const url = `${API_BASE_URL}/chat/stream`; const token = localStorage.getItem("auth_token"); const requestBody: any = { message, mode: mode, }; // 添加模型ID if (model) { requestBody.model = model; } // 只在有值时添加 session_id if (sessionId !== undefined && sessionId !== null) { requestBody.session_id = sessionId; } // 只在有值且非空数组时添加 knowledge_base_ids if (knowledgeBaseIds && knowledgeBaseIds.length > 0) { // 智能提取数字ID const numericIds = knowledgeBaseIds .map(id => { // 如果已经是数字字符串,直接转换 if (/^\d+$/.test(id)) { return parseInt(id); } // 如果是 "kb-system-1" 格式,提取末尾数字 const match = id.match(/\d+$/); return match ? parseInt(match[0]) : null; }) .filter(id => id !== null && !isNaN(id)); // 只有在有有效ID时才添加 if (numericIds.length > 0) { requestBody.knowledge_base_ids = numericIds; } } console.log("[DEBUG-CHAT] 发送流式请求:", { url, body: requestBody, bodyJSON: JSON.stringify(requestBody), headers: { "Content-Type": "application/json", ...(token && { Authorization: `Bearer ${token}` }), } }); console.log("[DEBUG-CHAT] 请求详情:", { message: message, sessionId: sessionId, mode: mode, knowledgeBaseIds: knowledgeBaseIds, model: model, hasToken: !!token }); let response; try { response = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json", ...(token && { Authorization: `Bearer ${token}` }), }, body: JSON.stringify(requestBody), signal, // 新增:传递 signal }); } catch (fetchError) { // 检查是否是用户中止 if (fetchError instanceof Error && fetchError.name === 'AbortError') { console.log("[DEBUG-STOP] 用户中止了请求"); onError?.("已停止生成"); return; } console.error("❌ Fetch failed:", fetchError); onError?.(`Network error: ${fetchError}`); return; } console.log("[DEBUG-STREAM] 流式响应:", { status: response.status, statusText: response.statusText, ok: response.ok, headers: Object.fromEntries(response.headers.entries()) }); if (!response.ok) { console.error("❌ Chat stream failed:", { status: response.status, statusText: response.statusText, url: response.url, ok: response.ok }); try { const errorData = await response.json(); console.error("❌ Error response body:", errorData); onError?.(`HTTP ${response.status}: ${errorData.detail || errorData.message || response.statusText}`); } catch (e) { console.error("❌ Could not parse error response:", e); onError?.(`HTTP ${response.status}: ${response.statusText}`); } return; } const reader = response.body?.getReader(); if (!reader) { onError?.("无法读取响应流"); return; } const decoder = new TextDecoder(); let buffer = ""; // Buffer for incomplete lines try { while (true) { const { done, value } = await reader.read(); if (done) break; const chunk = decoder.decode(value, { stream: true }); buffer += chunk; // Accumulate data const lines = buffer.split("\n"); // Keep the last incomplete line in buffer buffer = lines.pop() || ""; for (const line of lines) { if (line.startsWith("data: ")) { try { const data = JSON.parse(line.slice(6)); if (data.type === "status") { console.log("[DEBUG-STREAM] 状态更新:", data.message); onStatus?.(data.message); } else if (data.type === "thinking") { console.log("[DEBUG-STREAM] 思考中:", data.message); // 将thinking数据作为JSON字符串传递给onChunk,让Zustand store处理 onChunk?.(JSON.stringify(data)); } else if (data.type === "thinking_content") { console.log("[DEBUG-STREAM] 思考内容:", data.content); onThinkingContent?.(data.content); } else if (data.type === "chunk") { console.log("[DEBUG-STREAM] 接收chunk:", data.content); onChunk?.(data.content); } else if (data.type === "sources") { console.log("[DEBUG-STREAM] 收到sources:", data.sources?.length, "个来源"); onChunk?.(JSON.stringify(data)); } else if (data.type === "done") { console.log("[DEBUG-STREAM] 流式完成, session_id:", data.session_id, "message_id:", data.message_id, "user_message_id:", data.user_message_id); onComplete?.(data.session_id, data.message_id, data.user_message_id); } else if (data.error) { console.log("[DEBUG-STREAM] 流式错误:", data.error); onError?.(data.error); } } catch (e) { // 忽略解析错误 console.warn("[DEBUG-STREAM] 解析SSE数据失败:", line, e); } } } } } catch (error) { // 检查是否是用户中止 if (error instanceof Error && error.name === 'AbortError') { console.log("[DEBUG-STOP] 读取流时被中止"); onError?.("已停止生成"); } else { onError?.(`流式请求失败: ${error}`); } } finally { reader.releaseLock(); } }, async getSessions() { return apiRequest>("/chat/sessions"); }, async getSessionMessages(sessionId: number) { return apiRequest>(`/chat/sessions/${sessionId}/messages`); }, // 会话管理 API async renameSession(sessionId: number, title: string) { return apiRequest<{ message: string; title: string }>(`/chat/sessions/${sessionId}`, { method: "PUT", body: JSON.stringify({ title }), }); }, async deleteSession(sessionId: number) { return apiRequest<{ message: string }>(`/chat/sessions/${sessionId}`, { method: "DELETE", }); }, async exportSession(sessionId: number, format: string = "json") { return apiRequest<{ session_id: number; title: string; messages: Array<{ id: number; role: string; content: string; created_at: string; feedback?: string; edited: boolean; }>; export_format: string; created_at: string; }>(`/chat/sessions/${sessionId}/export?format=${format}`, { method: "POST", }); }, // 消息操作 API async editMessage(messageId: number, content: string) { return apiRequest<{ message: string }>(`/chat/messages/${messageId}`, { method: "PUT", body: JSON.stringify({ content }), }); }, async regenerateMessage(messageId: number, model?: string) { return apiRequest<{ message: string; new_message_id: number; content: string; }>(`/chat/messages/${messageId}/regenerate`, { method: "POST", body: JSON.stringify({ model: model || null }), }); }, async feedbackMessage(messageId: number, feedback: "like" | "dislike") { return apiRequest<{ message: string; feedback: string }>(`/chat/messages/${messageId}/feedback`, { method: "POST", body: JSON.stringify({ feedback }), }); }, }; // 文档相关API export const documentAPI = { /** * @deprecated 此方法已废弃,请使用 knowledgeBaseAPI.uploadDocument() */ async uploadDocument(file: File) { throw new Error("此方法已废弃,请使用知识库上传接口:knowledgeBaseAPI.uploadDocument()"); }, async getDocuments() { return apiRequest>("/documents"); }, }; // 知识库相关API export const knowledgeBaseAPI = { // 获取用户的所有知识库 async getKnowledgeBases() { return apiRequest>("/knowledge-bases"); }, // 创建新知识库 async createKnowledgeBase(data: { name: string; description?: string; }) { return apiRequest<{ id: number; name: string; description?: string; user_id: number; document_count: number; created_at: string; updated_at: string; }>("/knowledge-bases", { method: "POST", body: JSON.stringify(data), }); }, // 获取知识库详情(包括文档列表) async getKnowledgeBase(id: string) { return apiRequest<{ id: number; name: string; description?: string; user_id: number; document_count: number; created_at: string; updated_at: string; documents: Array<{ id: number; filename: string; title: string; file_size: number; file_type: string; is_processed: boolean; is_public: boolean; knowledge_base_id: number; created_at: string; updated_at: string; }>; }>(`/knowledge-bases/${id}`); }, // 更新知识库信息 async updateKnowledgeBase(id: string, data: { name?: string; description?: string; }) { return apiRequest<{ id: number; name: string; description?: string; user_id: number; document_count: number; created_at: string; updated_at: string; }>(`/knowledge-bases/${id}`, { method: "PUT", body: JSON.stringify(data), }); }, // 删除知识库 async deleteKnowledgeBase(id: string) { return apiRequest(`/knowledge-bases/${id}`, { method: "DELETE", }); }, // 上传文档到指定知识库 async uploadDocument( knowledgeBaseId: string, file: File, title: string, description?: string ) { const formData = new FormData(); formData.append("file", file); formData.append("title", title); if (description) { formData.append("description", description); } const token = localStorage.getItem("auth_token"); const response = await fetch(`${API_BASE_URL}/knowledge-bases/${knowledgeBaseId}/documents`, { method: "POST", headers: { ...(token && { Authorization: `Bearer ${token}` }), }, body: formData, }); if (!response.ok) { const errorData = await response.json().catch(() => ({})); throw new Error(errorData.detail || `HTTP ${response.status}`); } return await response.json(); }, // 获取知识库的文档列表 async getKnowledgeBaseDocuments(knowledgeBaseId: string) { return apiRequest>(`/knowledge-bases/${knowledgeBaseId}/documents`); }, // 删除文档 async deleteDocument(documentId: string) { return apiRequest(`/documents/${documentId}`, { method: "DELETE", }); }, }; // 论坛 API export const forumAPI = { async getCategories() { return apiRequest("/forum/categories"); }, async getPosts(categoryId: number, limit: number = 20) { const params = new URLSearchParams({ limit: String(limit) }); return apiRequest( `/forum/categories/${categoryId}/posts?${params.toString()}` ); }, async createPost(categoryId: number, data: { title: string; content: string }) { return apiRequest(`/forum/categories/${categoryId}/posts`, { method: "POST", body: JSON.stringify(data), }); }, async getPost(postId: number) { return apiRequest(`/forum/posts/${postId}`); }, async createReply(postId: number, content: string) { return apiRequest(`/forum/posts/${postId}/replies`, { method: "POST", body: JSON.stringify({ content }), }); }, }; // 学习分析 API export const analyticsAPI = { async getPlatformStats() { return apiRequest<{ active_users: number; knowledge_documents: number; qa_dialogues: number; generated_images: number; }>("/analytics/platform-stats", { method: "GET", }); }, // 获取用户统计数据 async getStatistics() { return apiRequest<{ total_sessions: number; total_messages: number; total_documents: number; active_days: number; user_since: string; last_login: string; }>("/analytics/statistics", { method: "GET", }); }, // 获取学习报告 async getLearningReport() { return apiRequest<{ user_id: number; total_questions: number; topics_covered: string[]; learning_progress: number; recommendations: string[]; study_time: number; knowledge_gaps: string[]; }>("/analytics/learning-report", { method: "GET", }); }, // 获取学习趋势 async getLearningTrends(days: number = 30) { return apiRequest<{ success: boolean; data: Array<{ date: string; sessions: number; messages: number; }>; }>(`/analytics/trends?days=${days}`, { method: "GET", }); }, // 获取热门问题 async getPopularQuestions(limit: number = 10) { return apiRequest<{ success: boolean; data: Array<{ question: string; count: number; category: string; }>; }>(`/analytics/popular-questions?limit=${limit}`, { method: "GET", }); }, // 获取知识覆盖度 async getKnowledgeCoverage() { return apiRequest<{ success: boolean; data: Array<{ topic: string; coverage: number; questions: number; }>; }>("/analytics/knowledge-coverage", { method: "GET", }); }, // 获取完整分析数据 async getFullAnalytics() { return apiRequest<{ success: boolean; data: { statistics: { total_sessions: number; total_messages: number; total_documents: number; active_days: number; user_since: string; last_login: string; }; learning_trends: Array<{ date: string; sessions: number; messages: number; }>; popular_questions: Array<{ question: string; count: number; category: string; }>; knowledge_coverage: Array<{ topic: string; coverage: number; questions: number; }>; learning_report: { user_id: number; total_questions: number; topics_covered: string[]; learning_progress: number; recommendations: string[]; study_time: number; knowledge_gaps: string[]; }; }; }>("/analytics/full-analytics", { method: "GET", }); }, }; // 课程内容API export const courseContentAPI = { // 获取书籍层级结构(新版本) async getCourseContent(): Promise { return apiRequest("/course-content", { method: "GET", }); }, // 获取章节完整内容 async getChapterContent(chapterId: number): Promise<{ content: string }> { return apiRequest<{ content: string }>(`/course-content/chapters/${chapterId}/content`, { method: "GET", }); }, // 获取节完整内容 async getSectionContent(sectionId: number): Promise<{ content: string }> { return apiRequest<{ content: string }>(`/course-content/sections/${sectionId}/content`, { method: "GET", }); }, // 获取小节完整内容(知识点) async getSubsectionContent(subsectionId: number): Promise<{ content: string }> { return apiRequest<{ content: string }>(`/course-content/subsections/${subsectionId}/content`, { method: "GET", }); }, }; // 书籍相关API export const bookAPI = { // 获取所有书籍列表 async getBooks(): Promise { return apiRequest("/books", { method: "GET", }); }, // 获取书籍章节列表 async getBookChapters(bookId: number): Promise { return apiRequest(`/books/${bookId}/chapters`, { method: "GET", }); }, // 获取PDF文件URL getBookFileUrl(bookId: number): string { return `${API_BASE_URL}/books/${bookId}/file`; }, }; // ===== 后台管理 API ===== export const adminAPI = { // 仪表盘 async getDashboard() { return apiRequest<{ total_users: number; active_users_7d: number; total_sessions: number; total_messages: number; total_documents: number; total_knowledge_bases: number; total_forum_posts: number; total_forum_replies: number; total_generated_images: number; }>("/admin/dashboard"); }, async getUserTrends(days = 30) { return apiRequest<{ date: string; count: number }[]>(`/admin/trends/users?days=${days}`); }, async getMessageTrends(days = 30) { return apiRequest<{ date: string; count: number }[]>(`/admin/trends/messages?days=${days}`); }, // 用户管理 async listUsers(skip = 0, limit = 50) { return apiRequest<{ id: number; username: string; email: string; full_name: string | null; is_active: boolean; is_superuser: boolean; created_at: string | null; last_login: string | null; session_count: number; message_count: number; }[]>(`/admin/users?skip=${skip}&limit=${limit}`); }, async toggleUserActive(userId: number) { return apiRequest<{ success: boolean; is_active: boolean }>(`/admin/users/${userId}/toggle-active`, { method: "PUT" }); }, async toggleUserAdmin(userId: number) { return apiRequest<{ success: boolean; is_superuser: boolean }>(`/admin/users/${userId}/toggle-admin`, { method: "PUT" }); }, async deleteUser(userId: number) { return apiRequest<{ success: boolean }>(`/admin/users/${userId}`, { method: "DELETE" }); }, // 论坛管理 async listForumCategories() { return apiRequest<{ id: number; slug: string; name: string; description: string | null; post_count: number; }[]>("/admin/forum/categories"); }, async createForumCategory(data: { name: string; slug: string; description?: string }) { return apiRequest("/admin/forum/categories", { method: "POST", body: JSON.stringify(data), }); }, async updateForumCategory(categoryId: number, data: { name: string; slug: string; description?: string }) { return apiRequest(`/admin/forum/categories/${categoryId}`, { method: "PUT", body: JSON.stringify(data), }); }, async deleteForumCategory(categoryId: number) { return apiRequest<{ success: boolean }>(`/admin/forum/categories/${categoryId}`, { method: "DELETE" }); }, async listForumPosts(skip = 0, limit = 50) { return apiRequest<{ id: number; title: string; author_name: string; category_name: string; reply_count: number; created_at: string; }[]>(`/admin/forum/posts?skip=${skip}&limit=${limit}`); }, async deleteForumPost(postId: number) { return apiRequest<{ success: boolean }>(`/admin/forum/posts/${postId}`, { method: "DELETE" }); }, // 知识库管理 async listKnowledgeBases() { return apiRequest<{ id: number; name: string; description: string | null; owner_name: string; is_system: boolean; document_count: number; chunk_count: number; created_at: string; }[]>("/admin/knowledge-bases"); }, async deleteKnowledgeBase(kbId: number) { return apiRequest<{ success: boolean }>(`/admin/knowledge-bases/${kbId}`, { method: "DELETE" }); }, // 系统状态 async getSystemStatus() { return apiRequest<{ database: { status: string }; vector_store: { status: string; vector_count: number }; llm_model: string; embedding_model: string; }>("/admin/system/status"); }, };