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

8.1 KiB
Raw Blame History

v2.3 — source-selector 修改規格

本章對應 R5砍 URL 推論)+ R5-6ffmpeg decoder 支援 mp4/avi/mov/mpeg/mpg。 影響檔案:frontend/src/components/camera/source-selector.tsxfrontend/src/stores/camera-store.tsfrontend/src/lib/i18n/zh-TW.tsfrontend/src/lib/i18n/en.tsfrontend/src/lib/i18n/types.ts 上層索引:../design-spec-v2.md


1. 修改範圍摘要

  • video tab 下的 URL modevideoMode === 'url' 分支整塊)
  • videoMode state 本身(因為 video tab 只剩單一 file 模式)
  • videoUrl state、handleUrlSubmit handler
  • startFromUrl store action 呼叫端與 store 內實作
  • 「正在解析影片連結YouTube 影片可能需要 10-30 秒…」的 hard-coded 紅字提示
  • YouTube / Vimeo / RTSP 相關文案
  • i18n keyscamera.uploadFilecamera.pasteUrlcamera.urlPlaceholdercamera.urlHelpText

  • <input accept>.mp4,.avi,.mov.mp4,.avi,.mov,.mpeg,.mpg
  • dropzone filter若有影片 drop 支援)同步
  • i18n key camera.mp4AviMov 值從 MP4, AVI, MOVMP4, 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 預期樣貌

{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.tsi18n/en.tsi18n/types.ts

三檔同步:

  • uploadFilepasteUrlurlPlaceholderurlHelpText
  • 新增 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.flvffmpeg LGPL minimal build 不含這些 decoder
  • 上傳中顯示 上傳中...,按鈕 disabled
  • 上傳完成後開始 inferencesourceFilename 顯示正確檔名
  • 全站 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 做回歸測試。