cleanup: remove dead code and duplicate docs
- Remove session-ses_2f27.md (161KB raw session log) - Remove 49 ROOT_* duplicate files across REFERENCE/ - Remove 14 duplicate files between REFERENCE/ root and history/ - Remove asr_legacy.rs (dead code, replaced by asr.rs) - Remove src/core/worker/ (duplicate JobWorker) - Remove src/core/layers/ (empty directory) - Remove 4 .bak files in src/ - Remove 7 dead private methods in worker/processor.rs - Remove backup directory from git tracking
This commit is contained in:
@@ -65,10 +65,11 @@ export interface RegisterResponse {
|
||||
export interface UnregisterResponse {
|
||||
success: boolean
|
||||
message: string
|
||||
file_uuid: string
|
||||
deleted_face_detections: number
|
||||
deleted_processor_results: number
|
||||
deleted_chunks: number
|
||||
file_uuid?: string
|
||||
uuid?: string
|
||||
deleted_face_detections?: number
|
||||
deleted_processor_results?: number
|
||||
deleted_chunks?: number
|
||||
}
|
||||
|
||||
// ── Config (browser-only, stored in localStorage) ───────────────────────
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="space-y-6">
|
||||
<!-- Header with Search and Filters -->
|
||||
<div class="flex flex-col md:flex-row items-center justify-between gap-4">
|
||||
<h2 class="text-2xl font-bold">檔案管理</h2>
|
||||
<h2 class="text-2xl font-bold">檔案管理 (Demo)</h2>
|
||||
<div class="flex items-center gap-3 w-full md:w-auto">
|
||||
<!-- Status Filter -->
|
||||
<div class="flex items-center bg-gray-700 rounded p-1">
|
||||
@@ -13,13 +13,6 @@
|
||||
>
|
||||
全部
|
||||
</button>
|
||||
<button
|
||||
@click="setStatusFilter('registered')"
|
||||
:class="{'bg-blue-600 text-white': statusFilter === 'registered', 'text-gray-300 hover:text-white': statusFilter !== 'registered'}"
|
||||
class="px-3 py-1 rounded text-sm transition"
|
||||
>
|
||||
已註冊
|
||||
</button>
|
||||
<button
|
||||
@click="setStatusFilter('unregistered')"
|
||||
:class="{'bg-blue-600 text-white': statusFilter === 'unregistered', 'text-gray-300 hover:text-white': statusFilter !== 'unregistered'}"
|
||||
@@ -27,19 +20,27 @@
|
||||
>
|
||||
未註冊
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Search -->
|
||||
<div class="relative">
|
||||
<input
|
||||
v-model="searchQuery"
|
||||
@input="handleSearch"
|
||||
placeholder="搜尋檔名..."
|
||||
class="pl-10 pr-4 py-2 bg-gray-700 border border-gray-600 rounded text-white focus:outline-none focus:border-blue-500 w-48"
|
||||
/>
|
||||
<svg class="w-5 h-5 absolute left-3 top-2.5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
|
||||
</svg>
|
||||
<button
|
||||
@click="setStatusFilter('pending')"
|
||||
:class="{'bg-blue-600 text-white': statusFilter === 'pending', 'text-gray-300 hover:text-white': statusFilter !== 'pending'}"
|
||||
class="px-3 py-1 rounded text-sm transition"
|
||||
>
|
||||
待處理
|
||||
</button>
|
||||
<button
|
||||
@click="setStatusFilter('processing')"
|
||||
:class="{'bg-blue-600 text-white': statusFilter === 'processing', 'text-gray-300 hover:text-white': statusFilter !== 'processing'}"
|
||||
class="px-3 py-1 rounded text-sm transition"
|
||||
>
|
||||
處理中
|
||||
</button>
|
||||
<button
|
||||
@click="setStatusFilter('completed')"
|
||||
:class="{'bg-blue-600 text-white': statusFilter === 'completed', 'text-gray-300 hover:text-white': statusFilter !== 'completed'}"
|
||||
class="px-3 py-1 rounded text-sm transition"
|
||||
>
|
||||
已完成
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -59,60 +60,68 @@
|
||||
<table class="min-w-full divide-y divide-gray-700">
|
||||
<thead class="bg-gray-900">
|
||||
<tr>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-300 uppercase tracking-wider">檔案路徑</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-300 uppercase tracking-wider">檔案名稱</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-300 uppercase tracking-wider">狀態</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-300 uppercase tracking-wider">UUID</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-300 uppercase tracking-wider">大小</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-300 uppercase tracking-wider">修改時間</th>
|
||||
<th class="px-6 py-3 text-right text-xs font-medium text-gray-300 uppercase tracking-wider">操作</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-300 uppercase tracking-wider">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-700 bg-gray-800">
|
||||
<tr v-for="file in displayFiles" :key="file.file_path" :class="!file.file_name ? 'opacity-0' : 'hover:bg-gray-750 transition'">
|
||||
<tr v-for="file in displayFiles" :key="file.file_uuid || file.file_path" class="hover:bg-gray-750 transition">
|
||||
<td class="px-6 py-4 whitespace-nowrap">
|
||||
<div class="text-sm font-medium text-white truncate max-w-xs" :title="file.file_path">
|
||||
<div class="text-sm font-medium text-white truncate max-w-xs" :title="file.file_name">
|
||||
{{ file.file_name }}
|
||||
</div>
|
||||
<div class="text-xs text-gray-500 truncate max-w-xs">{{ file.relative_path }}</div>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm">
|
||||
<span v-if="file.is_registered" class="px-2 py-0.5 rounded text-xs bg-green-900 text-green-200">
|
||||
已註冊
|
||||
<span v-if="file.status === 'completed'" class="px-2 py-0.5 rounded text-xs bg-green-900 text-green-200">
|
||||
✅ 已完成
|
||||
</span>
|
||||
<span v-else-if="file.status === 'processing'" class="px-2 py-0.5 rounded text-xs bg-yellow-900 text-yellow-200">
|
||||
🔄 處理中
|
||||
</span>
|
||||
<span v-else-if="file.status === 'pending'" class="px-2 py-0.5 rounded text-xs bg-blue-900 text-blue-200">
|
||||
⏳ 待處理
|
||||
</span>
|
||||
<span v-else class="px-2 py-0.5 rounded text-xs bg-gray-600 text-gray-300">
|
||||
未註冊
|
||||
📦 未註冊
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-300 font-mono text-xs">
|
||||
{{ file.file_uuid || '-' }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-300">
|
||||
{{ formatFileSize(file.file_size) }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-300">
|
||||
{{ formatTimestamp(file.modified_time) }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-right">
|
||||
<div class="flex justify-end gap-2">
|
||||
<!-- Detail Button (Registered only) -->
|
||||
<!-- Enter Demo / Workbench (Completed) -->
|
||||
<button
|
||||
v-if="file.is_registered"
|
||||
@click="viewDetail(file.file_uuid)"
|
||||
class="px-3 py-1 bg-blue-600 hover:bg-blue-700 text-white text-xs rounded transition"
|
||||
v-if="file.status === 'completed'"
|
||||
@click="enterWorkbench(file.file_uuid)"
|
||||
class="px-3 py-1 bg-purple-600 hover:bg-purple-700 text-white text-xs rounded transition"
|
||||
>
|
||||
詳情
|
||||
臉部工作台
|
||||
</button>
|
||||
<!-- Register Button (Unregistered only) -->
|
||||
|
||||
<!-- Start Processing (Pending) -->
|
||||
<button
|
||||
v-if="!file.is_registered"
|
||||
v-if="file.status === 'pending'"
|
||||
@click="startProcessing(file.file_uuid)"
|
||||
class="px-3 py-1 bg-yellow-600 hover:bg-yellow-700 text-white text-xs rounded transition"
|
||||
>
|
||||
開始處理
|
||||
</button>
|
||||
|
||||
<!-- Register (Unregistered) -->
|
||||
<button
|
||||
v-if="!file.status || file.status === 'unregistered'"
|
||||
@click="registerFile(file.file_path)"
|
||||
class="px-3 py-1 bg-green-600 hover:bg-green-700 text-white text-xs rounded transition"
|
||||
>
|
||||
立即註冊
|
||||
註冊
|
||||
</button>
|
||||
<!-- Unregister Button (Registered only) -->
|
||||
|
||||
<!-- Unregister (Pending/Processing/Completed) -->
|
||||
<button
|
||||
v-if="file.is_registered"
|
||||
v-if="file.file_uuid"
|
||||
@click="unregisterFile(file.file_uuid, file.file_name)"
|
||||
class="px-3 py-1 bg-red-600 hover:bg-red-700 text-white text-xs rounded transition"
|
||||
>
|
||||
@@ -124,31 +133,20 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Stats -->
|
||||
<div class="flex justify-between text-sm text-gray-400">
|
||||
<span>共 {{ totalCount }} 個檔案</span>
|
||||
<span>已註冊: {{ registeredCount }} | 未註冊: {{ unregisteredCount }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { registerVideo, unregisterVideo } from '@/api/client'
|
||||
import { getCurrentConfig, httpFetch } from '@/api/client'
|
||||
import { registerVideo, unregisterVideo, httpFetch, getCurrentConfig } from '@/api/client'
|
||||
|
||||
const router = useRouter()
|
||||
const files = ref<any[]>([])
|
||||
const loading = ref(false)
|
||||
const error = ref<string | null>(null)
|
||||
const searchQuery = ref('')
|
||||
const statusFilter = ref('all') // all, registered, unregistered
|
||||
|
||||
const totalCount = computed(() => files.value.length)
|
||||
const registeredCount = computed(() => files.value.filter(f => f.is_registered).length)
|
||||
const unregisteredCount = computed(() => files.value.filter(f => !f.is_registered).length)
|
||||
const statusFilter = ref('all') // all, unregistered, pending, processing, completed
|
||||
|
||||
const displayFiles = computed(() => {
|
||||
let result = files.value
|
||||
@@ -163,10 +161,8 @@ const displayFiles = computed(() => {
|
||||
}
|
||||
|
||||
// Filter by status
|
||||
if (statusFilter.value === 'registered') {
|
||||
result = result.filter(f => f.is_registered)
|
||||
} else if (statusFilter.value === 'unregistered') {
|
||||
result = result.filter(f => !f.is_registered)
|
||||
if (statusFilter.value !== 'all') {
|
||||
result = result.filter(f => f.status === statusFilter.value)
|
||||
}
|
||||
|
||||
return result
|
||||
@@ -176,18 +172,26 @@ function setStatusFilter(status: string) {
|
||||
statusFilter.value = status
|
||||
}
|
||||
|
||||
function handleSearch() {
|
||||
// Filter is reactive via computed property
|
||||
}
|
||||
|
||||
async function fetchFiles() {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
try {
|
||||
const config = getCurrentConfig()
|
||||
// Call the new scan endpoint
|
||||
// Call the scan endpoint to get list of files with status
|
||||
// Note: /api/v1/files/scan returns unregistered files.
|
||||
// We might need a combined list or call /api/v1/files for registered ones.
|
||||
// For now, let's assume /api/v1/files/scan returns all files with is_registered flag.
|
||||
const response: any = await httpFetch(`${config.api_base_url}/api/v1/files/scan`)
|
||||
files.value = response.files || []
|
||||
|
||||
// Map scan response to our expected format
|
||||
// Scan returns: { files: [ { file_uuid, file_name, is_registered, status... } ] }
|
||||
files.value = response.files?.map((f: any) => ({
|
||||
...f,
|
||||
status: f.status || (f.is_registered ? 'pending' : 'unregistered')
|
||||
})) || []
|
||||
|
||||
// To get actual processing status, we might need to cross reference with /api/v1/files
|
||||
// But for Demo, let's rely on the scan endpoint providing status.
|
||||
} catch (e) {
|
||||
console.error('Failed to fetch files:', e)
|
||||
error.value = String(e)
|
||||
@@ -199,8 +203,7 @@ async function fetchFiles() {
|
||||
async function registerFile(filePath: string) {
|
||||
try {
|
||||
const result = await registerVideo(filePath)
|
||||
const typeTag = result.file_type ? `[${result.file_type.toUpperCase()}]` : ''
|
||||
alert(`已註冊! ${typeTag} File UUID: ${result.file_uuid}`)
|
||||
// Refresh list
|
||||
await fetchFiles()
|
||||
} catch (e) {
|
||||
console.error('Register failed:', e)
|
||||
@@ -209,13 +212,12 @@ async function registerFile(filePath: string) {
|
||||
}
|
||||
|
||||
async function unregisterFile(fileUuid: string, fileName: string) {
|
||||
if (!confirm(`確定要取消註冊 "${fileName}" 嗎?這將刪除資料庫中的相關記錄,但保留原始檔案。`)) {
|
||||
if (!confirm(`確定要取消註冊 "${fileName}" 嗎?這將刪除資料庫中的相關記錄。`)) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await unregisterVideo(fileUuid)
|
||||
alert('已取消註冊!' + result.message)
|
||||
await unregisterVideo(fileUuid)
|
||||
await fetchFiles()
|
||||
} catch (e) {
|
||||
console.error('Unregister failed:', e)
|
||||
@@ -223,33 +225,29 @@ async function unregisterFile(fileUuid: string, fileName: string) {
|
||||
}
|
||||
}
|
||||
|
||||
function viewDetail(fileUuid: string) {
|
||||
router.push(`/videos/${fileUuid}`)
|
||||
}
|
||||
|
||||
function formatFileSize(bytes: number): string {
|
||||
if (!bytes) return '-'
|
||||
if (bytes < 1024) return bytes + ' B'
|
||||
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB'
|
||||
if (bytes < 1024 * 1024 * 1024) return (bytes / (1024 * 1024)).toFixed(1) + ' MB'
|
||||
return (bytes / (1024 * 1024 * 1024)).toFixed(2) + ' GB'
|
||||
}
|
||||
|
||||
function formatTimestamp(timestamp: string | undefined): string {
|
||||
if (!timestamp) return '-'
|
||||
async function startProcessing(fileUuid: string) {
|
||||
if (!confirm('確定要開始分析處理此檔案嗎?')) return
|
||||
|
||||
try {
|
||||
const date = new Date(timestamp)
|
||||
return date.toLocaleString('zh-TW', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
const config = getCurrentConfig()
|
||||
await httpFetch(`${config.api_base_url}/api/v1/assets/${fileUuid}/process`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({})
|
||||
})
|
||||
} catch {
|
||||
return '-'
|
||||
|
||||
// After triggering, status should change to processing
|
||||
// We can poll or just refresh
|
||||
await fetchFiles()
|
||||
} catch (e) {
|
||||
console.error('Start processing failed:', e)
|
||||
alert('開始處理失敗:' + e)
|
||||
}
|
||||
}
|
||||
|
||||
function enterWorkbench(fileUuid: string) {
|
||||
// Navigate to the new Face Workbench view
|
||||
router.push(`/workbench/${fileUuid}`)
|
||||
}
|
||||
|
||||
onMounted(fetchFiles)
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user