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

@bigsongethbigsongeth 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

bigsongethand 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-actionsBot 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 等顶级域结尾、多段域名)收紧判定条件。

ifletSome(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(包仍存在时),或把文案改成「用当前风格重试」。

construn=async(kind: 'retry'|'apply')=>{constpackId=kind==='apply' ? selectedPackId : undefined;if(kind==='apply'&&!packId)return;setRunning(kind);setError(null);try{consttext=awaitrepolish(session.rawTranscript,session.mode,packId);constpack=packId ? packs?.find(p=>p.id===packId) : undefined;constresult: 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

Copy link
Copy Markdown
Contributor

Persistent review updated to latest commit b77a6c4

@bigsongethbigsongeth 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:betaAug 5, 2026
4 checks passed
bigsongeth added a commit to bigsongeth/openless that referenced this pull request Aug 5, 2026
beta 一次进了 13 个提交,其中三条正是本地 daily 上那批私货合上游了
(Open-Less#910 历史落点+重试、Open-Less#908 翻译、Open-Less#909 概览),daily 的 patch queue 相应缩小。
冲突只有 selection.rs 一处,而且 HEAD 侧是空的:beta 的选区润色 macOS 移植
(Open-Less#926)在同一位置加了 current_front_app_pid 与 Windows/其他平台的
current_front_app,本分支在附近加了 bundle_id_for_pid(闸门按元素归属判定用)。
两者互不相干,取 beta 侧即可。
解完核对:bundle_id_for_pid 仍在(1 处),current_front_app 三个定义分属
macOS / Windows / 其他平台且 cfg 互斥,current_front_app_pid 无重复。
验证:cargo check 干净、cargo test --lib 1032 passed(beta 带来 30 个新用例)、
tsc --noEmit 干净、npm test 通过。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for freeto 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

@bigsongeth@H-Chris233