修复: OpenAI SSE error 识别 + 条件引擎 warn + MidStream stop_flag 优先 + 双窗口 listener 时序加固

This commit is contained in:
lxy
2026-08-01 14:53:03 +08:00
parent ba578dfe6c
commit db99e7107c
6 changed files with 161 additions and 8 deletions
+1 -1
View File
@@ -875,7 +875,7 @@ export async function startListener() {
/** 停止事件监听(卸载时调用,释放后端 listener + 清看门狗)
* 清看门狗:卸载时若仍在生成,_streamWatchdog 计时器未释放,130s 后 onStreamTimeout
* 仍写 state(messages.push/置 streaming)——已卸载组件不应再被触发。故同步清除。 */
function stopListener() {
export function stopListener() {
_unlistenAiEvent?.()
_unlistenConvChanged?.()
_unlistenApprovalClear?.()
+12
View File
@@ -14,6 +14,7 @@ import { nextMsgId, getConvState, convStates } from './aiShared'
import type { MessageId } from '@/api/types'
import { setStreaming } from './streamingGuard'
import { persistUiState } from './useAiPanel'
import { stopListener } from './useAiEvents'
// ── 主窗口事件跟随 unlistener ──
let _unlistenMove: (() => void) | null = null
@@ -100,6 +101,11 @@ async function detachPanel(convId?: string) {
stopFollowMain()
})
state.detached = true
// 时序加固:detached 置 true 后立即停主窗口 listener,不依赖 v-if → AiChat onBeforeUnmount 的
// 异步传播。消除 detached=true 到 AiChat 实际卸载的窗口期(reattach 重叠期/HMR/极速 mount-unmount
// 竞态下,onBeforeUnmount 可能延迟,旧 listener 残留会与分离窗口新 listener 并存竞态)。
// stopListener 同步释放后端 listener + 清看门狗,分离窗口 resumeInDetached 起自己的 startListener。
stopListener()
}
/** 关闭分离窗口并回到内嵌面板(主窗口侧调用)。
@@ -119,6 +125,9 @@ async function reattachPanel() {
state.docked = false
state.panelOpen = true
persistUiState()
// 时序加固:reattach 关闭分离窗口后,显式停分离窗口残留 listener(分离窗口 close 异步,
// onBeforeUnmount stopListener 可能延迟),防主面板重新挂载 startListener 时与旧 listener 重叠竞态。
stopListener()
// F-09:清所有 per-conv 快照(分离窗口已全关,快照无消费方)
cleanupAllDetachedSnapshots()
}
@@ -221,6 +230,9 @@ async function resumeInDetached(convId: string | null) {
async function closeDetachedWindow() {
state.docked = false
stopFollowMain()
// 时序加固:关闭分离窗口前先停 listener,防 close 异步传播期间 onBeforeUnmount 延迟、
// 主面板重挂载 startListener 与旧分离窗口 listener 短暂并存(极速 mount-unmount/HMR 竞态)。
stopListener()
const { getCurrentWebviewWindow } = await import('@tauri-apps/api/webviewWindow')
const currentWin = getCurrentWebviewWindow()
const lbl = currentWin.label