依 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>
538 lines
37 KiB
Markdown
538 lines
37 KiB
Markdown
# 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 研究角度推測有幾個合理動機,**按可能性由高到低**排序:
|
||
|
||
1. **對齊 localhost 生態的心智模型**
|
||
使用者自己是 FAE / 開發者,日常工具鏈是 `docker desktop` / `ollama serve` / `jupyter notebook` / `stable-diffusion-webui`。這些工具都走「桌面端只是 server 控制器,真正操作在瀏覽器 / CLI」的雙重介面模式。把 visionA-local 做成同樣形狀,學習成本 = 0,符合「這類工具應該長這樣」的直覺。
|
||
|
||
2. **瀏覽器能力是 Wails WebView 的超集**
|
||
- **多視窗**:同時開兩個 tab 對照 camera 推論 vs 影片推論
|
||
- **devtools**:F12 即用,可以看 network、console、storage
|
||
- **書籤 / 分頁歷史**:跳回上次看的裝置
|
||
- **瀏覽器擴充**:screenshot、錄影、Postman 等 workflow 工具直接接上
|
||
- **瀏覽器字體 / 高 DPI / 縮放**:Wails 在 Windows 的 WebView2 某些版本字體渲染有 bug,瀏覽器沒這問題
|
||
- **分享 URL**:`http://localhost:3721/workspace/xxx` 可以貼給同事或記在筆記裡
|
||
|
||
3. **推論結果可以在不同視窗同時看**
|
||
目前 Wails 單視窗模式,使用者只能看一個 workspace。FAE 到客戶現場 demo 時常見需求:一邊跑 camera、一邊準備下一段影片 — 開兩個瀏覽器 tab 就解決了。
|
||
|
||
4. **切換到不同 OS 或遠端機時維持一致**
|
||
雖然 visionA-local 定位單機,但使用者也許會在 Linux 無頭機器上 run server,然後用 Mac 筆電瀏覽器連 LAN 過去 demo。**這個動機如果存在,就不是單純的 UX 偏好,而是產品範圍的擴張**(LAN 可訪問),要主動問清楚(見 D 節)。
|
||
|
||
5. **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.open` with same `target` name 就行)。
|
||
- 如果桌面控制台已經在跑、使用者雙擊 app 第二次,要 raise 既有控制台視窗(single-instance,第四輪決策有定義 `/ipc/raise` endpoint,可重用)。
|
||
|
||
#### 錯誤排除流程
|
||
|
||
**這是新架構最大的 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% 可以零改動直接搬**。實際修改集中在三個點:
|
||
1. 砍 workspace 的 URL 推論 UI(1 個元件 + 相關 i18n)
|
||
2. 桌面控制台是全新 app(Wails main.go 大改)
|
||
3. First-Run 自動開瀏覽器的新 hook
|
||
|
||
### A4. 砍掉 URL 推論的影響
|
||
|
||
使用者這句「推論只需包含這三種 camera/image/上傳影片」等於宣判 URL 推論死刑,影響面:
|
||
|
||
**UX 面影響**:
|
||
|
||
1. **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**。
|
||
|
||
2. **i18n 文案刪除**:
|
||
`camera.pasteUrl` / `camera.urlPlaceholder` / `camera.urlHelpText` 三個 key 刪掉(zh-TW + en),避免殘留 dead key。
|
||
|
||
3. **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 的第三方授權頁、安裝檔預期大小等多處更新。
|
||
|
||
4. **URL 推論是否有隱藏的未來用途?**
|
||
例如「RTSP 串流」(IP camera)也是走 URL 輸入。如果使用者未來想接監視器場景,RTSP 就回不來了。**這條要和 PM 確認是否真的永久砍,還是只是 MVP 先砍**。(我的 PM hat 建議:MVP 先砍,future backlog 標記 `RTSP 可能回歸`。)
|
||
|
||
5. **對現場 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`(綠 / semantic `color.success`)
|
||
- `●Starting...`(黃 / semantic `color.warning`,附 spinner)
|
||
- `●Stopped`(灰 / semantic `color.muted-foreground`)
|
||
- `●Crashed`(紅 / semantic `color.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. 首次啟動行為
|
||
|
||
**建議預設行為**:
|
||
|
||
1. 雙擊 app → 桌面控制台視窗開(預設位置:螢幕中央)
|
||
2. **自動** start server(不需要使用者點 Start)
|
||
3. Server 起來後(status → Running),**自動**呼叫 OS 開預設瀏覽器到 `http://localhost:3721/`
|
||
4. 桌面控制台**留在背景**(不最小化、不關閉 — 不然使用者會以為 app 壞了)
|
||
5. 使用者下次啟動時,記住**上次**的行為:
|
||
- 如果上次使用者**手動把控制台關閉**,下次啟動仍然彈控制台(因為關閉 = 結束程式,第四輪 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-control` namespace。
|
||
- **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:
|
||
|
||
1. **R4-2 MJPEG 延遲指標的 URL 分母要移除**(原本可能包含 `via url` 情境,砍後只剩 camera + local file)
|
||
2. **第三方授權頁要更新**:yt-dlp 從 vendor 清單移除,安裝檔預期大小從 220MB → 185MB,發佈 AC 要重算
|
||
3. **RTSP 未來回歸路徑**:使用者說「只做這三種」,要寫到 vision-and-non-goals.md 的非目標清單,避免未來說好的功能被砍後爭議
|
||
4. **Persona 更新**:新架構下「FAE 帶筆電到客戶現場」的首次啟動腳本要重寫,因為多一個「桌面控制台自動彈 + 瀏覽器自動開」步驟
|
||
5. **PRD 產品定位語言**:從「桌面 app」改成「本機服務 + 網頁控制台」— 這是 strategic repositioning,不只 UI 改動
|
||
6. **Q-A 砍 tray 決策要不要推翻**(D1)— 這是三方要一起拉回來討論的關鍵點
|
||
|
||
### 給 Architect:
|
||
|
||
1. **main.go 架構大改**:從「`go:embed all:frontend` + splash redirect」改為「全新 control panel 頁面 + server lifecycle 控制器」。M7-B 的 splash 修復的邏輯大部分可重用(輪詢 `GetServerStatus`),但呈現層完全不同。
|
||
2. **`/ipc/raise` endpoint**:從「將 splash 視窗提前」改為「raise 控制台視窗 + open browser」。語意變了。
|
||
3. **Single-instance 第二次雙擊 UX**:第四輪 `spec/06-cross-platform.md §6.9` 定義是「靜默 raise」,新架構下要不要同時 open browser?(我的建議:是,順手)
|
||
4. **OS 通知**:第四輪 R4-8 的 `Server 崩潰 → shell out 原生通知`,新架構下改由桌面控制台直接呼(桌面控制台本來就是 Wails,直接 `runtime.MessageDialog` 或用系統 notification API)
|
||
5. **Log pipeline**:server stderr/stdout → 桌面控制台 WebView。需要一個 IPC 或直接讀 log file tail 的機制。建議用 Wails `runtime.EventsEmit` 把新行推到前端
|
||
6. **yt-dlp vendor**:`make vendor-ytdlp` 任務移除,payload-macos / payload-windows / payload-linux 都不再 stage yt-dlp。**Go server 端如果還有任何 `exec.Command("yt-dlp", ...)` 的程式碼路徑,要一起刪**。
|
||
7. **ffmpeg 保留**:即使砍 URL 推論,ffmpeg 仍要給本地 video file decode 用。R4 的 GPL release blocker 仍然存在。
|
||
8. **Port 衝突 fallback**:原架構下 port 佔用是 Next.js 跑不起來 → 整個 app 廢掉。新架構下控制台仍能跑,可以試 fallback port 3722 / 3723,並在 header 顯示當前 port
|
||
9. **Log 檔案格式契約**(E6 風險):需要寫到 TDD 新增章節「日誌格式」,定義欄位與 stability commitment
|
||
10. **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 顯示
|
||
|
||
### 給三方共同討論:
|
||
|
||
1. **要不要正式推翻「砍 tray」決策?**(見 D1)這是這次變更的單一最大決策點。
|
||
2. **要不要正式把 Q7「關閉=結束」改成「關閉=背景 daemon」?**(依賴 1)
|
||
3. **要不要讓 web UI 也可以綁 LAN?**(D3)這會把產品從「本機單人工具」變成「一人發起,多人觀看」的雛形,需要 PM 重新評估定位。
|
||
4. **新架構下的 First-Run flow 在哪一層跑?**
|
||
- 選項 A:完全在瀏覽器(Next.js),控制台只管啟動
|
||
- 選項 B:桌面控制台也顯示「首次啟動,正在準備…」進度條
|
||
- 建議 A(簡單),控制台永遠只做 server 管理。
|
||
|
||
---
|
||
|
||
## 結語 / 下一步建議
|
||
|
||
這次變更的本質是**產品類別變更**:從「桌面軟體」→「本機服務 + 網頁控制台」。如果使用者確定要這樣做,我強烈建議:
|
||
|
||
1. **先解 D1**(桌面控制台關閉後 server 是否繼續跑 + 是否復活 tray),這是最大的架構叉路
|
||
2. **再解 D2/D3**(啟動行為、LAN 範圍),這些決定預設體驗
|
||
3. 三方各自產出正式文件的 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)
|
||
4. 然後才進開發
|
||
|
||
**不要先進開發再補文件。** 這次是第三輪 Q-A 決策的直接推翻 + Q7 決策的間接推翻,如果跳過文件同步,未來不知道為什麼會這樣做。
|
||
|
||
— Design Agent / 2026-04-14
|