依 R5 五輪決策把 visionA-local 從「Wails 內嵌 Next.js」重構為「Wails
本機伺服器控制台 + 瀏覽器 Web UI」模式(類比 Docker Desktop / Ollama)。
程式碼變動
- M8-1 砍 yt-dlp 全套(後端 resolver / URL handler / 前端 URL tab /
Makefile vendor / installer / bootstrap / CI workflow,-555 行)
- M8-2 砍 Mock 模式全套(driver/mock、mock_camera、Settings runtimeMode、
VISIONA_MOCK 環境變數,-528 行)
- M8-3 ffmpeg 從 GPL 切換到 LGPL 混合方案:Windows/Linux 用 BtbN 現成
LGPL binary,macOS 自 build minimal decoder-only 進 git
(vendor/ffmpeg/macos/ffmpeg 5.7MB + ffprobe 5.6MB,比 GPL 版省 85% 空間)
- M8-4 Wails Server Controller:state machine、log ring buffer 2000 行、
preferences.json atomic write、boot-id、Gin SkipPaths、shutdown 7+1 秒、
notify_*.go 三平台 OS 通知、watchServer 改 Error state 不 os.Exit
- M8-4b 啟動階段管線 R5-E:6 階段進度 event、20s soft / 60s hard timeout、
stage 5/6 skip 規則、sentinel file、RestartStartupSequence 5 步驟
- M8-5 Wails 控制台 vanilla HTML/JS/CSS(9 檔 ~2012 行)取代 M7-B splash:
state 視覺、log panel、startup progress panel、Stage 6 manual CTA
pulse、shutdown modal、Settings、Dark Mode、i18n 中英雙語
- M8-6 上傳影片副檔名擴充(mp4/avi/mov/mpeg/mpg)
- M8-7 Web UI Server Offline Overlay(role=alertdialog + focus trap +
wsEverConnected 容錯 + Page Visibility)
- M8-8 CORS middleware(127.0.0.1/localhost only + suffix attack 防護)+
ws/origin.go 獨立 WebSocket CheckOrigin 避 package cycle
- MAJ-4 server:shutdown-imminent WebSocket broadcast 機制
(/ws/system endpoint + notifyShutdownImminent helper)
- M8-9 Boot-ID + 瀏覽器 tab 自動重連(sessionStorage loop guard)
品質
- ~105+ 新 unit test + race detector (-count=2) 全綠
- 10 個 milestone 全部通過 Reviewer 審查
- 三方 v2 + v2.1 文件(PRD / Design Spec / TDD)+ 交叉互審紀錄
收錄在 .autoflow/
交付前待處理(M8-10)
- 重跑 make payload-macos 把舊 GPL 77MB binary 換成新 LGPL
- 三平台 end-to-end build 驗證
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
205 lines
8.1 KiB
Markdown
205 lines
8.1 KiB
Markdown
# v2.3 — source-selector 修改規格
|
||
|
||
> 本章對應 R5(砍 URL 推論)+ R5-6(ffmpeg decoder 支援 mp4/avi/mov/mpeg/mpg)。
|
||
> 影響檔案:`frontend/src/components/camera/source-selector.tsx`、`frontend/src/stores/camera-store.ts`、`frontend/src/lib/i18n/zh-TW.ts`、`frontend/src/lib/i18n/en.ts`、`frontend/src/lib/i18n/types.ts`
|
||
> 上層索引:`../design-spec-v2.md`
|
||
|
||
---
|
||
|
||
## 1. 修改範圍摘要
|
||
|
||
**砍**:
|
||
- video tab 下的 `URL mode`(`videoMode === 'url'` 分支整塊)
|
||
- `videoMode` state 本身(因為 video tab 只剩單一 `file` 模式)
|
||
- `videoUrl` state、`handleUrlSubmit` handler
|
||
- `startFromUrl` store action 呼叫端與 store 內實作
|
||
- 「正在解析影片連結,YouTube 影片可能需要 10-30 秒…」的 hard-coded 紅字提示
|
||
- YouTube / Vimeo / RTSP 相關文案
|
||
- i18n keys:`camera.uploadFile`、`camera.pasteUrl`、`camera.urlPlaceholder`、`camera.urlHelpText`
|
||
|
||
**改**:
|
||
- `<input accept>` 從 `.mp4,.avi,.mov` → `.mp4,.avi,.mov,.mpeg,.mpg`
|
||
- dropzone filter(若有影片 drop 支援)同步
|
||
- i18n key `camera.mp4AviMov` 值從 `MP4, AVI, MOV` → `MP4, AVI, MOV, MPEG`
|
||
- Video tab 內只剩一個「選擇影片」按鈕 + 格式說明文字,無 sub-mode 切換
|
||
|
||
**保留**:
|
||
- `isUploading` state(上傳大檔仍需 loading)
|
||
- `uploadVideo` action
|
||
- `sourceFilename` 顯示
|
||
- Camera / Image tabs 的所有邏輯完全不動
|
||
|
||
---
|
||
|
||
## 2. 新的 video tab UI 狀態表
|
||
|
||
### 2.1 結構(ASCII)
|
||
|
||
```
|
||
┌─ Video tab ────────────────────────────────────────────────┐
|
||
│ │
|
||
│ [ 選擇影片 ] MP4, AVI, MOV, MPEG │
|
||
│ │
|
||
│ (可選)dropzone:拖放影片檔到此 │
|
||
│ │
|
||
└──────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
### 2.2 狀態對照表
|
||
|
||
| 狀態 | 條件 | 可見元素 | 按鈕狀態 |
|
||
|------|------|--------|---------|
|
||
| Idle(預設) | 非 streaming、非 uploading | `[選擇影片]` button + 格式說明 `MP4, AVI, MOV, MPEG` | enabled |
|
||
| Uploading | `isUploading === true` | `[上傳中...]` button(disabled)+ 格式說明 | disabled |
|
||
| Streaming | `isStreaming === true`(由父元件處理,此 tab 的內容被 `[停止影片]` 按鈕取代) | `[停止影片]` + 檔名 | — |
|
||
|
||
**注意**:Video tab 不再有 `Upload file` / `Paste URL` 兩顆 sub-mode 切換按鈕;使用者點 tab 後**直接就是上傳區**。
|
||
|
||
---
|
||
|
||
## 3. i18n key 異動清單
|
||
|
||
### 3.1 新增 key
|
||
|
||
| Key | zh-TW | en | 說明 |
|
||
|-----|-------|----|------|
|
||
| `camera.mp4AviMovMpeg` | MP4, AVI, MOV, MPEG | MP4, AVI, MOV, MPEG | 取代舊的 `mp4AviMov` |
|
||
|
||
(也可以選擇直接改舊 key 的值,見 §3.3 的建議)
|
||
|
||
### 3.2 刪除 key(zh-TW / en / types 三檔都要刪)
|
||
|
||
| Key | 原值(zh-TW) | 刪除理由 |
|
||
|-----|-------------|---------|
|
||
| `camera.uploadFile` | 上傳檔案 | Video tab 不再有 sub-mode 切換,`camera.selectVideo` 已足夠 |
|
||
| `camera.pasteUrl` | 貼上連結 | URL mode 砍除 |
|
||
| `camera.urlPlaceholder` | `https://example.com/video.mp4` | 同上 |
|
||
| `camera.urlHelpText` | 支援 YouTube、直接影片 URL(.mp4 等)及 RTSP 串流。 | 同上 |
|
||
|
||
### 3.3 修改 key(保留但更新值)
|
||
|
||
| Key | 原值(zh-TW / en) | 新值(zh-TW / en) |
|
||
|-----|-----------------|-----------------|
|
||
| `camera.mp4AviMov` | `MP4, AVI, MOV` / `MP4, AVI, MOV` | `MP4, AVI, MOV, MPEG` / `MP4, AVI, MOV, MPEG` |
|
||
|
||
**注意**:`key 名`雖然寫 `mp4AviMov`,但值改為包含 `MPEG`。保留 key 名避免 rename 造成其他地方引用失效。或者也可以 rename 為 `camera.supportedVideoFormats`,若 rename,type 檔也要同步。**建議 rename**,更語義化。
|
||
|
||
### 3.4 `camera.selectVideo` 保留
|
||
|
||
文字「選擇影片 / Select video」不變,仍用於 video tab 的主 CTA。
|
||
|
||
---
|
||
|
||
## 4. 程式碼變更預期(示意,實作以 Frontend Agent 為準)
|
||
|
||
### 4.1 `source-selector.tsx` diff 摘要
|
||
|
||
**刪除**(大約 50 行):
|
||
- `const [videoMode, setVideoMode] = useState<'file' | 'url'>('file');`
|
||
- `const [videoUrl, setVideoUrl] = useState('');`
|
||
- `const handleUrlSubmit = async () => { ... }`
|
||
- 從 store 解構 `startFromUrl`
|
||
- `{activeTab === 'video' && (...)}` 內的 sub-mode 切換按鈕 `[上傳檔案] / [貼上連結]`
|
||
- URL mode 的整個 JSX 分支(輸入框 + 「正在解析」提示 + `camera.urlHelpText`)
|
||
- `import { Input }` 如果其他地方沒用,連 import 一起砍
|
||
|
||
**修改**:
|
||
- Video tab 的結構從 `<div className="flex flex-col gap-2 w-full">` 內含兩個 mode 分支,改為直接 render file upload 區
|
||
- `<input accept=".mp4,.avi,.mov">` → `<input accept=".mp4,.avi,.mov,.mpeg,.mpg">`
|
||
- 格式說明文字改為 `t('camera.supportedVideoFormats')`(或維持 `mp4AviMov` key)
|
||
|
||
**最終 video tab JSX 預期樣貌**:
|
||
|
||
```tsx
|
||
{activeTab === 'video' && (
|
||
<div className="flex items-center gap-3">
|
||
<Button
|
||
onClick={() => videoFileRef.current?.click()}
|
||
disabled={isUploading}
|
||
>
|
||
{isUploading ? t('common.uploading') : t('camera.selectVideo')}
|
||
</Button>
|
||
<span className="text-sm text-muted-foreground">
|
||
{t('camera.supportedVideoFormats')}
|
||
</span>
|
||
<input
|
||
ref={videoFileRef}
|
||
type="file"
|
||
accept=".mp4,.avi,.mov,.mpeg,.mpg"
|
||
className="hidden"
|
||
onChange={handleVideoSelect}
|
||
/>
|
||
</div>
|
||
)}
|
||
```
|
||
|
||
### 4.2 `camera-store.ts` diff 摘要
|
||
|
||
**刪除**:
|
||
- interface 中的 `startFromUrl: (url: string, deviceId: string) => Promise<void>;`
|
||
- 實作中的 `startFromUrl: async (url, deviceId) => { ... }` 整塊
|
||
|
||
**保留**:
|
||
- `uploadVideo` 不動
|
||
- `isUploading` state 不動
|
||
|
||
### 4.3 `i18n/zh-TW.ts`、`i18n/en.ts`、`i18n/types.ts`
|
||
|
||
三檔同步:
|
||
- 刪 `uploadFile`、`pasteUrl`、`urlPlaceholder`、`urlHelpText`
|
||
- 新增 `supportedVideoFormats`(或修改 `mp4AviMov` 值)
|
||
- types 檔移除對應 type 定義
|
||
|
||
### 4.4 其他可能殘留
|
||
|
||
執行 grep 檢查:
|
||
- `grep -r "startFromUrl" frontend/src/`
|
||
- `grep -r "pasteUrl" frontend/src/`
|
||
- `grep -r "youtube\|vimeo\|RTSP" frontend/src/`
|
||
|
||
若有殘留,一併清理。
|
||
|
||
---
|
||
|
||
## 5. 無障礙考量
|
||
|
||
Video tab 變為單一按鈕後:
|
||
|
||
| 項目 | 設計 |
|
||
|------|------|
|
||
| Tab order | camera → image → video → (內部) 選擇影片 button |
|
||
| Button aria-label | `aria-label="選擇影片檔案,支援 MP4 AVI MOV MPEG"` |
|
||
| Focus ring | 沿用 Button 元件預設 focus ring |
|
||
| Screen reader | Button 點擊開啟 native file picker,macOS VoiceOver / Windows Narrator 會自動 announce file dialog |
|
||
|
||
---
|
||
|
||
## 6. 回歸測試重點(給 Testing Agent)
|
||
|
||
- [ ] Video tab 點擊後**直接**看到 `[選擇影片]` 按鈕,無 sub-mode 切換
|
||
- [ ] 支援上傳 `.mp4`、`.avi`、`.mov`、`.mpeg`、`.mpg` 五種副檔名
|
||
- [ ] 不支援上傳 `.mkv`、`.webm`、`.flv`(ffmpeg LGPL minimal build 不含這些 decoder)
|
||
- [ ] 上傳中顯示 `上傳中...`,按鈕 disabled
|
||
- [ ] 上傳完成後開始 inference,`sourceFilename` 顯示正確檔名
|
||
- [ ] 全站 grep 不到任何 `startFromUrl` / `pasteUrl` / `youtube` / `vimeo` 字串
|
||
- [ ] 兩個語系(zh-TW / en)都沒有 dead keys
|
||
- [ ] 舊版 i18n key 被刪後,TypeScript 編譯無 dangling reference
|
||
|
||
---
|
||
|
||
## 7. 與 v1 差異對照
|
||
|
||
| 面向 | v1 | v2 |
|
||
|------|----|----|
|
||
| Video 來源類型 | file + URL(YouTube / Vimeo / RTSP / 直接 .mp4) | 僅 file |
|
||
| 支援副檔名 | `.mp4, .avi, .mov` | `.mp4, .avi, .mov, .mpeg, .mpg` |
|
||
| Sub-mode 切換 | 有(2 顆切換按鈕) | 無 |
|
||
| URL 輸入框 | 有 | **無** |
|
||
| 解析 URL 提示 | 有(「YouTube 影片可能需要 10-30 秒…」) | **無** |
|
||
| i18n key 數 | 含 4 個 URL 相關 key | 砍 4 個、新增 / 修改 1 個 |
|
||
| `startFromUrl` store action | 有 | **無** |
|
||
|
||
---
|
||
|
||
**下一步**:交 Frontend Agent 按上述 spec 實作,完成後交 Reviewer 審查、Testing Agent 做回歸測試。
|