diff --git a/docs/PRD-Integrated.md b/docs/PRD-Integrated.md index c5631f6..c26d92a 100644 --- a/docs/PRD-Integrated.md +++ b/docs/PRD-Integrated.md @@ -8,8 +8,8 @@ |------|------| | 文件名稱 | 邊緣 AI 開發平台 PRD | | 產品名稱 | (暫未定名,以下稱「本平台」) | -| 版本 | v3.1 | -| 日期 | 2026-03-05 | +| 版本 | v3.2 | +| 日期 | 2026-03-10 | | 狀態 | 更新中 | --- @@ -1225,16 +1225,16 @@ Kneron Dongle Arduino 開發板 非 Kneron 晶片 | **Relay 設定步驟** | 新增 Relay Configuration 步驟:輸入 Relay URL、Relay Token、本地 Port(預設 3721);設定儲存至共用設定檔,供 Launcher 與 server 讀取 | | **i18n 支援** | 安裝程式支援多語系切換(English + 繁體中文),安裝介面右上角提供語言切換器,所有步驟文字與錯誤訊息皆翻譯 | | **設定檔** | `~/.edge-ai-platform/config.json`,安裝程式與 Launcher(系統匣應用)共用同一設定檔,包含 relay URL/token、port、語言偏好、auto-start 設定等 | -| **必要元件(自動安裝)** | edge-ai-server binary(含嵌入式前端)、Python 3 venv + numpy + opencv-python-headless + pyusb、Kneron 韌體檔案(KL520 + KL720)、NEF 預訓練模型、libusb(USB 裝置通訊) | -| **可選元件** | ffmpeg(攝影機/影片串流)、yt-dlp(YouTube 影片下載) | -| **自動依賴解析** | macOS: 自動安裝 Homebrew(若未安裝)→ `brew install libusb python3 ffmpeg`;Windows: 自動下載 Python embedded + libusb DLL,免管理員權限 | +| **必要元件(自動安裝)** | edge-ai-server binary(含嵌入式前端)、Python 3 venv + numpy + opencv-python-headless + pyusb、Kneron 韌體檔案(KL520 + KL720)、NEF 預訓練模型、libusb(USB 裝置通訊)、WinUSB 驅動程式(Windows,自動自簽安裝)、ffmpeg + yt-dlp(媒體工具) | +| **自動依賴解析** | macOS: 自動安裝 Homebrew(若未安裝)→ `brew install libusb python3 ffmpeg yt-dlp`;Windows: 自動下載 Python embedded + libusb DLL + WinUSB driver 自簽安裝 + winget 安裝 ffmpeg/yt-dlp | +| **Windows WinUSB 驅動自動安裝** | 安裝程式自動建立自簽 code signing 憑證(PowerShell `New-SelfSignedCertificate`)→ 安裝至 TrustedPublisher + Root 憑證存儲區 → `pnputil /add-driver /install` 安裝 WinUSB 驅動,支援 KL520(VID_3231&PID_0100)+ KL720(PID_0200, PID_0720),無需使用者手動操作 Zadig | | **即時進度顯示** | 每個安裝步驟獨立顯示進度條 + 狀態文字(下載中 → 解壓中 → 設定中 → 完成),失敗時顯示錯誤訊息 + 重試按鈕 | | **硬體偵測** | 安裝完成後自動掃描 USB Kneron 裝置,顯示偵測到的晶片型號(KL520/KL720)、韌體版本、連線狀態;KL720 KDP legacy 裝置提示一鍵韌體更新 | -| **解除安裝** | 內建解除安裝功能:刪除 server binary + Python venv + 資料檔案 + symlink/PATH,macOS 提供拖曳到垃圾桶 + 深度清理選項,Windows 整合「新增或移除程式」 | +| **解除安裝** | 內建解除安裝功能(Wails 原生確認對話框):停止 server 進程 → 移除 auto-restart 服務 → 刪除 server binary + Python venv + 資料檔案 + scripts + config/logs + symlink/PATH;系統依賴(Python、libusb、Homebrew)保留不動 | | **安裝目錄** | macOS: `~/.edge-ai-platform/`(不需 sudo)、Windows: `%LOCALAPPDATA%\EdgeAIPlatform\`(不需管理員) | | **安裝完成動作** | 自動啟動選項現在註冊 Launcher(系統匣應用程式)而非裸 server 進程;可選擇立即啟動 Launcher + 自動開啟瀏覽器(`http://localhost:3721`)、建立桌面捷徑、設定開機自動啟動 Launcher | | **更新支援** | 偵測現有安裝版本,僅更新 binary + 新模型,保留使用者資料(custom-models、設定檔)與 Python venv | -| **打包產出** | macOS: `EdgeAI-Installer.dmg`(含 .app + 背景圖 + Applications 捷徑)、Windows: `EdgeAI-Setup.exe`(NSIS + Wails 包裝) | +| **打包產出** | macOS: `EdgeAI-Installer.dmg`(含 .app,ad-hoc codesign + xattr quarantine 清除)、Windows: `EdgeAI-Installer.exe`(Wails 單一執行檔) | | **安裝體積** | 完整安裝約 300-400 MB(binary ~10MB + 模型 ~73MB + Python venv ~250MB + firmware ~2MB) | | **UI 設計** | 現代化視覺設計,包含步驟進度指示器、平滑過渡動畫、統一的色彩主題與字體排版 | diff --git a/docs/TDD.md b/docs/TDD.md index a9a9a9f..08753f8 100644 --- a/docs/TDD.md +++ b/docs/TDD.md @@ -7,9 +7,9 @@ | 項目 | 內容 | |------|------| | 文件名稱 | 邊緣 AI 開發平台 TDD | -| 對應 PRD | PRD-Integrated.md v3.1 | -| 版本 | v2.0 | -| 日期 | 2026-03-05 | +| 對應 PRD | PRD-Integrated.md v3.2 | +| 版本 | v2.1 | +| 日期 | 2026-03-10 | | 狀態 | 更新中 | --- @@ -1919,15 +1919,17 @@ func (a *Installer) LaunchServer() error // 啟動 edge-ai-server func (a *Installer) OpenBrowser() error // 開啟瀏覽器 // 解除安裝 API -func (a *Installer) GetInstalledInfo() InstalledInfo // 讀取已安裝版本 + 大小 -func (a *Installer) Uninstall(keepData bool) error // 解除安裝(可選保留資料) +func (a *Installer) ConfirmUninstall() (bool, error) // Wails 原生確認對話框 +func (a *Installer) Uninstall() error // 解除安裝(非同步 goroutine) // 進度回報(透過 Wails Events 推送到前端) -type StepProgress struct { - Step string `json:"step"` // "download", "extract", "python", "libusb", ... - Percent int `json:"percent"` // 0-100 - Message string `json:"message"` // 人類可讀狀態 - Error string `json:"error"` // 錯誤訊息(空=正常) +// 安裝: "install:progress"、解除安裝: "uninstall:progress" +type ProgressEvent struct { + Step string `json:"step"` // "extract", "python", "libusb", "driver", "ffmpeg", ... + Message string `json:"message"` // 人類可讀狀態 + Percent float64 `json:"percent"` // 0-100 + IsError bool `json:"isError"` // 是否為錯誤 + IsComplete bool `json:"isComplete"` // 是否完成 } ``` @@ -2061,19 +2063,47 @@ type StepProgress struct { **安裝步驟實作細節:** -| 步驟 | macOS 實作 | Windows 實作 | -|------|-----------|-------------| -| 下載 binary | `net/http` GET → Gitea release tar.gz(tray-enabled build) | `net/http` GET → Gitea release zip(tray-enabled build) | -| 解壓 | `archive/tar` + `compress/gzip` | `archive/zip` | -| symlink | `os.Symlink()` → `/usr/local/bin/edge-ai-server` (需確認權限) | 加入 User PATH (`os.Setenv` + registry) | -| libusb | `exec.Command("brew", "install", "libusb")` 或內嵌 dylib | 內嵌 `libusb-1.0.dll` 到安裝目錄 | -| Python venv | `exec.Command("python3", "-m", "venv", ...)` | `exec.Command("python", "-m", "venv", ...)` 或內嵌 Python embedded | -| pip install | `venv/bin/pip install numpy opencv-python-headless pyusb` | `venv\Scripts\pip install ...` | -| ffmpeg | `brew install ffmpeg` (可選) | `winget install Gyan.FFmpeg` 或內嵌 (可選) | -| 寫入設定檔 | 寫入 `~/.edge-ai-platform/config.json`(含 port、relay URL/token、語言偏好) | 寫入 `%LOCALAPPDATA%\EdgeAIPlatform\config.json` | -| 硬體偵測 | `exec.Command(python, "kneron_detect.py")` | 同左 | -| 桌面捷徑 | `~/Desktop/EdgeAI.command` | `shell:desktop\EdgeAI.lnk` (COM API) | -| 開機啟動 | `~/Library/LaunchAgents/com.innovedus.edge-ai.plist`(執行 `edge-ai-server --tray`) | `HKCU\...\Run` registry(執行 `edge-ai-server.exe --tray`) | +**安裝步驟(11 步驟,實際實作):** + +| # | 步驟名稱 | 進度% | macOS 實作 | Windows 實作 | +|---|---------|-------|-----------|-------------| +| 1 | Extracting server | 5% | 從 `payload/` embed.FS 解壓 binary 到 `~/.edge-ai-platform/` | 解壓到 `%LOCALAPPDATA%\EdgeAIPlatform\` | +| 2 | Creating symlink | 10% | `os.Symlink()` → `/usr/local/bin/edge-ai-server` | 加入 User PATH (PowerShell `SetEnvironmentVariable`) | +| 3 | Extracting models | 15% | 複製 `data/models.json` + `data/nef/kl520/*.nef` + `data/nef/kl720/*.nef` | 同左 | +| 4 | Extracting scripts | 22% | 複製 `scripts/kneron_bridge.py` + `requirements.txt` + firmware bins | 同左 + 複製 `libusb-1.0.dll` + KneronPLUS .whl | +| 5 | Setting up libusb | 30% | `brew install libusb`(自動安裝 Homebrew 若未安裝) | 複製 `libusb-1.0.dll` 到安裝目錄 | +| 6 | Setting up USB driver | 35% | No-op (macOS 不需要) | **自簽憑證 + pnputil 安裝 WinUSB driver**(見下方詳細流程) | +| 7 | Removing quarantine | 38% | `xattr -cr` 移除 Gatekeeper 隔離標記 | No-op | +| 8 | Setting up Python | 42% | `python3 -m venv` + `pip install -r requirements.txt` | 同左(自動偵測 Python 路徑) | +| 9 | Installing media tools | 78% | `brew install ffmpeg yt-dlp` | `winget install Gyan.FFmpeg` + `winget install yt-dlp.yt-dlp` | +| 10 | Writing config | 85% | 寫入 `~/.edge-ai-platform/config.json`(port、relay、語言) | 寫入 `%LOCALAPPDATA%\EdgeAIPlatform\config.json` | +| 11 | Setting up auto-start | 90% | `launchctl load` + LaunchAgent plist | Registry Run key `HKCU\...\Run\EdgeAIPlatformServer` + 立即啟動 `--gui` | + +**Windows WinUSB Driver 自動安裝流程(Step 6 詳細):** + +``` +1. 從 payload 解壓 driver 檔案到 installDir/drivers/ + ├── kneron_winusb.inf (支援 KL520 + KL720 全系列) + └── amd64/ + ├── WdfCoInstaller01011.dll + └── winusbcoinstaller2.dll + +2. PowerShell New-SelfSignedCertificate -Type CodeSigningCert + → 建立自簽 code signing 憑證 + +3. certutil -addstore TrustedPublisher + certutil -addstore Root + → 安裝憑證到受信任存儲區 + +4. pnputil /add-driver kneron_winusb.inf /install + → 安裝 WinUSB driver 到所有匹配的 Kneron 裝置 + → 支援 Hardware ID: + - USB\VID_3231&PID_0100 (KL520) + - USB\VID_3231&PID_0200 (KL720 KDP) + - USB\VID_3231&PID_0720 (KL720 KDP2) +``` + +背景:Windows 不像 macOS/Linux 有 inbox USB driver 支援。Kneron 裝置需要 WinUSB kernel-level driver 才能被 libusb 存取。之前用戶必須手動使用 Zadig 工具安裝 — 現在完全自動化。 **macOS 無 Homebrew 情境處理:** @@ -2097,29 +2127,38 @@ if !commandExists("python") && !commandExists("python3") { **解除安裝流程:** +使用者按下 Uninstall 按鈕 → Wails 原生 `MessageDialog` 確認 → 非同步執行: + ```go -func (a *Installer) Uninstall(keepData bool) error { - // 1. 停止正在運行的 edge-ai-server 進程 - killProcess("edge-ai-server") +func (inst *Installer) ConfirmUninstall() (bool, error) { + // Wails 原生 QuestionDialog(跨平台,避免 WebView confirm() 不可靠) + result, _ := wailsRuntime.MessageDialog(inst.ctx, wailsRuntime.MessageDialogOptions{ + Type: wailsRuntime.QuestionDialog, + Title: "Uninstall Edge AI Platform", + Message: "This will remove the Edge AI Platform...", + }) + return result == "Yes", nil +} - // 2. 移除 symlink / PATH - // macOS: os.Remove("/usr/local/bin/edge-ai-server") - // Windows: 從 User PATH 移除安裝目錄 +func (inst *Installer) runUninstall() { + // 1. (10%) 停止 server + 移除 auto-restart 服務 + // macOS: launchctl unload + 刪除 plist + // Windows: 刪除 Registry Run key + taskkill + // Linux: systemctl --user disable + 刪除 service - // 3. 移除安裝目錄 - if keepData { - // 保留 data/custom-models/(使用者自訂模型) - removeAllExcept(installDir, "data/custom-models") - } else { - os.RemoveAll(installDir) - } + // 2. (20%) 移除 symlink / PATH + // macOS/Linux: os.Remove("/usr/local/bin/edge-ai-server") + // Windows: 從 User PATH 移除安裝目錄 - // 4. 移除桌面捷徑 / 開機啟動 - removeDesktopShortcut() - removeAutoStart() + // 3. (30-85%) 移除平台檔案(保留系統依賴) + // - server binary (30%) + // - data/ 目錄 (45%) + // - scripts/ 目錄 (55%) + // - venv/ 目錄 (70%) + // - config.json + logs/ (85%) - // 5. macOS: 不需要額外清理(brew 套件保留) - // Windows: 不移除 Python(可能被其他程式使用) + // 4. (100%) 若安裝目錄為空,刪除目錄本身 + // 系統依賴(Python、libusb、Homebrew、ffmpeg)保留不動 } ``` diff --git a/edge-ai-platform/installer/app.go b/edge-ai-platform/installer/app.go index 3a68553..a4f5f2a 100644 --- a/edge-ai-platform/installer/app.go +++ b/edge-ai-platform/installer/app.go @@ -734,6 +734,21 @@ func (inst *Installer) GetExistingInstall() (string, error) { return "", nil } +// ConfirmUninstall shows a native dialog asking the user to confirm uninstall. +func (inst *Installer) ConfirmUninstall() (bool, error) { + result, err := wailsRuntime.MessageDialog(inst.ctx, wailsRuntime.MessageDialogOptions{ + Type: wailsRuntime.QuestionDialog, + Title: "Uninstall Edge AI Platform", + Message: "This will remove the Edge AI Platform and all installed files. System dependencies (Python, libusb) will be preserved.\n\nContinue?", + DefaultButton: "No", + Buttons: []string{"Yes", "No"}, + }) + if err != nil { + return false, err + } + return result == "Yes", nil +} + // Uninstall removes all installed files. func (inst *Installer) Uninstall() error { go inst.runUninstall() diff --git a/edge-ai-platform/installer/frontend/app.js b/edge-ai-platform/installer/frontend/app.js index e3e7f1e..75d7e75 100644 --- a/edge-ai-platform/installer/frontend/app.js +++ b/edge-ai-platform/installer/frontend/app.js @@ -408,8 +408,12 @@ document.getElementById('btn-close').addEventListener('click', () => { // ── Uninstall ───────────────────────────────────────────── document.getElementById('btn-uninstall').addEventListener('click', async () => { - if (!confirm(t('uninstall.confirm'))) { - return; + try { + const confirmed = await window.go.main.Installer.ConfirmUninstall(); + if (!confirmed) return; + } catch (err) { + // Fallback to JS confirm if native dialog fails + if (!confirm(t('uninstall.confirm'))) return; } showStep(4); document.getElementById('progress-title').textContent = t('uninstall.title');