@@ -4,32 +4,32 @@
<!-- 消息列表 ( 唯一滚动区 ) -- >
< div ref = "listRef" class = "flex-1 overflow-y-auto px-6 py-6 space-y-6" >
< div v-for = "msg in chatMsgList" :key="msg.msg Id" class="flex items-start gap-3"
: class = "msg.msg Role === MessageRole.ME ? 'justify-end' : 'justify-start'" >
< div v-for = "msg in chatMsgList" :key="msg.message Id" class="flex items-start gap-3"
: class = "msg.message Role === MessageRole.ME ? 'justify-end' : 'justify-start'" >
<!-- AI avatar -- >
< img v-if = "msg.msg Role === MessageRole.AI" class="w-9 h-9 rounded-full shrink-0"
< img v-if = "msg.message Role === MessageRole.AI" class="w-9 h-9 rounded-full shrink-0"
src = "@assets/images/login/blue_logo.png" / >
<!-- 消息气泡 -- >
< div class = "max-w-[70%]" >
< div class = "flex items-start gap-2 pt-0.5 mb-2"
: class = "msg.msg Role === MessageRole.ME ? 'flex-row-reverse' : 'flex-row'" >
: class = "msg.message Role === MessageRole.ME ? 'flex-row-reverse' : 'flex-row'" >
< span class = "text-xs text-[#4E5969]" > ZHINIAN < / span >
< span class = "text-xs text-[#86909C]" > 20 : 30 < / span >
< / div >
< div class = "text-sm text-gray-700"
: class = "msg.msg Role === MessageRole.ME ? 'bg-[#f7f9fc] rounded-md px-2 py-2' : ''" >
{ { msg . msg } }
: class = "msg.message Role === MessageRole.ME ? 'bg-[#f7f9fc] rounded-md px-2 py-2' : ''" >
{ { msg . messageContent } }
< / div >
<!-- AI 标识 -- >
< div v-if = "msg.msg Role === MessageRole.AI" class="mt-2 text-xs text-gray-400 " >
< div v-if = "msg.message Role === MessageRole.AI" class="mt-2 text-xs text-gray-400 " >
本回答由 AI 生成
< / div >
< ! - - AI 操作按钮 - - >
< div v-if = "msg.msg Role === MessageRole.AI"
< div v-if = "msg.message Role === MessageRole.AI"
class = "mt-4 text-gray-500 flex items-center justify-between gap-4 " >
< RiFileCopyLine size = "16px" @click ="copyFileClick(msg)" / >
< div class = "flex items-center gap-4" >
@@ -43,7 +43,7 @@
< / div >
<!-- User avatar -- >
< img v-if = "msg.msg Role === MessageRole.ME" class="w-9 h-9 rounded-full shrink-0"
< img v-if = "msg.message Role === MessageRole.ME" class="w-9 h-9 rounded-full shrink-0"
src = "@assets/images/login/user_icon.png" / >
< / div >
< / div >
@@ -82,7 +82,7 @@ import { ref } from 'vue'
import { RiLink , RiSendPlaneFill , RiFileCopyLine , RiShareForwardLine , RiDownload2Line , RiThumbUpLine , RiThumbDownLine } from '@remixicon/vue'
import { onMounted , nextTick , onUnmounted } from "vue" ;
import { WebSocketManager } from "@common/WebSocketManager" ;
import { MessageRole , MessageType , ChatMessage } from "./model/ChatModel" ;
import { MessageRole , ChatMessage } from "./model/ChatModel" ;
import { ThrottleUtils , IdUtils } from "@common/index" ;
import { Session } from '../../utils/storage' ;
@@ -108,8 +108,8 @@ const agentId = ref("1");
const conversationId = ref ( "" ) ;
// 会话进行中标志
const isSessionActive = ref ( false ) ;
/// 指令
let commonType = "" ;
/// 指令通用消息类型
let commonTypeMessage : string = "" ;
// WebSocket 相关
let webSocketManager : WebSocketManager | null = null ;
@@ -159,7 +159,7 @@ const handleReplyText = (text: string) => {
const handleReplyInstruct = async ( message : string , type : string ) => {
// await checkToken();
commonType = type ;
commonTypeMessage = type ;
// 重置消息状态, 准备接收新的AI回复
resetMessageState ( ) ;
sendMessage ( message , true ) ;
@@ -214,7 +214,7 @@ const initHandler = () => {
const getAccessToken = ( ) => {
// 从本地存储获取 token
return 'cLKaO8WiZfFrxFmpsHnuy6STXndBMxnCpFem1AOM3b_LDEqkbQsUw5laUYv5mnUmgZcp2bIdnfKlAs7SPb6OvGlCf-TJrt0ez9OHULDzxG5Zfv63RR12a-s4nwD0LXeX ';
return Session . get ( 'token' ) || ' ';
} ;
const checkToken = async ( ) => {
@@ -335,7 +335,7 @@ const handleWebSocketMessage = (data: any) => {
} else {
// 向后搜索最近的 AI 消息
for ( let i = chatMsgList . value . length - 1 ; i >= 0 ; i -- ) {
if ( chatMsgList . value [ i ] && chatMsgList . value [ i ] . msg Role === MessageRole . AI ) {
if ( chatMsgList . value [ i ] && chatMsgList . value [ i ] . message Role === MessageRole . AI ) {
aiMsgIndex = i ;
break ;
}
@@ -350,23 +350,23 @@ const handleWebSocketMessage = (data: any) => {
if ( data . content ) {
if ( chatMsgList . value [ aiMsgIndex ] . isLoading ) {
// 首次收到内容:替换“加载中”文案并取消 loading 状态(恢复原始渲染逻辑)
chatMsgList . value [ aiMsgIndex ] . msg = data . content ;
chatMsgList . value [ aiMsgIndex ] . messageContent = data . content ;
chatMsgList . value [ aiMsgIndex ] . isLoading = false ;
} else {
// 后续流式内容追加
chatMsgList . value [ aiMsgIndex ] . msg += data . content ;
chatMsgList . value [ aiMsgIndex ] . messageContent += data . content ;
}
nextTick ( ( ) => scrollToBottom ( ) ) ;
}
// 处理完成状态
if ( data . finish ) {
const msg = chatMsgList . value [ aiMsgIndex ] . msg ;
const msg = chatMsgList . value [ aiMsgIndex ] . messageContent ;
if ( ! msg || chatMsgList . value [ aiMsgIndex ] . isLoading ) {
chatMsgList . value [ aiMsgIndex ] . msg = "未获取到内容,请重试" ;
chatMsgList . value [ aiMsgIndex ] . messageContent = "未获取到内容,请重试" ;
chatMsgList . value [ aiMsgIndex ] . isLoading = false ;
if ( data . toolCall ) {
chatMsgList . value [ aiMsgIndex ] . msg = "" ;
chatMsgList . value [ aiMsgIndex ] . messageContent = "" ;
}
}
@@ -455,14 +455,9 @@ const sendMessage = async (message: string, isInstruct: boolean = false) => {
}
isSessionActive . value = true ;
const newMsg : ChatMessage = {
msgId : ` msg_ ${ chatMsgList . value . length } ` ,
msgRole : MessageRole . ME ,
msg : message ,
msgContent : {
type : MessageType . TEXT ,
text : message ,
} ,
messageId : IdUtils . generateMessageId ( ) ,
messageRole : MessageRole . ME ,
messageContent : message ,
} ;
chatMsgList . value . push ( newMsg ) ;
inputMessage . value = "" ;
@@ -473,7 +468,7 @@ const sendMessage = async (message: string, isInstruct: boolean = false) => {
} ;
// 通用WebSocket消息发送函数 -> 返回 Promise<boolean>
const sendWebSocketMessage = async ( messageType : number , messageContent : any , options : any = { } ) => {
const sendWebSocketMessage = async ( messageType : number , messageContent : string , options : any = { } ) => {
const args = {
conversationId : conversationId . value ,
agentId : agentId . value ,
@@ -482,7 +477,7 @@ const sendWebSocketMessage = async (messageType: number, messageContent: any, op
messageId : options . messageId || currentSessionMessageId ,
} ;
/// 重试机制参数
const maxRetries = typeof options . retries === 'number' ? options . retries : 3 ;
const baseDelay = typeof options . baseDelay === 'number' ? options . baseDelay : 300 ; // ms
const maxDelay = typeof options . maxDelay === 'number' ? options . maxDelay : 5000 ; // ms
@@ -571,8 +566,8 @@ const sendChat = async (message: string, isInstruct = false) => {
isSessionActive . value = connected || false ;
// 更新AI消息状态
const aiMsgIndex = chatMsgList . value . length - 1 ;
if ( aiMsgIndex >= 0 && chatMsgList . value [ aiMsgIndex ] . msg Role === MessageRole . AI ) {
chatMsgList . value [ aiMsgIndex ] . msg = connected ? "" : "发送消息失败,请重试" ;
if ( aiMsgIndex >= 0 && chatMsgList . value [ aiMsgIndex ] . message Role === MessageRole . AI ) {
chatMsgList . value [ aiMsgIndex ] . messageContent = connected ? "" : "发送消息失败,请重试" ;
chatMsgList . value [ aiMsgIndex ] . isLoading = connected || false ;
}
if ( connected ) {
@@ -585,22 +580,18 @@ const sendChat = async (message: string, isInstruct = false) => {
return ;
}
/// 发送消息类型 指令/对话文本
const messageType = isInstruct ? 1 : 0 ;
const messageContent = isInstruct ? commonType : message ;
const messageContent = isInstruct ? commonTypeMessage : message ;
// 生成 messageId 并保存到当前会话变量( stopRequest 可能使用)
currentSessionMessageId = IdUtils . generateMessageId ( ) ;
// 插入AI消息, 并在 pendingMap 中记录
const aiMsg : ChatMessage = {
msgId : ` msg_ ${ chatMsgList . value . length } ` ,
msgRole : MessageRole . AI ,
msg : "加载中" ,
isLoading : true ,
msgContent : {
type : MessageType . TEXT ,
text : "" ,
} ,
messageId : currentSessionMessageId ,
messageRole : MessageRole . AI ,
messageContent : "加载中" ,
isLoading : true ,
} ;
chatMsgList . value . push ( aiMsg ) ;
@@ -615,7 +606,7 @@ const sendChat = async (message: string, isInstruct = false) => {
const timeoutId = setTimeout ( ( ) => {
const idx = pendingMap . get ( currentSessionMessageId ) ;
if ( idx != null && chatMsgList . value [ idx ] && chatMsgList . value [ idx ] . isLoading ) {
chatMsgList . value [ idx ] . msg = "请求超时,请重试" ;
chatMsgList . value [ idx ] . messageContent = "请求超时,请重试" ;
chatMsgList . value [ idx ] . isLoading = false ;
pendingMap . delete ( currentSessionMessageId ) ;
pendingTimeouts . delete ( currentSessionMessageId ) ;
@@ -630,7 +621,7 @@ const sendChat = async (message: string, isInstruct = false) => {
if ( ! success ) {
const idx = pendingMap . get ( currentSessionMessageId ) ;
if ( idx != null && chatMsgList . value [ idx ] ) {
chatMsgList . value [ idx ] . msg = "发送消息失败,请重试" ;
chatMsgList . value [ idx ] . messageContent = "发送消息失败,请重试" ;
chatMsgList . value [ idx ] . isLoading = false ;
}
// 清理 pending
@@ -663,14 +654,14 @@ const stopRequest = async () => {
}
if ( chatMsgList . value [ aiMsgIndex ] &&
chatMsgList . value [ aiMsgIndex ] . msg Role === MessageRole . AI ) {
chatMsgList . value [ aiMsgIndex ] . message Role === MessageRole . AI ) {
chatMsgList . value [ aiMsgIndex ] . isLoading = false ;
if ( chatMsgList . value [ aiMsgIndex ] . msg &&
chatMsgList . value [ aiMsgIndex ] . msg . trim ( ) &&
! chatMsgList . value [ aiMsgIndex ] . msg . startsWith ( "加载中" ) ) {
if ( chatMsgList . value [ aiMsgIndex ] . messageContent &&
chatMsgList . value [ aiMsgIndex ] . messageContent . trim ( ) &&
! chatMsgList . value [ aiMsgIndex ] . messageContent . startsWith ( "加载中" ) ) {
// 保留已显示内容
} else {
chatMsgList . value [ aiMsgIndex ] . msg = "请求已停止" ;
chatMsgList . value [ aiMsgIndex ] . messageContent = "请求已停止" ;
}
}