新增: 后台任务卡徽章+轮次投影(convRounds), 可恢复分区(parked), reattachToMain i18n 补键(#12)
This commit is contained in:
@@ -17,7 +17,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
|
||||
@@ -200,19 +200,19 @@ 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。
|
||||
// 时序注(2026-10-05 租约模型):原此处同步调 stopListener() 提前拆主窗 listener,消除
|
||||
// detached=true 到 AiChat 实际卸载的窗口期竞态——但同步拆全局单例正是双实例互踩根源,
|
||||
// 租约模型下删除:主窗 v-if 立即失效 → onBeforeUnmount 归还租约(最后一份才真拆)。
|
||||
// 竞态窗口期(毫秒级)旧 listener 多活无害:事件按 conversation_id 路由,分离窗口独立注册
|
||||
// (BUG-260624-02 已实证审批事件无缺口),主窗残听仅多收一次同事件,无串台。
|
||||
//
|
||||
// BUG-260624-02 核验结论(2026-08-15):分离窗口事件监听独立存在,审批事件无缺口。
|
||||
// 链路:本窗口 url '#/ai-detached' → AiDetached.vue 挂 AiChat(detached) → AiChat onMounted
|
||||
// 调 store.startListener()(独立 JS context 各自注册);后端 ai-chat-event 为 app 级广播
|
||||
// (emit 到全部 webview),故审批类事件(工具 pending/审批结果)分离窗口自身可收。
|
||||
// 前端派生的 'ai-pending-arrived'(useAiStreamingEvents/useAiToolEvents emit)亦为全局广播,
|
||||
// 分离窗口 ToolCardList 的 listener 同样收到。主窗口 listener 在此 stopListener 释清,
|
||||
// 分离窗口 ToolCardList 的 listener 同样收到。主窗 listener 由租约归还(见上方时序注)释清,
|
||||
// 不与分离窗口并存竞态。审批超时计时器(aiShared)同样随 listener 在接管窗口内重建。
|
||||
stopListener()
|
||||
}
|
||||
|
||||
/** 关闭分离窗口并回到内嵌面板(主窗口侧调用)。
|
||||
@@ -234,9 +234,9 @@ async function reattachPanel() {
|
||||
persistUiState()
|
||||
// V11c: 清分离窗活跃会话 KV(分离窗已全关,残留无意义;下次 detachPanel 会重新种子)
|
||||
void useAppSettingsStore().remove('df-ai-active-conv-detached')
|
||||
// 时序加固:reattach 关闭分离窗口后,显式停分离窗口残留 listener(分离窗口 close 异步,
|
||||
// onBeforeUnmount stopListener 可能延迟),防主面板重新挂载 startListener 时与旧 listener 重叠竞态。
|
||||
stopListener()
|
||||
// 时序注(租约模型):原此处显式 stopListener 防「分离窗旧 listener 与主面板重挂载重叠」——
|
||||
// 但分离窗 listener 活在分离窗自己的 JS context,主窗此处拆的只是本 context 已归还空的
|
||||
// listener(跨 webview 隔离,原调用实为 no-op 式心理安慰)。租约归还链已完整覆盖,删除。
|
||||
// 清所有 per-conv 快照(分离窗口已全关,快照无消费方)
|
||||
cleanupAllDetachedSnapshots()
|
||||
}
|
||||
@@ -348,9 +348,8 @@ async function resumeInDetached(convId: string | null) {
|
||||
async function closeDetachedWindow() {
|
||||
state.docked = false
|
||||
stopFollowMain()
|
||||
// 时序加固:关闭分离窗口前先停 listener,防 close 异步传播期间 onBeforeUnmount 延迟、
|
||||
// 主面板重挂载 startListener 与旧分离窗口 listener 短暂并存(极速 mount-unmount/HMR 竞态)。
|
||||
stopListener()
|
||||
// 时序注(租约模型):原此处关闭前先 stopListener 防竞态——同上,租约归还链
|
||||
// (本窗口 AiChat onBeforeUnmount)已完整覆盖,强拆会使租约计数与实际状态失配,删除。
|
||||
const { getCurrentWebviewWindow } = await import('@tauri-apps/api/webviewWindow')
|
||||
const currentWin = getCurrentWebviewWindow()
|
||||
const lbl = currentWin.label
|
||||
|
||||
Reference in New Issue
Block a user