mirror of
https://codeup.aliyun.com/67762337eccfc218f6110e0e/vue/learning-system-portal.git
synced 2025-12-06 17:36:42 +08:00
feat(ai-chat): 实现AI聊天对话与会话消息记录功能
新增AI聊天对话接口和会话消息记录查询接口,支持SSE流式响应处理。 在sendMessage组件中集成真实SE调用逻辑,替换原有模拟实现, 并完善conversationId的获取与保存机制。新增sseHelper工具类用于统一处理SSE连接和数据解析。
This commit is contained in:
21
src/api/boe/aiChat.js
Normal file
21
src/api/boe/aiChat.js
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import ajax from '@/api/boe/boeApiAjax.js'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AI聊天对话接口
|
||||||
|
* @param {Object} data - 请求参数
|
||||||
|
* @param {string} data.conversationId - 会话ID,如果为空则创建新会话
|
||||||
|
* @param {string} data.query - 用户提问内容
|
||||||
|
* @returns {Promise} - 返回SSE流
|
||||||
|
*/
|
||||||
|
export function aiChat(data) {
|
||||||
|
return ajax.postJson('/xboe/m/boe/case/ai/chat', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询会话消息记录接口
|
||||||
|
* @param {string} conversationId - 会话ID
|
||||||
|
* @returns {Promise} - 返回会话历史记录
|
||||||
|
*/
|
||||||
|
export function getChatMessages(conversationId) {
|
||||||
|
return ajax.get('/xboe/m/boe/case/ai/messages?conversationId=' + conversationId)
|
||||||
|
}
|
||||||
83
src/utils/sseHelper.js
Normal file
83
src/utils/sseHelper.js
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
/**
|
||||||
|
* SSE流式数据处理工具
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理SSE响应数据
|
||||||
|
* @param {String} data - SSE响应数据
|
||||||
|
* @param {Function} onMessage - 处理消息的回调函数
|
||||||
|
* @param {Function} onComplete - 完成时的回调函数
|
||||||
|
* @param {Function} onError - 错误处理回调函数
|
||||||
|
*/
|
||||||
|
export function processSSEData(data, onMessage, onComplete, onError) {
|
||||||
|
try {
|
||||||
|
const lines = data.split('\n')
|
||||||
|
for (const line of lines) {
|
||||||
|
if (line.startsWith('data: ')) {
|
||||||
|
const jsonData = JSON.parse(line.substring(6))
|
||||||
|
onMessage(jsonData)
|
||||||
|
|
||||||
|
// 如果状态为4,表示对话结束
|
||||||
|
if (jsonData.data && jsonData.data.status === 4) {
|
||||||
|
onComplete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('处理SSE数据时出错:', error)
|
||||||
|
if (onError) {
|
||||||
|
onError(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建SSE连接
|
||||||
|
* @param {String} url - 请求地址
|
||||||
|
* @param {Object} data - 请求数据
|
||||||
|
* @param {Function} onMessage - 消息处理回调
|
||||||
|
* @param {Function} onComplete - 完成回调
|
||||||
|
* @param {Function} onError - 错误回调
|
||||||
|
* @returns {Promise} - 返回fetch Promise
|
||||||
|
*/
|
||||||
|
export function createSSEConnection(url, data, onMessage, onComplete, onError) {
|
||||||
|
return fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(data)
|
||||||
|
}).then(response => {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const reader = response.body.getReader()
|
||||||
|
const decoder = new TextDecoder('utf-8')
|
||||||
|
let buffer = ''
|
||||||
|
|
||||||
|
function read() {
|
||||||
|
reader.read().then(({ done, value }) => {
|
||||||
|
if (done) {
|
||||||
|
if (onComplete) onComplete()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer += decoder.decode(value, { stream: true })
|
||||||
|
processSSEData(buffer, onMessage, onComplete, onError)
|
||||||
|
|
||||||
|
// 继续读取
|
||||||
|
read()
|
||||||
|
}).catch(error => {
|
||||||
|
console.error('SSE读取错误:', error)
|
||||||
|
if (onError) onError(error)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 开始读取数据
|
||||||
|
read()
|
||||||
|
}).catch(error => {
|
||||||
|
console.error('SSE连接错误:', error)
|
||||||
|
if (onError) onError(error)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="message-suggestions" v-if="messageList[messageList.length-1].textCompleted">
|
<div class="message-suggestions" v-if="messageList[messageList.length-1].textCompleted">
|
||||||
<div class="suggestion-item" v-for="(item, index) in suggestions" :key="index">
|
<div class="suggestion-item" v-for="(item, index) in suggestions" :key="index">
|
||||||
<a @click="sendSuggestions"> {{ item }} →</a>
|
<a @click="sendSuggestions(item)"> {{ item }} →</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="isLoading" class="loading-message">
|
<div v-if="isLoading" class="loading-message">
|
||||||
@@ -49,6 +49,7 @@
|
|||||||
@new-conversation="startNewConversation"
|
@new-conversation="startNewConversation"
|
||||||
:disabled="isLoading"
|
:disabled="isLoading"
|
||||||
class="input-area-wrapper"
|
class="input-area-wrapper"
|
||||||
|
ref="sendMessage"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -121,8 +122,13 @@ export default {
|
|||||||
this.suggestions = arr
|
this.suggestions = arr
|
||||||
},
|
},
|
||||||
// 处理建议
|
// 处理建议
|
||||||
sendSuggestions(){
|
sendSuggestions(item){
|
||||||
this.suggestions = []
|
// this.suggestions = []
|
||||||
|
this.AIContent = item
|
||||||
|
setTimeout(()=>{
|
||||||
|
this.$refs.sendMessage.handleSend()
|
||||||
|
this.AIContent = ''
|
||||||
|
},500)
|
||||||
},
|
},
|
||||||
startNewConversation() {
|
startNewConversation() {
|
||||||
this.messageList = [
|
this.messageList = [
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// import { aiChat } from '@/api/boe/aiChat.js'
|
import { aiChat } from '@/api/boe/aiChat.js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'SendMessage',
|
name: 'SendMessage',
|
||||||
@@ -55,7 +55,7 @@ export default {
|
|||||||
methods: {
|
methods: {
|
||||||
handleSend() {
|
handleSend() {
|
||||||
if (!this.inputContent.trim() || this.disabled) return
|
if (!this.inputContent.trim() || this.disabled) return
|
||||||
|
console.log(1);
|
||||||
// 添加用户消息到列表
|
// 添加用户消息到列表
|
||||||
const userMessage = {
|
const userMessage = {
|
||||||
isBot: false,
|
isBot: false,
|
||||||
@@ -73,128 +73,7 @@ export default {
|
|||||||
this.inputContent = ''
|
this.inputContent = ''
|
||||||
},
|
},
|
||||||
|
|
||||||
// 调用AI聊天接口
|
|
||||||
callAIChat(question) {
|
|
||||||
// 创建新的AI消息对象
|
|
||||||
const aiMessage = {
|
|
||||||
isBot: true,
|
|
||||||
text: '',
|
|
||||||
thinkText: '',
|
|
||||||
caseRefers: [], // 添加caseRefers字段
|
|
||||||
textCompleted: false // 添加文字处理完成状态,默认为false
|
|
||||||
};
|
|
||||||
this.messageList.push(aiMessage);
|
|
||||||
|
|
||||||
// 模拟SSE流式响应
|
|
||||||
this.simulateSSE(question, aiMessage);
|
|
||||||
},
|
|
||||||
|
|
||||||
// 模拟SSE流式响应
|
|
||||||
simulateSSE(question, aiMessage) {
|
|
||||||
// 模拟AI思考过程,包含think标签内容
|
|
||||||
const fullResponse =
|
|
||||||
"<think>" +
|
|
||||||
"让我思考一下如何更好地回答用户的问题。首先需要分析问题的关键点,然后查找相关的知识库内容。\n" +
|
|
||||||
"用户询问的是关于" + question + "的问题,我需要提供准确且有用的信息。\n" +
|
|
||||||
"</think>" +
|
|
||||||
"正在分析问题:" + question + "\n" +
|
|
||||||
"检索相关案例...\n" +
|
|
||||||
"匹配最佳解决方案...\n" +
|
|
||||||
|
|
||||||
"您好,我已经收到您的问题:\"" + question + "\"。\n" +
|
|
||||||
"正在分析相关内容...\n" +
|
|
||||||
"根据我的知识库,我为您提供以下解答:\n" +
|
|
||||||
"首先,我们需要了解问题的背景和关键点。\n" +
|
|
||||||
"在这个案例中,我们可以采用以下步骤来解决:\n" +
|
|
||||||
"1. 确认问题的具体表现和影响范围\n" +
|
|
||||||
"2. 分析可能的原因和影响因素\n" +
|
|
||||||
"3. 制定针对性的解决方案\n" +
|
|
||||||
"4. 实施并验证解决方案的有效性\n" +
|
|
||||||
"如果您还有其他相关问题,欢迎继续提问!";
|
|
||||||
|
|
||||||
let index = 0;
|
|
||||||
let inThinkTag = false;
|
|
||||||
let thinkContent = '';
|
|
||||||
let mainContent = '';
|
|
||||||
|
|
||||||
|
|
||||||
this.suggestions = [1,2,3]
|
|
||||||
|
|
||||||
this.$emit('update-suggestions', [1,2,3]);
|
|
||||||
// 模拟打字机效果
|
|
||||||
const typeNextCharacter = () => {
|
|
||||||
if (index < fullResponse.length) {
|
|
||||||
const char = fullResponse.charAt(index);
|
|
||||||
|
|
||||||
// 处理<think>标签
|
|
||||||
if (fullResponse.substring(index, index + 7) === '<think>') {
|
|
||||||
inThinkTag = true;
|
|
||||||
index += 7; // 跳过<think>标签
|
|
||||||
setTimeout(typeNextCharacter, 10);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理</think>标签
|
|
||||||
if (fullResponse.substring(index, index + 8) === '</think>') {
|
|
||||||
inThinkTag = false;
|
|
||||||
aiMessage.thinkText = thinkContent;
|
|
||||||
index += 8; // 跳过</think>标签
|
|
||||||
// 更新父组件的messageList
|
|
||||||
this.$emit('update-message', aiMessage);
|
|
||||||
setTimeout(typeNextCharacter, 10);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 根据当前状态添加字符
|
|
||||||
if (inThinkTag) {
|
|
||||||
thinkContent += char;
|
|
||||||
aiMessage.thinkText = thinkContent;
|
|
||||||
} else {
|
|
||||||
mainContent += char;
|
|
||||||
aiMessage.text = mainContent;
|
|
||||||
}
|
|
||||||
|
|
||||||
index++;
|
|
||||||
setTimeout(typeNextCharacter, 30);
|
|
||||||
// 实时更新父组件的messageList
|
|
||||||
this.$emit('update-message', aiMessage);
|
|
||||||
} else {
|
|
||||||
// 设置文字处理完成状态为true
|
|
||||||
aiMessage.textCompleted = true;
|
|
||||||
|
|
||||||
// 模拟status 0的caseRefers数据
|
|
||||||
// 在消息完成后添加引用案例
|
|
||||||
setTimeout(() => {
|
|
||||||
aiMessage.caseRefers = [
|
|
||||||
{
|
|
||||||
caseId: "case_001",
|
|
||||||
title: "案例1标题",
|
|
||||||
authorName: "张三",
|
|
||||||
keywords: ["关键词1", "关键词2"],
|
|
||||||
content: "这是案例1的内容摘要..."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
caseId: "case_002",
|
|
||||||
title: "案例2标题",
|
|
||||||
authorName: "李四",
|
|
||||||
keywords: ["关键词3", "关键词4"],
|
|
||||||
content: "这是案例2的内容摘要..."
|
|
||||||
}
|
|
||||||
];
|
|
||||||
this.$emit('update-message', aiMessage);
|
|
||||||
}, 500);
|
|
||||||
|
|
||||||
// 所有内容完成
|
|
||||||
this.$emit('loading', false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 开始打字机效果
|
|
||||||
setTimeout(typeNextCharacter, 100);
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
/*
|
|
||||||
// 真实的SSE实现(暂时注释掉)
|
// 真实的SSE实现(暂时注释掉)
|
||||||
callAIChat(question) {
|
callAIChat(question) {
|
||||||
// 创建新的AI消息对象
|
// 创建新的AI消息对象
|
||||||
@@ -213,19 +92,8 @@ export default {
|
|||||||
query: question
|
query: question
|
||||||
};
|
};
|
||||||
|
|
||||||
// 使用fetch API处理SSE
|
|
||||||
const url = process.env.VUE_APP_BOE_BASE_API + '/xboe/m/boe/case/ai/chat';
|
|
||||||
const token = this.$store.getters.token;
|
|
||||||
|
|
||||||
// 创建POST请求
|
// 创建POST请求
|
||||||
fetch(url, {
|
aiChat(requestData).then(response => {
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
[this.$ajax.tokenName]: token
|
|
||||||
},
|
|
||||||
body: JSON.stringify(requestData)
|
|
||||||
}).then(response => {
|
|
||||||
// 处理流式响应
|
// 处理流式响应
|
||||||
const reader = response.body.getReader();
|
const reader = response.body.getReader();
|
||||||
const decoder = new TextDecoder('utf-8');
|
const decoder = new TextDecoder('utf-8');
|
||||||
@@ -266,6 +134,10 @@ export default {
|
|||||||
// 更新父组件的messageList
|
// 更新父组件的messageList
|
||||||
this.$emit('update-message', aiMessage);
|
this.$emit('update-message', aiMessage);
|
||||||
}
|
}
|
||||||
|
// 从响应中获取并保存conversationId
|
||||||
|
if (jsonData.data.conversationId) {
|
||||||
|
this.conversationId = jsonData.data.conversationId;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 1: // 流式对话内容
|
case 1: // 流式对话内容
|
||||||
@@ -296,6 +168,10 @@ export default {
|
|||||||
aiMessage.textCompleted = true;
|
aiMessage.textCompleted = true;
|
||||||
// 更新父组件的messageList
|
// 更新父组件的messageList
|
||||||
this.$emit('update-message', aiMessage);
|
this.$emit('update-message', aiMessage);
|
||||||
|
// 从响应中获取并保存conversationId
|
||||||
|
if (jsonData.data.conversationId) {
|
||||||
|
this.conversationId = jsonData.data.conversationId;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 3: // 返回建议
|
case 3: // 返回建议
|
||||||
@@ -305,6 +181,10 @@ export default {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 4: // 交互完成
|
case 4: // 交互完成
|
||||||
|
// 从响应中获取并保存conversationId
|
||||||
|
if (jsonData.data.conversationId) {
|
||||||
|
this.conversationId = jsonData.data.conversationId;
|
||||||
|
}
|
||||||
this.$emit('loading', false);
|
this.$emit('loading', false);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -339,7 +219,6 @@ export default {
|
|||||||
this.$emit('update-message', aiMessage);
|
this.$emit('update-message', aiMessage);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
*/
|
|
||||||
handleNewConversation() {
|
handleNewConversation() {
|
||||||
this.$emit('new-conversation')
|
this.$emit('new-conversation')
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user