Skip to content

feat(history): 记录插入目标应用;详情页支持重试与换风格重新润色;条目显示真实风格包名 - #910

Merged
H-Chris233 merged 9 commits into
Open-Less:betafrom
bigsongeth:feat/history-repolish
Aug 5, 2026
Merged

feat(history): 记录插入目标应用;详情页支持重试与换风格重新润色;条目显示真实风格包名#910
H-Chris233 merged 9 commits into
Open-Less:betafrom
bigsongeth:feat/history-repolish

Conversation

@bigsongeth

@bigsongeth bigsongeth commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

User description

围绕「在历史里回溯和重做一条听写」的一组改动。

1. 记录听写落到了哪个应用

历史详情页的「插入」行早就会渲染 app_nameHistory.tsx),但听写写历史时这个字段写死 None —— 实测本机 200 条历史全部 appName=null,那一行永远只有字数,看不出这段话落到了哪个 app。

前台应用其实 begin_session 就采过(capture_frontmost_app),日志里能看到:

[coord] front_app captured: Claude (com.anthropic.claudefordesktop)

只是一直只喂给 polish prompt,没写进历史。补上写入即可,前端一个字不用改。成功、空转写、转录失败三条路径都补上 —— 排查「在某个 app 里总是识别不到」时,这一列本身就是线索。

macOS 采到的是 名称 (bundle id) 的显示串,新增 split_front_app_label 拆成两个字段落库,详情页只显示人读得懂的应用名,bundle id 进它自己的字段。划词润色与语音问答两条路径此前把整串塞在 app_name 里,一并改走同一个拆分,避免同一个列表里两种写法。

拆分对 Windows 的窗口标题要保守:只有括号内容形如反向域名(含点、无空格)才认作 bundle id,「未命名文档 (未保存)」「Inbox (12)」这类标题保持原样不截断(有单测)。

2. 详情页把「识别 / 润色 / 插入」明细移到正文之上

这三行是这条记录的元信息(走了哪个模型、各步多久、落到哪个 app),读的时候应该先看到它、再读正文;放在两张长文本卡片之后,长内容一多就要滚到底才看得见。纯挪位,块内容不变。

3. 重试 / 换风格重新润色

两个入口,一条后端通道:

  • 用原风格重试 —— 同样的原文用当前风格再跑一遍。「AI 这次不太对」时真正想做的是这个对照实验:判断上次结果是模型抖动还是稳定行为。
  • 选风格 + 应用 —— 同一段话换个风格包看效果,结果作为新卡片出现在下方。

repolish 命令本来就在(commands/dictation.rs),但写死用当前激活的风格包,而且前端没有任何页面调它 —— 等于一条建好没接通的通道。这里给它加一个可选的 style_pack_id:省略 = 当前激活包,给了 id = 用指定包试算一次。

指定的包不需要处于激活状态,也不会改变激活状态 —— 这只是一次性试算,不该有把用户当前风格换掉的副作用。显式指定时按 id 精确取,不走 get_or_default_active 的兜底链:静默回落到别的包会让结果无从解释。

结果只在本次查看时显示,不写回历史条目:历史的 final_text 是「当时真的插进去的那段文字」,是一条事实记录,不该被事后试算覆盖。面板上的说明把这点直说了,结果卡带复制按钮。切换到另一条记录时结果清空。

范围:这里只重跑润色、不重跑识别。成功听写的录音在插入后就删了(隐私设计),原文是唯一还留着的输入。真正的「重新转录」入口仍然只对留有录音的失败条目开放,本次不动。

4. 历史条目显示真实风格包名

session.mode 是风格包的 base_mode —— 四个内置分类之一。所有自建包都会落进这四个桶,所以用「超级豆包」口述出来的记录,Pill 上写的是「轻度润色」,看不出到底是哪个包产出的。

而上面第 3 节的重新润色面板用的是包名(下拉选项、结果标题都是 pack.name),于是同一屏出现两套命名:下拉里选「超级豆包」,上方 Pill 却写着「轻度润色」。

改成优先用 session.style_pack_id 查真实包名 —— 该字段后端写历史时早就在填(dictation.rs),前端一直没读。

内置包例外,仍走 i18n 的 mode 名:内置包名在后端是硬编码中文("轻度润色"…),直接显示会在英/日/韩界面串语言。旧历史没有 style_pack_id、或包已被删除时同样回落到 mode 名。Pill 的 tone 仍按 base_mode 走,颜色保留原有的粗分类信息。

风格包加载顺带提到 History 顶层与面板共用:面板带 key={item.id},原先每切一条记录就重挂载一次、重发一次 list_style_packs。顶层存全部包(含已禁用的 —— 历史可能出自后来被禁用的包,名字要查得到),面板自己再 filter(enabled),可选风格的行为不变。

