Skip to content

fix(polish): 对话感知上下文——按风格包隔离 + 翻译会话参与润色(不串味)#583

Merged
appergb merged 3 commits into
betafrom
fix/polish-context-isolation
Jun 4, 2026
Merged

fix(polish): 对话感知上下文——按风格包隔离 + 翻译会话参与润色(不串味)#583
appergb merged 3 commits into
betafrom
fix/polish-context-isolation

Conversation

@appergb
Copy link
Copy Markdown
Collaborator

@appergb appergb commented Jun 4, 2026

User description

本 PR 两个 commit,围绕「对话感知 polish」的上下文复用做隔离与扩展。

commit 1 · 按风格包隔离 + (初版)排除翻译

对话感知 polish 回看最近历史当上下文时,原先复用所有成功会话,导致串味。第一版:

  • 只复用同一 active 风格包的历史(切风格包 = 上下文边界;旧历史无 style_pack_id 保守排除)。
  • DictationSessionstyle_pack_id / translation_activeserde(default) 兼容旧历史)。

commit 2 · 翻译会话参与润色(取代「直接排除」)

按需求把「翻译会话直接排除」改成「参与上下文但按轮剔除译文」:

  • 翻译管线改为单次 LLM 调用同时润色源文 + 翻译:合成系统提示词(翻译规则 + 追加输出润色后源文 + 两段哨兵格式)走现成 polish() 通道(复用 prior_turns 上下文);解析两段。解析失败 → 退回专用翻译拿干净译文(不丢字、不插畸形文本)。
  • 新增字段 polish_source(翻译会话润色后的源语言文本,serde(default))。
  • 上下文构造按当前轮分流:翻译历史在普通润色轮polish_source(剔除译文;源文缺失则跳过),在翻译轮final_text(译文);普通历史始终喂 final_text
  • 前后端类型同步(polishSource)。

验证

  • cargo test --lib 全过(370 通过,含 9 个新单测:解析器、上下文分流、风格包重置等)。
  • 前端 tsc --noEmit 通过;cargo clippy 无新增告警。

行为权衡

升级后存量历史缺 style_pack_id 会被排除在上下文外,直到新历史积累——对话感知短暂退化为单轮,是有意的保守策略。


PR Type

Enhancement, Bug fix


Description

  • Isolate polish context by style pack to prevent cross-pack leakage

  • Include translation sessions with source-only text in normal polish turns

  • Combine polish and translation into single LLM call with fallback to plain translation

  • Add new fields and frontend types for context persistence


Diagram Walkthrough

flowchart LR
  A[Dictation Session] --> B{Style pack match?}
  B -- yes --> C{Translation active?}
  B -- no --> D[Skip session]
  C -- yes (next also translation) --> E[Use final_text (translation)]
  C -- yes (next is normal polish) --> F{polish_source exists?}
  F -- yes --> G[Use polish_source]
  F -- no --> H[Skip session]
  C -- no (normal history) --> I[Use final_text (polished source)]
Loading

File Walkthrough

Relevant files
Enhancement
coordinator.rs
Add combined polish+translate LLM call and marker parsing

openless-all/app/src-tauri/src/coordinator.rs

  • Added markers and parsing for combined polish+translate output
  • Implemented polish_and_translate_or_passthrough with fallback to plain
    translate
  • Added unit tests for marker parsing scenarios (6 new tests)
  • Removed separate translate_or_passthrough function
+188/-35
dictation.rs
Filter context turns by style pack and translation status

openless-all/app/src-tauri/src/coordinator/dictation.rs

  • Implemented eligible_polish_context_turns to filter turns by style
    pack and translation mode
  • Updated translation path to use combined polish+translate call
  • Captured polish_source for future context use
  • Added extensive unit tests for context selection logic
+173/-15
types.rs
Add polish context fields to DictationSession                       

