jim800121chen 8cd5751ce3 feat(local-tool): M8 重構 — Wails 控制台 + 瀏覽器 Web UI(R5 決策)
依 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>
2026-04-15 17:57:54 +08:00

220 lines
12 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.

# v2/code-reuse-v2.md — 沿用 vs 改寫 vs 新寫
> 所屬TDD v2 §2.8
> 目的:以 v1 M1-M7 已完成)為基準,量化 v2 refactor 的沿用率
> 承接:`architect-analysis-round2-refactor.md` §D
> 結論:**整體沿用率 85-95%**(三方共識 #1**不是丟掉重做**,是**擴充 + 砍功能**
---
## 1. 模組沿用率總表
| 模組 | 現況大小 | 改動量 | 沿用率 | 關鍵影響 |
|------|---------|-------|-------|---------|
| `server/` Go 後端 | ~15k LoC | ~400 行修改 + 新增 boot-id endpoint | **~96%** | 砍 yt-dlp + Mock + 新增 CORS whitelist + boot-id預置模型 / inference / camera pipeline / Python bridge / WebSocket / static serving 全部不動 |
| `server/web/out/` go:embed 的 Next.js static | ~20k LoC | ~100 行修改 + ~250 行新增offline overlay + boot-id watcher + i18n | **~88%** | 砍 URL tab + Mock UI新增 Offline Overlay + boot-id watcher業務頁面全部不動 |
| `visiona-local/app.go` Wails 殼 | 1584 行 | ~350 行新增 / ~30 行刪改 | **~85%** | 新增 ServerController + LogBuffer + PreferenceswatchServer 改為 Error state`mockMode` 欄位與 VISIONA_MOCK 砍除;核心 startup / shutdown / single-instance / driver installer 全部不動 |
| `visiona-local/frontend/` Wails 內嵌 UI | 211 行html + js + css | 三檔全部改寫 + 新增 12 檔 | **程式面砍掉重寫,概念承襲** | 沿用 ES module + wailsjs 串接模式,但內容是全新控制台 UI |
| `installer/` 打包腳本 | macOS dmgbuild / Windows iss / Linux AppImage | 各 ~3 行修改 | **~98%** | 只改 ffmpeg/ffprobe/LGPL licnese 的 copy其餘不變 |
| `Makefile` | 570 行 | ~60 行修改 + ~80 行新增macOS build | **~85%** | 砍 yt-dlp targets改 Windows/Linux ffmpeg URL新增 macOS 自 build |
| `scripts/bootstrap-*.sh/.ps1` | 中等 | 各 ~5 行修改 | **~99%** | 只砍 yt-dlp 參照 |
| `vendor/` | 現有依賴 | 砍 `yt-dlp/`;改 `ffmpeg/`;新增 `ffmpeg/macos/` 進 git | N/A | 內容差異顯著 |
**總計**:整個 repo 程式碼層 ~85-95% 沿用(依粗估方式不同,但結論是「絕大多數 M1-M7 投入仍有效」)。
---
## 2. 逐目錄細節
### 2.1 `server/` — 96% 沿用
**完全不動**> 99%
- `server/internal/api/ws/` — WebSocket hub / handlers除 CheckOrigin 那個 helper 是 M8-8 新增)
- `server/internal/camera/` — 除 `video_source.go` 的 yt-dlp 函式§2.2+ `mock_camera.go`(整檔刪)
- `server/internal/device/` — 除 `manager.go` 的 mockMode 條件 + `driver/mock/` 目錄
- `server/internal/inference/` — 推論 service 邏輯
- `server/internal/model/` — 模型 repository / store / upload
- `server/internal/flash/` — flash serviceKneron 韌體燒錄v1 已保留)
- `server/pkg/logger/` — logger + broadcaster
- `server/scripts/kneron_bridge.py` — Python KneronPLUS bridge
- `server/data/` — 預置 .nef 模型
**修改**< 5% 程式碼
- `server/main.go` 86-87 log message)、 89-95 註解)、 142 NewManager 簽名)、 147 NewManager 簽名
- `server/internal/api/router.go` 83 `/media/url`+ 新增 `/api/system/boot-id`
- `server/internal/api/handlers/camera_handler.go` 251 擴充副檔名+ 341-497 yt-dlp 相關
- `server/internal/api/handlers/system_handler.go` 新增 `BootID` method + constructor 新增 bootID 參數
- `server/internal/api/middleware.go` 整檔覆寫CORS 白名單
- `server/internal/config/config.go` MockMode / MockCamera / MockDeviceCount
- `server/internal/deps/checker.go` yt-dlp entry + 註解更新
- `server/internal/camera/video_source.go` `ResolveWithYTDLP` / `friendlyYTDLPError`可能砍 `NewVideoSourceFromURL` grep 結果
- `server/internal/camera/manager.go` mockMode / mockCamera
- `server/internal/device/manager.go` mockMode / mockCount
**新增**
- `server/internal/api/ws/origin.go` CheckOrigin helper~30
- `server/internal/api/middleware_test.go` TestIsAllowedOrigin~50
### 2.2 `server/web/out/`go:embed 的 Next.js SPA實際源碼在 `frontend/`)— 88% 沿用
**完全不動**
- `frontend/src/app/dashboard/` / `devices/` / `models/` / `workspace/` 業務頁面
- `frontend/src/components/device/` / `model/` / `inference/` 業務元件
- `frontend/src/lib/api/` API client
- `frontend/src/stores/` `camera-store.ts` `startFromUrl`
**修改**
- `frontend/src/components/camera/source-selector.tsx` URL tab擴充副檔名
- `frontend/src/stores/camera-store.ts` `startFromUrl`
- `frontend/src/app/settings/page.tsx` Mock 模式切換
- `frontend/src/app/layout.tsx` `<ServerOfflineOverlay />` + `<BootIdWatcherMount />`
- `frontend/src/lib/i18n/{types,zh-TW,en}.ts` 4 yt-dlp keys + 4 Mock keys + `noDevices` 文案 + 新增 `serverOffline` 區塊
**新增**
- `frontend/src/stores/system-store.ts`~40
- `frontend/src/hooks/use-boot-id-watcher.ts`~70
- `frontend/src/components/server-offline-overlay.tsx`~90
- `frontend/src/components/boot-id-watcher-mount.tsx`~10
### 2.3 `visiona-local/app.go` — 85% 沿用
**完全不動** v1 邏輯
- `startup()` data dir migration / single-instance lock / IPC server / seed user data dir
- `shutdown()` watchCancel / ipcListener.Close / releaseLock
- `reportFatal()` + `showNativeError()` 三平台原生對話框保留給 startup 致命錯誤
- `ensurePythonRuntime()` / `findSystemPython()` / `ensureBundledPython()` 完整 Python 雙策略
- `ensureDriverInstalled()` / `markDriverInstalled()` / `InstallKneronDriver()` Kneron WinUSB driver 安裝
- `locateServerBinary()` / `pickPort()` / `waitHealthy()` / `writeIPCPort()` 這些 helper 不動
- `seedUserDataDir()` 首次啟動的資料 seed
- `configureSysProcAttr()` / `openBrowser()` 跨平台封裝
**修改**
- 83 `mockMode` 欄位新增 `ctrl` / `logBuf` / `prefs` / `startupPipeline` / `pipelineCancelFn` 欄位R5-D3 規則每次 Start 都開瀏覽器不需 per-session flag詳見 `server-lifecycle.md` §11.5
- 119-120 VISIONA_MOCK env 讀取
- 313-326 `GetBootstrapStatus` / `setBootstrapStatus` / `bootstrapStatus`
- 425-564 `startServer()` 改名為 `startServerV2()`內容改為
- `StdoutPipe / StderrPipe` 而非 file
- 啟動兩個 `logPump` goroutine
- mockMode 相關條件全砍
- `--mock` arg 拿掉
- 571-610 `watchServer()` `reportFatal + os.Exit` 改為 `ctrl.setState(Error)` + event emit
**新增 bindings**13 method `v2/control-panel.md` §4.2
- `StartServer` / `StopServer` / `RestartServer`
- `GetServerStatus`改版/ `GetRecentLogs` / `ClearLogs`
- `GetSystemInfo`
- `OpenInBrowser` / `RevealLogsFolder` / `ExportLog`
- `GetPreferences` / `SetPreferences`
- `RestartStartupSequence`v2.1Startup Error state Retry 按鈕 `startup-pipeline.md` §9
### 2.4 `visiona-local/` 新增檔案
- `visiona-local/server_control.go`~250 )— ServerController + startServerV2 + logPump
- `visiona-local/log_buffer.go`~120 )— LogBuffer ring buffer
- `visiona-local/preferences.go`~60 )— JSON load/save
### 2.5 `visiona-local/main.go` — 修改 < 5 行
新增 `OnBeforeClose` handler其餘不動
### 2.6 `visiona-local/frontend/` — 改寫
原本M7-B `index.html` 25 + `app.js` 74 + `style.css` 112 = 211 splash
v2
- `index.html` ~80 控制台 layout
- `app.js` ~130 init + binding 呼叫 + event 訂閱
- `style.css` ~300 控制台樣式 + dark/light tokens
- 新增 components/×4 + i18n/×3 + icons/×6 + wailsjs/自動生成
**概念沿用**ES module + `import` wailsjs + `EventsOn` 訂閱模式——v1 M7-B splash 就是這樣寫的v2 延續這個模式只是元件更多
**程式碼沿用**0三個檔案的內容全部重寫但這三個檔案加起來才 211 重寫 2 天是合理的
### 2.7 `installer/` — 98% 沿用
- `installer/windows/visiona-local.iss` 1 yt-dlp+ 2 ffprobe + license
- `installer/linux/build-appimage.sh` 1 yt-dlp for loop
- macOS `dmgbuild` 路徑完全不動dmg 是從 `visiona-local/build/bin/visiona-local.app` 直接建ffmpeg/ffprobe 已透過 payload-macos 置入
### 2.8 `Makefile` — 85% 沿用
**新增 target**`vendor-ffmpeg-macos-build`~50
**修改**
- `vendor-ffmpeg` 改為驗證~10
- `vendor-ffmpeg-windows` URL + Python zipfile 提取清單擴充為 ffmpeg + ffprobe + LICENSE
- `vendor-ffmpeg-linux` URL + 解壓邏輯 + 加抓 ffprobe
- `payload-macos / payload-windows / payload-linux` 全部改為 `cp ffmpeg + ffprobe` `cp yt-dlp`
**刪除**
- `vendor-ytdlp` / `vendor-ytdlp-windows` / `vendor-ytdlp-linux` 三個 target
- 三個 `YTDLP_URL_*` 變數
- `vendor-sync` 中的 `vendor-ytdlp` 依賴
### 2.9 `scripts/bootstrap-*.sh/.ps1` — 99% 沿用
只改刪 yt-dlp 參照其他完全不動
### 2.10 `vendor/` 目錄
| 子目錄 | v1 | v2 | 備註 |
|-------|----|----|------|
| `vendor/python/` | vendor不進 git| 不變 | - |
| `vendor/wheels/` | vendor | 不變 | - |
| `vendor/ffmpeg/darwin/` | vendor不進 gitGPL build| **目錄改名** `vendor/ffmpeg/macos/` | gitLGPL build |
| `vendor/ffmpeg/windows/` | vendor | 同路徑LGPL build | - |
| `vendor/ffmpeg/linux/` | vendor | 同路徑LGPL build | - |
| `vendor/yt-dlp/` | vendor | **整個刪除** | R5-7 |
---
## 3. 文字統計估算
| 類別 | 行數 |
|------|------|
| v2 新增 Go 程式碼 | ~600 server_control.go + log_buffer.go + preferences.go + boot-id handler + CORS middleware + origin.go + middleware_test.go |
| v2 新增 JS/TS 程式碼 | ~700 Wails 控制台 5 JS + 6 SVG + 2 JSON + frontend Offline Overlay 4 |
| v2 修改 Go 程式碼 | ~400 diff |
| v2 修改 TS 程式碼 | ~200 diff |
| v2 修改 Makefile / scripts / installer | ~100 diff |
| v2 刪除 Go 程式碼 | ~500 mock_driver.go 183 + mock_camera.go 95 + camera_handler.go yt-dlp 160 + video_source.go yt-dlp 50 |
| v2 刪除 TS 程式碼 | ~80 source-selector URL tab 70 + camera-store startFromUrl 30 - 扣除同 section 重複計算 |
| v2 刪除 Makefile / scripts | ~60 |
**總 diff 量**~2600 新增 + 修改 + 刪除
**總 repo 規模**~40k LoC
**diff 比例**~6.5%
換算成**淨沿用率**~93%落在三方共識的85-95%」區間上半部
---
## 4. 為什麼沿用率這麼高
M1-M7 的投資集中在打包這件事上vendor 機制dmg/iss/AppImagePython runtime 雙策略single-instancedata dir migrationKneron driver installer這些**完全不動**。
v2 refactor 集中在
1. 把業務 UI Wails WebView 搬到瀏覽器純移動Next.js 源碼不動
2. Wails 視窗內容從 splash 換成控制台全新 UI但只有 ~700 行程式碼
3. 砍兩個功能yt-dlp, Mock)— 都是局部刪除不觸及核心架構
4. ffmpeg 授權 vendor 不動執行邏輯
**等於說 v2 是在 v1 基礎上做「加工」,不是推倒重來**原本這個 refactor 有可能變成丟掉重做的惡夢 M7-B 已經把 Next.js UI Wails binding 強耦合解開變成純 HTTP少了這個前置條件 v2 真的會很痛
---
## 5. 被保留但可能失去商業意義的部分
這些 v2 不砍但隨 R5 決策的不同方向它們的重要性變低了
- **Python runtime 雙策略 + PBS 內嵌M3 work** 仍然需要Kneron inference 必須有 Python保留
- **驗證 driver installerM4 Windows WinUSB** 仍然需要保留
- **預置 .nef 模型**8 73 MB 仍然需要保留
- **Kneron KL520/KL720 韌體檔案** 仍然需要最近才補上)。保留
- **splash 進度訊息 `setBootstrapStatus`** 砍掉R5 Wails 視窗是控制台沒有loading階段Start async 不阻塞 UI)。
---
**結論**v2 refactor 的沿用率接近 93%剩下的 7% diff 主要是新增控制台 UI 與整合 boot-id 機制總工時 ~10 人天是合理估算