Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
水玉婷
ai-wechat
Commits
567853a3
Commit
567853a3
authored
Jan 29, 2026
by
水玉婷
Browse files
feat:添加sse断开后消息重发
parent
82acfe7e
Changes
4
Hide whitespace changes
Inline
Side-by-side
src/views/components/AiChat.vue
View file @
567853a3
...
@@ -73,6 +73,15 @@
...
@@ -73,6 +73,15 @@
</div>
</div>
</template>
</template>
</div>
</div>
<!-- 失败状态重发按钮 -->
<div
v-if=
"msg.status === -1"
class=
"message-failed-wrapper"
>
<div
class=
"failed-indicator"
>
<span
class=
"failed-text"
>
发送失败
</span>
<a-button
class=
"retry-button"
@
click=
"retrySendMessage(index)"
title=
"重新发送"
>
<reload-outlined
/>
</a-button>
</div>
</div>
<!-- 操作按钮 -->
<!-- 操作按钮 -->
<!-- <div class="operation-box" v-if="msg.recordId">
<!-- <div class="operation-box" v-if="msg.recordId">
<p>
<p>
...
@@ -110,7 +119,7 @@
...
@@ -110,7 +119,7 @@
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
,
UpOutlined
,
DownOutlined
}
from
'
@ant-design/icons-vue
'
;
import
{
SendOutlined
,
UserOutlined
,
UpOutlined
,
DownOutlined
,
ReloadOutlined
}
from
'
@ant-design/icons-vue
'
;
import
defaultAvatar
from
'
@/assets/logo.png
'
;
import
defaultAvatar
from
'
@/assets/logo.png
'
;
import
ChartComponent
from
'
./ChartComponent.vue
'
;
// 导入独立的图表组件
import
ChartComponent
from
'
./ChartComponent.vue
'
;
// 导入独立的图表组件
import
VoiceRecognition
from
'
./VoiceRecognition.vue
'
;
// 导入语音识别组件
import
VoiceRecognition
from
'
./VoiceRecognition.vue
'
;
// 导入语音识别组件
...
@@ -379,6 +388,9 @@ const sendMessage = async (type: MessageType = 'text', params: MessageParams = {
...
@@ -379,6 +388,9 @@ const sendMessage = async (type: MessageType = 'text', params: MessageParams = {
totalTokens
:
0
,
totalTokens
:
0
,
date
:
dayjs
().
format
(
'
HH:mm
'
),
date
:
dayjs
().
format
(
'
HH:mm
'
),
contentBlocks
:
[]
as
any
[],
contentBlocks
:
[]
as
any
[],
status
:
1
,
// 发送中状态
originalContent
:
messageContent
,
// 保存原始内容用于重发
originalMessageType
:
type
// 保存消息类型用于重发
};
};
switch
(
type
)
{
switch
(
type
)
{
...
@@ -417,22 +429,6 @@ const sendMessage = async (type: MessageType = 'text', params: MessageParams = {
...
@@ -417,22 +429,6 @@ const sendMessage = async (type: MessageType = 'text', params: MessageParams = {
await
nextTick
();
await
nextTick
();
scrollToBottom
();
scrollToBottom
();
// 检查SSE连接状态,如果连接断开或连接中则触发重连
const
connectionState
=
sseService
.
getConnectionState
();
console
.
log
(
'
SSE连接状态:
'
,
connectionState
);
// EventSource readyState: 0=CONNECTING, 1=OPEN, 2=CLOSED
if
(
connectionState
===
2
)
{
// 连接已关闭
sseService
.
reconnectSSE
(
dialogSessionId
.
value
);
const
simulatedMessage
=
{
message
:
'
检测到SSE连接错误,触发重连
'
,
status
:
-
1
,
// 错误状态
type
:
0
// 错误类型
};
// 调用handleSSEMessage处理模拟消息
return
handleSSEMessage
(
simulatedMessage
,
true
);
}
try
{
try
{
// 调用外部传入的消息发送函数
// 调用外部传入的消息发送函数
if
(
props
.
onMessageSend
)
{
if
(
props
.
onMessageSend
)
{
...
@@ -465,15 +461,174 @@ const sendMessage = async (type: MessageType = 'text', params: MessageParams = {
...
@@ -465,15 +461,174 @@ const sendMessage = async (type: MessageType = 'text', params: MessageParams = {
const
data
=
await
response
.
json
();
const
data
=
await
response
.
json
();
if
(
data
.
code
===
0
)
{
if
(
data
.
code
===
0
)
{
console
.
log
(
`发送成功`
);
console
.
log
(
`发送成功`
);
// 发送成功,更新消息状态为已发送
if
(
messageData
)
{
messageData
.
status
=
2
;
}
}
else
if
(
data
.
code
===
-
100
){
// 处理-100错误码,触发重连
sseService
.
reconnectSSE
(
dialogSessionId
.
value
);
// 设置当前消息为失败状态
if
(
messageData
)
{
messageData
.
status
=
-
1
;
}
const
simulatedMessage
=
{
message
:
'
检测到SSE连接错误,触发重连,请重新发送消息
'
,
status
:
-
1
,
// 错误状态
type
:
0
// 错误类型
};
// 调用handleSSEMessage处理模拟消息
return
handleSSEMessage
(
simulatedMessage
,
true
);
}
}
}
}
}
catch
(
e
)
{
}
catch
(
e
)
{
loading
.
value
=
false
;
console
.
error
(
`发送失败:`
,
e
);
console
.
error
(
`发送失败:`
,
e
);
}
finally
{
}
finally
{
loading
.
value
=
false
;
loading
.
value
=
false
;
}
}
};
};
// 重发消息
const
retrySendMessage
=
async
(
messageIndex
:
number
)
=>
{
const
originalMessage
=
messages
.
value
[
messageIndex
];
if
(
!
originalMessage
||
originalMessage
.
status
!==
-
1
)
{
console
.
log
(
'
消息不存在或不是失败状态,无需重发
'
);
return
;
}
console
.
log
(
'
重发消息:
'
,
originalMessage
.
originalContent
,
originalMessage
.
originalMessageType
);
try
{
// 创建新消息进行重发,保留原失败消息
// const messageContent = '出差标准'
const
messageContent
=
originalMessage
.
originalContent
;
const
messageType
=
originalMessage
.
originalMessageType
;
originalMessage
.
status
=
0
;
// 重置原失败消息状态为正常
loading
.
value
=
true
;
isAIResponding
.
value
=
false
;
isInThinkingMode
.
value
=
false
;
currentAIResponse
.
value
=
null
;
// 创建新消息
const
newMessageData
=
{
messageType
:
'
sent
'
,
avatar
:
'
我
'
,
recordId
:
''
,
promptTokens
:
0
,
completionTokens
:
0
,
totalTokens
:
0
,
date
:
dayjs
().
format
(
'
HH:mm
'
),
contentBlocks
:
[]
as
any
[],
status
:
1
,
// 发送中状态
originalContent
:
messageContent
,
// 保存原始内容用于重发
originalMessageType
:
messageType
// 保存消息类型用于重发
};
switch
(
messageType
)
{
case
'
text
'
:
newMessageData
.
contentBlocks
.
push
({
content
:
templateService
.
generateTextTemplate
(
messageContent
),
thinkContent
:
''
,
hasThinkBox
:
false
,
thinkBoxExpanded
:
false
,
});
break
;
case
'
audio
'
:
newMessageData
.
contentBlocks
.
push
({
audioData
:
{
src
:
messageContent
,
durationTime
:
0
},
thinkContent
:
''
,
hasThinkBox
:
false
,
thinkBoxExpanded
:
false
,
});
break
;
default
:
loading
.
value
=
false
;
return
;
}
// 添加新消息到聊天记录
messages
.
value
.
push
(
newMessageData
);
await
nextTick
();
scrollToBottom
();
// 调用外部传入的消息发送函数
if
(
props
.
onMessageSend
)
{
const
sendContent
=
messageType
===
'
audio
'
?
messageContent
:
messageContent
;
console
.
log
(
`调用外部发送函数重发消息`
,
sendContent
);
await
props
.
onMessageSend
(
sendContent
);
// 发送成功后更新新消息状态为已发送,并重置原失败消息状态为正常
newMessageData
.
status
=
2
;
}
else
{
// 默认的API调用逻辑
console
.
log
(
`默认API调用逻辑重发消息`
,
dialogSessionId
.
value
);
const
requestData
=
messageType
===
'
audio
'
?
{
questionLocalAudioFilePath
:
messageContent
,
audioDuration
:
0
,
...
props
.
params
,
}
:
{
question
:
messageContent
,
...
props
.
params
,
};
const
response
=
await
fetch
(
`
${
import
.
meta
.
env
.
VITE_SSE_PATH
}
/ask/app/
${
props
.
params
?.
appId
}
`
,
{
method
:
'
POST
'
,
headers
:
{
'
Content-Type
'
:
'
application/json
'
,
'
token
'
:
props
.
token
,
'
x-session-id
'
:
props
?.
token
,
'
x-app-code
'
:
props
.
appCode
||
''
},
body
:
JSON
.
stringify
(
requestData
)
});
const
data
=
await
response
.
json
();
if
(
data
.
code
===
0
)
{
console
.
log
(
`重发成功`
);
// 发送成功,更新新消息状态为已发送,并重置原失败消息状态为正常
newMessageData
.
status
=
2
;
originalMessage
.
status
=
0
;
// 重置原失败消息状态为正常
}
else
if
(
data
.
code
===
-
100
)
{
// 处理-100错误码,触发重连
sseService
.
reconnectSSE
(
dialogSessionId
.
value
);
// 设置新消息为失败状态
newMessageData
.
status
=
-
1
;
const
simulatedMessage
=
{
message
:
'
检测到SSE连接错误,触发重连,请重新发送消息
'
,
status
:
-
1
,
// 错误状态
type
:
0
// 错误类型
};
// 调用handleSSEMessage处理模拟消息
return
handleSSEMessage
(
simulatedMessage
,
true
);
}
else
{
// 其他错误码,设置新消息为失败状态
newMessageData
.
status
=
-
1
;
console
.
error
(
`重发失败,错误码:
${
data
.
code
}
`
);
}
}
}
catch
(
error
)
{
console
.
error
(
'
重发消息失败:
'
,
error
);
// 如果创建了新消息但发送失败,可以将其状态设置为失败
if
(
newMessageData
)
{
newMessageData
.
status
=
-
1
;
}
}
finally
{
loading
.
value
=
false
;
}
};
// 重新连接SSE
// 重新连接SSE
const
reconnectSSE
=
(
newDialogSessionId
:
string
)
=>
{
const
reconnectSSE
=
(
newDialogSessionId
:
string
)
=>
{
console
.
log
(
'
开始重连SSE,新的dialogSessionId:
'
,
newDialogSessionId
);
console
.
log
(
'
开始重连SSE,新的dialogSessionId:
'
,
newDialogSessionId
);
...
...
src/views/components/TableComponent.vue
View file @
567853a3
...
@@ -264,7 +264,6 @@ const renderCellContent = (header: string, value: any, record: any) => {
...
@@ -264,7 +264,6 @@ const renderCellContent = (header: string, value: any, record: any) => {
.message-chart
{
.message-chart
{
width
:
100%
;
width
:
100%
;
max-width
:
100%
;
max-width
:
100%
;
margin
:
0
0
16px
;
}
}
.table-title
{
.table-title
{
...
...
src/views/components/style.less
View file @
567853a3
...
@@ -301,6 +301,43 @@ li {
...
@@ -301,6 +301,43 @@ li {
margin-top: 16px;
margin-top: 16px;
}
}
// 失败消息样式
.message-failed-wrapper {
margin-top: 8px;
.failed-indicator {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 8px;
font-size: 12px;
color: @error-color;
.failed-text {
color: @error-color;
}
.retry-button {
background: @error-bg;
border: 1px solid @error-border;
color: @error-color;
padding: 4px;
border-radius: 4px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
width: 24px;
height: 24px;
&:hover {
background: @error-color;
color: white;
}
}
}
}
// 错误消息样式
// 错误消息样式
:deep(.message-error) {
:deep(.message-error) {
line-height: 1.5;
line-height: 1.5;
...
@@ -915,6 +952,9 @@ li {
...
@@ -915,6 +952,9 @@ li {
color: @gray-9;
color: @gray-9;
background: none;
background: none;
white-space: initial;
white-space: initial;
br{
display: contents;
}
// 标题样式
// 标题样式
h1,
h1,
...
...
src/views/components/utils/contentTemplateService.ts
View file @
567853a3
...
@@ -48,6 +48,12 @@ export interface Message {
...
@@ -48,6 +48,12 @@ export interface Message {
date
:
string
;
date
:
string
;
customClass
?:
string
;
customClass
?:
string
;
contentBlocks
:
MessageBlock
[];
contentBlocks
:
MessageBlock
[];
// 消息状态:-1=失败,0=正常,1=发送中,2=已发送
status
?:
number
;
// 原始消息内容(用于重发)
originalContent
?:
string
;
// 消息类型(用于重发)
originalMessageType
?:
'
text
'
|
'
audio
'
;
}
}
// SSE数据类型定义
// SSE数据类型定义
...
...
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment