From bcd8960ea970d916855f36e5ecc0671451d9199b Mon Sep 17 00:00:00 2001 From: Aaron Roberts Date: Fri, 3 Jul 2026 10:14:35 +0100 Subject: [PATCH] Show synced to qdrant status on Browse Jobs --- backend/main.py | 3 ++- frontend/src/components/JobsPanel.jsx | 38 +++++++++++++++++++++++++-- 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/backend/main.py b/backend/main.py index c2e6967..c0c612d 100644 --- a/backend/main.py +++ b/backend/main.py @@ -552,7 +552,8 @@ async def list_jobs( cur.execute( f""" SELECT id, author, book, chapter, page, submitted_at, status, - reviewer_name, reviewed_at, mode, ocr_model, original_filename + reviewer_name, reviewed_at, mode, ocr_model, original_filename, + qdrant_synced_at, updated_at FROM ocr_jobs {where} ORDER BY submitted_at DESC LIMIT %s OFFSET %s diff --git a/frontend/src/components/JobsPanel.jsx b/frontend/src/components/JobsPanel.jsx index b1720d7..798d5e8 100644 --- a/frontend/src/components/JobsPanel.jsx +++ b/frontend/src/components/JobsPanel.jsx @@ -4,7 +4,7 @@ import { useModels } from '../hooks/useModels' import { motion, AnimatePresence } from 'framer-motion' import { Search, ChevronLeft, ChevronRight, CheckCircle2, Clock, - FileText, Loader2, Save, RefreshCw, Trash2, Sparkles, + FileText, Loader2, Save, RefreshCw, Trash2, Sparkles, Cloud, CloudOff, } from 'lucide-react' import axios from 'axios' @@ -29,6 +29,38 @@ function StatusBadge({ status }) { ) } +// A job is synced "in its most recent state" when it was pushed to Qdrant at or +// after its last update. `updated_at` is bumped by a DB trigger on every change, +// so any edit after a sync makes the job stale until it's re-synced. +function isSynced(job) { + if (!job.qdrant_synced_at) return false + if (!job.updated_at) return true + return new Date(job.qdrant_synced_at).getTime() >= new Date(job.updated_at).getTime() +} + +function SyncBadge({ job }) { + const synced = isSynced(job) + const Icon = synced ? Cloud : CloudOff + const title = synced + ? 'Synced with Qdrant' + : job.qdrant_synced_at + ? 'Changed since last Qdrant sync — needs re-sync' + : 'Never synced with Qdrant' + return ( + + + {synced ? 'Synced' : 'Not synced'} + + ) +} + // ───────────────────────────────────────────────────────────── // Full-screen Job Detail // ───────────────────────────────────────────────────────────── @@ -210,6 +242,7 @@ function JobDetail({ jobId, onClose, onReviewed, onDeleted, suggestions = {} }) {job && ( <> + -
+
+
{job.book &&

{job.book}

}