Skip to content

fix(remote): 听写完成后 emit remote:result,手机端才能看到识别结果 (#691)#692

Open
appergb wants to merge 1 commit into
betafrom
fix/remote-result-emit
Open

fix(remote): 听写完成后 emit remote:result,手机端才能看到识别结果 (#691)#692
appergb wants to merge 1 commit into
betafrom
fix/remote-result-emit

Conversation

@appergb

@appergb appergb commented Jun 16, 2026

Copy link
Copy Markdown
Collaborator

User description

关联 issue

Closes #691

问题

remote_server/mod.rs 的 WS handler 订阅了 "remote:result" 并把 {type:"result", text} 转发给手机、H5 也准备好显示,但全仓没有任何一处 emit("remote:result", ...) → 手机端结果区永远空(状态会动,结果文本不出)。

改动(单一职责)

在听写 finalize(coordinator/dictation.rshistory.append_with_retention 之后)emit 最终文字 polished"remote:result",与同函数里已有的 app.emit("vocab:updated", ...) 完全同模式(inner.app.lock().clone() 取 AppHandle)。非空才发;无手机连接时无人转发 = 无害空操作。

payload 是 String → tauri 序列化为带引号 JSON 字符串,正是 remote_server 那侧 serde_json::from_str::<String> 期望的格式。

⚠️ 验证

本地编译通过(emit 是副作用,无单测);需手机真机验证:远程输入一次,手机结果区出现最终文字。

注:审计另发现「任意 WS 关闭无条件 cancel_remote_dictation(双手机/重连时取消他人听写)」属另一独立 bug,单独跟进,不在本 PR。

来源:2026-06-16 全仓多 Agent 审计(remote_server 专项)。


PR Type

Bug fix


Description

  • 在听写 finalize 时 emit "remote:result" 事件

  • 修复手机端永远看不到识别结果的 bug

  • 非空文本才发送,无手机连接时无害


Diagram Walkthrough

flowchart LR
  A["听写 finalize"] --> B["emit remote:result"]
  B --> C["手机端接收结果"]
Loading

File Walkthrough

Relevant files
Bug fix
dictation.rs
添加 remote:result 事件发射                                                                       

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

  • end_sessionhistory.append_with_retention 之后添加事件发射
  • 仅当 polished 非空时,通过 inner.app.lock().clone() 获取 AppHandle 并 emit
  • 与已有 vocab:updated 的发射模式一致
+8/-0     

remote_server 的 WS handler 订阅了 "remote:result" 并转发给手机、H5 也准备好显示,
但全仓没有任何一处 emit 该事件——手机端结果区永远空。

在 finalize(history.append 之后)按已有 app.emit("vocab:updated", ...) 同模式
emit 最终文字 polished 到 "remote:result"(非空才发)。无手机连接时无人转发 = 无害空操作。
@appergb

appergb commented Jun 16, 2026

Copy link
Copy Markdown
Collaborator Author

@claude 请审核:补上从未 emit 的 remote:result,手机端才能看到识别结果。重点看 (1) emit 时机放在 history.append 之后是否合适、polished 是否就是最终落字文本;(2) String payload 与 remote_server 侧 from_str:: 是否对得上;(3) 是否应只在远程会话时 emit(本 PR 选择总是 emit,理由:无手机连接时无人转发、且连着手机通常就是在用远程输入)。

@claude

claude Bot commented Jun 16, 2026

Copy link
Copy Markdown

Claude finished @appergb's task in 2m 4s —— View job


代码审核:fix/remote-result-emit

  • 读取 coordinator/dictation.rs 变更内容
  • 读取 remote_server/mod.rs 验证 payload 反序列化
  • 分析三个审核要点并撰写完整审核意见

整体评价:逻辑正确,可以合入。 三个审核要点全部通过,下面逐一展开。


1. emit 时机 & polished 是否就是最终落字文本 ✅

polished 在 emit 之前经历了两轮最终化:

  1. finalize_polished_text(line 2242):应用了繁简转换 + 纠错规则,是"加工后"的最终文本。
  2. history.append_with_retention(line 2402):写入历史记录时用的 final_text: polished.clone()(line 2385),与 emit 的变量完全同源。

放在 history.append 之后 emit 没有问题——这只是副作用的顺序,不影响 polished 的值,且 append 失败(log::error!)不影响 emit 继续执行。

唯一值得确认的边界场景:streaming 路径already_streamed = true)。在流式插入路径中,polished 是实际逐字上屏的完整文本(run_streaming_polish 返回的 final_text);手机端此时会经由 capsule:state 看到实时过程,remote:result 则相当于"最终确认结果"显示——符合预期。

2. String payload 与 from_str::<String> 是否对得上 ✅

链路:

// emit 侧(dictation.rs:2414)
app.emit("remote:result", polished.clone())
// Tauri 对 String 的序列化:serde_json::to_string("hello") → `"\"hello\""`
// event.payload() 返回带引号的 JSON 字符串

// 接收侧(remote_server/mod.rs:616)
serde_json::from_str::<String>(event.payload())
// `from_str::<String>(r#""hello""#)` → Ok("hello")  ✅

remote_server/mod.rs:615 的注释也已经说明了这个序列化预期,两侧完全匹配。

3. 总是 emit vs 仅在远程会话时 emit ✅

总是 emit 是正确选择。 理由:

  • Tauri emit 在无监听者时是纯空操作,没有运行时开销。
  • remote_server 在每次 WS 连接建立时 listen_any("remote:result", ...) 注册监听,断开时 unlisten(line 676-677),生命周期绑定连接——这才是控制"谁接收"的正确位置。
  • 在 emit 侧额外检查"远程会话是否活跃"会引入 coordinator → remote_server 的反向依赖,以及不可避免的 TOCTOU 竞态(手机恰好在检查后连接),得不偿失。

一个极小的 nit(不阻塞合入)

dictation.rs:2414

let _ = app.emit("remote:result", polished.clone());

polished 在这行之后不再被使用,可以直接 move 而无需 .clone()

let _ = app.emit("remote:result", polished);

这节省一次堆分配,但对正确性没有影响,且需要确认编译器不报 borrow 错误(polished.trim() 的临时借用在 if 条件求值后已释放,应无问题)。完全可选。


结论:三个问题都审查通过,代码修改本身准确、最小化、无副作用。建议在真机验证后合入。

@github-actions

Copy link
Copy Markdown
Contributor

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

🎫 Ticket compliance analysis 🔶

691 - Partially compliant

Compliant requirements:

  • 在听写finalize(coordinator/dictation.rs,history.append_with_retention之后)emit最终文字到"remote:result"
  • 仅在非空文本时发送
  • 使用inner.app.lock().clone()获取AppHandle
  • 无手机连接时无害空操作(无人订阅则忽略)

Non-compliant requirements:

(无)

Requires further human verification:

  • 需要手机真机验证:远程输入一次,手机结果区出现最终文字
⏱️ Estimated effort to review: 1 🔵⚪⚪⚪⚪
🧪 No relevant tests
🔒 No security concerns identified
⚡ No major issues detected

@HKLHaoBin

Copy link
Copy Markdown
Contributor

PR 合并冲突提示(听写模块)

本 PR 与 #682#688#666 均修改了 openless-all/app/src-tauri/src/coordinator/dictation.rs。

实测结论:

建议:#666 也会合,请在合并前与 #666 作者协调顺序——要么先合本 PR 三件套再 rebase #666,要么先合 #666 再 rebase 本 PR。

(维护者批量 merge 时可参考此顺序。)

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.

[bug][remote] 手机端永远收不到识别结果:remote:result 事件全仓从未 emit

2 participants