5. 三个视觉/文案修复

  • 耗时被从中间截断换行(「143 毫秒」显示成「143 毫 / 秒」两行)。这一行是 grid-template-columns: auto 1fr auto,右侧 auto 列会缩到 min-content,而中文能在任意字符间断行 → min-content 只有一个字宽,中间贪心的 1fr 列把它挤扁。三个右列加 nowrap,挤压转移到中间的 provider·model 列(那列本来就带 overflowWrap: anywhere)。

  • 超时错误说人话。后端 LLMError::Timeout 过 IPC 后退化成裸字符串 "timeout",前端拼成「重新润色失败:timeout」—— 既没说是谁超时也没说能怎么办,第一反应必然是「这个新功能坏了」。实测某免费模型池 13 次非流式请求里 6 次撞 30s 超时,成功的 7 次耗时 2.3s~21.2s。超时单独给一句能照着做的提示;其余错误保持原文透传(凭据/参数类错误原文更有诊断价值)。

  • 长风格包名撑破布局Pill 自带 white-space: nowrap + flex-shrink: 0,长包名会把同排的「复制」按钮挤成文字竖排;外层 grid 子项默认 min-width: auto,还会把整列撑出卡片、逼出横向滚动条。显示包名的 Pill 统一改成可收缩 + 省略号(全名挂在 title 上悬停查看),两个 grid 子项补 min-width: 0

验证

cargo check / cargo test --lib / npm test / tsc --noEmit 全绿;浏览器里点过重试与换风格两条路径、切换记录的清空行为、以及窄窗口下的换行;本地装机实测。

第 4 节额外覆盖到的四种取名来源:自定义包(显示包名)、内置包(走 i18n)、旧历史无 style_pack_id(回落 mode 名)、包已被删除(回落 mode 名);切到英文确认内置包变成 "Light polish" 而自定义包保留用户起的原名;超长包名在 1280 / 430 两种宽度下都不再撑出横向滚动条。

🤖 Generated with Claude Code


PR Type

Enhancement, Bug fix


Description

  • 听写历史全部路径记录前台应用名与 bundle id

  • 新增 split_front_app_label 拆分 macOS 应用标签,带单测

  • 历史详情页支持「用原风格重试」与「换风格重新润色」

  • 历史条目显示真实自定义风格包名,替换内置 mode 标签


Diagram Walkthrough

flowchart LR
  A["capture frontmost app"] --> B["split_front_app_label"]
  B --> C["app_name + app_bundle_id 写入历史"]
  D["History RepolishPanel"] -- "repolish(raw, mode, stylePackId?)" --> E["repolish 后端命令"]
  E -- "stylePackId: None" --> F["当前激活风格包"]
  E -- "stylePackId: Some(id)" --> G["指定风格包"]
  F --> H["LLM 润色"]
  G --> H
  H --> I["结果卡片(不写回历史)"]
Loading

File Walkthrough

Relevant files
Enhancement
10 files
dictation.rs
repolish 命令新增可选 style_pack_id 参数                                                 
+7/-3     
coordinator.rs
repolish 支持按指定风格包试算且不改变激活状态                                                           
+25/-6   
types.rs
新增 split_front_app_label 解析函数及单元测试                                             
+74/-0   
en.ts
添加重新润色面板英文文案                                                                                         
+17/-0   
ja.ts
添加重新润色面板日文文案                                                                                         
+17/-0   
ko.ts
添加重新润色面板韩文文案                                                                                         
+17/-0   
zh-CN.ts
添加重新润色面板简体中文文案                                                                                     
+17/-0   
zh-TW.ts
添加重新润色面板繁体中文文案                                                                                     
+17/-0   
style-packs.ts
repolish 包装函数接受可选 stylePackId                                                       
+13/-2   
History.tsx
详情页新增重新润色面板并显示真实风格包名                                                                         
+321/-36
Bug fix
3 files
dictation.rs
三条历史写入路径记录前台应用名与 bundle id                                                             
+32/-9   
qa_session.rs
语音问答历史拆分应用名与 bundle id                                                                     
+7/-2     
selection_polish.rs
划词润色历史拆分应用名与 bundle id                                                                     
+16/-4   

bigsongeth and others added 5 commits August 4, 2026 14:21
历史详情页的「插入」行早就会渲染 app_name(History.tsx),但听写写历史时这个
字段写死 None —— 实测本机 200 条历史全部 appName=null,那一行永远只有字数,
看不出这段话落到了哪个 app。

