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

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

parent d9bbddfb
......@@ -93,7 +93,7 @@
<!-- 没有更多数据提示 -->
<div v-if="!hasMore && recentSessions.length > 0" class="no-more-data">
<span>没有更多历史记录</span>
<span>没有更多数据</span>
</div>
<!-- 空状态 -->
......@@ -140,7 +140,6 @@ import defaultAvatar from '@/assets/logo2.png';
import menuIcon from '@/assets/sidebar-unfold-line.svg'
import newChatIcon from '@/assets/chat-ai-line.svg'
import {
PlusOutlined,
SearchOutlined,
DeleteOutlined,
FileSearchOutlined
......
......@@ -14,8 +14,8 @@
<div class="chat-intro-center" v-if="!props?.dialogSessionId && !hasStartedConversation">
<div class="intro-content">
<img :src="defaultAvatar" alt="avatar" class="avatar-image" />
<h3>嗨,我是国械小智</h3>
<p>我可以帮您搜索数据、查找流程制度,<br/>请把问题交给我吧</p>
<h3>嗨,我是{{ appData?.app_name || '国械小智' }}</h3>
<p>{{ appData?.ai_app_prop?.prologue }}</p>
</div>
</div>
......@@ -126,14 +126,13 @@
import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue';
import dayjs from 'dayjs';
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 defaultAvatar from '@/assets/logo.png';
import rightIcon from '@/assets/right.svg'
import thinkIcon from '@/assets/think.svg'
import ChartComponent from './ChartComponent.vue'; // 导入独立的图表组件
import VoiceRecognitionText from './VoiceRecognitionText.vue'; // 导入语音识别组件
import { createSSEService, type SSEData } from './utils/sseService'; // 导入SSE服务
import { createContentTemplateService, type Message } from './utils/contentTemplateService'; // 导入模板服务
// 定义组件属性接口
......@@ -175,6 +174,12 @@ const props = withDefaults(defineProps<Props>(), {
})
});
// 定义SSE数据接口
export interface SSEData {
message: any;
status: number | string;
type: number | string;
}
// 响应式数据
const messageText = ref('');
......@@ -272,37 +277,21 @@ const handleSSEMessage = (data: SSEData, isSimulated: boolean = false) => {
dialogSessionId.value = result.newDialogSessionId;
}
// 在收到status: 29(消息结束)时以及status: -1(错误)时,设置loading为false
if (data.status === 29 || data.status === -1) {
loading.value = false;
}
nextTick(() => {
scrollToBottom();
});
} catch (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();
......@@ -452,13 +441,15 @@ const sendMessage = async (type: MessageType = 'text', params: MessageParams = {
audioDuration: durationTime,
...props.params,
dialogSessionId: dialogSessionId.value,
appId: props.params?.appId,
} : {
question: messageContent,
...props.params,
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',
headers: {
'Content-Type': 'application/json',
......@@ -469,17 +460,17 @@ const sendMessage = async (type: MessageType = 'text', params: MessageParams = {
body: JSON.stringify(requestData)
});
const data = await response.json();
if (data.code === 0) {
console.log(`发送成功`);
// 检查响应是否为SSE流式响应
if (response.ok && response.headers.get('content-type')?.includes('text/event-stream')) {
// 处理SSE流式响应
await processSSEStreamResponse(response.body);
console.log(`发送成功`)
// 发送成功,更新消息状态为已发送
if (messageData) {
messageData.status = 2;
}
}else if(data.code === -100){
// 处理-100错误码,触发重连
sseService.reconnectSSE(dialogSessionId.value);
}else{
loading.value = false;
// 设置当前消息为失败状态
if (messageData) {
messageData.status = -1;
......@@ -502,7 +493,6 @@ const retrySendMessage = async (messageIndex: number) => {
return;
}
console.log('重发消息:', originalMessage.originalContent, originalMessage.originalMessageType);
// 重置原失败消息状态为正常
originalMessage.status = 0;
......@@ -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连接
const closeSSE = () => {
sseService.closeSSE();
};
// 处理SSE流式响应
const processSSEStreamResponse = async (stream: ReadableStream | null) => {
if (!stream) {
console.warn('SSE流为空');
return;
}
// 初始化SSE连接
const initSSE = () => {
sseService.initSSE(dialogSessionId.value);
const reader = stream.getReader();
const decoder = new TextDecoder();
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(() => {
closeSSE();
isAIResponding.value = false;
isInThinkingMode.value = false;
sseService.destroy();
currentBlockIndex.value = -1;
});
// 处理历史记录数据
......@@ -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) => {
if (messages.value[messageIndex] && messages.value[messageIndex].contentBlocks[blockIndex]) {
......@@ -724,11 +758,6 @@ const scrollToBottom = () => {
// 暴露组件方法给外部使用
defineExpose({
// SSE相关方法
initSSE, // 初始化SSE连接
closeSSE, // 关闭SSE连接
reconnectSSE, // 重新连接SSE
// 消息处理方法
processHistoryData, // 处理历史记录数据
......@@ -747,15 +776,11 @@ defineExpose({
// 生命周期
onMounted(() => {
console.log('组件挂载,初始 dialogSessionId:', props.dialogSessionId);
initSSE();
scrollToBottom();
if (props.dialogSessionId) {
getChatRecord(props.dialogSessionId);
}
});
onBeforeUnmount(() => {
closeSSE();
getAppInfo();
});
// 模拟折线图消息
......
......@@ -122,6 +122,7 @@ li {
color: @gray-6;
line-height: 1.5;
margin-bottom: 30px;
word-break: keep-all;
}
.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