Keep streaming history aligned with partial typing

Constraint: type_unicode_chunk returns the actual count of inserted characters, including partial failures
Rejected: appending the full delta after every successful call | hides short writes and makes typed_text diverge from the screen
Confidence: high
Scope-risk: narrow
Directive: preserve typed_text as the source of truth for what actually reached the focused field during streaming insertion
Tested: cargo fmt --manifest-path src-tauri/Cargo.toml; git diff --check; cargo check --manifest-path src-tauri/Cargo.toml; cargo test --manifest-path src-tauri/Cargo.toml --lib
Not-tested: desktop runtime smoke on macOS/Windows
This commit is contained in:
H-Chris233 committed 2026-05-14 10:46:28 +08:00
1 parent 3563c8eb16
commit 97e91ecdb6
2 files changed
+56 -4

No files matched your search

@@ -122,11 +122,24 @@ async fn run_streaming_polish(
// 把 mpsc drain 完,避免发送端阻塞。
continue;
}
let delta_chars = delta.chars().count();
match crate::unicode_keystroke::type_unicode_chunk(&delta) {
Ok(_) => {
typed_text.push_str(&delta);
Ok(typed_chars) => {
let appended = append_typed_prefix(&mut typed_text, &delta, typed_chars);
if appended < delta_chars {
let reason = format!(
"type_unicode_chunk typed only {appended}/{delta_chars} chars without error"
);
log::error!(
"[coord] streaming_insert: {reason} at typed={} chars; \
dropping remaining deltas",
typed_text.chars().count()
);
first_failure = Some(reason);
}
}
Err(e) => {
append_typed_prefix(&mut typed_text, &delta, e.typed_chars());
log::error!(
"[coord] streaming_insert: type_unicode_chunk failed at typed={} chars: {e}; \
dropping remaining deltas",
@@ -1593,9 +1606,20 @@ pub(super) fn cancel_session(inner: &Arc<Inner>) {
schedule_capsule_idle(inner, CAPSULE_AUTO_HIDE_DELAY_MS);
}
fn append_typed_prefix(target: &mut String, delta: &str, typed_chars: usize) -> usize {
let mut end = 0;
let mut appended = 0;
for (idx, ch) in delta.char_indices().take(typed_chars) {
end = idx + ch.len_utf8();
appended += 1;
}
target.push_str(&delta[..end]);
appended
}
#[cfg(test)]
mod tests {
use super::finalize_polished_text;
use super::{append_typed_prefix, finalize_polished_text};
use crate::types::{ChineseScriptPreference, CorrectionRule, PolishMode};
fn correction_rule(pattern: &str, replacement: &str) -> CorrectionRule {
@@ -1643,4 +1667,24 @@ mod tests {
assert_eq!(result, "OpenAI");
}
#[test]
fn append_typed_prefix_keeps_unicode_char_boundaries() {
let mut typed = String::from("前");
let appended = append_typed_prefix(&mut typed, "a你🙂b", 3);
assert_eq!(appended, 3);
assert_eq!(typed, "前a你🙂");
}
#[test]
fn append_typed_prefix_caps_at_delta_length() {
let mut typed = String::new();
let appended = append_typed_prefix(&mut typed, "好", 10);
assert_eq!(appended, 1);
assert_eq!(typed, "好");
}
}
+9 -1
View File
@@ -1771,7 +1771,7 @@ fn compose_hotword_block_preview(hotwords: &[String]) -> String {
.collect::<Vec<_>>()
.join("\n");
format!(
"热词(用户希望以下写法在输出中保持准确;当转写中出现这些词的同音 / 近形词识别时,优先按上述写法输出,不做无关词的机械替换):\n{}",
"热词(用户希望以下写法在输出中保持准确;当转写中出现这些词的同音 / 近形误识别时,优先按上述写法输出,不做无关词的机械替换):\n{}",
bullets
)
}
@@ -2720,6 +2720,14 @@ mod tests {
assert!(prompt.contains("- OpenLess"));
}
#[test]
fn hotword_preview_uses_correct_misrecognition_wording() {
let preview = compose_hotword_block_preview(&["OpenLess".into()]);
assert!(preview.contains("同音 / 近形误识别时,优先按上述写法输出"));
assert!(!preview.contains("近形词识别"));
}
#[test]
fn compose_system_prompt_uses_user_style_system_prompt_as_base() {
let prompt = compose_system_prompt("像正式邮件,但结尾不要客套话", &[]);