修复: 主菜单点不开 + 对话列表样式/菜单 + 标题卡死/弹回/updated_at(God 拆分遗漏 + 多 bug 合并)

主菜单点不开(7f1242c):c38cefe ErrorBoundary 嵌 transition 内部,slot 透传非元素根 →
  mode="out-in" 过渡卡死 → 点主菜单 URL 变页面不切换。修:ErrorBoundary 移 transition 外 +
  根固定 div.error-boundary-root + display:contents

对话列表样式/菜单(God 拆分遗漏 4f8f1cf + 脉冲点 5c395f4 + 审查 agent 菜单):
  - God 拆分样式没移子组件,AiChat scoped 不匹配子元素 → 列表/输入框样式丢。修:样式移
    ConversationSidebar/ChatInput scoped + 复用抽 components.css
  - 脉冲点 vertical-align/margin-left 冲突。修:移除用 flex gap 统一
  - 4 操作按钮窄宽撑高 → 收进"..."下拉菜单(置顶/归档/删除)+ 移除导出(后端 IPC 保留)

标题 3 重根因(8192cd2 合并):
  - 卡死:ensure :40 title.is_some() return,"新对话"占位永远跳过;switch need_title_regen
    仅 None 不触发。修:排除"新对话"占位 + need_title_regen 含"新对话"
  - 弹回::121 无条件落 LLM 结果,clean_title 空兜底"新对话"覆盖 extract。修:非空非"新对话"才落
  - updated_at 跳变:update_field 强制 SET updated_at=now,标题生成致时间分组跳。修:set_title 不动 updated_at

CR-20/21 审查登记(待审查.md)
cargo check + vue-tsc + vite build EXIT 0
This commit is contained in:
2026-06-20 01:29:20 +08:00
parent 4746288a70
commit 4e3a11f925
10 changed files with 180 additions and 159 deletions

View File

@@ -135,8 +135,10 @@ pub async fn ai_conversation_switch(
let messages_json = record.messages.clone();
let title = record.title.clone();
// B-260617-17 续:历史会话 title 空(显"新对话")→ 切入后触发重新生成(用户诉求)
let need_title_regen = record.title.is_none();
// B-260617-17 续:历史会话 title 空(显"新对话")→ 切入后触发重新生成(用户诉求)
// 含 "新对话" 占位(Some 但未生成):title.rs ensure :40 同步排除"新对话"占位不跳过,
// 否则 title 落"新对话"(LLM 返空兜底/默认)后 is_some()=true 永远跳过 → 标题卡死。
let need_title_regen = record.title.is_none() || record.title.as_deref() == Some("新对话");
let mut session = state.ai_session.lock().await;
// F-260616-09 B 批4(决策 e 真并发上线):删除 readonly 分支。

View File

@@ -35,10 +35,14 @@ pub(crate) async fn ensure_conversation_title(
) {
let conv_repo = AiConversationRepo::new(db);
// 已有标题(用户改名或已生成)→ 不覆盖
// 已有标题(用户改名或已生成)→ 不覆盖
// 但 "新对话" 是未生成占位(默认/LLM 返空兜底/清理后空回退),视为未生成可重新生成,
// 否则一旦 title 落"新对话"便 is_some()=true 永远跳过 → 标题卡死"新对话"。
if let Ok(Some(rec)) = conv_repo.get_by_id(conv_id).await {
if rec.title.is_some() {
return;
if let Some(t) = &rec.title {
if !t.is_empty() && t != "新对话" {
return;
}
}
}
@@ -78,7 +82,7 @@ pub(crate) async fn ensure_conversation_title(
// spawn 未跑完即刷新)时,既不返回 None 也不落库 → 标题长期停留"新对话"。现先落 extract
// 兜底,下方 LLM 生成成功后覆盖;LLM 卡住/失败/超时则保留兜底,无论如何不再"新对话"。
let fallback_title = extract_title(&all_msgs).unwrap_or_else(|| "新对话".to_string());
if let Err(e) = conv_repo.update_field(conv_id, "title", &fallback_title).await {
if let Err(e) = conv_repo.set_title(conv_id, &fallback_title).await {
tracing::error!("对话标题兜底落库失败(conv_id={}, title={}): {}", conv_id, fallback_title, e);
}
let _ = app_handle.emit("ai-conversation-changed", ());
@@ -117,11 +121,14 @@ pub(crate) async fn ensure_conversation_title(
}
};
// LLM 生成成功 → 覆盖 extract 兜底
if let Err(e) = conv_repo.update_field(conv_id, "title", &title).await {
tracing::error!("对话标题 LLM 落库失败(conv_id={}, title={}): {}", conv_id, title, e);
// LLM 生成成功且有效(非空非"新对话")→ 覆盖 extract 兜底
// LLM 返空/无效(clean_title 兜底"新对话")→ 保留上方已落的 extract 兜底,不覆盖(防弹回"新对话")。
if !title.is_empty() && title != "新对话" {
if let Err(e) = conv_repo.set_title(conv_id, &title).await {
tracing::error!("对话标题 LLM 落库失败(conv_id={}, title={}): {}", conv_id, title, e);
}
let _ = app_handle.emit("ai-conversation-changed", ());
}
let _ = app_handle.emit("ai-conversation-changed", ());
}
/// 后台生成对话标题(不阻塞主流程;失败有 extract_title 兜底)