diff --git a/src/express/schedules.ts b/src/express/schedules.ts index fe12470..6ca24eb 100644 --- a/src/express/schedules.ts +++ b/src/express/schedules.ts @@ -397,9 +397,9 @@ export function planProgress(plan: ContentPlan) { // from the last half minute, or one still on its way, answers both. forgetReads drops them after an action. const PRODUCTION_FRESH = 30_000 const productionReads = new Map }>() -export function productionStatus(id: number): Promise { +export function productionStatus(id: number, maxAge = PRODUCTION_FRESH): Promise { const kept = productionReads.get(id) - if (kept && Date.now() - kept.at < PRODUCTION_FRESH) return kept.read + if (kept && Date.now() - kept.at < maxAge) return kept.read const read = request(`${AGENTS}/${id}/content`) productionReads.set(id, { at: Date.now(), read }) read.catch(() => { if (productionReads.get(id)?.read === read) productionReads.delete(id) }) diff --git a/src/pages/Schedule.tsx b/src/pages/Schedule.tsx index b4e1e23..a8384b2 100644 --- a/src/pages/Schedule.tsx +++ b/src/pages/Schedule.tsx @@ -7,8 +7,8 @@ import { toast } from '../components/Toast' import { describeRule } from '../express/parse' import { locale, t, type Key } from '../i18n' import { - archiveSchedule, confirmProduction, isMine, isRunSchedule, knowledgeReadiness, listRuns, listSchedules, scheduleOwners, pauseSchedule, PLAN_ACTIVE, productionStatus, quoteProduction, resumeSchedule, - planProgress, productionError, runNow as startRun, updateTopic, type ContentPlan, type Owners, type RunSummary, type Schedule, + archiveSchedule, confirmProduction, forgetProductionReads, isMine, isRunSchedule, knowledgeReadiness, listRuns, listSchedules, scheduleOwners, pauseSchedule, PLAN_ACTIVE, productionStatus, quoteProduction, resumeSchedule, + planProgress, productionError, runNow as startRun, updateTopic, type ContentPlan, type Owners, type ProductionStatus, type RunSummary, type Schedule, } from '../express/schedules' const STUDIO_URL: string = import.meta.env.VITE_STUDIO_URL @@ -152,13 +152,20 @@ function ScheduleRow({ schedule, owners }: { schedule: Schedule; owners?: Owners } // Content schedules report this week's progress from the engine's production period. Each row -// asks for its own, so it is read once a minute; starting a production refreshes it at once. -const useProduction = (agentId: number) => useQuery({ - queryKey: ['production', agentId], - queryFn: () => productionStatus(agentId), - refetchInterval: 60_000, - staleTime: 60_000, -}) +// asks for its own, so it is read once a minute; while its latest production is under way it is +// read every few seconds, so each step shows as it happens. Starting a production refreshes it at once. +const LIVE_POLL = 5000 +const isLive = (s: ProductionStatus | undefined) => !!s?.plans[0] && PLAN_ACTIVE.has(s.plans[0].status) +function useProduction(agentId: number) { + const queryClient = useQueryClient() + const key = ['production', agentId] + return useQuery({ + queryKey: key, + queryFn: () => productionStatus(agentId, isLive(queryClient.getQueryData(key)) ? LIVE_POLL : undefined), + refetchInterval: q => (isLive(q.state.data) ? LIVE_POLL : 60_000), + staleTime: q => (isLive(q.state.data) ? LIVE_POLL : 60_000), + }) +} function ProductionLine({ agentId, active }: { agentId: number; active: boolean }) { const status = useProduction(agentId) @@ -218,6 +225,8 @@ function ProducePanel({ schedule: { agent, rule }, onClose }: { schedule: Schedu const start = useMutation({ mutationFn: (plan: ContentPlan) => confirmProduction(agent.id, plan), onSuccess: () => { + // The read kept from before the start still shows no production; it must not answer the refresh. + forgetProductionReads() queryClient.invalidateQueries({ queryKey: ['production', agent.id] }) queryClient.invalidateQueries({ queryKey: ['results'] }) onClose()