diff --git a/common/js/ai-service.js b/common/js/ai-service.js index 32fc199..2fea18a 100644 --- a/common/js/ai-service.js +++ b/common/js/ai-service.js @@ -1,3 +1,4 @@ +import Config from './config.js' import http from './http.js' import store from '@/store/index.js' @@ -32,7 +33,10 @@ export default { 'Content-Type': 'application/json' } } - + // ✅ 发送请求前打印日志,用于调试 + console.log('Sending request to:', params.url); + console.log('Request data:', params.data); + // 发送请求 const response = await http.sendRequest({ ...params, @@ -63,19 +67,233 @@ export default { 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流式请求 + * HTTP流式请求(现有方法保持不变) */ async sendHttpStream(message, onChunk, onComplete) { const params = { url: '/api/kefu/chat', data: { query: message, - conversation_id: this.generateConversationId(), + conversation_id: '', // 可以在外部传入或保持为空让服务器生成 user_id: store.state.memberInfo?.id || 'anonymous', stream: true, - uniacid: store.state.uniacid, // 保留必填参数 + uniacid: store.state.uniacid, // 保留必填参数 + inputs: {}, + response_mode: 'streaming', + user: store.state.memberInfo?.id || 'anonymous' }, header: { 'Content-Type': 'application/json' @@ -85,16 +303,18 @@ export default { // 使用fetch API进行流式请求(H5环境) // #ifdef H5 try { - const response = await fetch(`/api/kefu/chat`, { + const url = Config.baseUrl + `/api/kefu/chat`; + const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ + uniacid: store.state.uniacid || '1', inputs: {}, query: message, response_mode: 'streaming', - user: store.state.memberInfo?.id || 'anonymous' + user_id: store.state.memberInfo?.id || 'anonymous' }) }) diff --git a/components/ai-chat-message/ai-chat-message.vue b/components/ai-chat-message/ai-chat-message.vue index 2f9679f..6467a4e 100644 --- a/components/ai-chat-message/ai-chat-message.vue +++ b/components/ai-chat-message/ai-chat-message.vue @@ -395,7 +395,7 @@ export default { // 是否启用流式响应 enableStreaming: { type: Boolean, - default: false + default: true }, // 流式响应速度(字符/秒) streamSpeed: {