fix(ai-assistant): 优化流式响应期间的 loading 状态显示

- 修改 loading 显示条件,streaming 状态时继续显示 loading icon
  - SSEClient 添加可选的 onFailed 回调,处理连接失败情况
  - 修复 done 事件处理,确保状态正确转为 completed
  - 解决工具调用期间 loading 动画过早消失的问题
This commit is contained in:
kuaifan 2026-01-18 14:44:03 +00:00
parent c4f0fb5a3d
commit 8eaba6f364
2 changed files with 20 additions and 11 deletions

View File

@ -58,7 +58,7 @@
<template v-if="response.status === 'error'"> <template v-if="response.status === 'error'">
<span class="ai-assistant-output-error">{{ $L('发送失败') }}</span> <span class="ai-assistant-output-error">{{ $L('发送失败') }}</span>
</template> </template>
<template v-else-if="response.rawOutput"> <template v-else-if="response.rawOutput && response.status !== 'streaming'">
<Button <Button
v-if="showApplyButton" v-if="showApplyButton"
type="primary" type="primary"
@ -69,9 +69,9 @@
{{ applyButtonText || $L('应用此内容') }} {{ applyButtonText || $L('应用此内容') }}
</Button> </Button>
</template> </template>
<template v-else> <template v-else-if="!response.rawOutput || response.status === 'streaming'">
<Icon type="ios-loading" class="ai-assistant-output-icon icon-loading"/> <Icon type="ios-loading" class="ai-assistant-output-icon icon-loading"/>
<span v-if="loadingText" class="ai-assistant-output-status">{{ loadingText }}</span> <span v-if="loadingText && !response.rawOutput" class="ai-assistant-output-status">{{ loadingText }}</span>
</template> </template>
</div> </div>
<div class="ai-assistant-output-meta"> <div class="ai-assistant-output-meta">
@ -795,7 +795,7 @@ export default {
const donePayload = this.parseStreamPayload(event); const donePayload = this.parseStreamPayload(event);
if (donePayload && donePayload.error) { if (donePayload && donePayload.error) {
this.markResponseError(responseEntry, donePayload.error); this.markResponseError(responseEntry, donePayload.error);
} else if (responseEntry && responseEntry.status !== 'error' && responseEntry.rawOutput) { } else if (responseEntry && responseEntry.status !== 'error') {
responseEntry.status = 'completed'; responseEntry.status = 'completed';
} }
this.releaseSSEClient(sse); this.releaseSSEClient(sse);
@ -803,6 +803,13 @@ export default {
this.saveCurrentSession(); this.saveCurrentSession();
break; break;
} }
}, () => {
// SSE
if (responseEntry && responseEntry.status === 'streaming') {
responseEntry.status = 'completed';
}
this.releaseSSEClient(sse);
this.saveCurrentSession();
}); });
return sse; return sse;
}, },

View File

@ -118,42 +118,44 @@ export class SSEClient {
}; };
} }
_onError(type, handler) { _onError(type, handler, onFailed) {
return () => { return () => {
if (window.systemInfo.debug === "yes") { if (window.systemInfo.debug === "yes") {
console.log("SSE retry: " + this.url); console.log("SSE retry: " + this.url);
} }
if (this.es) { if (this.es) {
this._removeAllEvent(type, handler); this._removeAllEvent(type, handler, onFailed);
this.unsunscribe(); this.unsunscribe();
} }
if (this.retry > 0) { if (this.retry > 0) {
this.retry--; this.retry--;
this.timer = setTimeout(() => { this.timer = setTimeout(() => {
this.subscribe(type, handler); this.subscribe(type, handler, onFailed);
}, this.options.interval); }, this.options.interval);
} else if (typeof onFailed === 'function') {
onFailed();
} }
}; };
} }
_removeAllEvent(type, handler) { _removeAllEvent(type, handler, onFailed) {
type = $A.isArray(type) ? type : [type] type = $A.isArray(type) ? type : [type]
this.es.removeEventListener("open", this._onOpen); this.es.removeEventListener("open", this._onOpen);
type.some(item => { type.some(item => {
this.es.removeEventListener(item, this._onMessage(item, handler)); this.es.removeEventListener(item, this._onMessage(item, handler));
}) })
this.es.removeEventListener("error", this._onError(type, handler)); this.es.removeEventListener("error", this._onError(type, handler, onFailed));
} }
subscribe(type, handler) { subscribe(type, handler, onFailed) {
type = $A.isArray(type) ? type : [type] type = $A.isArray(type) ? type : [type]
this.es = new EventSource(this.url); this.es = new EventSource(this.url);
this.es.addEventListener("open", this._onOpen); this.es.addEventListener("open", this._onOpen);
type.some(item => { type.some(item => {
this.es.addEventListener(item, this._onMessage(item, handler)); this.es.addEventListener(item, this._onMessage(item, handler));
}) })
this.es.addEventListener("error", this._onError(type, handler)); this.es.addEventListener("error", this._onError(type, handler, onFailed));
} }
unsunscribe() { unsunscribe() {