local-tool/: visionA-local desktop app
- M1: Wails shell + Go server + Next.js UI + Mock mode (macOS dmg ready)
- M2: i18n (zh-TW/en) + Settings 4-tab refactor
- M3: Embedded Python 3.12 runtime (python-build-standalone) + KneronPLUS wheels
- M4: Windows Inno Setup script (build on Windows runner)
- M5: Linux AppImage script + udev rule (build on Linux runner)
- M6: ffmpeg (GPL, pending legal review) + yt-dlp bundled
- Lifecycle: watchServer health check, fatal native dialog,
Wails IPC raise endpoint, stale process cleanup
.autoflow/: full PRD / Design Spec / Architecture / Testing docs
(4 rounds tri-party discussion + cross review)
.github/workflows/: macOS / Windows / Linux build CI
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
13 KiB
08 — 錯誤狀態與空狀態設計方向
8.1 錯誤狀態分級
| 級別 | 範例情境 | 視覺語彙 | UX 回應 |
|---|---|---|---|
| Critical | Server 啟動失敗、崩潰 | 全螢幕錯誤頁 + 紅色 | 提供重啟、查看日誌、回報問題 |
| Error | 模型載入失敗、USB 連線中斷 | Toast (destructive) + 頁面 inline error | 提供重試、排錯提示 |
| Warning | Mock 模式運行中、未簽章警告 | Badge / Banner (warning) | 持續顯示,資訊性質 |
| Info | 操作成功、模型已切換 | Toast (success/info) | 3 秒自動消失 |
8.2 全域錯誤頁(Critical)
當 Server 無法啟動或崩潰,整個前端顯示:
┌──────────────────────────────────────────────┐
│ │
│ ⚠ │
│ │
│ Server 無法啟動 │
│ │
│ 錯誤原因:Port 3721 已被其他程式佔用 │
│ │
│ [重新啟動 Server] [更改 Port] │
│ [查看日誌] [回報問題] │
│ │
│ 技術資訊 ▾ (點擊展開 stacktrace) │
│ │
└──────────────────────────────────────────────┘
規格:
- 置中佈局,最大寬度 560px
- 主標題
font.size.3xl / semibold - 錯誤原因用純文字(友善語氣,非 raw error)
- 技術資訊預設收合,展開後顯示 monospace 的 raw error log
- CTA 按鈕橫排,主要動作用
primary
常見錯誤 + 對應文案:
| 錯誤類型 | 友善描述 | 建議動作 |
|---|---|---|
| Port 衝突 | Port {port} 已被其他程式佔用 | 更改 Port / 關閉佔用程式 |
| Python venv 建立失敗 | 無法建立 Python 環境,可能缺少系統套件 | 查看日誌 / 重新安裝 |
| KneronPLUS 載入失敗 | 無法載入 Kneron SDK | 檢查裝置 / 切到 Mock 模式 |
| 磁碟空間不足 | 磁碟空間不足,無法啟動 | 清理磁碟 |
| 未知錯誤 | Server 發生未預期錯誤 | 重啟 / 查看日誌 / 回報 |
8.3 Inline Error(Error level)
當頁面部分區塊載入失敗:
┌─ Models ─────────────────────────┐
│ │
│ ⚠ │
│ 無法載入模型清單 │
│ 可能是 server 暫時無法回應 │
│ │
│ [重試] │
│ │
└──────────────────────────────────┘
規格:置中於所在區塊,icon 48px,提供單一主要動作(重試)。
8.4 Toast 規格
位置:右上角(距邊 16px)
寬度:固定 360px(行動裝置尺寸下改全寬 - 32px)
高度:自適應(最低 56px)
堆疊:多個 toast 垂直堆疊,間隔 8px
最多同時顯示:4 個(超過的排隊)
Variants:
| Variant | 背景 | 前景 | icon | 預設停留 |
|---|---|---|---|---|
success |
color.success (8% opacity) |
color.success |
✓ | 3s |
info |
color.info (8% opacity) |
color.info |
ⓘ | 3s |
warning |
color.warning (10% opacity) |
color.warning |
⚠ | 5s |
destructive |
color.destructive (10% opacity) |
color.destructive |
⊗ | 持續(需手動關) |
結構:
┌─────────────────────────────────────┐
│ [icon] 主要訊息 ✕ │
│ 次要說明(可選) │
│ [動作](可選) │
└─────────────────────────────────────┘
動畫:
- 進入:slide-in-right + fade-in,
motion.duration.normal+motion.easing.decelerate - 離開:fade-out,
motion.duration.fast
8.5 空狀態(Empty States)
所有空狀態遵循統一結構:Icon + 標題 + 次要描述 + 主要 CTA (+ 次要 CTA)
通用樣板
┌──────────────────────────────────────┐
│ │
│ [icon 64px] │
│ │
│ [標題 — 說明沒有什麼] │
│ [次要描述 — 為什麼沒有、該做什麼] │
│ │
│ [主要 CTA] [次要 CTA] │
│ │
└──────────────────────────────────────┘
空狀態清單
| 頁面 | 情境 | Icon | 標題 | 描述 | 主 CTA | 次 CTA |
|---|---|---|---|---|---|---|
| Models | 無任何模型 | 📦 | 還沒有模型 | 上傳一個 .nef 檔案開始使用,或啟用預置模型 | 上傳模型 | 啟用預置模型 |
| Devices (Real) | 無裝置 | 🔌 | 沒有偵測到 Kneron 裝置 | 接上 USB 裝置後會自動顯示 | 重新掃描 | 切到 Mock 模式 |
| Devices (Mock) | 永遠有 3 顆假裝置 | — | — | — | — | — |
| Workspace | 無裝置 | ▶ | 還沒有可用的裝置 | 先到 Devices 接上裝置,或切到 Mock 模式 | 前往 Devices | 切到 Mock |
| Workspace > Batch | 無上傳圖片 | 🖼 | 還沒有圖片 | 拖放圖片檔到此處,或點選上傳 | 上傳圖片 | — |
| Workspace > Video | 無上傳影片 | 🎞 | 還沒有影片 | 拖放影片檔開始推論 | 上傳影片 | — |
| Dashboard > Activity | 無活動紀錄 | 🕒 | 還沒有任何活動 | 開始使用後,這裡會顯示最近的事件 | — | — |
| Settings > 模型 > 自訂路徑 | 無自訂路徑 | 📁 | 沒有自訂模型路徑 | 新增資料夾讓 App 自動載入其中的 .nef | 新增路徑 | — |
空狀態的「正向語氣」原則
- 不用「沒有」「無」開頭 — 用「還沒有」(暗示「之後會有」)
- 主 CTA 用動詞 —「上傳」「前往」「切換」
- 描述要告訴使用者為什麼會空 +怎麼讓它不空
8.6 載入狀態
| 場景 | 方案 |
|---|---|
| 頁面初次載入 | Skeleton(沿用 shadcn skeleton 元件) |
| 清單刷新 | Top bar 細線 progress bar(2px,無確定進度) |
| 按鈕執行中 | Button 內 spinner + 文字改為「...中」 |
| 長時間操作(> 3 秒) | Modal + 進度條 + 可取消按鈕 |
| Mock 模式假進度 | 遵循真實時間,不要故意 fake 慢 |
8.7 破壞性操作確認
| 操作 | 確認強度 |
|---|---|
| 切換 Mock/Real 模式 | Dialog 確認(說明會中斷正在進行的推論) |
| 刪除單一模型 | Dialog 確認(顯示模型名稱) |
| 清除所有快取 | Dialog 確認 |
| 重置所有設定 | 雙重確認(需輸入 RESET 字樣) |
| 結束 App(主視窗關閉) | 無確認(Q7 決策:傳統式,直接結束) |
所有確認對話框使用 destructive 按鈕樣式在主動作上,次要動作(取消)用 ghost。
8.8 Python sidecar 專用狀態(第四輪新增)
背景:Architect tray-and-lifecycle.md §4.3(或等效章節)規劃 Python sidecar「空閒 N 分鐘自動 kill、崩潰時 Go server 自動重啟最多 3 次」。這些生命週期事件對使用者來說都是**「按下 Start 後 1-3 秒無回應」**的體驗斷裂,必須有明確的前端呈現。
8.8.1 自動重啟 loading(崩潰後)
觸發:Go server 偵測到 Python sidecar exit code ≠ 0,前端透過 WebSocket 事件 sidecar.state 收到 restarting。
呈現:Workspace 的 Camera Feed 區塊覆蓋一層半透明 overlay,顯示:
┌──── Camera Feed ────────────────┐
│ │
│ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ │
│ ░ ░ │
│ ░ ⟳ ░ │
│ ░ 推論引擎正在重新啟動 ░ │
│ ░ (第 1/3 次) ░ │
│ ░ ░ │
│ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ │
│ │
└─────────────────────────────────┘
- 背景:
color.background+ 60% opacity 黑色覆蓋 - Spinner:
motion.duration.normal,不用 progress bar(不知道要多久) - 文案:「推論引擎正在重新啟動(第 X/3 次)」
- 不阻擋使用者切換到其他頁面(overlay 只蓋在 Camera Feed,Sidebar/Header 仍可互動)
- 重啟成功 → overlay 淡出(300ms)→ 恢復影像串流
- 3 次重啟都失敗 → 進入 8.8.3 的 Critical 錯誤頁
8.8.2 空閒被 kill 後的冷啟動(使用者再次按 Start)
觸發:使用者閒置超過 Architect 設定的 N 分鐘,sidecar 被 Go server 主動 kill(節省記憶體)。使用者下次按 [▶ Start] 時,sidecar 需要 2-3 秒冷啟動。
呈現:Start 按鈕進入 loading 狀態 + Camera Feed 顯示 skeleton:
[▶ Start] → [⟳ 正在啟動推論引擎...] (按鈕 disabled,aria-busy=true)
Camera Feed 區域:
┌──── Camera Feed ────────────────┐
│ │
│ [Skeleton + 「首次啟動 │
│ 需要 2-3 秒,請稍候」] │
│ │
└─────────────────────────────────┘
- Start 按鈕文字即時改為「正在啟動推論引擎...」+ spinner,不要只靠 disabled 讓使用者以為當掉
- 預期時間上限 5 秒,超過 → 改顯示「啟動時間較長,請稍候」,10 秒 → 進入錯誤狀態
- 這是使用者首次按 Start(冷啟動)或閒置重啟時的標準體驗,文案相同
8.8.3 Sidecar 3 次重啟失敗(Critical)
觸發:Go server 連續重啟 sidecar 3 次都失敗,發送 sidecar.state = failed。
呈現:Workspace 整區切換為 Critical 錯誤頁(沿用 §8.2 通用全域錯誤頁樣板,但文案與動作為 sidecar-specific):
┌──────────────────────────────────────────────┐
│ │
│ ⚠ │
│ │
│ 推論引擎無法啟動 │
│ │
│ 已嘗試重新啟動 3 次,Python 推論引擎 │
│ 仍無法正常運作。 │
│ │
│ 可能的原因: │
│ • KneronPLUS SDK 與 OS 不相容 │
│ • 內嵌 Python 環境損毀 │
│ • 系統資源不足 │
│ │
│ [手動重試] [切換到系統 Python] │
│ [切到 Mock 模式] [查看 Python 日誌] │
│ │
│ 技術資訊 ▾ (點擊展開 stacktrace) │
│ │
└──────────────────────────────────────────────┘
- 「切換到系統 Python」連結到 Settings > 進階的 Python 執行模式(見
03-wireframes §3.5) - 「查看 Python 日誌」開啟
server-log-viewer並 filter 到 sidecar 類別 - OS 原生通知:同時 shell out 原生通知(因為 Server 崩潰屬 R4-8 定義的嚴重事件),文案「visionA-local:推論引擎無法啟動」
8.8.4 與 Architect 的 API 依賴
本節所有呈現都依賴 Architect 提供:
- WebSocket 事件
sidecar.state,payload:{ state: 'starting' | 'running' | 'idle' | 'restarting' | 'failed', attempt?: number } POST /api/sidecar/restart(手動重試按鈕)GET /api/sidecar/logs?since=<timestamp>(日誌 viewer)
若 Architect 未提供以上 API/事件,本節狀態無法落地,需在 M2 前與 Architect 對齊。