openless-all/app/src-tauri/src/types.rs

  • Added style_pack_id, translation_active, polish_source fields to
    DictationSession
  • Added #[serde(default)] for backward compatibility
  • Minor formatting change in default_style_system_prompt_for_mode
+17/-2   
types.ts
Add context fields to TypeScript types                                     

openless-all/app/src/lib/types.ts

  • Added stylePackId, translationActive, polishSource fields to
    DictationSession interface
+3/-0     
Tests
ipc.ts
Update mock history with new context fields                           

openless-all/app/src/lib/ipc.ts

  • Added mock values for stylePackId, translationActive, polishSource in
    mock history
+3/-0     

对话感知 polish 回看最近历史作为 LLM 上下文时,之前复用所有成功会话,导致两类串味:

1. 切换风格包后,旧风格包的历史仍被带进新提示词,把当前风格带偏;
2. 翻译会话的译文/指令污染下一段普通润色(如中文输入被带成英文)。

给 DictationSession 增加 style_pack_id / translation_active 两字段(serde default 向后兼容旧历史),抽出 eligible_polish_context_turns 过滤:仅复用同一 active 风格包、且非翻译的成功会话;旧历史无 style_pack_id 时保守排除。前后端类型同步,补两条单测。
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 4, 2026

PR Reviewer Guide 🔍

(Review updated until commit 4c31448)

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪
🧪 PR contains tests
🔒 No security concerns identified
⚡ No major issues detected

接续上一条「按风格包隔离」,把「翻译会话直接排除出上下文」改成「保留但按需剔除」:翻译会话也参与对话连续性,又不让外语译文污染普通润色。

- 翻译改为单次 LLM 调用同时润色源文 + 翻译:polish_and_translate_or_passthrough 用合成系统提示词(翻译规则 + 追加输出润色后源文 + 两段哨兵)走现成 polish() 通道,复用对话上下文;解析失败 / 译文为空则退回专用 translate_text 拿干净译文(不丢字)。
- DictationSession 加 polish_source(serde default 兼容旧历史),存润色后的源语言文本。
- eligible_polish_context_turns 加 current_translation_active 分流:翻译历史在普通轮喂 polish_source(无则跳过=剔除译文),翻译轮喂 final_text;普通历史始终喂 final_text。
- 前后端类型同步(polishSource);补解析器 + 上下文分流单测,cargo test --lib 371 全过。
@appergb appergb changed the title fix(polish): 对话感知上下文按风格包隔离 + 排除翻译会话 feat(polish): 对话感知上下文按风格包隔离 + 翻译会话参与润色(按轮剔除译文) Jun 4, 2026
@github-actions github-actions Bot changed the title feat(polish): 对话感知上下文按风格包隔离 + 翻译会话参与润色(按轮剔除译文) fix(polish): 对话感知上下文按风格包隔离 + 排除翻译会话 Jun 4, 2026
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 4, 2026

Persistent review updated to latest commit bff4bd7

@appergb appergb changed the title fix(polish): 对话感知上下文按风格包隔离 + 排除翻译会话 fix(polish): 对话感知上下文——按风格包隔离 + 翻译会话参与润色(不串味) Jun 4, 2026
@appergb
Copy link
Copy Markdown
Collaborator Author

appergb commented Jun 4, 2026

@claude 请审核这个 PR(目标合并到 beta)。重点:

  1. Rust:split_polish_translate_output 哨兵切片在中文/日文多字节下是否 UTF-8 安全(不会 panic);eligible_polish_context_turns 按当前轮分流(普通轮喂 polish_source 剔除译文、翻译轮喂译文、源文缺失则跳过)是否有漏洞。
  2. 单次「润色+翻译」合成调用:解析失败退回专用翻译的兜底是否健壮、是否满足「不丢字」约定、有无重复/死循环。
  3. 全部 DictationSession 构造点是否补齐新字段、前后端 camelCase 一致。
    能否安全合并?给出 CRITICAL/HIGH/MEDIUM/LOW 分级。

