jim800121chen 5a8d2797c2 chore(local-tool): rename local_tool → local-tool, add linux/windows bootstrap scripts
- 統一目錄名為 local-tool(連字號),修正所有文件中殘留的底線版本
- 新增 scripts/bootstrap-linux.sh 與 scripts/bootstrap-windows.ps1
  一鍵安裝依賴(Go/Node/pnpm/Wails/MSYS2)並執行 payload + installer build

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 23:23:29 +08:00

262 lines
11 KiB
Markdown
Raw Permalink 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.

# Code Reuse Plan — visionA-local
> 從 `/Users/jimchen/Innovedus/edge-ai-platform/edge-ai-platform/` 取用程式碼的完整對照表。
> 三種策略:**直接複製**、**改寫**、**新寫**。
---
## 1. 總體比例(預估)
- **直接複製**~60% LOC
- **改寫**~20%
- **新寫**~20%
- **刪除**:獨立計算,見 [`removed-code.md`](./removed-code.md)
## 2. 目錄層級策略
| Fromedge-ai-platform | Tolocal-tool | 策略 | 備註 |
|------------------------|----------------|------|------|
| `server/main.go` | `server/main.go` | **改寫** | 移除 cluster / tunnel / relay / hwid / gitea 邏輯 |
| `server/go.mod` | `server/go.mod` | **改寫** | 移除不再需要的 importsmodule name 可改為 `visiona-local` |
| `server/go.sum` | `server/go.sum` | **改寫** | 跟著 go.mod 重新生成 |
| `server/internal/api/router.go` | `server/internal/api/router.go` | **改寫** | 見 [`api-endpoints.md`](./api-endpoints.md) §4 |
| `server/internal/api/middleware.go` | 同路徑 | **直接複製** | |
| `server/internal/api/handlers/` | 同路徑 | **部分複製 / 改寫** | 見下方 §3.1 |
| `server/internal/api/ws/` | 同路徑 | **部分複製 / 改寫** | 見下方 §3.2 |
| `server/internal/api/api_e2e_test.go` | 同路徑 | **改寫** | 刪除 cluster / flash / auth test case |
| `server/internal/camera/` | 同路徑 | **直接複製** | 除了 ffmpeg 路徑查找改為「優先 bundled」見 §3.4 |
| `server/internal/config/config.go` | 同路徑 | **改寫** | 見 §3.3 |
| `server/internal/deps/` | 同路徑 | **改寫** | 移除 update / relay 相關檢查 |
| `server/internal/device/` | 同路徑 | **直接複製** | 核心裝置管理 |
| `server/internal/driver/` | 同路徑 | **直接複製** | |
| `server/internal/inference/` | 同路徑 | **直接複製** | |
| `server/internal/model/` | 同路徑 | **直接複製** | |
| `server/internal/flash/` | ❌ **刪除** | 使用者決策 Q9 |
| `server/internal/cluster/` | ❌ **刪除** | 使用者決策 |
| `server/internal/tunnel/` | ❌ **刪除** | 使用者決策 |
| `server/internal/update/` | ❌ **刪除** | 使用者決策 Q6 |
| `server/pkg/logger/` | 同路徑 | **直接複製** | |
| `server/pkg/hwid/` | ❌ **刪除** | 只給 relay 用的 |
| `server/cmd/relay-server/` | ❌ **刪除** | 不需要 relay |
| `server/tray/` | ❌ **刪除** | 使用者決策 Q-A=A3砍掉 tray省跨平台圖資產與 Wails tray 踩坑 |
| `server/scripts/kneron_bridge.py` | `server/scripts/kneron_bridge.py` | **直接複製** | |
| `server/scripts/requirements.txt` | 同路徑 | **直接複製** | |
| `server/scripts/update_kl720_firmware.py` | ❌ **刪除** | flash 已砍 |
| `server/scripts/firmware/` | ❌ **刪除** | 同上 |
| `server/data/models.json` | 同路徑 | **直接複製** | |
| `server/data/nef/kl520/` | 同路徑 | **直接複製** | 全部預置模型 |
| `server/data/nef/kl720/` | 同路徑 | **直接複製** | |
| `server/web/` | 同路徑(空目錄) | **直接複製** | go:embed 掛載點 |
| `frontend/` | `frontend/` | **改寫** | M1 就要清乾淨:刪除 cluster / relay / tunnel 相關頁面、元件、store、API client`pnpm build` 通過且 UI 乾淨(使用者決策 Q-C=C2 |
| `installer/` | `visiona-local/` | **改寫(改名 + 精簡)** | 見 §3.5 |
| `installer/wheels/` | `vendor/wheels/``visiona-local/payload/scripts/wheels/` | **直接複製** | KneronPLUS wheel |
| `installer/drivers/` | `vendor/drivers/``visiona-local/payload/drivers/` | **直接複製** | Windows WinUSB |
| `installer/payload/` | `visiona-local/payload/` | **結構沿用,內容重建** | 由 Makefile payload target 重新 stage |
| `Makefile` | `Makefile` | **改寫** | 見 [`build-pipeline.md`](./build-pipeline.md) |
| `docker/` | ❌ **刪除** | |
| `scripts/deploy-*.sh` | ❌ **刪除** | |
| `scripts/kneron_detect.py` | `server/scripts/kneron_detect.py` | **直接複製** | installer 裝置偵測用 |
| `tools/` | 視檔案決定 | **多數刪除** | 只保留開發相關的 script |
| `docs/` | ❌ **刪除**(之後重寫) | | |
## 3. 需要改寫的檔案細節
### 3.1 `server/internal/api/handlers/`
| 檔案 | 策略 | 動作 |
|------|-----|-----|
| `system_handler.go` | 改寫 | 移除 `CheckUpdate``giteaURL` 參數;刪除 `update-check` handler |
| `model_handler.go` | 直接複製 | |
| `model_upload_handler.go` | 直接複製 | |
| `device_handler.go` | 改寫 | 移除 `FlashDevice` handler 與 `flashSvc` 注入 |
| `camera_handler.go` | 直接複製 | 但 `StartFromURL` 內部的 yt-dlp 路徑查找改為 bundled見 §3.4 |
| `cluster_handler.go` | ❌ 刪除 | |
### 3.2 `server/internal/api/ws/`
| 檔案 | 策略 |
|------|-----|
| `hub.go` | 直接複製 |
| `device_events.go` | 直接複製 |
| `server_logs.go` | 直接複製 |
| `inference.go` | 直接複製 |
| `flash_progress.go` | ❌ 刪除 |
| `cluster_*.go` | ❌ 刪除 |
### 3.3 `server/internal/config/config.go` 改寫版
```go
package config
import (
"flag"
"fmt"
"os"
"path/filepath"
)
type Config struct {
Port int
Host string
MockMode bool
MockCamera bool
MockDeviceCount int
LogLevel string
DevMode bool
PythonBin string // 新增:由 Wails app 傳入
ScriptsDir string // 新增
DataDir string // 新增
// ❌ 以下全部移除:
// RelayURL, RelayToken, GUIMode, GiteaURL
}
func Load() *Config {
cfg := &Config{}
flag.IntVar(&cfg.Port, "port", 3721, "Server port")
flag.StringVar(&cfg.Host, "host", "127.0.0.1", "Server host (always localhost)")
flag.BoolVar(&cfg.MockMode, "mock", false, "Enable mock device driver")
flag.BoolVar(&cfg.MockCamera, "mock-camera", false, "Enable mock camera")
flag.IntVar(&cfg.MockDeviceCount, "mock-devices", 1, "Number of mock devices")
flag.StringVar(&cfg.LogLevel, "log-level", "info", "Log level")
flag.BoolVar(&cfg.DevMode, "dev", false, "Dev mode")
flag.StringVar(&cfg.PythonBin, "python", "", "Path to python3 interpreter")
flag.StringVar(&cfg.ScriptsDir, "scripts-dir", "", "Path to scripts directory")
flag.StringVar(&cfg.DataDir, "data-dir", "", "Path to data directory")
flag.Parse()
// 合理 defaultdev 模式)
if cfg.ScriptsDir == "" {
cfg.ScriptsDir = filepath.Join(".", "scripts")
}
if cfg.DataDir == "" {
cfg.DataDir = filepath.Join(".", "data")
}
return cfg
}
func (c *Config) Addr() string {
return fmt.Sprintf("%s:%d", c.Host, c.Port)
}
```
### 3.4 ffmpeg / yt-dlp 路徑查找
新增 `server/internal/camera/binaries.go`
```go
package camera
import (
"os"
"path/filepath"
"runtime"
)
// FfmpegBinary returns the ffmpeg binary to use, preferring bundled over PATH.
func FfmpegBinary() string {
if p := os.Getenv("VISIONA_FFMPEG"); p != "" {
return p
}
if p := bundled("ffmpeg"); p != "" {
return p
}
return "ffmpeg" // PATH fallback
}
func YtDlpBinary() string {
if p := os.Getenv("VISIONA_YTDLP"); p != "" {
return p
}
if p := bundled("yt-dlp"); p != "" {
return p
}
return "yt-dlp"
}
func bundled(name string) string {
exe, err := os.Executable()
if err != nil {
return ""
}
dir := filepath.Dir(exe)
candidate := filepath.Join(dir, name)
if runtime.GOOS == "windows" {
candidate += ".exe"
}
if _, err := os.Stat(candidate); err == nil {
return candidate
}
return ""
}
```
### 3.5 `installer/app.go` → `visiona-local/app.go` 改寫
原 894 行,改寫要點:
**移除的欄位 / 函式**
- `relayURL`, `relayToken`, `dashboardURL`
- `GetDashboardURL`, `GenerateToken`, `OpenBrowser` 中任何跟 relay 有關的邏輯
- `stepInstallUSBDriver` 需要保留,但只給 Windows 用flash driver 已砍但 WinUSB 還需要)
- `stepInstallFfmpeg` **改寫**:不去系統 PATH 找,而是從 payload/bin 解壓 bundled ffmpeg
**新增的欄位 / 函式**
- `pythonMode``bundled` / `system` / `auto`
- `stepSetupPythonStandalone`(解壓內嵌 python-build-standalone
- `stepSetupPythonSystem`fallback 到系統 python沿用舊的 `setupPythonVenv` 邏輯)
- `stepInstallWheelsOffline`(用 `pip install --no-index --find-links`
- `i18n` translator 注入
- `launchServer` / `stopServer` / `watchServer`(見 [`tray-and-lifecycle.md`](./tray-and-lifecycle.md) §4
- `single-instance lock`
**保留(幾乎不動)**
- `GetSystemInfo`, `BrowseDirectory`, `ValidatePath`
- `stepCreateDir`, `stepExtractBinary`, `stepExtractData`, `stepExtractScripts`
- `DetectHardware`, `parseDetectOutput`
- `extractFile`, `extractDir`
- Platform-specific 檔案(`platform_darwin.go` / `platform_linux.go` / `platform_windows.go`
## 4. 新寫的程式碼
完全新寫的檔案:
### 4.1 Wails app 側
```
visiona-local/
├── python_runtime.go ← python-build-standalone 解壓 + 版本偵測
├── server_launcher.go ← spawn / stop / watch Go server
├── lifecycle.go ← single-instance lock、port picking、cleanup
├── ipc.go ← /ipc/raise 小型 HTTP listener
├── i18n.go ← Translator + locales embed
└── locales/
├── en.json
└── zh-TW.json
```
### 4.2 Build / CI
```
scripts/
├── vendor-sync.sh ← 下載 python-build-standalone、ffmpeg、yt-dlp 到 vendor/
├── build-appimage.sh ← Linux AppImage 打包腳本
├── check-i18n.sh ← 檢查 i18n key 一致性
└── smoke-test.sh ← 安裝後 smoke test
visiona-local-installer.iss ← Inno Setup 腳本
dmg-config.py ← macOS dmgbuild 設定
.github/workflows/release.yml ← CI選配
```
## 5. 搬家步驟(建議執行順序)
1. **建立骨架**`mkdir -p local-tool/{server,frontend,visiona-local,vendor,scripts,dist}`
2. **複製 server core**`cp -r edge-ai-platform/server/{internal/{api,camera,config,deps,device,driver,inference,model},pkg/logger,scripts,data,web} local-tool/server/`
3. **跳過要刪的 package**:不要複製 `cluster``tunnel``flash``update``pkg/hwid``cmd/relay-server``tray`(使用者決策 Q-A砍 tray
4. **改寫 `main.go` + `config.go` + `router.go`**(見上方 §3
5. **改寫 go.mod**`go mod init visiona-local/server``go mod tidy`(會自動清掉 unused imports
6. **驗證 `go build ./...` 通過**
7. **複製 frontend**`cp -r edge-ai-platform/frontend local-tool/frontend`
8. **清理前端 cluster / relay / tunnel UI**(使用者決策 Q-C=C2M1 就要清乾淨,不留到 M2刪除 `src/app/clusters/``src/app/workspace/cluster/``src/components/cluster/``src/components/relay-token-sync.tsx``src/lib/api/clusters.ts``src/lib/api/tunnel.ts`(若有)、`src/lib/api/update.ts`(若有),修改 `sidebar.tsx` 移除 Clusters 導航項、`page.tsx` 移除 cluster stat、`settings/page.tsx` 移除 relay / cluster 區塊。最後驗證 `pnpm build` 通過且 UI 乾淨。
9. **複製 installer**`cp -r edge-ai-platform/installer local-tool/visiona-local`,改 `main.go` 的 app 名稱與 bundle ID
10. **先跑 M1-12**:全新機器上 installer 能裝起來並跑通 Mock 模式