Files
OSGKeyboard/docs/clipboard-voice-command-plan.md
T
Rocky f197b68573 feat(keyboard): add clipboard voice command mode
Long-press mic runs ASR as an instruction over eligible clipboard text,
with host-confirm recording UI, min-record gate, and light ASR prewarm.
Bump CURRENT_PROJECT_VERSION to 53.
2026-08-07 16:08:42 +08:00

16 KiB
Raw Blame History

剪贴板语音指令(Clipboard Voice Command)一页规划

文档状态:产品与架构规划(决策已冻结;实现进行中)
适用范围:iOS 主 App + 键盘扩展(复用现有 Flow / ASR / 润色管线)
分支feature/clipboard-voice-command
创建日期2026-08-07
修订日期2026-08-07(架构评审 + 状态机 + 过滤/手势 + 协议/Prompt 冻结)
关联能力Flow 会话、PiP 保活、Polish Style Packs、字段指纹插入


1. 一句话定义

用户复制一段材料到剪贴板后,长按麦克风口述处理意图;系统以「剪贴板快照 = 材料、ASR = 指令、当前风格包 = 底色」生成结果,并直接写入当前输入框。短按始终回到普通听写。

覆盖场景:社交回复(委婉拒绝、温暖安慰)与 文档处理(精简、总结)共用一条管线。


2. 冻结决策

选择
入口 复用麦克风,长按为新增状态:短按 = 现有听写 toggle;长按 = 剪贴板指令
长按录音 达标后 立刻开录松手结束本轮并进入生成
生成中麦克风 F1:锁定,等结果上屏后再长按连续改写(不排队、不取消重录)
提示文案 有开场资格时改为:「点击说话,长按处理剪贴板」
剪贴板读取 机会读(键盘出现 / 回前台 / 触摸键盘)更新提示;长按开场再读一次并 冻结快照;不后台轮询
开场资格时钟 复制后 30 秒内 可进入
材料过滤 R0R6;过短 有效长度 < 15;见 §4
长按达标 0.45s;松手结束;滑出再松仍送生成(见 §4.1)
指令会话时钟 开场成功起 30 秒;每轮 成功上屏 刷新 30 秒;失败不刷新
与 Flow/PiP 独立子状态:指令 30s ≠ Flow 无活动超时;小会话结束不退出 Flow
材料快照 开场瞬间冻结;会话中剪贴板变更不影响本会话
首轮写入 空框写入;非空追加
连续改写 干净 → 替换上轮产物不干净 → 追加(用户自删)
替换实现 不依赖系统撤销 API(扩展无法调用宿主 Undo);用自记文本 + deleteBackward 尽力替换
会话中短按 结束指令会话 → 听写
换输入框 结束指令会话(拿不准是否换框时,按不干净处理:只追加不删)
风格 共享基础润色契约;当前 Style Pack 作底色;本轮指令优先
翻译开关 指令模式 MVP 忽略
预览 直接上屏,不强制确认
录音条 展示指令 ASR 原文(仅 UI,不上屏到输入框)
语音历史 只记成功产物;不记指令 ASR;快照不落盘
密码 / 安全框 禁止指令模式
扩展 jetsam 指令会话结束(内存态丢弃,可接受)
失败 fallback 指令模式 禁止 raw ASR 插入输入框(与听写失败落原文相反)
协议 扩展 FlowCommandA1utteranceMode + clipboardSnapshotstart 即携带;见 §11
快照上限 wire 与送模型均为 3000 字(超出截断)
Prompt 独立 ClipboardCommandPromptComposer;材料 / 指令 / 上一版;风格 B1 短偏置;见 §11

3. 交互主路径

