kuaifan 8eaba6f364 fix(ai-assistant): 优化流式响应期间的 loading 状态显示
- 修改 loading 显示条件,streaming 状态时继续显示 loading icon
  - SSEClient 添加可选的 onFailed 回调,处理连接失败情况
  - 修复 done 事件处理,确保状态正确转为 completed
  - 解决工具调用期间 loading 动画过早消失的问题
2026-01-18 14:44:03 +00:00

174 lines
4.8 KiB
JavaScript
Vendored
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* @param key
* @param requestData
* @param state
* @returns {$callData}
*/
function __callData(key, requestData, state) {
if (!$A.isJson(requestData)) {
requestData = {}
}
const callKey = key + "::" + encodeURIComponent(new URLSearchParams($A.sortObject(requestData, [
'page',
'pagesize',
'timerange',
])).toString())
const callData = state.callAt.find(item => item.key === callKey) || {}
callData.__last = $A.dayjs().unix()
if (typeof callData.key === "undefined") {
callData.key = callKey
callData.updated = 0
callData.deleted = 0
state.callAt.push(callData)
$A.IDBSet("callAt", state.callAt).catch(_ => { })
}
/**
* @returns {*}
*/
this.get = () => {
requestData.timerange = requestData.timerange || `${callData.updated || 0},${callData.deleted || 0}`
return requestData
}
/**
* @param total
* @param current_page
* @param deleted_id
* @returns {Promise<unknown>}
*/
this.save = ({total, current_page, deleted_id}) => {
return new Promise(async resolve => {
if (current_page !== 1) {
return
}
let hasUpdate = false
const time = callData.__last || $A.dayjs().unix()
if (total > 0) {
callData.updated = time
hasUpdate = true
}
if ($A.isArray(deleted_id) && deleted_id.length > 0) {
callData.deleted = time
hasUpdate = true
} else {
deleted_id = []
}
if ($A.isEEUIApp) {
hasUpdate = state.isFirstPage
}
if (hasUpdate) {
if ($A.isSubElectron || ($A.isEEUIApp && !state.isFirstPage)) {
// 子窗口Electron、不是第一个页面App 不保存
} else {
await $A.IDBSet("callAt", state.callAt)
}
}
resolve(deleted_id)
})
}
return this
}
export function $callData(key, requestData, state) {
return new __callData(key, requestData, state)
}
export function $urlSafe(value, encode = true) {
if (value) {
if (encode) {
value = String(value).replace(/\+/g, "-").replace(/\//g, "_").replace(/\n/g, '$')
} else {
value = String(value).replace(/\-/g, "+").replace(/\_/g, "/").replace(/\$/g, '\n')
}
}
return value
}
/**
* EventSource
*/
const SSEDefaultOptions = {
retry: 5,
interval: 3 * 1000,
}
export class SSEClient {
constructor(url, options = SSEDefaultOptions) {
this.url = url;
this.es = null;
this.options = options;
this.retry = options.retry;
this.timer = null;
}
_onOpen() {
if (window.systemInfo.debug === "yes") {
console.log("SSE open: " + this.url);
}
}
_onMessage(type, handler) {
return (event) => {
this.retry = this.options.retry;
if (typeof handler === "function") {
handler(type, event);
}
};
}
_onError(type, handler, onFailed) {
return () => {
if (window.systemInfo.debug === "yes") {
console.log("SSE retry: " + this.url);
}
if (this.es) {
this._removeAllEvent(type, handler, onFailed);
this.unsunscribe();
}
if (this.retry > 0) {
this.retry--;
this.timer = setTimeout(() => {
this.subscribe(type, handler, onFailed);
}, this.options.interval);
} else if (typeof onFailed === 'function') {
onFailed();
}
};
}
_removeAllEvent(type, handler, onFailed) {
type = $A.isArray(type) ? type : [type]
this.es.removeEventListener("open", this._onOpen);
type.some(item => {
this.es.removeEventListener(item, this._onMessage(item, handler));
})
this.es.removeEventListener("error", this._onError(type, handler, onFailed));
}
subscribe(type, handler, onFailed) {
type = $A.isArray(type) ? type : [type]
this.es = new EventSource(this.url);
this.es.addEventListener("open", this._onOpen);
type.some(item => {
this.es.addEventListener(item, this._onMessage(item, handler));
})
this.es.addEventListener("error", this._onError(type, handler, onFailed));
}
unsunscribe() {
if (this.es) {
this.es.close();
this.es = null;
}
if (this.timer) {
clearTimeout(this.timer);
}
if (window.systemInfo.debug === "yes") {
console.log("SSE cancel: " + this.url);
}
}
}