diff --git a/manager/LoginManager.js b/manager/LoginManager.js index 2b66d4f..1fd4a5a 100644 --- a/manager/LoginManager.js +++ b/manager/LoginManager.js @@ -15,7 +15,8 @@ export async function loginAuth() { password: 'YehdBPev' }); if (response.access_token) { - return response.data; + uni.setStorageSync('token', response.access_token) + return response; } else { throw new Error(response.message || '登录失败'); } diff --git a/pages/chat/ChatCardAI.vue b/pages/chat/ChatCardAI.vue index 0c14f53..cd19f2f 100644 --- a/pages/chat/ChatCardAI.vue +++ b/pages/chat/ChatCardAI.vue @@ -19,6 +19,7 @@ .chat-ai { margin: 6px 12px; padding: 16px; + min-width: 60px; background: rgba(255,255,255,0.4); box-shadow: 2px 2px 10px 0px rgba(0,0,0,0.1); diff --git a/pages/chat/ChatMainList.vue b/pages/chat/ChatMainList.vue index 988e6a7..5388cb9 100644 --- a/pages/chat/ChatMainList.vue +++ b/pages/chat/ChatMainList.vue @@ -21,20 +21,10 @@ - - - - - - - + @@ -83,6 +73,7 @@ import { MessageRole, ChatModel, MessageType } from '../../model/ChatModel'; import OneFeelMK001 from '../module/OneFeelMK001.vue'; + import request from '../../request/base/request'; // 导航栏相关 const statusBarHeight = ref(20); @@ -175,19 +166,8 @@ } chatMsgList.value.push(newMsg) - - let type = chatMsgList.value.length % 3 === 0 - - const newMsgAI: ChatModel = { - msgId: `msg_${chatMsgList.value.length}`, - msgType: MessageRole.AI, - msg: `我是ai,你输入的内容是:${text}`, - msgContent: { - type: type ? MessageType.IMAGE : MessageType.TEXT, - url: '' - } - } - chatMsgList.value.push(newMsgAI) + + sendChat('酒店一共有哪些温泉?') console.log("发送的新消息:",JSON.stringify(newMsg)) } @@ -201,6 +181,94 @@ }); } + let loadingTimer: any = null; + let typeWriterTimer: any = null; + let aiMsgBuffer = ''; // 全局缓冲区 + let isTyping = false; // 是否正在打字 + + const sendChat = (text) => { + + console.log('=============会话测试') + const args = { + conversationId: "1931957498711957505", + agentId: "1", + messageType: 0, + messageContent: text + } + + // 1. 插入AI消息 + const aiMsg: ChatModel = { + msgId: `msg_${chatMsgList.value.length}`, + msgType: MessageRole.AI, + msg: '加载中.', + msgContent: { + type: MessageType.TEXT, + url: '' + } + } + chatMsgList.value.push(aiMsg) + const aiMsgIndex = chatMsgList.value.length - 1 + + // 动态加载中动画 + let dotCount = 1; + loadingTimer && clearInterval(loadingTimer); + loadingTimer = setInterval(() => { + dotCount = dotCount % 3 + 1; + chatMsgList.value[aiMsgIndex].msg = '加载中' + '.'.repeat(dotCount); + }, 400); + + aiMsgBuffer = ''; + isTyping = false; + if (typeWriterTimer) { + clearTimeout(typeWriterTimer); + typeWriterTimer = null; + } + + // 2. 流式接收内容 + request.getAIChatStream(args, (chunk) => { + console.log('分段内容:', chunk) + if (chunk && chunk.content) { + // 收到内容,停止动画 + if (loadingTimer) { + clearInterval(loadingTimer); + loadingTimer = null; + } + // 把新内容追加到缓冲区 + aiMsgBuffer += chunk.content; + + // 启动打字机(只启动一次) + if (!isTyping) { + isTyping = true; + chatMsgList.value[aiMsgIndex].msg = ''; + typeWriter(); + } + } + if (chunk && chunk.finish) { + // 结尾处理:确保剩余内容全部输出 + const finishInterval = setInterval(() => { + if (aiMsgBuffer.length === 0) { + clearInterval(finishInterval); + isTyping = false; + scrollToBottom(); + } + }, 50); + } + }); + + // 打字机函数 + function typeWriter() { + if (aiMsgBuffer.length > 0) { + chatMsgList.value[aiMsgIndex].msg += aiMsgBuffer[0]; + aiMsgBuffer = aiMsgBuffer.slice(1); + scrollToBottom(); + typeWriterTimer = setTimeout(typeWriter, 30); + } else { + // 等待新内容到来,不结束 + typeWriterTimer = setTimeout(typeWriter, 30); + } + } + } + diff --git a/pages/drawer/DrawerHome.vue b/pages/drawer/DrawerHome.vue index d9fe506..eb96131 100644 --- a/pages/drawer/DrawerHome.vue +++ b/pages/drawer/DrawerHome.vue @@ -6,6 +6,8 @@ + + {{item}} @@ -20,6 +22,9 @@ const emits = defineEmits(['closeDrawer']) import * as loginMnager from '@/manager/LoginManager' + import { getAgentChatMessage } from '@/request/api/AgentChatApi.js' +import request from '../../request/base/request' + const closeDrawer = () => { emits('closeDrawer') console.log('=============关闭抽屉') @@ -37,7 +42,26 @@ }) console.log('=============登录') // 这里可以处理登录逻辑,比如调用登录接口等 - } + } + + const sendChat = () => { + console.log('=============会话测试') + + const args = { + "conversationId":"1931957498711957505", + "agentId":"1", + "messageType": 0, + "messageContent":"酒店一共有哪些温泉?" + } + + + + request.getAIChatStream(args, (chunk) => { + // 每收到一段数据都会回调 + console.log('分段内容:', chunk) + // 你可以在这里追加到消息列表 + }) + } diff --git a/request/api/AgentChatApi.js b/request/api/AgentChatApi.js new file mode 100644 index 0000000..d477c0a --- /dev/null +++ b/request/api/AgentChatApi.js @@ -0,0 +1,19 @@ +import request from "../base/request"; + +function getAgentChatMessage() { + const args = { + "conversationId":"1931957498711957505", + "agentId":"1", + "messageType": 0, + "messageContent":"酒店一共有哪些温泉?" + } + const defaultConfig = { + header: { + Accept: 'text/event-stream', + 'Content-Type': 'application/json' + }, + }; + return request.post('/agent/assistant/chat', args, defaultConfig); +} + +export { getAgentChatMessage } \ No newline at end of file diff --git a/request/base/request.js b/request/base/request.js index b1b26ce..5e2116d 100644 --- a/request/base/request.js +++ b/request/base/request.js @@ -14,6 +14,7 @@ function request(url, args = {}, method = 'POST', customConfig = {}) { } // 动态获取 token const token = uni.getStorageSync('token'); + let header = { ...defaultConfig.header, ...customConfig.header @@ -23,9 +24,12 @@ function request(url, args = {}, method = 'POST', customConfig = {}) { delete header.Authorization; } else { if (token) { - header.Authorization = `Basic ${token}`; + header.Authorization = `Bearer ${token}`; } } + + console.log("请求头customConfig:" + JSON.stringify(customConfig)) + const config = { ...defaultConfig, ...customConfig, @@ -54,8 +58,8 @@ function request(url, args = {}, method = 'POST', customConfig = {}) { } // 默认 POST -request.post = function(url, args = {}) { - return request(url, args, 'POST'); +request.post = function(url, args = {}, config = {}) { + return request(url, args, 'POST', config); }; // 支持 GET @@ -63,4 +67,77 @@ request.get = function(url, args = {}) { return request(url, args, 'GET'); }; +/** + * 获取AI聊天流式信息(仅微信小程序支持) + * @param {Object} params 请求参数 + * @param {Function} onChunk 回调,每收到一段数据触发 + * @returns {Promise} + */ +request.getAIChatStream = function(params, onChunk) { + return new Promise((resolve, reject) => { + const token = uni.getStorageSync('token'); + + console.log("发送请求内容: ", params) + // #ifdef MP-WEIXIN + const requestTask = uni.request({ + url: BASE_URL + '/agent/assistant/chat', // 替换为你的接口地址 + method: 'POST', + data: params, + enableChunked: true, + header: { + Accept: 'text/event-stream', + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}`, // 如需token可加 + }, + responseType: 'arraybuffer', + success(res) { + resolve(res.data); + }, + fail(err) { + reject(err); + } + }); + + requestTask.onHeadersReceived(res => { + console.log('onHeadersReceived', res); + }); + + requestTask.onChunkReceived(res => { + const base64 = uni.arrayBufferToBase64(res.data); + let data = ''; + try { + data = decodeURIComponent(escape(atob(base64))); + } catch (e) { + // 某些平台可能不支持 atob,可以直接用 base64 + data = base64; + } + const messages = parseSSEChunk(data); + messages.forEach(msg => { + if (onChunk) onChunk(msg); + }); + }); + // #endif + }); +} + +// 解析SSE分段数据 +function parseSSEChunk(raw) { + // 拆分为多段 + const lines = raw.split('\n\n'); + const results = []; + lines.forEach(line => { + // 只处理包含 data: 的行 + const dataMatch = line.match(/data:(\{.*\})/); + if (dataMatch && dataMatch[1]) { + try { + const obj = JSON.parse(dataMatch[1]); + results.push(obj); + } catch (e) { + // 解析失败忽略 + } + } + }); + return results; +} + export default request; \ No newline at end of file