# Pairing 流程設計 — visionA Cloud > **雲端版最關鍵的新增流程**。使用者要能在雲端 Web 取得 Pairing Token、帶到自己電腦上的 local agent、完成配對,之後就能從瀏覽器遠端控制自己的 Kneron 裝置。 > > 配套的文字版 wireframe 見 [`wireframes/wf-pairing.md`](../wireframes/wf-pairing.md)。 --- ## 1. User Story > **作為** 一個 Kneron 開發者, > **我想要** 用一組短短的 token 讓雲端和我桌機上的 Kneron 裝置建立連線, > **這樣** 我就能從任何地方(出差、客戶端、家裡)用瀏覽器操作我的裝置,不需要每次都連 VPN 或 SSH。 **成功條件**: - Token 產生到完成連線 < 3 分鐘 - 使用者不需要懂 tunnel / yamux / WebSocket 等技術細節 - 失敗時能明確告訴使用者哪一步錯、怎麼修 --- ## 2. 流程全景圖 ``` 雲端 Web 使用者電腦 雲端後端 ─────── ───────── ──────── local agent (Go binary) [登入] ──→ [/devices/pair] ──────────────────────────────────→ POST /api/pairing/token ▼ ↓ 產生 token 產生 vAc_ + 32 hex token ▼ (綁 user_id, TTL 15 分鐘) [PairingTokenCard 顯示 token] ↓ ▼ 回傳 token 使用者按「複製」 ←───── ▼ 切換到「下載 local agent」分頁 ▼ 根據 OS 顯示對應下載連結 + 指令 ▼ 使用者下載、安裝、啟動 local agent ▼ local agent 啟動 ────→ GET /tunnel/connect?token=xxx ▼ ↓ 第 3 步:等待連線 WebSocket 升級 ▼ yamux session 建立 [polling] GET /api/pairing/status?token=xxx ──────────────────→ ↓ ▲ 回 200 + 裝置資訊 │ ↓ ◀─────────────────────────────────────────────────────────── ▼ 「✓ 已連線!」+ 顯示裝置資訊 ▼ [繼續] 按鈕 ▼ 跳 /devices,顯示新裝置 ``` --- ### 2.1 Token 格式與兩階段 Token 模型 **雲端有兩種 token,使用者只會接觸到第一種:** | Token | 用途 | 格式 | TTL | 使用者可見 | |-------|------|------|-----|----------| | **Pairing Token** | 一次性配對 token,使用者貼到 local agent 啟動連線 | `vAc_` + 32 字元 hex(總長 36)| **15 分鐘** | ✅ 看得到、要複製 | | **Session Token** | Local agent 換到後維持 tunnel 連線用 | 後端規格(使用者不關心) | 90 天 | ❌ 完全看不到 | **換取流程(使用者無感):** ``` 1. 雲端 Web 產生 Pairing Token(vAc_xxxx…)→ 使用者複製 2. 使用者貼到 local agent → local agent 用這個 token 打 /tunnel/connect 3. 雲端驗證 Pairing Token → 核發 Session Token 給 local agent 4. Pairing Token 立即失效(one-time use) 5. 後續 local agent 所有連線都帶 Session Token,使用者完全感受不到 ``` **UI 顯示策略(36 字元太長、15 分鐘很緊):** - API 回傳完整 `vAc_` + 32 hex(一整串,共 36 字元,無空格) - **UI 顯示切成兩行**提升可讀性(`font-mono text-xl tracking-wider`): - 第 1 行:`vAc_` 前綴 + 前 16 字元 hex(共 20 字元) - 第 2 行:後 16 字元 hex - 行內用 `-` 或純空白切開都行,**切法純視覺**;`select-all` 選取時選到整個區塊 - **實際複製到剪貼簿的永遠是無空格、無換行的完整字串**(`vAc_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8`) - Mobile(< 640px):字級降為 `text-lg`,仍切兩行;不允許水平捲動 - 螢幕閱讀器:`aria-label` 給完整字串,但文字節點用分組避免一字一念 --- ## 3. 三步式 Stepper 使用既有視覺語彙,以 Tabs 或 Stepper UI 呈現。**建議使用 Stepper**(因為步驟有順序依賴)。 ### 3.1 視覺 ``` ┌─────────────────────────────────────────────────────────────────┐ │ ← 返回 │ │ │ │ 配對新裝置 │ │ 讓你的 Kneron 裝置連上雲端,就能從任何地方遠端操作 │ │ │ │ ●──────────────────●──────────────────○ │ │ 1 2 3 │ │ 取得 Token 設定 Local Agent 確認連線 │ │ │ │ [當前步驟內容] │ │ │ │ [上一步] [下一步] │ └─────────────────────────────────────────────────────────────────┘ ``` ### 3.2 Stepper 規格 - 步驟指示器:`flex items-center gap-2`,使用 Lucide `Circle` / `CheckCircle` - 已完成:`bg-primary text-primary-foreground` + `CheckCircle` 填色 - 當前:`ring-2 ring-primary bg-background` - 未完成:`bg-muted text-muted-foreground` - 步驟間連線:`h-0.5 bg-muted`(已完成段落 `bg-primary`) - 下方步驟標題:`text-sm font-medium`(已完成/當前)、`text-muted-foreground`(未完成) ### 3.3 底部按鈕 - 上一步:`variant=outline`,第 1 步 disabled - 下一步:`variant=default`,第 3 步改為「完成 → 進入裝置列表」 --- ## 4. Step 1 — 取得 Token ### 4.1 版型 ``` ┌──────────────────────────────────────────────────────────────┐ │ Step 1 · 取得 Pairing Token │ │ │ │ 複製下方 token,在 Step 2 讓 local agent 使用這組 token 連線雲端 │ │ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ 🔗 你的 Pairing Token │ │ │ │ │ │ │ │ ┌──────────────────────────────────────────────────┐ │ │ │ │ │ vAc_a1b2c3d4e5f6a7b8 │ │ │ │ │ │ c9d0e1f2a3b4c5d6e7f8 │ │ │ │ │ └──────────────────────────────────────────────────┘ │ │ │ │ (視覺切兩行,複製永遠是完整 36 字元無空格) │ │ │ │ │ │ │ │ [📋 複製] [🔄 重新產生] │ │ │ │ │ │ │ │ ⏱ 剩餘 14:52 ─────────────────────── │ │ │ │ 進度條(bg-primary,隨時間變 amber → red) │ │ │ │ 📅 產生時間:14:30 │ │ │ └─────────────────────────────────────────────────────────┘ │ │ │ │ ⚠ 這組 token 15 分鐘內有效,請立刻到 Step 2 完成配對 │ │ token 是一次性使用,完成配對後自動失效 │ │ │ └──────────────────────────────────────────────────────────────┘ ``` ### 4.2 主要元件 - `PairingTokenCard`(見 `components.md` 10.2)— 含倒數計時器 - 下方安全提示:`Callout` 樣式(`bg-amber-50 border-amber-300`,或用 Alert 元件) ### 4.3 初始狀態 - 進入頁面自動呼叫 `POST /api/pairing/token` 產生新 token(`vAc_` + 32 hex,TTL 15 分鐘) - 客戶端啟動 1 秒一次的倒數計時(從 `expiresAt - now()` 算起),不 polling 後端 - 若 API 失敗:顯示 error state 「無法產生 token,請重試」+ 重試按鈕 ### 4.4 倒數計時器視覺規格 TTL 從 72 小時降到 15 分鐘,倒數計時器要**顯著且突出**,讓使用者意識到要馬上行動: | 剩餘時間 | 視覺 | 行為 | |---------|------|------| | > 10:00 | `text-foreground` + 進度條 `bg-primary` | 正常顯示 | | 10:00 – 3:00 | `text-amber-600` + 進度條 `bg-amber-500` | 文字變粗 `font-medium` | | 3:00 – 0:30 | `text-red-600` + 進度條 `bg-red-500` + 卡片 `ring-1 ring-red-300` | 計時文字前加 ⚠;建議配 `prefers-reduced-motion` 尊重,不閃爍 | | ≤ 0:30 | 同上 + Toast「Token 即將過期,請立刻完成或重新產生」 | 只 Toast 一次 | | 00:00 | Token 轉灰 `text-muted-foreground line-through`;複製按鈕 disabled;顯示「此 token 已過期,請重新產生」 | `aria-live="assertive"` 宣告 | **格式**:`mm:ss`(例:`14:52`、`02:17`、`00:08`);不要顯示小時,15 分鐘內絕不超過 15:00。 ### 4.5 互動行為 | 操作 | 行為 | |------|------| | 點「複製」| 呼叫 `navigator.clipboard.writeText(token)` 複製**完整 36 字元無空格**字串;按鈕暫態變「已複製 ✓」2 秒;toast「Token 已複製到剪貼簿,15 分鐘內有效」| | 點「重新產生」| 開 AlertDialog:「確定要重新產生?舊 token 將立即失效,新 token 有效期 15 分鐘。」→ 確認後 POST API,更新 UI,重啟倒數 | | Token 過期(0:00)| 自動將 UI 切至過期狀態;主 CTA 從「下一步」改為「重新產生 token」| | 進入 Step 2 前 | 檢查使用者是否已複製(用 state)。未複製則禁止下一步,提示「請先複製 token」或允許繼續但 toast warning | ### 4.6 i18n key ``` pairing.step1.title → Step 1 · 取得 Pairing Token pairing.step1.description → 複製下方 token,15 分鐘內到 Step 2 完成配對 pairing.token.title → 你的 Pairing Token pairing.copy → 複製 pairing.copied → 已複製 pairing.regenerate → 重新產生 pairing.timeRemaining → 剩餘 {time} pairing.expiresIn15min → 15 分鐘內有效 pairing.generatedAt → 產生時間:{time} pairing.security.warning → 這組 token 15 分鐘內有效,請立刻到 Step 2 完成配對 pairing.security.oneTime → token 是一次性使用,完成配對後自動失效 pairing.regenerateConfirm.title → 確定要重新產生? pairing.regenerateConfirm.description → 舊 token 將立即失效,新 token 有效期 15 分鐘 pairing.toast.copied → Token 已複製到剪貼簿,15 分鐘內有效 pairing.toast.generateFailed → 無法產生 token,請重試 pairing.toast.expiringSoon → Token 即將過期,請立刻完成或重新產生 pairing.token.expired.label → 此 token 已過期,請重新產生 ``` --- ## 5. Step 2 — 設定 Local Agent ### 5.1 版型 ``` ┌──────────────────────────────────────────────────────────────┐ │ Step 2 · 設定 Local Agent │ │ │ │ 在你的電腦下載並啟動 local agent,讓它連上雲端 │ │ │ │ 選擇作業系統: │ │ [ macOS ] [ Windows ] [ Linux ] │ │ ▲ active │ │ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ 1. 下載 local agent │ │ │ │ [📥 下載 visionA-local-agent-macos.dmg] │ │ │ │ │ │ │ │ 2. 安裝並啟動 │ │ │ │ 打開 DMG,拖曳到 Applications,雙擊啟動 │ │ │ │ │ │ │ │ 3. 輸入 Pairing Token │ │ │ │ 在 local agent 的「雲端」設定頁貼上 token │ │ │ │ │ │ │ │ 或使用命令列(CLI 進階使用者): │ │ │ │ ┌──────────────────────────────────────────────────┐ │ │ │ │ │ visiona-agent --pair-token=\ │ │ │ │ │ │ vAc_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8 │ │ │ │ │ └──────────────────────────────────────────────────┘ │ │ │ │ (複製時為完整單行無反斜線) │ │ │ │ │ │ │ │ 4. 連線成功後,回到這個頁面進入 Step 3 │ │ │ └─────────────────────────────────────────────────────────┘ │ │ │ │ 💡 已經有 local agent?[跳到 Step 3] │ └──────────────────────────────────────────────────────────────┘ ``` ### 5.2 作業系統分頁 使用 `Tabs` 元件,三個 tab:`macOS` / `Windows` / `Linux`,自動偵測 `navigator.userAgent` 設預設。 **平台差異:** | OS | 下載檔案 | 安裝步驟 | 命令列 | |----|---------|---------|--------| | macOS | `.dmg` | 拖曳到 Applications | `visiona-agent --pair-token=vAc_...` | | Windows | `.exe` installer | 雙擊執行 | `visiona-agent.exe --pair-token=vAc_...` | | Linux | `.AppImage` | `chmod +x` 後執行 | `./visiona-agent --pair-token=vAc_...` | ### 5.3 指令區塊 - 使用 `bg-muted font-mono text-sm p-3 rounded-md` 樣式 - 右上角有「複製」小按鈕 - 將使用者 token 自動嵌入指令(`--pair-token=[當前 token]`) ### 5.4 互動 | 操作 | 行為 | |------|------| | 切換 OS Tab | 內容重新渲染 | | 點「下載」按鈕 | 直接下載對應 OS 的 installer | | 點指令「複製」| 複製整行 `visiona-agent --pair-token=vAc_xxxx…`(完整 36 字元 token,單行無跳行)| | 「已經有 local agent?」| 直接跳 Step 3 | ### 5.5 i18n key ``` pairing.step2.title → Step 2 · 設定 Local Agent pairing.step2.description → 在你的電腦下載並啟動 local agent... pairing.step2.selectOS → 選擇作業系統 pairing.step2.download → 下載 local agent pairing.step2.install → 安裝並啟動 pairing.step2.installHint.macos → 打開 DMG,拖曳到 Applications... pairing.step2.installHint.windows → 雙擊 installer... pairing.step2.installHint.linux → chmod +x 後執行... pairing.step2.enterToken → 輸入 Pairing Token pairing.step2.tokenHint → 在 local agent 的「雲端」設定頁貼上 token pairing.step2.orCli → 或使用命令列(CLI 進階使用者): pairing.step2.waitConnect → 連線成功後,回到這個頁面進入 Step 3 pairing.step2.alreadyHaveAgent → 已經有 local agent?[跳到 Step 3] ``` --- ## 6. Step 3 — 確認連線 ### 6.1 視覺(等待中) ``` ┌──────────────────────────────────────────────────────────────┐ │ Step 3 · 確認連線 │ │ │ │ ⏳ 等待 local agent 連線... │ │ │ │ 已等待 0:15(最長 3 分鐘) │ │ │ │ [取消] [查看 Troubleshooting] │ │ │ │ 提示: │ │ • 確認 local agent 已啟動 │ │ • 確認 token 正確無誤 │ │ • 確認你的網路可以連到 cloud.visiona.ai │ └──────────────────────────────────────────────────────────────┘ ``` ### 6.2 視覺(成功) ``` ┌──────────────────────────────────────────────────────────────┐ │ Step 3 · 確認連線 │ │ │ │ ✓ 已成功連線! │ │ │ │ 檢測到的裝置: │ │ ┌────────────────────────────────────┐ │ │ │ 🔌 Kneron KL520 │ │ │ │ Firmware 2.3.1 · Host: office-mac│ │ │ └────────────────────────────────────┘ │ │ │ │ [進入裝置列表 →] │ └──────────────────────────────────────────────────────────────┘ ``` ### 6.3 視覺(失敗 / 超時) ``` ┌──────────────────────────────────────────────────────────────┐ │ Step 3 · 確認連線 │ │ │ │ ⚠ 連線超時 │ │ │ │ 超過 3 分鐘沒收到 local agent 連線 │ │ │ │ 可能原因: │ │ • local agent 尚未啟動 │ │ • token 輸入錯誤 │ │ • 防火牆擋住 WebSocket 連線 │ │ │ │ [重新檢查] [回到 Step 2] [查看完整說明] │ └──────────────────────────────────────────────────────────────┘ ``` ### 6.4 行為 - 進入 Step 3 自動開始 polling `/api/pairing/status?token=xxx`(每 3 秒) - 計時器顯示已等待時間 - 成功(`status: connected`):停止 polling,顯示成功畫面 + 裝置資訊 - 超時(> 3 分鐘):停止 polling,顯示失敗畫面 - 「取消」:返回 `/devices`,token 保留(使用者可稍後重試) - 「重新檢查」:重啟 polling ### 6.5 i18n key ``` pairing.step3.title → Step 3 · 確認連線 pairing.step3.waiting → 等待 local agent 連線... pairing.step3.elapsed → 已等待 {time}(最長 3 分鐘) pairing.step3.cancel → 取消 pairing.step3.troubleshooting → 查看 Troubleshooting pairing.step3.hints.title → 提示: pairing.step3.hints.running → 確認 local agent 已啟動 pairing.step3.hints.token → 確認 token 正確無誤 pairing.step3.hints.network → 確認你的網路可以連到 cloud.visiona.ai pairing.step3.success → 已成功連線! pairing.step3.success.detected → 檢測到的裝置: pairing.step3.success.continue → 進入裝置列表 → pairing.step3.failure.timeout → 連線超時 pairing.step3.failure.reason → 超過 3 分鐘沒收到 local agent 連線 pairing.step3.failure.causes → 可能原因: pairing.step3.failure.retry → 重新檢查 pairing.step3.failure.backToStep2 → 回到 Step 2 pairing.step3.failure.docs → 查看完整說明 ``` --- ## 7. 邊界情境 ### 7.1 Pairing Token 過期(15 分鐘後) - 進入 Step 3 polling 時若收到 `status: expired` → 顯示「Token 已過期」+ 「回到 Step 1 重新產生」 - Step 1 若使用者停留超過 15 分鐘未下一步,倒數歸零 UI 會自動切到過期狀態(見 4.4) - **注意**:這裡過期的是 Pairing Token(15 分鐘)。Local agent 一旦換到 Session Token(90 天),後續連線不受影響,使用者看不到這層 ### 7.2 同一 token 被多次使用 - Architect 端決策:token 應該是**一次性**的(綁第一個連上的 local agent) - 後續若同 token 被其他 agent 嘗試連線 → Step 3 polling 顯示「此 token 已被其他裝置使用」 ### 7.3 網路中斷(polling 失敗) - 顯示臨時錯誤 + 自動重試(指數退避) - 3 次失敗後提示「網路不穩,請檢查連線」 ### 7.4 頁面離開 / 重新整理 - 若 token 已產生但未完成連線:重新進入頁面應該偵測到並回到 Step 3(或重回 Step 1 重新產) - 使用 URL query param `?token=xxx` 保存進行中的 token ### 7.5 多裝置一次配對 - Phase 0:一次只配對一個裝置(一個 token 對一台 agent / 一台 host) - Phase 1:考慮支援一台 host 多張 Kneron 卡(agent 端回報多裝置) --- ## 8. 成功後的導航 完成配對後: 1. 在 `/devices` 列表看到新裝置(`RemoteDeviceBadge` = 在線) 2. Toast:「裝置 Kneron KL520 已成功配對」 3. Activity Timeline 新增一筆 `device_paired` 紀錄 --- ## 9. 給 Architect / Backend 的 API 契約提議 ### 9.1 `POST /api/pairing/token` Pairing Token:`vAc_` 前綴 + 32 字元 hex,TTL 15 分鐘,一次性使用。 ```json Request: (需 auth,雛形可 skip auth) Response 200: { "token": "vAc_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8", "expiresAt": "2026-04-21T14:45:00Z", "createdAt": "2026-04-21T14:30:00Z", "ttlSeconds": 900 } ``` Session Token(使用者看不到,雲端 ↔ local agent 內部):TTL 90 天,Pairing Token 成功換取後發給 local agent。此層 UI 無關,僅記錄備查。 ### 9.2 `GET /api/pairing/status?token=xxx` ```json Response 200: { "status": "pending" | "connected" | "expired" | "used", "device": { "id": "kl520-usb-0001", "name": "Kneron KL520", "type": "KL520", "firmwareVersion": "2.3.1", "hostName": "office-mac" } | null, "pairedAt": "2026-04-21T14:35:00Z" | null } ``` ### 9.3 `DELETE /api/pairing/token/:token` 重新產生時,先刪除舊的。 ### 9.4 `POST /api/devices/:id/unpair` 解除配對(從 `/devices/[id]` 的 DeviceSettingsCard 或 `/account` 觸發)。 --- ## 10. 無障礙檢查 - Stepper:`role="list"` + 每個 step `role="listitem"` + 當前 step `aria-current="step"` - Token 顯示:`aria-label="Pairing token, {token}"`(但不念出字元) - 複製按鈕:`aria-live="polite"` 宣告「已複製」 - Step 3 等待:`aria-live="polite"` 宣告 status 變更 - 所有按鈕可 Tab 聚焦、Enter / Space 觸發 - 錯誤訊息:`role="alert"` --- ## 11. TODO | 項目 | 時機 | |------|------| | Local agent UI 的「雲端」分頁設計(接收 token 的那端)| Phase 0 同步設計(需跨 local-tool,另開需求)| | ~~Token 過期警告~~ → 已納入 Phase 0(見 4.4 倒數計時器,因 TTL 降至 15 分鐘必做)| — | | 支援 Email 發送 token(方便跨裝置)| Phase 2 | | QR Code 配對(手機掃描)| Phase 2 | | 一次性 pairing code(更短 6 位數字,UX 更好)| Phase 2 |