背景 / 动机
路线图(README.zh.md)已将「历史增强:复制、搜索、重新润色、重新插入」列为规划项;其中复制与搜索已落地,但重新润色尚未有 UI 入口。设计稿(design_handoff_openless/README.md · History 屏)也写明详情区头部应有「重新润色」按钮,并支持切换风格重新生成。
典型场景:
- 同一段识别原文想用另一种风格包对比效果(例如「简洁」vs「会议纪要」)
- 当时润色结果不满意,想用同一风格包重跑(模型/提示词已更新,或当时偶发失败)
- 历史条目
finalText 为空(润色失败)但 rawTranscript 仍在,需要补润色
现状(基于当前 beta 分支代码)
已有能力
| 层级 |
现状 |
| 数据模型 |
DictationSession 已持久化 stylePackId、mode、rawTranscript、finalText、translationActive、polishSource 及 LLM/耗时归因字段(llmProvider / llmModel / polishMs) |
| 后端 IPC |
存在 repolish(raw_text, mode) 命令(commands/dictation.rs → coordinator::repolish),注释写明面向「历史记录里手动重新润色」 |
| 前端 IPC |
lib/ipc/style-packs.ts 已导出 repolish() |
| 历史页 UI |
History.tsx 已有「重新转录」(#613)、复制、播放/导出录音;无重新润色按钮 |
| 历史展示 |
列表/详情仅展示 legacy mode 标签(清晰/简洁/…),未展示 stylePackId 对应的风格包名称 |
缺口 / 不一致
repolish 未接历史页:后端能力存在但前端 History 未调用。
- 风格包选择不符合预期:
coordinator::repolish 实际读取的是 prefs.active_style_pack_id(当前全局激活风格包),传入的 mode 仅打日志,effective_mode 取自激活包的 baseMode——不会默认使用历史条目当时的 stylePackId。
- 无
style_pack_id 参数:IPC 签名只有 (rawText, mode),无法指定「用哪个风格包重润色」。
- 不回写历史:
repolish 只返回 String,不像 retranscribe_recording 那样原地更新 history.json 对应条目(注释亦写明「不回写历史的模型/耗时字段」)。
- 翻译会话语义未定义:实时听写时
translationActive 由热键修饰符 + translationTargetLanguage 决定;历史重润色时是否重新走翻译路径、输入应取 rawTranscript 还是 polishSource,目前没有约定。
建议方案
UI(History.tsx 详情区)
- 在详情头部操作区(与「重新转录」「复制」「删除」并列)增加 「重新润色」 按钮。
- 旁侧或点击后展开 风格包选择器(下拉 / Popover):
- 默认选中:该条历史的
stylePackId;若为空(旧历史)则按 mode 映射到对应内置包(builtin.raw / builtin.light / …)。
- 可选列表:当前已启用(
enabled)的风格包;展示名称 + baseMode 标签。
- 若原风格包已被删除:默认项显示「原风格包已删除」,回退到
mode 对应内置包,并提示用户。
- 进行中状态:按钮禁用 + 文案「润色中…」(与
retranscribing 同模式)。
- 显示优化(建议一并做):详情区 Pill 除 legacy
mode 外,展示实际风格包名称(stylePackId → listStylePacks 解析);旧数据仅显示 mode 即可。
后端 API
建议新增专用命令(与 #613 的 retranscribe_recording 对称),例如:
repolish_history_entry(
session_id: String,
style_pack_id: Option<String>, // None = 用该条历史自己的 stylePackId / mode 回退
) -> Result<DictationSession, String>
或扩展现有 repolish 并增加 session_id + 回写逻辑——但更推荐历史专用命令,避免破坏潜在的其他 repolish 调用方。
润色输入:始终使用 rawTranscript(ASR 原文),不使用 finalText。
润色路径(对齐现有 coordinator::repolish 设计意图):
- 使用指定风格包的
prompt + baseMode
- 使用当前热词表、工作语言、输出语言偏好、thinking 开关
- 不注入对话感知上下文(
prior_turns = [])——用户明确只重润色这一条
- 前台 app 上下文:可取当前前台 app(与现有
repolish 一致),不绑定录音时的 appBundleId(可选:后续再讨论是否恢复录音时 app)
成功后回写该历史条目(参考 apply_retranscription 模式):
finalText ← 新润色结果
mode ← 所用风格包的 baseMode
stylePackId ← 实际使用的包 id
llmProvider / llmModel / polishMs ← 本次调用快照(与实时听写一致,便于详情页对比)
errorCode:若此前为润色失败类错误,成功后清除
translationActive / polishSource:见下方开放问题
Raw / 无 LLM 路径:与实时一致——默认内置 Raw 且 prompt 未启用 LLM 时直通返回原文。
按钮可用性
| 条件 |
行为 |
rawTranscript 为空 |
禁用 |
正在 repolishing |
禁用 |
| LLM 凭据缺失 / 调用失败 |
保留原 finalText,toast/行内错误 |
所选风格包 enabled = false |
选择器中不展示;若原包被禁用则回退并提示 |
建议接受标准
开放问题 / 待产品确认
- 翻译会话:若
translationActive = true 的历史条目,「重新润色」是否应
- (A) 仅重新润色源语言(更新
polishSource + finalText?),还是
- (B) 按当前
translationTargetLanguage 设置重新走「润色+翻译」,还是
- (C) v1 不支持翻译条目重润色?
倾向 v1 选 (A) 或 (C),避免用户改目标语言后误译;可在 issue 讨论。
- 重润色后是否提供「插入」:路线图另有「重新插入」,是否与本 issue 解耦(本 issue 只更新文本 + 复制)?
- 是否记录重润色次数/时间戳:便于审计,或 v1 仅覆盖
finalText?
- 旧
repolish(rawText, mode) 公共 IPC:是否保留给未来其他入口,还是合并进 repolish_history_entry 后废弃?
相关代码索引
- 历史页:
openless-all/app/src/pages/History.tsx
- 历史命令:
openless-all/app/src-tauri/src/commands/history.rs(retranscribe_recording 可作对称参考)
- 现有 repolish:
openless-all/app/src-tauri/src/coordinator.rs (repolish)
- 会话模型:
openless-all/app/src-tauri/src/types.rs (DictationSession.style_pack_id)
- 前端 IPC:
openless-all/app/src/lib/ipc/style-packs.ts (repolish)
- 设计稿:
openless-all/design_handoff_openless/README.md(History 屏)
参考
- 已实现的类似能力:#613 转录失败 → 历史保留录音 → 「重新转录」
背景 / 动机
路线图(README.zh.md)已将「历史增强:复制、搜索、重新润色、重新插入」列为规划项;其中复制与搜索已落地,但重新润色尚未有 UI 入口。设计稿(
design_handoff_openless/README.md· History 屏)也写明详情区头部应有「重新润色」按钮,并支持切换风格重新生成。典型场景:
finalText为空(润色失败)但rawTranscript仍在,需要补润色现状(基于当前
beta分支代码)已有能力
DictationSession已持久化stylePackId、mode、rawTranscript、finalText、translationActive、polishSource及 LLM/耗时归因字段(llmProvider/llmModel/polishMs)repolish(raw_text, mode)命令(commands/dictation.rs→coordinator::repolish),注释写明面向「历史记录里手动重新润色」lib/ipc/style-packs.ts已导出repolish()History.tsx已有「重新转录」(#613)、复制、播放/导出录音;无重新润色按钮mode标签(清晰/简洁/…),未展示stylePackId对应的风格包名称缺口 / 不一致
repolish未接历史页:后端能力存在但前端 History 未调用。coordinator::repolish实际读取的是prefs.active_style_pack_id(当前全局激活风格包),传入的mode仅打日志,effective_mode取自激活包的baseMode——不会默认使用历史条目当时的stylePackId。style_pack_id参数:IPC 签名只有(rawText, mode),无法指定「用哪个风格包重润色」。repolish只返回String,不像retranscribe_recording那样原地更新history.json对应条目(注释亦写明「不回写历史的模型/耗时字段」)。translationActive由热键修饰符 +translationTargetLanguage决定;历史重润色时是否重新走翻译路径、输入应取rawTranscript还是polishSource,目前没有约定。建议方案
UI(
History.tsx详情区)stylePackId;若为空(旧历史)则按mode映射到对应内置包(builtin.raw/builtin.light/ …)。enabled)的风格包;展示名称 +baseMode标签。mode对应内置包,并提示用户。retranscribing同模式)。mode外,展示实际风格包名称(stylePackId→listStylePacks解析);旧数据仅显示mode即可。后端 API
建议新增专用命令(与 #613 的
retranscribe_recording对称),例如:或扩展现有
repolish并增加session_id+ 回写逻辑——但更推荐历史专用命令,避免破坏潜在的其他repolish调用方。润色输入:始终使用
rawTranscript(ASR 原文),不使用finalText。润色路径(对齐现有
coordinator::repolish设计意图):prompt+baseModeprior_turns = [])——用户明确只重润色这一条repolish一致),不绑定录音时的appBundleId(可选:后续再讨论是否恢复录音时 app)成功后回写该历史条目(参考
apply_retranscription模式):finalText← 新润色结果mode← 所用风格包的baseModestylePackId← 实际使用的包 idllmProvider/llmModel/polishMs← 本次调用快照(与实时听写一致,便于详情页对比)errorCode:若此前为润色失败类错误,成功后清除translationActive/polishSource:见下方开放问题Raw / 无 LLM 路径:与实时一致——默认内置 Raw 且 prompt 未启用 LLM 时直通返回原文。
按钮可用性
rawTranscript为空repolishingfinalText,toast/行内错误enabled = false建议接受标准
stylePackId(旧记录按mode回退内置包)rawTranscript,成功后原地更新该条历史的finalText及相关归因字段,列表/详情即时刷新zh-CN/zh-TW/en/ja/ko补齐文案mode开放问题 / 待产品确认
translationActive = true的历史条目,「重新润色」是否应polishSource+finalText?),还是translationTargetLanguage设置重新走「润色+翻译」,还是finalText?repolish(rawText, mode)公共 IPC:是否保留给未来其他入口,还是合并进repolish_history_entry后废弃?相关代码索引
openless-all/app/src/pages/History.tsxopenless-all/app/src-tauri/src/commands/history.rs(retranscribe_recording可作对称参考)openless-all/app/src-tauri/src/coordinator.rs(repolish)openless-all/app/src-tauri/src/types.rs(DictationSession.style_pack_id)openless-all/app/src/lib/ipc/style-packs.ts(repolish)openless-all/design_handoff_openless/README.md(History 屏)参考