diff --git a/src/views/LibraryView.vue b/src/views/LibraryView.vue index 4cc9b90..2e90d2d 100644 --- a/src/views/LibraryView.vue +++ b/src/views/LibraryView.vue @@ -99,6 +99,9 @@
✅ {{ t('library.status.completed') }} ({{ completedFiles.length }}) + @@ -145,6 +148,9 @@
📋 {{ t('library.status.registered') }} ({{ registeredFiles.length }}) + @@ -1069,6 +1075,7 @@ async function playVideo(f: any) { const registering = ref(false) const processing = ref(false) const unregistering = ref(false) +const batchProcessing = ref(false) const reprocessing = ref(false) const confirmDialog = ref(false) const confirmTarget = ref<{ mode: 'register' | 'process' | 'reprocess'; file: any } | null>(null) @@ -1871,6 +1878,84 @@ async function batchRegister() { setTimeout(() => { actionMsg.value = '' }, 8000) } +async function batchDeepScan() { + if (batchProcessing.value) return + if (selectedRegisteredFiles.value.length === 0) return + + const filesToProcess = selectedRegisteredFiles.value + + if (!confirm(`Deep scan ${filesToProcess.length} files?\n\nThis will process all selected files with default settings.`)) return + + batchProcessing.value = true + actionMsg.value = `Starting deep scan for ${filesToProcess.length} files...` + + const maxConcurrent = 3 + const limit = pLimit(maxConcurrent) + + let successCount = 0 + let failCount = 0 + let queueCount = 0 + + const tasks = filesToProcess.map((f, index) => { + return limit(async () => { + const fileUuid = f.file_uuid + if (!fileUuid) { + failCount++ + return + } + + const position = index + 1 + if (position > maxConcurrent) { + queueCount++ + actionMsg.value = `Processing ${successCount + 1}/${filesToProcess.length} (${queueCount} in queue)...` + } else { + actionMsg.value = `Processing ${position}/${filesToProcess.length}...` + } + + try { + const file = filesCache.value.find((x: any) => x.file_uuid === fileUuid) + if (!file) { + failCount++ + return + } + + const result: any = await apiCall('re_scan_file', { + fileUuid: fileUuid, + autoPipeline: true + }) + + if (result.success) { + successCount++ + if (file) { + file.status = 'scanning' + fileStatusCache.value[fileUuid] = 'scanning' + await syncFileStatus(fileUuid) + } + } else { + failCount++ + } + } catch (e: any) { + failCount++ + console.error('[batchDeepScan] Failed:', f.file_name, e) + } + }) + }) + + await Promise.all(tasks) + + selectedFiles.value = [] + batchProcessing.value = false + + if (successCount > 0) { + actionMsg.value = `Deep scan started for ${successCount} files${failCount > 0 ? `, ${failCount} failed` : ''}` + } else { + actionMsg.value = `Failed to start deep scan for all ${failCount} files` + } + + await loadFiles() + setTimeout(() => { actionMsg.value = '' }, 8000) +} + async function batchUnregister() { if (unregistering.value) return if (selectedRegisteredFiles.value.length === 0) return @@ -2248,7 +2333,9 @@ function resetFilters() { .ms-fm-section-title { font-size: 13px; font-weight: 600; color: var(--text-secondary); margin-bottom: 12px; padding-left: 4px; display: flex; align-items: center; gap: 12px; } .ms-batch-register-btn { margin-left: auto; padding: 4px 12px; font-size: 12px; font-weight: 600; background: var(--primary-color); color: white; border: none; border-radius: 6px; cursor: pointer; } .ms-batch-register-btn:hover { background: var(--primary-hover); } -.ms-batch-unregister-btn { margin-left: auto; padding: 4px 12px; font-size: 12px; font-weight: 600; background: var(--danger-color, #dc3545); color: white; border: none; border-radius: 6px; cursor: pointer; } +.ms-batch-scan-btn { margin-left: 8px; padding: 4px 12px; font-size: 12px; font-weight: 600; background: var(--info-color, #17a2b8); color: white; border: none; border-radius: 6px; cursor: pointer; } +.ms-batch-scan-btn:hover { background: var(--info-hover, #138496); } +.ms-batch-unregister-btn { margin-left: 8px; padding: 4px 12px; font-size: 12px; font-weight: 600; background: var(--danger-color, #dc3545); color: white; border: none; border-radius: 6px; cursor: pointer; } .ms-batch-unregister-btn:hover { background: var(--danger-hover, #c82333); } .ms-fm-card-checkbox { position: absolute; bottom: 8px; right: 8px; width: 24px; height: 24px; border: 2px solid var(--border-color); border-radius: 4px; background: var(--card-background); cursor: pointer; display: flex; align-items: center; justify-content: center; font-size: 14px; color: var(--primary-color); z-index: 10; } .ms-fm-card-checkbox:hover { border-color: var(--primary-color); }