新增: 后台任务卡徽章+轮次投影(convRounds), 可恢复分区(parked), reattachToMain i18n 补键(#12)
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
//! AI 事件监听与分发(分派器)— startListener / stopListener / handleEvent
|
||||
//! AI 事件监听与分发(分派器)— startListener(租约)/ ensureListenerStarted(幂等确保)/ handleEvent
|
||||
//!
|
||||
//! 拆分(批次 A-B1):14 个事件 case 按域拆到 3 个叶子 handler:
|
||||
//! - useAiStreamingEvents.handleStreamingEvent(流式)
|
||||
@@ -16,7 +16,7 @@ import { t } from '@/i18n/i18n-helpers'
|
||||
import { aiApi } from '@/api'
|
||||
import { useAppSettingsStore } from '@/stores/appSettings'
|
||||
import { state } from '@/stores/ai'
|
||||
import { nextMsgId, setConvState, convStates, switchingConvs, getConvStreamState, setConvCurrentText, setConvStreaming, clearConvStreamState, clearAllApprovalTimers, clearAllToolSlowTimers, flushCurrentText, friendlyError, notifyConversationChanged, clearLastDelta, discardConvPendingApprovals, addConvAgentFinished, clearConvAgentFinished, type AgentFinishedStatus } from './aiShared'
|
||||
import { nextMsgId, setConvState, convStates, switchingConvs, getConvStreamState, setConvCurrentText, setConvStreaming, clearConvStreamState, clearAllApprovalTimers, clearAllToolSlowTimers, flushCurrentText, friendlyError, notifyConversationChanged, clearLastDelta, discardConvPendingApprovals, addConvAgentFinished, clearConvAgentFinished, setConvRound, clearConvRound, type AgentFinishedStatus } from './aiShared'
|
||||
import { resetStreamWatchdog, clearAllStreamWatchdogs, clearResumeAttempts } from './useAiStream'
|
||||
import { setStreaming } from './streamingGuard'
|
||||
import { scheduleConversationsRefresh, activeConvKvKey } from './useAiConversations'
|
||||
@@ -40,6 +40,9 @@ let _unlistenApprovalClear: (() => void) | null = null
|
||||
// startListener 并发去重:防 onMounted 与 sendMessage 首发竞态下重复注册 listener
|
||||
// (两回调写同一 state.currentText → 流式文字双倍)
|
||||
let _startPromise: Promise<void> | null = null
|
||||
// 租约计数(所有权模型,2026-10-05 根本修):仅组件挂载级 startListener 借/还,
|
||||
// send 等运行时路径走 ensureListenerStarted 不计数——计数只由对称生命周期驱动,不漂移。
|
||||
let _leaseCount = 0
|
||||
|
||||
const appSettings = useAppSettingsStore()
|
||||
|
||||
@@ -219,6 +222,8 @@ export function handleEvent(event: AiChatEvent) {
|
||||
clearResumeAttempts(convId || undefined)
|
||||
// 终态即清进度快照(事件即逝态,会话已终止不留陈旧「进行中」)
|
||||
pendingTaskProgress.delete(convId || '')
|
||||
// #14 桌面任务卡:终态即清轮次(任务卡据 convRounds 渲染在途项,终止不留陈旧项)
|
||||
clearConvRound(convId || '')
|
||||
// FE1:后台会话终止只刷新列表(防抖调度,避免与 AiCompleted 收尾的 notify 双拉)。
|
||||
scheduleConversationsRefresh()
|
||||
// 后台会话终止也触发队列续发/清队(完成续发;错误清该会话队列)
|
||||
@@ -238,6 +243,10 @@ export function handleEvent(event: AiChatEvent) {
|
||||
// 切换完成后由渲染层续显,避免切换窗口丢可见内容
|
||||
setConvStreaming(convId, true)
|
||||
setConvCurrentText(convId, (getConvStreamState(convId)?.currentText ?? '') + (event.delta ?? ''))
|
||||
} else if (event.type === 'AiAgentRound' && convId && event.round > 0) {
|
||||
// #14 桌面任务卡:后台会话轮次追踪(isCurrent=false 原本直接丢弃本事件,
|
||||
// 任务卡需要后台轮次显示「进行中 N 轮」;取 max 防事件乱序回退)
|
||||
setConvRound(convId, event.round)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -260,8 +269,30 @@ export function handleEvent(event: AiChatEvent) {
|
||||
void handleLifecycleEvent(event)
|
||||
}
|
||||
|
||||
/** 启动事件监听(幂等 + 并发去重,onMounted 与 sendMessage 首发竞态不会重复注册) */
|
||||
export async function startListener() {
|
||||
/** 启动事件监听(幂等确保注册,无所有权——send 等运行时路径调用,不计入租约)。
|
||||
* 所有权模型(2026-10-05 根本修):
|
||||
* - ensureListenerStarted:仅保证 listener 存在,谁都可调、任意次,不持租约;
|
||||
* - startListener:组件挂载用,返回 release 租约句柄,unmount 时调用归还;
|
||||
* 最后一份租约归还才真拆 listener + 清全局计时器。
|
||||
* 两者分离根治「send 裸调 start 无配对 stop → 裸计数只涨不跌 → 永不真拆」的结构性漂移:
|
||||
* 计数只由组件挂载/卸载这一对对称生命周期驱动,不变量可静态审计。 */
|
||||
export async function startListener(): Promise<() => void> {
|
||||
await ensureListenerStarted()
|
||||
_leaseCount++
|
||||
let released = false
|
||||
return () => {
|
||||
if (released) {
|
||||
// 双重归还是调用方 bug(组件卸载钩子重复执行等),dev 下即刻暴露防静默漂移
|
||||
if (import.meta.env.DEV) console.error('[useAiEvents] listener 租约重复归还(调用方 bug)')
|
||||
return
|
||||
}
|
||||
released = true
|
||||
releaseLease()
|
||||
}
|
||||
}
|
||||
|
||||
/** 幂等确保 listener 已注册(并发去重;不持租约,不触发拆卸)。 */
|
||||
export async function ensureListenerStarted(): Promise<void> {
|
||||
if (_unlistenAiEvent && _unlistenConvChanged && _unlistenApprovalClear) {
|
||||
return
|
||||
}
|
||||
@@ -304,8 +335,14 @@ export async function startListener() {
|
||||
}
|
||||
}
|
||||
|
||||
/** 停止事件监听(卸载时调用,释放后端 listener + 清看门狗/计时器,防回调改已卸载组件 state) */
|
||||
export function stopListener() {
|
||||
/** 归还一份租约:计数归零才真拆(多实例挂载时先退出的实例不误拆他人在用的 listener)。 */
|
||||
function releaseLease() {
|
||||
if (_leaseCount === 0) {
|
||||
if (import.meta.env.DEV) console.error('[useAiEvents] 租约下溢(归还多于借出,调用方 bug)')
|
||||
return
|
||||
}
|
||||
_leaseCount--
|
||||
if (_leaseCount > 0) return
|
||||
_unlistenAiEvent?.()
|
||||
_unlistenConvChanged?.()
|
||||
_unlistenApprovalClear?.()
|
||||
@@ -323,7 +360,7 @@ export function stopListener() {
|
||||
export function useAiEvents() {
|
||||
return {
|
||||
startListener,
|
||||
stopListener,
|
||||
ensureListenerStarted,
|
||||
handleEvent,
|
||||
flushCurrentText,
|
||||
friendlyError,
|
||||
|
||||
Reference in New Issue
Block a user