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:
2026-05-14 17:50:48 +08:00
parent fed3962965
commit bd778c0283
3 changed files with 66 additions and 52 deletions

View File

@@ -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,

View File

@@ -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 {
@@ -544,31 +544,6 @@ export default {
const hasEmoji = this.containsEmoji(this.inputText)
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() {
@@ -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)

View File

@@ -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: {