前台应用其实 begin_session 就采过(capture_frontmost_app,日志里能看到
`front_app captured: Claude (com.anthropic.claudefordesktop)`),只是一直
只喂给 polish prompt,没写进历史。补上写入即可,前端不用改。

顺带把成功、空转写、转录失败三条路径都补上:排查「在某个 app 里总是识别不到」
时,这一列本身就是线索。

macOS 采到的是 `名称 (bundle id)` 的显示串,新增 `split_front_app_label` 拆成
两个字段落库,详情页只显示人读得懂的应用名,bundle id 进它自己的字段。划词润色
与语音问答两条路径此前把整串塞在 app_name 里,一并改走同一个拆分,避免同一个
列表里两种写法。

拆分对 Windows 的窗口标题要保守:只有括号内容形如反向域名(含点、无空格)才认
作 bundle id,「未命名文档 (未保存)」「Inbox (12)」这类标题保持原样不截断。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
这三行是这条记录的元信息(走了哪个模型、各步多久、落到哪个 app),读的时候
应该先看到它、再读正文;放在两张长文本卡片之后,长内容一多就要滚到底才看得见。

纯挪位:块内容不变,分隔线从上缘(marginTop/borderTop)改到下缘
(marginBottom/borderBottom)。这也给下一步在正文卡片下方增加「重新润色」
区域腾出了位置。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
两个入口,一条后端通道:
- 「用原风格重试」:同样的原文用当前风格再跑一遍。用户说「AI 这次不太对」时,
  真正想做的是这个对照实验 —— 判断上次结果是模型抖动还是稳定行为。
- 选风格 +「应用」:同一段话换个风格包看效果,结果作为新卡片出现在下方。

后端 `repolish` 命令本来就在(commands/dictation.rs),但写死用当前激活的风格包,
而且前端没有任何页面调它 —— 等于一条建好没接通的通道。这里给它加一个可选的
`style_pack_id`:省略 = 当前激活包(重试),给了 id = 用指定包试算一次。指定的包
不需要处于激活状态,也不会改变激活状态 —— 这只是一次性试算,不该有把用户当前
风格换掉的副作用。显式指定时按 id 精确取,不走 get_or_default_active 的兜底链:
静默回落到别的包会让结果无从解释。

结果只在本次查看时显示,不写回历史条目:历史的 finalText 是「当时真的插进去的
那段文字」,是一条事实记录,不该被事后试算覆盖。面板上的说明把这点直说了,结果
卡带复制按钮。切换到另一条记录时结果清空(key={item.id}),不会把上一条的结果
留在新条目下面。

范围说明:这里只重跑润色、不重跑识别。成功听写的录音在插入后就删了(隐私设计),
原文是唯一还留着的输入。真正的「重新转录」入口仍然只对留有录音的失败条目开放,
本次不动。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
「143 毫秒」会显示成「143 毫 / 秒」两行。原因是这一行用的是
`grid-template-columns: auto 1fr auto`:右侧 auto 列会缩到 min-content,
而中文可以在任意字符间断行,所以它的 min-content 只有一个字宽 —— 中间那个
贪心的 1fr 列就把它挤扁了。

三个右列(识别耗时 / 润色耗时 / 插入状态)都加 nowrap,min-content 变成整串
宽度。挤压转移到中间的 provider·model 列,那一列本来就带 overflowWrap:anywhere,
换行是它该做的事。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
后端的 LLMError::Timeout 过 IPC 边界后退化成裸字符串 "timeout",前端直接拼进
「重新润色失败:{err}」——用户看到的就是「重新润色失败:timeout」。这句话既没
说是谁超时了,也没说能怎么办,第一反应必然是「这个新功能坏了」。

实际原因通常是当前 LLM provider 30 秒内没回包。实测一次:某免费模型池
(openrouter free)13 次非流式请求里 6 次撞上 30s 客户端超时,成功的那 7 次
耗时从 2.3s 到 21.2s 不等。功能本身是通的,换个 provider 就好。

超时单独给一句能照着做的提示:说明是 provider 没响应、建议换更快的或稍后重试。
其余错误保持原样透传(那些通常是凭据/参数问题,原文更有诊断价值)。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

PR Reviewer Guide 🔍

(Review updated until commit b77a6c4)

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪
🧪 PR contains tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Possible Issue

split_front_app_label 只在括号内容「含点且无空格」时就判定为 bundle id,对 Windows 窗口标题仍不够保守。像「未命名 (2024.01.05)」「script.py (C:\dir\script.py)」「会议 (meet.example.com)」这类标题会在 Windows 上被误拆成应用名 + bundle id,详情页显示被截断的标题,同时写入错误的 app_bundle_id——这与函数注释「不要把窗口标题里的普通括号误当成 bundle id」的目标不符。建议按反向域名特征(如以 com/org/net 等顶级域结尾、多段域名)收紧判定条件。

