refactor: reorganize repo — move edge-ai-platform to subdirectory

- Move all Edge AI Platform code into edge-ai-platform/ subdirectory
- Remove legacy local_service_win/ and relay-server-linux binary
- Keep docs/ and README.md at repo root
- Update docs to latest PRD v3.1 and TDD v2.0

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
jim800121chen 2026-03-07 02:41:24 +08:00
parent c326e228a1
commit dd8d9b0ce2
214 changed files with 5160 additions and 5709 deletions

View File

@ -8,8 +8,8 @@
|------|------|
| 文件名稱 | 邊緣 AI 開發平台 PRD |
| 產品名稱 | (暫未定名,以下稱「本平台」) |
| 版本 | v2.7 |
| 日期 | 2026-03-02 |
| 版本 | v3.1 |
| 日期 | 2026-03-05 |
| 狀態 | 更新中 |
---
@ -1087,7 +1087,7 @@ Kneron Dongle Arduino 開發板 非 Kneron 晶片
### B4.5 已實作的額外功能
> 以下功能為 MVP 開發過程中額外實作,超出原始 PRD 範圍。共 11 項功能。
> 以下功能為 MVP 開發過程中額外實作,超出原始 PRD 範圍。共 18 項功能。
#### F5 — 多來源推論輸入
@ -1203,15 +1203,16 @@ Kneron Dongle Arduino 開發板 非 Kneron 晶片
| 項目 | 規格 |
|------|------|
| **概述** | 提供一行指令安裝體驗,涵蓋 binary 下載、環境設定、硬體偵測,支援 macOS 與 Windows |
| **概述** | 提供一行指令安裝體驗,涵蓋 binary 下載、環境設定、硬體偵測,支援 macOS、Windows 與 Linux |
| **macOS/Linux 安裝** | `curl -fsSL https://gitea.innovedus.com/.../install.sh \| bash`,自動偵測 OS + 架構darwin/linux + amd64/arm64 |
| **Windows 安裝** | `irm https://gitea.innovedus.com/.../install.ps1 \| iex`,自動下載 zip、解壓、加入 PATH |
| **安裝步驟** | 4 步驟自動化:(1) 下載 binary + 資料檔 (2) 安裝 USB 驅動libusb(3) 建立 Python venv + pyusb (4) 檢查環境 + 偵測硬體 |
| **安裝目錄** | macOS: `~/.edge-ai-platform/`、Windows: `%LOCALAPPDATA%\EdgeAIPlatform` |
| **解除安裝** | macOS: `rm -rf ~/.edge-ai-platform && sudo rm -f /usr/local/bin/edge-ai-server`、Windows: 刪除目錄 + 移除 PATH |
| **安裝目錄** | macOS: `~/.edge-ai-platform/`Linux: `~/.edge-ai-platform/`Windows: `%LOCALAPPDATA%\EdgeAIPlatform` |
| **解除安裝** | macOS/Linux: `rm -rf ~/.edge-ai-platform && sudo rm -f /usr/local/bin/edge-ai-server`、Windows: 刪除目錄 + 移除 PATH |
| **啟動依賴檢查** | Server 啟動時自動檢查 ffmpeg、yt-dlp、python3缺少時顯示對應平台的安裝指引 |
| **GoReleaser 打包** | 跨平台 archive 自動產出darwin amd64/arm64 tar.gz、windows amd64 zip含 binary + data + scripts |
| **GoReleaser 打包** | 跨平台 archive 自動產出darwin amd64/arm64、linux amd64/arm64 tar.gz、windows amd64 zip含 binary + data + scripts + firmware 目錄 + kneron_detect.py |
| **Kneron 硬體偵測** | 安裝完成時自動偵測 USB Kneron 裝置KL520/KL720/KL730使用 pyusbKL720 KDP legacy 裝置提示韌體更新 |
| **Linux 特殊處理** | Ubuntu/Debian: `apt install libusb-1.0-0-dev python3-venv`;設定 udev rules 以允許非 root 使用者存取 USB 裝置 |
#### F17 — 圖形化安裝與解除安裝程式
@ -1220,7 +1221,10 @@ Kneron Dongle Arduino 開發板 非 Kneron 晶片
| **概述** | 提供非技術人員可使用的桌面 GUI 安裝精靈雙擊即可完成所有安裝步驟server binary、Python 環境、系統依賴、硬體驅動),無需開終端機或輸入任何指令 |
| **目標平台** | macOS (.dmg → .app) + Windows (.exe installer) |
| **技術方案** | Go + Wails v2WebView-based GUI前端以 HTML/CSS/JS 實作安裝畫面,後端 Go 執行實際安裝邏輯。共用 Go 工具鏈,無需額外 runtime |
| **安裝精靈流程** | 6 步驟:(1) 歡迎頁 + 授權協議 → (2) 安裝路徑選擇 → (3) 元件選擇(必要/可選)→ (4) 自動安裝 + 即時進度 → (5) 硬體偵測結果 → (6) 完成 + 啟動選項 |
| **安裝精靈流程** | 7 步驟:(1) 歡迎頁 + 授權協議 → (2) Relay 連線設定 → (3) 安裝路徑選擇 → (4) 元件選擇(必要/可選)→ (5) 自動安裝 + 即時進度 → (6) 硬體偵測結果 → (7) 完成 + 啟動選項 |
| **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 預訓練模型、libusbUSB 裝置通訊) |
| **可選元件** | ffmpeg攝影機/影片串流、yt-dlpYouTube 影片下載) |
| **自動依賴解析** | macOS: 自動安裝 Homebrew若未安裝`brew install libusb python3 ffmpeg`Windows: 自動下載 Python embedded + libusb DLL免管理員權限 |
@ -1228,10 +1232,11 @@ Kneron Dongle Arduino 開發板 非 Kneron 晶片
| **硬體偵測** | 安裝完成後自動掃描 USB Kneron 裝置顯示偵測到的晶片型號KL520/KL720、韌體版本、連線狀態KL720 KDP legacy 裝置提示一鍵韌體更新 |
| **解除安裝** | 內建解除安裝功能:刪除 server binary + Python venv + 資料檔案 + symlink/PATHmacOS 提供拖曳到垃圾桶 + 深度清理選項Windows 整合「新增或移除程式」 |
| **安裝目錄** | macOS: `~/.edge-ai-platform/`(不需 sudo、Windows: `%LOCALAPPDATA%\EdgeAIPlatform\`(不需管理員) |
| **安裝完成動作** | 可選擇立即啟動 server + 自動開啟瀏覽器(`http://localhost:3721`)、建立桌面捷徑、設定開機自動啟動 |
| **安裝完成動作** | 自動啟動選項現在註冊 Launcher系統匣應用程式而非裸 server 進程;可選擇立即啟動 Launcher + 自動開啟瀏覽器(`http://localhost:3721`)、建立桌面捷徑、設定開機自動啟動 Launcher |
| **更新支援** | 偵測現有安裝版本,僅更新 binary + 新模型保留使用者資料custom-models、設定檔與 Python venv |
| **打包產出** | macOS: `EdgeAI-Installer.dmg`(含 .app + 背景圖 + Applications 捷徑、Windows: `EdgeAI-Setup.exe`NSIS + Wails 包裝) |
| **安裝體積** | 完整安裝約 300-400 MBbinary ~10MB + 模型 ~73MB + Python venv ~250MB + firmware ~2MB |
| **UI 設計** | 現代化視覺設計,包含步驟進度指示器、平滑過渡動畫、統一的色彩主題與字體排版 |
#### F18 — Kneron 硬體通訊整合KL520 + KL720
@ -1270,6 +1275,75 @@ Kneron Dongle Arduino 開發板 非 Kneron 晶片
| **WebSocket** | 叢集推論結果使用 `inference:cluster:{clusterId}` room 廣播 |
| **限制** | MVP 階段:每叢集最多 8 裝置;叢集設定不持久化;不支援跨機器叢集 |
#### F20 — 雲端中繼隧道Cloud Relay Tunnel
| 項目 | 規格 |
|------|------|
| **概述** | 允許部署在雲端AWS EC2 等)的前端 UI 透過中繼伺服器存取使用者本地端的 edge-ai-server無需本地開放防火牆或使用 VPN。採用業界標準的反向隧道架構參考 Edge Impulse daemon / Balena tunnel 模式) |
| **架構** | 瀏覽器 → nginx(:80) → relay-server(:3800) ←yamux/WebSocket→ 本地 edge-ai-server(:3721) ↔ Kneron 硬體。relay-server 支援多台 local server 同時連線,以 token 為 key 管理多個 yamux session |
| **Relay Server** | 獨立 Go binary`relay-server`),部署於雲端 EC2。接受本地 server 的 WebSocket 隧道連線,透過 yamux 多工串流轉發前端的 HTTP 和 WebSocket 請求 |
| **Tunnel Client** | 整合在 edge-ai-server 內,透過 `--relay-url``--relay-token` 啟動。主動發起 WebSocket 連線到 relay server建立 yamux session接收並處理轉發的請求 |
| **yamux 多工** | 使用 hashicorp/yamuxConsul/Nomad 同款)在單一 WebSocket 上建立多條獨立串流,每個 HTTP 請求或 WebSocket 連線是獨立的 yamux stream |
| **透明轉發** | 前端自動從 localhost:3721/auth/token 取得本機 relay tokenHTTP 請求透過 `X-Relay-Token` header、WebSocket 透過 `?token=` query parameter 路由到對應的本地 server |
| **串流支援** | 完整支援 MJPEG 即時影像串流relay 使用 `http.Flusher` 逐塊轉發、WebSocket 推送upgrade 後雙向 byte copy、大檔案上傳 |
| **認證機制** | 硬體 ID 自動認證local server 啟動時自動從 MAC 位址 SHA-256 雜湊產生 16 字元 token連接 relay 時透過 URL query parameter 傳遞。`--relay-token` flag 仍可手動覆蓋 |
| **自動重連** | Tunnel client 斷線後自動重連使用指數退避策略1s → 2s → 4s → ... → 30s 上限)|
| **健康檢查** | `GET /relay/status?token=xxx` 回傳 `{"tunnelConnected": true/false}`,無 token 時回傳所有隧道總數 |
| **部署** | `deploy-ec2.sh --relay` 自動上傳 relay-server binary、建立 systemd service、設定 nginx proxy 規則 |
| **不影響本地** | 未設定 `--relay-url` 時完全不啟動 tunnel client本地模式零影響 |
#### F21 — 系統匣啟動器System Tray Launcher
| 項目 | 規格 |
|------|------|
| **概述** | 系統匣常駐應用程式,整合於 edge-ai-server binary 中,透過 `--tray` flag 啟動(參考 Ollama 模式)。提供圖形化的 server 生命週期管理,無需開啟終端機 |
| **GUI 框架** | fyne.io/systray輕量級無 GTK 依賴,由 Fyne 團隊維護),僅使用系統原生匣圖示 API不引入完整 GUI 框架 |
| **功能** | Start/Stop server 切換、伺服器狀態顯示Running / Stopped + port 號)、匣圖示狀態反映(綠色 = 運行中、灰色 = 已停止、Open Browser開啟 `http://localhost:{port}` 或 relay URL + token、View Logs開啟 log 檔案或系統 Console、Relay 狀態顯示Connected / Disconnected、Quit停止 server 並退出) |
| **架構** | `--tray` flag 啟動系統匣模式Launcher 作為主進程管理 server 作為子進程child process`~/.edge-ai-platform/config.json` 讀取 port、relay URL/token 等設定,傳遞為 server 啟動參數 |
| **Build Tags** | 使用 Go build tags 區分建置模式:預設建置包含 tray 功能CGO_ENABLED=1`notray` tag 產出無 CGO 依賴的純 CLI binary安裝程式建置啟用 CGO 以支援系統匣 |
| **macOS 平台** | 開機自動啟動透過 launchd`~/Library/LaunchAgents/com.edge-ai-platform.launcher.plist`);使用 template icons支援 Light/Dark menu bar 自動切換) |
| **Windows 平台** | 開機自動啟動透過 Scheduled Task`schtasks /create`,登入時觸發);使用 .ico 格式匣圖示 |
| **Linux 平台** | 開機自動啟動透過 XDG autostart`~/.config/autostart/edge-ai-platform.desktop`);使用 PNG 格式匣圖示 |
| **設定整合** | 讀取 `~/.edge-ai-platform/config.json`(與 F17 安裝程式共用),設定項包含:`port``relayUrl``relayToken``autoStart``language` |
| **子進程管理** | 啟動 server 時以 `exec.Command` 建立子進程監控其存活狀態Quit 時先優雅關閉子進程SIGTERM超時後強制終止SIGKILL |
#### F22 — 多租戶裝置隔離Multi-Tenant Device Isolation
| 項目 | 規格 |
|------|------|
| **概述** | 多位使用者同時透過同一 relay server 存取各自的本機 edge-ai-server 時,每位使用者只能看到自己電腦上的裝置。零設定、自動化認證,無需手動輸入 token |
| **硬體 ID Token** | `pkg/hwid/hwid.go`:讀取第一個非 loopback 網路介面的 MAC 位址 → SHA-256 → 取前 16 字元 hex 作為 token。確保同一台電腦每次產生相同 token |
| **Relay Server 多工** | Relay server 從單一 session 改為 `map[string]*yamux.Session`(以 token 為 key支援多台 local server 同時連線 |
| **Token 路由** | 前端 HTTP 請求透過 `X-Relay-Token` header、WebSocket 透過 `?token=` query parameter 傳遞 tokenrelay 根據 token 路由到對應的 yamux session |
| **自動傳遞** | Local server 啟動並連線 relay 後,自動開啟瀏覽器前往 `{relay_url}/?token={hwid_token}`。前端從 URL query param 讀取 token → 存入 localStorage → 以 `history.replaceState` 移除 URL 參數。此機制取代先前的 localhost fetch 方式,因 Chrome Private Network Access (PNA) 策略禁止公網頁面 fetch localhost |
| **手動備份** | Settings 頁面提供 Relay Token 手動輸入欄位,供自動傳遞失敗時使用(如瀏覽器未自動開啟) |
| **安全性** | Token 在 log 中遮蔽顯示(僅前 8 字元);`X-Relay-Token` 在轉發至 local server 前被移除 |
| **向下相容** | 未設定 relay 時所有 token 機制不啟動,本地模式完全不受影響 |
#### F23 — 新手引導體驗Onboarding & Empty States
| 項目 | 規格 |
|------|------|
| **概述** | 針對首次使用者提供引導式體驗,降低上手門檻。包含首次使用導覽、空狀態引導、以及更友善的錯誤訊息 |
| **首次導覽** | 首次進入平台時顯示 3 步驟快速導覽:(1) 連接 Kneron 裝置 (2) 選擇 AI 模型 (3) 開始推論。使用 localStorage 記錄是否已完成導覽 |
| **裝置空狀態** | Devices 頁面無裝置時顯示插圖 + 引導文字:「請插入 Kneron USB Dongle 並點擊掃描」,附帶硬體購買連結與常見問題 |
| **模型空狀態** | Models 頁面無模型時引導使用者瀏覽預訓練模型庫或上傳自訂模型 |
| **推論空狀態** | Workspace 頁面顯示清楚的步驟提示:先連接裝置 → 載入模型 → 選擇輸入來源 |
| **錯誤訊息改善** | 所有錯誤訊息附帶可能原因與排除建議。例:「無法偵測到裝置」→ 建議檢查 USB 連接、安裝驅動、嘗試其他 USB 孔位 |
| **幫助連結** | 關鍵操作旁提供 tooltip 或 info icon連結至文件或常見問題頁 |
#### F24 — 自動版本檢查與更新提示Auto Update Check
| 項目 | 規格 |
|------|------|
| **概述** | Server 啟動時自動檢查是否有新版本可用,前端 Settings 頁面顯示更新提示與下載連結 |
| **版本檢查 API** | Server 啟動時呼叫 Gitea API`/api/v1/repos/{owner}/{repo}/releases/latest`)取得最新版本號,與當前 `Version` 比較 |
| **檢查頻率** | Server 啟動時檢查一次;前端 Settings 頁面可手動觸發檢查 |
| **前端顯示** | Settings 頁面顯示:當前版本、最新版本、更新日期。若有新版本則顯示醒目的更新提示 badge + 變更日誌摘要 + 下載連結 |
| **API 端點** | `GET /api/system/update-check` — 回傳 `{ currentVersion, latestVersion, updateAvailable, releaseUrl, releaseNotes }` |
| **降級處理** | 檢查失敗時靜默忽略網路不通、Gitea 不可達),不影響正常使用 |
| **隱私** | 版本檢查不傳送任何使用者資料或硬體資訊 |
---
## B5. 功能路線圖Post-MVP
@ -1456,4 +1530,16 @@ Phase 3 — 進階功能(長期差異化)
---
*文件版本v2.7 | 日期2026-03-02 | 狀態:更新中*
*文件版本v3.1 | 日期2026-03-05 | 狀態:更新中*
---
## 修訂紀錄
| 版本 | 日期 | 變更內容 |
|------|------|---------|
| v3.1 | 2026-03-05 | F22: Token 傳遞改為 URL query param因 Chrome PNA 限制移除 localhost fetch。F16: 加入 Linux 平台支援 + GoReleaser 打包 firmware/detect script。F21: System Tray 增強Open Browser、View Logs、Relay 狀態)。新增 F23 新手引導體驗Onboarding + 空狀態 UX。新增 F24 自動版本檢查與更新提示。功能總數更新至 20 項 |
| v3.0 | 2026-03-04 | F20: 更新為多租戶架構(多 session map + token routing。新增 F22 多租戶裝置隔離(硬體 ID 自動 token + 前端自動偵測 + relay token routing。功能總數更新至 18 項 |
| v2.9 | 2026-03-04 | F17: 新增 Relay 設定步驟、i18n 支援EN + zh-TW、config.json 共用設定檔、auto-start 改為註冊 Launcher、UI 現代化設計。新增 F21 系統匣啟動器System Tray Launcher。功能總數更新至 17 項 |
| v2.8 | 2026-03-04 | 前次更新 |
| v2.7 | 2026-03-02 | 前次更新 |

View File

@ -7,9 +7,9 @@
| 項目 | 內容 |
|------|------|
| 文件名稱 | 邊緣 AI 開發平台 TDD |
| 對應 PRD | PRD-Integrated.md v2.7 |
| 版本 | v1.6 |
| 日期 | 2026-03-02 |
| 對應 PRD | PRD-Integrated.md v3.1 |
| 版本 | v2.0 |
| 日期 | 2026-03-05 |
| 狀態 | 更新中 |
---
@ -88,6 +88,27 @@
│ ● 模型儲存庫 │
│ ● 使用者帳號 │
└──────────────────┘
雲端中繼模式Cloud Relay Mode
┌────────────────────────────────────────────┐
│ 雲端 EC2 │
│ │
│ ┌──────────┐ proxy ┌────────────────┐ │
│ │ nginx │────────►│ relay-server │ │
│ │ (:80) │ │ (:3800) │ │
│ └──────────┘ └───────┬────────┘ │
└───────────────────────────────┼────────────┘
│ yamux/WebSocket
│ (outbound from local)
┌───────────────────────────────┼────────────┐
│ 使用者電腦 │ │
│ ┌────────────────────────────┴──────────┐ │
│ │ edge-ai-server (:3721) │ │
│ │ ├── tunnel client (→ relay) │ │
│ │ ├── REST API + WebSocket │ │
│ │ └── Device Manager → Kneron 硬體 │ │
│ └───────────────────────────────────────┘ │
└────────────────────────────────────────────┘
```
### 1.2 技術選型總覽
@ -1910,14 +1931,21 @@ type StepProgress struct {
}
```
**安裝精靈 UI 流程6 步驟):**
**i18n 多語系支援:**
安裝程式支援英文EN與繁體中文zh-TW雙語介面
- 語系偵測啟動時讀取系統語言設定macOS: `defaults read -g AppleLanguages`、Windows: `GetUserDefaultUILanguage()`
- 翻譯檔案Go `embed` 嵌入 `installer/locales/en.json``installer/locales/zh-TW.json`
- 使用者可在歡迎頁右上角切換語言,選擇結果寫入 config.json 的 `launcher.language`
**安裝精靈 UI 流程7 步驟):**
```
┌─────────────────────────────────────────────────────────┐
│ Step 1: 歡迎頁 │
│ Step 1: 歡迎頁 [EN|中]
│ │
│ ┌─────────────────────────────────┐ │
│ │ 🔧 Edge AI Platform Installer │ │
│ │ Edge AI Platform Installer │ │
│ │ │ │
│ │ 版本: v0.2.0 │ │
│ │ 安裝大小: ~350 MB │ │
@ -1942,7 +1970,25 @@ type StepProgress struct {
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ Step 3: 元件選擇 │
│ Step 3: Relay 中繼設定(選填) │
│ │
│ 雲端中繼可讓您從外部網路存取本機裝置。 │
│ 若不使用,請直接按「下一步」跳過。 │
│ │
│ Relay URL: │
│ ┌──────────────────────────────────────────┐ │
│ │ wss://relay.example.com/tunnel/connect │ │
│ └──────────────────────────────────────────┘ │
│ Relay Token: │
│ ┌──────────────────────────────────────────┐ │
│ │ ●●●●●●●●●●●● │ │
│ └──────────────────────────────────────────┘ │
│ │
│ [← 上一步] [下一步 →] │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ Step 4: 元件選擇 │
│ │
│ 必要元件: │
│ ☑ Edge AI Server (10 MB) ── 核心伺服器 │
@ -1959,7 +2005,7 @@ type StepProgress struct {
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ Step 4: 安裝進度 │
│ Step 5: 安裝進度 │
│ │
│ ✅ 下載 Edge AI Server 完成 │
│ ✅ 解壓縮檔案 完成 │
@ -1969,13 +2015,14 @@ type StepProgress struct {
│ │ 安裝 numpy... │ │
│ └──────────────────────────────────┘ │
│ ○ 安裝模型檔案 等待中 │
│ ○ 寫入設定檔 等待中 │
│ ○ 偵測硬體 等待中 │
│ │
│ [取消] │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ Step 5: 硬體偵測 │
│ Step 6: 硬體偵測 │
│ │
│ 偵測到的 Kneron 裝置: │
│ │
@ -1989,13 +2036,13 @@ type StepProgress struct {
│ │ PID: 0x0100 │ │
│ └──────────────────────────────────┘ │
│ │
⚠️ 未偵測到裝置?請確認 USB 連接 │
│ 未偵測到裝置?請確認 USB 連接
│ │
│ [← 上一步] [下一步 →] │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ Step 6: 完成 │
│ Step 7: 完成 │
│ │
│ ✅ 安裝成功! │
│ │
@ -2006,7 +2053,7 @@ type StepProgress struct {
│ ☑ 立即啟動 Edge AI Server │
│ ☑ 開啟瀏覽器 (http://localhost:3721) │
│ ☐ 建立桌面捷徑 │
開機自動啟動 │
開機自動啟動 │
│ │
│ [完成] │
└─────────────────────────────────────────────────────────┘
@ -2016,16 +2063,17 @@ type StepProgress struct {
| 步驟 | macOS 實作 | Windows 實作 |
|------|-----------|-------------|
| 下載 binary | `net/http` GET → Gitea release tar.gz | `net/http` GET → Gitea release zip |
| 下載 binary | `net/http` GET → Gitea release tar.gztray-enabled build | `net/http` GET → Gitea release ziptray-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` | `HKCU\...\Run` registry |
| 開機啟動 | `~/Library/LaunchAgents/com.innovedus.edge-ai.plist`(執行 `edge-ai-server --tray` | `HKCU\...\Run` registry(執行 `edge-ai-server.exe --tray` |
**macOS 無 Homebrew 情境處理:**
@ -2291,6 +2339,520 @@ POST /api/clusters/:id/flash {modelId: "yolov5s"}
實際吞吐量受 USB 頻寬和 host CPU 限制,建議使用多個 USB controller 或 powered hub。
#### 8.5.16 雲端中繼隧道F20 — Cloud Relay Tunnel
| 前端元件 | 後端模組 | 說明 |
|---------|---------|------|
| (無需修改) | `internal/relay/server.go` | Relay server 核心邏輯 |
| — | `cmd/relay-server/main.go` | Relay server 獨立 binary 進入點 |
| — | `internal/tunnel/client.go` | Tunnel client整合在 edge-ai-server |
| — | `pkg/wsconn/wsconn.go` | WebSocket-to-net.Conn 轉接器 |
| — | `pkg/hwid/hwid.go` | 硬體 ID 產生 |
| — | `internal/api/middleware.go` | CORS 支援 X-Relay-Token |
| — | `internal/config/config.go` | 新增 `--relay-url``--relay-token` flags |
| — | `main.go` | 條件啟動 tunnel client |
| — | `scripts/deploy-ec2.sh` | EC2 部署腳本(含 relay 部署) |
| `lib/constants.ts` | — | relay token 管理 |
| `lib/api.ts` | — | X-Relay-Token header |
| `lib/ws.ts` | — | token query param |
| `components/relay-token-sync.tsx` | — | 自動偵測 token |
| `app/settings/page.tsx` | — | relay token UI |
**整體架構:**
```
瀏覽器 → nginx(:80) ─┬─ /api/* ──► relay-server(:3800) ◄──yamux/WS──► edge-ai-server(:3721)
├─ /ws/* ──► ↑ ↕
├─ /tunnel/ ─► │ Kneron 硬體
└─ /* ──► 靜態前端 (SPA)
```
**wsconn — WebSocket-to-net.Conn 轉接器(`pkg/wsconn/wsconn.go`**
```go
// 將 gorilla/websocket.Conn 包裝成 net.Conn 介面
// 供 hashicorp/yamux 多工器使用
type Conn struct {
ws *websocket.Conn
reader io.Reader // 當前 WebSocket 訊息的 reader
rmu sync.Mutex // 讀取鎖
wmu sync.Mutex // 寫入鎖
}
// Read: 從 WebSocket Binary frame 讀取,自動處理訊息邊界
// Write: 每次 Write 呼叫包裝為一個 Binary frame
// 實現 net.Conn 所有方法: Close, LocalAddr, RemoteAddr, SetDeadline...
```
**Relay Server`internal/relay/server.go`**
```go
type Server struct {
sessions map[string]*yamux.Session // token → yamux session
mu sync.RWMutex
}
// 路由:
// GET /tunnel/connect?token=xxx → handleTunnel() — 接受 WS tunnel 連線
// GET /relay/status → handleStatus() — 回傳 tunnel 狀態
// /* → handleProxy() — 轉發請求到 tunnel
// getToken 從 X-Relay-Token header 或 ?token query param 取得 token
func getToken(r *http.Request) string {
if tok := r.Header.Get("X-Relay-Token"); tok != "" { return tok }
return r.URL.Query().Get("token")
}
```
**handleTunnel 流程:**
1. 從 URL query parameter 取得 token必填無 token 回傳 401
2. `upgrader.Upgrade()` 升級為 WebSocket
3. `wsconn.New(conn)` 包裝成 net.Conn
4. `yamux.Server(netConn, config)` 建立 server-side session
5. 以 token 為 key 存入 `sessions[token]`,替換該 token 的舊 session若有舊 session 關閉
6. 阻塞等待 `session.CloseChan()` 直到斷線,斷線後從 `sessions` 移除
**handleProxy 流程(一般 HTTP 請求):**
1. 從 `X-Relay-Token` header 或 `?token=` query param 取得 token`getToken(r)`
2. 以 token 查找 `sessions[token]` 取得對應 yamux sessionRLock找不到回傳 502
3. `r.Header.Del("X-Relay-Token")` 移除 token header避免傳到後端
4. `session.Open()` 開啟新串流
5. `r.Write(stream)` 將原始 HTTP request 寫入串流
6. `http.ReadResponse(stream)` 讀取 response
7. 複製 response headers + status code
8. 串流 response body使用 `http.Flusher` 支援 MJPEG 等串流回應)
**proxyWebSocket 流程WebSocket 升級請求):**
1. 寫入 HTTP upgrade request 到 yamux stream
2. 讀取 response預期 101 Switching Protocols
3. `hijacker.Hijack()` 奪取 client TCP 連線
4. 寫入 101 response 給瀏覽器
5. 雙向 `io.Copy`client ↔ tunnel stream
**Tunnel Client`internal/tunnel/client.go`**
```go
type Client struct {
relayURL string // ws(s)://host:port/tunnel/connect
token string
localAddr string // "127.0.0.1:3721"
stopCh chan struct{}
stoppedCh chan struct{}
}
// Start() — 背景 goroutine 執行連線迴圈
// Stop() — 關閉 tunnel 並等待清理完成
```
**connect() 流程:**
1. WebSocket Dial 到 relay server
2. `yamux.Client(netConn, config)` 建立 client-side session
3. 迴圈 `session.Accept()` 接受 relay 開啟的串流
4. 每個串流在 goroutine 中處理
**handleStream() 流程:**
1. `http.ReadRequest(stream)` 從 yamux stream 讀取 HTTP request
2. 判斷是否為 WebSocket upgrade
3. 一般請求:`http.DefaultTransport.RoundTrip(req)``resp.Write(stream)`
4. WebSocketraw TCP 連到 local server → 雙向 `io.Copy`
**自動重連:**
```go
// 指數退避: 1s, 2s, 4s, 8s, 16s, 30s (cap)
func backoff(attempt int) time.Duration {
d := min(2^(attempt-1), 30) * time.Second
return d
}
```
**部署方式:**
```bash
# 建置 relay-server binary
make build-relay
# 部署到 EC2含 relay + nginx proxy— relay-server 不再需要 --relay-tokentokens 由各 client 自帶)
bash scripts/deploy-ec2.sh user@host --key key.pem --build --relay
# 本地 server 連接到 relaytoken 由 hwid.Generate() 自動產生)
./edge-ai-server --relay-url ws://ec2-host:3800/tunnel/connect --relay-token <auto-generated>
```
**nginx proxy 設定(自動由 deploy-ec2.sh 產生):**
```nginx
# API 請求 → relay關閉 buffering 以支援串流)
location /api/ {
proxy_pass http://127.0.0.1:3800;
proxy_buffering off;
proxy_read_timeout 3600s;
}
# WebSocket → relayupgrade 支援)
location /ws/ {
proxy_pass http://127.0.0.1:3800;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
# Tunnel 端點(超長 timeout
location /tunnel/ {
proxy_pass http://127.0.0.1:3800;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 86400s;
}
```
**依賴:**
| 套件 | 版本 | 用途 |
|------|------|------|
| `github.com/hashicorp/yamux` | v0.1.2 | WebSocket 上的多工串流Consul/Nomad 同款)|
| `github.com/gorilla/websocket` | 既有 | WebSocket 連線relay + tunnel 兩端共用)|
#### 8.5.17 系統匣啟動器F21 — System Tray Launcher
| 前端元件 | 後端模組 | 說明 |
|---------|---------|------|
| (無) | `server/tray/config.go` | Config struct、`LoadConfig()``Save()``ConfigDir()``ConfigPath()` |
| — | `server/tray/stub.go` | `//go:build notray` — 未啟用 CGO 時印出錯誤訊息 |
| — | `server/tray/tray.go` | `//go:build !notray``fyne.io/systray` 系統匣實作 |
| — | `server/tray/icons.go` | `//go:embed` 載入 tray icon 資源 |
| — | `server/tray/assets/` | `icon_running.png`22x22 綠色)、`icon_stopped.png`22x22 灰色) |
| — | `server/main.go` | `--tray` flag 進入點:`config.Load()``tray.Run(tray.LoadConfig())` |
**目錄結構:**
```
server/tray/
├── config.go # Config struct, LoadConfig(), Save(), ConfigDir(), ConfigPath()
├── stub.go # //go:build notray — prints error if --tray used without CGO
├── tray.go # //go:build !notray — fyne.io/systray implementation
├── icons.go # //go:embed for tray icon assets
└── assets/
├── icon_running.png # 22x22 green icon
└── icon_stopped.png # 22x22 gray icon
```
**Config 檔案格式**`~/.edge-ai-platform/config.json`
```json
{
"version": 1,
"server": { "port": 3721, "host": "127.0.0.1" },
"relay": { "url": "", "token": "" },
"launcher": { "autoStart": true, "language": "zh-TW" }
}
```
**核心元件:**
```go
// server/tray/config.go
type Config struct {
Version int `json:"version"`
Server ServerConfig `json:"server"`
Relay RelayConfig `json:"relay"`
Launcher LauncherConfig `json:"launcher"`
}
type ServerConfig struct {
Port int `json:"port"`
Host string `json:"host"`
}
type RelayConfig struct {
URL string `json:"url"`
Token string `json:"token"`
}
type LauncherConfig struct {
AutoStart bool `json:"autoStart"`
Language string `json:"language"`
}
func ConfigDir() string // 回傳 ~/.edge-ai-platform/
func ConfigPath() string // 回傳 ~/.edge-ai-platform/config.json
func LoadConfig() *Config // 讀取設定檔,不存在時回傳預設值
func (c *Config) Save() error // 寫入設定檔
```
```go
// server/tray/tray.go (//go:build !notray)
type TrayApp struct {
cfg *Config
cmd *exec.Cmd // server 子程序
running bool
mu sync.Mutex
}
// Run() — systray.Run(onReady, onExit) 主迴圈
// onReady() — 設定 icon、tooltip、選單項目
// startServer() — 以 os/exec 啟動子程序: os.Args[0] --port X --relay-url Y --relay-token Z
// stopServer() — SIGTERM (Unix) / Process.Kill() (Windows) 優雅關閉
// watchServer() — goroutine 監控 cmd.Wait(),子程序結束時更新 tray 狀態
```
**`TrayApp` 管理 server 子程序:**
- Server 以子程序方式啟動:`os.Args[0] --port X --relay-url Y --relay-token Z`
- 背景 goroutine 呼叫 `cmd.Wait()` 監控子程序狀態,若異常結束則更新 tray icon 及選單狀態
- 優雅關閉Unix 發送 `SIGTERM`Windows 呼叫 `Process.Kill()`
**選單結構:**
```
┌──────────────────────────┐
│ Edge AI Platform │ ← Statusdisabled僅顯示
├──────────────────────────┤
│ ▶ Start Server │ ← Start/Stop 切換
├──────────────────────────┤
│ Quit │
└──────────────────────────┘
```
- 選單項目Status (disabled) → Start/Stop toggle → Separator → Quit
- Icon 平台適配macOS 使用 `SetTemplateIcon()` 自動適配深色/淺色模式Windows/Linux 使用 `SetIcon()`
- 狀態圖示:運行中 = `icon_running.png`(綠色)、已停止 = `icon_stopped.png`(灰色)
**Build System**
| 建置模式 | 環境變數 | 用途 |
|---------|---------|------|
| `CGO_ENABLED=0 -tags notray` | CLI 發行版 | GoReleaser 跨平台 CLI 分發stub 印出錯誤) |
| `CGO_ENABLED=1` | Tray 完整版 | 安裝程式 payload含系統匣功能 |
```makefile
# Makefile 新增目標
build-server-tray:
cd server && CGO_ENABLED=1 go build -o ../dist/edge-ai-server-tray
# installer-payload 目標改為依賴 build-server-tray
installer-payload: build-frontend build-server-tray
```
**進入點(`server/main.go`**
```go
func main() {
cfg := config.Load() // 解析 --tray flag
if cfg.TrayMode {
// Tray 模式啟動系統匣server 由 TrayApp 管理
tray.Run(tray.LoadConfig())
return
}
// 一般模式:直接啟動 server
startServer(cfg)
}
```
**Stub`server/tray/stub.go`**
```go
//go:build notray
package tray
import (
"fmt"
"os"
)
func Run(cfg *Config) {
fmt.Fprintln(os.Stderr, "error: --tray requires CGO_ENABLED=1 build (this binary was built with -tags notray)")
os.Exit(1)
}
```
**依賴:**
| 套件 | 版本 | 用途 |
|------|------|------|
| `fyne.io/systray` | latest | 跨平台系統匣macOS/Windows/Linux |
#### 8.5.18 多租戶裝置隔離F22 — Multi-Tenant Device Isolation
| 前端元件 | 後端模組 | 說明 |
|---------|---------|------|
| `components/relay-token-sync.tsx` | `pkg/hwid/hwid.go` | 硬體 ID → token 產生 |
| `lib/constants.ts` | `internal/relay/server.go` | Relay 多 session map |
| `lib/api.ts` | `internal/api/router.go` | /auth/token endpoint |
| `lib/ws.ts` | `internal/api/middleware.go` | CORS X-Relay-Token |
| `app/settings/page.tsx` | `main.go` | 自動產生 + 注入 token |
| `stores/camera-store.ts` | — | Stream URL token 注入 |
| `components/camera/*` | — | appendRelayToken for <img> |
**硬體 ID 產生(`pkg/hwid/hwid.go`**
```go
func Generate() string {
ifaces, _ := net.Interfaces()
for _, iface := range ifaces {
if iface.Flags&net.FlagLoopback != 0 { continue }
mac := iface.HardwareAddr.String()
if mac == "" { continue }
return SHA256(mac)[:16] // 16-char hex token
}
return SHA256("unknown")[:16]
}
```
**Token 流程:**
```
啟動時:
local server → hwid.Generate() → relayToken
local server → tunnel.NewClient(relayURL, relayToken, addr) → 連到 relay
local server → relayWebURL(wsURL, token) → http://relay-host/?token=xxx
local server → openBrowser(relayHTTP) → 自動開啟瀏覽器
瀏覽器載入時URL 帶 ?token=xxx
RelayTokenSync → syncRelayTokenFromURL()
→ URLSearchParams.get("token") → "abc123..."
→ localStorage.setItem("edge-ai-relay-token", token)
→ history.replaceState() 移除 URL 中的 token 參數
※ 取代先前的 fetch("http://localhost:3721/auth/token") 方式
※ 原因Chrome Private Network Access (PNA) 禁止公網頁面 fetch localhost
API 請求:
api.ts → buildHeaders() → { "X-Relay-Token": token }
relay → getToken(r) → sessions[token] → yamux stream → local server
WebSocket
ws.ts → buildUrl() → ws://host/ws/path?token=abc123
relay → getToken(r) → sessions[token] → yamux stream → local server
MJPEG/圖片(<img src>
appendRelayToken(url) → url + "?token=abc123"
relay → getToken(r) → sessions[token] → yamux stream → local server
```
**Relay Server 多租戶架構:**
```go
type Server struct {
sessions map[string]*yamux.Session // token → session
mu sync.RWMutex
}
func NewServer() *Server {
return &Server{sessions: make(map[string]*yamux.Session)}
}
// getToken 從 X-Relay-Token header 或 ?token query param 取得 token
func getToken(r *http.Request) string {
if tok := r.Header.Get("X-Relay-Token"); tok != "" { return tok }
return r.URL.Query().Get("token")
}
```
**前端 Token 管理(`lib/constants.ts`**
```typescript
// 從 localStorage 取得快取的 token
export function getRelayToken(): string
// 儲存 token 到 localStorage
export function setRelayToken(token: string): void
// 從 URL query param 讀取 token → 存入 localStorage → 清理 URL
export function syncRelayTokenFromURL(): string
// 向下相容 wrapper內部呼叫 syncRelayTokenFromURL
export async function fetchAndCacheRelayToken(): Promise<string>
// 為 <img src> 等無法帶 header 的元素附加 ?token= 參數
export function appendRelayToken(url: string): string
```
**Server 端自動開啟瀏覽器(`main.go`**
```go
// relayWebURL 將 WebSocket URL 轉換為帶 token 的 HTTP URL
// ws://host:port/tunnel/connect → http://host:port/?token=xxx
func relayWebURL(wsURL, token string) string
// openBrowser 使用系統預設瀏覽器開啟 URL
// darwin: open、linux: xdg-open、windows: cmd /c start
func openBrowser(url string)
```
**安全考量:**
- Token 在 log 中僅顯示前 8 字元(`relayToken[:8]`
- `X-Relay-Token` header 在轉發前被 `r.Header.Del("X-Relay-Token")` 移除
- CORS middleware 新增 `X-Relay-Token``Access-Control-Allow-Headers`
- Token 透過 URL 傳遞後立即從網址列移除(`history.replaceState`),降低被意外分享的風險
#### 8.5.19 新手引導體驗F23 — Onboarding & Empty States
| 前端元件 | 後端模組 | 說明 |
|---------|---------|------|
| `components/onboarding/tour.tsx` | — | 3 步驟首次導覽(連接裝置 → 選模型 → 推論) |
| `components/onboarding/empty-state.tsx` | — | 可重用空狀態元件(插圖 + 引導文字 + CTA |
| `hooks/use-first-visit.ts` | — | localStorage 記錄首次造訪狀態 |
| `app/devices/page.tsx` | — | 裝置頁空狀態引導 |
| `app/models/page.tsx` | — | 模型頁空狀態引導 |
| `app/workspace/[deviceId]/page.tsx` | — | Workspace 步驟提示 |
| `components/ui/error-message.tsx` | — | 增強錯誤訊息(附原因 + 排除建議) |
**首次導覽流程:**
```
用戶首次進入
→ useFirstVisit() 檢查 localStorage("onboarding-complete")
→ 未完成 → 顯示 OnboardingTour
→ Step 1: "連接你的 Kneron 裝置"(示意圖 + 插入 USB 提示)
→ Step 2: "選擇 AI 模型"(模型庫預覽 + 說明)
→ Step 3: "開始推論!"(攝影機/影片/圖片選擇提示)
→ 完成 → localStorage.set("onboarding-complete", "true")
```
**空狀態設計原則:**
- 每個空狀態包含:插圖/icon + 主標題 + 描述文字 + 主要 CTA 按鈕
- 裝置頁:「尚未偵測到裝置」→ [掃描裝置] 按鈕 + 疑難排解連結
- 模型頁:「探索預訓練模型」→ [瀏覽模型庫] + [上傳自訂模型]
- Workspace步驟式引導 → 1. 連接裝置 ✓/✗ → 2. 載入模型 ✓/✗ → 3. 選擇輸入
**錯誤訊息增強:**
```typescript
interface EnhancedError {
message: string; // 原始錯誤訊息
possibleCauses: string[]; // 可能原因列表
suggestions: string[]; // 排除建議
helpUrl?: string; // 文件連結
}
```
#### 8.5.20 自動版本檢查F24 — Auto Update Check
| 前端元件 | 後端模組 | 說明 |
|---------|---------|------|
| `app/settings/page.tsx` | `internal/api/handlers/system.go` | 版本資訊 + 更新提示 |
| `components/update-badge.tsx` | `internal/update/checker.go` | 更新提示 badge |
| — | `main.go` | 啟動時觸發檢查 |
**後端版本檢查(`internal/update/checker.go`**
```go
type UpdateInfo struct {
CurrentVersion string `json:"currentVersion"`
LatestVersion string `json:"latestVersion"`
UpdateAvailable bool `json:"updateAvailable"`
ReleaseURL string `json:"releaseUrl"`
ReleaseNotes string `json:"releaseNotes"`
PublishedAt string `json:"publishedAt"`
}
// Check 呼叫 Gitea API 取得最新 release與當前版本比較
func Check(currentVersion, giteaURL, owner, repo string) (*UpdateInfo, error) {
// GET /api/v1/repos/{owner}/{repo}/releases/latest
// 比較 semver失敗時靜默返回不影響正常使用
}
```
**API 端點:**
```
GET /api/system/update-check
→ 200 { currentVersion, latestVersion, updateAvailable, releaseUrl, releaseNotes, publishedAt }
→ 200 { currentVersion, latestVersion: "", updateAvailable: false } // 檢查失敗時
```
**前端顯示:**
- Settings 頁面「關於」區塊顯示當前版本 + build time
- 有新版本時:醒目 badge + 版本號對比 + 變更摘要 + 下載按鈕
- 檢查按鈕可手動觸發 `/api/system/update-check`
---
## 9. 開發環境與工具鏈
@ -2370,6 +2932,8 @@ edge-ai-platform/
│ ├── go.sum
│ ├── main.go
│ ├── cmd/
│ │ └── relay-server/ # Relay server 獨立 binary
│ │ └── main.go
│ ├── internal/
│ │ ├── api/
│ │ ├── device/
@ -2377,10 +2941,24 @@ edge-ai-platform/
│ │ ├── model/
│ │ ├── flash/
│ │ ├── inference/
│ │ ├── relay/ # Relay server 核心邏輯
│ │ │ └── server.go
│ │ ├── tunnel/ # Tunnel client連接 relay
│ │ │ └── client.go
│ │ └── config/
│ ├── tray/ # 系統匣啟動器F21
│ │ ├── config.go # Config struct, LoadConfig(), Save()
│ │ ├── stub.go # //go:build notray stub
│ │ ├── tray.go # //go:build !notray — systray 實作
│ │ ├── icons.go # //go:embed tray icon assets
│ │ └── assets/
│ │ ├── icon_running.png # 22x22 運行中(綠色)
│ │ └── icon_stopped.png # 22x22 已停止(灰色)
│ ├── pkg/
│ │ ├── serial/
│ │ ├── usb/
│ │ ├── wsconn/ # WebSocket↔net.Conn 轉接器
│ │ │ └── wsconn.go
│ │ └── logger/
│ ├── data/ # 內建模型中繼資料
│ │ └── models.json
@ -2448,6 +3026,23 @@ edge-ai-platform/
| Windows | windows/amd64 | `edge-ai-platform.exe` | `.msi` 安裝包 |
| Linux | linux/amd64 | `edge-ai-platform-linux-amd64` | `.deb` / `.rpm` / 直接執行 |
#### Relay Server雲端部署
| 平台 | 架構 | 輸出檔案 | 部署方式 |
|------|------|---------|---------|
| Linux (EC2) | linux/amd64 | `relay-server` | `deploy-ec2.sh --relay` + systemd service |
```bash
# 建置 relay-server
make build-relay
# 部署到 EC2自動上傳 binary + 建立 systemd service + 設定 nginx proxy
bash scripts/deploy-ec2.sh user@host --key key.pem --build --relay --relay-token SECRET
# 本地 edge-ai-server 連接到雲端 relay
./edge-ai-server --relay-url ws://ec2-host:3800/tunnel/connect --relay-token SECRET
```
### 11.3 GoReleaser 設定
```yaml
@ -2465,6 +3060,8 @@ builds:
- arm64
env:
- CGO_ENABLED=0
tags:
- notray # CLI 發行版使用 stub無系統匣
ldflags:
- -s -w -X main.version={{.Version}}
@ -2473,8 +3070,20 @@ archives:
format_overrides:
- goos: windows
format: zip
files:
- src: server/data/**/* # 模型定義 + NEF 檔案
strip_parent: true
- src: server/scripts/**/* # Python bridge + detect
strip_parent: true
- src: server/firmware/**/* # Kneron firmware 檔案
strip_parent: true
```
**打包內容說明:**
- `data/`: models.json、custom-models/、nef/ 預訓練模型
- `scripts/`: kneron_bridge.pyJSON-RPC bridge、kneron_detect.pyUSB 裝置偵測)
- `firmware/`: KL520 fw_scpu.bin/fw_ncpu.bin、KL720 韌體更新檔
### 11.4 Makefile
```makefile
@ -2497,7 +3106,18 @@ build-frontend:
cp -r frontend/out server/frontend/out
build-backend: build-frontend
cd server && go build -o ../dist/edge-ai-platform
cd server && CGO_ENABLED=0 go build -tags notray -o ../dist/edge-ai-platform
# Tray-enabled build安裝程式 payload 使用)
build-server-tray: build-frontend
cd server && CGO_ENABLED=1 go build -o ../dist/edge-ai-server-tray
# Relay server
build-relay:
cd server && go build -o ../dist/relay-server ./cmd/relay-server
# Installer payload使用 tray-enabled build
installer-payload: build-frontend build-server-tray
# 跨平台打包
release:
@ -2839,4 +3459,15 @@ go.uber.org/zap // 結構化日誌
---
*文件版本v1.6 | 日期2026-03-02 | 狀態:更新中*
*文件版本v2.0 | 日期2026-03-05 | 狀態:更新中*
---
## 變更紀錄
| 版本 | 日期 | 變更內容 |
|------|------|---------|
| v2.0 | 2026-03-05 | 更新 8.5.18 token 傳遞方式為 URL query param因 Chrome PNA 限制移除 localhost fetch新增 8.5.19 新手引導體驗F23新增 8.5.20 自動版本檢查F24GoReleaser 打包加入 firmware + detect script |
| v1.9 | 2026-03-04 | 更新 8.5.16 中繼隧道為多租戶架構;新增 8.5.18 多租戶裝置隔離F22硬體 ID token 自動產生、relay multi-session map、前端自動 token 偵測、stream URL token 注入 |
| v1.8 | 2026-03-04 | 新增 8.5.17 系統匣啟動器F21更新 8.5.14 安裝程式:新增 Step 3 Relay 中繼設定、i18n 雙語支援EN/zh-TW、config.json 寫入步驟、開機自動啟動改用 `--tray` flag更新專案目錄結構新增 `server/tray/`Makefile 新增 `build-server-tray``installer-payload` 目標GoReleaser 新增 `-tags notray` |
| v1.7 | 2026-03-04 | 前次更新 |

116
edge-ai-platform/README.md Normal file
View File

@ -0,0 +1,116 @@
# Edge AI Platform
邊緣 AI 開發平台 — 管理 AI 模型、連接邊緣裝置Kneron KL720/KL730、即時攝影機推論。
單一執行檔,下載即可使用。
## Quick Start
### macOS
```bash
# 安裝(下載至 ~/.edge-ai-platform
curl -fsSL https://gitea.innovedus.com/warrenchen/web_academy_prototype/raw/branch/main/scripts/install.sh | bash
# 啟動Mock 模式,不需硬體)
edge-ai-server --mock --mock-devices=3
# 開啟瀏覽器
open http://127.0.0.1:3721
```
### Windows (PowerShell)
```powershell
# 安裝
irm https://gitea.innovedus.com/warrenchen/web_academy_prototype/raw/branch/main/scripts/install.ps1 | iex
# 啟動Mock 模式)
edge-ai-server.exe --mock --mock-devices=3
# 開啟瀏覽器
Start-Process http://127.0.0.1:3721
```
### 手動下載
從 [Releases](https://gitea.innovedus.com/warrenchen/web_academy_prototype/releases) 下載對應平台的壓縮檔:
| 平台 | 檔案 |
|:-----|:-----|
| macOS Intel | `edge-ai-platform_vX.Y.Z_darwin_amd64.tar.gz` |
| macOS Apple Silicon | `edge-ai-platform_vX.Y.Z_darwin_arm64.tar.gz` |
| Windows x64 | `edge-ai-platform_vX.Y.Z_windows_amd64.zip` |
解壓後執行:
```bash
# macOS
tar xzf edge-ai-platform_*.tar.gz
cd edge-ai-platform_*/
./edge-ai-server --mock --mock-devices=3
# Windows: 解壓 zip在資料夾中開啟 PowerShell
.\edge-ai-server.exe --mock --mock-devices=3
```
然後開啟瀏覽器 http://127.0.0.1:3721
## 命令列選項
| Flag | 預設值 | 說明 |
|:-----|:------|:-----|
| `--port` | `3721` | 伺服器連接埠 |
| `--host` | `127.0.0.1` | 伺服器位址 |
| `--mock` | `false` | 啟用模擬裝置驅動 |
| `--mock-camera` | `false` | 啟用模擬攝影機 |
| `--mock-devices` | `1` | 模擬裝置數量 |
| `--log-level` | `info` | 日誌等級debug/info/warn/error |
| `--dev` | `false` | 開發模式(停用嵌入式前端) |
## 可選依賴
以下工具可增強功能,但**非必要**
| 工具 | 用途 | macOS 安裝 | Windows 安裝 |
|:-----|:-----|:----------|:------------|
| `ffmpeg` | 攝影機擷取、影片處理 | `brew install ffmpeg` | `winget install Gyan.FFmpeg` |
| `yt-dlp` | YouTube / 影片 URL 解析 | `brew install yt-dlp` | `winget install yt-dlp` |
| `python3` | Kneron KL720 硬體驅動 | `brew install python3` | `winget install Python.Python.3.12` |
啟動時會自動檢查並提示缺少的工具。
## 解除安裝
### macOS
```bash
rm -rf ~/.edge-ai-platform
sudo rm -f /usr/local/bin/edge-ai-server
```
### Windows (PowerShell)
```powershell
Remove-Item -Recurse -Force "$env:LOCALAPPDATA\EdgeAIPlatform"
# 手動從系統環境變數移除 PATH 中的 EdgeAIPlatform 路徑
```
## 開發
```bash
# 安裝依賴
make install
# 啟動開發伺服器(前端 :3000 + 後端 :3721
make dev
# 編譯單一 binary
make build
# 跨平台打包(本機測試,不發佈)
make release-snapshot
# 發佈至 Gitea Release
make release
```

File diff suppressed because it is too large Load Diff

2842
edge-ai-platform/docs/TDD.md Normal file

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More