复制文本
  → 机会读发现开场资格(≤30s + 通过材料过滤)
  → 提示:「点击说话,长按处理剪贴板」
  → 长按达标 → 立刻录音,再读并冻结 clipboardSnapshot,开启指令会话
  → 松手 → 停录;录音条曾展示指令 ASR
  → ASR(指令) + Snapshot(材料) + StylePack(底色) → LLM(生成中 mic 锁定)
  → 成功:空框写入 / 非空追加;记录本轮产物;刷新会话 30s;可写入语音历史(仅产物)
  → 失败:不插入;不写历史;会话保持但不续期
  →(可选)出字后再长按连续改写:
        干净则删上轮产物再写新版;否则追加
  → 短按 / 会话到期 / 换输入框 / 收起键盘 / jetsam → 结束会话,丢弃快照

4. 开场过滤(可测规则,冻结)

命中任一条 → 保持听写提示,不进入指令模式

计数: trim 首尾空白后用 Swift String.count(扩展字形簇),下称「有效长度」。

# 规则 可测定义
R0 空 / 非文本 trim 后为空
R1 整段像号码 去掉空白后匹配 ^[\d\-\+\(\)\s]+$ 且至少含 1 位数字(任意长度)
R2 纯 emoji/符号 去掉空白后 字母、汉字、数字
R3 验证码形态 有效长度 4…8,整段仅 [A-Za-z0-9],且 同时含 字母与数字
R4 过短 有效长度 < 15(定死)
R5 单字符刷屏 去掉空白后长度 ≥ 15,不同字符种类 ≤ 2,且某一字符占比 ≥ 80%
R6 运行时拒绝 安全输入框、无 Full Access、读剪贴板失败(含系统粘贴权限拒绝)

不做(MVP): 用模型判断「有无意义」;因含 URL/邮箱整段拒绝;因过长拒绝入口。

送模型截断(不影响入口): 快照超过 3000 字则截断(与 wire 上限相同),prompt 可注明已截断。

例:

文本 结果
13812345678+86 138-1234-5678 拒(R1
😀😀😀!!! 拒(R2
A8f2K1 拒(R3
周末吃饭吗<15 拒(R4
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊 拒(R5
周末有空一起吃个饭吗?我想聊下项目进度。

首次读剪贴板触发系统粘贴权限且失败 → 无资格 + 弱提示。


4.1 长按阈值(冻结)

长按达标 0.45s
未达 0.45s 松手 视为 短按 → 听写 toggle
达到 0.45s 可选轻震 + 立刻开指令录音
达标后滑出按钮再松手 MVP仍结束本轮并送去生成(不做滑出取消)

5. 写入与「干净」判定

可替换(干净)须同时满足: 同一指令会话、能判定仍在同一输入框、有上轮插入记录、光标仍在上轮产物末尾、上轮文本未被用户改动。

推荐执行顺序: LLM 成功拿到新文本 → 再删旧 → 再插新;删后插失败则尽力写回旧文本。不确定时 只追加或不插,绝不误删用户原文

说明: 用户仍可自行使用系统撤销(摇一摇等)作为逃生口;产品自动替换不依赖该能力。


6. 失败态与边界(优先级)

  1. 不丢用户原文(替换不确定 → 追加或不插)
  2. 不把指令 ASR 写入输入框(ASR/LLM 失败均不插入;禁用听写式 raw fallback
  3. 不静默滥用剪贴板(无资格不进指令;机会读 + 开场再读;不轮询)
  4. 软失败可重试但不续期(没听清 / 生成失败:不插入、会话保持、时钟不刷新)
  5. 少打断(弱提示,不弹模态)
场景 策略
开场资格过期 仅改回普通听写文案;不必再弹「已过期」。此后长按不进指令
无有效语音 / ASR 空 不调用 LLM;不插入;麦克风上方文本提示;会话保持(不续期)
指令不可解析 麦克风上方提示说明要怎么处理;不插入
LLM 超时/拒绝/空结果 不插入;麦克风上方提示可重试;不刷新时钟
换输入框 / 收起键盘 / jetsam 结束会话;已上屏保留
会话中剪贴板被覆盖 本会话仍用旧快照
密码框 禁止指令模式
生成中再长按 忽略(mic 锁定),等上屏后再改写

隐私: 快照仅内存;会话结束丢弃;不写历史明文;不把指令 ASR 写入历史。


7. 与现有架构的关系

可复用 必须新建(实现期)
Flow 命令/结果桥、PiP、ASR、插入器、字段指纹 长按手势层;机会读与 R0R6 过滤
PolishingService(含 systemPrompt 覆盖) ClipboardCommandPromptComposerfinalize 按 utteranceMode 分流
Flow 大会话保活 指令会话态(扩展);FlowCommand mode/snapshot/previousOutput;禁 raw 门禁

两套会话(写死):

  • Flow / PiP:主 App 可响应录音(分钟级无活动等现有策略)
  • 指令会话:一次「处理剪贴板」任务(30s 刷新规则如上)
  • 小会话结束 ≠ 退出 Flow;勿把指令 30s 接到 FlowInactivityDuration

非目标(本期): 后台轮询剪贴板、强制预览、独立回复大按钮、生成中排队/取消重录(F2/F3)、把指令模式混进现有 transcript-polish 路径、依赖宿主 Undo API。


8. 状态机(冻结)

状态机回答三件事:现在在哪、什么事件会跳转、哪些操作允许。分两层,勿混用。

8.1 层 A — 手势(手指)

状态 含义
空闲 未按下
按下待判定 已按下,尚未达到长按阈值(可能变成短按)
指令按住录音 长按已达标,正在录指令;松手 → 停录
(听写录音) 仍由现有 短按 toggle 进入/结束,不经本层长按路径

8.2 层 B — 剪贴板任务(生意)

状态 提示 / mic 含义
无资格 普通听写文案 材料不合格、已过期、安全框、无权限等
有资格 「点击说话,长按处理剪贴板」 机会读通过:合格材料且复制后 ≤30s
指令录音中 录音条可展示指令 ASR 长按已开场,快照已冻结
生成中 mic 锁定F1 转写 + LLM;不接受新的短按/长按开录
可连续改写 可再长按;短按则退出 上一轮已结束(成功上屏或失败提示);会话 30s 未到期
已结束 清快照与上轮产物记录;再评估有资格/无资格

8.3 主转移

无资格
  │ 机会读:合格且 ≤30s
  ▼
有资格 ◄─────────────────────────────────────────┐
  │ 长按达标(开场读并冻快照)                      │
  ▼                                               │
指令录音中 ──松手──► 生成中                         │
                      │                             │
          ┌───────────┼───────────┐                 │
          ▼           ▼           ▼                 │
       成功上屏    失败(仅麦克风上方文本提示)      │
          │           │                             │
          └─────┬─────┘                             │
                ▼                                   │
           可连续改写 ──再长按───────────────────────┘
                │         (回到「指令录音中」,同一快照)
                │ 短按听写 / 会话30s到期 / 换输入框
                │ / 收起键盘 / jetsam
                ▼
             已结束 ──► 机会读 → 有资格 或 无资格

资格过期: 机会读发现超时 → 无资格,提示改回普通文案即可,不再单独弹「剪贴板已过期」。

生成失败: 不插入、不写历史、不刷新会话 30s;落在 可连续改写(若会话未到期),麦克风上方文本提示;若墙钟已超过会话 30s → 已结束

换框拿不准: 不强制跳「已结束」时,按「不干净」只追加不删(见 §5);一旦能判定换框 → 已结束

8.4 事件 × 任务态(摘要)

当前任务态 短按 长按达标 松手
无资格 听写 toggle 不进指令
有资格 听写 toggle → 指令录音中
指令录音中 → 生成中
生成中 忽略 忽略
可连续改写 → 已结束,再听写 → 指令录音中(同快照)
已结束 听写 视重新评估后的资格

9. 成功标准(验收)

  1. 复制合格文本后,经机会读,30s 内提示变为「点击说话,长按处理剪贴板」;过期后仅改回普通文案。
  2. 长按立刻录音,松手后生成;口述「委婉拒绝 / 精简 / 总结」等,结果直接进入当前输入框。
  3. 录音条可见指令 ASR;失败时输入框不被指令原文污染,麦克风上方有文本提示。
  4. 出字后再长按改写:未手改则替换上轮产物;手改或不干净则追加。
  5. 生成中无法再开下一轮录音(F1)。
  6. 会话中短按恢复听写;换输入框结束指令会话。
  7. 纯数字 / 纯 emoji / 验证码 / 有效长度<15 / 刷屏等不出现指令提示。
  8. 语音历史仅出现成功产物,无指令 ASR、无剪贴板快照明文。
  9. 长按 0.45s 开录;未达阈值松手走听写;滑出松手仍生成。

11. 协议与 Prompt(冻结)

11.1 FlowCommand 扩展(A1

在现有 start/stop/abort 上增加(protocolVersion bump,保持旧字段可解码):

字段 时机 含义
utteranceMode start(必填语义) dictation(默认/缺省)| clipboardCommand
clipboardSnapshot start 即带 开场冻结的材料;≤3000 字;仅 clipboardCommand
fieldContext 可仍在 stop 时带 插入指纹 / 空框判定等(沿用现状)
长按达标 → 冻快照(≤3000
  → startRecording { mode: clipboardCommand, clipboardSnapshot }
  → 松手 → stopRecording { fieldContext? }
  → host finalizemode=clipboardCommand → 指令 Composer
                 失败 → errorrawText 可留作调试,ext 不得 insert raw

连续改写 = 同一指令会话内多个 utterance;每轮 start 重复携带同一份内存快照。
「上一版产物」不进 FlowCommand,由扩展在成功上屏后记住,经 host 侧 prompt 用户区传入(见 11.2)——若上一版仅 ext 知道,则需在 stop/start 增加可选 previousOutput,或 finalize 前写入 App Group。

推荐补字段(冻结): start 或 stop 可选 previousOutput: String?(连续改写时由 ext 带上轮成功产物;首轮 nil)。与快照同属 utterance 自描述,避免旁路。

FlowResult 回传 utteranceMode(或等效标记)。ext 对 clipboardCommand跳过一切 raw ASR 上屏路径(含 deliverRawFallbackIfAvailable)。

11.2 Prompt 分流

听写 剪贴板指令
Composer PolishPromptComposer(含 R6 新建 ClipboardCommandPromptComposer
用户文本角色 ASR = 待整理正文 ASR = 指令;快照 = 材料
翻译开关 可生效 忽略,强制非 translate
失败 可 fallback raw ASR 禁止 raw 上屏
Style Pack 完整听写人格装配 B1:短语气偏置;指令优先

System 契约(精神): 按材料执行指令;只输出可直接发送的最终文本;无解释套话;不编造材料中没有的关键事实(除非指令要求语气发挥);Style Pack 为弱底色;口述指令覆盖风格。

User 结构:

【材料】
{clipboardSnapshot}

【指令】
{asrInstruction}

【上一版结果】   ← 仅连续改写且有 previousOutput
{previousOutput}

11.3 职责切分

职责 归属
机会读、R0–R6、资格 30s、hint、长按 0.45s 扩展
指令会话 30s、快照内存、上轮产物、干净替换 扩展
ASR、指令 LLM、结果 mode 标记 主 App
坚持不插 raw、插入/替换执行 扩展
Style Pack 短偏置读取 主 App(现有 store

主 App 对指令 utterance 尽量无会话态(按包执行);指令会话态在扩展 → 与 jetsam=会话结束一致。


12. 下一步(可进入实现)

  1. 实现 Shared:过滤纯函数 + 单测;FlowCommand/FlowResult 字段与兼容解码。
  2. 实现 ClipboardCommandPromptComposer + finalize 分流 + 禁 raw。
  3. 扩展:机会读 hint、长按 0.45s、指令会话态、插入/替换。
  4. 联调验收对照 §9。