@claude
Copy link
Copy Markdown

claude Bot commented Jun 4, 2026

Claude finished @appergb's task in 3m 52s —— View job


PR #583 代码审核

审核任务

  • 读取变更文件(coordinator.rs, dictation.rs, types.rs, ipc.ts, types.ts)
  • 审核 UTF-8 安全性(split_polish_translate_output 哨兵切片)
  • 审核 eligible_polish_context_turns 上下文分流逻辑
  • 审核 解析失败退回专用翻译的兜底健壮性
  • 审核 DictationSession 构造点完整性 + 前后端 camelCase 一致性
  • 输出分级结论(CRITICAL/HIGH/MEDIUM/LOW)

审核结论

🟢 可安全合并到 beta。无 CRITICAL / HIGH 问题。


1. UTF-8 安全性:split_polish_translate_outputcoordinator.rs:2957

结论:SAFE,中日文多字节下不会 panic。

两个哨兵均为纯 ASCII 字面量:

const POLISH_TRANSLATE_SRC_MARKER: &str = "[[OPENLESS_POLISHED_SOURCE]]";
const POLISH_TRANSLATE_TGT_MARKER: &str = "[[OPENLESS_TRANSLATION]]";

关键推导:

  • str::find() 在 Rust 中返回字节偏移,保证指向字符边界(Rust 不允许 &str 在非边界处切片,否则 panic)。
  • ASCII 字节(0x00–0x7F)永远不会出现在 UTF-8 多字节字符的续字节中(0x80–0xBF),所以 find 对纯 ASCII 模式只会在合法字符边界上命中。
  • raw[tgt_idx + POLISH_TRANSLATE_TGT_MARKER.len()..]:在 ASCII 哨兵结束后,字节立即对齐到新的字符边界,切片安全。

✅ 中文 / 日文 / 表情符号等多字节内容在哨兵两侧,不影响切片。

LOW(可接受风险):若模型输出恰好在内容中包含字面量 [[OPENLESS_TRANSLATION]]find 会命中第一个出现位置,导致解析错位。概率极低(哨兵格式具有充分唯一性),且命中后仍会进入 fallback 而非 panic。无需额外处理。


