fix(ai-chat): 修复历史记录加载、WebSocket长连接、服务端消息格式兼容
- 修复 WebSocket message_end 未收到问题:兼容 data.event === 'done' + data.data.conversation_id 嵌套格式 - 修复子组件 onShow/onUnload 不触发:移到页面 index.vue 的页面生命周期中 - 修复 loadHistoryIfNeeded 被重复 methods 块覆盖:合并到同一 methods 块 - 修复历史消息映射字段:msg.query → msg.content,添加 type:'text' - 去掉所有有帮助/没帮助按钮 - CONVERSATION_KEY 改为统一导出常量 - WebSocket 长连接重构 + #ifdef 环境隔离
This commit is contained in:
@@ -3,7 +3,7 @@ import http from './http.js'
|
||||
import store from '@/store/index.js'
|
||||
|
||||
let currentConversationId = null;
|
||||
const CONVERSATION_KEY = 'ai_conversation_id';
|
||||
export const CONVERSATION_KEY = 'ai_conversation_id';
|
||||
|
||||
/**
|
||||
* 获取域名配置参数(避免重复代码)
|
||||
@@ -111,16 +111,24 @@ function ensureWebSocketConnected() {
|
||||
|
||||
if (data.event === 'message') {
|
||||
var text = data.answer || data.content || data.text || '';
|
||||
// 兼容嵌套 data.data.content 格式
|
||||
if (!text && data.data) {
|
||||
text = data.data.content || data.data.answer || '';
|
||||
}
|
||||
wsMessageContent += text;
|
||||
if (wsMessageOnChunk) wsMessageOnChunk(text);
|
||||
} else if (data.event === 'message_end') {
|
||||
var conversationId = data.conversation_id || '';
|
||||
} else if (data.event === 'message_end' || data.event === 'done') {
|
||||
// 兼容两种结束信号:message_end(流结束)或 done(服务端完成)
|
||||
// 兼容两种数据层级:conversation_id 可能在顶层或 data.data 中
|
||||
var conversationId = data.conversation_id || (data.data && data.data.conversation_id) || '';
|
||||
console.log('[AI WS] 流结束信号, event:', data.event, 'type:', data.type, 'conversationId:', conversationId, '当前内存:', currentConversationId);
|
||||
// 保存会话ID(独立函数,不使用 this)
|
||||
if (conversationId) {
|
||||
if (conversationId !== currentConversationId) {
|
||||
currentConversationId = conversationId;
|
||||
try {
|
||||
uni.setStorageSync(CONVERSATION_KEY, conversationId);
|
||||
console.log('[AI WS] 会话ID已持久化到 storage:', conversationId);
|
||||
} catch (e) {
|
||||
console.error('保存会话ID失败:', e);
|
||||
}
|
||||
@@ -133,6 +141,9 @@ function ensureWebSocketConnected() {
|
||||
wsMessageContent = '';
|
||||
if (wsMessageOnComplete) wsMessageOnComplete({ content: content, conversation_id: convId });
|
||||
if (wsMessageResolve) wsMessageResolve({ content: content, conversation_id: convId });
|
||||
} else {
|
||||
// 兜底日志:打印所有未识别的消息类型
|
||||
console.log('[AI WS] 未识别的消息:', JSON.stringify(data).substring(0, 200));
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('WebSocket 消息解析失败:', e, '原始数据:', res.data);
|
||||
@@ -507,6 +518,7 @@ export default {
|
||||
if (!params.conversation_id) {
|
||||
throw new Error('会话ID(conversation_id)是必填参数');
|
||||
}
|
||||
console.log('[AI History] 请求历史, conversation_id:', params.conversation_id, 'limit:', params.limit);
|
||||
const requestData = {
|
||||
uniacid: params.uniacid || this.getUniacid(),
|
||||
conversation_id: params.conversation_id,
|
||||
|
||||
@@ -426,7 +426,7 @@
|
||||
</view>
|
||||
</template>
|
||||
<script>
|
||||
import aiService from '@/common/js/ai-service.js'
|
||||
import aiService, { CONVERSATION_KEY } from '@/common/js/ai-service.js'
|
||||
import IMAGE_CONFIG from '@/common/js/image-config.js'
|
||||
|
||||
export default {
|
||||
@@ -545,31 +545,6 @@ export default {
|
||||
return hasContent || hasEmoji
|
||||
}
|
||||
},
|
||||
onShow() {
|
||||
// 如果不是 AI智能客服,立即退出并跳转
|
||||
if (customerServiceType !== 'ai') {
|
||||
uni.showToast({ title: '当前客服类型不支持此页面', icon: 'none' });
|
||||
setTimeout(() => {
|
||||
uni.navigateBack({ delta: 1 });
|
||||
}, 1000);
|
||||
return;
|
||||
}
|
||||
// 只在初次渲染或会话ID发生变化时才加载历史
|
||||
const localConvId = this.getConversationIdFromLocal();
|
||||
const shouldReload = this.currentConversationId !== localConvId;
|
||||
|
||||
if (localConvId && shouldReload) {
|
||||
// 切换到已有的历史会话 → 重新加载历史
|
||||
this.currentConversationId = localConvId;
|
||||
aiService.setConversationId(localConvId);
|
||||
this.loadChatHistoryIfExist();
|
||||
// 注意: historyOffset 和 hasMoreHistory 在 loadChatHistoryIfExist() 内部设置
|
||||
} else if (!localConvId && !this.currentConversationId) {
|
||||
// 全新会话,初始化偏移量
|
||||
this.historyOffset = this.messages.length;
|
||||
}
|
||||
// 会话未变化且已有消息 → 跳过加载,保留当前消息
|
||||
},
|
||||
// 新增:组件销毁时清除所有定时器
|
||||
beforeDestroy() {
|
||||
if (this.scrollTimer) clearTimeout(this.scrollTimer);
|
||||
@@ -589,6 +564,7 @@ export default {
|
||||
// 初始化用户昵称(缓存优先)
|
||||
this.initUserNicknameData()
|
||||
this.currentConversationId = aiService.getConversationId();
|
||||
console.log('[AI Chat] created, currentConversationId:', this.currentConversationId, 'messages.length:', this.messages.length);
|
||||
},
|
||||
mounted() {
|
||||
// 初始加载时滚底(仅一次)
|
||||
@@ -596,10 +572,6 @@ export default {
|
||||
this.scrollToBottom()
|
||||
})
|
||||
},
|
||||
onUnload() {
|
||||
// 页面销毁时断开 WebSocket 长连接
|
||||
aiService.disconnectWebSocket();
|
||||
},
|
||||
watch: {
|
||||
messages: {
|
||||
handler(newVal, oldVal) {
|
||||
@@ -614,6 +586,31 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 供父页面调用的加载历史方法
|
||||
* 在 page onShow 中通过 ref 调用
|
||||
*/
|
||||
loadHistoryIfNeeded() {
|
||||
const localConvId = this.getConversationIdFromLocal();
|
||||
const shouldReload = this.currentConversationId !== localConvId;
|
||||
console.log('[AI Chat] loadHistoryIfNeeded | localConvId:', localConvId, '| currentConvId:', this.currentConversationId, '| shouldReload:', shouldReload, '| messages.length:', this.messages.length, '| initialMessages.length:', this.initialMessages.length);
|
||||
|
||||
if (localConvId && shouldReload) {
|
||||
console.log('[AI Chat] → 分支1: ID变化,加载历史');
|
||||
this.currentConversationId = localConvId;
|
||||
aiService.setConversationId(localConvId);
|
||||
this.loadChatHistoryIfExist();
|
||||
} else if (localConvId && !shouldReload && this.messages.length <= this.initialMessages.length) {
|
||||
console.log('[AI Chat] → 分支2: ID不变但只有初始消息,从服务端恢复');
|
||||
this.currentConversationId = localConvId;
|
||||
this.loadChatHistoryIfExist();
|
||||
} else if (!localConvId && !this.currentConversationId) {
|
||||
console.log('[AI Chat] → 分支3: 全新会话');
|
||||
this.historyOffset = this.messages.length;
|
||||
} else {
|
||||
console.log('[AI Chat] → 分支4: 跳过加载,保留当前消息, messages.length:', this.messages.length);
|
||||
}
|
||||
},
|
||||
// 生成消息的唯一 key(兼容非 H5 平台)
|
||||
getMessageKey(message, index) {
|
||||
const id = message.id || message.timestamp || index;
|
||||
@@ -633,14 +630,14 @@ export default {
|
||||
// 【新增:会话 ID 本地缓存方法】
|
||||
saveConversationIdToLocal(convId) {
|
||||
if (convId) {
|
||||
uni.setStorageSync('dify_conversation_id', convId);
|
||||
uni.setStorageSync(CONVERSATION_KEY, convId);
|
||||
}
|
||||
},
|
||||
getConversationIdFromLocal() {
|
||||
return uni.getStorageSync('dify_conversation_id') || null;
|
||||
return uni.getStorageSync(CONVERSATION_KEY) || null;
|
||||
},
|
||||
clearConversationIdFromLocal() {
|
||||
uni.removeStorageSync('dify_conversation_id');
|
||||
uni.removeStorageSync(CONVERSATION_KEY);
|
||||
},
|
||||
// 初始化音频上下文
|
||||
initAudioContext() {
|
||||
@@ -684,44 +681,47 @@ export default {
|
||||
// 新增:加载历史记录
|
||||
async loadChatHistoryIfExist() {
|
||||
const convId = aiService.getConversationId();
|
||||
console.log('[AI Chat] loadChatHistoryIfExist 被调用, convId:', convId);
|
||||
if (convId) {
|
||||
try {
|
||||
// 调用正确的方法,并传入会话ID
|
||||
console.log('[AI Chat] 请求历史 API...');
|
||||
const response = await aiService.getConversationHistory({
|
||||
conversation_id: convId
|
||||
});
|
||||
console.log('[AI Chat] 历史 API 返回:', JSON.stringify(response).substring(0, 200));
|
||||
|
||||
if (!response.success || !Array.isArray(response.messages)) {
|
||||
console.warn('历史记录为空或格式无效');
|
||||
console.warn('[AI Chat] 历史记录为空或格式无效, success:', response.success, 'messages:', Array.isArray(response.messages));
|
||||
return;
|
||||
}
|
||||
console.log('[AI Chat] 原始消息数:', response.messages.length, 'total:', response.total);
|
||||
// 需要根据实际返回的 response.data.messages 来映射
|
||||
this.messages = response.messages.map(msg => {
|
||||
let content = '';
|
||||
if (msg.role === 'user') {
|
||||
content = msg.query || (typeof msg.inputs === 'string' ? msg.inputs : JSON.stringify(msg.inputs || '')) || '';
|
||||
content = msg.query || msg.content || (typeof msg.inputs === 'string' ? msg.inputs : JSON.stringify(msg.inputs || '')) || '';
|
||||
} else {
|
||||
content = msg.answer || '';
|
||||
content = msg.answer || msg.content || '';
|
||||
}
|
||||
// 只使用后端返回的真实时间,不使用假时间
|
||||
const realTimestamp = msg.created_at || msg.create_time || msg.time;
|
||||
return {
|
||||
id: msg.message_id || (Date.now() + Math.random() * 10000),
|
||||
role: msg.role === 'user' ? 'user' : 'ai',
|
||||
type: 'text',
|
||||
content: content,
|
||||
timestamp: realTimestamp,
|
||||
actions: msg.role !== 'user' ? [
|
||||
{ id: 1, text: '有帮助', type: 'like' },
|
||||
{ id: 2, text: '没帮助', type: 'dislike' }
|
||||
] : []
|
||||
actions: []
|
||||
};
|
||||
}).filter(msg => msg.content.trim() !== ''); // 过滤掉内容为空的消息
|
||||
console.log('[AI Chat] 过滤后消息数:', this.messages.length, '内容摘要:', this.messages.map(m => ({role: m.role, content: m.content.substring(0, 30)})));
|
||||
// 根据服务端 total 判断是否还有更多历史可加载
|
||||
const msgCount = this.messages.length;
|
||||
this.hasMoreHistory = response.total > msgCount;
|
||||
this.historyOffset = msgCount;
|
||||
} catch (error) {
|
||||
console.error('加载历史记录失败:', error);
|
||||
console.error('[AI Chat] 加载历史记录失败:', error.message, error.stack);
|
||||
// 只需调用服务层的清除方法,它会处理本地存储
|
||||
aiService.clearConversationId();
|
||||
this.currentConversationId = null;
|
||||
@@ -1003,10 +1003,7 @@ async loadChatHistoryIfExist() {
|
||||
timestamp: Date.now(),
|
||||
conversationId: response.conversationId,
|
||||
messageId: response.messageId,
|
||||
actions: [
|
||||
{ id: 1, text: '有帮助', type: 'like' },
|
||||
{ id: 2, text: '没帮助', type: 'dislike' }
|
||||
]
|
||||
actions: []
|
||||
}
|
||||
this.shouldScrollToBottom = true
|
||||
this.messages.push(aiMessage)
|
||||
@@ -1144,10 +1141,7 @@ _finishStream(msg, result) {
|
||||
}
|
||||
}
|
||||
|
||||
msg.actions = [
|
||||
{ id: 1, text: '有帮助', type: 'like' },
|
||||
{ id: 2, text: '没帮助', type: 'dislike' }
|
||||
]
|
||||
msg.actions = []
|
||||
|
||||
this.$nextTick(() => this.scrollToBottom())
|
||||
this.$emit('ai-response', msg)
|
||||
|
||||
@@ -76,6 +76,7 @@ import navigationHelper from '@/common/js/navigation';
|
||||
import { EventSafety } from '@/common/js/event-safety';
|
||||
import aiChatMessage from './ai-chat-message.vue';
|
||||
import IMAGE_CONFIG from '@/common/js/image-config.js';
|
||||
import aiService from '@/common/js/ai-service.js';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -146,6 +147,7 @@ export default {
|
||||
},
|
||||
|
||||
onShow() {
|
||||
console.log('[AI Chat Page] onShow 触发');
|
||||
// 监听键盘高度变化
|
||||
uni.onKeyboardHeightChange((res) => {
|
||||
this.keyboardHeight = res.height;
|
||||
@@ -153,6 +155,10 @@ export default {
|
||||
this.$refs.chat.updateKeyboardHeight(res.height);
|
||||
}
|
||||
});
|
||||
// 每次页面显示时尝试加载历史记录
|
||||
if (this.$refs.chat && this.$refs.chat.loadHistoryIfNeeded) {
|
||||
this.$refs.chat.loadHistoryIfNeeded();
|
||||
}
|
||||
},
|
||||
|
||||
onHide() {
|
||||
@@ -165,7 +171,9 @@ export default {
|
||||
},
|
||||
|
||||
onUnload() {
|
||||
console.log('[AI Chat Page] onUnload 触发');
|
||||
this.cleanup();
|
||||
aiService.disconnectWebSocket();
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
Reference in New Issue
Block a user