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/backend/app/services/ml/embedder.py b/backend/app/services/ml/embedder.py index 49c40f4..5da36c5 100644 --- a/backend/app/services/ml/embedder.py +++ b/backend/app/services/ml/embedder.py @@ -34,10 +34,21 @@ class Embedder: if self._model is not None: return import torch - from transformers import AutoModel, AutoProcessor + from transformers import AutoModel, SiglipImageProcessor self._torch = torch - self._processor = AutoProcessor.from_pretrained(str(self._model_dir)) + # FC's embedder only does IMAGE inference — never text. AutoProcessor + # loads the full processor including SiglipTokenizer, which requires + # the sentencepiece library at import time even if we never call it. + # SiglipImageProcessor loads ONLY preprocessor_config.json (image + # side) and skips the tokenizer config entirely. Operator hit the + # ImportError 2026-05-25 once the ml-worker started actually running + # tag_and_embed; switching to the image-only loader avoids the + # tokenizer dep without adding ~30 MB of unused C++ build to the + # lean ml-worker image. + self._processor = SiglipImageProcessor.from_pretrained( + str(self._model_dir) + ) self._model = AutoModel.from_pretrained(str(self._model_dir)) self._model.eval() diff --git a/extension/manifest.json b/extension/manifest.json index 92b18d9..bf740eb 100644 --- a/extension/manifest.json +++ b/extension/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "FabledCurator", - "version": "1.0.0", + "version": "1.0.1", "description": "Export cookies from supported platforms to FabledCurator and add creators as sources in one click.", "browser_specific_settings": { diff --git a/extension/package.json b/extension/package.json index 1a0bb06..181bb0f 100644 --- a/extension/package.json +++ b/extension/package.json @@ -1,6 +1,6 @@ { "name": "fabledcurator-extension", - "version": "1.0.0", + "version": "1.0.1", "private": true, "description": "Firefox extension for FabledCurator", "scripts": { 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 @@