# 邊緣 AI 開發平台 — 技術設計文件（TDD）

---

## 文件資訊

| 項目 | 內容 |
|------|------|
| 文件名稱 | 邊緣 AI 開發平台 TDD |
| 對應 PRD | PRD-Integrated.md v2.6 |
| 版本 | v1.5 |
| 日期 | 2026-03-01 |
| 狀態 | 更新中 |

---

## 目錄

- [1. 技術架構總覽](#1-技術架構總覽)
- [2. 前端架構設計](#2-前端架構設計)
- [3. 後端架構設計（本地端 Backend Server）](#3-後端架構設計本地端-backend-server)
- [4. 前後端通訊設計](#4-前後端通訊設計)
- [5. 裝置通訊抽象層設計](#5-裝置通訊抽象層設計)
- [6. 模型庫設計](#6-模型庫設計)
- [7. 資料模型設計](#7-資料模型設計)
- [8. MVP 功能模組對照](#8-mvp-功能模組對照)
- [8.5 已實作額外功能技術對照](#85-已實作額外功能技術對照)
- [9. 開發環境與工具鏈](#9-開發環境與工具鏈)
- [10. 專案目錄結構](#10-專案目錄結構)
- [11. 部署與打包策略](#11-部署與打包策略)
- [12. 安全性考量](#12-安全性考量)
- [13. 效能目標](#13-效能目標)
- [14. 測試策略](#14-測試策略)
- [15. 開發階段規劃](#15-開發階段規劃)

---

## 1. 技術架構總覽

### 1.1 整體架構圖

```
┌─────────────────────────────────────────────────────────────────────────┐
│                            使用者電腦                                     │
│                                                                         │
│  ┌─────────────────────────┐   HTTP/WS    ┌───────────────────────────┐ │
│  │   前端 Web App            │◄═══════════►│   本地端 Backend Server     │ │
│  │   (瀏覽器)                │ :3721       │   (Go 單一執行檔)           │ │
│  │                          │              │                           │ │
│  │  Next.js (React)         │              │  ┌──────────────────────┐ │ │
│  │  + shadcn/ui             │              │  │ HTTP REST API        │ │ │
│  │  + Tailwind CSS          │              │  │ WebSocket Server     │ │ │
│  │  + Zustand               │              │  │ Static File Server   │ │ │
│  │                          │              │  └──────────┬───────────┘ │ │
│  │  Features:               │              │             │             │ │
│  │  ├── 模型庫瀏覽/篩選      │              │  ┌──────────┴───────────┐ │ │
│  │  ├── 裝置狀態面板         │              │  │ Device Manager       │ │ │
│  │  ├── 燒錄進度 UI          │              │  │ ├── USB 監聽          │ │ │
│  │  ├── Camera 即時預覽      │              │  │ ├── Driver Registry   │ │ │
│  │  ├── 推論結果疊加顯示     │              │  │ └── Session Manager   │ │ │
│  │  └── Canvas 邊界框繪製    │              │  ├────────────────────────┤ │ │
│  └─────────────────────────┘              │  │ Camera Manager       │ │ │
│                                            │  │ ├── Webcam 擷取       │ │ │
│                                            │  │ ├── Frame → Device    │ │ │
│                                            │  │ └── MJPEG/WS 串流     │ │ │
│                                            │  └──────────┬───────────┘ │ │
│                                            │             │ USB         │ │
│  ┌──────────────┐                          │             ▼             │ │
│  │  USB Webcam   │─── Video ──────────────►│  ┌──────────────────────┐ │ │
│  │  (UVC)        │                         │  │ Device Drivers       │ │ │
│  └──────────────┘                          │  │ ├── Kneron KL720     │ │ │
│                                            │  │ ├── Kneron KL730     │ │ │
│                                            │  │ └── (擴充)           │ │ │
│                                            │  └──────────┬───────────┘ │ │
│                                            └─────────────┼─────────────┘ │
│                                                          │               │
│                                                          ▼               │
│                                               ┌──────────────────┐      │
│                                               │  Edge Device      │      │
│                                               │  (Kneron Dongle)  │      │
│                                               └──────────────────┘      │
└─────────────────────────────────────────────────────────────────────────┘
          │
          │ HTTPS（選用，Post-MVP）
          ▼
┌──────────────────┐
│  雲端服務（選用）  │
│  ● 模型儲存庫     │
│  ● 使用者帳號     │
└──────────────────┘
```

### 1.2 技術選型總覽

| 層面 | 技術選型 | 版本 | 選型理由 |
|------|---------|------|---------|
| **前端框架** | Next.js (React) | 15.x | SSG 輸出、生態系成熟、元件庫豐富 |
| **UI 框架** | shadcn/ui + Tailwind CSS | latest | 可客製化、不鎖定、輕量 |
| **狀態管理** | Zustand | 5.x | 輕量、適合即時資料流、無 boilerplate |
| **後端語言** | Go | 1.22+ | 跨平台編譯、單一 binary、串列埠函式庫成熟 |
| **HTTP 框架** | Gin 或 Echo | latest | 高效能、middleware 豐富 |
| **WebSocket** | gorilla/websocket | latest | Go 生態最成熟的 WS 實作 |
| **串列埠** | go.bug.st/serial | latest | 跨平台、純 Go 實作 |
| **USB 偵測** | gousb / 平台原生 | latest | USB 裝置偵測與熱插拔 |
| **Kneron SDK** | Kneron PLUS SDK (C API) | 1.3.x | 透過 CGo 呼叫或 subprocess |
| **打包工具** | GoReleaser | latest | 跨平台 binary 打包與發佈 |
| **Camera 擷取** | OpenCV (gocv) / ffmpeg | latest | 後端擷取 webcam 影像，送至裝置推論 |

### 1.3 設計原則

1. **本地優先 (Local-first)**：所有核心功能在本地端完成，不依賴雲端
2. **Driver 抽象化**：從第一天就設計裝置抽象層，確保未來硬體擴展不需重構
3. **前後端分離**：前端透過 REST + WebSocket 與後端通訊，後端同時提供靜態檔案服務
4. **零依賴安裝**：使用者只需下載一個執行檔，前端 build 後嵌入 binary
5. **漸進式複雜度**：UI 預設簡潔，進階功能可展開
6. **Camera-in-the-loop**：Camera 影像擷取由後端負責，送至 AI 硬體推論後將結果與畫面同步回傳前端

---

## 2. 前端架構設計

### 2.1 技術棧

```
Next.js 15.x (App Router)
├── React 19
├── TypeScript 5.x
├── Tailwind CSS 4.x
├── shadcn/ui (元件庫)
├── Zustand 5.x (狀態管理)
├── Recharts (圖表)
├── Lucide Icons (圖示)
└── next export (靜態輸出，嵌入 Go binary)
```

### 2.2 為什麼選 Next.js + React

| 考量面向 | Next.js (React) | Nuxt (Vue) | SvelteKit | 評估 |
|---------|:-:|:-:|:-:|------|
| 元件生態系 | ★★★★★ | ★★★★ | ★★★ | React 的 Dashboard/資料視覺化元件最豐富 |
| 靜態輸出 | ★★★★★ | ★★★★ | ★★★★ | `next export` 成熟，可直接嵌入 Go binary |
| WebSocket 整合 | ★★★★★ | ★★★★ | ★★★★ | 搭配 Zustand 管理即時資料流 |
| Canvas/繪圖 | ★★★★★ | ★★★★ | ★★★ | 邊界框繪製、影像疊加等需求，React Canvas 生態最成熟 |
| 團隊熟悉度 | 考量中 | 考量中 | 考量中 | 需確認 |
| 長期維護性 | ★★★★★ | ★★★★ | ★★★ | React 社群最大，招募最容易 |

### 2.3 頁面結構

```
/ (首頁 / Dashboard)
├── /models                    ← Step 1：模型庫
│   ├── /models?type=...       ← 篩選結果
│   └── /models/[id]           ← 模型詳情
├── /devices                   ← Step 2：裝置管理
│   └── /devices/[id]          ← 裝置詳情/燒錄
├── /workspace                 ← Step 3：即時推論工作區
│   └── /workspace/[deviceId]  ← 單一裝置推論介面
└── /settings                  ← 設定
```

### 2.4 元件架構

```
src/
├── app/                           # Next.js App Router
│   ├── layout.tsx                 # 全域 Layout (側邊欄 + 頂部列)
│   ├── page.tsx                   # Dashboard / 首頁
│   ├── models/
│   │   ├── page.tsx               # 模型庫列表
│   │   └── [id]/page.tsx          # 模型詳情
│   ├── devices/
│   │   ├── page.tsx               # 裝置列表
│   │   └── [id]/page.tsx          # 裝置詳情 + 燒錄
│   └── workspace/
│       └── [deviceId]/page.tsx    # 即時推論工作區
│
├── components/
│   ├── ui/                        # shadcn/ui 基礎元件
│   │   ├── button.tsx
│   │   ├── card.tsx
│   │   ├── dialog.tsx
│   │   ├── progress.tsx
│   │   ├── slider.tsx
│   │   └── ...
│   │
│   ├── layout/                    # 版面元件
│   │   ├── sidebar.tsx            # 側邊導覽列
│   │   ├── header.tsx             # 頂部列 (裝置狀態指示)
│   │   └── connection-status.tsx  # Server 連線狀態
│   │
│   ├── models/                    # 模型庫相關元件
│   │   ├── model-card.tsx         # 模型卡片
│   │   ├── model-grid.tsx         # 模型卡片網格
│   │   ├── model-filters.tsx      # 篩選面板
│   │   ├── model-detail.tsx       # 模型詳情
│   │   └── model-compatibility.tsx # 硬體相容性徽章
│   │
│   ├── devices/                   # 裝置相關元件
│   │   ├── device-list.tsx        # 裝置列表
│   │   ├── device-card.tsx        # 裝置卡片 (狀態、類型)
│   │   ├── device-status.tsx      # 裝置即時狀態
│   │   ├── flash-dialog.tsx       # 燒錄確認對話框
│   │   └── flash-progress.tsx     # 燒錄進度條
│   │
│   ├── inference/                 # 推論結果相關元件
│   │   ├── inference-panel.tsx    # 推論主面板
│   │   ├── classification-result.tsx  # 分類結果 (長條圖)
│   │   ├── detection-canvas.tsx   # 物件偵測 Canvas (邊界框)
│   │   ├── performance-metrics.tsx # FPS / 延遲指標
│   │   ├── confidence-slider.tsx  # Confidence Threshold 滑桿
│   │   └── device-log.tsx         # 裝置日誌 (展開/收合)
│   │
│   └── camera/                    # Camera 相關元件
│       ├── camera-feed.tsx        # Camera 即時影像顯示 (MJPEG/WS)
│       ├── camera-overlay.tsx     # 推論結果疊加層 (Canvas: 邊界框+標籤)
│       ├── camera-controls.tsx    # Camera 控制面板 (選擇/解析度/開關)
│       ├── camera-selector.tsx    # Camera 裝置選擇下拉選單
│       └── camera-inference-view.tsx  # Camera + 推論整合檢視 (主元件)
│
│   ├── dashboard/                   # 儀表板元件
│   │   ├── activity-timeline.tsx    # 活動時間線
│   │   └── connected-devices-list.tsx # 已連線裝置列表
│   │
│   ├── theme-sync.tsx               # 主題同步（Zustand → <html> class）
│   └── lang-sync.tsx                # 語言同步（Zustand → <html> lang）
│
├── stores/                        # Zustand 狀態管理
│   ├── device-store.ts            # 裝置狀態 (列表、連線、狀態)
│   ├── model-store.ts             # 模型庫狀態 (列表、篩選、選取)
│   ├── inference-store.ts         # 推論結果狀態 (即時資料)
│   ├── camera-store.ts            # Camera 狀態 (選擇、解析度、串流狀態)
│   └── flash-store.ts             # 燒錄狀態 (進度、錯誤)
│   ├── settings-store.ts            # 設定狀態 (主題、語言、persist)
│   ├── activity-store.ts            # 活動記錄 (時間線事件)
│   └── device-preferences-store.ts  # 裝置偏好 (alias、notes、persist)
│
├── hooks/                         # 自訂 Hooks
│   ├── use-websocket.ts           # WebSocket 連線管理
│   ├── use-device-events.ts       # 裝置插拔事件
│   ├── use-inference-stream.ts    # 推論結果串流
│   ├── use-camera-stream.ts       # Camera 影像串流訂閱
│   └── use-flash-progress.ts      # 燒錄進度訂閱
│
├── lib/                           # 工具函式
│   ├── api.ts                     # REST API client
│   ├── i18n/                     # 多語系模組
│   │   ├── types.ts              # TranslationDict 介面 + Paths 型別
│   │   ├── en.ts                 # 英文翻譯字典
│   │   ├── zh-TW.ts             # 繁體中文翻譯字典
│   │   └── index.ts             # useTranslation / getTranslation
│   ├── toast.ts                  # Toast 通知工具
│   ├── ws.ts                      # WebSocket client
│   └── constants.ts               # 常數定義
│
└── types/                         # TypeScript 型別定義
    ├── device.ts                  # 裝置相關型別
    ├── model.ts                   # 模型相關型別
    ├── inference.ts               # 推論結果型別
    └── api.ts                     # API 回應型別
```

### 2.5 狀態管理設計 (Zustand)

```typescript
// stores/device-store.ts
interface DeviceState {
  devices: Device[];
  selectedDevice: Device | null;
  connectionStatus: 'connected' | 'disconnected' | 'connecting';

  // Actions
  setDevices: (devices: Device[]) => void;
  selectDevice: (id: string) => void;
  updateDeviceStatus: (id: string, status: DeviceStatus) => void;
}

// stores/inference-store.ts
interface InferenceState {
  isRunning: boolean;
  results: InferenceResult[];        // 最近 N 筆結果
  latestResult: InferenceResult | null;
  fps: number;
  latency: number;
  confidenceThreshold: number;

  // Actions
  addResult: (result: InferenceResult) => void;
  setConfidenceThreshold: (value: number) => void;
  clearResults: () => void;
  startInference: () => void;
  stopInference: () => void;
}

// stores/flash-store.ts
interface FlashState {
  isFlashing: boolean;
  progress: number;                 // 0-100
  stage: 'idle' | 'preparing' | 'transferring' | 'verifying' | 'rebooting' | 'done' | 'error';
  error: string | null;

  // Actions
  startFlash: (deviceId: string, modelId: string) => void;
  updateProgress: (progress: number, stage: string) => void;
  setError: (error: string) => void;
  reset: () => void;
}
```

### 2.6 Camera 狀態管理與元件設計

#### Camera Store

```typescript
// stores/camera-store.ts
interface CameraState {
  cameras: CameraDevice[];           // 可用的 Camera 列表
  selectedCameraId: string | null;   // 目前選擇的 Camera
  isStreaming: boolean;              // 是否正在串流
  resolution: { width: number; height: number };
  streamUrl: string | null;          // MJPEG 串流 URL

  // Actions
  setCameras: (cameras: CameraDevice[]) => void;
  selectCamera: (id: string) => void;
  setResolution: (width: number, height: number) => void;
  setStreaming: (streaming: boolean) => void;
  setStreamUrl: (url: string | null) => void;
}

interface CameraDevice {
  id: string;
  name: string;            // e.g., "FaceTime HD Camera", "USB Camera"
  index: number;           // OpenCV camera index
  resolutions: { width: number; height: number }[];  // 支援的解析度
}
```

#### Camera 推論整合檢視元件

```
┌──────────────────────────────────────────────────────────────┐
│  Camera Inference View                                       │
│  ┌────────────────────────────────┐  ┌─────────────────────┐│
│  │                                │  │ 推論結果面板         ││
│  │   Camera Feed                  │  │                     ││
│  │   (MJPEG 串流)                 │  │ 🏷 person   92%     ││
│  │                                │  │ 🏷 car      85%     ││
│  │   ┌─────────┐                  │  │ 🏷 dog      23%     ││
│  │   │ person  │ ← 邊界框疊加     │  │                     ││
│  │   │  92%    │   (Canvas Layer)  │  │ ─────────────────── ││
│  │   └─────────┘                  │  │ FPS: 24.3           ││
│  │                                │  │ Latency: 38ms       ││
│  │                                │  │ Model: YOLOv5-face  ││
│  └────────────────────────────────┘  │                     ││
│  ┌────────────────────────────────┐  │ Threshold: ──●──── ││
│  │ Camera: [FaceTime HD ▼]       │  │              0.65   ││
│  │ Resolution: [640x480 ▼]       │  │                     ││
│  │ [▶ Start] [⏹ Stop]            │  │ [📋 Log ▼]          ││
│  └────────────────────────────────┘  └─────────────────────┘│
└──────────────────────────────────────────────────────────────┘
```

#### Camera Feed 元件設計

```typescript
// components/camera/camera-feed.tsx
// 顯示後端 MJPEG 串流的影像

interface CameraFeedProps {
  streamUrl: string;              // MJPEG stream URL from backend
  onFrameSize?: (w: number, h: number) => void;
}

// 實作方式：<img> tag 直接載入 MJPEG stream URL
// MJPEG 是 motion JPEG，瀏覽器原生支援，無需特殊解碼
// <img src="http://localhost:3721/api/camera/stream?id=0" />


// components/camera/camera-overlay.tsx
// 在 Camera Feed 上方疊加推論結果的 Canvas 層

interface CameraOverlayProps {
  width: number;
  height: number;
  detections: DetectionResult[];     // 邊界框 + 標籤
  classifications: ClassResult[];    // 分類結果
  confidenceThreshold: number;
}

// 使用 Canvas 2D API 繪製：
// - 邊界框 (不同顏色對應不同類別)
// - 類別標籤 + 信心分數
// - 僅顯示 confidence > threshold 的結果
```

### 2.7 WebSocket Hook 設計

```typescript
// hooks/use-websocket.ts
function useWebSocket(url: string, options?: {
  onMessage?: (data: any) => void;
  onConnect?: () => void;
  onDisconnect?: () => void;
  reconnect?: boolean;
  reconnectInterval?: number;
}) {
  // 自動連線、斷線重連、心跳檢測
  // 回傳: { send, disconnect, status }
}

// hooks/use-inference-stream.ts
function useInferenceStream(deviceId: string) {
  const ws = useWebSocket(`ws://localhost:3721/ws/devices/${deviceId}/inference`, {
    onMessage: (data) => {
      useInferenceStore.getState().addResult(data);
    }
  });
  return ws;
}

// hooks/use-camera-stream.ts
function useCameraStream(cameraId: string) {
  const { setStreamUrl, setStreaming } = useCameraStore();

  const start = async (deviceId: string) => {
    // POST /api/camera/start → 啟動後端 camera 擷取 + 推論 pipeline
    // 取得 MJPEG stream URL
    const res = await api.post(`/api/camera/start`, { cameraId, deviceId });
    setStreamUrl(res.data.streamUrl);
    setStreaming(true);
  };

  const stop = async () => {
    await api.post(`/api/camera/stop`);
    setStreamUrl(null);
    setStreaming(false);
  };

  return { start, stop };
}
```

### 2.7 靜態輸出策略

前端使用 `next export` 產生純靜態檔案（HTML/CSS/JS），嵌入 Go binary：

```
next build → out/ 目錄 → Go embed → 單一執行檔
```

Go 端使用 `embed.FS` 提供靜態檔案服務：

```go
//go:embed frontend/out/*
var frontendFS embed.FS

func main() {
    // 將前端靜態檔案掛載到 HTTP server
    http.Handle("/", http.FileServer(http.FS(frontendFS)))
}
```

---

## 3. 後端架構設計（本地端 Backend Server）

### 3.1 模組架構

```
server/
├── main.go                        # 入口點
│
├── cmd/                           # CLI 命令
│   └── root.go                    # 啟動 server、版本資訊
│
├── internal/
│   ├── api/                       # HTTP API 層
│   │   ├── router.go              # 路由定義
│   │   ├── middleware/             # 中間件 (CORS, logging)
│   │   ├── handlers/
│   │   │   ├── device_handler.go  # /api/devices 端點
│   │   │   ├── model_handler.go   # /api/models 端點
│   │   │   └── system_handler.go  # /api/system 端點
│   │   └── ws/
│   │       ├── hub.go             # WebSocket 連線管理
│   │       ├── inference_ws.go    # 推論結果 WS 端點
│   │       ├── flash_ws.go        # 燒錄進度 WS 端點
│   │       └── device_events_ws.go # 裝置事件 WS 端點
│   │
│   ├── device/                    # 裝置管理核心
│   │   ├── manager.go             # Device Manager (偵測、生命週期)
│   │   ├── registry.go            # Driver Registry
│   │   ├── session.go             # 裝置 Session 管理
│   │   └── types.go               # 裝置相關型別
│   │
│   ├── driver/                    # 裝置驅動層
│   │   ├── interface.go           # DeviceDriver 介面定義
│   │   ├── kneron/
│   │   │   ├── kl720_driver.go    # Kneron Driver (KL520/KL720, JSON-RPC → Python bridge)
│   │   │   └── detector.go        # USB 裝置偵測 (透過 Python bridge scan)
│   │   └── mock/
│   │       └── mock_driver.go     # 模擬驅動 (開發/測試用)
│   │
│   ├── model/                     # 模型管理
│   │   ├── repository.go          # 模型中繼資料儲存
│   │   ├── downloader.go          # 模型檔案下載
│   │   └── types.go               # 模型相關型別
│   │
│   ├── flash/                     # 燒錄服務
│   │   ├── service.go             # 燒錄流程控制
│   │   └── progress.go            # 進度追蹤與回報
│   │
│   ├── inference/                 # 推論服務
│   │   ├── service.go             # 推論流程控制
│   │   ├── parser.go              # 推論結果解析
│   │   └── stream.go              # 結果串流管理
│   │
│   ├── camera/                    # Camera 管理
│   │   ├── manager.go             # Camera Manager (偵測、開啟、串流)
│   │   ├── capture.go             # 影像擷取 (OpenCV/ffmpeg)
│   │   ├── mjpeg.go               # MJPEG 串流 HTTP handler
│   │   └── pipeline.go            # Camera → Device 推論 Pipeline
│   │
│   └── config/                    # 設定管理
│       └── config.go              # 設定檔讀取、預設值
│
├── pkg/                           # 可重用套件
│   ├── serial/                    # 串列埠工具
│   │   └── serial.go              # go.bug.st/serial 封裝
│   ├── usb/                       # USB 工具
│   │   └── monitor.go             # USB 裝置熱插拔監聽
│   └── logger/                    # 日誌工具
│       └── logger.go
│
└── frontend/                      # 嵌入的前端靜態檔案
    └── out/                       # next export 輸出
```

### 3.2 核心模組設計

#### 3.2.1 Device Manager

```go
// internal/device/manager.go

type DeviceManager struct {
    registry    *DriverRegistry
    sessions    map[string]*DeviceSession
    usbMonitor  *usb.Monitor
    eventBus    chan DeviceEvent
    mu          sync.RWMutex
}

// 核心方法
func (m *DeviceManager) Start() error                          // 啟動 USB 監聽
func (m *DeviceManager) Stop()                                 // 停止
func (m *DeviceManager) ListDevices() []DeviceInfo              // 列出裝置
func (m *DeviceManager) GetDevice(id string) (*DeviceInfo, error)
func (m *DeviceManager) Connect(id string) error                // 連線
func (m *DeviceManager) Disconnect(id string) error             // 斷線
func (m *DeviceManager) Flash(id string, modelPath string, progressCh chan<- FlashProgress) error
func (m *DeviceManager) StartInference(id string, resultCh chan<- InferenceResult) error
func (m *DeviceManager) StopInference(id string) error
func (m *DeviceManager) Events() <-chan DeviceEvent             // 裝置事件串流
```

#### 3.2.2 Driver Registry

```go
// internal/device/registry.go

type DriverRegistry struct {
    drivers []DriverFactory
}

type DriverFactory struct {
    Name     string
    Match    func(usbInfo USBDeviceInfo) bool   // VID/PID 比對
    Create   func(usbInfo USBDeviceInfo) (DeviceDriver, error)
}

func (r *DriverRegistry) Register(factory DriverFactory)
func (r *DriverRegistry) FindDriver(usbInfo USBDeviceInfo) (DeviceDriver, error)
```

#### 3.2.3 WebSocket Hub

```go
// internal/api/ws/hub.go

type Hub struct {
    rooms      map[string]map[*Client]bool  // roomId → clients
    register   chan *Subscription
    unregister chan *Subscription
    broadcast  chan *Message
}

type Subscription struct {
    Client *Client
    Room   string   // e.g., "inference:device-123", "flash:device-123"
}

// 前端訂閱特定裝置的推論結果或燒錄進度
// Hub 負責將 DeviceManager 的事件分發到對應的 WebSocket 客戶端
```

### 3.3 Kneron SDK 整合策略

由於 Kneron PLUS SDK 是 C/C++ API，有兩種整合方式：

| 方式 | 優點 | 缺點 | 建議 |
|------|------|------|------|
| **CGo 直接呼叫** | 效能好、整合緊密 | 交叉編譯複雜、需 C 編譯環境 | Phase 1+ |
| **Python subprocess** | 開發快、隔離好 | 需安裝 Python、效能略差 | MVP 階段 |

**MVP 建議方案：** 使用 Python subprocess 呼叫 Kneron PLUS Python SDK，透過 JSON 通訊。後續再以 CGo 替換。

```go
// internal/driver/kneron/kneron_sdk.go

type KneronSDK struct {
    pythonPath string
    scriptDir  string
}

func (sdk *KneronSDK) LoadModel(deviceIndex int, nefPath string) error {
    // 呼叫 Python script: kp_load_model_from_file()
    cmd := exec.Command(sdk.pythonPath, filepath.Join(sdk.scriptDir, "load_model.py"),
        "--device-index", strconv.Itoa(deviceIndex),
        "--nef-path", nefPath,
    )
    // 解析 JSON 輸出
}

func (sdk *KneronSDK) StartInference(deviceIndex int, resultCh chan<- InferenceResult) error {
    // 啟動長時間運行的 Python 推論 process
    // 透過 stdout JSON lines 串流結果
}
```

### 3.4 Mock Driver（開發/測試用）

為了在沒有實體硬體的情況下開發和測試前後端整合：

```go
// internal/driver/mock/mock_driver.go

type MockDriver struct {
    deviceType string
    connected  bool
}

func (d *MockDriver) Detect() bool { return true }

func (d *MockDriver) Flash(binary []byte, progressCh chan<- FlashProgress) error {
    // 模擬燒錄進度：每 200ms 增加 5%
    for i := 0; i <= 100; i += 5 {
        progressCh <- FlashProgress{Percent: i, Stage: "transferring"}
        time.Sleep(200 * time.Millisecond)
    }
    return nil
}

func (d *MockDriver) ReadInference() (*InferenceResult, error) {
    // 回傳模擬的推論結果
    return &InferenceResult{
        Type: "classification",
        Results: []ClassResult{
            {Label: "person", Confidence: 0.92},
            {Label: "car", Confidence: 0.05},
        },
        LatencyMs: 28,
    }, nil
}
```

### 3.5 Camera Manager 設計

KL720 Dongle 本身沒有攝影機，推論流程是：**電腦端 Webcam 擷取影像 → 透過 USB 送到 KL720 NPU 推論 → 推論結果回傳 → 前端顯示影像 + 疊加邊界框**。

Camera 相關的影像擷取由 **後端 Go Server** 負責（而非前端瀏覽器），原因：

| 方案 | 優點 | 缺點 | 決策 |
|------|------|------|------|
| **後端擷取 (Go/OpenCV)** | 直接將 frame 送到裝置推論，無需前端→後端傳輸影像；可背景執行 | 需 OpenCV 或 ffmpeg 依賴 | **採用** |
| 前端擷取 (getUserMedia) | 不需後端處理影像 | 影像需從瀏覽器→後端→裝置，延遲高；關頁面斷流 | 不採用 |

#### Camera-Inference Pipeline 架構

```
┌──────────┐    USB/UVC     ┌─────────────────────────────────────────────────┐
│  Webcam   │───────────────►│  Go Backend Server                              │
│  (UVC)    │    frame       │                                                 │
└──────────┘                │  ┌─────────────┐   frame   ┌─────────────────┐ │
                             │  │ Camera      │──────────►│ Inference       │ │
                             │  │ Manager     │           │ Pipeline        │ │
                             │  │             │           │                 │ │
                             │  │ OpenCV /    │  result   │ frame → resize  │ │
                             │  │ gocv        │◄──────────│ → send to USB   │ │
                             │  │             │           │ → read result   │ │
                             │  └──────┬──────┘           └────────┬────────┘ │
                             │         │                           │          │
                             │    MJPEG stream              WS push          │
                             │    (HTTP /api/camera/stream)  (inference)      │
                             │         │                           │          │
                             └─────────┼───────────────────────────┼──────────┘
                                       │                           │
                                       ▼                           ▼
                             ┌─────────────────────────────────────────────────┐
                             │  Browser (React)                                │
                             │                                                 │
                             │  ┌──────────────────┐  ┌──────────────────────┐│
                             │  │ <img> MJPEG      │  │ <canvas> overlay     ││
                             │  │ Camera 即時影像   │  │ 邊界框 + 標籤        ││
                             │  │                  │  │ (InferenceResult)    ││
                             │  └──────────────────┘  └──────────────────────┘│
                             └─────────────────────────────────────────────────┘
```

#### Camera Manager 實作

```go
// internal/camera/manager.go

type CameraManager struct {
    cameras    []CameraInfo
    capture    *gocv.VideoCapture  // OpenCV camera capture
    streaming  bool
    frameCh    chan gocv.Mat        // 影像 frame channel
    mu         sync.Mutex
}

type CameraInfo struct {
    ID          string `json:"id"`
    Name        string `json:"name"`
    Index       int    `json:"index"`       // OpenCV camera index (0, 1, 2...)
    Width       int    `json:"width"`
    Height      int    `json:"height"`
}

// 列出可用 Camera
func (m *CameraManager) ListCameras() ([]CameraInfo, error) {
    // 嘗試開啟 index 0, 1, 2... 偵測可用 camera
}

// 開啟 Camera
func (m *CameraManager) Open(index int, width, height int) error {
    m.capture = gocv.OpenVideoCapture(index)
    m.capture.Set(gocv.VideoCaptureFrameWidth, float64(width))
    m.capture.Set(gocv.VideoCaptureFrameHeight, float64(height))
}

// 關閉 Camera
func (m *CameraManager) Close() error

// 讀取一幀
func (m *CameraManager) ReadFrame() (gocv.Mat, error)
```

#### Camera-Inference Pipeline

```go
// internal/camera/pipeline.go

type InferencePipeline struct {
    camera      *CameraManager
    device      driver.DeviceDriver
    resultCh    chan *driver.InferenceResult
    frameCh     chan []byte                    // JPEG encoded frames for MJPEG
    running     bool
    mu          sync.Mutex
}

// 核心推論迴圈
func (p *InferencePipeline) Run(ctx context.Context) error {
    for {
        select {
        case <-ctx.Done():
            return nil
        default:
            // 1. 從 Camera 擷取一幀
            frame, err := p.camera.ReadFrame()

            // 2. 編碼為 JPEG → 送到 MJPEG 串流 (給前端顯示)
            jpegBuf := encodeJPEG(frame)
            p.frameCh <- jpegBuf

            // 3. 前處理 (resize 到模型輸入大小)
            resized := preprocess(frame, modelInputWidth, modelInputHeight)

            // 4. 送到裝置推論 (透過 Kneron PLUS SDK)
            result, err := p.device.RunInference(resized)

            // 5. 推論結果送到 WebSocket (給前端疊加邊界框)
            p.resultCh <- result
        }
    }
}
```

#### MJPEG 串流 HTTP Handler

```go
// internal/camera/mjpeg.go

// MJPEG 是 motion JPEG 串流格式，瀏覽器 <img> tag 原生支援
// Content-Type: multipart/x-mixed-replace; boundary=frame
//
// 前端只需: <img src="http://localhost:3721/api/camera/stream" />

func (h *MJPEGHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "multipart/x-mixed-replace; boundary=frame")
    w.Header().Set("Cache-Control", "no-cache")

    for {
        select {
        case <-r.Context().Done():
            return
        case jpegFrame := <-h.frameCh:
            // 寫入 MJPEG boundary + JPEG data
            fmt.Fprintf(w, "--frame\r\n")
            fmt.Fprintf(w, "Content-Type: image/jpeg\r\n")
            fmt.Fprintf(w, "Content-Length: %d\r\n\r\n", len(jpegFrame))
            w.Write(jpegFrame)
            fmt.Fprintf(w, "\r\n")
            w.(http.Flusher).Flush()
        }
    }
}
```

#### Driver 介面擴充 — Camera 推論

```go
// 在 DeviceDriver 介面中新增 Camera 推論方法

type DeviceDriver interface {
    // ... 原有方法 ...

    // Camera 推論（送入影像 frame，回傳推論結果）
    RunInference(imageData []byte) (*InferenceResult, error)

    // 取得模型所需的輸入尺寸
    GetInputSize() (width int, height int, error)
}
```

#### 影像擷取技術選型

| 方案 | 優點 | 缺點 | 評估 |
|------|------|------|------|
| **gocv (OpenCV Go binding)** | 功能完整、影像處理能力強、Camera 支援好 | 需安裝 OpenCV C 函式庫 (可靜態連結) | **MVP 推薦** |
| ffmpeg (透過 subprocess) | 不需 CGo、格式支援廣 | 啟動慢、控制粒度粗 | 備選 |
| v4l2 (Linux) / AVFoundation (macOS) | 零依賴、效能最佳 | 需各平台實作、開發量大 | 不建議 MVP |

> **注意：** 使用 gocv 會引入 OpenCV 依賴，影響「單一執行檔零依賴」的目標。
> 緩解策略：靜態連結 OpenCV 或提供帶 OpenCV 的安裝包，或 MVP 先使用 ffmpeg subprocess 方案。

---

## 4. 前後端通訊設計

### 4.1 REST API 規格

#### 裝置管理 API

```
GET    /api/devices
       回應: { devices: Device[] }
       說明: 列出所有已偵測到的裝置

GET    /api/devices/:id
       回應: { device: DeviceDetail }
       說明: 取得單一裝置詳細資訊

POST   /api/devices/:id/connect
       回應: { success: boolean }
       說明: 建立與裝置的連線

POST   /api/devices/:id/disconnect
       回應: { success: boolean }
       說明: 中斷裝置連線

POST   /api/devices/:id/flash
       請求: { modelId: string }
       回應: { taskId: string }
       說明: 開始燒錄流程（進度透過 WebSocket 回報）

GET    /api/devices/:id/status
       回應: { status: DeviceStatus }
       說明: 取得裝置當前狀態

POST   /api/devices/:id/inference/start
       回應: { success: boolean }
       說明: 啟動推論（結果透過 WebSocket 串流）

POST   /api/devices/:id/inference/stop
       回應: { success: boolean }
       說明: 停止推論
```

#### 模型管理 API

```
GET    /api/models
       查詢: ?type=classification&hardware=kl720&q=face
       回應: { models: ModelSummary[], total: number }
       說明: 列出模型，支援篩選與搜尋

GET    /api/models/:id
       回應: { model: ModelDetail }
       說明: 取得模型詳細資訊

POST   /api/models/:id/download
       回應: { taskId: string, status: "downloading" | "cached" }
       說明: 下載模型到本地快取
```

#### 媒體管理 API

```
POST   /api/media/upload/image
       請求: multipart/form-data (file + deviceId)
       回應: { success: boolean, filename: string }
       說明: 上傳圖片檔案作為推論輸入

POST   /api/media/upload/video
       請求: multipart/form-data (file + deviceId)
       回應: { success: boolean, filename: string }
       說明: 上傳影片檔案作為推論輸入

POST   /api/media/url
       請求: { url: string, deviceId: string }
       回應: { success: boolean, resolvedUrl?: string }
       說明: 提供影片 URL 作為推論輸入，支援直接連結、YouTube（yt-dlp 解析）、RTSP
```

#### 自訂模型上傳 API

```
POST   /api/models/upload
       請求: multipart/form-data (file + metadata JSON)
       回應: { success: boolean, model: ModelSummary }
       說明: 上傳自訂 .nef 模型

DELETE /api/models/:id
       回應: { success: boolean }
       說明: 刪除自訂模型
```

#### Camera API

```
GET    /api/camera/list
       回應: { cameras: CameraDevice[] }
       說明: 列出所有可用的 Camera 裝置

POST   /api/camera/start
       請求: { cameraId: string, deviceId: string, resolution?: { width, height } }
       回應: { streamUrl: string, inputSize: { width, height } }
       說明: 啟動 Camera 擷取 + 推論 Pipeline
             回傳 MJPEG 串流 URL 供前端 <img> 顯示

POST   /api/camera/stop
       回應: { success: boolean }
       說明: 停止 Camera 擷取與推論

GET    /api/camera/stream
       查詢: ?id=0
       回應: multipart/x-mixed-replace (MJPEG 串流)
       說明: Camera 即時影像 MJPEG 串流端點
             前端直接 <img src="此URL"> 即可顯示
```

#### 系統 API

```
GET    /api/system/info
       回應: { version: string, platform: string, uptime: number }
       說明: Server 系統資訊

GET    /api/system/health
       回應: { status: "ok" }
       說明: 健康檢查
```

### 4.2 WebSocket 端點

```
ws://localhost:3721/ws/devices/events
  訊息格式: { event: "connected" | "disconnected" | "updated", device: Device }
  說明: 裝置插拔與狀態變更事件

ws://localhost:3721/ws/devices/:id/inference
  訊息格式: InferenceResult (JSON)
  說明: 即時推論結果串流

ws://localhost:3721/ws/devices/:id/flash-progress
  訊息格式: { percent: number, stage: string, message?: string }
  說明: 燒錄進度即時更新

ws://localhost:3721/ws/devices/:id/log
  訊息格式: { timestamp: string, level: string, message: string }
  說明: 裝置通訊日誌
```

### 4.3 資料流圖

#### Camera 推論資料流 (MVP 核心流程)

```
Webcam              Go Server                           Browser (React)
  │                     │                                    │
  │  UVC frame          │                                    │
  ├────────────────────►│                                    │
  │  (video capture)    │                                    │
  │                     │  ┌──────────────────────┐          │
  │                     │  │ Camera-Inference     │          │
  │                     │  │ Pipeline             │          │
  │                     │  │                      │          │
  │                     │  │ 1. capture frame     │          │
  │                     │  │ 2. encode JPEG ──────┼──── MJPEG stream ───────►│
  │                     │  │                      │    (HTTP multipart)      │  <img> 顯示
  │                     │  │ 3. resize frame      │                          │  Camera 畫面
  │                     │  │ 4. send to device ───┼──► KL720 Dongle         │
  │                     │  │                      │    (USB bulk transfer)   │
  │                     │  │ 5. read result ◄─────┼─── NPU inference        │
  │                     │  │ 6. push result ──────┼──── WebSocket ──────────►│
  │                     │  │                      │    { detections, fps }   │  Canvas overlay
  │                     │  └──────────────────────┘                          │  邊界框繪製
  │                     │                                                    │
  │                     │                                    ┌───────────────┤
  │                     │                                    │ 疊加結果到     │
  │                     │                                    │ Camera 畫面上  │
  │                     │                                    └───────────────┘
```

#### 裝置端直接推論資料流 (無 Camera)

```
Device (MCU)                   Go Server                       Browser (React)
    │                              │                                │
    │  Serial/USB data             │                                │
    ├─────────────────────────────►│                                │
    │  (raw inference output)      │                                │
    │                              │  parse & structure             │
    │                              ├──────┐                         │
    │                              │      │ InferenceResult         │
    │                              │◄─────┘                         │
    │                              │                                │
    │                              │  WebSocket push                │
    │                              ├───────────────────────────────►│
    │                              │  { type, results, latency }    │
    │                              │                                │  Zustand store
    │                              │                                ├──────┐
    │                              │                                │      │ update
    │                              │                                │◄─────┘
    │                              │                                │
    │                              │                                │  React re-render
    │                              │                                ├──────┐
    │                              │                                │      │ UI update
    │                              │                                │◄─────┘
```

### 4.4 通用 API 回應格式

```typescript
// 成功
{
  "success": true,
  "data": { ... }
}

// 錯誤
{
  "success": false,
  "error": {
    "code": "DEVICE_NOT_FOUND",
    "message": "裝置未找到",
    "detail": "未偵測到 ID 為 'abc123' 的裝置，請確認 USB 連接。"
  }
}
```

### 4.5 錯誤代碼

| 代碼 | HTTP | 說明 |
|------|------|------|
| `DEVICE_NOT_FOUND` | 404 | 裝置未找到 |
| `DEVICE_NOT_CONNECTED` | 409 | 裝置未連線 |
| `DEVICE_BUSY` | 409 | 裝置正在燒錄或推論中 |
| `MODEL_NOT_FOUND` | 404 | 模型未找到 |
| `MODEL_INCOMPATIBLE` | 400 | 模型與裝置不相容 |
| `FLASH_FAILED` | 500 | 燒錄失敗 |
| `FLASH_TIMEOUT` | 408 | 燒錄逾時 |
| `INFERENCE_ERROR` | 500 | 推論錯誤 |
| `SERIAL_ERROR` | 500 | 串列埠通訊錯誤 |
| `CAMERA_NOT_FOUND` | 404 | Camera 裝置未找到 |
| `CAMERA_BUSY` | 409 | Camera 已被其他 pipeline 使用 |
| `CAMERA_OPEN_FAILED` | 500 | Camera 開啟失敗 |
| `PIPELINE_ALREADY_RUNNING` | 409 | 推論 pipeline 已在執行中 |

---

## 5. 裝置通訊抽象層設計

### 5.1 Driver 介面

```go
// internal/driver/interface.go

package driver

// DeviceDriver 定義所有裝置驅動必須實作的介面
type DeviceDriver interface {
    // 基本資訊
    Info() DeviceInfo

    // 連線管理
    Connect() error
    Disconnect() error
    IsConnected() bool

    // 燒錄
    Flash(modelPath string, progressCh chan<- FlashProgress) error

    // 推論
    StartInference() error
    StopInference() error
    ReadInference() (*InferenceResult, error)

    // 命令
    SendCommand(cmd string) (string, error)

    // 系統
    GetSystemInfo() (*SystemInfo, error)
    GetModelInfo() (*ModelInfo, error)
}

// DeviceInfo 裝置基本資訊
type DeviceInfo struct {
    ID          string         `json:"id"`
    Name        string         `json:"name"`
    Type        string         `json:"type"`         // "kneron_kl720", "kneron_kl730", ...
    Port        string         `json:"port"`         // 串列埠路徑 or USB device path
    VendorID    uint16         `json:"vendorId"`
    ProductID   uint16         `json:"productId"`
    Status      DeviceStatus   `json:"status"`
    FirmwareVer string         `json:"firmwareVersion,omitempty"`
}

type DeviceStatus string

const (
    StatusDetected    DeviceStatus = "detected"     // 已偵測，未連線
    StatusConnecting  DeviceStatus = "connecting"   // 連線中
    StatusConnected   DeviceStatus = "connected"    // 已連線
    StatusFlashing    DeviceStatus = "flashing"     // 燒錄中
    StatusInferencing DeviceStatus = "inferencing"  // 推論中
    StatusError       DeviceStatus = "error"        // 錯誤
    StatusDisconnected DeviceStatus = "disconnected" // 已斷線
)

// FlashProgress 燒錄進度
type FlashProgress struct {
    Percent int    `json:"percent"`   // 0-100
    Stage   string `json:"stage"`     // preparing, transferring, verifying, rebooting, done
    Message string `json:"message,omitempty"`
}

// InferenceResult 推論結果
type InferenceResult struct {
    Type      string           `json:"type"`      // "classification" | "detection"
    Timestamp int64            `json:"timestamp"`  // Unix ms
    LatencyMs float64          `json:"latencyMs"`

    // 分類結果
    Classifications []ClassResult `json:"classifications,omitempty"`

    // 偵測結果
    Detections []DetectionResult `json:"detections,omitempty"`
}

type ClassResult struct {
    Label      string  `json:"label"`
    Confidence float64 `json:"confidence"` // 0.0 - 1.0
}

type DetectionResult struct {
    Label      string  `json:"label"`
    Confidence float64 `json:"confidence"`
    BBox       BBox    `json:"bbox"`
}

type BBox struct {
    X      float64 `json:"x"`       // 左上角 X (0.0-1.0, 歸一化)
    Y      float64 `json:"y"`       // 左上角 Y
    Width  float64 `json:"width"`   // 寬
    Height float64 `json:"height"`  // 高
}
```

### 5.2 Kneron Driver 實作策略（已實作，KL520 + KL720）

Go Driver 透過 Python subprocess（`kneron_bridge.py`）與 Kneron PLUS SDK 通訊，採用 JSON-RPC over stdin/stdout 模式。支援 KL520（USB Boot）和 KL720（Flash-based）兩種晶片。

```
Go Server ──stdin/stdout──► kneron_bridge.py ──ctypes──► libkplus.dylib ──USB──► KL520/KL720
                JSON-RPC                      kp module                  BULK EP
```

```go
// internal/driver/kneron/kl720_driver.go

type KneronDriver struct {
    info        driver.DeviceInfo
    chipType    string          // "KL520" or "KL720" — 從 info.Type 推導
    connected   bool
    inferring   bool
    modelLoaded string
    mu          sync.Mutex
    scriptPath  string          // kneron_bridge.py 路徑
    pythonCmd   *exec.Cmd       // Python 子進程
    stdin       io.WriteCloser  // JSON 指令輸入
    stdout      *bufio.Scanner  // JSON 回應輸出
    pythonReady bool
}

// chipType 推導邏輯
func NewKneronDriver(info driver.DeviceInfo) *KneronDriver {
    chip := "KL520"
    if strings.Contains(strings.ToLower(info.Type), "kl720") {
        chip = "KL720"
    }
    return &KneronDriver{info: info, chipType: chip, ...}
}
```

**USB 裝置識別：**

| 晶片 | Vendor ID | Product ID | USB | 韌體模式 |
|------|-----------|-----------|-----|---------|
| KL520 | 0x3231 | 0x0100 | 2.0 High-Speed | USB Boot（每次上傳） |
| KL720 (KDP2) | 0x3231 | 0x0720 | 3.0 Super-Speed | Flash-based（預燒） |
| KL720 (KDP legacy) | 0x3231 | 0x0200 | 3.0 Super-Speed | 舊版韌體，需更新 |

**Python Bridge 指令協定：**

| 指令 | 參數 | 回應 | 說明 |
|------|------|------|------|
| `scan` | — | `{devices: [{port, firmware, kn_number, product_id, connectable}]}` | USB 裝置掃描 |
| `connect` | `port`, `device_type` | `{status, firmware, kn_number, chip, kdp_legacy?}` | 晶片感知連線 + 自動韌體處理 |
| `load_model` | `path` | `{status, model_id, model_type, input_size, target_chip}` | NEF 模型載入 + 自動類型偵測 |
| `inference` | `image_base64` | `{taskType, latencyMs, detections[], classifications[]}` | 圖片推論（偵測或分類） |
| `reset` | — | `{status}` | 裝置重置（KP_RESET_REBOOT） |
| `disconnect` | — | `{status}` | 斷線 + 狀態清理 |

**KL520 連線流程（USB Boot 模式）：**

```
connect_devices(port_id)
  → set_timeout(10000ms)
  → 偵測 firmware 狀態
  → if "Loader": load_firmware_from_file(scpu, ncpu)
    → sleep(5s) 等待 reboot
    → 重新 scan + connect
  → 回傳 firmware 版本
```

**KL720 連線流程（Flash-based）：**

```
KL720 KDP2 (pid=0x0720):
  → connect_devices(port_id) — 正常連線
  → firmware 已在 flash，跳過上傳
  → 直接就緒

KL720 KDP legacy (pid=0x0200):
  → connect_devices_without_check(port_id) — 繞過韌體版本檢查
  → load_firmware_from_file(KL720/fw_scpu.bin, fw_ncpu.bin) — 載入 KDP2 到 RAM
  → sleep(5s) → reconnect
  → 提示使用者執行 update_kl720_firmware.py 永久更新
```

**KL720 KDP→KDP2 韌體更新流程（`update_kl720_firmware.py`）：**

```
1. scan_devices() → 找到 pid=0x0200 的 KL720
2. kp_connect_devices(port, &KDP_MAGIC_CONNECTION_PASS) — DFUT magic bypass
3. kp_update_kdp_firmware_from_files(dg, scpu, NULL) — flash SCPU
4. kp_reset_device() → sleep(3s) → reconnect
5. kp_update_kdp_firmware_from_files(dg, NULL, ncpu) — flash NCPU
6. kp_reset_device() → verify pid=0x0720
```

**晶片感知 Error 40 處理（`Flash()` 方法）：**

```
KL520 — Error 40 (KP_ERROR_USB_BOOT_LOAD_SECOND_MODEL_40):
  → restartBridge() — 完全重啟 Python 子進程
  → 重新 connect + load_firmware
  → retry load_model

KL720 — Error 40（理論上不會發生）:
  → 先直接 retry load_model（不需 restart）
  → 若仍失敗才 fallback 到 restartBridge()
```

**跨晶片模型路徑解析（`flash/service.go`）：**

```go
// resolveModelPath 自動替換晶片特定 NEF 路徑
// 例：data/nef/kl520/kl520_20001_... → data/nef/kl720/kl720_20001_...
func resolveModelPath(filePath string, deviceType string) string {
    targetChip := chipFromDeviceType(deviceType)  // "kl520" or "kl720"
    if alreadyTargetChip(filePath, targetChip) { return filePath }
    candidate := swapChipPrefix(filePath, sourceChip, targetChip)
    if fileExists(candidate) { return candidate }
    return filePath  // fallback 原路徑
}
```

**多模型後處理引擎（`kneron_bridge.py`）：**

| 模型類型 | Model ID | 輸入尺寸 | 後處理 | 適用晶片 |
|---------|----------|---------|--------|---------|
| Tiny YOLO v3 | 0/19 | 224×224 | 雙尺度 YOLO decode + NMS | KL520 |
| YOLOv5s | 20005 | 640×640 | 三尺度 YOLO decode + NMS | KL520, KL720 |
| FCOS | 20004 | 512×512 | 5 層 FPN decode + NMS | KL520, KL720 |
| SSD (人臉) | — | 320×320 | SSD bbox decode + NMS | KL520 |
| ResNet18 | 20001 | 224×224 | Softmax + Top-5 分類 | KL520, KL720 |

### 5.3 Driver 註冊流程

```go
// main.go or cmd/root.go

func setupDrivers(registry *device.DriverRegistry) {
    // 註冊 Kneron Driver（KL520 + KL720）
    // detector.go 的 chipFromProductID() 自動判斷晶片型號
    // KL520: PID=0x0100, KL720: PID=0x0200(KDP)/0x0720(KDP2)
    registry.Register(device.DriverFactory{
        Name: "kneron_kl520",
        Match: func(info USBDeviceInfo) bool {
            return info.VendorID == 0x3231 && info.ProductID == 0x0100
        },
        Create: func(info USBDeviceInfo) (DeviceDriver, error) {
            return kneron.NewKneronDriver(info)
        },
    })
    registry.Register(device.DriverFactory{
        Name: "kneron_kl720",
        Match: func(info USBDeviceInfo) bool {
            return info.VendorID == 0x3231 &&
                (info.ProductID == 0x0200 || info.ProductID == 0x0720)
        },
        Create: func(info USBDeviceInfo) (DeviceDriver, error) {
            return kneron.NewKneronDriver(info)
        },
    })

    // 註冊 Mock Driver（開發模式）
    if config.DevMode {
        registry.Register(device.DriverFactory{
            Name: "mock",
            Match: func(info USBDeviceInfo) bool { return true },
            Create: func(info USBDeviceInfo) (DeviceDriver, error) {
                return mock.NewMockDriver(), nil
            },
        })
    }
}
```

---

## 6. 模型庫設計

### 6.1 模型中繼資料結構

```typescript
// types/model.ts

interface Model {
  id: string;
  name: string;
  description: string;
  thumbnail: string;            // 縮圖 URL

  // 分類
  taskType: "classification" | "detection" | "segmentation";
  categories: string[];          // ["face", "vehicle", "industrial"]

  // 技術規格
  framework: string;             // "kneron_nef" | "onnx" | "tflite"
  inputSize: { width: number; height: number };
  modelSize: number;             // bytes
  quantization: "int8" | "float16" | "float32";

  // 效能指標
  accuracy: number;              // 0-100 (%)
  latencyMs: number;             // 推論延遲
  fps: number;                   // 在目標硬體上的 FPS

  // 相容性
  supportedHardware: string[];   // ["kneron_kl720", "kneron_kl730"]

  // 模型內容
  labels: string[];              // 類別標籤列表
  sampleImages: string[];        // 範例圖片

  // 元資料
  version: string;
  author: string;
  license: string;
  createdAt: string;
  updatedAt: string;
}
```

### 6.2 MVP 模型庫資料來源

MVP 階段使用本地 JSON 檔案作為模型中繼資料儲存：

```
server/data/
├── models.json                  # 模型中繼資料列表
└── models/                      # 模型檔案目錄
    ├── kneron-face-detect-v1.nef
    ├── kneron-person-detect-v1.nef
    └── ...
```

後續可擴展為：
- 遠端模型庫 API
- 社群模型市集
- Kneron 官方模型庫整合

### 6.3 模型篩選邏輯

```go
// internal/model/repository.go

type ModelFilter struct {
    TaskType   string   `query:"type"`       // classification, detection
    Hardware   string   `query:"hardware"`   // kneron_kl720
    Category   string   `query:"category"`   // face, vehicle
    Query      string   `query:"q"`          // 關鍵字搜尋
    SortBy     string   `query:"sortBy"`     // name, accuracy, size
    SortOrder  string   `query:"order"`      // asc, desc
    Page       int      `query:"page"`
    PageSize   int      `query:"pageSize"`
}

func (r *Repository) List(filter ModelFilter) ([]ModelSummary, int, error) {
    // 1. 載入所有模型中繼資料
    // 2. 依篩選條件過濾
    // 3. 排序
    // 4. 分頁
    // 5. 回傳結果與總數
}
```

---

## 7. 資料模型設計

### 7.1 核心型別對照

```
┌──────────────────────────────────────────────────────────────────┐
│  前端 (TypeScript)              後端 (Go)                         │
│                                                                  │
│  Device                    ←→  device.DeviceInfo                 │
│  DeviceStatus              ←→  device.DeviceStatus               │
│  Model                     ←→  model.Model                       │
│  ModelSummary              ←→  model.ModelSummary                 │
│  InferenceResult           ←→  driver.InferenceResult             │
│  ClassResult               ←→  driver.ClassResult                 │
│  DetectionResult           ←→  driver.DetectionResult             │
│  FlashProgress             ←→  driver.FlashProgress               │
│  DeviceEvent               ←→  device.DeviceEvent                 │
└──────────────────────────────────────────────────────────────────┘
```

### 7.2 本地儲存

MVP 階段不使用資料庫，使用檔案系統：

```
~/.edge-ai-platform/              # 應用資料目錄
├── config.json                    # 使用者設定
├── models/                        # 模型檔案快取
│   ├── kneron-face-detect-v1.nef
│   └── ...
└── logs/                          # 日誌
    └── server.log
```

---

## 8. MVP 功能模組對照

### PRD 功能 → 技術實作對照

| PRD 功能 (B4) | 前端元件 | 後端模組 | API | 優先級 |
|--------------|---------|---------|-----|:------:|
| **F1 模型庫瀏覽** | `ModelGrid`, `ModelCard`, `ModelFilters` | `model/repository` | `GET /api/models` | P0 |
| **F1 模型篩選** | `ModelFilters` (TaskType, Hardware, Search) | `model/repository.List(filter)` | `GET /api/models?type=&q=` | P0 |
| **F1 模型詳情** | `ModelDetail`, `ModelCompatibility` | `model/repository` | `GET /api/models/:id` | P0 |
| **F1 模型下載** | 下載按鈕 + 進度 | `model/downloader` | `POST /api/models/:id/download` | P0 |
| **F2 裝置偵測** | `DeviceList`, `DeviceCard` | `device/manager`, `usb/monitor` | `GET /api/devices` + WS | P0 |
| **F2 裝置連線** | `DeviceStatus` | `device/manager.Connect()` | `POST /api/devices/:id/connect` | P0 |
| **F2 裝置熱插拔** | `useDeviceEvents` hook | `usb/monitor` | `ws://.../ws/devices/events` | P1 |
| **F3 燒錄** | `FlashDialog`, `FlashProgress` | `flash/service`, `driver` | `POST .../flash` + WS progress | P0 |
| **F3 燒錄進度** | `FlashProgress` (進度條) | `flash/progress` | `ws://.../flash-progress` | P0 |
| **F4 推論串流** | `InferencePanel` | `inference/service`, `driver` | WS `/inference` | P0 |
| **F4 分類結果** | `ClassificationResult` (長條圖) | `inference/parser` | InferenceResult JSON | P0 |
| **F4 偵測結果** | `DetectionCanvas` (邊界框) | `inference/parser` | InferenceResult JSON | P1 |
| **F4 效能指標** | `PerformanceMetrics` (FPS, 延遲) | `inference/service` | 含在 InferenceResult 內 | P0 |
| **F4 Threshold** | `ConfidenceSlider` | 前端過濾 | 不需 API（前端處理） | P1 |
| **F4 裝置日誌** | `DeviceLog` | `api/ws/device_events_ws` | `ws://.../log` | P1 |
| **F5 Camera 列表** | `CameraSelector` | `camera/manager.ListCameras()` | `GET /api/camera/list` | P0 |
| **F5 Camera 串流** | `CameraFeed` (MJPEG `<img>`) | `camera/mjpeg`, `camera/capture` | `GET /api/camera/stream` | P0 |
| **F5 Camera 推論** | `CameraInferenceView` | `camera/pipeline` | `POST /api/camera/start` | P0 |
| **F5 推論疊加** | `CameraOverlay` (Canvas 邊界框) | `inference/service` | WS `/inference` | P0 |
| **F5 Camera 控制** | `CameraControls` (解析度/開關) | `camera/manager` | `POST /api/camera/stop` | P1 |

### 8.5 已實作額外功能技術對照

以下為 MVP 開發期間實作但不在原始 PRD 範圍內的功能，及其技術實作對照。

#### 8.5.1 多來源推論輸入（F5）

| 前端元件 | 後端模組 | API |
|---------|---------|-----|
| `SourceSelector` (Tab: camera/image/video) | `camera/manager` | `POST /api/camera/start` |
| Image upload button | `api/handlers/media_handler` | `POST /api/media/upload/image` |
| Batch image upload (多選 + drag & drop) | `api/handlers/camera_handler` | `POST /api/media/upload/batch-images` |
| Batch image frame viewer | `camera/multi_image_source` | `GET /api/media/batch-images/:index` |
| Video upload button | `api/handlers/media_handler` | `POST /api/media/upload/video` |
| URL input + submit | `api/handlers/media_handler` | `POST /api/media/url` |

**影片 URL 處理流程：**

```
URL 輸入 → 後端分類 URL 類型：
├── 直接連結 (.mp4 等) → 直接開啟影片串流
├── YouTube URL → yt-dlp 解析取得直接連結 → 開啟影片串流
└── 無效 URL → 回傳錯誤
```

#### 8.5.2 多語系支援 i18n（F6）

**架構設計：**

```
src/lib/i18n/
├── types.ts       # TranslationDict 介面 + Paths<T> 遞迴型別
├── en.ts          # 英文字典（~130+ keys）
├── zh-TW.ts       # 繁體中文字典
└── index.ts       # useTranslation() hook + getTranslation() helper
```

| 使用場景 | API | 說明 |
|---------|-----|------|
| React 元件 | `useTranslation()` | Hook，語言切換時自動 re-render |
| Zustand Store | `getTranslation()` | 非 hook 存取器，用於 store action 中的 toast 訊息 |
| 參數插值 | `t('key', { n: 5 })` | 支援 `{param}` 格式的字串替換 |

**型別安全：** `TranslationKey` 由 `Paths<TranslationDict>` 遞迴產生，所有 `t()` 呼叫都有 key 自動完成，新增 key 時兩份語言檔必須同步更新否則 TypeScript 報錯。

**同步元件：**
- `LangSync`：監聽 settings store 的 `language`，同步更新 `<html lang>` 屬性
- `ThemeSync`：監聽 settings store 的 `theme`，同步更新 `<html>` 的 `class` 屬性（加/移除 `dark`）

#### 8.5.3 深色模式（F7）

| 元件 | 機制 |
|------|------|
| `ThemeSync` | Zustand `settings-store.theme` → `document.documentElement.classList.toggle('dark')` |
| Tailwind CSS | 使用 `dark:` variant，搭配 CSS custom properties 定義色彩 |
| Settings UI | 淺色/深色切換按鈕，persist 至 localStorage |

#### 8.5.4 儀表板 Dashboard（F8）

| 前端元件 | 資料來源 | 說明 |
|---------|---------|------|
| `app/page.tsx` | `model-store`, `device-store`, `activity-store` | 統計卡片 + 快速操作 |
| `ActivityTimeline` | `activity-store` | 最近 10 筆活動，含圖示和相對時間 |
| `ConnectedDevicesList` | `device-store` | 已連線裝置即時列表 |

**Activity Store 事件類型：** `model_upload`, `model_delete`, `device_connect`, `device_disconnect`, `flash_start`, `flash_complete`, `flash_error`

#### 8.5.5 裝置偏好設定（F9）

| Store | 持久化 | 資料結構 |
|-------|--------|---------|
| `device-preferences-store` | Zustand persist → localStorage | `Record<deviceId, { alias?: string, notes?: string }>` |

#### 8.5.6 模型比較（F10）

| 前端元件 | 功能 |
|---------|------|
| `ModelComparisonDialog` | 最多 3 模型並排比較 |
| 比較指標 | 準確率、FPS、模型大小、延遲、任務類型 |
| 視覺化 | 指標長條圖（百分比 bar） |

#### 8.5.7 設定頁面（F12）

| 區塊 | 內容 |
|------|------|
| 伺服器設定 | API URL、WebSocket URL（唯讀顯示） |
| 外觀 | 主題切換（淺色/深色）、語言切換（EN/zh-TW） |
| 關於 | 版本資訊 |
| 操作 | 恢復預設值 |

**Settings Store：** `settings-store.ts` 管理 `theme` ('light' | 'dark') 和 `language` ('en' | 'zh-TW')，persist 至 localStorage。

#### 8.5.8 自訂模型上傳（F11）

| 前端元件 | 後端模組 | API |
|---------|---------|-----|
| `ModelUploadDialog` | `api/handlers/model_handler` | `POST /api/models/upload` |
| 表單驗證（檔案、名稱、任務類型、標籤） | 模型元資訊存檔 | `DELETE /api/models/:id` |

**上傳流程：** 前端 FormData（.nef + 元資訊）→ 後端儲存檔案 + 建立模型紀錄 → 回傳模型 ID → 前端更新模型庫。

#### 8.5.9 裝置健康狀態與連線歷史（F13）

| 前端元件 | 資料來源 | 說明 |
|---------|---------|------|
| `DeviceHealthCard` | `device-store` | 狀態、韌體版本、運行時間（useEffect + setInterval 更新） |
| `DeviceConnectionLog` | `device-preferences-store` | 連線/斷線事件紀錄，含時間戳記 |

**Hydration 安全：** 運行時間使用 `useState(0)` + `useEffect` 避免 `Date.now()` 造成 SSR 不匹配；時間格式化使用 `suppressHydrationWarning`。

#### 8.5.10 批次圖片推論（F14）

| 前端元件 | 後端模組 | API |
|---------|---------|-----|
| `BatchImageThumbnails` | `camera/multi_image_source.go` | `POST /api/media/upload/batch-images` |
| `SourceSelector`（多選 + drag & drop） | `api/handlers/camera_handler` | `GET /api/media/batch-images/:index` |
| `InferencePanel`（批次進度卡片） | `camera/pipeline.go`（SourceBatchImage 分支） | WebSocket 推論結果串流 |

**資料流：**

```
前端多選/拖放圖片 → FormData POST 至後端
→ 後端建立 MultiImageSource → Pipeline 逐張 ReadFrame
→ NPU 推論 → InferenceResult 加入 imageIndex/totalImages/filename
→ WebSocket 即時回傳 → 前端 addBatchResult → 更新進度 + 縮圖狀態
→ 使用者點擊縮圖 → 切換 batchSelectedIndex → 顯示對應圖片 + BBox overlay
```

**前端 Store 擴展：**
- `camera-store`：新增 `batchImages`, `batchSelectedIndex`, `batchProcessedCount`, `uploadBatchImages()`
- `inference-store`：新增 `batchResults: Record<number, InferenceResult>`, `addBatchResult()`
- `use-inference-stream`：判斷 `imageIndex !== undefined` 走批次處理邏輯

**型別擴展：**
- `SourceType` 新增 `'batch_image'`
- `BatchImageInfo`：`{ index, filename, width, height }`
- `InferenceResult` 新增 `imageIndex?`, `totalImages?`, `filename?`

#### 8.5.11 攝影機自動偵測（F15）

| 前端元件 | 後端模組 | API |
|---------|---------|-----|
| `workspace-client.tsx`（fetchCameras） | `camera/manager.go` | `GET /api/camera/list` |
| `SourceSelector`（cameraDisabled 邏輯） | `camera/ffmpeg_detect.go` | — |

**偵測流程：**

```
workspace 載入 → fetchCameras() → GET /api/camera/list
→ 後端 Manager.ListCameras():
  ├── mockMode=true → 回傳 mock camera
  └── mockMode=false → ffmpeg 偵測真實攝影機 → 回傳列表或空陣列
→ 前端 cameras.length === 0 → useEffect 切換至 Image tab + disable Camera tab
```

**Hydration 安全：** 使用 `useHydrated()` hook 確保 `disabled` 屬性在 SSR 時為 `false`（與 `Tabs value="camera"` 一致），hydration 完成後才根據 cameras 列表設定 disabled 狀態。

#### 8.5.12 跨平台安裝與分發（F16）

| 安裝腳本 | 後端模組 | 配置檔 |
|---------|---------|--------|
| `scripts/install.sh`（macOS/Linux） | `internal/deps/checker.go` | `.goreleaser.yaml` |
| `scripts/install.ps1`（Windows） | `server/main.go`（baseDir） | `Makefile`（release targets） |
| `scripts/setup-kneron.sh`（獨立硬體設定） | — | — |
| `scripts/kneron_detect.py`（USB 偵測） | — | — |

**安裝流程（macOS `install.sh`）：**

```
curl | bash → detect_platform() → resolve_version()
→ Step 1/4: install_binary() — 下載 tar.gz + 解壓到 ~/.edge-ai-platform/ + symlink
→ Step 2/4: setup_libusb() — brew install libusb (macOS) / apt install (Linux)
→ Step 3/4: setup_python_venv() — python3 -m venv + pip install pyusb
→ Step 4/4: check_optional_deps() + detect_kneron_devices()
```

**安裝流程（Windows `install.ps1`）：**

```
irm | iex → Invoke-RestMethod (resolve version)
→ Step 1/4: 下載 zip + Expand-Archive + 加入 PATH (User scope)
→ Step 2/4: 檢查 libusb-1.0.dll，提示 Zadig 安裝
→ Step 3/4: python -m venv + pip install pyusb（同時嘗試 python3 / python）
→ Step 4/4: 檢查 ffmpeg/yt-dlp + kneron_detect.py
```

**Binary 路徑解析（`main.go` baseDir）：**

```go
func baseDir(devMode bool) string {
    if devMode { return "." }          // go run 用 CWD
    exe, _ := os.Executable()
    return filepath.Dir(exe)           // production 用 binary 目錄
}
// 3 處路徑：data/models.json, data/custom-models, scripts/kneron_bridge.py
```

**啟動依賴檢查（`internal/deps/checker.go`）：**

```
Server 啟動 → deps.PrintStartupReport(logger)
→ 檢查 ffmpeg (exec.LookPath) → [OK] / [MISSING]
→ 檢查 yt-dlp → [OK] / [OPTIONAL]
→ 檢查 python3 → [OK] / [OPTIONAL]
```

**GoReleaser 打包產出：**

| 平台 | 格式 | 檔名範例 |
|------|------|---------|
| macOS Intel | tar.gz | `edge-ai-platform_v0.1.0_darwin_amd64.tar.gz` |
| macOS Apple Silicon | tar.gz | `edge-ai-platform_v0.1.0_darwin_arm64.tar.gz` |
| Windows x64 | zip | `edge-ai-platform_v0.1.0_windows_amd64.zip` |

每個 archive 含：`edge-ai-server` binary + `data/models.json` + `scripts/kneron_bridge.py` + `scripts/requirements.txt` + `scripts/firmware/KL520/` + `scripts/firmware/KL720/`

#### 8.5.13 Kneron 硬體通訊整合（F18 — KL520 + KL720）

| 前端元件 | 後端模組 | 腳本/資源 |
|---------|---------|----------|
| 既有 inference panel | `internal/driver/kneron/kl720_driver.go` | `scripts/kneron_bridge.py` |
| 既有 device panel | `internal/driver/kneron/detector.go` | `scripts/firmware/KL520/fw_{scpu,ncpu}.bin` |
| — | `internal/flash/service.go` (resolveModelPath) | `scripts/firmware/KL720/fw_{scpu,ncpu}.bin` |
| — | `internal/driver/interface.go` | `scripts/update_kl720_firmware.py` |
| — | — | `data/nef/kl720/kl720_2000{1,4,5}_*.nef` |

**通訊架構（晶片感知）：**

```
┌──────────┐  JSON-RPC   ┌──────────────────┐  ctypes   ┌──────────────┐  USB BULK  ┌─────────────┐
│ Go Server│──stdin/out──►│ kneron_bridge.py │──────────►│libkplus.dylib│──────────►│ KL520 (2.0) │
│ (Gin)    │◄────────────│ (Python 3.9)     │◄──────────│(Kneron PLUS) │◄──────────│ KL720 (3.0) │
└──────────┘             └──────────────────┘           └──────────────┘           └─────────────┘
      │                         │
      │ chipType="KL520"/"KL720" │ _device_chip 全域變數
      │ device_type 參數傳遞      │ 依晶片調整連線/韌體邏輯
```

**kneron_bridge.py 推論流程：**

```python
# 1. 接收 base64 圖片
img_bytes = base64.b64decode(image_base64)

# 2. OpenCV 解碼 + 色彩空間轉換
img = cv2.imdecode(np.frombuffer(img_bytes, np.uint8), cv2.IMREAD_COLOR)
img_bgr565 = cv2.cvtColor(img, cv2.COLOR_BGR2BGR565)

# 3. 建立推論描述符
inf_config = kp.GenericImageInferenceDescriptor(
    model_id=model_id,
    input_node_image_list=[kp.GenericInputNodeImage(
        image=img_bgr565,
        image_format=kp.ImageFormat.KP_IMAGE_FORMAT_RGB565,
    )]
)

# 4. 送出推論 + 接收結果
kp.inference.generic_image_inference_send(device_group, inf_config)
result = kp.inference.generic_image_inference_receive(device_group)

# 5. 依 model_type 自動選擇後處理
#    tiny_yolov3 → _parse_yolo_output(ANCHORS_TINY_YOLOV3)
#    yolov5s     → _parse_yolo_output(ANCHORS_YOLOV5S)
#    fcos        → _parse_fcos_output()
#    ssd         → _parse_ssd_output()
#    resnet18    → _parse_classification_output()
```

**後處理引擎細節：**

| 模型 | 偵測頭 / 輸出 | Anchor / 架構 | 信心門檻 | NMS IoU |
|------|-------------|--------------|---------|---------|
| Tiny YOLO v3 | 雙尺度 7×7 + 14×14 | (81,82)(135,169)(344,319) / (10,14)(23,27)(37,58) | ≥ 0.25 | 0.45 |
| YOLOv5s | 三尺度 P5/P4/P3 | (116,90)(156,198)(373,326) / (30,61)(62,45)(59,119) / (10,13)(16,30)(33,23) | ≥ 0.25 | 0.45 |
| FCOS | 5 層 FPN (stride 8~128) | Anchor-free (centerness × cls) | ≥ 0.25 | 0.45 |
| SSD 人臉 | 2 類（bg + face） | SSD prior boxes | ≥ 0.25 | 0.45 |
| ResNet18 | 1000 類 softmax | Top-5 排序 | — | — |

**Letterbox 校正（`_correct_bbox_for_letterbox`）：**

SDK 的 `hw_pre_proc_info` 提供 `pad_left`, `pad_top`, `resized_w`, `resized_h` 等參數，後處理自動從模型空間轉換到原始圖片空間，消除 padding 偏移。

**macOS Apple Silicon 相容性：**

```
Kneron PLUS SDK (C source) ──編譯──► libkplus.dylib (x86_64)
                                        ↓
Python 3.9 (x86_64 via Rosetta 2) ──ctypes.cdll.LoadLibrary──► libkplus.dylib
                                        ↓
pendian.h 修正：#if defined(__APPLE__) #include <machine/endian.h>
CMakeLists.txt 修正：移除 -Werror，改用 -Wno-unused-but-set-variable
```

**KL520 驗證結果（bike_cars_street_224x224.bmp）：**

| 物件 | 信心值 | BBox (x, y, w, h) |
|------|--------|-------------------|
| car | 0.998 | (0.66, 0.38, 0.20, 0.37) |
| car | 0.971 | (0.53, 0.34, 0.43, 0.43) |
| person | 0.965 | (0.26, 0.34, 0.10, 0.44) |
| person | 0.754 | (0.18, 0.34, 0.26, 0.43) |
| car | 0.499 | (0.42, 0.41, 0.12, 0.07) |
| bicycle | 0.465 | (0.26, 0.50, 0.12, 0.37) |
| car | 0.368 | (0.16, 0.37, 0.05, 0.05) |
| car | 0.294 | (0.45, 0.43, 0.06, 0.09) |

推論延遲：KL520 ~25ms（NPU 硬體推論）

**KL720 驗證結果：**

| 項目 | 結果 |
|------|------|
| 韌體更新 | KDP→KDP2 成功（PID 0x0200→0x0720，firmware: KDP2 Comp/F） |
| 連線 | `connect_devices()` 正常連線 |
| 模型載入 | NEF 模型載入成功（跨晶片路徑解析正確） |
| USB 速度 | USB 3.0 Super-Speed |

#### 8.5.14 圖形化安裝與解除安裝程式（F17）

**技術選型：Wails v2**

| 項目 | 規格 |
|------|------|
| 框架 | [Wails v2](https://wails.io/) — Go + WebView GUI 框架 |
| 選型理由 | 共用 Go 工具鏈（不需 Node.js runtime）、單一 binary 產出、WebView 體積小（~5MB vs Electron ~150MB）、macOS/Windows 原生 WebView 支援 |
| 前端 | 輕量 HTML/CSS/JS（vanilla 或 Preact），不使用 React 以減少安裝程式體積 |
| 後端 | Go — 執行實際安裝邏輯（檔案操作、下載、venv 建立、硬體偵測） |
| 產出 | macOS: `.app` → `.dmg`、Windows: `.exe`（Wails 內建 NSIS 包裝） |
| 架構分離 | Installer 是獨立 Go module（`installer/`），與 server 分開編譯，但共用 GoReleaser 發布 |

**安裝程式目錄結構：**

```
installer/
├── main.go                 # Wails 應用程式入口
├── app.go                  # Go 後端邏輯（安裝/解除安裝/偵測）
├── wails.json              # Wails 專案設定
├── frontend/
│   ├── index.html          # 安裝精靈 UI
│   ├── style.css           # 樣式（含深色模式）
│   └── main.js             # 前端邏輯（步驟導航、進度更新）
└── build/
    ├── darwin/
    │   └── Info.plist       # macOS app metadata
    └── windows/
        └── installer.nsi   # NSIS 安裝腳本
```

**Go 後端 API（Wails binding → 前端可呼叫）：**

```go
// installer/app.go

type Installer struct {
    ctx         context.Context
    installDir  string
    platform    string  // "darwin" / "windows"
    arch        string  // "amd64" / "arm64"
    progress    chan StepProgress
}

// 安裝流程 API
func (a *Installer) GetSystemInfo() SystemInfo          // 偵測 OS、已安裝版本、磁碟空間
func (a *Installer) SetInstallDir(dir string) error     // 設定安裝路徑
func (a *Installer) GetComponents() []Component         // 取得可安裝元件清單
func (a *Installer) Install(components []string) error   // 執行安裝（串流進度）
func (a *Installer) DetectHardware() []HardwareInfo     // 掃描 Kneron USB 裝置
func (a *Installer) UpdateKL720Firmware() error         // KDP→KDP2 韌體更新
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       // 解除安裝（可選保留資料）

// 進度回報（透過 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"`      // 錯誤訊息（空=正常）
}
```

**安裝精靈 UI 流程（6 步驟）：**

```
┌─────────────────────────────────────────────────────────┐
│ Step 1: 歡迎頁                                          │
│                                                         │
│   ┌─────────────────────────────────┐                   │
│   │  🔧 Edge AI Platform Installer │                   │
│   │                                 │                   │
│   │  版本: v0.2.0                   │                   │
│   │  安裝大小: ~350 MB              │                   │
│   │                                 │                   │
│   │  ☐ 我同意授權條款               │                   │
│   │                                 │                   │
│   │              [下一步 →]         │                   │
│   └─────────────────────────────────┘                   │
└─────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────┐
│ Step 2: 安裝路徑                                        │
│                                                         │
│   安裝位置:                                              │
│   ┌──────────────────────────────┐ [瀏覽...]            │
│   │ ~/.edge-ai-platform         │                       │
│   └──────────────────────────────┘                       │
│   可用空間: 58.3 GB                                      │
│   需要空間: ~350 MB                                      │
│                                                         │
│              [← 上一步]  [下一步 →]                      │
└─────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────┐
│ Step 3: 元件選擇                                        │
│                                                         │
│   必要元件:                                              │
│   ☑ Edge AI Server (10 MB)          ── 核心伺服器        │
│   ☑ Python 環境 (250 MB)            ── Kneron SDK 執行   │
│   ☑ 預訓練模型 (73 MB)              ── KL520/KL720 NEF  │
│   ☑ 韌體檔案 (2 MB)                 ── KL520/KL720 FW   │
│   ☑ USB 驅動 (libusb) (1 MB)        ── 裝置通訊         │
│                                                         │
│   可選元件:                                              │
│   ☐ ffmpeg (80 MB)                  ── 攝影機/影片串流   │
│   ☐ yt-dlp (15 MB)                  ── YouTube 影片     │
│                                                         │
│              [← 上一步]  [安裝 →]                        │
└─────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────┐
│ Step 4: 安裝進度                                        │
│                                                         │
│   ✅ 下載 Edge AI Server              完成               │
│   ✅ 解壓縮檔案                        完成               │
│   ✅ 安裝 libusb                       完成               │
│   🔄 建立 Python 環境                  62%               │
│   ┌──────────────████████░░░░░░──────┐                   │
│   │ 安裝 numpy...                    │                   │
│   └──────────────────────────────────┘                   │
│   ○ 安裝模型檔案                       等待中             │
│   ○ 偵測硬體                           等待中             │
│                                                         │
│              [取消]                                       │
└─────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────┐
│ Step 5: 硬體偵測                                        │
│                                                         │
│   偵測到的 Kneron 裝置:                                  │
│                                                         │
│   ┌──────────────────────────────────┐                   │
│   │ ✅ KL720 (port 136)             │                   │
│   │    Firmware: KDP2 Comp/F        │                   │
│   │    PID: 0x0720                   │                   │
│   ├──────────────────────────────────┤                   │
│   │ ✅ KL520 (port 33)              │                   │
│   │    Firmware: Loader              │                   │
│   │    PID: 0x0100                   │                   │
│   └──────────────────────────────────┘                   │
│                                                         │
│   ⚠️ 未偵測到裝置？請確認 USB 連接                       │
│                                                         │
│              [← 上一步]  [下一步 →]                      │
└─────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────┐
│ Step 6: 完成                                            │
│                                                         │
│   ✅ 安裝成功！                                          │
│                                                         │
│   安裝位置: ~/.edge-ai-platform                          │
│   版本: v0.2.0                                           │
│   安裝大小: 342 MB                                       │
│                                                         │
│   ☑ 立即啟動 Edge AI Server                              │
│   ☑ 開啟瀏覽器 (http://localhost:3721)                   │
│   ☐ 建立桌面捷徑                                         │
│   ☐ 開機自動啟動                                         │
│                                                         │
│              [完成]                                       │
└─────────────────────────────────────────────────────────┘
```

**安裝步驟實作細節：**

| 步驟 | macOS 實作 | Windows 實作 |
|------|-----------|-------------|
| 下載 binary | `net/http` GET → Gitea release tar.gz | `net/http` GET → Gitea release zip |
| 解壓 | `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` 或內嵌 (可選) |
| 硬體偵測 | `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 |

**macOS 無 Homebrew 情境處理：**

```
if !commandExists("brew") {
    // 方案 A: 提示安裝 Homebrew（推薦）
    // 方案 B: 直接下載 libusb dylib 放入安裝目錄
    //         設定 DYLD_LIBRARY_PATH 環境變數
}
```

**Windows 無 Python 情境處理：**

```
if !commandExists("python") && !commandExists("python3") {
    // 方案 A: 自動下載 Python embeddable package (無需安裝)
    //         解壓到 installDir/python/，直接使用
    // 方案 B: 提示使用者安裝 Python (winget install Python.Python.3.12)
}
```

**解除安裝流程：**

```go
func (a *Installer) Uninstall(keepData bool) error {
    // 1. 停止正在運行的 edge-ai-server 進程
    killProcess("edge-ai-server")

    // 2. 移除 symlink / PATH
    // macOS: os.Remove("/usr/local/bin/edge-ai-server")
    // Windows: 從 User PATH 移除安裝目錄

    // 3. 移除安裝目錄
    if keepData {
        // 保留 data/custom-models/（使用者自訂模型）
        removeAllExcept(installDir, "data/custom-models")
    } else {
        os.RemoveAll(installDir)
    }

    // 4. 移除桌面捷徑 / 開機啟動
    removeDesktopShortcut()
    removeAutoStart()

    // 5. macOS: 不需要額外清理（brew 套件保留）
    //    Windows: 不移除 Python（可能被其他程式使用）
}
```

**更新流程（覆蓋安裝）：**

```go
func (a *Installer) Install(components []string) error {
    existing := a.GetInstalledInfo()
    if existing.Version != "" {
        // 升級模式：保留 venv + custom-models + 設定
        // 只更新 binary + 預訓練模型 + firmware + scripts
        backupUserData()
        installNewVersion()
        restoreUserData()
    } else {
        // 全新安裝
        fullInstall()
    }
}
```

**打包與發布：**

```yaml
# .goreleaser.yaml — Installer 額外產出
builds:
  - id: installer
    dir: installer
    binary: EdgeAI-Installer
    goos: [darwin, windows]
    goarch: [amd64, arm64]
    hooks:
      pre: wails build -platform {{ .Os }}/{{ .Arch }}

# macOS: create-dmg 產出 .dmg
# Windows: NSIS 包裝成 Setup.exe
```

---

## 9. 開發環境與工具鏈

### 9.1 前端開發環境

| 工具 | 版本 | 用途 |
|------|------|------|
| Node.js | 20 LTS | JavaScript runtime |
| pnpm | 9.x | 套件管理（比 npm 快、磁碟效率高） |
| TypeScript | 5.x | 型別安全 |
| ESLint | 9.x | 程式碼品質 |
| Prettier | 3.x | 程式碼格式化 |

### 9.2 後端開發環境

| 工具 | 版本 | 用途 |
|------|------|------|
| Go | 1.22+ | 後端語言 |
| golangci-lint | latest | Go linter |
| Air | latest | Go 熱重載（開發用） |
| GoReleaser | latest | 跨平台打包 |

### 9.3 開發流程

```
開發模式 (Development):
┌─────────────────┐    ┌──────────────────┐    ┌─────────────────┐
│ Next.js Dev      │    │ Go Server        │    │ Device / Mock   │
│ (pnpm dev)       │    │ (air / go run)   │    │                 │
│ localhost:3000    │───►│ localhost:3721    │───►│ USB / Serial    │
│                  │    │                  │    │                 │
│ HMR 熱重載       │    │ 熱重載 (Air)     │    │ Mock Driver     │
└─────────────────┘    └──────────────────┘    └─────────────────┘

生產模式 (Production):
┌─────────────────────────────────────────────┐
│ 單一執行檔                                     │
│                                               │
│ Go Server (localhost:3721)                    │
│ ├── REST API  (/api/...)                      │
│ ├── WebSocket (/ws/...)                       │
│ ├── Static    (/ → 嵌入的前端 build)           │
│ └── Device Communication (USB/Serial)         │
└─────────────────────────────────────────────┘
```

### 9.4 開發模式代理設定

```javascript
// next.config.js
module.exports = {
  async rewrites() {
    return [
      {
        source: '/api/:path*',
        destination: 'http://localhost:3721/api/:path*',
      },
      // WebSocket 透過環境變數指定
    ];
  },
};
```

---

## 10. 專案目錄結構

```
edge-ai-platform/
├── README.md
├── Makefile                       # 建置腳本
├── .goreleaser.yml                # 跨平台打包設定
│
├── server/                        # Go 後端
│   ├── go.mod
│   ├── go.sum
│   ├── main.go
│   ├── cmd/
│   ├── internal/
│   │   ├── api/
│   │   ├── device/
│   │   ├── driver/
│   │   ├── model/
│   │   ├── flash/
│   │   ├── inference/
│   │   └── config/
│   ├── pkg/
│   │   ├── serial/
│   │   ├── usb/
│   │   └── logger/
│   ├── data/                      # 內建模型中繼資料
│   │   └── models.json
│   ├── scripts/                   # Kneron SDK Python 腳本
│   │   ├── load_model.py
│   │   ├── start_inference.py
│   │   └── requirements.txt
│   └── frontend/                  # 嵌入用前端 build
│       └── out/
│
├── frontend/                      # Next.js 前端
│   ├── package.json
│   ├── pnpm-lock.yaml
│   ├── next.config.js
│   ├── tailwind.config.ts
│   ├── tsconfig.json
│   ├── components.json            # shadcn/ui 設定
│   ├── src/
│   │   ├── app/
│   │   ├── components/
│   │   ├── stores/
│   │   ├── hooks/
│   │   ├── lib/
│   │   └── types/
│   └── public/
│       └── images/
│
├── docs/                          # 文件
│   ├── api.md                     # API 文件
│   └── driver-development.md      # Driver 開發指南
│
└── .github/                       # CI/CD
    └── workflows/
        ├── ci.yml
        └── release.yml
```

---

## 11. 部署與打包策略

### 11.1 建置流程

```
1. 前端建置
   pnpm build && pnpm export
   → frontend/out/ (靜態檔案)

2. 複製前端到 Go embed 目錄
   cp -r frontend/out server/frontend/out

3. Go 編譯 (含嵌入前端)
   cd server && go build -o edge-ai-platform

4. 或使用 GoReleaser 跨平台打包
   goreleaser release --snapshot
```

### 11.2 目標平台

| 平台 | 架構 | 輸出檔案 | 安裝方式 |
|------|------|---------|---------|
| macOS (Apple Silicon) | darwin/arm64 | `edge-ai-platform-darwin-arm64` | `.dmg` 或直接執行 |
| macOS (Intel) | darwin/amd64 | `edge-ai-platform-darwin-amd64` | `.dmg` 或直接執行 |
| Windows | windows/amd64 | `edge-ai-platform.exe` | `.msi` 安裝包 |
| Linux | linux/amd64 | `edge-ai-platform-linux-amd64` | `.deb` / `.rpm` / 直接執行 |

### 11.3 GoReleaser 設定

```yaml
# .goreleaser.yml
builds:
  - id: edge-ai-platform
    dir: server
    binary: edge-ai-platform
    goos:
      - darwin
      - linux
      - windows
    goarch:
      - amd64
      - arm64
    env:
      - CGO_ENABLED=0
    ldflags:
      - -s -w -X main.version={{.Version}}

archives:
  - format: tar.gz
    format_overrides:
      - goos: windows
        format: zip
```

### 11.4 Makefile

```makefile
.PHONY: dev dev-frontend dev-backend build clean

# 開發模式
dev: dev-frontend dev-backend

dev-frontend:
	cd frontend && pnpm dev

dev-backend:
	cd server && air

# 建置
build: build-frontend build-backend

build-frontend:
	cd frontend && pnpm build && pnpm export
	cp -r frontend/out server/frontend/out

build-backend: build-frontend
	cd server && go build -o ../dist/edge-ai-platform

# 跨平台打包
release:
	goreleaser release --snapshot --clean

clean:
	rm -rf dist/ frontend/out/ server/frontend/out/
```

### 11.5 安裝腳本

| 腳本 | 平台 | 安裝目錄 | 執行方式 |
|------|------|---------|---------|
| `scripts/install.sh` | macOS / Linux | `~/.edge-ai-platform/` | `curl -fsSL <url> \| bash` |
| `scripts/install.ps1` | Windows | `%LOCALAPPDATA%\EdgeAIPlatform` | `irm <url> \| iex` |
| `scripts/setup-kneron.sh` | macOS | 同上（venv 子目錄） | `bash scripts/setup-kneron.sh` |

**安裝內容：**
1. Edge AI Server binary + data 檔案
2. Python venv（`$INSTALL_DIR/venv`）+ pyusb + numpy + opencv-python-headless
3. libusb 系統驅動（macOS: Homebrew / Linux: apt / Windows: Zadig 提示）
4. `/usr/local/bin/edge-ai-server` symlink（macOS）或 PATH 設定（Windows）
5. KL520 韌體檔案：`scripts/firmware/KL520/fw_scpu.bin`, `fw_ncpu.bin`
6. Kneron PLUS SDK：`kp` Python module + `libkplus.dylib`（macOS）/ `libkplus.so`（Linux）

**scripts 目錄結構：**
```
scripts/
├── kneron_bridge.py          # Go↔Kneron JSON-RPC bridge
├── requirements.txt          # numpy, opencv-python-headless, pyusb
├── firmware/
│   └── KL520/
│       ├── fw_scpu.bin       # SCPU 韌體 (52KB)
│       └── fw_ncpu.bin       # NCPU 韌體 (40KB)
└── venv/                     # Python venv（安裝時建立）
    └── lib/.../site-packages/kp/  # Kneron PLUS SDK module
```

**解除安裝：**
- macOS: `rm -rf ~/.edge-ai-platform && sudo rm -f /usr/local/bin/edge-ai-server`
- Windows: `Remove-Item -Recurse -Force "$env:LOCALAPPDATA\EdgeAIPlatform"` + 移除 PATH

### 11.6 啟動依賴檢查

`internal/deps/checker.go` 在 Server 啟動時檢查外部工具：

| 工具 | 等級 | 用途 |
|------|------|------|
| ffmpeg | Required（Camera 功能）| Camera 擷取、影片處理 |
| yt-dlp | Optional | YouTube URL 解析 |
| python3 | Optional | Kneron 硬體驅動（pyusb） |

啟動輸出範例：
```
[INFO] External dependency check:
[INFO]   [OK] ffmpeg: ffmpeg version 7.1.1
[INFO]   [OPTIONAL] yt-dlp: not found — macOS: brew install yt-dlp | Windows: winget install yt-dlp
[INFO]   [OPTIONAL] python3: Python 3.12.3
```

---

## 12. 安全性考量

### 12.1 本地 Server 安全

| 風險 | 緩解措施 |
|------|---------|
| 外部存取本地 Server | 僅綁定 `127.0.0.1`，拒絕外部連線 |
| 跨站請求偽造 (CSRF) | 驗證 `Origin` header |
| 路徑穿越 (Path Traversal) | 模型檔案路徑白名單、嚴格驗證 |
| 命令注入 | 不將使用者輸入直接傳入 shell 命令 |
| CORS | 嚴格限制為 `localhost` 來源 |
| WebSocket 安全 | 驗證 Origin header |

### 12.2 裝置通訊安全

| 風險 | 緩解措施 |
|------|---------|
| 惡意韌體 | 驗證 NEF 檔案完整性（CRC/Hash） |
| USB 攻擊面 | 僅處理已知 VID/PID 的裝置 |
| 串列埠注入 | 嚴格解析裝置回應格式 |

---

## 13. 效能目標

### 13.1 回應時間目標（對應 PRD 驗收標準）

| 指標 | 目標 | PRD 來源 |
|------|------|---------|
| 裝置偵測延遲 | USB 接上 < 5 秒出現在列表 | B2.3 驗收標準 |
| 燒錄進度更新 | < 500ms 更新一次 | B2.3 |
| 推論結果延遲 | < 200ms（裝置到前端） | B2.4 驗收標準 |
| 首次推論顯示 | 燒錄完成後 < 5 秒 | B2.4 驗收標準 |
| 前端頁面載入 | < 2 秒 | 一般標準 |
| API 回應時間 | < 100ms（列表/詳情） | 一般標準 |
| 模型搜尋 | 使用者可在 30 秒內找到模型 | B2.2 驗收標準 |

### 13.2 資源使用

| 指標 | 目標 |
|------|------|
| Server 記憶體 | < 100 MB（空閒）/ < 200 MB（推論串流中） |
| Server CPU | < 5%（空閒）/ < 15%（推論串流中） |
| 執行檔大小 | < 50 MB（含前端） |

---

## 14. 測試策略

### 14.1 前端測試

| 類型 | 工具 | 覆蓋範圍 |
|------|------|---------|
| 單元測試 | Vitest | 工具函式、Store 邏輯 |
| 元件測試 | Vitest + Testing Library | UI 元件渲染、互動 |
| E2E 測試 | Playwright | 完整使用者流程 |

### 14.2 後端測試

| 類型 | 工具 | 覆蓋範圍 |
|------|------|---------|
| 單元測試 | Go testing | Driver 介面、解析器、業務邏輯 |
| 整合測試 | Go testing + httptest | API 端點、WebSocket |
| Mock 測試 | Mock Driver | 無硬體環境的完整流程測試 |

### 14.3 關鍵測試場景

```
模型庫:
├── 模型列表載入與顯示
├── 篩選功能（單選、多選、組合）
├── 搜尋功能
└── 模型詳情頁內容正確性

裝置管理:
├── 裝置偵測（插入 USB）
├── 裝置移除（拔出 USB）
├── 多裝置同時連線
└── 裝置斷線重連

燒錄:
├── 正常燒錄流程（進度 0→100%）
├── 燒錄中斷線（錯誤處理）
├── 模型不相容（預先阻止）
└── 燒錄逾時

推論:
├── 分類結果即時顯示
├── 偵測結果邊界框繪製
├── 高頻率更新不卡頓
├── Confidence Threshold 過濾
└── 推論啟停控制

Camera:
├── Camera 列表偵測
├── Camera 開啟與 MJPEG 串流
├── Camera 切換（多 Camera 支援）
├── Camera → Device 推論 Pipeline
├── MJPEG 串流 + 邊界框疊加同步
├── Camera 關閉後正確釋放資源
└── 不同解析度切換
```

---

## 15. 開發階段規劃

### 15.1 MVP 開發階段

```
Phase 0 — 基礎建設 (Scaffold)
━━━━━━━━━━━━━━━━━━━━━━━━━━━
├── 初始化前端專案 (Next.js + shadcn/ui + Tailwind)
├── 初始化後端專案 (Go module + 專案結構)
├── 設定開發工具鏈 (ESLint, Prettier, golangci-lint)
├── 實作 Mock Driver
├── 前後端通訊基礎 (REST + WebSocket)
└── CI 設定

Phase 1 — 模型庫 (Step 1)
━━━━━━━━━━━━━━━━━━━━━━━━
├── 後端: 模型中繼資料 API (GET /api/models, GET /api/models/:id)
├── 後端: 模型篩選與搜尋
├── 前端: 模型庫頁面 (ModelGrid + ModelCard)
├── 前端: 篩選面板 (ModelFilters)
├── 前端: 模型詳情頁 (ModelDetail)
└── 測試: 模型 API + 前端元件

Phase 2 — 裝置管理 (Step 2)
━━━━━━━━━━━━━━━━━━━━━━━━━━
├── 後端: USB 裝置偵測與熱插拔
├── 後端: Driver 抽象層 + Mock Driver 完善
├── 後端: 裝置 API (GET /api/devices, POST connect/disconnect)
├── 後端: WebSocket 裝置事件
├── 前端: 裝置列表頁面 (DeviceList + DeviceCard)
├── 前端: 裝置狀態即時更新
└── 測試: 裝置偵測 + WebSocket 事件

Phase 3 — 燒錄 (Step 2 續)
━━━━━━━━━━━━━━━━━━━━━━━━━
├── 後端: 燒錄服務 (Flash Service)
├── 後端: 燒錄進度 WebSocket
├── 後端: Kneron SDK 整合 (Python subprocess)
├── 前端: 燒錄對話框 + 進度條
├── 前端: 錯誤處理 UI
└── 測試: 燒錄流程 (Mock + 實機)

Phase 4 — Camera 與即時推論 (Step 3)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
├── 後端: Camera Manager (偵測、開啟、擷取)
├── 後端: MJPEG 串流 HTTP handler
├── 後端: Camera-Inference Pipeline (frame → device → result)
├── 後端: 推論結果串流 (WebSocket)
├── 後端: 推論結果解析器
├── 前端: Camera 選擇器 + 控制面板
├── 前端: Camera Feed 元件 (MJPEG <img>)
├── 前端: 推論結果 Canvas 疊加層 (邊界框 + 標籤)
├── 前端: Camera + 推論整合檢視 (CameraInferenceView)
├── 前端: 分類結果長條圖 (Recharts)
├── 前端: 效能指標顯示 (FPS, 延遲)
├── 前端: Confidence Threshold 滑桿
├── 前端: 裝置日誌面板
└── 測試: Camera 串流 + 推論疊加 + UI 效能

Phase 5 — 整合與打包
━━━━━━━━━━━━━━━━━━━
├── 前端 build 嵌入 Go binary
├── 跨平台打包 (GoReleaser)
├── 安裝包製作 (dmg, msi, deb)
├── E2E 測試 (完整使用者流程)
├── 效能測試與最佳化
└── 文件撰寫
```

### 15.2 技術風險與緩解

| 風險 | 嚴重度 | 緩解策略 |
|------|:------:|---------|
| Kneron PLUS SDK 整合困難 | 高 | 先用 Python subprocess；確保 Mock Driver 可用於平行開發 |
| 跨平台串列埠差異 | 中 | 使用成熟的 `go.bug.st/serial`；各平台 CI 測試 |
| Kneron USB VID/PID 未確認 | 中 | 取得實體硬體後確認；Mock Driver 先行開發 |
| WebSocket 高頻推論資料效能 | 中 | 前端節流（throttle）；二進位格式（MessagePack）備選 |
| OpenCV (gocv) 依賴與跨平台 | 中 | 靜態連結 OpenCV 或使用 ffmpeg subprocess 作為 fallback；Docker 建置環境 |
| Camera 擷取延遲 | 中 | MJPEG 串流減少前端解碼負擔；後端控制 frame rate (可調 15-30 FPS) |
| Go embed 前端檔案大小 | 低 | Next.js 靜態輸出已最佳化；gzip 壓縮 |
| CGo 交叉編譯 | 中 | MVP 用 Python subprocess 避免；Phase 1+ 再評估 CGo |

### 15.3 開發模式與 Mock 策略

為了在沒有實體 Kneron Dongle 的情況下進行完整的前後端開發：

```
啟動方式:
  edge-ai-platform --mock          # 使用 Mock Driver
  edge-ai-platform --mock-devices 3 # 模擬 3 台裝置

Mock Driver 行為:
  ● 偵測: 模擬 1-N 台虛擬裝置
  ● 連線: 立即成功
  ● 燒錄: 模擬 10 秒燒錄過程（進度遞增）
  ● 推論: 每 100ms 產生隨機推論結果
    ├── 分類: 在 5 個類別中隨機分配信心分數
    └── 偵測: 隨機位置的 1-3 個邊界框

Mock Camera 行為:
  ● Camera 列表: 回傳 1 台虛擬 Camera ("Mock Camera 0")
  ● MJPEG 串流: 產生帶有時間戳的測試圖卡影像 (color bars / gradient)
  ● Camera 推論: 以測試圖卡影像送入 Mock Driver，回傳隨機推論結果
  ● 用途: 在沒有實體 Camera 和 Dongle 的環境下完整測試前後端整合
```

---

## 附錄

### A. 預設連接埠

| 服務 | 預設埠 | 說明 |
|------|:------:|------|
| Go Server (HTTP + WS + Static) | 3721 | 所有服務共用 |
| Next.js Dev Server | 3000 | 僅開發模式 |

### B. 環境變數

| 變數 | 預設值 | 說明 |
|------|--------|------|
| `PORT` | `3721` | Server 監聽埠 |
| `HOST` | `127.0.0.1` | Server 綁定地址 |
| `LOG_LEVEL` | `info` | 日誌等級 (debug/info/warn/error) |
| `DEV_MODE` | `false` | 開發模式 (啟用 Mock Driver) |
| `MOCK_DEVICES` | `1` | Mock 裝置數量 |
| `DATA_DIR` | `~/.edge-ai-platform` | 應用資料目錄 |
| `PYTHON_PATH` | `python3` | Python 路徑 (Kneron SDK) |

### C. 相依套件清單

**前端 (package.json):**

```json
{
  "dependencies": {
    "next": "^15.0.0",
    "react": "^19.0.0",
    "react-dom": "^19.0.0",
    "zustand": "^5.0.0",
    "recharts": "^2.12.0",
    "lucide-react": "^0.400.0",
    "clsx": "^2.0.0",
    "tailwind-merge": "^2.0.0"
  },
  "devDependencies": {
    "typescript": "^5.5.0",
    "tailwindcss": "^4.0.0",
    "@types/react": "^19.0.0",
    "vitest": "^2.0.0",
    "@testing-library/react": "^16.0.0",
    "playwright": "^1.45.0",
    "eslint": "^9.0.0",
    "prettier": "^3.0.0"
  }
}
```

**後端 (go.mod 主要依賴):**

```
go.bug.st/serial        // 串列埠通訊
github.com/gin-gonic/gin // HTTP 框架 (或 echo)
github.com/gorilla/websocket // WebSocket
github.com/google/gousb  // USB 裝置偵測
gocv.io/x/gocv          // OpenCV Go binding (Camera 擷取)
github.com/fsnotify/fsnotify // 檔案監聽
github.com/spf13/cobra   // CLI 框架
github.com/spf13/viper   // 設定管理
go.uber.org/zap          // 結構化日誌
```

---

*文件版本：v1.3 | 日期：2026-02-28 | 狀態：更新中*
