From 36f8b7291e610d57e21d6e0bc973af926b249b29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=95=E6=9F=8F=E9=9D=92?= Date: Tue, 9 Jun 2026 20:06:11 +0800 Subject: [PATCH] =?UTF-8?q?fix(polish):=20Traditional=20=E6=A8=A1=E5=BC=8F?= =?UTF-8?q?=E5=BC=BA=E5=88=B6=E4=B8=AD=E6=96=87=E5=AD=97=E5=BD=A2=E8=BD=AC?= =?UTF-8?q?=E6=8D=A2=EF=BC=8C=E4=B8=8D=E5=86=8D=E5=8F=AA=E9=9D=A0=20prompt?= =?UTF-8?q?=20(#622)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 繁体用户实测:选 Traditional 后最终插入文字仍可能含简体。根因—— 1. finalize_polished_text 仅在 Raw / 翻译 / 润色失败时强制 apply_chinese_script_preference; 普通模式 LLM 润色成功时只靠 system prompt 指示 LLM 输出繁体,而部分 provider 会 跟随简体 ASR 输入继续输出简体,prompt 指示不可靠(issue #622)。 2. 流式插入路径逐字落字,刻意跳过 apply_chinese_script_preference(dictation_streaming.rs 注释明示),无法回退已落字符。 修复: - finalize_polished_text 在非流式路径下无条件套用 apply_chinese_script_preference (对 Auto 是 no-op,不影响自动模式行为;非 Auto 时保证最终文字不混简体)。 - streaming_insert_eligible 增加 chinese_script_preference 门:非 Auto 时关闭流式, 转走会做字形转换的一次性路径(满足 issue「无法安全转换则 fallback 到 one-shot」)。 - 新增单测:finalize 对 issue 两个验收用例输出繁体;非 Auto 时流式不 eligible。 Auto 模式保持原行为不变。 --- .../src-tauri/src/coordinator/dictation.rs | 41 +++++++++++++++++++ .../src/coordinator/dictation_end.rs | 1 + .../src/coordinator/dictation_streaming.rs | 30 +++++++------- 3 files changed, 58 insertions(+), 14 deletions(-) diff --git a/openless-all/app/src-tauri/src/coordinator/dictation.rs b/openless-all/app/src-tauri/src/coordinator/dictation.rs index e8366861..0eff9c51 100644 --- a/openless-all/app/src-tauri/src/coordinator/dictation.rs +++ b/openless-all/app/src-tauri/src/coordinator/dictation.rs @@ -341,9 +341,50 @@ mod tests { false, PolishMode::Light, false, + crate::types::ChineseScriptPreference::Auto, )); } + // issue #622:非 Auto 字形偏好必须关闭流式,改走会做字形转换的一次性路径。 + #[test] + fn streaming_insert_ineligible_when_chinese_script_forced() { + for pref in [ + crate::types::ChineseScriptPreference::Traditional, + crate::types::ChineseScriptPreference::Simplified, + ] { + assert!(!streaming_insert_eligible( + true, + false, + PolishMode::Light, + false, + pref, + )); + } + } + + // issue #622:非 Auto + 成功 LLM 润色(非流式、无 error)时,最终插入文字 + // 必须套用字形转换,不能只靠 prompt 指示。覆盖 issue 给出的两个验收用例。 + #[test] + fn finalize_forces_traditional_even_on_successful_polish() { + let cases = [ + ("你知道你今天想要做什么吗?", "你知道你今天想要做什麼嗎?"), + ("所以你已经考过了吗?", "所以你已經考過了嗎?"), + ]; + for (input, expected) in cases { + let out = finalize_polished_text( + input.to_string(), + false, // translation_active + true, // raw_uses_llm + PolishMode::Light, // 成功润色路径(非 Raw、非 error) + &None, // 无 polish_error + crate::types::ChineseScriptPreference::Traditional, + &[], // 无 correction rules + false, // already_streamed + ); + assert_eq!(out, expected); + } + } + #[test] fn batch_asr_chunk_limit_applies_only_to_zhipu() { assert_eq!(batch_asr_chunk_limit_ms("zhipu"), Some(30_000)); diff --git a/openless-all/app/src-tauri/src/coordinator/dictation_end.rs b/openless-all/app/src-tauri/src/coordinator/dictation_end.rs index 453f5e9b..09439d9b 100644 --- a/openless-all/app/src-tauri/src/coordinator/dictation_end.rs +++ b/openless-all/app/src-tauri/src/coordinator/dictation_end.rs @@ -531,6 +531,7 @@ pub(crate) async fn end_session(inner: &Arc) -> Result<(), String> { translation_active, mode, raw_uses_llm, + chinese_script_preference, ); log::info!( "[coord] polish dispatch: translation={translation_active} mode={mode:?} streaming_eligible={streaming_eligible}" diff --git a/openless-all/app/src-tauri/src/coordinator/dictation_streaming.rs b/openless-all/app/src-tauri/src/coordinator/dictation_streaming.rs index 6472c3af..3d2dcbbe 100644 --- a/openless-all/app/src-tauri/src/coordinator/dictation_streaming.rs +++ b/openless-all/app/src-tauri/src/coordinator/dictation_streaming.rs @@ -345,10 +345,10 @@ where pub(crate) fn finalize_polished_text( polished: String, - translation_active: bool, + _translation_active: bool, _raw_uses_llm: bool, - mode: PolishMode, - polish_error: &Option, + _mode: PolishMode, + _polish_error: &Option, chinese_script_preference: crate::types::ChineseScriptPreference, correction_rules: &[crate::types::CorrectionRule], already_streamed: bool, @@ -356,16 +356,12 @@ pub(crate) fn finalize_polished_text( if already_streamed { return polished; } - let should_force_script = if translation_active { - polish_error.is_some() - } else { - mode == PolishMode::Raw || polish_error.is_some() - }; - let polished = if should_force_script { - apply_chinese_script_preference(&polished, chinese_script_preference) - } else { - polished - }; + // issue #622:无论是否经 LLM 润色成功,插入前都套用中文字形转换。 + // apply_chinese_script_preference 对 Auto 是 no-op;非 Auto 时确保最终插入文字 + // 不混简体——此前仅 Raw / 翻译 / 润色失败时强制转换,成功润色只靠 prompt 指示, + // 而部分 provider 会跟随简体 ASR 输入继续输出简体(流式路径已在上游回退为一次性, + // 见 streaming_insert_eligible)。 + let polished = apply_chinese_script_preference(&polished, chinese_script_preference); if correction_rules.is_empty() { polished } else { @@ -386,8 +382,14 @@ pub(crate) fn streaming_insert_eligible( translation_active: bool, mode: PolishMode, raw_uses_llm: bool, + chinese_script_preference: crate::types::ChineseScriptPreference, ) -> bool { - streaming_insert_enabled && !translation_active && (mode != PolishMode::Raw || raw_uses_llm) + streaming_insert_enabled + && !translation_active + && (mode != PolishMode::Raw || raw_uses_llm) + // issue #622:非 Auto 字形偏好需在插入前整体转换;流式逐字落字无法回退, + // 故关闭流式、走一次性路径(由 finalize_polished_text 套用字形转换)。 + && chinese_script_preference == crate::types::ChineseScriptPreference::Auto } pub(crate) fn default_done_message(status: InsertStatus, polish_failed: bool) -> Option {