fix: resolve TypeScript compilation errors

Fixes:
- VideoPlayer.vue: restore allTags, filteredMarks, marksByTag (removed by mistake)
- PeopleView.vue: fix frameTime scope in playFaceDetailVideo catch block
- Both errors caused white screen on build failure

Root cause:
1. visibleTags duplicate declaration (old + new logic)
2. frameTime undefined in catch block (scope issue)

Solution:
1. Keep allTags/filteredMarks/marksByTag (needed in template)
2. Keep visibleTags (new logic for processor toggles)
3. Move frameTime calculation to correct scope in catch block
This commit is contained in:
2026-07-24 23:33:40 +08:00
parent 48975ef247
commit cb7e1ba0ba
37 changed files with 6875 additions and 205 deletions

273
src/locales/en.json Normal file
View File

@@ -0,0 +1,273 @@
{
"common": {
"loading": "Loading...",
"error": "Error",
"success": "Success",
"cancel": "Cancel",
"confirm": "Confirm",
"delete": "Delete",
"save": "Save",
"edit": "Edit",
"search": "Search",
"back": "Back",
"close": "Close",
"play": "Play",
"logout": "Logout",
"login": "Login"
},
"library": {
"sort": {
"title": "Sort By",
"time_desc": "Time (Newest First)",
"time_asc": "Time (Oldest First)",
"size_desc": "Size (Largest First)",
"size_asc": "Size (Smallest First)",
"name_asc": "Name (A to Z)",
"name_desc": "Name (Z to A)"
},
"filter": {
"title": "Filter By",
"unregistered": "Unregistered",
"registered": "Registered",
"pending": "Pending",
"completed": "Completed",
"videos_only": "Videos Only",
"photos_only": "Photos Only"
},
"size": {
"title": "Size",
"min_mb": "Min MB",
"max_mb": "Max MB"
},
"duration": {
"title": "Video Duration",
"min_minutes": "Min Minutes",
"max_minutes": "Max Minutes"
},
"status": {
"files": "{count} files",
"registered": "Registered",
"unregistered": "Unregistered",
"pending": "Pending",
"processing": "Processing",
"completed": "Completed"
},
"thumbnail": {
"register_to_preview": "Register to see preview"
},
"context": {
"register_file": "Register File",
"process_file": "Process File",
"reprocess": "Reprocess",
"run_identity_agent": "Run Identity Agent",
"unregister": "Unregister",
"play_video": "Play Video",
"processor_selection": "Processor Selection",
"overall_progress": "Overall Progress",
"missing_file_path": "Missing file path",
"file_not_registered": "File not registered",
"select_processor": "Please select at least one processor",
"file_must_be_completed": "File must be completed first"
},
"pipeline": {
"processors": "Processors",
"rule1_ingestion": "Rule1 Ingestion",
"face_tracing": "Face Tracing",
"tkg_nodes": "TKG Nodes",
"tkg_edges": "TKG Edges",
"rule2_ingestion": "Rule2 Ingestion"
},
"phase": {
"processor_output": "Rule1: Processor Output",
"postgresql_ingestion": "Rule1: PostgreSQL Ingestion",
"qdrant_ingestion": "Rule1: Qdrant Ingestion"
},
"action": {
"registering": "Registering...",
"registered": "Registered: {name}",
"registration_failed": "Registration failed: {message}",
"registration_error": "Registration error: {error}",
"processing": "Processing...",
"processing_started": "Processing started: {name}",
"processing_failed": "Processing failed: {message}",
"processing_error": "Processing error: {error}",
"identity_agent_started": "Identity Agent started: {name}",
"identity_agent_failed": "Identity Agent failed: {message}",
"identity_agent_error": "Identity Agent error: {error}",
"removing": "Removing...",
"removed": "Removed: {name}",
"removal_failed": "Removal failed: {message}",
"removal_error": "Removal error: {error}",
"clear_filters": "Clear Filters"
},
"refresh": "Refresh",
"refresh_status": "Refresh Status"
},
"people": {
"toolbar": {
"all_files": "All Files",
"view_all_people": "View All People",
"view_starred_people": "View Starred People",
"refresh": "Refresh",
"pending_people": "People",
"skipped_people": "Skipped People",
"pending_faces": "Faces",
"face_group": "Face Group"
},
"section": {
"known_people": "Known People",
"pending_people": "People",
"skipped_people": "Skipped People",
"pending_faces": "Faces",
"face_group": "Face Group"
},
"sort": {
"title": "Sort By",
"recent": "Recently Viewed",
"file_name": "File Name",
"name_az": "Name (A to Z)",
"name_za": "Name (Z to A)",
"time_desc": "Time (Newest First)",
"time_asc": "Time (Oldest First)"
},
"search": {
"known_people": "Search known people",
"pending_people": "Search pending people",
"person_name": "Search person name..."
},
"action_history": {
"title": "Action History · {name}",
"undo_all": "Undo All",
"redo_all": "Redo All",
"delete_person": "Delete This Person",
"no_history": "No action history"
},
"expand": {
"collapse": "Collapse",
"expand": "Expand",
"view_faces": "View Faces",
"appears_in": "Appears in:",
"faces": "{count} faces",
"set_as_profile": "Set as Profile",
"no_face_data": "No face data"
},
"context": {
"star_person": "★ Mark as Important",
"unstar_person": "☆ Remove Important",
"details": "📋 Details",
"confirm_person": "✓ Confirm Person",
"pending": "Pending",
"edit_name": "✎ Edit Name",
"merge_person": "⇄ Already Have This Person",
"skip_person": "✕ Skip This Person",
"undo": "↩ Undo",
"redo": "↪ Redo",
"delete_person": "🗑 Delete This Person"
},
"face_context": {
"details": "📋 Details",
"assign_existing": "⇄ Assign to Existing Person",
"skip_face": "✕ Skip This Face"
},
"assign": {
"title": "Assign to Existing Person",
"search_placeholder": "Search person name...",
"no_results": "No matching people found",
"confirm_assign": "Confirm Assignment"
},
"face_detail": {
"title": "Pending Face Details",
"file": "File:",
"trace_id": "Trace ID:",
"frame_number": "Frame Number:",
"confidence": "Confidence:",
"face_id": "Face ID:",
"play_segment": "Play Video Segment",
"new_identity": "Create as New Person",
"enter_name": "Enter person name...",
"add": "Add",
"close": "Close"
},
"detail": {
"back": "Back",
"edit": "Edit",
"role": "Role",
"description": "Description",
"enter_name": "Enter name",
"enter_alias": "Enter alias and press Enter",
"add_alias": "+ Alias",
"collapse": "Collapse",
"role_name": "Role name",
"custom_description": "Custom description",
"saving": "Saving...",
"save_changes": "✓ Save Changes",
"cancel": "Cancel",
"add_same_person": "Add same person",
"play": "▶ Play",
"unbind": "✕ Unbind",
"segments": "{count} segments",
"traces": "{count} traces",
"all_segments": "All segments",
"mode_trace": "Trace",
"mode_segment": "Segment",
"mode_continuous": "Continuous",
"status": "Status:",
"confirm": "✓ Confirm",
"skip": "Skip",
"bind_face": "+ Bind Face",
"merge_other": "⇄ Merge to Other Person",
"delete_person": "Delete This Person",
"bind_face_title": "Bind Face",
"merge_title": "⇄ Merge to Other Person",
"unbind_trace": "Unbind trace T{trace_id} (face_id: {face_id})?",
"no_face_id": "This trace has no face_id, cannot unbind",
"unbind_failed": "Unbind failed: {error}",
"merge_confirm": "Merge \"{source}\" into \"{target}\"?",
"not_this_person": "Not This Person",
"delete_confirm": "Delete \"{name}\"?",
"delete_confirm_message": "Delete \"{name}\"? This action cannot be undone.",
"delete_failed": "Delete failed: {error}",
"create_identity_failed": "Failed to create person: API did not return identity_uuid",
"create_identity_error": "Failed to create person: {error}",
"assign_failed": "Assignment failed: {error}"
}
},
"agent": {
"welcome": {
"title": "👋 Welcome to Momentry Studio",
"intro": "I'll help you make better use of the system. Let me introduce the main features:",
"smart_search": {
"title": "Smart Search",
"desc": "Keyword, semantic, and people search modes"
},
"people_management": {
"title": "People Management",
"desc": "Identify people in videos, build people database"
},
"file_management": {
"title": "File Management",
"desc": "Manage and process your video and photo files"
},
"got_it": "Got it, start using"
}
},
"login": {
"title": "Momentry Studio",
"subtitle": "Login to continue",
"username": "Username",
"password": "Password",
"username_placeholder": "Enter username",
"password_placeholder": "Enter password",
"login_button": "Login",
"logging_in": "Logging in...",
"login_success": "Login successful!",
"default_credentials": "Default credentials: demo / demo123"
},
"nav": {
"search": "Search",
"library": "Library",
"people": "People",
"admin": "Admin",
"client": "Client"
}
}

