docs(design): 裝置使用流程 UX 重新設計(連接電腦→啟用裝置→載入模型→推論)
解使用者實測 4 問題:從未連線誤導、兩層狀態命名混淆(動詞不重複三層:連接 電腦/啟用裝置/載入模型/推論)、模型狀態四態、載入後推論引導。全純前端、落地 拆 3 批。待補:多電腦分組(design 進行中)。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
8270c4fb0e
commit
b35b989d9d
453
docs/autoflow/03-design/flow-device-usage-ux.md
Normal file
453
docs/autoflow/03-design/flow-device-usage-ux.md
Normal file
@ -0,0 +1,453 @@
|
|||||||
|
# 裝置使用流程 UX 重新設計 — 連接電腦 → 連線裝置 → 載入模型 → 推論
|
||||||
|
|
||||||
|
> 狀態:待 frontend 落地 · 產出:Design Agent · 語言:zh-TW
|
||||||
|
> 觸發:使用者實測後提出整條裝置使用流程的 4 個 UX 問題(狀態誤導、兩層命名混淆、模型狀態、載入後路由)。
|
||||||
|
> 定位:**這是整條使用流程的 UX 重新設計**,不是零散修 bug。統一定調三層狀態語意與命名,讓使用者一眼分清「哪一層還沒好」。
|
||||||
|
>
|
||||||
|
> **本文件只出設計 + 文案 mapping,不改任何 code / i18n 檔。** frontend 照本文件落地。
|
||||||
|
> 相關 code(現況):
|
||||||
|
> - `visionA-frontend/src/app/devices/[id]/device-detail-client.tsx`(裝置詳情頁 + 兩步式連線 + flash + 開啟工作區)
|
||||||
|
> - `visionA-frontend/src/components/cloud/remote-device-badge.tsx`(tunnel 狀態徽章 — 問題 1 根源在此)
|
||||||
|
> - `visionA-frontend/src/stores/device-store.ts`(remoteStatus / flashedModel / lastSeenAt / pairedAt / registeredAt / connect actions)
|
||||||
|
> - `visionA-frontend/src/lib/device-state.ts`(deriveTriState 三態運算)
|
||||||
|
> - `visionA-frontend/src/lib/i18n/dictionaries/zh-Hant.ts` + `en.ts`(文案)
|
||||||
|
> 延續:`flow-offline-handling.md`(離線降級)、`flow-pairing.md`、`pairing-rename-mapping.md`(用詞體系,必須一致)、ADR-018(serial 路由、兩步式連線)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 0. 問題總覽與設計解對照(一頁看懂)
|
||||||
|
|
||||||
|
| # | 使用者症狀 | 根因 | 設計解 | 純前端? |
|
||||||
|
|---|-----------|------|--------|---------|
|
||||||
|
| 1 | 離線後顯示「離線 · **從未連線**」誤導(明明連過) | `remote-device-badge.tsx:100-102` 只看 `!lastSeenAt` 就顯示「從未連線」,沒用 `pairedAt`/`registeredAt` 區分「連過但離線」vs「真的沒連過」 | 改「從未連線」判定邏輯:優先用 lastSeenAt,缺時 fallback 到 pairedAt/registeredAt 判定「連過」,只有全都沒有才顯示「從未連線」(§4) | ✅ 純前端(欄位都已存在);lastSeenAt 後端未穩定回傳為 §7 待確認 |
|
||||||
|
| 2 | 兩層都用「線」字,「在線」又要點「連線」很困惑(核心) | 第 1 層 tunnel(remoteStatus)用「在線/離線」、第 2 層 driver(connect)用「連線/斷線」,兩層命名撞、層次不明 | 三層語意重新定調:電腦(在線→「已連接雲端/未連上」)、裝置就緒(driver→「就緒/待啟用」+動作按鈕「啟用裝置/停用」)、模型(§2、§3) | ✅ 純前端(改 i18n value + badge/詳情頁呈現) |
|
||||||
|
| 3 | 連線載入過模型不顯示 / 重連後未刷新 | 模型狀態 Card 只有「有 flashedModel/沒有」二態,離線時仍顯示 stale,重連刷新 UX 不清楚 | 模型狀態四態呈現(已載入/未載入/離線-上次已知/載入中)+重連刷新引導(§5) | ✅ 純前端 |
|
||||||
|
| 4 | 載入完模型要能順暢路由去推論 | 「開啟工作區」按鈕雖有 ring highlight,但缺主動引導、埋在一排按鈕裡 | 載入完成 → 成功態卡片 + 主 CTA「前往推論」+ 引導文案,讓下一步不用找(§6) | ✅ 純前端 |
|
||||||
|
|
||||||
|
**一句話總結設計主張**:使用者的困惑本質是「三件不同的事都叫『連線』」。這次把三層拆成**三組不會撞的詞** + **一條線性的引導流**(每一步完成就明確指向下一步),讓使用者永遠知道「現在卡在哪一層、下一步該點什麼」。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 完整流程圖(四步線性引導)
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─ 第 0 層(一次性,此流程外)──────────────────────────────────────────┐
|
||||||
|
│ 連接這台電腦(pairing)— 建立雲端帳號 ↔ 這台電腦的信任。做過就不用再做。 │
|
||||||
|
└──────────────────────────────────────────────────────────────────────┘
|
||||||
|
│(電腦已連接,裝置出現在 /devices 列表)
|
||||||
|
▼
|
||||||
|
┌─ STEP 1:電腦連上雲端?(第 1 層 tunnel / remoteStatus)─────────────┐
|
||||||
|
│ │
|
||||||
|
│ ● 電腦已連接雲端(online) → 進 STEP 2 │
|
||||||
|
│ ○ 電腦未連上(offline) → 離線降級:顯示「這台電腦目前未連上雲端」│
|
||||||
|
│ +上次連線資訊,所有裝置操作 disable │
|
||||||
|
│ ◐ 重新連線中(reconnecting)→ 唯讀等待 │
|
||||||
|
│ │
|
||||||
|
│ CTA(offline 時):引導「請在電腦上啟動 local agent」 │
|
||||||
|
└──────────────────────────────────────────────────────────────────────┘
|
||||||
|
│(電腦在線 online)
|
||||||
|
▼
|
||||||
|
┌─ STEP 2:裝置就緒?(第 2 層 driver / status connect)───────────────┐
|
||||||
|
│ │
|
||||||
|
│ 狀態:待啟用(detected/disconnected)→ CTA「啟用裝置」 │
|
||||||
|
│ 啟用中(connecting)→ loading +「首次啟用需載入韌體,約1分鐘」 │
|
||||||
|
│ 已就緒(connected)→ 進 STEP 3,顯示「停用裝置」次要動作 │
|
||||||
|
│ │
|
||||||
|
│ ⚠ serial 缺 → disable + tooltip「需重新連接電腦回報序號」 │
|
||||||
|
└──────────────────────────────────────────────────────────────────────┘
|
||||||
|
│(裝置已就緒 connected)
|
||||||
|
▼
|
||||||
|
┌─ STEP 3:載入模型?(flash / flashedModel)─────────────────────────┐
|
||||||
|
│ │
|
||||||
|
│ 未載入 → CTA「載入模型」→ 選模型 → 相容檢查 → flash → WS 進度 │
|
||||||
|
│ 載入中(flashing)→ 進度條 +「載入中,請勿關閉」 │
|
||||||
|
│ 已載入 → 顯示模型名 +「已就緒,可開始推論」→ 進 STEP 4 │
|
||||||
|
└──────────────────────────────────────────────────────────────────────┘
|
||||||
|
│(模型已載入 flashedModel != null)
|
||||||
|
▼
|
||||||
|
┌─ STEP 4:推論(workspace)──────────────────────────────────────────┐
|
||||||
|
│ │
|
||||||
|
│ 成功態卡片彈出 + 主 CTA「前往推論 →」(醒目、size lg、primary) │
|
||||||
|
│ 路由 → /workspace/:id │
|
||||||
|
└──────────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
**進度指示(詳情頁頂部,選配但建議)**:在裝置詳情頁頂部放一條**輕量四步進度列**,讓使用者看到自己在整條流程的哪一步:
|
||||||
|
|
||||||
|
```
|
||||||
|
①電腦連接 ──→ ②裝置就緒 ──→ ③載入模型 ──→ ④推論
|
||||||
|
✓已完成 ●進行中 ○待完成 ○待完成
|
||||||
|
```
|
||||||
|
|
||||||
|
- 已完成:`bg-primary` 實心 + CheckCircle;進行中:`ring-2 ring-primary`;待完成:`bg-muted`。
|
||||||
|
- 尊重 `prefers-reduced-motion`,狀態切換不做閃爍動畫。
|
||||||
|
- 這條進度列是本次 UX 的**核心引導元件**——它把「三層各自的狀態」收攏成一條使用者能理解的線性路徑。i18n key 見 §8「Flow Stepper」。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 兩層狀態命名 + 視覺方案(問題 2 核心)
|
||||||
|
|
||||||
|
### 2.1 心智模型:為什麼會撞
|
||||||
|
|
||||||
|
系統實際有**三層**不同的「連接/連線」,過去第 1、2 層都用「線」字:
|
||||||
|
|
||||||
|
| 層 | 技術欄位 | 語意 | 頻率 | 對象 | 舊用詞(撞) |
|
||||||
|
|----|---------|------|------|------|------------|
|
||||||
|
| **第 0 層** 信任連結 | pairing / pairedAt | 電腦接進雲端帳號 | 一次 | 電腦 | 配對 → 已改「連接這台電腦」✅ |
|
||||||
|
| **第 1 層** tunnel | `remoteStatus` (online/offline) | 這台電腦的 local agent 有沒有連上雲端 | 每次 | 電腦(host) | **在線 / 離線** ← 撞 |
|
||||||
|
| **第 2 層** driver | `status` (connect/disconnect) | 對這顆 USB 晶片有沒有開 driver 連線準備用 | 每次 | 裝置(USB 晶片) | **連線 / 斷線** ← 撞 |
|
||||||
|
|
||||||
|
**使用者的困惑點**:看到裝置「在線」(第 1 層 tunnel),卻還要點「連線」(第 2 層 driver)才能用——兩個都是「線」,使用者以為「在線」就該能用了。
|
||||||
|
|
||||||
|
### 2.2 命名決策:三層各給一組不撞的詞
|
||||||
|
|
||||||
|
**設計原則**:
|
||||||
|
1. 第 1 層講「**電腦**」——沿用 pairing-rename 的主詞策略(帶主詞就不會跟裝置層撞)。
|
||||||
|
2. 第 2 層講「**裝置就緒**」——用「就緒 / 待啟用」描述**狀態**,用「啟用 / 停用」描述**動作**,徹底避開「線」字。
|
||||||
|
3. 三層的動詞不重複:電腦「連接」、裝置「啟用」、模型「載入」、最後「推論」。
|
||||||
|
|
||||||
|
| 層 | 狀態文案(新) | 動作按鈕(新) | 避開的舊詞 |
|
||||||
|
|----|--------------|--------------|-----------|
|
||||||
|
| 第 1 層 tunnel | **這台電腦已連上雲端** / **這台電腦未連上雲端** | (無按鈕,靠 local agent 啟動) | 在線 / 離線 |
|
||||||
|
| 第 2 層 driver | **裝置已就緒** / **裝置待啟用** / **啟用中…** | **啟用裝置** / **停用** | 連線 / 斷線 |
|
||||||
|
| 模型 flash | **模型已載入** / **尚未載入模型** | **載入模型** | — |
|
||||||
|
|
||||||
|
**為什麼是「就緒/待啟用」不是「已連線/未連線」**:
|
||||||
|
- 「就緒(ready)」直接對應使用者心裡的問題「這顆晶片能用了嗎」,比「已連線」更貼近意圖。
|
||||||
|
- 「啟用(activate)」當動作動詞,跟第 1 層「連接電腦」、第 0 層「配對」全都不同字,使用者不會混。
|
||||||
|
- 保留 `flow-offline-handling.md` §2 對 remoteStatus 的既有狀態機(online/offline/reconnecting/error/unknown),只換**顯示字**,不動狀態邏輯。
|
||||||
|
|
||||||
|
### 2.3 視覺呈現:兩層垂直堆疊、層次分明
|
||||||
|
|
||||||
|
詳情頁頂部狀態區改為**兩行明確標示的層次結構**(取代目前單一 RemoteDeviceBadge 混在標題下):
|
||||||
|
|
||||||
|
```
|
||||||
|
┌────────────────────────────────────────────────────────────┐
|
||||||
|
│ Kneron KL720 [停用] [載入模型] │
|
||||||
|
│ (別名/原名) │
|
||||||
|
│ │
|
||||||
|
│ ┌── 第 1 層 ──────────────────────────────────────────┐ │
|
||||||
|
│ │ 💻 這台電腦 ● 已連上雲端 │ │
|
||||||
|
│ │ (office-mac · 最後心跳 2 秒前) │ │
|
||||||
|
│ └──────────────────────────────────────────────────────┘ │
|
||||||
|
│ ┌── 第 2 層 ──────────────────────────────────────────┐ │
|
||||||
|
│ │ 🔌 裝置 ● 已就緒(可載入模型 / 推論) │ │
|
||||||
|
│ └──────────────────────────────────────────────────────┘ │
|
||||||
|
└────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
**設計規格**:
|
||||||
|
- 兩層各自一個 badge,**帶明確的層標籤**(💻 這台電腦 / 🔌 裝置),這是消除混淆的關鍵——使用者一眼看到「這是電腦的狀態、那是裝置的狀態」。
|
||||||
|
- 第 1 層 badge(電腦):沿用 `RemoteDeviceBadge` 結構(dot + icon + label),只改 label 文案(見 §8)+前綴層標籤。
|
||||||
|
- 第 2 層 badge(裝置就緒):新增一個 badge 呈現 driver 狀態,用 `devices.status.*` 收斂後的語意(見 §2.4 對照)。
|
||||||
|
- **不只靠顏色**(沿用既有原則):dot 色 + icon(✓/○/◐)+ 文字三重編碼。
|
||||||
|
- 色彩沿用既有 `--status-*` token,不裸寫色碼(globals.css 既有)。
|
||||||
|
- 響應式:mobile(<640px)兩層仍垂直堆疊、層標籤縮短為 icon+短詞;tablet/desktop 同結構。
|
||||||
|
|
||||||
|
### 2.4 第 2 層 driver 狀態的語意收斂
|
||||||
|
|
||||||
|
現有 `devices.status.*` 有七值(detected/connecting/connected/flashing/inferencing/error/disconnected/unknown),但對使用者而言第 2 層只需知道「就緒了沒」。收斂成三個使用者可理解的態:
|
||||||
|
|
||||||
|
| 底層 `status` | 第 2 層使用者語意 | badge 文案 | dot/icon |
|
||||||
|
|--------------|-----------------|-----------|----------|
|
||||||
|
| `connected` / `flashing` / `inferencing` | 已就緒 | **裝置已就緒** | 🟢 ✓ |
|
||||||
|
| `connecting` | 啟用中 | **啟用中…** | 🟡 ◐(pulse) |
|
||||||
|
| `detected` / `disconnected` | 待啟用 | **裝置待啟用** | ⚪ ○ |
|
||||||
|
| `error` | 裝置錯誤 | **裝置錯誤** | 🔴 ⚠ |
|
||||||
|
| `unknown` | 未確認 | **狀態未確認** | ⚪ — |
|
||||||
|
|
||||||
|
> 註:`devices.status.*` 七值的原始文案**保留不動**(其他地方如「裝置資訊」Card 的 driver 狀態明細仍可能用到);本層新增一組「就緒語意」文案(`devices.readiness.*`,見 §8),供第 2 層 badge 使用。這是**新增**、不覆蓋既有 key,避免波及其他引用點。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 兩層狀態文案 mapping(跟 pairing-rename 一致)
|
||||||
|
|
||||||
|
> frontend 照表落地。「動作」欄凡涉及第 2 層 driver 一律改「啟用/停用」;第 1 層一律帶主詞「電腦」。
|
||||||
|
|
||||||
|
### 3.1 第 1 層 tunnel(電腦連上雲端)— 改既有 `remote.status.*` value
|
||||||
|
|
||||||
|
| i18n key | 舊 zh | 新 zh | 舊 en | 新 en | 備註 |
|
||||||
|
|----------|-------|-------|-------|-------|------|
|
||||||
|
| `remote.status.online` | 在線 | 已連上雲端 | Online | Connected to cloud | 帶「雲端」點出這是電腦↔雲端層。搭配層標籤「這台電腦」使用 |
|
||||||
|
| `remote.status.offline` | 離線 | 未連上雲端 | Offline | Not connected to cloud | 成套 |
|
||||||
|
| `remote.status.reconnecting` | 重新連線中 | 重新連上雲端中 | Reconnecting | Reconnecting to cloud | 成套 |
|
||||||
|
| `remote.status.error` | 連線錯誤 | 雲端連線錯誤 | Connection error | Cloud connection error | 成套 |
|
||||||
|
| `remote.status.unknown` | 未確認 | 未確認 | Unknown | Unknown | 不動 |
|
||||||
|
|
||||||
|
> ⚠️ 避撞檢查:這些是第 1 層文案,必搭配層標籤「💻 這台電腦」呈現。若某些地方(如列表卡片空間小)不放層標籤,則用**帶主詞版**避免歧義 → 見 §3.4「列表卡片精簡版」。
|
||||||
|
|
||||||
|
### 3.2 第 1 層層標籤(新增 key)
|
||||||
|
|
||||||
|
| i18n key(新) | zh | en |
|
||||||
|
|---------------|-----|-----|
|
||||||
|
| `deviceUsage.layer.computer` | 這台電腦 | This computer |
|
||||||
|
| `deviceUsage.layer.device` | 裝置 | Device |
|
||||||
|
|
||||||
|
### 3.3 第 2 層 driver 就緒語意(新增 `devices.readiness.*`,不覆蓋既有 status.*)
|
||||||
|
|
||||||
|
| i18n key(新) | zh | en | 對應底層 status |
|
||||||
|
|---------------|-----|-----|----------------|
|
||||||
|
| `devices.readiness.ready` | 裝置已就緒 | Device ready | connected/flashing/inferencing |
|
||||||
|
| `devices.readiness.activating` | 啟用中… | Activating… | connecting |
|
||||||
|
| `devices.readiness.notReady` | 裝置待啟用 | Device not activated | detected/disconnected |
|
||||||
|
| `devices.readiness.error` | 裝置錯誤 | Device error | error |
|
||||||
|
| `devices.readiness.unknown` | 狀態未確認 | Status unknown | unknown |
|
||||||
|
| `devices.readiness.readyHint` | 可載入模型 / 推論 | Ready to load a model / run inference | — |
|
||||||
|
|
||||||
|
### 3.4 第 2 層動作按鈕 — 改既有 `devices.connect.*` value(動詞改「啟用/停用」)
|
||||||
|
|
||||||
|
| i18n key | 舊 zh | 新 zh | 舊 en | 新 en | 備註 |
|
||||||
|
|----------|-------|-------|-------|-------|------|
|
||||||
|
| `devices.connect.action` | 連線 | 啟用裝置 | Connect | Activate device | 動作動詞改「啟用」,徹底避開「連線」撞第 1 層 |
|
||||||
|
| `devices.connect.connecting` | 連線中… | 啟用中… | Connecting… | Activating… | 成套 |
|
||||||
|
| `devices.connect.connectingHint` | 首次連線需載入韌體,約 1 分鐘,請勿關閉頁面 | 首次啟用需載入韌體,約 1 分鐘,請勿關閉頁面 | (First connection loads firmware…) | First activation loads firmware, ~1 min, keep this page open | 連線→啟用 |
|
||||||
|
| `devices.connect.disconnect` | 斷線 | 停用 | Disconnect | Deactivate | 成套(次要動作、outline 樣式) |
|
||||||
|
| `devices.connect.disconnecting` | 斷線中… | 停用中… | Disconnecting… | Deactivating… | 成套 |
|
||||||
|
| `devices.connect.needConnectHint` | 請先連線裝置才能載入模型 | 請先啟用裝置才能載入模型 | (Activate the device first…) | Activate the device before loading a model | 連線→啟用 |
|
||||||
|
| `devices.connect.driverStatusLabel` | 連線狀態 | 裝置狀態 | Connection status | Device status | 避開「連線」;這是裝置資訊 Card 的明細 label |
|
||||||
|
| `devices.connect.toast.success` | 裝置已連線 | 裝置已就緒 | Device connected | Device ready | 對齊就緒語意 |
|
||||||
|
| `devices.connect.toast.failed` | 連線失敗 | 啟用失敗 | Connection failed | Activation failed | 成套 |
|
||||||
|
| `devices.connect.toast.failedDesc` | 請確認裝置已插上且 local agent 在線 | 請確認裝置已插上,且這台電腦已連上雲端 | (…local agent online) | Make sure the device is plugged in and this computer is connected to the cloud | 「local agent 在線」→「電腦已連上雲端」對齊第 1 層用語 |
|
||||||
|
| `devices.connect.toast.timeoutDesc` | 連線逾時,請重試 | 啟用逾時,請重試 | Connection timed out | Activation timed out, please retry | 成套 |
|
||||||
|
| `devices.connect.toast.disconnected` | 裝置已斷線 | 裝置已停用 | Device disconnected | Device deactivated | 成套 |
|
||||||
|
| `devices.connect.toast.disconnectFailed` | 斷線失敗 | 停用失敗 | Disconnect failed | Deactivation failed | 成套 |
|
||||||
|
|
||||||
|
> **i18n key 名一律不改**(`devices.connect.*` 保留),只改 value——避開改 key 波及元件引用,比照 pairing-rename §3.1 判定。
|
||||||
|
|
||||||
|
### 3.5 明確「不動」清單(frontend 別誤改)
|
||||||
|
|
||||||
|
- `common.connect="連接"` / `common.disconnect="中斷連線"`(通用詞、非此流程專屬)
|
||||||
|
- `devices.status.*` 七值原始文案(detected/connecting/connected/…)— 保留,供裝置資訊 Card 明細用;本次新增 `devices.readiness.*` 平行使用,不覆蓋
|
||||||
|
- pairing-rename 已定的所有「連接電腦」相關 key — 不動
|
||||||
|
- `pairing.step3.waiting`(等待 local agent 連線)— 屬 agent↔雲端網路語意,pairing-rename 已判定不動
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 「從未連線」vs「離線」的狀態邏輯(問題 1)
|
||||||
|
|
||||||
|
### 4.1 根因
|
||||||
|
|
||||||
|
`remote-device-badge.tsx:99-104` 現況:
|
||||||
|
|
||||||
|
```
|
||||||
|
if (!showLastSeen || status === "online" || status === "unknown") return "";
|
||||||
|
if (!lastSeenAt) return t("remote.lastSeenNever"); // ← 只看 lastSeenAt,缺就顯示「從未連線」
|
||||||
|
...
|
||||||
|
return formatRelativeTime(lastSeenAt, nowMs, t);
|
||||||
|
```
|
||||||
|
|
||||||
|
問題:`lastSeenAt` 缺(後端 offline 時可能沒回 last_seen_at,或 DB 靜態 offline 從未寫過)就直接說「從未連線」,但裝置**明明配對過、連過**。使用者關掉 local agent 後看到「從未連線」=明顯錯誤。
|
||||||
|
|
||||||
|
### 4.2 設計解:三層 fallback 判定「連過沒」
|
||||||
|
|
||||||
|
離線裝置的次要文字,依以下優先序決定:
|
||||||
|
|
||||||
|
```
|
||||||
|
若 status === online 或 unknown → 不顯示次要文字(沿用既有)
|
||||||
|
否則(offline / reconnecting / error):
|
||||||
|
1. 有 lastSeenAt → 「最後心跳 {相對時間}」(最準)
|
||||||
|
2. 無 lastSeenAt,但有 pairedAt → 「上次連線:{pairedAt 日期}」(退而求其次:連接過就不是「從未」)
|
||||||
|
3. 無 lastSeenAt,但有 registeredAt → 「上次連線:{registeredAt 日期}」(同上,已註冊=用過)
|
||||||
|
4. 三者皆無 → 「從未連線」(真的沒連過,才顯示這句)
|
||||||
|
```
|
||||||
|
|
||||||
|
**判定「連過」的訊號**:`lastSeenAt || pairedAt || registeredAt` 任一存在 → 這台裝置曾經連上過,**不可**顯示「從未連線」。
|
||||||
|
|
||||||
|
### 4.3 文案 mapping(問題 1)
|
||||||
|
|
||||||
|
| i18n key | 舊 zh | 新 zh | 舊 en | 新 en | 備註 |
|
||||||
|
|----------|-------|-------|-------|-------|------|
|
||||||
|
| `remote.lastSeenNever` | 從未連線 | 尚未連線過 | Never connected | Never connected | 只在「真的沒連過」時顯示;文案微調更中性 |
|
||||||
|
| `remote.lastSeen` | 最後心跳 {time} | 最後心跳 {time} | Last seen {time} | Last seen {time} | 有 lastSeenAt 時用;不動 |
|
||||||
|
| `remote.lastConnectedAt`(新) | 上次連線:{time} | 上次連線:{time} | Last connected {time} | Last connected {time} | 缺 lastSeenAt 但有 pairedAt/registeredAt 時用(fallback) |
|
||||||
|
|
||||||
|
### 4.4 呈現範例
|
||||||
|
|
||||||
|
| 情境 | lastSeenAt | pairedAt | 顯示 |
|
||||||
|
|------|-----------|----------|------|
|
||||||
|
| 連過、剛關 agent | 2 分鐘前 | 有 | 未連上雲端 · 最後心跳 2 分鐘前 |
|
||||||
|
| 連過、後端沒回 lastSeenAt | ✗ | 2026-07-01 | 未連上雲端 · 上次連線:2026-07-01 |
|
||||||
|
| 真的沒連過(剛配對、agent 從沒起來) | ✗ | ✗(極少見) | 未連上雲端 · 尚未連線過 |
|
||||||
|
|
||||||
|
> 註:多數情境會落在前兩列。第三列(pairedAt 也沒有)在正常流程幾乎不會發生(配對成功必寫 pairedAt),保留只為極端防呆。
|
||||||
|
|
||||||
|
### 4.5 是否需要後端配合
|
||||||
|
|
||||||
|
- **主要:純前端**。`pairedAt`、`registeredAt`、`lastSeenAt` 三欄在 `Device` 型別都已存在(device-store.ts:98-100, 108),前端改 badge 判定邏輯即可。
|
||||||
|
- **待確認(§7)**:後端 offline 時是否穩定回傳 `last_seen_at`?若後端能在裝置每次上線時寫入 last_seen_at,則情境二會落回情境一(最準)。**即使後端沒回,本設計的 pairedAt fallback 也已能避免「從未連線」誤導**——後端修不修都不阻擋本次落地,只是 last_seen_at 有回會更精準。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 模型狀態呈現(問題 3)
|
||||||
|
|
||||||
|
### 5.1 現況
|
||||||
|
|
||||||
|
模型狀態 Card(device-detail-client.tsx:443-466)只有二態:`flashedModel` 有值 → 顯示模型名 + 「已就緒,可開始推論」;沒值 → 「尚未燒錄任何模型」。缺離線態、載入中態、以及重連刷新引導。
|
||||||
|
|
||||||
|
### 5.2 四態設計
|
||||||
|
|
||||||
|
| 態 | 條件 | 呈現 | icon |
|
||||||
|
|----|------|------|------|
|
||||||
|
| **已載入** | `flashedModel != null` 且 online | 模型名(font-medium)+ 「已就緒,可開始推論」+ CTA「前往推論」 | ✓ 綠 |
|
||||||
|
| **未載入** | `flashedModel == null` 且裝置已就緒 | 「尚未載入模型」+ 提示「載入模型後即可開始推論」+ 引導看上方「載入模型」按鈕 | ○ 灰 |
|
||||||
|
| **載入中** | `status === "flashing"` | 進度條(WS 推送)+ 「模型載入中,請勿關閉頁面」 | ◐ pulse |
|
||||||
|
| **離線 · 上次已知** | offline 且有 cache 的 flashedModel | 模型名 + 標註「資料截至上次連線」(淡化 opacity-75)+ 「裝置重新連上後會自動刷新」 | ⚪ + 時間戳 |
|
||||||
|
|
||||||
|
### 5.3 重連刷新邏輯 UX
|
||||||
|
|
||||||
|
- **重連後刷新**:裝置從 offline → online(或 driver 重新啟用)時,詳情頁應重新 `fetchDevice(id)` 拉最新 flashedModel(現況 connect 成功後已呼叫 fetchDevice,disconnect 亦同)。
|
||||||
|
- **重連=清空舊模型狀態**:使用者提到「重連後應刷新為未載入」——driver 停用再啟用(或 agent 重連)後,若晶片已無 flash 的模型,`fetchDevice` 會拿回 `flashedModel: null` → 自動回到「未載入」態。**不需前端額外清空邏輯**,只要確保重連後有 fetchDevice(現況已有)。
|
||||||
|
- **離線時不主動 polling**(沿用 flow-offline-handling §12),顯示 cache 並標「資料截至上次連線」,避免顯示過時資料卻讓使用者以為是即時。
|
||||||
|
|
||||||
|
### 5.4 文案 mapping(問題 3)
|
||||||
|
|
||||||
|
| i18n key | zh | en | 備註 |
|
||||||
|
|----------|-----|-----|------|
|
||||||
|
| `devices.detail.readyForInference` | 已就緒,可開始推論 | Ready for inference | 不動 |
|
||||||
|
| `devices.detail.noModelFlashed` | 尚未載入模型 | No model loaded | 「燒錄」→「載入」對齊 flash 已改用「載入」;補一句引導見下 |
|
||||||
|
| `devices.detail.noModelHint`(新) | 載入模型後即可開始推論。點上方「載入模型」開始。 | Load a model to start inference. Use "Load model" above. | 未載入態引導 |
|
||||||
|
| `devices.detail.modelFlashing`(新) | 模型載入中,請勿關閉頁面 | Loading model, keep this page open | 載入中態 |
|
||||||
|
| `devices.detail.modelStaleHint`(新) | 資料截至上次連線,裝置重新連上後會自動刷新 | As of last connection; refreshes when the device reconnects | 離線 cache 態 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 載入後 → 推論的引導(問題 4)
|
||||||
|
|
||||||
|
### 6.1 現況
|
||||||
|
|
||||||
|
flash 完成後 `fetchDevice` → `flashedModel` 有值 → 「開啟工作區」按鈕出現(device-detail-client.tsx:287),已有 `ring-primary/40 ring-2` highlight。但按鈕跟「停用/載入模型/移除」擠在同一排,使用者反映「原本樣式不明顯要自己找」。
|
||||||
|
|
||||||
|
### 6.2 設計解:flash 完成後彈成功態 + 主 CTA
|
||||||
|
|
||||||
|
**flash 完成的當下**(FlashDialog 關閉、fetchDevice 回來 flashedModel 有值),做兩件事:
|
||||||
|
|
||||||
|
1. **模型狀態 Card 切換為「已載入」成功態**,Card 內直接放主 CTA:
|
||||||
|
```
|
||||||
|
┌── 模型狀態 ──────────────────────────┐
|
||||||
|
│ ✓ 模型已載入:yolov5s_640.nef │
|
||||||
|
│ 已就緒,可開始推論 │
|
||||||
|
│ │
|
||||||
|
│ [ 前往推論 → ] ← 主 CTA,size lg │
|
||||||
|
└────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
2. **一次性 toast**:「模型載入成功,可以開始推論了」+ toast action「前往推論」(點了直接 router.push)。
|
||||||
|
|
||||||
|
**為什麼不自動跳轉**:沿用現況決策(device-detail-client.tsx:282-286 註解)——自動 push 會誤導、使用者可能還想載別的模型或檢查。維持**手動導航但把入口做到不用找**:模型 Card 內的主 CTA + toast action 雙入口。
|
||||||
|
|
||||||
|
### 6.3 主 CTA 規格
|
||||||
|
|
||||||
|
- 位置優先序:**模型狀態 Card 內**(最貼近「模型剛載入好」的語境)> 頂部按鈕排(保留現況的 ring highlight 版作為次要入口)。
|
||||||
|
- 樣式:`size="lg"` + primary 填色 + ArrowRight 圖示 + `ring-primary/40 ring-2 ring-offset-2`(沿用現況 highlight)。
|
||||||
|
- 文案:主 CTA 用「**前往推論**」(比「開啟工作區」更直指使用者意圖——工作區=推論的地方)。保留 `devices.openWorkspace` 作為別處引用;新增 `devices.goToInference`。
|
||||||
|
- 離線 / serial 缺:沿用現況 disable + tooltip 分支(device-detail-client.tsx:299-322)。
|
||||||
|
|
||||||
|
### 6.4 文案 mapping(問題 4)
|
||||||
|
|
||||||
|
| i18n key | zh | en | 備註 |
|
||||||
|
|----------|-----|-----|------|
|
||||||
|
| `devices.openWorkspace` | 開啟工作區 | Open workspace | 保留(別處引用) |
|
||||||
|
| `devices.goToInference`(新) | 前往推論 | Go to inference | flash 完成後主 CTA |
|
||||||
|
| `devices.flash.readyToast`(新) | 模型載入成功,可以開始推論了 | Model loaded — ready for inference | 成功 toast |
|
||||||
|
| `devices.flash.readyToastAction`(新) | 前往推論 | Go to inference | toast action |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 各狀態的空 / 載入 / 錯誤 / 離線降級(統整)
|
||||||
|
|
||||||
|
> 沿用 `flow-offline-handling.md` §5 的降級策略,補齊本流程各步驟的四態。
|
||||||
|
|
||||||
|
### 7.1 詳情頁整體
|
||||||
|
|
||||||
|
| 情境 | 呈現 |
|
||||||
|
|------|------|
|
||||||
|
| **載入中**(fetchDevice 中) | Skeleton(現況已有:device-detail-client.tsx:123-134) |
|
||||||
|
| **404 / 找不到** | 現況占位(:137-155)→ 建議補明確「找不到此裝置」空狀態 + 返回列表 CTA |
|
||||||
|
| **第 1 層 offline** | 頂部離線 banner(現況 :188-204)+ 第 2 層 badge 顯示「狀態未確認」+ 啟用/載入/推論全 disable + tooltip「這台電腦未連上雲端」 |
|
||||||
|
| **第 1 層 online、第 2 層待啟用** | 顯示「啟用裝置」CTA;載入模型/推論 disable + tooltip「請先啟用裝置」 |
|
||||||
|
| **serial 缺** | 啟用/載入/推論 disable + tooltip「此裝置尚未回報序號,需在 local agent 重新連接電腦一次」(沿用 `devices.serial.missingHint`,該文案 pairing-rename 已改「重新連接」) |
|
||||||
|
| **啟用失敗** | toast「啟用失敗」+ desc「請確認裝置已插上,且這台電腦已連上雲端」 |
|
||||||
|
| **flash 失敗** | toast「模型載入失敗」(現況 `devices.flash.flashFailed`)+ 保留在未載入態,可重試 |
|
||||||
|
|
||||||
|
### 7.2 離線 banner 文案調整
|
||||||
|
|
||||||
|
| i18n key | 舊 zh | 新 zh | 備註 |
|
||||||
|
|----------|-------|-------|------|
|
||||||
|
| `devices.detail.offlineBanner.title` | 此裝置目前離線 | 這台電腦目前未連上雲端 | 校正語意:離線的是電腦(tunnel),非單一裝置;對齊第 1 層用語 |
|
||||||
|
| `devices.detail.offlineBanner.description` | 部分操作無法使用,待 local agent 重新連線後自動恢復 | 部分操作無法使用,待這台電腦重新連上雲端後自動恢復 | 對齊 |
|
||||||
|
|
||||||
|
> ⚠️ 注意:offlineBanner 目前語意是「裝置離線」,但根因是第 1 層 tunnel(電腦)離線——local agent 一關,該電腦上所有裝置都離線(flow-offline-handling §1 失效點 C)。改文案點出是「電腦」層級,避免使用者以為只是這一顆晶片壞。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 完整新增 / 修改 i18n key 清單(frontend 落地總表)
|
||||||
|
|
||||||
|
> zh 與 en 同步。**修改既有 value** 的已列在 §3/§4/§5/§6/§7;此處彙整**新增 key**,frontend 一次補齊。
|
||||||
|
|
||||||
|
### 8.1 新增 key
|
||||||
|
|
||||||
|
| i18n key | zh | en |
|
||||||
|
|----------|-----|-----|
|
||||||
|
| `deviceUsage.layer.computer` | 這台電腦 | This computer |
|
||||||
|
| `deviceUsage.layer.device` | 裝置 | Device |
|
||||||
|
| `deviceUsage.stepper.pairing` | 電腦連接 | Computer |
|
||||||
|
| `deviceUsage.stepper.activate` | 裝置就緒 | Device ready |
|
||||||
|
| `deviceUsage.stepper.loadModel` | 載入模型 | Load model |
|
||||||
|
| `deviceUsage.stepper.inference` | 推論 | Inference |
|
||||||
|
| `devices.readiness.ready` | 裝置已就緒 | Device ready |
|
||||||
|
| `devices.readiness.activating` | 啟用中… | Activating… |
|
||||||
|
| `devices.readiness.notReady` | 裝置待啟用 | Device not activated |
|
||||||
|
| `devices.readiness.error` | 裝置錯誤 | Device error |
|
||||||
|
| `devices.readiness.unknown` | 狀態未確認 | Status unknown |
|
||||||
|
| `devices.readiness.readyHint` | 可載入模型 / 推論 | Ready to load a model / run inference |
|
||||||
|
| `remote.lastConnectedAt` | 上次連線:{time} | Last connected {time} |
|
||||||
|
| `devices.detail.noModelHint` | 載入模型後即可開始推論。點上方「載入模型」開始。 | Load a model to start inference. Use "Load model" above. |
|
||||||
|
| `devices.detail.modelFlashing` | 模型載入中,請勿關閉頁面 | Loading model, keep this page open |
|
||||||
|
| `devices.detail.modelStaleHint` | 資料截至上次連線,裝置重新連上後會自動刷新 | As of last connection; refreshes when the device reconnects |
|
||||||
|
| `devices.goToInference` | 前往推論 | Go to inference |
|
||||||
|
| `devices.flash.readyToast` | 模型載入成功,可以開始推論了 | Model loaded — ready for inference |
|
||||||
|
| `devices.flash.readyToastAction` | 前往推論 | Go to inference |
|
||||||
|
|
||||||
|
### 8.2 修改既有 value(彙整,詳見對應章節)
|
||||||
|
|
||||||
|
- `remote.status.online/offline/reconnecting/error`(§3.1)
|
||||||
|
- `remote.lastSeenNever`(§4.3)
|
||||||
|
- `devices.connect.action/connecting/connectingHint/disconnect/disconnecting/needConnectHint/driverStatusLabel/toast.*`(§3.4)
|
||||||
|
- `devices.detail.noModelFlashed`(§5.4)
|
||||||
|
- `devices.detail.offlineBanner.title/description`(§7.2)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. 無障礙(沿用既有 + 本次補充)
|
||||||
|
|
||||||
|
- 兩層 badge 各自 `role="status"` + `aria-live="polite"`,狀態變更宣告(現況 RemoteDeviceBadge 已有,第 2 層新 badge 比照)。
|
||||||
|
- 層標籤(「這台電腦」/「裝置」)不只用 icon——搭配文字,SR 可讀。
|
||||||
|
- Flow Stepper:`role="list"` + 每步 `role="listitem"` + 當前步 `aria-current="step"`(比照 flow-pairing §10)。
|
||||||
|
- 三態(就緒/待啟用/錯誤)不只靠顏色:dot + icon(✓/○/◐/⚠)+ 文字三重編碼。
|
||||||
|
- flash 完成 toast:`aria-live="polite"`;主 CTA 可 Tab 聚焦、Enter 觸發。
|
||||||
|
- 重連倒數 / pulse 動畫尊重 `prefers-reduced-motion`。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. 落地後驗證(frontend / Design QA 自檢)
|
||||||
|
|
||||||
|
1. **兩層不打架**:詳情頁同時出現「💻 這台電腦 已連上雲端」與「🔌 裝置 待啟用」時,使用者能分清兩層、知道要點「啟用裝置」。
|
||||||
|
2. **「從未連線」修正**:關掉 local agent 後,連過的裝置顯示「未連上雲端 · 最後心跳 X」或「上次連線:X」,**不再**顯示「從未連線/尚未連線過」。
|
||||||
|
3. **grep 檢查**:第 2 層動作按鈕文案不再出現「連線/斷線」(改「啟用/停用」);第 1 層文案帶「雲端」或搭配「這台電腦」層標籤。
|
||||||
|
4. **模型四態**:離線時模型 Card 顯示 cache + 「資料截至上次連線」;重連後 fetchDevice 刷新為最新(含未載入)。
|
||||||
|
5. **載入後引導**:flash 完成 → 模型 Card 內出現「前往推論」主 CTA + toast action,不用在按鈕排裡找。
|
||||||
|
6. **三斷點截圖**:mobile(≤375)/tablet(768)/desktop(≥1280) 兩層 badge + 進度列佈局正常。
|
||||||
|
7. **對比度**:兩層 badge 文字 + 層標籤 WCAG AA(4.5:1)。
|
||||||
|
8. **離線降級**:第 1 層 offline 時,啟用/載入/推論全 disable + tooltip 指出是「電腦未連上雲端」。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. 給 Orchestrator / frontend 的落地摘要
|
||||||
|
|
||||||
|
**全部純前端**(改 i18n value + badge 呈現 + 詳情頁佈局 + 模型 Card 四態 + flash 完成引導)。底層欄位(`remoteStatus`/`status`/`flashedModel`/`lastSeenAt`/`pairedAt`/`registeredAt`)都已存在,無需改 store 型別。
|
||||||
|
|
||||||
|
**唯一後端待確認(不阻擋落地)**:offline 時後端是否穩定回傳 `last_seen_at`(§7)。有回 → 「最後心跳 X」最精準;沒回 → 本設計已用 pairedAt fallback 避免「從未連線」誤導。建議 Orchestrator 順帶問後端 / architect:裝置每次上線時是否有寫 last_seen_at。
|
||||||
|
|
||||||
|
**落地建議拆批(frontend 增量)**:
|
||||||
|
- 批 1(問題 1,最小、解使用者最痛):改 `remote-device-badge.tsx` 的「從未連線」fallback 邏輯 + `remote.lastSeenNever` 文案 + 新增 `remote.lastConnectedAt`。
|
||||||
|
- 批 2(問題 2 核心):兩層命名 i18n value 全改 + 詳情頁兩層 badge 呈現 + 層標籤 + 第 2 層就緒 badge。
|
||||||
|
- 批 3(問題 3+4):模型 Card 四態 + flash 完成主 CTA + toast action + Flow Stepper 進度列。
|
||||||
Loading…
x
Reference in New Issue
Block a user