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

431 lines
15 KiB
TypeScript
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.

import type { TranslationDict } from './types';
export const zhTW: TranslationDict = {
common: {
save: '儲存',
cancel: '取消',
delete: '刪除',
back: '返回',
close: '關閉',
done: '完成',
retry: '重試',
connect: '連線',
disconnect: '中斷連線',
manage: '管理',
view: '檢視',
upload: '上傳',
uploading: '上傳中...',
loading: '載入中...',
search: '搜尋',
reset: '重設',
start: '開始',
stop: '停止',
na: 'N/A',
custom: '自訂',
},
nav: {
dashboard: '儀表板',
modelLibrary: '模型庫',
devices: '裝置',
workspace: '工作區',
settings: '設定',
appName: 'visionA Local',
version: 'visionA Local v0.1.0',
platformTitle: 'visionA Local — 邊緣 AI 工作站',
serverConnected: '伺服器已連線',
serverDisconnected: '伺服器未連線',
},
workspace: {
title: '工作區',
subtitle: '選擇裝置以開始',
noConnectedDevice: '沒有已連線的裝置',
noConnectedDeviceDesc: '請先連線一台裝置,再開始推論。',
goToDevices: '前往裝置頁',
},
dashboard: {
title: '儀表板',
subtitle: 'visionA 工作站總覽',
models: '模型',
devices: '裝置',
connected: '已連線',
flashes: '燒錄次數',
quickActions: '快速操作',
browseModels: '瀏覽模型',
manageDevices: '管理裝置',
uploadModel: '上傳模型',
recentActivity: '近期活動',
noActivity: '尚無活動紀錄。',
connectedDevices: '已連線裝置',
noConnectedDevices: '目前沒有已連線的裝置。',
justNow: '剛剛',
minutesAgo: '{n} 分鐘前',
hoursAgo: '{n} 小時前',
daysAgo: '{n} 天前',
},
devices: {
title: '裝置',
subtitle: '管理你的 Edge AI 裝置',
scan: '掃描裝置',
scanning: '掃描中...',
noDevices: '未偵測到 Kneron 裝置。請連接 KL520 / KL720 後按「掃描」。',
type: '類型',
firmware: '韌體',
flashedModel: '已燒錄模型',
status: {
detected: '已偵測',
connecting: '連線中...',
connected: '已連線',
disconnecting: '斷線中...',
flashing: '燒錄中',
inferencing: '推論中',
error: '錯誤',
disconnected: '已中斷',
},
health: {
title: '裝置健康狀態',
status: '狀態',
firmwareVersion: '韌體版本',
uptime: '運行時間',
lastSeen: '最後活動',
},
connectionLog: {
title: '連線歷史',
noEvents: '尚無連線事件紀錄。',
connected: '已連線',
disconnected: '已中斷',
},
settings: {
title: '裝置設定',
alias: '自訂名稱',
aliasPlaceholder: '例如:實驗室裝置 #1',
notes: '備註',
notesPlaceholder: '新增關於此裝置的備註...',
saveSettings: '儲存設定',
settingsSaved: '裝置設定已儲存',
},
detail: {
deviceInfo: '裝置資訊',
id: 'ID',
type: '類型',
firmware: '韌體',
port: '連接埠',
modelStatus: '模型狀態',
readyForInference: '裝置已準備好進行推論。開啟工作區即可開始。',
noModelFlashed: '此裝置尚未燒錄模型。請使用「燒錄模型」按鈕部署模型。',
openWorkspace: '開啟工作區',
},
flash: {
flashModel: '燒錄模型',
flashToDevice: '燒錄模型至裝置',
selectModel: '選擇模型',
hardwareIncompatible: '硬體不相容',
incompatibleDesc: '此模型不支援 {device}。',
incompatibleCannotFlash: '不相容 — 無法燒錄',
startFlash: '開始燒錄',
flashFailed: '燒錄失敗',
preparingFlash: '準備燒錄中...',
flashComplete: '燒錄完成!',
},
},
models: {
title: '模型庫',
subtitle: '瀏覽並選擇 AI 模型進行部署',
noModelsFound: '未找到符合條件的模型。',
compareModels: '比較模型',
exitCompare: '離開比較',
uploadModel: '上傳模型',
accuracy: '準確率',
fps: 'FPS',
size: '大小',
hardware: '硬體',
latency: '延遲',
framework: '框架',
inputSize: '輸入大小',
quantization: '量化',
version: '版本',
author: '作者',
license: '授權',
labels: '標籤',
performance: '效能',
specifications: '規格',
hardwareCompatibility: '硬體相容性',
metadata: '後設資料',
deployToDevice: '部署至裝置',
deleteCustomModel: '刪除自訂模型',
deleteConfirm: '確定要刪除「{name}」嗎?此操作無法復原。',
comparison: {
title: '模型比較',
metric: '指標',
selected: '已選取 {n} 個模型',
compare: '比較',
clear: '清除',
taskType: '任務類型',
modelSize: '模型大小',
},
filters: {
searchPlaceholder: '搜尋模型...',
taskType: '任務類型',
allTypes: '所有類型',
allHardware: '所有硬體',
},
upload: {
title: '上傳自訂模型',
modelFile: '模型檔案 (.nef) *',
clickToSelect: '點擊選擇 .nef 檔案',
modelName: '模型名稱 *',
modelNamePlaceholder: '例如My Custom YOLOv5',
description: '描述',
descriptionPlaceholder: '選填描述',
taskType: '任務類型 *',
taskTypePlaceholder: '選擇任務類型',
objectDetection: '物件偵測',
classification: '分類',
segmentation: '語意分割',
poseEstimation: '姿態估計',
labels: '標籤 *(以逗號分隔)',
labelsPlaceholder: '例如person, car, dog, cat',
inputWidth: '輸入寬度',
inputHeight: '輸入高度',
quantization: '量化',
errors: {
noFile: '請選擇 .nef 檔案',
noName: '請輸入模型名稱',
noTaskType: '請選擇任務類型',
noLabels: '至少需要一個標籤',
uploadFailed: '上傳失敗,請重試。',
},
},
},
camera: {
camera: '攝影機',
image: '圖片',
video: '影片',
startCamera: '啟動攝影機',
stopCamera: '停止攝影機',
stopImage: '停止圖片',
stopVideo: '停止影片',
stopBatch: '停止批次',
selectImage: '選擇圖片',
selectImages: '選擇圖片',
selectVideo: '選擇影片',
jpgPng: 'JPG, PNG',
jpgPngMultiple: 'JPG, PNG支援多選',
mp4AviMov: 'MP4 / AVI / MOV / MPEG / MPG',
noInputSource: '無輸入來源',
selectSourceHint: '請在上方選擇攝影機、圖片或影片',
uploadedImage: '已上傳圖片',
videoPlayback: '影片播放',
cameraFeed: '攝影機畫面',
dropImagesHere: '將圖片拖放至此',
batchProcessing: '處理中',
batchImages: '批次圖片',
noCameraDetected: '未偵測到攝影機,請使用圖片或影片進行推論。',
},
inference: {
confidenceFilter: '信心度篩選',
confidenceThreshold: '信心度門檻',
classificationResults: '分類結果',
noResultsAboveThreshold: '沒有超過門檻的結果',
details: '詳細資訊',
model: '模型',
task: '任務',
latency: '延遲',
fps: 'FPS',
workspace: '工作區',
startInference: '開始推論',
stopInference: '停止推論',
batchProgress: '批次進度',
processed: '已處理',
currentImage: '檢視中',
totalImages: '總共',
videoProgress: '影片進度',
frames: '幀',
framesProcessed: '已處理幀數',
},
settings: {
title: '設定',
subtitle: '應用程式設定',
tabs: {
general: '一般',
hardware: '硬體',
models: '模型',
advanced: '進階',
},
general: {
title: '一般',
language: '語言',
languageFollowSystem: '跟隨系統',
languageFollowSystemLabel: '跟隨系統(目前:{detected}',
languageRestartHint: '語言切換會立即生效,但原生系統選單可能需重啟 App 才會更新。',
theme: '主題',
themeFollowSystem: '跟隨系統',
themeFollowSystemHint: '深色模式會自動跟隨系統設定。',
themeCurrentLight: '目前:淺色',
themeCurrentDark: '目前:深色',
dataDirectory: '資料目錄',
dataDirectoryHint: '日誌、設定與自訂模型皆存放於此目錄。',
},
hardware: {
title: '硬體',
pythonMode: 'Python 執行模式',
pythonModeAuto: '自動(優先內嵌)',
pythonModeBundled: '內嵌(推薦)',
pythonModeSystem: '系統 Python',
pythonModeHint: '此階段為唯讀顯示,後續版本會開放切換。',
serverPort: 'Server Port',
serverPortHint: '後端實際監聽的連接埠。',
},
models: {
title: '模型',
presetModels: '預置模型',
presetModelsEmpty: '目前沒有預置模型。',
presetModelsLoadFailed: '無法載入模型清單。',
uploadPath: '上傳路徑',
uploadPathHint: '你上傳的自訂模型會存放於此。',
},
advanced: {
title: '進階',
viewLogs: '查看伺服器日誌',
viewLogsHint: '檢視近期的伺服器日誌紀錄。',
restartServer: '重啟伺服器',
restartServerHint: '所有進行中的推論會停止,介面會自動重新連線。',
about: '關於',
resetAll: '重置所有設定',
resetAllHint: '將所有偏好恢復成預設值,不會刪除資料。',
},
serverConfig: '伺服器設定',
backendUrl: '後端 URL',
backendUrlPlaceholder: '例如http://192.168.1.100:3721',
backendUrlHint: '留空表示使用同源模式(前端嵌入 Go binary 時)。',
backendUrlSaved: '後端 URL 已更新',
saveBackendUrl: '儲存',
apiUrl: 'API URL',
wsUrl: 'WebSocket URL',
serverNote: '伺服器位址於建置時設定。',
appearance: '外觀',
theme: '主題',
themeLight: '淺色',
themeDark: '深色',
language: '語言',
displayLanguage: '顯示語言',
languageZhTW: '繁體中文',
languageEn: 'English',
about: '關於',
versionLabel: '版本',
platform: 'visionA Local — 邊緣 AI 工作站',
resetToDefaults: '恢復預設值',
resetSuccess: '設定已恢復預設值',
serverLogs: {
title: '伺服器日誌',
clear: '清除',
filter: '篩選',
autoScroll: '自動捲動',
noLogs: '尚無日誌紀錄。伺服器產生日誌後將即時顯示。',
entries: '筆紀錄',
},
serverStatus: {
title: '伺服器狀態',
uptimeLabel: '運行時間',
goroutinesLabel: 'Goroutine 數',
heapAllocLabel: '堆積記憶體',
sysMemLabel: '系統記憶體',
gcCyclesLabel: 'GC 次數',
nextGcLabel: '下次 GC',
versionLabel: '版本',
buildTimeLabel: '建置時間',
platformLabel: '平台',
goVersionLabel: 'Go 版本',
depsTitle: '相依套件',
depAvailable: '可用',
depNotFound: '未安裝',
restart: '重啟伺服器',
restarting: '重啟中...',
waitingForServer: '等待伺服器...',
serverBack: '伺服器已恢復',
restartConfirmTitle: '確定重啟伺服器?',
restartConfirmDesc: '伺服器將重新啟動。所有進行中的推論工作階段將被停止,介面將自動重新連線。',
restartConfirm: '重啟',
offline: '伺服器未連線',
offlineDesc: '無法連線到後端伺服器。請確認伺服器是否正在運行。',
backendAddress: '後端位址',
reconnect: '重新連線',
reconnecting: '重新連線中...',
},
},
onboarding: {
welcome: '歡迎使用 visionA Local',
step1Title: '連接你的裝置',
step1Desc: '插入 Kneron USB Dongle 並掃描裝置。平台將自動偵測支援的硬體。',
step2Title: '選擇 AI 模型',
step2Desc: '瀏覽模型庫並將預訓練模型燒錄到你的裝置。模型已針對邊緣推論進行最佳化。',
step3Title: '開始推論!',
step3Desc: '開啟工作區,選擇攝影機、圖片或影片,在你的邊緣裝置上執行即時 AI 推論。',
next: '下一步',
previous: '上一步',
getStarted: '開始使用',
skipTour: '跳過導覽',
},
emptyState: {
devicesTitle: '未偵測到裝置',
devicesDesc: '請插入 Kneron USB Dongle 並點擊掃描來偵測裝置。',
devicesScan: '掃描裝置',
modelsTitle: '探索 AI 模型',
modelsDesc: '瀏覽預訓練模型庫或上傳你自己的自訂模型。',
modelsBrowse: '瀏覽模型',
modelsUpload: '上傳模型',
},
tour: {
step1Title: '掃描裝置',
step1Desc: '點擊此按鈕掃描已連接的 Kneron USB 裝置。',
step1Pending: '請先點擊「掃描裝置」,目前尚未偵測到任何裝置。',
step2Title: '連線裝置',
step2Desc: '點擊「連線」與偵測到的裝置建立連線。',
step2Pending: '請先點擊裝置上的「連線」按鈕。',
step3Title: '管理裝置',
step3Desc: '點擊「管理」檢視裝置詳情、燒錄模型及開啟工作區。',
step3Pending: '請先連線裝置,才會顯示「管理」按鈕。',
step4Title: '燒錄模型',
step4Desc: '從模型庫選擇 AI 模型並燒錄至你的裝置。',
step4Pending: '請先將模型燒錄至裝置。',
step5Title: '開啟工作區',
step5Desc: '開啟工作區設定輸入來源並執行推論。',
step5Pending: '請先燒錄模型,完成後會出現「開啟工作區」按鈕。',
step6Title: '開始推論',
step6Desc: '選擇攝影機或上傳圖片/影片,開始即時 AI 推論!',
step6Pending: '請開始推論以完成導覽。',
next: '下一步',
prev: '上一步',
done: '完成',
exit: '離開導覽',
of: '/',
helpTooltip: '開始導覽',
},
errors: {
serverDisconnected: '伺服器連線中斷',
serverReconnected: '伺服器已重新連線',
failedToLoadDevices: '無法載入裝置列表',
deviceConnected: '裝置已連線',
deviceDisconnected: '裝置已中斷連線',
modelUploadSuccess: '模型上傳成功',
modelUploadFailed: '上傳失敗,請檢查網路連線',
modelDeleted: '模型已刪除',
modelDeleteFailed: '刪除失敗',
cannotStopStream: '無法停止串流',
imageUploadFailed: '圖片上傳失敗',
videoUploadFailed: '影片上傳失敗',
batchUploadFailed: '批次圖片上傳失敗',
unexpectedError: '發生未預期的錯誤',
},
offline: {
title: 'Local Server 已離線',
subtitle: {
quit: 'visionA Local 已結束,請重新開啟應用程式。',
restart: 'Server 正在重新啟動,請稍候…',
healthcheck: '偵測到 Server 無回應,正在重試連線…',
},
retryButton: '重試連線',
retrying: '正在重試中…',
helpText: '如要離開本頁,請直接關閉此分頁。',
},
};