visionA/local-tool/.autoflow/03-design/v2/source-selector-update.md
jim800121chen 8cd5751ce3 feat(local-tool): M8 重構 — Wails 控制台 + 瀏覽器 Web UI(R5 決策)
依 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>
2026-04-15 17:57:54 +08:00

205 lines
8.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# v2.3 — source-selector 修改規格
> 本章對應 R5砍 URL 推論)+ R5-6ffmpeg 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` | `[上傳中...]` buttondisabled+ 格式說明 | 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 刪除 keyzh-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`,若 renametype 檔也要同步。**建議 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 pickermacOS 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 + URLYouTube / 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 做回歸測試。