visionA/docs/autoflow/03-design/flows/flow-offline-handling.md
jim800121chen fb7da5d180 chore(autoflow): migrate .autoflow/ 共享層文件至 docs/autoflow/
依 autoflow-agent workspace v2 設計把 PRD / 設計 / 架構 / 交付類
共享文件從個人層 .autoflow/(ignored)搬到 docs/autoflow/(進 git),
讓團隊可共享產品與架構文件,個人層只留 progress / review / testing 等
per-branch 筆記。

- 02-prd/        21 個檔(PRD、features、market-analysis 等)
- 03-design/     18 個檔(design-spec、wireframes、flows 等)
- 04-architecture/ 31 個檔(TDD、design-doc、ADR×14、API 規格等)
- 07-delivery/   3 個檔(project-summary、phase-0.6-handover、stage-deployment-setup)

合計 73 檔。原檔已從 .autoflow/ 移除(migration 工具執行 git mv,
但因 .autoflow/ 在 .gitignore 中、git 將此操作視為新增、無 rename history)。
2026-05-04 16:55:55 +08:00

15 KiB
Raw Permalink Blame History

離線 / 掉線 UI 行為 — visionA Cloud

雲端版的根本差異:使用者的 Kneron 裝置不在瀏覽器那端,而是在某台電腦上透過 local agent 中繼。這條 tunnel 會因為網路抖動、電腦休眠、agent 被關等原因中斷。UI 必須誠實呈現狀態,不讓使用者以為「點了沒反應 = 壞了」。


1. 連線拓樸與失效點

Browser (User)  ←HTTPS/WSS→  Cloud API Server  ←yamux/WS→  Local Agent  ←USB→  Kneron Device
       A                             B                            C                   D
       │                             │                            │                   │
       ▼                             ▼                            ▼                   ▼
   斷網 (A)                     雲端服務掛 (B)              Agent 掉線 (C)          USB 拔掉 (D)

四種失效層級UI 給使用者的回饋應該不同:

失效點 影響範圍 UI 呈現
A. 使用者斷網 全部操作失效 瀏覽器層級navigator.onLine+ 全域 NetworkErrorBanner
B. 雲端 API 掛 全部操作失效 全域 NetworkErrorBanner
C. Local agent 掉線 該使用者的所有遠端裝置 每個裝置卡片顯示離線Activity log 寫入
D. USB 裝置拔除 單一裝置 該裝置狀態 error / disconnected(既有行為)

核心設計原則:

  1. 「我這端沒事」vs「那台電腦沒事」要可區分 — 使用者需要判斷要怎麼處理
  2. 狀態要即時 — 透過 WebSocket 推送 + 合理的 heartbeat interval
  3. 優雅降級 — 裝置掉線時,操作按鈕要變成 disabled而不是點了丟 error

2. 裝置連線狀態模型

雲端版的 Device 新增 remoteStatus 欄位(遠端 tunnel 層級),與既有 status 欄位USB / 硬體層級)並存:

interface Device {
  // 既有
  id: string;
  name: string;
  type: string;
  status: 'detected' | 'connecting' | 'connected' | 'flashing' | 'inferencing' | 'error' | 'disconnected';
  firmwareVersion?: string;
  flashedModel?: string;

  // 雲端版新增
  remoteStatus: 'online' | 'offline' | 'reconnecting' | 'error';
  lastSeenAt: string;       // ISO 8601最後心跳時間
  hostName?: string;        // local agent 回報的主機名稱
  pairedAt: string;         // 配對時間
  errorMessage?: string;    // 錯誤訊息remoteStatus=error 時)
}

2.1 狀態組合矩陣

remoteStatus statusUSB 使用者看到 可執行操作
online connected 🟢 已連線 全部
online inferencing 🔵 推論中 停止推論、切換來源
online flashing 🟡 燒錄中 等待中(唯讀)
online error 🔴 裝置錯誤 重試、查看日誌
online disconnected 裝置未連接 重新連接agent 端)
reconnecting * 🟡 重新連線中pulse 唯讀等待
offline * 離線・最後心跳 X 前 解除配對、查看歷史
error * 🔴 連線錯誤:{message} 查看 troubleshooting

顯示優先級remoteStatus != onlineUI 優先顯示 remoteStatus因為連遠端都沒連上USB 狀態已不可信)。


3. 全域NetworkErrorBanner

觸發條件:雲端 API 不可達A 或 B 失效)

3.1 偵測邏輯(給 Frontend 參考)

// 簡化版邏輯
let consecutiveFailures = 0;
setInterval(async () => {
  try {
    await fetch('/api/system/health');
    consecutiveFailures = 0;
    hideBanner();
  } catch {
    consecutiveFailures++;
    if (consecutiveFailures >= 3) showBanner();
  }
}, 10_000);

// 搭配 navigator.onLine 事件
window.addEventListener('offline', () => showBanner({ cause: 'local' }));
window.addEventListener('online', () => checkHealth());

3.2 狀態展示

狀態 文案 樣式 按鈕
本機斷網 「你的網路似乎離線了」 bg-amber-50 [重試]
API 失敗重試中 「連線中斷 — 無法連上雲端服務。正在重試...」 bg-amber-50 [立即重試]
恢復 「✓ 已恢復連線」 bg-green-50(短暫 3 秒消失) -

