dddk — Subtitle UI
字幕條 — dddk 跟用戶溝通的主要介面。用戶按 space 確認、按雙 space 拒絕、按 Tab 接受部分、按 Escape 取消。
結構
┌────────────────────────────────────────────────────┐
│ │
│ [page content] │
│ │
│ │
│ ┌─────────────────────────────────────────────┐ │
│ │ 💬 Subtitle text shown here │ │
│ │ 按 space 同意 | 雙擊 space 拒絕 | Tab 接受一行│ │
│ └─────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────┘
↑ fixed bottom, centered
五種類型
| Type | 觸發者 | 行為 |
|---|---|---|
voice |
語音轉文字結果 | 顯示 + 等用戶 accept / reject |
selection |
SelectionAgent 結果 | 顯示 + 提供 copy / accept |
agent |
webagent 的 show_subtitle |
顯示 + 允許 agent 繼續 |
post |
一般文字建議(autocomplete) | accept 直接插入 / reject 消失 |
info |
純訊息 | 不需要回應,3 秒自動消 |
API
import { Subtitle } from '@perhapxin/dddk';
const subtitle = new Subtitle();
subtitle.show({
text: '要把標題改成「年度報告」嗎?',
type: 'agent',
onAccept?: () => agent.respond('yes'),
onReject?: () => agent.respond('no'),
onCancel?: () => agent.stop(),
hints?: string, // 自訂 hint 文字
autoHide?: number, // ms,預設不自動隱藏
});
subtitle.hide();
subtitle.update(newText); // 不重建,只改文字
subtitle.isVisible();
鍵盤對應
字幕條顯示中:
| 鍵 | 行為 |
|---|---|
space(單擊) |
onAccept |
space(雙擊) |
onReject |
Tab |
onAcceptLine — 多行內容只接受第一行(剩下繼續顯示) |
Escape |
onCancel |
ctrl+space |
input 內專用 accept |
Voice indicator
Voice 啟動時顯示獨立的小 indicator(圓點跳動),跟 subtitle 不同 UI:
subtitle.showIndicator('listening', '聽取中 — 鬆開結束');
subtitle.showIndicator('processing', '處理中...');
subtitle.hideIndicator();
樣式上:indicator 在右下角,subtitle 在正下方。
A2UI 整合
字幕條 placement = inline 的 A2UI surface 渲染在字幕條上方(短表單 / 單選):
subtitle.showA2UI({
surface: { /* A2UI envelope */ },
onSubmit: (data) => agent.respond(data),
});
placement = center 的 A2UI 不用字幕條,直接 modal。
主題化(要點)
完整變數見 11-theming.md。字幕條相關:
:root {
--dddk-bar-bg: rgba(255, 255, 255, 0.95);
--dddk-bar-text: #1a1a1a;
--dddk-bar-shadow: 0 4px 24px rgba(0, 0, 0, 0.1);
--dddk-bar-radius: 12px;
--dddk-bar-padding: 12px 16px;
--dddk-bar-bottom: 24px;
--dddk-bar-max-width: 640px;
--dddk-bar-z-index: 10000;
--dddk-bar-font: system-ui, -apple-system, sans-serif;
}
[data-theme="dark"] {
--dddk-bar-bg: rgba(28, 28, 28, 0.95);
--dddk-bar-text: #ffffff;
}
DOM 結構
完全用 data-dddk-ui 屬性標記,方便 host 自己清理 / inspect:
<div data-dddk-ui="bar" data-dddk-bar-type="agent">
<div data-dddk-ui="bar-text">字幕文字</div>
<div data-dddk-ui="bar-hints">space 同意 | 雙擊 space 拒絕</div>
<div data-dddk-ui="bar-buttons">
<button data-dddk-action="accept">✓</button>
<button data-dddk-action="reject">✕</button>
<button data-dddk-action="copy">📋</button>
</div>
</div>
多 subtitle 怎麼辦
任何時刻最多一個 subtitle bar。新的 show() 取代舊的(除非舊的是 info 類型自動消失中)。
agent 連續 show 多個 → 後面的覆蓋前面的。如果 host 想做「逐步顯示」,自己排序:
const steps = ['第一步', '第二步', '第三步'];
for (const step of steps) {
subtitle.show({ text: step, type: 'info', autoHide: 1500 });
await new Promise(r => setTimeout(r, 1500));
}
響應式
- Mobile (< 640px):字幕條變全寬,hints 折行
- 字幕條會避開 keyboard(mobile 鍵盤彈起時 bottom offset 自動加)
不會做
- ❌ 浮動 / 拖移視窗
- ❌ Pin 在 page 內某位置(永遠 fixed bottom)
- ❌ 多 instance 並存
- ❌ Rich markdown(subtitle 是純文字 + 少量 emoji)