From ef9db9645d7cfdac8e112929f0453cf1eda95d5f Mon Sep 17 00:00:00 2001 From: baiqing Date: Thu, 30 Apr 2026 17:55:23 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20polish=20=E5=A4=B1=E8=B4=A5=E6=9C=89?= =?UTF-8?q?=E6=84=9F=E7=9F=A5=20+=20=E8=AF=8D=E6=B1=87=E5=90=AF=E5=81=9C?= =?UTF-8?q?=E5=A4=B1=E8=B4=A5=E5=9B=9E=E6=BB=9A=20(closes=20#57=20#60)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #57: polish 失败静默回退原文 → 用户感知'风格设置没生效' - polish_or_passthrough 返回类型 String → (String, Option) 失败时第二个返回值是 reason,let _ = drop 用不到 - end_session: polish_error.is_some() 时 done_message='润色失败,已插入原文' + history.error_code='polishFailed'。文本仍插入(保留'用户的话不丢'语义) #60: 词汇启停乐观更新无回滚 - Vocab.tsx::onToggle try { await setVocabEnabled } catch { setEntries 回滚 + 显示错误 } 避免 UI 显示已禁用但 ASR/polish 仍注入 --- openless-all/app/src-tauri/src/coordinator.rs | 42 ++++++++++++------- openless-all/app/src/pages/Vocab.tsx | 9 +++- 2 files changed, 35 insertions(+), 16 deletions(-) diff --git a/openless-all/app/src-tauri/src/coordinator.rs b/openless-all/app/src-tauri/src/coordinator.rs index c2d8c9b2..07332ecb 100644 --- a/openless-all/app/src-tauri/src/coordinator.rs +++ b/openless-all/app/src-tauri/src/coordinator.rs @@ -614,7 +614,7 @@ async fn end_session(inner: &Arc) -> Result<(), String> { let prefs = inner.prefs.get(); let mode = prefs.default_mode; let hotword_strs = enabled_phrases(inner); - let polished = polish_or_passthrough(&raw, mode, &hotword_strs).await; + let (polished, polish_error) = polish_or_passthrough(&raw, mode, &hotword_strs).await; // 原子化最后一次 cancel 检查 + 转 Inserting: // 在同一 lock 内决定「丢弃」还是「进入 Inserting」。一旦设到 Inserting, @@ -655,6 +655,10 @@ async fn end_session(inner: &Arc) -> Result<(), String> { } } + // polish 失败时在 history 里标记 polishFailed,让用户能在历史详情看到为什么这次输出 + // 不是预期的 mode 风格。即使失败也不丢词 — final_text 仍是原文(保留"用户的话不丢"语义)。 + let error_code = polish_error.as_ref().map(|_| "polishFailed".to_string()); + let session = DictationSession { id: Uuid::new_v4().to_string(), created_at: Utc::now().to_rfc3339(), @@ -664,7 +668,7 @@ async fn end_session(inner: &Arc) -> Result<(), String> { app_bundle_id: None, app_name: None, insert_status: status, - error_code: None, + error_code, duration_ms: Some(raw.duration_ms), // 历史详情页的"X 个热词"显示:用本次实际命中次数(每个匹配实例算一次), // 比"启用词条总数"更能反映本段口述命中了多少。u64 → u32 截断对单段听写足够。 @@ -674,14 +678,19 @@ async fn end_session(inner: &Arc) -> Result<(), String> { log::error!("[coord] history append failed: {e}"); } - let done_message = match status { - InsertStatus::Inserted => None, - InsertStatus::CopiedFallback => Some(if cfg!(target_os = "windows") { - "已复制,请 Ctrl+V".to_string() - } else { - "已复制,请粘贴".to_string() - }), - InsertStatus::Failed => Some("插入失败".to_string()), + let done_message = if polish_error.is_some() { + // polish 失败优先告知用户,即使 insert 成功也要让用户知道这版是原文 + Some("润色失败,已插入原文".to_string()) + } else { + match status { + InsertStatus::Inserted => None, + InsertStatus::CopiedFallback => Some(if cfg!(target_os = "windows") { + "已复制,请 Ctrl+V".to_string() + } else { + "已复制,请粘贴".to_string() + }), + InsertStatus::Failed => Some("插入失败".to_string()), + } }; emit_capsule( @@ -788,19 +797,22 @@ fn ensure_asr_credentials() -> Result<(), String> { } } +/// 润色文本;失败时返回原文 + 失败原因,调用方据此弹错误胶囊 + 写历史 error_code。 +/// 之前固定返回 String,调用方拿不到失败信号 → 用户感知"为什么风格设置没生效"。issue #57。 async fn polish_or_passthrough( raw: &RawTranscript, mode: PolishMode, hotwords: &[String], -) -> String { +) -> (String, Option) { if mode == PolishMode::Raw { - return raw.text.clone(); + return (raw.text.clone(), None); } match polish_text(&raw.text, mode, hotwords).await { - Ok(s) => s, + Ok(s) => (s, None), Err(e) => { - log::error!("[coord] polish failed, falling back to raw: {e}"); - raw.text.clone() + let reason = e.to_string(); + log::error!("[coord] polish failed, falling back to raw: {reason}"); + (raw.text.clone(), Some(reason)) } } } diff --git a/openless-all/app/src/pages/Vocab.tsx b/openless-all/app/src/pages/Vocab.tsx index b6cbed5e..bc7bcd4d 100644 --- a/openless-all/app/src/pages/Vocab.tsx +++ b/openless-all/app/src/pages/Vocab.tsx @@ -71,8 +71,15 @@ export function Vocab() { const onToggle = async (entry: DictionaryEntry) => { const next = !entry.enabled; + // 乐观更新 UI;后端失败时回滚 + 让用户看到错误,避免 UI 显示「已禁用」但 ASR/polish + // 仍在注入此词条造成的诡异状态。issue #60。 setEntries(prev => prev.map(e => (e.id === entry.id ? { ...e, enabled: next } : e))); - await setVocabEnabled(entry.id, next); + try { + await setVocabEnabled(entry.id, next); + } catch (err) { + setEntries(prev => prev.map(e => (e.id === entry.id ? { ...e, enabled: entry.enabled } : e))); + setError(err instanceof Error ? err.message : String(err)); + } }; return (