Update PRD v3.2 + TDD v2.1: WinUSB auto-install, ffmpeg, uninstall fixes
PRD v3.2: - Document WinUSB driver auto-install (self-signed cert + pnputil) - ffmpeg/yt-dlp now mandatory install components - Update uninstall description (Wails native dialog, preserve system deps) - Fix packaging output description (DMG + ad-hoc codesign) TDD v2.1: - Rewrite install steps to match actual 11-step implementation - Add detailed Windows WinUSB driver install flow (self-sign + pnputil) - Update uninstall flow with ConfirmUninstall native dialog - Update ProgressEvent struct to match actual implementation Installer: - Add ConfirmUninstall() with Wails native MessageDialog - Replace JS confirm() with Go native dialog (fixes macOS WebView issue) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
98b89fe104
commit
7b4492c41e
@ -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 設計** | 現代化視覺設計,包含步驟進度指示器、平滑過渡動畫、統一的色彩主題與字體排版 |
|
||||
|
||||
|
||||
121
docs/TDD.md
121
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 <cert>
|
||||
certutil -addstore Root <cert>
|
||||
→ 安裝憑證到受信任存儲區
|
||||
|
||||
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)保留不動
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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');
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user