Commit 5d8d6101 authored by 水玉婷's avatar 水玉婷
Browse files

feat:sse消息从长链接改为ask触发,调整数据结构

parent d9bbddfb
...@@ -93,7 +93,7 @@ ...@@ -93,7 +93,7 @@
<!-- 没有更多数据提示 --> <!-- 没有更多数据提示 -->
<div v-if="!hasMore && recentSessions.length > 0" class="no-more-data"> <div v-if="!hasMore && recentSessions.length > 0" class="no-more-data">
<span>没有更多历史记录</span> <span>没有更多数据</span>
</div> </div>
<!-- 空状态 --> <!-- 空状态 -->
...@@ -140,7 +140,6 @@ import defaultAvatar from '@/assets/logo2.png'; ...@@ -140,7 +140,6 @@ import defaultAvatar from '@/assets/logo2.png';
import menuIcon from '@/assets/sidebar-unfold-line.svg' import menuIcon from '@/assets/sidebar-unfold-line.svg'
import newChatIcon from '@/assets/chat-ai-line.svg' import newChatIcon from '@/assets/chat-ai-line.svg'
import { import {
PlusOutlined,
SearchOutlined, SearchOutlined,
DeleteOutlined, DeleteOutlined,
FileSearchOutlined FileSearchOutlined
......
...@@ -14,8 +14,8 @@ ...@@ -14,8 +14,8 @@
<div class="chat-intro-center" v-if="!props?.dialogSessionId && !hasStartedConversation"> <div class="chat-intro-center" v-if="!props?.dialogSessionId && !hasStartedConversation">
<div class="intro-content"> <div class="intro-content">
<img :src="defaultAvatar" alt="avatar" class="avatar-image" /> <img :src="defaultAvatar" alt="avatar" class="avatar-image" />
<h3>嗨,我是国械小智</h3> <h3>嗨,我是{{ appData?.app_name || '国械小智' }}</h3>
<p>我可以帮您搜索数据、查找流程制度,<br/>请把问题交给我吧</p> <p>{{ appData?.ai_app_prop?.prologue }}</p>
</div> </div>
</div> </div>
...@@ -126,14 +126,13 @@ ...@@ -126,14 +126,13 @@
import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue'; import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { markdownTemplate, isLastBlockMarkdown, getLastMarkdownBlockIndex, mergeMarkdownContent } from './utils/markdownTemplate'; import { markdownTemplate, isLastBlockMarkdown, getLastMarkdownBlockIndex, mergeMarkdownContent } from './utils/markdownTemplate';
import { SendOutlined, UserOutlined, ReloadOutlined, CopyOutlined, LikeOutlined, DislikeOutlined } from '@ant-design/icons-vue'; import { SendOutlined, ReloadOutlined, CopyOutlined, LikeOutlined, DislikeOutlined } from '@ant-design/icons-vue';
import { message as antdMessage } from 'ant-design-vue'; import { message as antdMessage } from 'ant-design-vue';
import defaultAvatar from '@/assets/logo.png'; import defaultAvatar from '@/assets/logo.png';
import rightIcon from '@/assets/right.svg' import rightIcon from '@/assets/right.svg'
import thinkIcon from '@/assets/think.svg' import thinkIcon from '@/assets/think.svg'
import ChartComponent from './ChartComponent.vue'; // 导入独立的图表组件 import ChartComponent from './ChartComponent.vue'; // 导入独立的图表组件
import VoiceRecognitionText from './VoiceRecognitionText.vue'; // 导入语音识别组件 import VoiceRecognitionText from './VoiceRecognitionText.vue'; // 导入语音识别组件
import { createSSEService, type SSEData } from './utils/sseService'; // 导入SSE服务
import { createContentTemplateService, type Message } from './utils/contentTemplateService'; // 导入模板服务 import { createContentTemplateService, type Message } from './utils/contentTemplateService'; // 导入模板服务
// 定义组件属性接口 // 定义组件属性接口
...@@ -175,6 +174,12 @@ const props = withDefaults(defineProps<Props>(), { ...@@ -175,6 +174,12 @@ const props = withDefaults(defineProps<Props>(), {
}) })
}); });
// 定义SSE数据接口
export interface SSEData {
message: any;
status: number | string;
type: number | string;
}
// 响应式数据 // 响应式数据
const messageText = ref(''); const messageText = ref('');
...@@ -272,37 +277,21 @@ const handleSSEMessage = (data: SSEData, isSimulated: boolean = false) => { ...@@ -272,37 +277,21 @@ const handleSSEMessage = (data: SSEData, isSimulated: boolean = false) => {
dialogSessionId.value = result.newDialogSessionId; dialogSessionId.value = result.newDialogSessionId;
} }
// 在收到status: 29(消息结束)时以及status: -1(错误)时,设置loading为false
if (data.status === 29 || data.status === -1) {
loading.value = false;
}
nextTick(() => { nextTick(() => {
scrollToBottom(); scrollToBottom();
}); });
} catch (error) { } catch (error) {
console.error('处理SSE消息时出错:', error); console.error('处理SSE消息时出错:', error);
// 错误时也设置loading为false
loading.value = false;
} }
}; };
// 创建SSE服务实例
const sseService = createSSEService({
apiBaseUrl: props.apiBaseUrl,
appCode: props.appCode,
token: props.token,
params: props.params
}, {
onMessage: handleSSEMessage,
onError: (error) => {
console.error('SSE error:', error);
isAIResponding.value = false;
isInThinkingMode.value = false;
closeSSE();
},
onOpen: (event) => {
console.log('SSE连接已建立', event);
},
onReconnect: (newDialogSessionId) => {
console.log('SSE重连成功,新的dialogSessionId:', newDialogSessionId);
dialogSessionId.value = newDialogSessionId;
}
});
// 创建模板服务实例 // 创建模板服务实例
const templateService = createContentTemplateService(); const templateService = createContentTemplateService();
...@@ -452,13 +441,15 @@ const sendMessage = async (type: MessageType = 'text', params: MessageParams = { ...@@ -452,13 +441,15 @@ const sendMessage = async (type: MessageType = 'text', params: MessageParams = {
audioDuration: durationTime, audioDuration: durationTime,
...props.params, ...props.params,
dialogSessionId: dialogSessionId.value, dialogSessionId: dialogSessionId.value,
appId: props.params?.appId,
} : { } : {
question: messageContent, question: messageContent,
...props.params, ...props.params,
dialogSessionId: dialogSessionId.value, dialogSessionId: dialogSessionId.value,
appId: props.params?.appId,
}; };
const response = await fetch(`${import.meta.env.VITE_SSE_PATH}/ask/app/${props.params?.appId}`, { const response = await fetch(`${import.meta.env.VITE_SSE_PATH}/sse/ask`, {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
...@@ -469,17 +460,17 @@ const sendMessage = async (type: MessageType = 'text', params: MessageParams = { ...@@ -469,17 +460,17 @@ const sendMessage = async (type: MessageType = 'text', params: MessageParams = {
body: JSON.stringify(requestData) body: JSON.stringify(requestData)
}); });
const data = await response.json(); // 检查响应是否为SSE流式响应
if (data.code === 0) { if (response.ok && response.headers.get('content-type')?.includes('text/event-stream')) {
console.log(`发送成功`); // 处理SSE流式响应
await processSSEStreamResponse(response.body);
console.log(`发送成功`)
// 发送成功,更新消息状态为已发送 // 发送成功,更新消息状态为已发送
if (messageData) { if (messageData) {
messageData.status = 2; messageData.status = 2;
} }
}else if(data.code === -100){ }else{
// 处理-100错误码,触发重连 loading.value = false;
sseService.reconnectSSE(dialogSessionId.value);
// 设置当前消息为失败状态 // 设置当前消息为失败状态
if (messageData) { if (messageData) {
messageData.status = -1; messageData.status = -1;
...@@ -502,7 +493,6 @@ const retrySendMessage = async (messageIndex: number) => { ...@@ -502,7 +493,6 @@ const retrySendMessage = async (messageIndex: number) => {
return; return;
} }
console.log('重发消息:', originalMessage.originalContent, originalMessage.originalMessageType);
// 重置原失败消息状态为正常 // 重置原失败消息状态为正常
originalMessage.status = 0; originalMessage.status = 0;
...@@ -564,29 +554,51 @@ const handleMessageClick = (message: Message, block: any) => { ...@@ -564,29 +554,51 @@ const handleMessageClick = (message: Message, block: any) => {
} }
}; };
// 重新连接SSE
const reconnectSSE = (newDialogSessionId: string) => {
console.log('开始重连SSE,新的dialogSessionId:', newDialogSessionId);
dialogSessionId.value = newDialogSessionId;
sseService.reconnectSSE(newDialogSessionId);
};
// 关闭SSE连接 // 处理SSE流式响应
const closeSSE = () => { const processSSEStreamResponse = async (stream: ReadableStream | null) => {
sseService.closeSSE(); if (!stream) {
}; console.warn('SSE流为空');
return;
}
// 初始化SSE连接 const reader = stream.getReader();
const initSSE = () => { const decoder = new TextDecoder();
sseService.initSSE(dialogSessionId.value);
try {
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value, { stream: true });
const lines = chunk.split('\n');
for (const line of lines) {
if (line.startsWith('data: ')) {
try {
const data = JSON.parse(line.slice(6)); // 移除'data: '前缀
// 使用现有的SSE消息处理函数
handleSSEMessage(data);
} catch (e) {
loading.value = false;
console.warn('解析SSE数据失败:');
}
}
}
}
} catch (error) {
loading.value = false;
console.error('处理SSE流失败:', error);
} finally {
reader.releaseLock();
}
}; };
// 组件卸载时清理所有资源 // 组件卸载时清理所有资源
onBeforeUnmount(() => { onBeforeUnmount(() => {
closeSSE();
isAIResponding.value = false; isAIResponding.value = false;
isInThinkingMode.value = false; isInThinkingMode.value = false;
sseService.destroy(); currentBlockIndex.value = -1;
}); });
// 处理历史记录数据 // 处理历史记录数据
...@@ -627,6 +639,28 @@ const getChatRecord = async (dialogSessionId: string) => { ...@@ -627,6 +639,28 @@ const getChatRecord = async (dialogSessionId: string) => {
} }
}; };
// 获取应用基本信息
const appData = ref<any>({});
const getAppInfo = async () => {
if(!props.params?.appId) {
return;
}
const response = await fetch(`${import.meta.env.VITE_SSE_PATH}/apps/getAppInfoById/${props.params?.appId}`, {
method: 'GET',
headers: {
'token': props.token,
'x-session-id': props.token,
'x-app-code': props.appCode || ''
}
});
const data = await response.json();
if (data.code === 0) {
appData.value = data.data || {};
} else {
console.error('获取应用基本信息失败:', data);
}
};
// 思考框切换 // 思考框切换
const toggleThinkBox = (messageIndex: number, blockIndex: number) => { const toggleThinkBox = (messageIndex: number, blockIndex: number) => {
if (messages.value[messageIndex] && messages.value[messageIndex].contentBlocks[blockIndex]) { if (messages.value[messageIndex] && messages.value[messageIndex].contentBlocks[blockIndex]) {
...@@ -724,11 +758,6 @@ const scrollToBottom = () => { ...@@ -724,11 +758,6 @@ const scrollToBottom = () => {
// 暴露组件方法给外部使用 // 暴露组件方法给外部使用
defineExpose({ defineExpose({
// SSE相关方法
initSSE, // 初始化SSE连接
closeSSE, // 关闭SSE连接
reconnectSSE, // 重新连接SSE
// 消息处理方法 // 消息处理方法
processHistoryData, // 处理历史记录数据 processHistoryData, // 处理历史记录数据
...@@ -747,15 +776,11 @@ defineExpose({ ...@@ -747,15 +776,11 @@ defineExpose({
// 生命周期 // 生命周期
onMounted(() => { onMounted(() => {
console.log('组件挂载,初始 dialogSessionId:', props.dialogSessionId); console.log('组件挂载,初始 dialogSessionId:', props.dialogSessionId);
initSSE();
scrollToBottom(); scrollToBottom();
if (props.dialogSessionId) { if (props.dialogSessionId) {
getChatRecord(props.dialogSessionId); getChatRecord(props.dialogSessionId);
} }
}); getAppInfo();
onBeforeUnmount(() => {
closeSSE();
}); });
// 模拟折线图消息 // 模拟折线图消息
......
...@@ -122,6 +122,7 @@ li { ...@@ -122,6 +122,7 @@ li {
color: @gray-6; color: @gray-6;
line-height: 1.5; line-height: 1.5;
margin-bottom: 30px; margin-bottom: 30px;
word-break: keep-all;
} }
.start-chat-btn { .start-chat-btn {
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment