# visionA Cloud Frontend > visionA Cloud 前端 Web 應用(Next.js 16 + React 19 + Tailwind 4)。 > > 目的:將 `local-tool/` 的使用體驗延伸到雲端,讓使用者透過瀏覽器操作由 local agent 連入的邊緣裝置(Kneron KL520/KL720 等)。 > 🚧 **Phase 0 雛形** — 任何功能皆以 UI 走通為目標,不做真實身分驗證、OAuth、WebSocket 即時推送、MJPEG 串流。完整功能清單見下方「雛形範圍與限制」。 > > 規劃中的正式版請見 [`.autoflow/02-prd/PRD.md`](../.autoflow/02-prd/PRD.md),架構決策見 [`.autoflow/04-architecture/design-doc.md`](../.autoflow/04-architecture/design-doc.md)。 --- ## 技術堆疊 | 層級 | 技術 | 版本 | |------|------|------| | 框架 | Next.js(App Router) | 16.1.6 | | UI 函式庫 | React / React DOM | 19.2.3 | | 語言 | TypeScript | ^5 | | 樣式 | Tailwind CSS(含 `@tailwindcss/postcss`)| ^4 | | 元件 primitive | Radix UI | ^1.4.3 | | 元件規範 | shadcn `new-york` style(`components.json`) | — | | 圖示 | lucide-react | ^0.575.0 | | 狀態管理 | Zustand | ^5.0.11 | | 主題 | next-themes | ^0.4.6 | | Toast | sonner | ^2.0.7 | | Class 工具 | clsx + tailwind-merge + class-variance-authority | — | | 測試 | Vitest + @testing-library/react + jsdom | ^4 / ^16 / ^28 | | Lint | ESLint + eslint-config-next | ^9 / 16.1.6 | 版本與 [`local-tool/frontend/`](../local-tool/frontend/) 對齊,確保「雲端版 / 離線版同一套前端」的架構決策可執行。 --- ## 前置需求 - Node.js **≥ 20** - pnpm **≥ 10** - **依賴:** 需同時運行 [`visionA-backend`](../visionA-backend/README.md)(Go)。前端所有 API 呼叫會指向 `NEXT_PUBLIC_API_BASE`,後端未啟動時功能會顯示錯誤但 UI 仍可瀏覽(多數頁面對 501 / 連線錯誤已做 graceful fallback)。 --- ## 快速啟動 ```bash # 1. 安裝依賴 cd visionA-frontend pnpm install # 2. 設定環境變數 cp .env.local.example .env.local # 檢查 NEXT_PUBLIC_API_BASE 是否指到正確後端位址 # 3. 啟動後端(另一個 terminal) cd ../visionA-backend make run # 或參考 visionA-backend/README.md # 4. 啟動前端 cd ../visionA-frontend pnpm dev # http://localhost:3000 ``` 進入後**任何 email + 任何密碼**都可登入(Phase 0 的 StaticAuthProvider)。 ### 可用腳本 | 指令 | 說明 | |------|------| | `pnpm dev` | 開發模式(Turbopack / HMR) | | `pnpm build` | 產線打包(`output: "standalone"`,便於 Docker 部署) | | `pnpm start` | 啟動 production build | | `pnpm lint` | 執行 ESLint | | `pnpm test` | 執行 Vitest(一次性) | | `pnpm test:watch` | 執行 Vitest(watch mode) | --- ## 專案結構 ``` visionA-frontend/ ├── src/ │ ├── app/ # Next.js App Router 路由 │ │ ├── layout.tsx # Root layout(Theme / Locale / AppShell) │ │ ├── page.tsx # / Dashboard │ │ ├── login/ # /login │ │ ├── register/ # /register(Phase 0 Coming Soon) │ │ ├── account/ # /account 帳號設定 stub │ │ ├── devices/ # /devices, /devices/[id], /devices/pair │ │ ├── models/ # /models, /models/[id] │ │ ├── workspace/ # /workspace, /workspace/[deviceId] │ │ ├── clusters/ # /clusters(Phase 0 預告頁) │ │ └── settings/ # /settings │ ├── components/ │ │ ├── ui/ # Shadcn primitive(Button / Card / Input ...) │ │ ├── layout/ # Sidebar / Header / PrototypeBanner / AppShell │ │ ├── dashboard/ # StatCard / ActivityTimeline / ConnectedDevicesList │ │ ├── devices/ # DeviceCard 相關 │ │ ├── models/ # ModelCard / ModelUploadDialog │ │ ├── cloud/ # RemoteDeviceBadge │ │ └── pairing/ # PairingTokenCard / PairingCountdown │ ├── hooks/ # useFetch / useWebsocket / useTunnelStatus │ ├── lib/ │ │ ├── api.ts # 統一 API client(envelope / 401 / 501 / timeout) │ │ ├── utils.ts # cn() 等 │ │ └── i18n/ # zh-Hant / en 字典 + Context │ ├── stores/ # Zustand stores(auth / session / device / model / activity / pairing) │ ├── tests/ # Vitest setup │ └── types/ # 共用型別(api / user / pairing ...) ├── public/ # 靜態資源 ├── components.json # Shadcn CLI 設定 ├── next.config.ts # output: standalone ├── tsconfig.json # @/* → ./src/* └── package.json ``` --- ## 主要頁面清單 | 路徑 | Phase 0 狀態 | 說明 | |------|-------------|------| | `/login` | ✅ 可用 | 任意帳密皆通過(StaticAuth) | | `/register` | ⚠️ Coming Soon | 引導回 /login;Phase 1 才實作 | | `/` Dashboard | ✅ 可用 | StatCard × 4、近期活動、快速操作;無裝置時顯示空狀態引導 /devices/pair | | `/devices` | ✅ 可用 | 裝置列表;RemoteDeviceBadge 顯示遠端連線狀態 | | `/devices/[id]` | ✅ 可用 | 裝置詳細、離線 banner | | `/devices/pair` | ✅ 可用(核心) | 三步配對流程:產生 Pairing Token → 15 分鐘倒數 → 輪詢 3 分鐘 | | `/models` | ✅ 可用 | 模型列表 + 上傳 Dialog(XHR 進度) | | `/models/[id]` | ✅ 可用 | 模型詳細、部署到裝置 | | `/workspace` | ✅ 可用 | 選擇線上裝置 | | `/workspace/[deviceId]` | ⚠️ Camera placeholder | 推論 Start/Stop 可按;MJPEG stream Phase 1 接上 | | `/clusters` | ⚠️ Phase 1 預告 | POC 有完整實作,雛形後端是 stub | | `/account` | ✅ 可用 stub | 顯示使用者 + 登出;其他操作是 toast 提醒 | | `/settings` | ✅ 可用 | 語言 / 主題 / API 端點切換 | --- ## 環境變數 | 變數 | 說明 | 預設值 | 必要 | |------|------|--------|------| | `NEXT_PUBLIC_API_BASE` | 雲端 API Server 位址(無尾斜線) | `http://localhost:3721` | 是 | | `NEXT_PUBLIC_WS_BASE` | WebSocket 位址;留空則從 API 推導 | 從 API base 推導 | 否 | 所有 runtime 變數都以 `NEXT_PUBLIC_` 開頭(Next.js 要求前端可讀),**不要放真正機密的值**——這類值應走後端。 --- ## Phase 0 雛形範圍與限制 ### ✅ 可做到的 - 完整 UI 骨架(Sidebar / Header / 全域雛形 Banner / Sonner toast) - i18n(zh-Hant / en)可即時切換,key 集合兩語系強制同步 - Light / Dark / System 主題切換 - 裝置 / 模型 / 活動 三個 store 的 CRUD UI(對齊 api-spec) - **Pairing 流程**:產 token → 視覺切兩行顯示(複製永遠是完整 36 字元)→ 15 分鐘倒數(3 色階段)→ 輪詢連線狀態 → 成功 toast + 跳轉 - 離線降級:`RemoteDeviceBadge` + 頁面離線 Banner + Workspace 遮罩 - 模型上傳:XHR progress + presigned URL 直送 storage ### ❌ Phase 0 刻意不做(Phase 1 接手) - **任何帳密可登入** — 後端 `StaticAuthProvider` 回 `demo-user`;重啟後端後前端 localStorage 的 token 雖然還在,但伺服器上沒有任何 session 資料 - **OAuth / 2FA / 密碼重設** — 全部 Phase 1 - **註冊功能** — 顯示 Coming Soon 頁面 - **重啟後端 → 裝置 / 模型資料消失** — 後端目前是 InMemoryRepository - **WebSocket 即時推送** — 事件 / 推論結果 / 配對狀態的 WS endpoint 目前回 501,前端改用 3 秒輪詢(pairing 頁) - **Camera / MJPEG 串流** — `/workspace/[deviceId]` 的 Camera tab 顯示 placeholder,Image / Video / Batch tabs disabled - **Cluster CRUD** — `/clusters` 顯示 Phase 1 預告頁 - **Account CRUD** — 變更個人資料 / 刪除帳號都是 toast stub - **真實 SHA-256 checksum** — 模型上傳用 `placeholder:{size}:{nameLen}`(詳見 `model-upload-dialog.tsx` §placeholderChecksum) 前端安全債詳見 [`.autoflow/04-architecture/security.md`](../.autoflow/04-architecture/security.md) §14(localStorage token / WS querystring / CSP / CSRF 等 6 項,每項有 Phase 1 計畫)。 --- ## 效能預算(Phase 1 開始嚴格執行) | 資源 | 預算 | |------|------| | JavaScript(首次載入,壓縮後) | < 200 KB | | CSS(壓縮後) | < 50 KB | | 首次載入總大小 | < 500 KB | | Core Web Vitals | LCP < 2.5s、INP < 200ms、CLS < 0.1 | 雛形(Phase 0)階段僅確保 `pnpm build` 成功;CI 中加入 bundle 檢查由 DevOps 任務處理。 --- ## 無障礙規範 目標 **WCAG 2.2 AA**(詳見 [`.autoflow/03-design/design-spec.md`](../.autoflow/03-design/design-spec.md) §8): - 色彩對比:一般文字 ≥ 4.5:1、大文字 ≥ 3:1 - 鍵盤可操作所有核心流程(Tab / Enter / Escape) - Focus ring 可見(shadcn 預設 `ring-2 ring-ring`) - 語意化 HTML(`