visionA/docs/autoflow/03-design/flow-device-usage-ux.md
jim800121chen b35b989d9d docs(design): 裝置使用流程 UX 重新設計(連接電腦→啟用裝置→載入模型→推論)
解使用者實測 4 問題:從未連線誤導、兩層狀態命名混淆(動詞不重複三層:連接
電腦/啟用裝置/載入模型/推論)、模型狀態四態、載入後推論引導。全純前端、落地
拆 3 批。待補:多電腦分組(design 進行中)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-05 00:27:29 +08:00

454 lines
35 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 裝置使用流程 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 進度列