local-tool/: visionA-local desktop app
- M1: Wails shell + Go server + Next.js UI + Mock mode (macOS dmg ready)
- M2: i18n (zh-TW/en) + Settings 4-tab refactor
- M3: Embedded Python 3.12 runtime (python-build-standalone) + KneronPLUS wheels
- M4: Windows Inno Setup script (build on Windows runner)
- M5: Linux AppImage script + udev rule (build on Linux runner)
- M6: ffmpeg (GPL, pending legal review) + yt-dlp bundled
- Lifecycle: watchServer health check, fatal native dialog,
Wails IPC raise endpoint, stale process cleanup
.autoflow/: full PRD / Design Spec / Architecture / Testing docs
(4 rounds tri-party discussion + cross review)
.github/workflows/: macOS / Windows / Linux build CI
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
217 lines
11 KiB
Markdown
217 lines
11 KiB
Markdown
# M1-3 Review — 改寫 main/config/router + 清 import + module rename
|
||
|
||
- 審查者:Reviewer Agent
|
||
- 日期:2026-04-10
|
||
- 審查對象:`/Users/jimchen/visionA/local-tool/server/`
|
||
- 依據文件:`api-endpoints.md §4`、`removed-code.md §3~§6`、`M1-2-review.md`
|
||
|
||
## 結論:✅ 通過
|
||
|
||
M1-3 的所有目標達成:module 已改名為 `visiona-local/server`、四個要刪的檔案全數刪除、CLI flag 對齊規格、router/config/main/handlers/test 全部清乾淨,`go build ./...` 與 `go vet ./...` 一次通過。可以直接進入 M1-5。
|
||
|
||
---
|
||
|
||
## 檢查清單
|
||
|
||
### A. 要刪除的檔案(removed-code.md §2)
|
||
|
||
| 檔案 | 狀態 |
|
||
|------|------|
|
||
| `internal/api/handlers/cluster_handler.go` | ✅ 已刪除 |
|
||
| `internal/api/ws/flash_ws.go` | ✅ 已刪除 |
|
||
| `internal/api/ws/cluster_flash_ws.go` | ✅ 已刪除 |
|
||
| `internal/api/ws/cluster_inference_ws.go` | ✅ 已刪除 |
|
||
|
||
剩餘 `internal/api/ws/` 下只剩:`device_events_ws.go`、`hub.go`、`inference_ws.go`、`server_logs_ws.go`,符合 api-endpoints.md 中保留的 WebSocket 清單。
|
||
|
||
剩餘 `internal/api/handlers/` 下只剩:`camera_handler.go`、`device_handler.go`、`model_handler.go`、`model_upload_handler.go`、`system_handler.go`,乾淨。
|
||
|
||
### B. Module 改名
|
||
|
||
| 檢查項 | 結果 |
|
||
|-------|------|
|
||
| `go.mod` module 行 | ✅ `module visiona-local/server` |
|
||
| `grep -rn "edge-ai-platform" server/ --include="*.go"` | ⚠️ 僅命中 `driver/kneron/detector.go` L32-33 兩處**字串 literal**(`~/.edge-ai-platform/venv/...`),**非 import path** |
|
||
| 全檔 `edge-ai-platform/` import | ✅ 0 處 |
|
||
|
||
`detector.go` 內的路徑字串是掃描使用者家目錄找既有 Python venv 的 fallback 路徑,不是 module import,**符合 M1-3 要求**(僅 import 需更新)。列為 🟢 備註:等 installer / python runtime 重設計時再調整這兩個 legacy 路徑。
|
||
|
||
### C. CLI flag 對齊(config.go)
|
||
|
||
| Flag | 規格 | 實際 | 結果 |
|
||
|------|------|------|------|
|
||
| `--mock` | 必須 | ✅ bool | ✅ |
|
||
| `--dev` | 必須 | ✅ bool | ✅ |
|
||
| `--port` | 必須 | ✅ int,default 3721 | ✅ |
|
||
| `--data-dir` | 必須 | ✅ string | ✅ |
|
||
| `--python-mode` | 必須 | ✅ string(auto/bundled/system) | ✅ |
|
||
| `--mock-camera` | 保留 | ✅ bool | ✅ 合理 |
|
||
| `--mock-devices` | 保留 | ✅ int | ✅ 合理 |
|
||
| `--log-level` | 保留 | ✅ string | ✅ 合理 |
|
||
| `--model-dir` | 保留 | ✅ string | ✅ 合理 |
|
||
| `--host` | 保留(強制覆寫) | ✅ string,default 127.0.0.1 | ✅ 見 D |
|
||
| `--relay-url` | **必砍** | ❌ 不存在 | ✅ |
|
||
| `--relay-token` | **必砍** | ❌ 不存在 | ✅ |
|
||
| `--tray` | **必砍** | ❌ 不存在 | ✅ |
|
||
| `--gui` | **必砍** | ❌ 不存在 | ✅ |
|
||
|
||
`PythonMode` 定義為 typed string + 三個常數 + 預設 fallback,乾淨且符合 api-endpoints.md §5.2 未來要擴充的規劃。
|
||
|
||
### D. Host 強制 127.0.0.1
|
||
|
||
`config.go` L48-49:
|
||
```go
|
||
// 強制 localhost-only
|
||
cfg.Host = "127.0.0.1"
|
||
```
|
||
✅ **確實 override**。在 `flag.Parse()` 之後無條件覆寫,即使使用者傳 `--host 0.0.0.0` 也會被改回 `127.0.0.1`。防呆正確。
|
||
|
||
### E. Router 乾淨度(api-endpoints.md §4)
|
||
|
||
| 檢查項 | 結果 |
|
||
|-------|------|
|
||
| 簽章移除 `clusterMgr`、`flashSvc`、`relayToken` 三參數 | ✅ |
|
||
| `/api/clusters/*` routes | ✅ 0 處 |
|
||
| `/api/devices/:id/flash` | ✅ 已移除 |
|
||
| `/api/system/update-check` | ✅ 已移除 |
|
||
| `/auth/token` + OPTIONS | ✅ 已移除 |
|
||
| `/ws/devices/:id/flash-progress` | ✅ 已移除 |
|
||
| `/ws/clusters/*` | ✅ 0 處 |
|
||
| 保留的 REST routes 與 §4 清單逐項比對 | ✅ 完全符合 |
|
||
| 保留的 WS routes(devices/events, devices/:id/inference, server-logs) | ✅ 三條都在 |
|
||
| grep `clusterMgr|flashSvc|/api/clusters|/auth/token|update-check|/flash-progress|CheckUpdate` | ✅ 0 matches |
|
||
|
||
### F. main.go 清理(removed-code.md §3, §4)
|
||
|
||
| 檢查項 | 結果 |
|
||
|-------|------|
|
||
| imports 內無 `cluster/flash/tunnel/hwid` | ✅ |
|
||
| 沒有 `tunnelClient`、`clusterMgr`、`flashSvc`、`relayToken` 變數 | ✅ |
|
||
| 沒有 `relayWebURL`、`openBrowser` 函式 | ✅ |
|
||
| `NewSystemHandler` 呼叫移除 `giteaURL` | ✅ L147 只傳 `Version, BuildTime, restartFn` |
|
||
| `NewRouter` 呼叫移除 3 個參數 | ✅ L150 簽章對齊 |
|
||
| 保留 graceful shutdown + self-restart exec | ✅ |
|
||
| 保留 `killExistingProcess` | ✅ 合理(解決 port 被占用) |
|
||
|
||
### G. device_handler.go
|
||
|
||
| 檢查項 | 結果 |
|
||
|-------|------|
|
||
| 移除 `internal/flash` import | ✅ |
|
||
| 移除 `FlashDevice` handler | ✅(檔內已無) |
|
||
| 建構子 `NewDeviceHandler` 不再注入 `flashSvc` | ✅ 只收 deviceMgr/inferenceSvc/wsHub |
|
||
| 保留 Connect/Disconnect/Scan/List/Get/Start/Stop Inference | ✅ |
|
||
|
||
### H. system_handler.go
|
||
|
||
| 檢查項 | 結果 |
|
||
|-------|------|
|
||
| 移除 `internal/update` import | ✅ |
|
||
| 移除 `CheckUpdate` handler 與 `giteaURL` 欄位 | ✅ |
|
||
| 保留 Health/Info/Metrics/Deps/Restart | ✅ |
|
||
|
||
**備註**:api-endpoints.md §1.1 有要求 `/api/system/info` 擴充 `actual_port`、`mode`、`python_mode` 三個欄位,以及新增 `/api/system/mode`、`/api/system/python-runtime`、`/api/system/port` 三個 endpoint。目前 `Info()` 只回傳 `version/platform/uptime/goVersion`,新 endpoint 也尚未新增。**但這些是後續 milestone(M2 以後)的工作,不屬於 M1-3 的範圍**(M1-3 的任務是 *清理*,不是新增功能)。列入待辦追蹤,不影響本次通過。
|
||
|
||
### I. api_e2e_test.go
|
||
|
||
| 檢查項 | 結果 |
|
||
|-------|------|
|
||
| 移除 `cluster` / `flash` import | ✅ |
|
||
| 移除 cluster / flash / auth 測試案例 | ✅ 檔內無殘留 |
|
||
| `setupTestServer` 呼叫 `NewRouter` 簽章對齊 | ✅ L50-53 |
|
||
| 保留的測試:Health、DeviceWorkflow、DeviceScan、ModelList、ConnectNonExistent、MultiDeviceIsolation | ✅ |
|
||
|
||
---
|
||
|
||
## Build 驗證
|
||
|
||
自己實際跑的結果:
|
||
|
||
```bash
|
||
cd /Users/jimchen/visionA/local-tool/server
|
||
go build ./... → exit 0(無任何輸出)
|
||
go vet ./... → exit 0(無任何輸出)
|
||
```
|
||
|
||
✅ **完全乾淨**,無 compile error、無 vet warning。
|
||
|
||
---
|
||
|
||
## 針對審查重點 6、7、8 的判斷
|
||
|
||
### 6. CORS 中的 `X-Relay-Token` header(middleware.go L19)
|
||
|
||
```go
|
||
c.Header("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Relay-Token")
|
||
```
|
||
|
||
**判斷:🟢 Minor(無害但可清)**。
|
||
|
||
- `relay-url/relay-token` flag、`RelayToken` config、`/auth/token` endpoint 都已移除,前端也不會再送這個 header。保留在 CORS allow-list 屬於**無害殘留**(瀏覽器只會放行,不會主動產生請求)。
|
||
- Backend 的判斷「保留但可清」合理。建議 M1-3 收尾或 M1-5 之前順手改成 `"Content-Type, Authorization"`,避免將來審計時引起疑問,但不阻擋進入 M1-5。
|
||
|
||
### 7. `internal/driver/` 裡的 `Flash` / `FlashProgress`
|
||
|
||
讀了 `internal/driver/interface.go`、`driver/kneron/kl720_driver.go:397` 附近、`driver/mock/mock_driver.go:68`,結論:
|
||
|
||
- `DeviceDriver` 介面的 `Flash(modelPath, progressCh)` 是**把 .nef 模型載入到 Kneron 裝置**的方法,對應 Kneron SDK 的 model load API(`kl720_driver.go` 的註解明確寫「models can be freely reloaded」,並搭配 `scripts/kneron_bridge.py`)。
|
||
- 這跟已刪除的 `internal/flash` package(韌體燒錄服務)完全是兩回事 —— 後者是 firmware flashing(KL720 firmware binary → device flash memory),前者是 model loading(把已訓練好的 .nef 模型丟到裝置的 RAM/ flash 區)。
|
||
- 既然 `/api/devices/:id/inference/start` 仍然需要先把模型載上去,這個 `Flash` 方法是**必要的**,不能刪。
|
||
|
||
**Backend 的判斷正確,列為 ✅ 無問題**。唯一 cosmetic 的點:未來可考慮把 `Flash` / `FlashProgress` 改名為 `LoadModel` / `LoadProgress` 避免語意混淆,但屬於重構,不在 M1-3 範圍。
|
||
|
||
### 8. `web/out/placeholder.txt` 解法
|
||
|
||
確認:
|
||
- `web/embed.go` 用 `//go:embed all:out` 指向 `out/` 目錄
|
||
- 因為 frontend 尚未 build,原本 `out/` 不存在會導致 `go build` 失敗(M1-2 review 已預警)
|
||
- M1-3 在 `out/` 下放了一個 `placeholder.txt`,讓 embed target 存在,build 即可通過
|
||
|
||
**判斷:🟢 合理的最小修補**。`all:` 前綴會一起 embed 底線或點開頭的檔案,但 placeholder.txt 不影響實際 static serving(因為等 frontend build 完就會覆蓋整個 `out/`)。簡單、正確、不擾動 embed.go。
|
||
|
||
---
|
||
|
||
## 問題
|
||
|
||
### 🔴 Critical
|
||
無。
|
||
|
||
### 🟡 Major
|
||
無。
|
||
|
||
### 🟢 Minor / Suggestion
|
||
|
||
1. **`middleware.go` L19 的 `X-Relay-Token`** — 無害殘留,建議之後收尾時清掉:
|
||
```go
|
||
c.Header("Access-Control-Allow-Headers", "Content-Type, Authorization")
|
||
```
|
||
2. **`driver/kneron/detector.go` L32-33 的 `.edge-ai-platform` 字串** — legacy 路徑字串,不是 import path,不影響 M1-3,但未來 installer/python runtime 重設計時應跟著改名(例如改為 `.visiona-local/venv/...`)。
|
||
3. **`/api/system/info` 尚未擴充 `actual_port` / `mode` / `python_mode`,以及 `/api/system/mode`、`/api/system/python-runtime`、`/api/system/port` 三個新 endpoint 尚未實作** — 屬於 M2 以後的工作(api-endpoints.md §1.1, §5),**不阻擋 M1-3 / M1-5**,但要記得追蹤。
|
||
4. **`Flash` / `FlashProgress` 命名語意模糊** — 未來重構時可考慮改為 `LoadModel` / `LoadProgress`,非必要。
|
||
|
||
### 優點
|
||
|
||
- Module rename 徹底、Go import 0 殘留,找不到任何 `edge-ai-platform/` import path。
|
||
- Router 與 api-endpoints.md §4 的規格**逐行對齊**,連註解順序都維持原作者風格。
|
||
- `config.go` 的 Host 強制 override 防呆寫得乾淨(flag 還是允許傳入便於開發者習慣,但結尾強制覆寫,二者兼顧)。
|
||
- `PythonMode` 用 typed string + fallback 預設值,為 M2 的 runtime 切換邏輯鋪好路。
|
||
- `api_e2e_test.go` 精簡得宜,保留了關鍵 workflow 測試,build + 編譯都乾淨(雖然本次沒跑 `go test`,但 vet 通過代表 test file 的 symbol 引用都對)。
|
||
- `killExistingProcess` 保留是明智的 — 對本地 daemon 很實用,開發時切換 binary 不會被 stale process 擋住。
|
||
|
||
---
|
||
|
||
## 可以進入 M1-5(build Go binary)嗎?
|
||
|
||
✅ **可以直接進入 M1-5**。
|
||
|
||
理由:
|
||
1. `go build ./...` 與 `go vet ./...` 在 reviewer 端重現,皆一次通過。
|
||
2. 所有刪除清單、module rename、router 簽章、CLI flag、Host 強制覆寫都對齊規格。
|
||
3. `web/out/placeholder.txt` 已解決 M1-2 預警的 embed 編譯障礙,`go build` 不再卡在 embed target 缺失。
|
||
4. 剩餘的 🟢 項目(CORS header、detector 路徑字串、system info 擴充)都不影響 binary 能不能 build,也不影響 binary 跑起來的核心功能,可延後處理。
|
||
|
||
建議 M1-5 時:
|
||
- 跑一次 `go test ./...` 當作額外保險(本次 review 未跑,僅跑 build + vet)。
|
||
- 驗證 `go build -o visiona-local-server .` 產出 binary 並直接執行一次,確認 `--mock --dev --port 3721` 能成功起 server。
|
||
- 順手把 🟢#1(CORS header)清掉,避免審計疑問。
|