if let Some(open) = trimmed.rfind(" (") {
    if trimmed.ends_with(')') {
        let name = trimmed[..open].trim();
        let bundle = trimmed[open + 2..trimmed.len() - 1].trim();
        // bundle id 必然是点分的反向域名。没有点的括号内容("记事本 (未保存)"
        // 这类窗口标题)不是 bundle id,不能拆。
        if !name.is_empty() && bundle.contains('.') && !bundle.contains(' ') {
            return (Some(name.to_string()), Some(bundle.to_string()));
        }
    }
Misleading Label

「用原风格重试」按钮调 repolish 时不传 stylePackId,而后端在 stylePackId 为 None 时使用的是当前激活的风格包。当用户当前激活风格与这条历史实际产出的风格不同(旧记录、或期间切换过风格)时,重试并非「原风格」重跑,用户拿它对照上次结果判断「模型是不是抖动」的实验条件不成立,结果可能误导。可改为优先传 session.stylePackId(包仍存在时),或把文案改成「用当前风格重试」。

const run = async (kind: 'retry' | 'apply') => {
  const packId = kind === 'apply' ? selectedPackId : undefined;
  if (kind === 'apply' && !packId) return;
  setRunning(kind);
  setError(null);
  try {
    const text = await repolish(session.rawTranscript, session.mode, packId);
    const pack = packId ? packs?.find(p => p.id === packId) : undefined;
    const result: RepolishResult = {
      key: packId ?? '__retry__',
      title: pack
        ? t('history.repolish.resultTitle', { name: pack.name })
        : t('history.repolish.retryResultTitle'),
      text,
    };
    // 同一个 key 覆盖旧结果,新 key 追加到最前面 —— 最新的试算结果离操作区最近。

历史列表和详情页的 Pill 一直显示 `session.mode`,那是风格包的 baseMode ——
四个内置分类之一。所有自建包都会落进这四个桶,所以用「超级豆包」口述出来的
记录,Pill 上写的是「轻度润色」,看不出到底是哪个包产出的。

同页的「重新润色」面板用的却是包名(下拉选项、结果标题都是 pack.name),
一页两套命名:下拉里选「超级豆包」,上面的 Pill 却写「轻度润色」。

改成优先用 session.stylePackId 查真实包名(该字段后端早就在写),内置包仍走
i18n 的 MODE_LABEL —— 内置包名在后端是硬编码中文,直接显示会在英/日/韩界面
串语言。旧历史没有 stylePackId、或包已被删除时回落到原来的模式名。
Pill 的 tone 仍按 baseMode 走,颜色保留原有的粗分类信息。

顺带两处修复:
- 风格包加载提到 History 顶层,与 RepolishPanel 共用。面板按 key={item.id}
  重挂载,原先每切一条历史都要重发一次 list_style_packs。顶层存全部包(含已
  禁用,历史可能出自后来被禁用的包),面板自己再 filter(enabled)。
- 长包名撑破布局:Pill 自带 nowrap + flexShrink: 0,会把同排的「复制」按钮挤
  成文字竖排;grid 子项默认 min-width: auto,还会逼出横向滚动条。显示包名的
  Pill 统一改成可收缩 + 省略号(全名挂 title),两个 grid 子项补 minWidth: 0。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Persistent review updated to latest commit b77a6c4

@bigsongeth bigsongeth changed the title feat(history): 记录插入目标应用;详情页支持重试与换风格重新润色 feat(history): 记录插入目标应用;详情页支持重试与换风格重新润色;条目显示真实风格包名 Aug 4, 2026
- 重试优先用原记录风格包(包已删除/旧历史回落当前激活包),结果标题显示真实包名
- split_front_app_label 增加平台开关:仅 macOS 拆分,Windows 窗口标题整串保留
- 重新润色面板排除 QA 记录
- 超时错误匹配收紧为整串形态,避免误判
- 结果卡标题 Pill 截断 + minWidth:0,长包名不再撑破结果网格
- 下拉默认选中当前激活包(defaultPackId)
- 面板下拉与结果标题的内置包名统一走 i18n mode 名(packDisplayName)
- 重试回落显式落到当前激活包,结果标题与实际执行一致
- split_front_app 返回 FrontApp 结构体,6 个调用点同步更新
- repolish hint 补充回落说明(5 locale)
…fixes

# Conflicts:
#	openless-all/app/src-tauri/src/types.rs
#	openless-all/app/src/pages/History.tsx
@H-Chris233
H-Chris233 merged commit cd33c89 into Open-Less:beta Aug 5, 2026
4 checks passed
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.

2 participants