3.3 行為

  • 顯示於 Header 下方sticky top-14 z-40
  • 顯示期間不阻擋 UI 操作(使用者仍可點擊按鈕;按下會顯示 toast 錯誤)
  • 恢復後 3 秒自動消失

詳細元件規格見 components.md 10.4。


4. 裝置列表:離線裝置顯示

4.1 在 /devices

┌──────────────────┐   ┌──────────────────┐   ┌──────────────────┐
│ Kneron KL520   🟢 │   │ Kneron KL720   ⚪ │   │ Kneron KL520   🟡 │
│                   │   │ 離線・2 分鐘前    │   │ 重連中...         │
│ 類型KL520        │   │ 類型KL720       │   │ 類型KL520       │
│ 韌體2.3.1        │   │ 韌體cache  │   │ 韌體2.3.1       │
│                   │   │                   │   │                   │
│ [管理][工作區]     │   │ [管理][解除配對]   │   │ [管理]disabled│
└──────────────────┘   └──────────────────┘   └──────────────────┘

離線裝置卡片

  • opacity-75(淡化但仍可讀)
  • RemoteDeviceBadge 顯示「離線・最後心跳 2 分鐘前」
  • 「工作區」按鈕 hidden 或 disabled
  • 「管理」按鈕仍可用(進詳情頁看歷史)
  • 新增「解除配對」選項Phase 0 可放進詳情頁而非卡片)

4.2 排序建議

  • 預設在線優先online → reconnecting → offline → error其次按配對時間
  • Phase 1 加篩選:[全部] [在線] [離線]

4.3 Dashboard ConnectedDevicesList

同樣策略:離線裝置顯示最後心跳時間;若全部裝置離線,顯示 EmptyState「所有裝置都離線了[查看 troubleshooting]」。


5. 裝置詳情頁(/devices/[id]):離線降級

5.1 頁面頂部狀態 Banner

remoteStatus === 'offline'

┌──────────────────────────────────────────────────────────────┐
│ ⚠ 此裝置目前離線                                               │
│   最後心跳時間2026-04-21 14:282 分鐘前)                    │
│   所在電腦office-mac                                         │
│   部分操作無法使用,待 local agent 重新連線後自動恢復              │
│                                                    [重新整理]  │
└──────────────────────────────────────────────────────────────┘

樣式bg-amber-50 dark:bg-amber-950/30 + border-amber-300;圖示 AlertTriangle

5.2 按鈕降級

按鈕 在線 離線
燒錄模型FlashDialog 可用 disabled + tooltip「裝置離線中」
開啟工作區 可用 disabled + tooltip「裝置離線中」
中斷連線 可用 隱藏(已無連線可中斷)
解除配對 可用 可用(使用者仍可清除此紀錄)
編輯別名 / 備註 可用 可用(本地資料修改,不需要 agent

5.3 Cached Data

離線時顯示最後已知資訊(從 server cache 讀):

  • 韌體版本
  • 已燒錄模型
  • 裝置健康狀態(標註「資料截至 X 時間」)

5.4 自動恢復

  • 頁面持續透過 WebSocket 監聽狀態變化
  • remoteStatus: online 推送到 → 自動隱藏 banner、恢復按鈕 → toast「✓ 裝置已重新連線」

6. Workspace/workspace/[deviceId]):執行中掉線處理

Workspace 是最不能忍受掉線的頁面(正在跑推論)。

6.1 頂部狀態列

既有 Workspace 頁頂部有 ← 返回 + 裝置名稱 + FlashDialog + 開始/停止推論,新增:

← 返回    工作區office-mac / Kneron KL520  🟢 在線 (150ms latency)
                                              │
                                              └─ 點擊展開詳情

連線品質提示Phase 1+

  • Latency < 200ms → 🟢 流暢
  • 200-500ms → 🟡 稍慢
  • 500ms → 🔴 不穩

6.2 裝置掉線時的全頁遮罩

┌──────────────────────────────────────────────────────────────┐
│                                                              │
│                      🔌                                      │
│                                                              │
│                裝置已離線                                      │
│                                                              │
│         與 Kneron KL520 的連線中斷,推論已自動停止              │
│                                                              │
│         [等待重連 (0:23)]  [返回裝置列表]                       │
│                                                              │
│      📄 最後 5 張推論結果已儲存在 /activity                    │
│                                                              │
└──────────────────────────────────────────────────────────────┘

