重构: F-09 batch8 启动恢复多conv restore + L0清残留(纯后端,F-09后端完整)
- audit.rs restore_pending_approvals: 多conv分配(按conversation_id分组惰性建/复用PerConvState,无主None不建R-9) + 2单测(分配不变量/全None无per_conv) - lib.rs L0握手: 遍历per_conv清多conv残留generating(dirty_convs+逐个复位+各发AiCompleted补偿) + active兜底 与双写兼容(顶层双写复位),不改IPC签名/前端/顶层字段 主代兜底: cargo check --workspace 0 + test 98 + grep restore多conv/L0遍历印证 F-09后端完整: batch1 PerConvState + batch2 per-conv迁移 + batch5 限流 + batch8 恢复; batch4(启用·前后端)待用户
This commit is contained in:
@@ -40,37 +40,56 @@ pub fn run() {
|
||||
let app_h = app_handle.clone();
|
||||
tauri::async_runtime::spawn(async move {
|
||||
let mut session = session_arc.lock().await;
|
||||
// F-260616-09 B 批2:读 active conv 的 per_conv.generating + 双写复位。
|
||||
// conv_id 来源:active_conversation_id(HMR 重连场景的当前展示 conv)。
|
||||
let conv_id = session.active_conversation_id.clone();
|
||||
let active_for_check = conv_id.clone().unwrap_or_default();
|
||||
let was_generating = if active_for_check.is_empty() {
|
||||
session.generating
|
||||
} else {
|
||||
session.conv_read(&active_for_check).map(|c| c.generating).unwrap_or(session.generating)
|
||||
};
|
||||
if was_generating {
|
||||
if !active_for_check.is_empty() {
|
||||
session.conv(&active_for_check).generating = false;
|
||||
// F-260616-09 B 批8(设计 §3 batch8 + §5.2):遍历 per_conv(HashMap)清多 conv
|
||||
// 残留 generating(HMR/dev 热载场景多 conv 并发跑 loop 致多 conv 卡 generating)。
|
||||
// 批2 前:仅读 active per_conv.generating 单值;批8 改遍历全部 per_conv 各归各复位。
|
||||
// 顶层 session.generating(批2 双写桥接)同步复位(批4 IPC 迁移后顶层移除)。
|
||||
let mut dirty_convs: Vec<String> = session
|
||||
.per_conv
|
||||
.iter()
|
||||
.filter(|(_, c)| c.generating)
|
||||
.map(|(id, _)| id.clone())
|
||||
.collect();
|
||||
let was_generating = !dirty_convs.is_empty() || session.generating;
|
||||
// 复位每个残留 generating 的 conv(批8 多 conv 全覆盖)。
|
||||
for cid in &dirty_convs {
|
||||
if let Some(conv) = session.per_conv.get_mut(cid) {
|
||||
conv.generating = false;
|
||||
}
|
||||
session.generating = false;
|
||||
session.pending_approvals.clear();
|
||||
drop(session);
|
||||
// 补偿事件:通知前端收尾(复位 streaming / generatingConvId)
|
||||
let _ = app_h.emit(
|
||||
"ai-chat-event",
|
||||
commands::ai::AiChatEvent::AiCompleted {
|
||||
total_tokens: 0,
|
||||
prompt_tokens: 0,
|
||||
completion_tokens: 0,
|
||||
incomplete: None,
|
||||
conversation_id: conv_id,
|
||||
},
|
||||
);
|
||||
} else {
|
||||
session.pending_approvals.clear();
|
||||
}
|
||||
tracing::info!("[L0-handshake] 前端重连握手完成, was_generating={}", was_generating);
|
||||
// 顶层双写复位(批2 桥接:IPC ai_is_generating 等仍读顶层,须同步)。
|
||||
session.generating = false;
|
||||
session.pending_approvals.clear();
|
||||
// active_conversation_id 兜底入 dirty_convs:批2 双写期顶层 generating=true 但
|
||||
// per_conv 为空(理论上不会发生,防御性保证 active conv 至少收一个 AiCompleted
|
||||
// 复位前端 streaming/generatingConvId)。
|
||||
if let Some(active) = session.active_conversation_id.clone() {
|
||||
if !active.is_empty() && !dirty_convs.contains(&active) {
|
||||
dirty_convs.push(active);
|
||||
}
|
||||
}
|
||||
drop(session);
|
||||
if was_generating {
|
||||
// 补偿事件:每个残留 conv 各发一个 AiCompleted(按 conversation_id 路由),
|
||||
// 前端 useAiEvents.ts:133-140 按 conv_id 各归各复位 streaming/generatingConvId。
|
||||
for cid in &dirty_convs {
|
||||
let _ = app_h.emit(
|
||||
"ai-chat-event",
|
||||
commands::ai::AiChatEvent::AiCompleted {
|
||||
total_tokens: 0,
|
||||
prompt_tokens: 0,
|
||||
completion_tokens: 0,
|
||||
incomplete: None,
|
||||
conversation_id: Some(cid.clone()),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
tracing::info!(
|
||||
"[L0-handshake] 前端重连握手完成, was_generating={}, dirty_convs={:?}",
|
||||
was_generating,
|
||||
dirty_convs
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user