解使用者實測 4 問題:從未連線誤導、兩層狀態命名混淆(動詞不重複三層:連接 電腦/啟用裝置/載入模型/推論)、模型狀態四態、載入後推論引導。全純前端、落地 拆 3 批。待補:多電腦分組(design 進行中)。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
35 KiB
裝置使用流程 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 層講「電腦」——沿用 pairing-rename 的主詞策略(帶主詞就不會跟裝置層撞)。
- 第 2 層講「裝置就緒」——用「就緒 / 待啟用」描述狀態,用「啟用 / 停用」描述動作,徹底避開「線」字。
- 三層的動詞不重複:電腦「連接」、裝置「啟用」、模型「載入」、最後「推論」。
| 層 | 狀態文案(新) | 動作按鈕(新) | 避開的舊詞 |
|---|---|---|---|
| 第 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 有值),做兩件事:
- 模型狀態 Card 切換為「已載入」成功態,Card 內直接放主 CTA:
┌── 模型狀態 ──────────────────────────┐ │ ✓ 模型已載入:yolov5s_640.nef │ │ 已就緒,可開始推論 │ │ │ │ [ 前往推論 → ] ← 主 CTA,size lg │ └────────────────────────────────────────┘ - 一次性 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 自檢)
- 兩層不打架:詳情頁同時出現「💻 這台電腦 已連上雲端」與「🔌 裝置 待啟用」時,使用者能分清兩層、知道要點「啟用裝置」。
- 「從未連線」修正:關掉 local agent 後,連過的裝置顯示「未連上雲端 · 最後心跳 X」或「上次連線:X」,不再顯示「從未連線/尚未連線過」。
- grep 檢查:第 2 層動作按鈕文案不再出現「連線/斷線」(改「啟用/停用」);第 1 層文案帶「雲端」或搭配「這台電腦」層標籤。
- 模型四態:離線時模型 Card 顯示 cache + 「資料截至上次連線」;重連後 fetchDevice 刷新為最新(含未載入)。
- 載入後引導:flash 完成 → 模型 Card 內出現「前往推論」主 CTA + toast action,不用在按鈕排裡找。
- 三斷點截圖:mobile(≤375)/tablet(768)/desktop(≥1280) 兩層 badge + 進度列佈局正常。
- 對比度:兩層 badge 文字 + 層標籤 WCAG AA(4.5:1)。
- 離線降級:第 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 進度列。