行為:

  • 遮罩層 bg-background/80 backdrop-blur-sm,覆蓋 CameraInferenceView + InferencePanel
  • Camera stream 顯示最後一幀 + overlay「連線中斷」
  • 持續 polling / WebSocket 重連嘗試
  • 若 60 秒內重連成功 → 遮罩消失toast「✓ 裝置已重新連線,請手動重啟推論」(不要自動 resume
  • 若 60 秒仍未連線 → 提示「建議先返回,稍後再試」

6.3 推論 buffer / 資料保全

  • Workspace 裡的推論結果(分類結果、效能指標)暫存在 Zustand store
  • 掉線時保留最近資料,可供使用者查看(唯讀)
  • 重連後使用者需要手動重啟推論(避免意外累積費用或資源浪費)

7. Cluster Workspace部分離線的降級

叢集工作區(/workspace/cluster/[clusterId])是多裝置場景,允許部分失效。

7.1 Degraded 狀態

從 POC 搬來的叢集已經有 degraded 狀態某裝置離線叢集自動降級。UI 呈現:

叢集Production Cluster A     🟡 降級執行中 (2/3 裝置在線)

成員裝置:
  ● office-mac KL520     🟢 在線  w=3  處理 85 fps
  ● home-pi KL720        🟢 在線  w=1  處理 25 fps
  ● backup KL520         ⚪ 離線  w=3  (已降級)

7.2 全叢集掉線

所有成員都離線 → 叢集狀態 offline → 顯示「叢集目前無法使用,等待裝置重新連線」


8. Toast / 通知策略

事件 Toast 類型 持續時間 範例文字
裝置上線 success 3s 「Kneron KL520 已連線」
裝置掉線(首次) error 5s 「Kneron KL520 已離線」
推論被迫停止(掉線) warning 5s 「裝置離線,推論已停止」
重新連線成功 success 3s 「✓ Kneron KL520 已重新連線」
API 失敗(單次) error 4s 「操作失敗,請稍後再試」
Pairing 完成 success 4s 「✓ Kneron KL520 已成功配對」
Pairing 失敗 error 5s 「配對失敗:{原因}」

重要不要對同一個裝置的反覆掉線 spam toast。建議 debounce / throttle同裝置同狀態 60 秒內只發一次通知。


9. Activity Timeline 擴充

Dashboard 的活動時間軸新增雲端版事件類型:

Event Icon 文案範例
device_paired Link2 已配對裝置 Kneron KL520
device_unpaired Unlink 已解除配對 Kneron KL520
device_online CheckCircle Kneron KL520 已上線
device_offline XCircle Kneron KL520 已離線
tunnel_reconnected RefreshCw 與 office-mac 的連線已恢復
cluster_degraded AlertTriangle Production Cluster A 降級執行中

10. i18n key整合

remote.status.online → 在線 / Online
remote.status.offline → 離線 / Offline
remote.status.reconnecting → 重新連線中 / Reconnecting
remote.status.error → 連線錯誤 / Connection error

remote.lastSeen → 最後心跳 {time} / Last seen {time}
remote.lastSeenNever → 從未連線 / Never connected

remote.banner.offline.title → 此裝置目前離線
remote.banner.offline.description → 部分操作無法使用,待 local agent 重新連線後自動恢復
remote.banner.offline.refresh → 重新整理

remote.workspace.disconnected.title → 裝置已離線
remote.workspace.disconnected.description → 與 {deviceName} 的連線中斷,推論已自動停止
remote.workspace.disconnected.waiting → 等待重連 ({time})
remote.workspace.disconnected.backToList → 返回裝置列表

remote.toast.online → {deviceName} 已連線
remote.toast.offline → {deviceName} 已離線
remote.toast.reconnected → ✓ {deviceName} 已重新連線
remote.toast.inferenceStopped → 裝置離線,推論已停止

network.disconnected.title → 連線中斷
network.disconnected.description → 無法連上雲端服務。正在重試...
network.disconnected.retryButton → 立即重試
network.restored → ✓ 已恢復連線

11. 無障礙

  • 所有狀態變更透過 aria-live="polite" 宣告
  • RemoteDeviceBadgerole="status"
  • 錯誤遮罩層用 role="alertdialog" + 焦點陷阱
  • 不只靠顏色(綠 / 紅 / 黃),都搭配文字與 icon
  • 掉線遮罩的按鈕可 Tab 聚焦、Enter 觸發
  • 重連倒數計時不閃爍(避免 seizure-inducing motion

12. 效能考量

  • Heartbeat intervallocal agent ↔ 雲端 10 秒 / 次(全棧統一)
  • 掉線判定閾值:3 次未收到心跳30 秒) 才標記為 offline避免短暫抖動誤判
  • UI 呈現對應時機:
    • 第 1 次心跳 miss10s 起):不做任何變化,避免閃爍
    • 第 2 次心跳 miss20s 起):裝置狀態切為 🟡 reconnecting(重連中),卡片 opacity-90發 toast
    • 第 3 次心跳 miss30s 達成):標記為 offline,發 toast「{裝置名稱} 已離線」Workspace 遮罩觸發
    • 任一期間心跳恢復:立即切回 🟢 online,若曾通知則發「✓ {裝置名稱} 已重新連線」
  • 前端 polling 避免過於密集;優先使用 WebSocket push
  • 離線裝置不主動 polling 詳情 API等使用者進詳情頁才拉

13. TODO

項目 時機
Latency 顯示 Phase 1
連線品質圖表(過去 24 小時 uptime Phase 1
使用者可訂閱特定裝置的掉線通知Email / Slack / Webhook Phase 2
自動重啟推論(使用者可選擇) Phase 2
SLA / 可用性儀表板 Phase 2+