# visionA Agent — Frontend visionA Agent 是 visionA 雲端的 **local agent / tunnel bridge** — 一個桌面應用,把本機的 Kneron 邊緣裝置安全地連上 visionA 雲端。本目錄是它的前端實作:純靜態匯出(Next.js + React),由 Wails Go runtime 透過 `go:embed` 嵌入可執行檔,三平台(macOS / Windows / Linux)共用同一份程式碼。 > 定位:**「開了就忘掉它」的工具**。使用者配對一次後,Agent 只要維持連線、不出問題就好。UI 極簡,沒有裝置面板、推論視窗、行銷素材 — 只有連線狀態、配對、設定三頁。 ## 技術堆疊 | 層級 | 技術 | 版本 | |------|------|------| | 框架 | Next.js(App Router,`output: "export"` static) | 16.1.x | | 語言 | TypeScript(strict) | 5.x | | UI | React | 19.2.x | | 樣式 | Tailwind CSS | 4.x | | 基礎元件 | Radix UI(`radix-ui` 單一套件,shadcn/ui 風格) | 1.4.x | | 圖示 | lucide-react | 0.575.x | | 主題 | next-themes(Light / Dark / System) | 0.4.x | | Toast | sonner | 2.x | | 動畫 | tw-animate-css | 1.4.x | | 狀態管理 | zustand(依賴保留;雛形階段實際未用) | 5.x | | 測試 | Vitest + @testing-library/react + jsdom | Vitest 4 / RTL 16 | ## 與其他前端專案的關係 | 專案 | 定位 | 是否為本專案的來源 | |------|------|------------------| | `visionA-frontend/`(雲端 Web) | 使用者的主要操作介面 — 裝置管理、模型管理、推論 | **是**。Design Tokens (`globals.css`)、Theme Provider、i18n 結構、`utils.ts`、Radix UI 基礎元件複製自此 | | `local-tool/frontend/`(本機 GUI) | 開發者跑推論用的桌面工具 | **不是**。本專案**不是** local-tool 的 fork — 我們只借鑒其 Wails + Next.js `output:"export"` 的打包模式,UI 和邏輯完全獨立 | | `local-agent/frontend/`(本目錄) | 連線橋樑 UI(狀態 / 配對 / 設定)| 獨立專案 | **核心設計決策**:視覺上與 visionA-frontend 保持一致,讓同一位使用者在雲端 Web 與桌面 Agent 之間切換「能一眼認出是同家人」。 ## 前置需求 - **Node.js** ≥ 20 - **pnpm** ≥ 9(專案使用 pnpm,**請勿** `npm install` / `yarn install`,會產生第二份 lockfile) - (整合模式才需要)**Go** ≥ 1.22 與 Wails CLI v2 — 詳見 `../README.md`(local-agent 根目錄) ## 兩種開發模式 ### 模式 A — 獨立前端開發(`pnpm dev`) 純前端迭代(調色、調文案、元件樣式、互動邏輯)時使用,**不需要** Wails binding。 ```bash pnpm install pnpm dev # → http://localhost:3000(支援 HMR) ``` 此模式下: - `src/hooks/use-*` 的 Wails binding 呼叫會自動走 **mock 實作**(見 `src/lib/agent-api.ts` 的 runtime 偵測) - 連線狀態、配對結果、設定值皆為假資料;可切 `notPaired` / `online` / `reconnecting` / `error` 等變體驗證 UI - 可直接切換 Light / Dark、zh-Hant / en 驗證 Design Tokens 與 i18n ### 模式 B — 與 Wails 整合(`wails dev`) 驗證與 Go backend(tunnel client / pairing / connstate)的真實互動時使用。 ```bash # 在 local-agent/ 根目錄 wails dev # → Wails 會自動啟動 `pnpm dev` 並注入 Wails runtime;前端呼叫 window.go.main.App.* 走真實 binding ``` 此模式下: - `isWailsRuntime()` 會回 `true`,`agent-api.ts` 改走真實 Wails `window.go.main.App.*` - 事件(`connection:status` / `connection:log` / `settings:updated` / `pairing:result`)由 Go broadcaster 推送 - Token 會真的寫入 OS keychain(macOS Keychain / Windows Credential Manager / Linux Secret Service) ## Build ```bash pnpm build ``` 產出到 `out/`(Next.js `output: "export"` 的靜態匯出結果)。Wails build 時透過 `wails.json` 的 `frontend:install` + `frontend:build` 呼叫此指令,然後透過 `assetdir: "./frontend/out"` 用 `//go:embed` 把 `out/` 整包嵌入最終可執行檔。 ```bash # 完整桌面應用 build(在 local-agent/ 根目錄) wails build ``` ### 為什麼是 `output: "export"`? | 方案 | 是否可行 | 理由 | |------|---------|------| | Next.js server(`standalone`) | ❌ | Wails 不跑 Node server | | Next.js SSR / SSG | ❌ | 同上,且 Wails 桌面環境無 HTTP server | | **Next.js `output: "export"`** | ✅ | 純靜態,可 embed;`local-tool` 已長期驗證此模式穩定 | | Vite + SPA | ❓ | 可行但放棄 — 會引入第二套 build pipeline,且 visionA-frontend 元件要改 import / `'use client'` directive | 詳見 `.autoflow/04-architecture/visiona-agent-tdd.md` §3.2。 ## 可用腳本 | 指令 | 說明 | |------|------| | `pnpm dev` | 啟動開發伺服器(mock bindings,http://localhost:3000)| | `pnpm build` | 產出靜態檔到 `out/`(供 Wails `go:embed`)| | `pnpm lint` | ESLint 檢查(`eslint-config-next` + `react-hooks`)| | `pnpm test` | 執行所有 Vitest 測試(one-shot)| | `pnpm test:watch` | Watch 模式 | ## 專案結構 ``` frontend/ ├── next.config.ts # output: "export" + trailingSlash + images.unoptimized ├── package.json # name: visiona-agent-frontend ├── tsconfig.json ├── postcss.config.mjs ├── eslint.config.mjs ├── vitest.config.ts ├── components.json # shadcn/ui 產生器設定(保留以利未來新增元件) ├── public/ │ └── visiona-logo.png └── src/ ├── app/ │ ├── globals.css # Design Tokens + Tailwind 層(100% 從 visionA-frontend 複製) │ ├── layout.tsx # Root layout(LocaleProvider / ThemeProvider / TooltipProvider / Toaster) │ └── page.tsx # AgentApp — 3 tabs(Radix Tabs)單頁切換入口 ├── components/ │ ├── theme-provider.tsx │ ├── layout/ # Agent 專屬 layout:AppShell / Header / TabNav / ConnectionStatusBadge │ ├── agent/ # 狀態頁元件:StatusHero / InfoCard / RecentLog;配對頁元件:TokenInput │ └── ui/ # 24 個 Radix UI primitives(shadcn 風格封裝)+ EmptyState / Spinner / Sonner ├── views/ # 3 個 tab 對應的 view 元件(不走 Next.js routing,以 useState 切換) │ ├── status-view.tsx │ ├── pair-view.tsx │ └── settings-view.tsx ├── hooks/ # Wails bindings 的 React wrapper │ ├── use-connection-status.ts # GetStatus() + connection:status event │ ├── use-recent-logs.ts # GetRecentLog() + connection:log event │ ├── use-pair.ts # Pair(token) + pairing:result event │ ├── use-settings.ts # GetSettings() + UpdateSettings(patch) + settings:updated │ ├── use-test-connection.ts # TestRelay(url) │ └── use-export-log.ts # ExportLog() ├── lib/ │ ├── utils.ts # cn() — clsx + tailwind-merge │ ├── agent-api.ts # Wails binding 抽象層(真實 binding + mock 雙實作自動切換) │ └── i18n/ │ ├── context.tsx # LocaleProvider / useLocale / useT │ ├── sync.tsx # LocaleSync — mount 後從 localStorage 拉偏好 │ ├── index.ts # dictionaries 匯出入口 │ ├── types.ts # Locale / Dictionary / SUPPORTED_LOCALES │ └── dictionaries/ │ ├── zh-Hant.ts # 繁中字典(預設,約 93 keys) │ └── en.ts # English dictionary(key 集合與順序與 zh-Hant 完全一致) ├── types/ # 前端型別定義 │ ├── agent.ts # ConnectionState / ConnectionSnapshot / LogEntry / AgentSettings / PairError │ └── api.ts # 與本地 server 互動的 envelope(預留) └── tests/ └── setup.ts # @testing-library/jest-dom 全域 matcher ``` ## 三個頁面對應 | Tab | 路徑 | 主要元件 | 對應 spec 章節 | |-----|------|---------|---------------| | 狀態(`status`,預設) | `views/status-view.tsx` | `StatusHero`(80px 大狀態圓)+ `InfoCard`(帳號/Relay/Session)+ `RecentLog`(最近 10 筆事件)| spec §4 | | 配對(`pair`) | `views/pair-view.tsx` | `TokenInput`(格式驗證 + 貼上 trim + Enter 送出)+ 錯誤 Alert + 底部安全提示 | spec §5 | | 設定(`settings`) | `views/settings-view.tsx` | 5 區塊:連線 / 行為 / Log / 關於 / 危險區域 | spec §6 | Tab 切換走 **Radix Tabs**(controlled `value`/`onValueChange`),不走 Next.js routing。跨 view 的程式化切換透過 `window` CustomEvent `agent:switch-tab` 實現(見 `status-view.tsx` 與 `page.tsx`)。 ## Design Tokens 完全沿用 `visionA-frontend`:不動一行 `globals.css`。 | Token 類別 | 來源 | |-----------|------| | 色彩(`--background` / `--primary` / `--muted-foreground` 等)| `globals.css`(`@layer base` 定義 light / dark 值)| | 連線狀態色(`--status-online` / `--status-offline` / `--status-reconnecting` / `--status-idle` / `--status-error`)| 同上(visionA-frontend F2 定義)| | 字型 | `font-sans`(UI)/ `font-mono`(Token / Log / Relay URL)| | 圓角 / 陰影 / 間距 | Tailwind 4 預設 + 少量 `@theme` 擴充 | **規則**:不允許在元件裡寫死 `#xxxxxx` 或 `rgb(...)`,一律透過 Tailwind utility class 引用 CSS 變數(例如 `bg-status-online`、`text-destructive`)。 ## i18n 採用自製輕量 i18n(不依賴 `next-intl`),以 React Context + `localStorage` 管理當前 locale。 - 預設 locale:`zh-Hant`(繁中) - 支援:`zh-Hant` / `en` - 儲存 key:`visionA.locale` - Fallback:找不到 key 時回傳 key 本身(production 靜默;dev 印 warning) - 測試保證:`i18n.test.ts` 驗證兩語系 key 集合完全一致,避免漏譯 字典採**扁平 key** 結構(例:`nav.status`),共分 8 個區塊: | 區塊 | 說明 | |------|------| | `app.*` | 產品名稱、標語 | | `common.*` | 通用按鈕 / 文案(loading / cancel / save ...)| | `nav.*` | Tab 導航標籤 | | `connection.*` | 連線狀態(online / offline / reconnecting / notPaired / error) | | `header.*` | Header 工具列(切主題、切語言)| | `status.*` | 狀態頁(hero / info / action / confirm / log / empty)| | `pair.*` | 配對頁(title / input / button / alert / error)| | `settings.*` | 設定頁(section / relayUrl / behavior / log / about / reset)| 新增 i18n key 時的 checklist: 1. 兩個字典(`zh-Hant.ts` + `en.ts`)**同步新增** — 否則 `i18n.test.ts` 會失敗 2. 保持兩邊區塊順序一致(便於 diff 與 review) 3. 變數內插用 `{name}` 風格(例:`Attempt {n} of 5`),呼叫端以 `String.prototype.replace` 替換 4. 錯誤訊息要「說清楚發生什麼 + 使用者可以做什麼」 ## 測試 ```bash pnpm test # one-shot 執行所有測試 pnpm test:watch # watch 模式 ``` ### 測試策略 | 層級 | 工具 | 範例 | |------|------|------| | 單元(元件) | Vitest + RTL | `button.test.tsx` / `token-input.test.tsx`(行為 + 格式驗證 regex)| | 單元(hooks / utils) | Vitest | `i18n.test.ts`(字典完整性 + key 集合一致性)| | 整合(view) | Vitest + RTL | `app-shell.test.tsx`(Header + Badge 組合)| | E2E | — | 由 Testing Agent 負責,不在本 repo | ### 關鍵測試 - **`i18n.test.ts`** — 字典完整性守門員;兩語系 key 集合差異時立即失敗 - **`token-input.test.tsx`** — Pairing Token regex `^vAc_[a-f0-9]{32}$/i`(大小寫不敏感)+ 貼上清理 + Enter 送出 - **`status-hero.test.tsx`** — 5 種狀態變體(online / offline / reconnecting / notPaired / error)+ aria-label 組合 + icon `aria-hidden` ## 效能預算 本 Agent 不是 Web App,不需要滿足 Core Web Vitals。但仍有一些自律規則: | 項目 | 目標 | |------|------| | Build 時間 | < 10s(Next.js 16 Turbopack 目前約 3-4s) | | `out/` 總大小 | < 5 MB(static export 後,Wails embed 成本敏感) | | 首次渲染到互動 | < 200ms(Wails WebView 2 本機載入,無網路延遲) | | 冷啟動到可互動 | < 1s(含 Wails process init + WebView init)| bundle 分析:`pnpm build` 時 Next.js 會印出每條 route 的大小。若單 route JS > 200KB,需檢討是否有多餘依賴或未做 code splitting。 ## 無障礙規範 嚴格遵守 WCAG 2.2 AA(對齊 spec §9): - **不只靠顏色**:每個連線狀態同時有「圓點顏色 + icon + 文字標籤」 - **語義化 HTML**:`
` / `
` / `