/** * 聊天API服务 - 对接新的 oneapi_replace 后端API * 使用 /retrieve 接口进行问答 */ class ChatService { constructor() { // 使用本地开发服务地址 // 生产环境: 'http://101.200.154.78:8081' // 本地开发: 'http://localhost:8000' this.baseURL = 'http://localhost:8000'; // 生成会话ID this.sessionId = this.generateSessionId(); // 存储会话历史(本地管理) this.conversations = new Map(); } /** * 生成会话ID */ generateSessionId() { // 使用UUID格式 return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { var r = Math.random() * 16 | 0; var v = c == 'x' ? r : (r & 0x3 | 0x8); return v.toString(16); }); } /** * 发送API请求的通用方法 */ async request(endpoint, options = {}) { // 保留token处理,用于其他需要认证的服务 const token = window.TokenManager ? window.TokenManager.getToken() : null; const defaultOptions = { headers: { 'Content-Type': 'application/json', // 新后端不需要Authorization header,但保留以备其他服务使用 // 'Authorization': token ? `Bearer ${token}` : '' } }; const config = { ...defaultOptions, ...options, headers: { ...defaultOptions.headers, ...options.headers } }; try { const response = await fetch(`${this.baseURL}${endpoint}`, config); // 处理空响应(如204 No Content) if (response.status === 204) { return { success: true }; } const data = await response.json(); if (!response.ok) { // 保留401错误处理,用于其他需要认证的服务 if (response.status === 401 && window.TokenManager) { window.TokenManager.clearAll(); window.location.href = '/auth/pages/login.html'; throw new Error('认证失败,请重新登录'); } throw new Error(data.message || data.detail || `请求失败: ${response.status}`); } return data; } catch (error) { console.error('Chat API request error:', error); throw error; } } /** * 发送消息(同步方式) * @param {object} messageData - 消息数据 * @returns {Promise} API响应 */ async sendMessage(messageData) { try { // 转换为新后端的请求格式 const isDeepResearch = messageData.mode === 'research' || messageData.deepResearch; // 生产环境使用 /query 接口,参数更简单 const response = await this.request('/query', { method: 'POST', body: JSON.stringify({ query: messageData.message, mode: isDeepResearch ? 'complex' : '0', // 自动判断或复杂模式 save_output: false // 不保存到文件 }) }); // 保存到本地会话历史 const conversationId = messageData.conversationId || this.sessionId; if (!this.conversations.has(conversationId)) { this.conversations.set(conversationId, []); } const userMessage = { role: 'user', content: messageData.message, timestamp: new Date().toISOString() }; const assistantMessage = { role: 'assistant', content: response.answer, timestamp: new Date().toISOString(), metadata: response.metadata }; this.conversations.get(conversationId).push(userMessage, assistantMessage); // 转换响应格式以适配前端 return { code: 200, data: { conversationId: conversationId, messageId: Date.now().toString(), content: response.answer, reference: response.supporting_facts, events: response.supporting_events, metadata: response.metadata }, message: '成功' }; } catch (error) { console.error('Send message failed:', error); throw error; } } /** * 发送消息(真正的流式方式) * @param {object} messageData - 消息数据 * @param {function} onMessage - 消息回调函数 * @param {function} onComplete - 完成回调函数 * @param {function} onError - 错误回调函数 * @param {function} onThinking - 思考过程回调函数 * @param {function} onStatus - 状态更新回调函数(新增) * @returns {AbortController} 可用于取消请求的控制器 */ sendMessageStream(messageData, onMessage, onComplete, onError, onThinking = null, onStatus = null) { const abortController = new AbortController(); // 保存原始的abort方法 const originalAbort = abortController.abort.bind(abortController); // 启动异步流处理(但立即返回AbortController) this._startStream(abortController, originalAbort, messageData, onMessage, onComplete, onError, onThinking, onStatus); // 立即返回AbortController,不等待异步处理 return abortController; } async _startStream(abortController, originalAbort, messageData, onMessage, onComplete, onError, onThinking, onStatus) { // 检查是否有流式端点 const streamEndpoint = '/retrieve/stream'; const hasStreamSupport = await this.checkStreamSupport(); if (!hasStreamSupport) { // 降级到原有的伪流式实现 return this.sendMessageStreamLegacy(messageData, onMessage, onComplete, onError, onThinking); } try { const isDeepResearch = messageData.deepResearch || false; // 构建请求body const requestBody = { query: messageData.message, mode: isDeepResearch ? 'complex' : '0', use_cache: true, save_output: false }; // 使用fetch处理SSE const response = await fetch(`${this.baseURL}${streamEndpoint}`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': 'text/event-stream' }, body: JSON.stringify(requestBody), signal: abortController.signal }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } // 读取流式响应 const reader = response.body.getReader(); const decoder = new TextDecoder(); let buffer = ''; let accumulatedAnswer = ''; let supportingFacts = []; let supportingEvents = []; let metadata = {}; let taskId = null; // 任务ID while (true) { const { done, value } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); const lines = buffer.split('\n'); buffer = lines.pop(); // 保留未完成的行 for (const line of lines) { if (line.startsWith('data: ')) { const data = line.slice(6); if (data === '[DONE]') { continue; } try { const event = JSON.parse(data); switch (event.type) { case 'task_created': // 保存任务ID taskId = event.task_id; console.log('[ChatService] Task created:', taskId); // 重写abort方法,添加取消任务的逻辑 abortController.abort = () => { // 发送取消请求到后端 if (taskId) { fetch(`${this.baseURL}/task/cancel/${taskId}`, { method: 'POST' }).catch(err => { console.log('[ChatService] Cancel request failed:', err); }); } // 执行原始的abort originalAbort(); }; break; case 'cache_hit': if (onStatus) { onStatus({ type: 'cache', message: event.message, cached: true }); } break; case 'cache_miss': if (onStatus) { onStatus({ type: 'cache', message: event.message, cached: false }); } break; case 'starting': case 'complexity_check': case 'documents': case 'sufficiency_check': case 'sub_queries': case 'iteration': case 'generating': if (onStatus) { onStatus(event); } if (onThinking && event.message) { onThinking(event.message + '\n'); } break; case 'answer_chunk': const chunk = event.data?.text || ''; accumulatedAnswer += chunk; if (onMessage) { onMessage(chunk); } break; case 'supporting_info': supportingFacts = event.data?.supporting_facts || []; supportingEvents = event.data?.supporting_events || []; break; case 'answer': // rag_api_server_production.py 使用这个类型 // 处理最终答案 if (event.data?.content) { accumulatedAnswer = event.data.content; } supportingFacts = event.data?.supporting_facts || supportingFacts; supportingEvents = event.data?.supporting_events || supportingEvents; metadata = event.data || {}; // 触发完成状态 if (onStatus) { onStatus({ type: 'complete', message: event.message || '✅ 答案生成完成' }); } break; case 'complete': const finalAnswer = event.data?.answer || accumulatedAnswer; metadata = event.data || {}; // 触发完成状态 if (onStatus) { onStatus({ type: 'complete', message: '✅ 答案生成完成' }); } break; case 'cached': if (onStatus) { onStatus({ type: 'cached', message: event.message }); } break; case 'cancelled': // 任务被取消 if (onStatus) { onStatus({ type: 'cancelled', message: event.message || '任务已被取消' }); } if (onError) { onError(new Error(event.message || '任务已被取消')); } return abortController; // 提前返回 break; case 'error': throw new Error(event.message || '检索过程出错'); } } catch (e) { console.warn('Failed to parse SSE event:', e); } } } } // 保存到会话历史 const conversationId = messageData.conversationId || this.sessionId; if (!this.conversations.has(conversationId)) { this.conversations.set(conversationId, []); } const userMessage = { role: 'user', content: messageData.message, timestamp: new Date().toISOString() }; const assistantMessage = { role: 'assistant', content: accumulatedAnswer, timestamp: new Date().toISOString(), metadata: metadata, supporting_facts: supportingFacts, supporting_events: supportingEvents }; this.conversations.get(conversationId).push(userMessage, assistantMessage); // 完成回调 const result = { conversationId: conversationId, messageId: Date.now().toString(), content: accumulatedAnswer, reference: supportingFacts, events: supportingEvents, metadata: metadata }; if (onComplete) { onComplete(result); } } catch (error) { if (error.name === 'AbortError') { console.log('Stream aborted'); } else { console.error('Stream error:', error); onError(error); } } } /** * 原有的伪流式实现(作为降级方案) */ async sendMessageStreamLegacy(messageData, onMessage, onComplete, onError, onThinking = null) { const abortController = new AbortController(); try { const isDeepResearch = messageData.deepResearch || false; const showThinking = messageData.showThinking || false; if (showThinking && onThinking) { onThinking('正在分析问题复杂度...\n'); } const response = await this.request('/retrieve', { method: 'POST', signal: abortController.signal, body: JSON.stringify({ query: messageData.message, mode: isDeepResearch ? 'complex' : '0', save_output: false }) }); // 显示思考过程 if (showThinking && onThinking && response.metadata) { const metadata = response.metadata; let thinkingText = ''; if (metadata.complexity_level) { thinkingText += `**问题复杂度**: ${metadata.complexity_level}\n`; thinkingText += `**置信度**: ${(metadata.complexity_confidence * 100).toFixed(1)}%\n`; } if (thinkingText) { onThinking(thinkingText); } } // 保存到会话历史 const conversationId = messageData.conversationId || this.sessionId; if (!this.conversations.has(conversationId)) { this.conversations.set(conversationId, []); } this.conversations.get(conversationId).push( { role: 'user', content: messageData.message, timestamp: new Date().toISOString() }, { role: 'assistant', content: response.answer, timestamp: new Date().toISOString() } ); // 模拟流式输出 const chunks = this.splitIntoChunks(response.answer, 20); for (const chunk of chunks) { if (abortController.signal.aborted) break; onMessage(chunk); await new Promise(resolve => setTimeout(resolve, 30)); } onComplete({ conversationId: conversationId, messageId: Date.now().toString(), content: response.answer, reference: response.supporting_facts, events: response.supporting_events, metadata: response.metadata }); } catch (error) { if (error.name !== 'AbortError') { console.error('Legacy stream error:', error); onError(error); } } return abortController; } /** * 检查是否支持流式端点 */ async checkStreamSupport() { try { // 直接返回true,因为我们知道端点存在 // 避免OPTIONS请求导致的405错误 return true; } catch (error) { return false; } } /** * 将文本分割成小块以模拟流式输出 */ splitIntoChunks(text, chunkSize = 20) { const chunks = []; for (let i = 0; i < text.length; i += chunkSize) { chunks.push(text.slice(i, i + chunkSize)); } return chunks; } /** * 获取用户的会话列表 * @returns {Promise} API响应 */ async getConversations() { // 从localStorage和内存中获取会话列表 try { const conversations = []; const chatManager = window.chatManager; // 从chatManager获取 if (chatManager && chatManager.chats) { for (const [id, chat] of chatManager.chats) { conversations.push({ id: id, title: chat.title || '新对话', createdAt: chat.createdAt || new Date().toISOString(), updatedAt: chat.updatedAt || new Date().toISOString() }); } } // 也包含当前服务中的会话 for (const [id, messages] of this.conversations) { if (!conversations.find(c => c.id === id)) { const firstMessage = messages.find(m => m.role === 'user'); conversations.push({ id: id, title: firstMessage ? firstMessage.content.substring(0, 30) + '...' : '新对话', createdAt: messages[0]?.timestamp || new Date().toISOString(), updatedAt: messages[messages.length - 1]?.timestamp || new Date().toISOString() }); } } return { success: true, conversations: conversations }; } catch (error) { console.error('Get conversations failed:', error); return { success: true, conversations: [] }; } } /** * 获取会话的消息历史 * @param {string|number} conversationId - 会话ID * @returns {Promise} API响应 */ async getConversationMessages(conversationId) { // 从localStorage和内存中获取消息历史 try { // 首先尝试从内存中获取 if (this.conversations.has(conversationId)) { const messages = this.conversations.get(conversationId); return { success: true, messages: messages.map((msg, index) => ({ id: msg.id || index.toString(), role: msg.role, content: msg.content, timestamp: msg.timestamp })) }; } // 然后尝试从chatManager获取 const chatManager = window.chatManager; if (chatManager && chatManager.chats && chatManager.chats.has(conversationId)) { const chat = chatManager.chats.get(conversationId); const messages = chat.messages || []; return { success: true, messages: messages.map((msg, index) => ({ id: msg.id || index.toString(), role: msg.role, content: msg.content, timestamp: msg.timestamp })) }; } return { success: true, messages: [] }; } catch (error) { console.error('Get conversation messages failed:', error); return { success: true, messages: [] }; } } /** * 创建新会话(通过发送第一条消息) * @param {string} message - 第一条消息 * @param {string} mode - 聊天模式 * @returns {Promise} API响应 */ async createConversation(message, mode = 'chat') { // 生成新的会话ID const newSessionId = this.generateSessionId(); // 使用新会话ID发送消息 return this.sendMessage({ message: message, conversationId: newSessionId, mode: mode }); } /** * 删除会话 * @param {string|number} conversationId - 会话ID * @returns {Promise} API响应 */ async deleteConversation(conversationId) { // 从内存中删除 this.conversations.delete(conversationId); // 如果chatManager存在,也从中删除 if (window.chatManager && window.chatManager.chats) { window.chatManager.chats.delete(conversationId); } return { success: true, message: '会话已删除' }; } /** * 检查服务状态 * @returns {Promise} 服务是否可用 */ async checkServiceStatus() { try { // 新后端使用 /health 接口进行健康检查 const response = await fetch(`${this.baseURL}/health`); return response.ok; } catch (error) { console.error('Service check failed:', error); return false; } } /** * 清除会话历史 * @param {string} sessionId - 会话ID * @returns {Promise} API响应 */ async clearConversation(sessionId) { try { // 清除本地会话历史 if (this.conversations.has(sessionId)) { this.conversations.set(sessionId, []); } return { success: true, message: '会话已清除' }; } catch (error) { console.error('Clear conversation failed:', error); throw error; } } } // 创建并导出服务实例 try { window.ChatAPIService = new ChatService(); console.log('ChatAPIService 创建成功 (新后端):', window.ChatAPIService); console.log('后端地址:', window.ChatAPIService.baseURL); } catch (error) { console.error('ChatAPIService 创建失败:', error); }