77
src/locales/index.ts Normal file
View File

@@ -0,0 +1,77 @@
import { createI18n } from 'vue-i18n'
import en from './en.json'
import zhTW from './zh-TW.json'
export type MessageSchema = typeof en
const supportedLanguages = {
'en': en,
'zh-TW': zhTW,
}
function detectInitialLanguage(): string {
const saved = localStorage.getItem('app_language')
if (saved && saved in supportedLanguages) {
return saved
}
if (saved === 'auto') {
return detectOSLanguage()
}
return 'en'
}
function detectOSLanguage(): string {
let osLang = 'en'
try {
if ((window as any).__TAURI__) {
const locale = (window as any).__TAURI__?.os?.getLocale?.()
if (locale) osLang = locale
} else {
osLang = navigator.language || 'en'
}
} catch (e) {
console.error('Language detection failed:', e)
}
if (osLang.startsWith('zh-TW') || osLang.startsWith('zh-Hant')) {
return 'zh-TW'
} else if (osLang.startsWith('zh-CN') || osLang.startsWith('zh-Hans')) {
return 'zh-CN'
} else if (osLang.startsWith('ja')) {
return 'ja'
} else if (osLang.startsWith('ko')) {
return 'ko'
}
return 'en'
}
const i18n = createI18n<[MessageSchema], 'en' | 'zh-TW'>({
legacy: false,
locale: detectInitialLanguage(),
fallbackLocale: 'en',
messages: supportedLanguages as any,
missingWarn: false,
fallbackWarn: false,
})
export function changeLanguage(lang: string) {
if (lang === 'auto') {
localStorage.setItem('app_language', 'auto')
const detected = detectOSLanguage()
i18n.global.locale = detected as any
} else {
localStorage.setItem('app_language', lang)
i18n.global.locale = lang as any
}
}
export function getCurrentLanguage(): string {
return i18n.global.locale as string
}
export { detectOSLanguage }
export default i18n

