docs(design): 裝置使用流程 UX 重新設計(連接電腦→啟用裝置→載入模型→推論)

解使用者實測 4 問題:從未連線誤導、兩層狀態命名混淆(動詞不重複三層:連接
電腦/啟用裝置/載入模型/推論)、模型狀態四態、載入後推論引導。全純前端、落地
拆 3 批。待補:多電腦分組(design 進行中)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
jim800121chen 2026-08-05 00:27:29 +08:00
parent 8270c4fb0e
commit b35b989d9d

View 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-018serial 路由、兩步式連線)。
---
## 0. 問題總覽與設計解對照(一頁看懂)
| # | 使用者症狀 | 根因 | 設計解 | 純前端? |
|---|-----------|------|--------|---------|
| 1 | 離線後顯示「離線 · **從未連線**」誤導(明明連過) | `remote-device-badge.tsx:100-102` 只看 `!lastSeenAt` 就顯示「從未連線」,沒用 `pairedAt`/`registeredAt` 區分「連過但離線」vs「真的沒連過」 | 改「從未連線」判定邏輯:優先用 lastSeenAt缺時 fallback 到 pairedAt/registeredAt 判定「連過」只有全都沒有才顯示「從未連線」§4 | ✅ 純前端欄位都已存在lastSeenAt 後端未穩定回傳為 §7 待確認 |
| 2 | 兩層都用「線」字,「在線」又要點「連線」很困惑(核心) | 第 1 層 tunnelremoteStatus用「在線/離線」、第 2 層 driverconnect用「連線/斷線」,兩層命名撞、層次不明 | 三層語意重新定調:電腦(在線→「已連接雲端/未連上」、裝置就緒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→ 唯讀等待 │
│ │
│ CTAoffline 時):引導「請在電腦上啟動 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 現況
模型狀態 Carddevice-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 成功後已呼叫 fetchDevicedisconnect 亦同)。
- **重連=清空舊模型狀態**使用者提到「重連後應刷新為未載入」——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 │
│ 已就緒,可開始推論 │
│ │
│ [ 前往推論 → ] ← 主 CTAsize 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 AA4.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 進度列。