2. eligible_polish_context_turns 上下文分流逻辑(dictation.rs:1916

结论:逻辑正确,覆盖所有四个分流情况。

.filter_map(|s| {
    if s.translation_active && !current_translation_active {
        // Case 1:翻译历史 + 普通润色轮 → 喂 polish_source(剔除译文)
        s.polish_source.filter(|src| !src.trim().is_empty())
                       .map(|src| (s.raw_transcript, src))
    } else {
        // Case 2:翻译历史 + 翻译轮 → 喂 final_text(维持目标语言)
        // Case 3:普通历史 + 普通润色轮 → 喂 final_text(正常上下文)
        // Case 4:普通历史 + 翻译轮 → 喂 final_text(本就是源语言润色结果)
        Some((s.raw_transcript, s.final_text))
    }
})

四个 Case 全部符合 PR 描述的语义:

历史类型 当前轮 喂入 结果
翻译 普通 polish_source(缺失则跳过)
翻译 翻译 final_text(译文)
普通 普通 final_text(润色结果)
普通 翻译 final_text(源语言润色结果)

filter(|s| s.error_code.is_none()) 同时排除了 fallback 路径下解析失败 + 退回专用翻译但仍成功的会话中 polish_source == None 的情况(这类会话 error_code 为空但 polish_source 缺失——但该会话仍通过前两层 filter,进入 Case 1 后 polish_source 为 None → 整条 filter_map 返回 None 跳过)。逻辑正确。

LOW:Case 4(普通历史 + 翻译轮)缺少单测。当前测试只覆盖 Case 1/2/3。建议补一个 fn translation_turn_keeps_normal_history_final_text() 固化此行为。Fix this →


3. 解析失败兜底的健壮性(coordinator.rs:2980

结论:完全健壮,满足「不丢字」约定,无重复/死循环风险。

polish_text (combined LLM call)
  ├─ Ok(out) → split_polish_translate_output(out)
  │     ├─ Some(source, translation) → ✅ 返回译文 + 源文
  │     └─ None (格式不符) → translate_text (独立翻译)
  │           ├─ Ok(translation) → ✅ 返回译文(polish_source = None)
  │           └─ Err → ✅ 返回 raw.text + polish_error(报 polishFailed)
  └─ Err → ✅ 返回 raw.text + polish_error(报 polishFailed)

各路径分析:

  • Happy pathtranslation 来自解析结果,split_polish_translate_output 已保证非空(空时返回 None),不会把空串插入光标 ✓
  • Fallback 路径translate_text 是一次无 prior_turns 的独立调用,正确(combined call 格式失败时不应相信上下文一致性)✓
  • Fallback 双重失败:返回 raw.text + Some(reason),写入历史时 error_code = Some("polishFailed"),前端显示"润色失败,已插入原文" ✓
  • 无递归/无循环:两层顺序调用,完全有界 ✓

LOW(设计权衡):Fallback 路径下 translate_text 不携带 prior_turns,翻译质量可能略低于正常路径。这是有意设计(格式混乱时不信任上下文),可接受。

LOW(极低概率):解析失败触发 Fallback 时会发起第二次 LLM 调用,双倍 token 消耗。在 LLM 严格遵守格式提示词的情况下,此路径极少触发。


4. DictationSession 构造点完整性

结论:全部 4 处构造点已补齐新字段。

文件 场景 style_pack_id translation_active polish_source
dictation.rs:1470 空转写 None false None
dictation.rs:1793 正常会话 Some(pack.id) translation_active polish_source
coordinator.rs:3656 QA placeholder None false None
dictation.rs:1977 测试辅助函数 参数传入 参数传入 参数传入

5. 前后端 camelCase 一致性

结论:完全一致。

// types.ts
export interface DictationSession {
  stylePackId: string | null;    // ← Rust style_pack_id ✓
  translationActive: boolean;    // ← Rust translation_active ✓
  polishSource: string | null;   // ← Rust polish_source ✓
}
// ipc.ts mock
polishSource: null,              // ✓ 新字段已加入 mock 历史数据
translationActive: false,        // ✓
stylePackId: "builtin.structured", // ✓

Rust 侧 #[serde(rename_all = "camelCase")] + #[serde(default)] 保证新字段序列化正确且向后兼容旧 JSON。✅


分级汇总

级别 发现
CRITICAL
HIGH
MEDIUM
LOW ① Case 4 缺测试 ② Fallback 无 prior_turns(有意设计)③ 哨兵字面量碰撞(概率极低)

总体评价

三个核心审核点全部通过:UTF-8 安全、上下文分流逻辑正确、fallback 无漏洞。新字段在所有构造点已补齐,前后端类型一致。370 个测试覆盖主要路径。建议合并。


@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 4, 2026

Persistent review updated to latest commit 4c31448

@appergb
Copy link
Copy Markdown
Collaborator Author

appergb commented Jun 4, 2026

✅ 第二轮复审(rust-reviewer agent):无 CRITICAL/HIGH。UTF-8 切片安全、四象限分流无译文泄漏、不丢字兜底无重入、构造点字段齐全、serde camelCase 与前端一致。仅 2 个 MEDIUM(可维护性,后续跟进)。批准合并。

@appergb appergb merged commit 85d6ed8 into beta Jun 4, 2026
4 checks passed
@appergb appergb deleted the fix/polish-context-isolation branch June 4, 2026 05:08
appergb pushed a commit that referenced this pull request Jun 4, 2026
… 快速录音提示音丢失修复(#584) + 正式发版自动更新 Homebrew cask(#586)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant