From f657582f30111b6d371378be966fb897b293f33a Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Mon, 25 May 2026 17:37:10 -0400 Subject: [PATCH] =?UTF-8?q?feat(import-ui):=20deep=20scan=20button,=20stic?= =?UTF-8?q?ky=20settings=20tabs,=20tasks-above-filters,=20fix=20Scanning-u?= =?UTF-8?q?ndefined=20source=5Fpath=20=E2=80=94=20Co-Authored-By:=20Claude?= =?UTF-8?q?=20Opus=204.7=20(1M=20context)=20?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/api/import_admin.py | 2 + .../settings/ImportTriggerPanel.vue | 48 ++++++++++++------- frontend/src/stores/import.js | 20 +++++--- frontend/src/views/SettingsView.vue | 19 ++++++-- 4 files changed, 63 insertions(+), 26 deletions(-) diff --git a/backend/app/api/import_admin.py b/backend/app/api/import_admin.py index f4ecf07..68ae32a 100644 --- a/backend/app/api/import_admin.py +++ b/backend/app/api/import_admin.py @@ -47,6 +47,8 @@ async def status(): if active: payload["active_batch"] = { "id": active.id, + "source_path": active.source_path, + "scan_mode": active.scan_mode, "total_files": active.total_files, "imported": active.imported, "skipped": active.skipped, diff --git a/frontend/src/components/settings/ImportTriggerPanel.vue b/frontend/src/components/settings/ImportTriggerPanel.vue index 957aaf6..963d456 100644 --- a/frontend/src/components/settings/ImportTriggerPanel.vue +++ b/frontend/src/components/settings/ImportTriggerPanel.vue @@ -7,7 +7,8 @@ indeterminate color="accent" size="20" /> - Scanning {{ store.activeBatch.source_path }} — + {{ store.activeBatch.scan_mode === 'deep' ? 'Deep scanning' : 'Scanning' }} + {{ store.activeBatch.source_path || '/import' }} — imported {{ store.activeBatch.imported }}, skipped {{ store.activeBatch.skipped }}, failed {{ store.activeBatch.failed }} / @@ -24,8 +25,12 @@

- Run a quick scan of the import directory. Deep scan (pHash dedup, - archives) lands in FC-2d. + Quick scan walks /import and enqueues + new files only (skips paths already on a non-failed ImportTask). + Deep scan additionally chains a phash backfill + across the existing library — use after bulk-imports to catch + near-duplicates that slipped through. Both modes route non-media + + sidecar pairs through PostAttachment capture. An active batch is in progress. Wait for it to finish, or click @@ -34,15 +39,26 @@

- - mdi-magnify-scan - Quick scan - +
+ + mdi-magnify-scan + Quick scan + + + mdi-magnify-plus-outline + Deep scan + +
{{ store.triggerError }} @@ -56,12 +72,12 @@ import { ref } from 'vue' import { useImportStore } from '../../stores/import.js' const store = useImportStore() -const busy = ref(false) +const busy = ref(null) const clearing = ref(false) -async function trigger() { - busy.value = true - try { await store.triggerScan() } catch {} finally { busy.value = false } +async function trigger(mode) { + busy.value = mode + try { await store.triggerScan(mode) } catch {} finally { busy.value = null } } async function onClearStuck() { diff --git a/frontend/src/stores/import.js b/frontend/src/stores/import.js index 2358274..6b4dd04 100644 --- a/frontend/src/stores/import.js +++ b/frontend/src/stores/import.js @@ -52,25 +52,31 @@ export const useImportStore = defineStore('import', () => { } } - async function triggerScan() { + async function triggerScan(mode = 'quick') { + if (!['quick', 'deep', 'verify'].includes(mode)) { + throw new Error(`unsupported scan mode: ${mode}`) + } triggerError.value = null try { - await api.post('/api/import/trigger', { body: { mode: 'quick' } }) + await api.post('/api/import/trigger', { body: { mode } }) // Acknowledge immediately so the click isn't invisible. scan_directory // can finalize the batch synchronously when every file in /import is // already on a non-failed ImportTask (operator-flagged 2026-05-25: // 233k existing tasks → all paths in skip-set → files_seen=0 → // batch flashes 'running' for <100ms then 'complete' before the // first refreshStatus() lands; UI never sees the active state). - window.__fcToast?.({ text: 'Scan triggered', type: 'success' }) + const label = mode === 'deep' + ? 'Deep scan triggered (pHash backfill chained)' + : mode === 'verify' ? 'Library verify triggered' : 'Quick scan triggered' + window.__fcToast?.({ text: label, type: 'success' }) await refreshStatus() // Re-poll twice over ~5s to catch quick-finalize transitions and - // surface a result toast either way. + // surface a result toast either way. Skip the "no new files" hint + // for 'verify' since it doesn't walk the import_root. setTimeout(async () => { await refreshStatus() - if (!activeBatch.value) { - // Either scan completed with zero new files, or it never visibly - // started. Fetch the freshest task to differentiate. + await loadTasks(true) + if (!activeBatch.value && mode !== 'verify') { window.__fcToast?.({ text: 'Scan complete — no new files (everything already on an ImportTask row)', type: 'info', diff --git a/frontend/src/views/SettingsView.vue b/frontend/src/views/SettingsView.vue index 8e91c80..00a7180 100644 --- a/frontend/src/views/SettingsView.vue +++ b/frontend/src/views/SettingsView.vue @@ -1,6 +1,16 @@