依 R5 五輪決策把 visionA-local 從「Wails 內嵌 Next.js」重構為「Wails
本機伺服器控制台 + 瀏覽器 Web UI」模式(類比 Docker Desktop / Ollama)。
程式碼變動
- M8-1 砍 yt-dlp 全套(後端 resolver / URL handler / 前端 URL tab /
Makefile vendor / installer / bootstrap / CI workflow,-555 行)
- M8-2 砍 Mock 模式全套(driver/mock、mock_camera、Settings runtimeMode、
VISIONA_MOCK 環境變數,-528 行)
- M8-3 ffmpeg 從 GPL 切換到 LGPL 混合方案:Windows/Linux 用 BtbN 現成
LGPL binary,macOS 自 build minimal decoder-only 進 git
(vendor/ffmpeg/macos/ffmpeg 5.7MB + ffprobe 5.6MB,比 GPL 版省 85% 空間)
- M8-4 Wails Server Controller:state machine、log ring buffer 2000 行、
preferences.json atomic write、boot-id、Gin SkipPaths、shutdown 7+1 秒、
notify_*.go 三平台 OS 通知、watchServer 改 Error state 不 os.Exit
- M8-4b 啟動階段管線 R5-E:6 階段進度 event、20s soft / 60s hard timeout、
stage 5/6 skip 規則、sentinel file、RestartStartupSequence 5 步驟
- M8-5 Wails 控制台 vanilla HTML/JS/CSS(9 檔 ~2012 行)取代 M7-B splash:
state 視覺、log panel、startup progress panel、Stage 6 manual CTA
pulse、shutdown modal、Settings、Dark Mode、i18n 中英雙語
- M8-6 上傳影片副檔名擴充(mp4/avi/mov/mpeg/mpg)
- M8-7 Web UI Server Offline Overlay(role=alertdialog + focus trap +
wsEverConnected 容錯 + Page Visibility)
- M8-8 CORS middleware(127.0.0.1/localhost only + suffix attack 防護)+
ws/origin.go 獨立 WebSocket CheckOrigin 避 package cycle
- MAJ-4 server:shutdown-imminent WebSocket broadcast 機制
(/ws/system endpoint + notifyShutdownImminent helper)
- M8-9 Boot-ID + 瀏覽器 tab 自動重連(sessionStorage loop guard)
品質
- ~105+ 新 unit test + race detector (-count=2) 全綠
- 10 個 milestone 全部通過 Reviewer 審查
- 三方 v2 + v2.1 文件(PRD / Design Spec / TDD)+ 交叉互審紀錄
收錄在 .autoflow/
交付前待處理(M8-10)
- 重跑 make payload-macos 把舊 GPL 77MB binary 換成新 LGPL
- 三平台 end-to-end build 驗證
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
37 KiB
Design 第一輪分析 — visionA-local 方向變更(2026-04-14)
Design Agent · 第一輪分析筆記(非正式規格) · 2026-04-14 對應事件:使用者提出方向重大變更 — Wails 桌面 app 由「跑 Next.js 主 UI 的殼」退化為「Local Server 控制台」,主 UI 改為純瀏覽器網頁。
摘要(3 行)
- 使用者要求把 visionA-local 從「Photo Booth 型桌面 app」轉為「Docker Desktop / Ollama 型服務控制台 + 瀏覽器網頁 UI」雙 UI 架構:桌面殼只管「server 生命週期 + log」,瀏覽器端承擔所有業務操作。
- 推論輸入從目前的
camera / image / video(file) / video(url)砍為三種:camera / image / video 檔案上傳,URL/YT-DLP 整條移除;模型來源只剩「預置 + 使用者上傳」,不再有第三方目錄。 - 這個改動在體驗面有實質 upside(多視窗、devtools、書籤、和 localhost 生態整合),但也讓原本已完成的 splash regression 修復方向逆轉、和第三輪 Q-A「砍 tray」決策再度打架(因為沒有 tray 又沒有主視窗、server 要不要在桌面 app 關閉後繼續活)。這幾個衝突必須在進開發前先解。
A. 變更解讀(體驗面)
A1. 為什麼使用者想這樣
使用者沒明講動機,但以 UX 研究角度推測有幾個合理動機,按可能性由高到低排序:
-
對齊 localhost 生態的心智模型 使用者自己是 FAE / 開發者,日常工具鏈是
docker desktop/ollama serve/jupyter notebook/stable-diffusion-webui。這些工具都走「桌面端只是 server 控制器,真正操作在瀏覽器 / CLI」的雙重介面模式。把 visionA-local 做成同樣形狀,學習成本 = 0,符合「這類工具應該長這樣」的直覺。 -
瀏覽器能力是 Wails WebView 的超集
- 多視窗:同時開兩個 tab 對照 camera 推論 vs 影片推論
- devtools:F12 即用,可以看 network、console、storage
- 書籤 / 分頁歷史:跳回上次看的裝置
- 瀏覽器擴充:screenshot、錄影、Postman 等 workflow 工具直接接上
- 瀏覽器字體 / 高 DPI / 縮放:Wails 在 Windows 的 WebView2 某些版本字體渲染有 bug,瀏覽器沒這問題
- 分享 URL:
http://localhost:3721/workspace/xxx可以貼給同事或記在筆記裡
-
推論結果可以在不同視窗同時看 目前 Wails 單視窗模式,使用者只能看一個 workspace。FAE 到客戶現場 demo 時常見需求:一邊跑 camera、一邊準備下一段影片 — 開兩個瀏覽器 tab 就解決了。
-
切換到不同 OS 或遠端機時維持一致 雖然 visionA-local 定位單機,但使用者也許會在 Linux 無頭機器上 run server,然後用 Mac 筆電瀏覽器連 LAN 過去 demo。這個動機如果存在,就不是單純的 UX 偏好,而是產品範圍的擴張(LAN 可訪問),要主動問清楚(見 D 節)。
-
Server 健康度獨立可見 目前 Wails UI 把 server 當成黑盒子,server 崩了 UI 才喊 fatal dialog。獨立 log 面板讓使用者在事情出錯前就看到徵兆(例如 Python sidecar 有 warning),降低 debug 門檻。
最有說服力的論證是 1 + 5 的組合:使用者想要的不是「瀏覽器介面」這個手段,而是「把 visionA-local 從『桌面軟體』重新定位為『本機服務 + 網頁控制台』」這個心智模型。這是產品層面的 re-positioning,不只是 UI 重排。
A2. 對使用者旅程的影響
首次開啟流程(First-Run)
現況(M7 splash + Next.js):
雙擊 app → Wails 視窗開 → splash 輪詢 GetServerStatus
→ server 起來 → window.location.replace 跳 Next.js 主 UI
→ First-Run 歡迎頁(歡迎 / 模式 / 偵測)→ Dashboard
新方向:
雙擊 app → 桌面控制台視窗開 → 自動 start server → log 跑 → status: running
→ 使用者點「Open in browser」→ 瀏覽器開 localhost:3721 → 進 First-Run → Dashboard
體驗斷裂點:
- 使用者從「雙擊 app → 看到 app」變成「雙擊 app → 看到 server 控制台(不是我要的)→ 還要再點一下」。這是多一個步驟。
- 對第一次用的 FAE 來說,看到「Local server: running · Open in browser」可能會愣一下 — 「我不是要裝網頁工具啊?」
- 解法:首次啟動自動彈出瀏覽器(桌面控制台照樣開著在背景)。Docker Desktop 沒這麼做是因為 docker daemon 不需要瀏覽器,但 Ollama / Stable Diffusion WebUI 都會自動開。對齊後者體驗。
日常使用流程
現況:雙擊 app → 直接操作 新方向:雙擊 app → 瀏覽器自動彈(或從 dock/tray 記憶上次狀態)→ 操作
- 如果使用者前一個 session 還沒關瀏覽器 tab,雙擊 app 時不該又彈一個新 tab — 應該偵測既有 tab 並 focus(技術上
window.openwith sametargetname 就行)。 - 如果桌面控制台已經在跑、使用者雙擊 app 第二次,要 raise 既有控制台視窗(single-instance,第四輪決策有定義
/ipc/raiseendpoint,可重用)。
錯誤排除流程
這是新架構最大的 UX 升級點。 目前 server 崩潰時,使用者看到的是:
- Wails 視窗從 Next.js UI 變回 splash
- 或者彈出 fatal dialog「Server 已停止」
- 使用者要自己去
~/Library/Application Support/visiona-local/logs/翻 log
新架構:
- 桌面控制台永遠看得到 log,server 崩了會有紅色行 / stack trace 直接 inline
- 一鍵 Restart 按鈕就地重試
- 可以 Copy log 貼 slack 問人
- 「Open logs folder」按鈕開 Finder/Explorer 到 log 目錄
這個價值是 A1 論證 5 的具體落地。桌面控制台不是退化,是獲得一個「自助除錯介面」。
離開流程
第四輪決策 Q7 是「B 傳統式(關閉 = 結束)」。新架構打到這個決策頭上:
- 如果桌面控制台關了 = server 停了 → 瀏覽器的網頁就 突然變磚塊(fetch 全 500,camera 串流斷線)。使用者會很困惑:「我只是關掉那個 log 視窗啊?為什麼我的推論頁面壞了?」
- 如果桌面控制台關了 ≠ server 停 → 又回到 tray / 背景服務心智模型,和第三輪 Q-A「砍 tray」決策衝突。
- 這是 D 節最重要的待決策問題。
A3. 現有頁面搬遷工作
現有 Next.js 頁面結構:
frontend/src/app/
├── layout.tsx ← 全域 layout(sidebar + header)
├── page.tsx ← Dashboard(/)
├── workspace/[deviceId] ← 推論主畫面
├── devices/ ← 裝置列表
├── devices/[id] ← 裝置細節
├── models/ ← 模型列表
├── models/[id] ← 模型細節
└── settings/ ← 設定(4 分頁)
搬遷需要改動的點:
| 面向 | 現況 | 新架構需要做什麼 | 工作量 |
|---|---|---|---|
| 頁面結構 | Next.js pages | 完全不用動。現有頁面直接以瀏覽器開 http://localhost:<port>/ 就能跑 |
0 |
| workspace 推論源 tabs | camera / image / video,video tab 內有 file/url 雙模式 | 砍掉 video tab 內的 url 按鈕,video tab 變成單一「upload file」。pasteUrl / urlPlaceholder / urlHelpText i18n keys 刪除。source-selector.tsx 的 videoMode state 和 startFromUrl 呼叫都刪。另外 batch_image sub-mode 保留(它是 image tab 的多檔上傳變體)。 |
S |
| 第四輪「url 推論首次 ≤ 250ms」指標 | 本來假設 url 來源也算在延遲指標內 | URL 整條砍後,這條指標只剩 camera + 本地 video file。更新 PRD 的 R4-2 註記。 | XS(文字調整) |
| 模型來源 | models 頁面有預置模型 + Upload Dialog | 使用者說「除了預設的幾種只能用上傳的」→ 確認這和目前狀態一致(預置 + 上傳),不需要改。只是要刪掉任何「從 URL 下載模型」或「線上模型市集」的殘留(目前沒有,但要檢查) | XS |
| devices 頁面 scan/connect | 現有已經有 Refresh Devices 按鈕(快捷鍵 ⌘Shift+R),也有 per-device connect |
不用改,使用者說的「scan/connect device 介面」已經存在 | 0 |
| Wails 原生呼叫 | splash 用 GetServerStatus() binding、fatal dialog 用 runtime.EventsEmit |
splash 從瀏覽器消失,變成桌面控制台內部邏輯。Next.js 主 UI 必須徹底不用任何 Wails JS binding(M7-B 設計時已經做到,這點不用改) | 0 |
| Dark Mode 偵測 | 現況走 CSS prefers-color-scheme(第四輪決策) |
瀏覽器原生支援,不用改 | 0 |
| i18n 切換 | Settings > 一般 > 語言 | 不用改,瀏覽器 localStorage 持久化即可 | 0 |
| OS 通知 | 第四輪 R4-8:裝置連/斷 toast、server 崩潰 native notification | toast 完全不變(瀏覽器支援好)。Server 崩潰的 native notification 換位置:不再由 Wails 處理,改由桌面控制台自己處理(它本來就是服務管理者,由它發通知比瀏覽器更合邏輯) | S |
| Sidebar 與 Top bar | Next.js 的 layout.tsx 已實作,含 sidebar navigation + user-friendly chrome |
不用改,但要考慮要不要隱藏任何「return to desktop app」按鈕之類的東西(目前沒有) | 0 |
| Window chrome tokens | 第四輪 spec/07-design-tokens.md §7.5 有 desktop 專用 elevation / window chrome token |
這些 token 只在桌面控制台用;網頁端不用(瀏覽器 chrome 由瀏覽器繪製)。不需要刪,但桌面控制台可以沿用 | 0 |
| 快捷鍵集(⌘1-4 切換主區塊、⌘,、⌘U 等) | 現有由 Next.js 內部 keyboard handler 綁定 | 分裂為兩套:桌面控制台只有 ⌘W(關閉=結束)/ ⌘Q;其餘 ⌘1-4、⌘, 、⌘Shift+R、⌘U 全部留在瀏覽器端。⌘W 在瀏覽器是「關 tab」這是原生行為,不衝突。但 ⌘Q 在瀏覽器是「結束 browser」會誤殺所有 tab,要和使用者確認怎麼處理(見 D 節) |
M |
總結:現有 Next.js 頁面 80-90% 可以零改動直接搬。實際修改集中在三個點:
- 砍 workspace 的 URL 推論 UI(1 個元件 + 相關 i18n)
- 桌面控制台是全新 app(Wails main.go 大改)
- First-Run 自動開瀏覽器的新 hook
A4. 砍掉 URL 推論的影響
使用者這句「推論只需包含這三種 camera/image/上傳影片」等於宣判 URL 推論死刑,影響面:
UX 面影響:
-
workspace 的 video tab 簡化:
- 現況:點 video tab → 看到
[上傳檔案] [貼上連結]兩顆 button → 選 file → 再點 Select Video - 新方向:點 video tab → 直接就是檔案上傳區(大 drop zone + 檔案選擇)
- 這改善了 video tab 的體驗(少一個決策層),符合 Jakob Nielsen 的「減少選擇負擔」原則。
- 支援格式:使用者說
avi, mpeg, mp4, 瀏覽器能吃的格式,實際上瀏覽器<input type="file" accept="video/*">就能列一批。我建議 accept list 顯式寫.mp4,.avi,.mpeg,.mpg,.mov,.mkv,.webm(瀏覽器相容格式的超集),因為後端是 ffmpeg 而非<video>tag,ffmpeg 吃得下的更多。這條要和 Architect 確認 ffmpeg 的 decoder whitelist。
- 現況:點 video tab → 看到
-
i18n 文案刪除:
camera.pasteUrl/camera.urlPlaceholder/camera.urlHelpText三個 key 刪掉(zh-TW + en),避免殘留 dead key。 -
yt-dlp 打包怎麼辦?
- 第四輪 R4 已決定 yt-dlp 要內嵌(35MB)+ ffmpeg 內嵌(77MB GPL build)。URL 推論砍掉後,yt-dlp 的 35MB 完全是死重量。
- 強烈建議把 yt-dlp 從 vendor 清單移除,安裝檔可以瘦身 35MB(220MB → 185MB)。
- ffmpeg 不能砍:本地影片解碼還是需要 ffmpeg(
avi / mpeg / mov瀏覽器原生<video>不一定吃,後端 ffmpeg 是 decoder + transcode 關鍵)。 - 這條要寫進 D 節給 PM / Architect:這是 M6 任務成果的部分退場,要追蹤 vendor 清單、Makefile、PRD 的第三方授權頁、安裝檔預期大小等多處更新。
-
URL 推論是否有隱藏的未來用途? 例如「RTSP 串流」(IP camera)也是走 URL 輸入。如果使用者未來想接監視器場景,RTSP 就回不來了。這條要和 PM 確認是否真的永久砍,還是只是 MVP 先砍。(我的 PM hat 建議:MVP 先砍,future backlog 標記
RTSP 可能回歸。) -
對現場 demo 的影響: FAE 帶筆電到客戶現場,如果沒有 Wi-Fi / 網路受限,YouTube URL 本來就跑不動 — 砍掉它和「完全離線」承諾更一致。這是 PM/PRD 的 strategy 論證可以直接套用的 UX narrative。
總結:砍 URL 是淨收益(簡化 UI + 瘦身 + 對齊離線承諾),只要注意 yt-dlp 要一起砍、PRD 文案要清。
B. 新的 Desktop Control Panel 設計提案
B1. 視窗規格
| 項目 | 建議值 | 說明 |
|---|---|---|
| 預設視窗寬度 | 720 px | 不能太窄,log 行要能看;不能太寬,沒必要 |
| 預設視窗高度 | 560 px | log 區塊要能看 15-20 行 |
| 最小寬度 | 560 px | 保證按鈕 + status 不疊 |
| 最小高度 | 420 px | |
| 可調整大小 | 是 | 使用者可能想把它拖大看更多 log |
| 最大化按鈕 | 保留 | |
| 最小化按鈕 | 保留 | |
| 置頂 | 否 | 不干擾瀏覽器操作 |
| 全螢幕 | 否(不需要) | |
| Title bar | 原生 | 不做自訂 title bar,省跨平台工 |
| 視窗標題 | visionA-local · Server Control |
|
| 應用程式 icon | 沿用現有 visiona-local/frontend/icon.png |
B2. 佈局區塊(ASCII wireframe)
┌─────────────────────────────────────────────────────────────────┐
│ ● visionA-local · Server Control [─][□][✕]│ ← Title bar(原生)
├─────────────────────────────────────────────────────────────────┤
│ │
│ [visionA-local] Status: ●Running Port: 3721 v0.1.0 │ ← Header(品牌 + 狀態列)
│ Uptime: 00:12:43 PID: 45821 │
│ │
├─────────────────────────────────────────────────────────────────┤
│ │
│ [ Open in Browser ] [ Start ] [ Stop ] [ Restart ] │ ← Primary controls
│ │
│ ☑ Follow tail ☑ Show timestamps [ Filter: _______ ] │ ← Log controls
│ │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 2026-04-14 10:23:41 INFO HTTP server listening on :3721 │
│ 2026-04-14 10:23:41 INFO wails ipc port: 49152 │
│ 2026-04-14 10:23:42 INFO device scan: found 1 Kneron KL520 │
│ 2026-04-14 10:23:43 INFO GET /api/devices 200 (4ms) │ ← Log panel
│ 2026-04-14 10:23:45 INFO GET /api/models 200 (2ms) │ (等寬字體, 可捲動,
│ 2026-04-14 10:23:58 WARN python sidecar restart (attempt 1) │ 等級著色)
│ 2026-04-14 10:23:59 INFO python sidecar ready │
│ 2026-04-14 10:24:12 INFO inference session start: classification│
│ ... │
│ │
│ │
│ │
├─────────────────────────────────────────────────────────────────┤
│ [ Clear log ] [ Copy log ] [ Export log ] [ Open log folder ] │ ← Log actions
│ │
│ Lines: 142 / 1000│ ← Footer status
└─────────────────────────────────────────────────────────────────┘
B3. 元件細節
Header
- 品牌標記:左側一個 20px 高的 visionA-local logo + 產品字樣(同現有 splash 視覺)
- 狀態指示燈:大圓點 + 文字
●Running(綠 / semanticcolor.success)●Starting...(黃 / semanticcolor.warning,附 spinner)●Stopped(灰 / semanticcolor.muted-foreground)●Crashed(紅 / semanticcolor.destructive,附小 ⚠️ icon)
- 運行資訊:port / uptime / PID / version — 次要資訊用
color.muted-foreground - 沿用既有 design tokens(shadcn oklch),不另外造一套
Primary Controls(3 顆服務按鈕 + 1 顆瀏覽器按鈕)
| 按鈕 | 狀態邏輯 | 視覺優先級 |
|---|---|---|
| Open in Browser | 只在 Running 時 enabled;點擊呼叫 OS open URL |
Primary(filled),位於最左側最顯眼處 |
| Start | 只在 Stopped / Crashed 時 enabled |
Secondary(outlined) |
| Stop | 只在 Running 時 enabled;點擊後要有確認(避免誤按) |
Secondary |
| Restart | 只在 Running 時 enabled |
Secondary |
設計 rationale:Open in Browser 是日常最高頻操作(每次啟動都要按一次),必須視覺最重;Start/Stop/Restart 只在出事時才點。這違反了「Start 應該在第一個」的慣例,但符合使用頻率。
可以考慮合併 Stop + Restart 為一顆「⋯」overflow menu,避免誤按:
[ Open in Browser ] [ Start ] [ ⋯ Manage ▼ ]
├─ Stop server
├─ Restart server
└─ Clear data (dangerous)
Log 控制列(Log controls)
- Follow tail:預設開。使用者往上捲動時自動關閉(像 Docker Desktop /
tail -f),捲到最底時自動重新開啟(或顯示Jump to latest按鈕) - Show timestamps:預設開。關閉後 log 行不含時間戳,節省寬度
- Filter:即時過濾(字串 match,不做 regex 避免使用者不會用)。keyboard:
⌘F聚焦 filter
Log panel
- 字體:等寬(SF Mono / Consolas / Monospace)
- 字級:12px,無 line-height 壓縮
- 等級著色:
INFO→ 預設文字色WARN→color.warning(琥珀)ERROR→color.destructive(紅)DEBUG→color.muted-foreground
- 長度上限:記憶體最多 1000 行(ring buffer),超過舊的丟。避免長時間跑爆記憶體。
- 完整歷史:寫檔到
~/Library/Application Support/visiona-local/logs/server.log,和當前stderr.log/stdout.log共存(Architect 確認) - 支援選取複製:使用者可以滑鼠拖選文字。無論 follow tail 是否開啟,選取時都要凍結畫面
- Auto scroll 平滑:新行滑入動畫 60ms,太快會閃
- Drag-select + ⌘A select all + ⌘C copy:原生支援即可
Log actions(底部按鈕列)
| 按鈕 | 功能 |
|---|---|
| Clear log | 清空畫面上的 log(不動 log 檔)。二次確認 |
| Copy log | 複製全部 log 到剪貼簿(連同 timestamp) |
| Export log | 匯出 .log 檔到指定位置(原生 Save Dialog) |
| Open log folder | 用 OS 預設 file manager 開 ~/Library/Application Support/visiona-local/logs/ |
Footer status
- 行數統計:
Lines: 142 / 1000(顯示當前行數 / 上限) - Memory hint(可選):
Server RAM: 240 MB— 從ps取樣每 3 秒刷新,供使用者觀察是否記憶體漏水
B4. 首次啟動行為
建議預設行為:
- 雙擊 app → 桌面控制台視窗開(預設位置:螢幕中央)
- 自動 start server(不需要使用者點 Start)
- Server 起來後(status → Running),自動呼叫 OS 開預設瀏覽器到
http://localhost:3721/ - 桌面控制台留在背景(不最小化、不關閉 — 不然使用者會以為 app 壞了)
- 使用者下次啟動時,記住上次的行為:
- 如果上次使用者手動把控制台關閉,下次啟動仍然彈控制台(因為關閉 = 結束程式,第四輪 Q7 決策)
- 如果上次使用者只是把瀏覽器關了,下次只要控制台還活著,雙擊 app → 直接 raise 既有控制台 + 重新彈瀏覽器
Settings(在桌面控制台內):
☑ 啟動時自動開啟瀏覽器(預設開)☑ 啟動時自動 start server(預設開)☑ Log 開啟時自動 follow tail(預設開)☑ Dark mode: Follow system(唯讀,和第三輪決策一致)- Language:
[ 繁體中文 ▼ ] / [ English ](桌面控制台自己也要 i18n)
例外情境:
- Server 在 5 秒內沒 ready(例如 Python sidecar 起不來)→ log 裡已經有錯訊,不彈瀏覽器;使用者看 log 自助排除
- Server 起來但 port 被佔 → 改用 fallback port 並在 header 顯示,
Open in Browser連到新 port
C. Web UI vs Desktop Control Panel 職責分野
| 功能 / 面向 | Desktop Control Panel | Web UI(瀏覽器) | 備註 |
|---|---|---|---|
| Server 啟動 / 停止 / 重啟 | ✅ 唯一入口 | ❌ | Web UI 不能自殺 |
| Server log 顯示與操作 | ✅ 唯一入口 | ❌ | |
| 「Open in Browser」一鍵跳轉 | ✅ 唯一入口 | — | |
| First-Run 歡迎 / 模式選擇 / 硬體偵測 | ❌ | ✅ | Next.js 現有 flow |
| Dashboard(快速開始 + 狀態卡) | ❌ | ✅ | |
| Devices 頁面(scan / connect / detail) | ❌ | ✅ | |
| Models 頁面(預置 + 上傳 + detail) | ❌ | ✅ | |
| Workspace 推論頁面(camera / image / video file) | ❌ | ✅ | 砍掉 URL tab |
| Settings > 一般(語言、深色模式狀態) | 部分(只控制桌面控制台自己) | ✅ | 語言設定兩套獨立!見下方問題 |
| Settings > 硬體 / 模型 / 進階 | ❌ | ✅ | |
| OS 通知:裝置連/斷 | ❌(App toast 走瀏覽器) | ✅(toast) | Web 端用瀏覽器 Notification API 或 in-page toast |
| OS 通知:Server 崩潰 | ✅ 改由控制台負責 | ❌ | 原本第四輪 R4-8 是 Wails shell out,現在改成桌面控制台直接呼 OS notification |
| 資料目錄開啟 | ✅ Open log folder 按鈕 | Settings > 進階 也可以 | 兩邊都有,方便 |
| 快捷鍵 ⌘,(Settings) | ❌ | ✅ | 瀏覽器端 |
| 快捷鍵 ⌘1-4(切換主區塊) | ❌ | ✅ | |
| 快捷鍵 ⌘Shift+R(重新整理裝置) | ❌ | ✅ | |
| 快捷鍵 ⌘U(上傳模型) | ❌ | ✅ | |
| 快捷鍵 ⌘W / ⌘Q | ✅ | ✅(瀏覽器原生關 tab) | 衝突:⌘Q 在瀏覽器會結束瀏覽器,誤殺所有 tab;見 D 節 |
| i18n 語系 | 自己一套(控制台) | 自己一套(web app) | 兩邊都要雙語,可共用 i18n 辭典 |
| Dark mode | 跟隨系統 | 跟隨系統 | 兩邊都是 CSS prefers-color-scheme |
| 品牌 logo / 字體 / 色盤 | 與 Web 一致 | — | Design token 跨兩套 UI 套用 |
設計決策重點:Settings 的語系切換
目前設計是「Settings > 一般 > 語言」在瀏覽器 web UI 內。桌面控制台本身也有文字,必須有自己的語系切換(通常跟系統 locale)。
兩個選項:
- 方案 1:桌面控制台只跟系統 locale,不提供手動切換(最省工,符合「控制台是透明的服務管理者」定位)
- 方案 2:桌面控制台 Settings 有獨立語系切換,但預設「跟隨 Web UI 設定」(會需要 IPC 讓兩邊同步)
Design 建議方案 1,因為控制台出現的文字量極少(按鈕 + log level)。
D. 待使用者決策的問題
D1. 桌面控制台關閉後 server 是否繼續跑? ⚠️ 最重要
目前第四輪 Q7 決策是「關閉 = 結束」,第三輪 Q-A 決策是「不做 tray」。新架構下這兩個決策打架:
| 選項 | 行為 | 代價 | 收穫 |
|---|---|---|---|
| A. 關閉 = 結束(維持第四輪 Q7) | 關桌面控制台 → server 也停 → 瀏覽器 tab 變磚塊 | 使用者會困惑「我為什麼不能關 log 視窗?」。必須顯示確認對話框「關閉將停止 server 並中斷瀏覽器操作,確定?」(但這本身就是 UX 摩擦) | 簡單、和現有決策一致 |
| B. 控制台可最小化但不可關閉 | 控制台只能最小化,關閉按鈕 disabled 或變「最小化」 | 非傳統,違反 OS 慣例,macOS 的 ⌘W / 紅點會壞掉 |
強制維持 server 活著 |
| C. 背景 daemon 模式(復活 tray) | 關控制台 → 縮回 menu bar / tray → server 繼續跑 → tray 點擊重開控制台 | 正面推翻第三輪 Q-A 決策,tray 跨平台工要做 | 最符合 Docker Desktop / Ollama 模式 |
| D. 關閉控制台彈警告,使用者選「停 server」或「只關視窗」 | 提供兩個選項 | 決策疲勞,每次關都彈 | 最靈活但最煩 |
Design 建議:選 C,並正式推翻 Q-A。理由:
- A 會讓使用者每次關控制台都中斷 server,完全違背「當成 daemon 用」的 Docker Desktop 心智模型
- B 不符合 OS 慣例,無障礙地雷
- D 每次都問太煩
- C 雖然要投資 tray 做圖資產與 Wails tray(第三輪說踩坑),但新架構下 tray 的價值從「可有可無」變成「核心」,值得投資。Ollama / Docker Desktop 都走這條,使用者預期會符合。
這題使用者必須親自拍板。
D2. 首次啟動是否自動彈瀏覽器?
| 選項 | 我的評價 |
|---|---|
| A. 自動彈(建議) | 符合 Ollama / SD WebUI 體驗,降低「我要怎麼開?」摩擦 |
| B. 手動點「Open in Browser」 | 符合 Docker Desktop 體驗,但 Docker Desktop 本身就不需要瀏覽器;visionA-local 不彈等於多一步 |
| C. Settings 可切換,預設自動 | 最靈活 |
建議 C,預設 A。
D3. 瀏覽器端是否需要 auth?Localhost-only 還是允許 LAN?
目前 server 預設 :3721 沒 auth。使用者如果把筆電 hotspot 打開、或連到公共 Wi-Fi,http://筆電-ip:3721 可能被同網段的人看到推論結果。
| 選項 | 說明 |
|---|---|
A. Localhost-only(127.0.0.1:3721)+ 不做 auth(建議) |
最安全,符合「單機工具」定位,無 LAN 視窗 |
B. 綁 0.0.0.0 + Token auth |
支援 LAN demo(副作用:使用者 A1 論證 4 可能要這個)但要做 token UI + URL 帶 token + 首次啟動產生 token |
C. 綁 0.0.0.0 + 不做 auth |
最危險,絕對不要 |
| D. Settings 可切換,預設 A | 靈活 |
建議 A。如果使用者真的要 LAN demo,future backlog,現在不做。
D4. Desktop 控制台支援 Dark Mode?跟隨系統?
建議:跟隨系統(和 Web UI 一致,使用 prefers-color-scheme)。不做手動切換。
D5. Log 保留多少 session?
- 畫面內 ring buffer:1000 行(建議)
- 磁碟 log 檔:保留最近 7 天 / 7 個 session,每個 10MB 上限,rotate
- 「Clear log」按鈕是否也清檔案?建議否,只清畫面
待確認:7 天 / 7 個 session 這個 retention 對使用者夠嗎?
D6. Desktop 控制台視窗位置記憶
第一次開:螢幕中央。
第二次開:記住上次關閉時的位置 + 大小嗎?建議是,寫到 ~/Library/Application Support/visiona-local/control-panel.json。
D7. 桌面控制台的 ⌘Q / ⌘W 行為
⌘W關視窗:在新架構下 = 觸發 D1 的決策(Stop/Hide/Ask)⌘Q結束 app:一定 = Stop server + quit
瀏覽器裡的 ⌘Q 會結束瀏覽器本身,不是 visionA-local 的問題,使用者本來就會知道。但要在 UI 文案明說「⌘Q 會結束 server」避免誤會。
D8. Web UI 是否保留任何 Wails 專屬功能?
例如:原生檔案對話框、native drag-and-drop、@wailsapp/runtime 事件。
建議:完全不用。Next.js 在瀏覽器跑就是標準 Web API,徹底切斷 Wails binding,這樣未來如果要讓使用者直接在 Chrome 開也能跑。現況 M7-B 已經做到這點,維持即可。
E. 風險觀察
E1. 使用者認知負荷:兩套 UI 會造成困惑嗎?
可能的情境:
- 使用者在桌面控制台看到
Language: [繁中],又在 web UI Settings 看到語言: [繁中]— 兩個是同一個還是不同? - 使用者在 web UI 砍了某個自上傳模型,桌面控制台 log 卻還顯示它?
緩解:
- 文案要明確:控制台叫 "Server Control Panel",web UI 是 "visionA-local Web",兩個名字不要混
- 桌面控制台 UI 極簡,只做服務管理,完全不碰業務邏輯(不在這裡管 device、model)— 避免使用者以為這裡也能操作
E2. 「一鍵啟動」變「兩步啟動」是體驗退化嗎?
如果自動開瀏覽器生效 → 體驗等於一鍵。如果沒有 → 退化。 D2 的決策直接決定這個風險是否存在。
E3. 既有的 i18n / Design System / Dark Mode 要重新套到桌面控制台嗎?
- i18n:要。桌面控制台文字雖少,但仍需中英雙語(符合第二輪 Q13 決策)。建議直接讀 web UI 的
zh-TW.ts / en.ts,抽一個desktop-controlnamespace。 - Design tokens:要。從
spec/07-design-tokens.md撿color.success / warning / destructive / muted / surface / on-surface,Wails WebView 一樣是 CSS,直接用。 - Dark Mode:要(和 web UI 一致
prefers-color-scheme)。
這代表桌面控制台不是 Tauri/Electron 原生 widget,而是一個 WebView 頁面(它本來就是 Wails = WebView)。這很好,因為可以直接複用 Tailwind + shadcn 元件。
E4. Log 面板的 PII / 敏感資訊風險
Server log 可能會印:
- 檔名(包含使用者的私人檔名)
- 模型名稱
- 裝置 serial
- API 呼叫 body(如果 log level = DEBUG)
使用者點「Copy log」貼到 Slack 就洩漏了。 緩解:
- 預設 log level = INFO(不印 body)
Copy log/Export log前顯示一次性提示「Log 可能包含檔名與裝置資訊,確定要複製?」- Log 目錄不加密,視 OS 檔案權限保護
E5. Server 崩潰後桌面控制台需要「自動重啟」嗎?
第四輪 Plan B 有 Python sidecar crash loading + 自動重啟(spec/08-states.md §8.8)。
新架構下,控制台自己是 watchdog,可以直接做 server 層級的 auto-restart(例如崩潰 3 秒後自動重啟一次,再崩就紅字等使用者手動)。這個行為要和 Architect 討論,因為第四輪原本是 sidecar 層級重啟。
E6. 「有 log 就有濫用」風險
如果使用者在桌面控制台看到 log 後,試圖自己寫 script 去 tail log 檔(例如公司 IT 規範要求把 log 送到 SIEM),log 檔格式就變成一個隱性 API。 緩解:log 檔格式要穩定,寫進 TDD 的「日誌格式契約」章節(這條以前沒有,可以新增)。
E7. 新架構和原 PRD 「零依賴、離線可用」承諾一致嗎?
- 瀏覽器?使用者一定有(系統預設)→ ✅
- 127.0.0.1 連線?完全本機 → ✅
- 不需要網路 → ✅
完全一致,沒問題。
E8. 「關閉 Wails 視窗」在新架構下的心智模型衝突
這個風險是 D1 的延伸。目前設計決策 Q7 (關閉=結束) 是假設主視窗是業務 UI 的前提下做的。主視窗變成控制台後,這個決策的前提崩了。必須重新詢問使用者。
F. 給 PM 和 Architect 的議題
給 PM:
- R4-2 MJPEG 延遲指標的 URL 分母要移除(原本可能包含
via url情境,砍後只剩 camera + local file) - 第三方授權頁要更新:yt-dlp 從 vendor 清單移除,安裝檔預期大小從 220MB → 185MB,發佈 AC 要重算
- RTSP 未來回歸路徑:使用者說「只做這三種」,要寫到 vision-and-non-goals.md 的非目標清單,避免未來說好的功能被砍後爭議
- Persona 更新:新架構下「FAE 帶筆電到客戶現場」的首次啟動腳本要重寫,因為多一個「桌面控制台自動彈 + 瀏覽器自動開」步驟
- PRD 產品定位語言:從「桌面 app」改成「本機服務 + 網頁控制台」— 這是 strategic repositioning,不只 UI 改動
- Q-A 砍 tray 決策要不要推翻(D1)— 這是三方要一起拉回來討論的關鍵點
給 Architect:
- main.go 架構大改:從「
go:embed all:frontend+ splash redirect」改為「全新 control panel 頁面 + server lifecycle 控制器」。M7-B 的 splash 修復的邏輯大部分可重用(輪詢GetServerStatus),但呈現層完全不同。 /ipc/raiseendpoint:從「將 splash 視窗提前」改為「raise 控制台視窗 + open browser」。語意變了。- Single-instance 第二次雙擊 UX:第四輪
spec/06-cross-platform.md §6.9定義是「靜默 raise」,新架構下要不要同時 open browser?(我的建議:是,順手) - OS 通知:第四輪 R4-8 的
Server 崩潰 → shell out 原生通知,新架構下改由桌面控制台直接呼(桌面控制台本來就是 Wails,直接runtime.MessageDialog或用系統 notification API) - Log pipeline:server stderr/stdout → 桌面控制台 WebView。需要一個 IPC 或直接讀 log file tail 的機制。建議用 Wails
runtime.EventsEmit把新行推到前端 - yt-dlp vendor:
make vendor-ytdlp任務移除,payload-macos / payload-windows / payload-linux 都不再 stage yt-dlp。Go server 端如果還有任何exec.Command("yt-dlp", ...)的程式碼路徑,要一起刪。 - ffmpeg 保留:即使砍 URL 推論,ffmpeg 仍要給本地 video file decode 用。R4 的 GPL release blocker 仍然存在。
- Port 衝突 fallback:原架構下 port 佔用是 Next.js 跑不起來 → 整個 app 廢掉。新架構下控制台仍能跑,可以試 fallback port 3722 / 3723,並在 header 顯示當前 port
- Log 檔案格式契約(E6 風險):需要寫到 TDD 新增章節「日誌格式」,定義欄位與 stability commitment
- Desktop control panel 是獨立 routing 還是和 Next.js 共用 WebView?
- 選項 A:Wails main window load 一個靜態 HTML/JS/CSS(不是 Next.js),主 UI 跑在瀏覽器 → 推薦,切得乾淨
- 選項 B:Wails main window load Next.js 的一個獨立 route
/_desktop-control,然後瀏覽器跳/→ 複用 Next.js 但混在一起,Next.js build 會多 chunk - 建議 A,和 M7-B 的 splash 同樣策略:極簡 HTML + vanilla JS(或小型 React),只做 server 控制和 log 顯示
給三方共同討論:
- 要不要正式推翻「砍 tray」決策?(見 D1)這是這次變更的單一最大決策點。
- 要不要正式把 Q7「關閉=結束」改成「關閉=背景 daemon」?(依賴 1)
- 要不要讓 web UI 也可以綁 LAN?(D3)這會把產品從「本機單人工具」變成「一人發起,多人觀看」的雛形,需要 PM 重新評估定位。
- 新架構下的 First-Run flow 在哪一層跑?
- 選項 A:完全在瀏覽器(Next.js),控制台只管啟動
- 選項 B:桌面控制台也顯示「首次啟動,正在準備…」進度條
- 建議 A(簡單),控制台永遠只做 server 管理。
結語 / 下一步建議
這次變更的本質是產品類別變更:從「桌面軟體」→「本機服務 + 網頁控制台」。如果使用者確定要這樣做,我強烈建議:
- 先解 D1(桌面控制台關閉後 server 是否繼續跑 + 是否復活 tray),這是最大的架構叉路
- 再解 D2/D3(啟動行為、LAN 範圍),這些決定預設體驗
- 三方各自產出正式文件的 delta(PM 更 PRD strategy + feature-inventory + vision-and-non-goals;Design 更 design-spec §1 IA + §5 replacement for Tray + §6 cross-platform + 新增 §11 Desktop Control Panel;Architect 更 design-doc 架構圖 + main.go 改寫 + api-endpoints + removed-code 加 yt-dlp)
- 然後才進開發
不要先進開發再補文件。 這次是第三輪 Q-A 決策的直接推翻 + Q7 決策的間接推翻,如果跳過文件同步,未來不知道為什麼會這樣做。
— Design Agent / 2026-04-14