import Config from './config.js' import http from './http.js' import store from '@/store/index.js' export default { /** * 发送消息到Dify API * @param {string} message 用户消息内容 * @param {Object} options 配置选项 * @returns {Promise} */ async sendMessage(message, options = {}) { try { // 获取AI配置 const aiConfig = store.getters.globalAIKefuConfig // const new_conversationId = await this.generateConversationId() // 构建Dify API请求参数 const params = { url: '/api/kefu/chat', // 后端代理接口 data: { // conversation_id: options.conversationId ?? new_conversationId, user_id: store.state.memberInfo?.id || 'anonymous', stream: false, // Dify API参数 inputs: {}, query: message, response_mode: options.stream ? 'streaming' : 'blocking', user: store.state.memberInfo?.id || 'anonymous' }, header: { 'Content-Type': 'application/json' } } // ✅ 发送请求前打印日志,用于调试 console.log('Sending request to:', params.url); console.log('Request data:', params.data); // 发送请求 const response = await http.sendRequest({ ...params, async: false // 使用Promise方式 }) return this.handleResponse(response, options) } catch (error) { console.error('Dify API请求失败:', error) throw new Error('AI服务暂时不可用,请稍后重试') } }, /** * 流式消息处理 * @param {string} message 用户消息 * @param {Function} onChunk 流式数据回调 * @param {Function} onComplete 完成回调 */ async sendStreamMessage(message, onChunk, onComplete) { try { // 使用HTTP流式请求 return this.sendHttpStream(message, onChunk, onComplete) } catch (error) { console.error('流式消息发送失败:', error) throw error } }, /** * EventSource 流式聊天 * @param {string} message 用户消息 * @param {string} conversationId 会话ID(可选) * @param {Function} onMessage 消息回调 * @param {Function} onComplete 完成回调 * @param {Function} onError 错误回调 * @returns {EventSource|null} EventSource实例 */ chatWithEventSource(message, conversationId = '', onMessage, onComplete, onError) { // 关闭之前的连接 if (window.currentEventSource) { window.currentEventSource.close(); } // 构建请求参数 const params = new URLSearchParams({ uniacid: store.state.uniacid || '1', user_id: store.state.memberInfo?.id || '123456', query: message, conversation_id: conversationId || '', stream: 'true' }); const url = `/api/kefu/chat?${params.toString()}`; try { const eventSource = new EventSource(url); window.currentEventSource = eventSource; let aiMessage = ''; // 监听消息事件 eventSource.addEventListener('message', (event) => { try { const data = JSON.parse(event.data); if (data.event === 'message') { // 更新 AI 消息 aiMessage += data.answer || ''; if (onMessage) onMessage(data.answer || ''); } if (data.event === 'message_end') { // 对话完成 if (onComplete) onComplete({ conversation_id: data.conversation_id, message: aiMessage }); } if (data.conversation_id) { conversationId = data.conversation_id; } } catch (error) { console.error('解析消息失败:', error); } }); // 监听完成事件 eventSource.addEventListener('done', (event) => { try { const data = JSON.parse(event.data); if (onComplete) onComplete(data); } catch (error) { console.error('解析完成事件失败:', error); } }); // 监听关闭事件 eventSource.addEventListener('close', (event) => { try { const data = JSON.parse(event.data); console.log('连接正常结束:', data); } catch (error) { console.error('解析关闭事件失败:', error); } window.currentEventSource = null; }); // 监听错误事件 eventSource.addEventListener('error', (error) => { console.error('EventSource错误:', error); if (onError) onError({ error: 'EventSource连接错误' }); window.currentEventSource = null; }); return eventSource; } catch (error) { console.error('创建EventSource失败:', error); if (onError) onError({ error: error.message }); return null; } }, /** * Fetch API 流式聊天 * @param {string} message 用户消息 * @param {string} conversationId 会话ID(可选) * @param {Function} onMessage 消息回调 * @param {Function} onComplete 完成回调 * @param {Function} onError 错误回调 */ async chatWithFetchStream(message, conversationId = '', onMessage, onComplete, onError) { try { // 构建请求体 - 支持JSON和FormData两种格式 let requestData; let headers = { 'Accept': 'text/event-stream' }; // 优先使用JSON格式 requestData = { uniacid: store.state.uniacid || '1', user_id: store.state.memberInfo?.id || '123456', query: message, conversation_id: conversationId || '', stream: true, inputs: {}, response_mode: 'streaming', user: store.state.memberInfo?.id || '123456' }; headers['Content-Type'] = 'application/json'; const response = await fetch('/api/kefu/chat', { method: 'POST', headers: headers, body: JSON.stringify(requestData) }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } if (!response.body) { throw new Error('响应体不可用'); } const reader = response.body.getReader(); const decoder = new TextDecoder('utf-8'); let buffer = ''; let aiMessage = ''; while (true) { const { done, value } = await reader.read(); if (done) break; // 解码新接收的数据 buffer += decoder.decode(value, { stream: true }); // 处理缓冲的数据,按行分割 let lineEnd; while ((lineEnd = buffer.indexOf('\n')) !== -1) { const line = buffer.substring(0, lineEnd); buffer = buffer.substring(lineEnd + 1); if (line.startsWith('data: ')) { try { const data = JSON.parse(line.substring(6)); if (data.event === 'message' || data.event === 'text_message') { const textContent = data.answer || data.text || ''; aiMessage += textContent; if (onMessage) onMessage(textContent); } else if (data.event === 'message_end' || data.event === 'done') { if (onComplete) onComplete({ conversation_id: data.conversation_id, message: aiMessage, ...data }); } else if (data.event === 'error' && onError) { onError(data); } if (data.conversation_id) { conversationId = data.conversation_id; } } catch (e) { console.warn('解析流式数据失败:', e); } } } } // 处理剩余的缓冲数据 if (buffer.startsWith('data: ')) { try { const data = JSON.parse(buffer.substring(6)); if ((data.event === 'message' || data.event === 'text_message') && onMessage) { const textContent = data.answer || data.text || ''; onMessage(textContent); } else if ((data.event === 'message_end' || data.event === 'done') && onComplete) { onComplete({ conversation_id: data.conversation_id, message: aiMessage, ...data }); } } catch (e) { console.warn('解析剩余数据失败:', e); } } } catch (error) { console.error('Fetch流式聊天请求失败:', error); if (onError) onError({ error: error.message }); } }, /** * HTTP流式请求(现有方法保持不变) */ async sendHttpStream(message, onChunk, onComplete) { const params = { url: '/api/kefu/chat', data: { query: message, conversation_id: '', // 可以在外部传入或保持为空让服务器生成 user_id: store.state.memberInfo?.id || 'anonymous', stream: true, uniacid: store.state.uniacid, // 保留必填参数 inputs: {}, response_mode: 'streaming', user: store.state.memberInfo?.id || 'anonymous' }, header: { 'Content-Type': 'application/json' } } // 使用fetch API进行流式请求(H5环境) // #ifdef H5 try { const url = Config.baseUrl + `/api/kefu/chat`; const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': 'text/event-stream', }, body: JSON.stringify({ uniacid: store.state.uniacid || '1', inputs: {}, query: message, response_mode: 'streaming', user_id: store.state.memberInfo?.id || 'anonymous' }) }) const reader = response.body.getReader() const decoder = new TextDecoder() let content = '' let buffer = '' // 处理流式数据 function processStreamData(buffer, callback) { const lines = buffer.split('\n'); buffer = lines.pop() || ''; // 最后一行可能不完整 lines.forEach(line => { line = line.trim(); if (!line) return; // 解析 SSE 格式 if (line.startsWith('data:')) { const dataPart = line.slice(5).trim(); if (dataPart) { try { const data = JSON.parse(dataPart); if (data.event === 'message') { callback(data.answer || ''); } if (data.conversation_id) { conversationId = data.conversation_id; } if (data.event === 'message_end') { // 对话完成 console.log('对话完成'); } } catch (error) { console.error('解析流式数据失败:', error); } } } }); return buffer; } while (true) { const { done, value } = await reader.read() if (done) break buffer += decoder.decode(value, { stream: true }); // 处理接收到的数据 buffer = processStreamData(buffer, (newData) => { if (newData) { // 更新 AI 消息 content += newData; if (onChunk) onChunk(newData); } }); } if (onComplete) onComplete(content) return content } catch (error) { console.error('HTTP流式请求失败:', error) throw error } // #endif // 非H5环境使用普通请求模拟流式效果 // #ifndef H5 const response = await http.sendRequest({ ...params, async: false }) // 模拟流式效果 if (response.success && response.data) { const content = response.data const chunkSize = 3 let index = 0 const streamInterval = setInterval(() => { if (index < content.length) { const chunk = content.substring(index, index + chunkSize) index += chunkSize if (onChunk) onChunk(chunk) } else { clearInterval(streamInterval) if (onComplete) onComplete(content) } }, 100) } // #endif }, /** * 处理API响应 */ handleResponse(response, options) { if (response.code === 0 || response.success) { return { success: true, content: response.data?.answer || response.data?.content || response.data?.reply|| response.data, conversationId: response.data?.conversation_id, messageId: response.data?.message_id } } else { throw new Error(response.message || 'AI服务返回错误') } }, /** * 生成会话ID */ async generateConversationId() { // 构建Dify API请求参数 const params = { url: '/api/kefu/createConversation', // 后端代理接口 data: { uniacid: store.state.uniacid, user_id: store.state.memberInfo?.id || 'anonymous', member_id: store.state.memberInfo?.id || '', }, header: { 'Content-Type': 'application/json' } } // 发送请求 const response = await http.sendRequest({ ...params, async: false // 使用Promise方式 }) return this.handleResponse(response, options) }, /** * 获取AI服务状态 */ async getServiceStatus() { try { // 简单的健康检查 const response = await http.sendRequest({ url: '/api/kefu/health', async: false, data: {} }) const available = [response?.data?.status, response?.data?.components?.ai_service_config?.status].filter(item => item === 'healthy').length > 0; return { available, reason: available ? '服务正常' : '服务异常' } } catch (error) { return { available: false, reason: '服务检查失败' } } }, /** * 清除会话历史 */ async clearConversation(conversationId) { try { const response = await http.sendRequest({ url: '/api/kefu/clear-conversation', data: { conversation_id: conversationId }, async: false }) return response.success } catch (error) { console.error('清除会话失败:', error) return false } } }