From 8f912edb7992dc327ec530f9eb153fcf7dd6907e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=95=E6=9F=8F=E9=9D=92?= Date: Tue, 16 Jun 2026 12:14:47 +0800 Subject: [PATCH] =?UTF-8?q?fix(polish):=20=E4=B8=8A=E4=B8=8B=E6=96=87?= =?UTF-8?q?=E6=84=9F=E7=9F=A5=E6=B6=A6=E8=89=B2=E9=99=90=E6=9C=80=E8=BF=91?= =?UTF-8?q?=204=20=E8=BD=AE=EF=BC=8C=E6=B6=88=E9=99=A4=E5=8E=86=E5=8F=B2?= =?UTF-8?q?=E5=A0=86=E7=A7=AF=E5=AF=BC=E8=87=B4=E7=9A=84=E9=A6=96=E5=AD=97?= =?UTF-8?q?=E5=BB=B6=E8=BF=9F=20(#678)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1.3.6 起非 Raw 润色默认把最近 5 分钟内「全部」历史轮前置进 LLM 请求 (types.rs 默认窗口 5 分钟,eligible_polish_context_turns 不限条数)。历史 越多、请求越长、首 token 越慢——流式插入下这正是「出第一个字」的时刻, 于是面向全体用户表现为转写变慢。 给 eligible_polish_context_turns 增加硬上限 MAX_POLISH_CONTEXT_TURNS=4: sessions 为 newest-first,take 保留最近 4 轮,把无界的上下文拼接收敛为常数 量级,同时保留多轮连续性。附单测验证超量历史只回看最近 N 轮。 --- .../src-tauri/src/coordinator/dictation.rs | 41 ++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/openless-all/app/src-tauri/src/coordinator/dictation.rs b/openless-all/app/src-tauri/src/coordinator/dictation.rs index cd5e263c..66c1d847 100644 --- a/openless-all/app/src-tauri/src/coordinator/dictation.rs +++ b/openless-all/app/src-tauri/src/coordinator/dictation.rs @@ -2505,6 +2505,12 @@ fn append_typed_prefix(target: &mut String, delta: &str, typed_chars: usize) -> appended } +/// 上下文感知润色最多回看的历史轮数。`sessions` 为 newest-first,故 take 保留最近 N 轮。 +/// 这是对「5 分钟窗口内不限条数」的硬上限:不限条数时历史堆积会无限拉长 LLM 请求、抬高 +/// 首 token 时间(流式插入下 = 「出第一个字」的时刻),拖慢全体用户的转写(见 issue #678)。 +/// 取值偏小以优先延迟;若要更长的多轮连续性可上调。 +const MAX_POLISH_CONTEXT_TURNS: usize = 4; + fn eligible_polish_context_turns( sessions: Vec, active_style_pack_id: &str, @@ -2533,6 +2539,8 @@ fn eligible_polish_context_turns( Some((s.raw_transcript, s.final_text)) } }) + // 最近 N 轮上限:newest-first,take 保留最近的,杜绝历史堆积拖慢首字延迟(#678)。 + .take(MAX_POLISH_CONTEXT_TURNS) .collect() } @@ -2541,7 +2549,7 @@ mod tests { use super::{ append_typed_prefix, batch_asr_chunk_limit_ms, default_done_message, drain_streaming_insert_deltas_with, eligible_polish_context_turns, finalize_polished_text, - flush_streaming_insert_buffer_with, streaming_insert_eligible, + flush_streaming_insert_buffer_with, streaming_insert_eligible, MAX_POLISH_CONTEXT_TURNS, }; use crate::types::{ ChineseScriptPreference, CorrectionRule, DictationSession, InsertStatus, PolishMode, @@ -2585,6 +2593,37 @@ mod tests { } } + #[test] + fn polish_context_is_capped_to_most_recent_turns() { + // newest-first:堆积多于上限的历史时,只保留最近 MAX_POLISH_CONTEXT_TURNS 轮, + // 避免无限拼接拖慢首字延迟(issue #678)。 + let sessions: Vec = (0..MAX_POLISH_CONTEXT_TURNS + 3) + .map(|i| { + history_session( + &format!("s{i}"), + &format!("raw {i}"), + &format!("final {i}"), + Some("pack.new"), + false, + None, + ) + }) + .collect(); + + let turns = eligible_polish_context_turns(sessions, "pack.new", false); + + assert_eq!(turns.len(), MAX_POLISH_CONTEXT_TURNS); + // 保留的是最近的(newest-first 输入的前 N 条)。 + assert_eq!(turns[0], ("raw 0".to_string(), "final 0".to_string())); + assert_eq!( + turns[MAX_POLISH_CONTEXT_TURNS - 1], + ( + format!("raw {}", MAX_POLISH_CONTEXT_TURNS - 1), + format!("final {}", MAX_POLISH_CONTEXT_TURNS - 1) + ) + ); + } + #[test] fn polish_context_resets_when_active_style_pack_changes() { let sessions = vec![