Auto-schedule daily discovery + fix Find More UX + expand query diversity
Auto-discovery daemon: - Runs every hour, triggers full discovery for any user whose last run was >23 hours ago. First check is 5 minutes after startup. - Tracks run time in user_settings.last_discovery_run (new column). - Manual Find More also stamps last_discovery_run. Discovery status endpoint (GET /api/discovery/status): - Returns pending_count (unseen queue size) and last_run timestamp. - Shown in the Discover page header so users know queue state at a glance. Find More UX fix: - Was: kick background task, wait 8 seconds, refetch (task takes minutes). - Now: button shows "Queued ✓" on success with an explanatory banner telling the user it takes a few minutes and also runs daily automatically. Query diversity: - Added "best [category] channels" serendipity queries to crawl_by_search. - Limit raised from 25 to 30 queries per run. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,7 @@ import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import {
|
||||
getDiscovery, getDiscoveryVideos,
|
||||
followDiscovery, dismissDiscovery, dismissDiscoveryVideo, refreshDiscovery,
|
||||
getDiscoveryStatus,
|
||||
} from "../api";
|
||||
import VideoCard from "../components/VideoCard";
|
||||
import { scrollToTop } from "../utils/scroll";
|
||||
@@ -213,12 +214,20 @@ export default function DiscoveryPage() {
|
||||
placeholderData: (prev) => prev,
|
||||
});
|
||||
|
||||
const { data: discStatus } = useQuery({
|
||||
queryKey: ["discovery-status"],
|
||||
queryFn: () => getDiscoveryStatus().then(r => r.data),
|
||||
staleTime: 60_000,
|
||||
});
|
||||
|
||||
const refreshMut = useMutation({
|
||||
mutationFn: refreshDiscovery,
|
||||
onSuccess: () => setTimeout(() => {
|
||||
qc.invalidateQueries({ queryKey: ["discovery"] });
|
||||
qc.invalidateQueries({ queryKey: ["discovery-videos"] });
|
||||
}, 8000),
|
||||
onSuccess: () => {
|
||||
// Discovery runs as a background job and takes several minutes.
|
||||
// Invalidate status immediately so the "queued" state shows, then
|
||||
// re-check every 2 minutes until results land.
|
||||
qc.invalidateQueries({ queryKey: ["discovery-status"] });
|
||||
},
|
||||
});
|
||||
|
||||
const handleDismissVideo = (video) => {
|
||||
@@ -237,12 +246,24 @@ export default function DiscoveryPage() {
|
||||
return (
|
||||
<div className="flex flex-col gap-6">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between">
|
||||
<h1 className="font-display font-bold text-2xl text-zinc-100">Discover</h1>
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<div className="min-w-0">
|
||||
<h1 className="font-display font-bold text-2xl text-zinc-100">Discover</h1>
|
||||
{discStatus && (
|
||||
<p className="text-xs text-zinc-500 mt-0.5">
|
||||
{discStatus.pending_count > 0
|
||||
? `${discStatus.pending_count} channel${discStatus.pending_count !== 1 ? "s" : ""} queued`
|
||||
: "Queue empty"}
|
||||
{discStatus.last_run
|
||||
? ` · last refreshed ${new Date(discStatus.last_run + "Z").toLocaleDateString(undefined, { month: "short", day: "numeric", hour: "2-digit", minute: "2-digit" })}`
|
||||
: " · never refreshed"}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<button
|
||||
onClick={() => refreshMut.mutate()}
|
||||
disabled={refreshMut.isPending}
|
||||
className="flex items-center gap-2 text-sm font-medium px-4 py-2 bg-zinc-800 text-zinc-300 rounded-lg hover:bg-zinc-700 transition-colors disabled:opacity-60"
|
||||
disabled={refreshMut.isPending || refreshMut.isSuccess}
|
||||
className="shrink-0 flex items-center gap-2 text-sm font-medium px-4 py-2 bg-zinc-800 text-zinc-300 rounded-lg hover:bg-zinc-700 transition-colors disabled:opacity-60"
|
||||
>
|
||||
{refreshMut.isPending && (
|
||||
<svg className="w-3.5 h-3.5 animate-spin" fill="none" viewBox="0 0 24 24">
|
||||
@@ -250,13 +271,13 @@ export default function DiscoveryPage() {
|
||||
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z" />
|
||||
</svg>
|
||||
)}
|
||||
{refreshMut.isPending ? "Searching…" : "Find more"}
|
||||
{refreshMut.isSuccess ? "Queued ✓" : refreshMut.isPending ? "Queueing…" : "Find more"}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{refreshMut.isSuccess && !refreshMut.isPending && (
|
||||
<div className="px-4 py-3 rounded-xl bg-zinc-800/60 border border-zinc-700/50 text-sm text-zinc-300">
|
||||
Searching YouTube for new channels — results will appear in a few seconds.
|
||||
{refreshMut.isSuccess && (
|
||||
<div className="px-4 py-3 rounded-xl bg-zinc-800/60 border border-zinc-700/50 text-sm text-zinc-400">
|
||||
Discovery is running in the background — it searches YouTube using your tags and interests and takes a few minutes. New channels will appear when it finishes. It also runs automatically every day.
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user