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>
11 KiB
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:
// 強制 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 |
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 驗證
自己實際跑的結果:
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)
c.Header("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Relay-Token")
判斷:🟢 Minor(無害但可清)。
relay-url/relay-tokenflag、RelayTokenconfig、/auth/tokenendpoint 都已移除,前端也不會再送這個 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/flashpackage(韌體燒錄服務)完全是兩回事 —— 後者是 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
middleware.goL19 的X-Relay-Token— 無害殘留,建議之後收尾時清掉:c.Header("Access-Control-Allow-Headers", "Content-Type, Authorization")driver/kneron/detector.goL32-33 的.edge-ai-platform字串 — legacy 路徑字串,不是 import path,不影響 M1-3,但未來 installer/python runtime 重設計時應跟著改名(例如改為.visiona-local/venv/...)。/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,但要記得追蹤。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。
理由:
go build ./...與go vet ./...在 reviewer 端重現,皆一次通過。- 所有刪除清單、module rename、router 簽章、CLI flag、Host 強制覆寫都對齊規格。
web/out/placeholder.txt已解決 M1-2 預警的 embed 編譯障礙,go build不再卡在 embed target 缺失。- 剩餘的 🟢 項目(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)清掉,避免審計疑問。