273
src/locales/zh-TW.json Normal file
View File

@@ -0,0 +1,273 @@
{
"common": {
"loading": "載入中...",
"error": "錯誤",
"success": "成功",
"cancel": "取消",
"confirm": "確認",
"delete": "刪除",
"save": "儲存",
"edit": "編輯",
"search": "搜尋",
"back": "返回",
"close": "關閉",
"play": "播放",
"logout": "登出",
"login": "登入"
},
"library": {
"sort": {
"title": "排序方式",
"time_desc": "依時間・由近到遠",
"time_asc": "依時間・由遠到近",
"size_desc": "依大小・由大到小",
"size_asc": "依大小・由小到大",
"name_asc": "依檔名・由 A 到 Z",
"name_desc": "依檔名・由 Z 到 A"
},
"filter": {
"title": "過濾方式",
"unregistered": "未註冊",
"registered": "已註冊",
"pending": "待處理",
"completed": "已完成",
"videos_only": "僅顯示影片",
"photos_only": "僅顯示照片"
},
"size": {
"title": "大小",
"min_mb": "最小 MB",
"max_mb": "最大 MB"
},
"duration": {
"title": "影片時長",
"min_minutes": "最小分鐘",
"max_minutes": "最大分鐘"
},
"status": {
"files": "{count} 個檔案",
"registered": "已註冊",
"unregistered": "未註冊",
"pending": "等待中",
"processing": "處理中",
"completed": "已完成"
},
"thumbnail": {
"register_to_preview": "註冊以預覽"
},
"context": {
"register_file": "註冊此檔案",
"process_file": "處理此檔案",
"reprocess": "重新處理",
"run_identity_agent": "執行 Identity Agent",
"unregister": "移除註冊",
"play_video": "播放影片",
"processor_selection": "處理器選擇",
"overall_progress": "整體進度",
"missing_file_path": "缺少檔案路徑",
"file_not_registered": "檔案尚未註冊",
"select_processor": "請至少選擇一個處理器",
"file_must_be_completed": "檔案需先完成處理"
},
"pipeline": {
"processors": "處理器",
"rule1_ingestion": "Rule1 入庫",
"face_tracing": "Face Tracing",
"tkg_nodes": "TKG Nodes",
"tkg_edges": "TKG Edges",
"rule2_ingestion": "Rule2 入庫"
},
"phase": {
"processor_output": "Rule1: 處理器輸出",
"postgresql_ingestion": "Rule1: PostgreSQL 入庫",
"qdrant_ingestion": "Rule1: Qdrant 入庫"
},
"action": {
"registering": "註冊中...",
"registered": "註冊成功:{name}",
"registration_failed": "註冊失敗:{message}",
"registration_error": "註冊錯誤:{error}",
"processing": "處理中...",
"processing_started": "處理已觸發:{name}",
"processing_failed": "處理失敗:{message}",
"processing_error": "處理錯誤:{error}",
"identity_agent_started": "Identity Agent 已啟動:{name}",
"identity_agent_failed": "Identity Agent 失敗:{message}",
"identity_agent_error": "Identity Agent 錯誤:{error}",
"removing": "移除中...",
"removed": "移除成功:{name}",
"removal_failed": "移除失敗:{message}",
"removal_error": "移除錯誤:{error}",
"clear_filters": "清除篩選"
},
"refresh": "重新整理",
"refresh_status": "重新整理狀態"
},
"people": {
"toolbar": {
"all_files": "全部檔案",
"view_all_people": "查看所有人物",
"view_starred_people": "查看重要人物",
"refresh": "重新整理",
"pending_people": "人物",
"skipped_people": "已略過",
"pending_faces": "人臉",
"face_group": "人臉群組"
},
"section": {
"known_people": "已知人物",
"pending_people": "人物",
"skipped_people": "已略過",
"pending_faces": "人臉",
"face_group": "人臉群組"
},
"sort": {
"title": "排序方式",
"recent": "最近瀏覽",
"file_name": "依檔案名稱",
"name_az": "依檔名・由 A 到 Z",
"name_za": "依檔名・由 Z 到 A",
"time_desc": "依時間・由近到遠",
"time_asc": "依時間・由遠到近"
},
"search": {
"known_people": "搜尋已知人物",
"pending_people": "搜尋待定人物",
"person_name": "搜尋人物名稱..."
},
"action_history": {
"title": "操作記錄 · {name}",
"undo_all": "全部復原",
"redo_all": "全部重做",
"delete_person": "刪除此人物",
"no_history": "尚無操作記錄"
},
"expand": {
"collapse": "收起",
"expand": "展開",
"view_faces": "查看面孔",
"appears_in": "出現檔案:",
"faces": "{count} 面孔",
"set_as_profile": "設為大頭貼",
"no_face_data": "無面孔資料"
},
"context": {
"star_person": "★ 標為重要人物",
"unstar_person": "☆ 取消重要人物",
"details": "📋 詳情",
"confirm_person": "✓ 確認人物",
"pending": "待定",
"edit_name": "✎ 編輯名稱",
"merge_person": "⇄ 已有此人物",
"skip_person": "✕ 略過此人物",
"undo": "↩ 復原",
"redo": "↪ 重做",
"delete_person": "🗑 刪除此人物"
},
"face_context": {
"details": "📋 詳情",
"assign_existing": "⇄ 指派給現有人物",
"skip_face": "✕ 略過此人臉"
},
"assign": {
"title": "指派給現有人物",
"search_placeholder": "搜尋人物名稱...",
"no_results": "沒有找到符合的人物",
"confirm_assign": "確認指派"
},
"face_detail": {
"title": "待定人臉詳情",
"file": "檔案:",
"trace_id": "Trace ID:",
"frame_number": "帧號:",
"confidence": "置信度:",
"face_id": "Face ID:",
"play_segment": "播放影片片段",
"new_identity": "新增為新人物",
"enter_name": "輸入人物名稱...",
"add": "新增",
"close": "關閉"
},
"detail": {
"back": "返回",
"edit": "編輯",
"role": "角色",
"description": "描述",
"enter_name": "加入人名",
"enter_alias": "輸入別名後按 Enter",
"add_alias": "+ 別名",
"collapse": "收起",
"role_name": "角色名稱",
"custom_description": "自訂描述",
"saving": "儲存中...",
"save_changes": "✓ 儲存更改",
"cancel": "取消",
"add_same_person": "加入相同人物",
"play": "▶ 播放",
"unbind": "✕ 解綁",
"segments": "{count} 個片段",
"traces": "{count} 個追蹤",
"all_segments": "所有片段",
"mode_trace": "追蹤",
"mode_segment": "片段",
"mode_continuous": "連續",
"status": "狀態:",
"confirm": "✓ 確認",
"skip": "略過",
"bind_face": "+ 綁定人臉",
"merge_other": "⇄ 合併到其他人物",
"delete_person": "刪除此人物",
"bind_face_title": "Bind Face",
"merge_title": "⇄ 合併到其他人物",
"unbind_trace": "解綁 trace T{trace_id}(face_id: {face_id})?",
"no_face_id": "此 trace 沒有 face_id,無法解綁",
"unbind_failed": "解綁失敗:{error}",
"merge_confirm": "合併「{source}」到「{target}」?",
"not_this_person": "不是此人物",
"delete_confirm": "刪除「{name}」?",
"delete_confirm_message": "刪除「{name}」?此操作無法復原。",
"delete_failed": "刪除失敗:{error}",
"create_identity_failed": "新增人物失敗:API 未返回 identity_uuid",
"create_identity_error": "新增人物失敗:{error}",
"assign_failed": "指派失敗:{error}"
}
},
"agent": {
"welcome": {
"title": "👋 歡迎使用 Momentry Studio",
"intro": "我會幫助你更好地使用系統。讓我介紹一下主要功能:",
"smart_search": {
"title": "智能搜索",
"desc": "關鍵詞、語義、人物三種搜索模式"
},
"people_management": {
"title": "人物管理",
"desc": "識別視頻中的人物,建立人物庫"
},
"file_management": {
"title": "文件管理",
"desc": "管理和處理你的視頻、照片文件"
},
"got_it": "知道了,開始使用"
}
},
"login": {
"title": "Momentry Studio",
"subtitle": "Login to continue",
"username": "Username",
"password": "Password",
"username_placeholder": "Enter username",
"password_placeholder": "Enter password",
"login_button": "Login",
"logging_in": "Logging in...",
"login_success": "Login successful!",
"default_credentials": "Default credentials: demo / demo123"
},
"nav": {
"search": "Search",
"library": "Library",
"people": "People",
"admin": "Admin",
"client": "Client"
}
}