Follow a running production every few seconds
A started production kept showing "AI picks new topics" for a minute or more: each schedule's status was read once a minute, and the refresh right after starting got the read kept from before the start. That read is now dropped when a production starts, and while the latest production is under way its status is read every five seconds. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -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.
|
// 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 PRODUCTION_FRESH = 30_000
|
||||||
const productionReads = new Map<number, { at: number; read: Promise<ProductionStatus> }>()
|
const productionReads = new Map<number, { at: number; read: Promise<ProductionStatus> }>()
|
||||||
export function productionStatus(id: number): Promise<ProductionStatus> {
|
export function productionStatus(id: number, maxAge = PRODUCTION_FRESH): Promise<ProductionStatus> {
|
||||||
const kept = productionReads.get(id)
|
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<ProductionStatus>(`${AGENTS}/${id}/content`)
|
const read = request<ProductionStatus>(`${AGENTS}/${id}/content`)
|
||||||
productionReads.set(id, { at: Date.now(), read })
|
productionReads.set(id, { at: Date.now(), read })
|
||||||
read.catch(() => { if (productionReads.get(id)?.read === read) productionReads.delete(id) })
|
read.catch(() => { if (productionReads.get(id)?.read === read) productionReads.delete(id) })
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import { toast } from '../components/Toast'
|
|||||||
import { describeRule } from '../express/parse'
|
import { describeRule } from '../express/parse'
|
||||||
import { locale, t, type Key } from '../i18n'
|
import { locale, t, type Key } from '../i18n'
|
||||||
import {
|
import {
|
||||||
archiveSchedule, confirmProduction, isMine, isRunSchedule, knowledgeReadiness, listRuns, listSchedules, scheduleOwners, pauseSchedule, PLAN_ACTIVE, productionStatus, quoteProduction, resumeSchedule,
|
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 RunSummary, type Schedule,
|
planProgress, productionError, runNow as startRun, updateTopic, type ContentPlan, type Owners, type ProductionStatus, type RunSummary, type Schedule,
|
||||||
} from '../express/schedules'
|
} from '../express/schedules'
|
||||||
|
|
||||||
const STUDIO_URL: string = import.meta.env.VITE_STUDIO_URL
|
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
|
// 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.
|
// asks for its own, so it is read once a minute; while its latest production is under way it is
|
||||||
const useProduction = (agentId: number) => useQuery({
|
// read every few seconds, so each step shows as it happens. Starting a production refreshes it at once.
|
||||||
queryKey: ['production', agentId],
|
const LIVE_POLL = 5000
|
||||||
queryFn: () => productionStatus(agentId),
|
const isLive = (s: ProductionStatus | undefined) => !!s?.plans[0] && PLAN_ACTIVE.has(s.plans[0].status)
|
||||||
refetchInterval: 60_000,
|
function useProduction(agentId: number) {
|
||||||
staleTime: 60_000,
|
const queryClient = useQueryClient()
|
||||||
|
const key = ['production', agentId]
|
||||||
|
return useQuery({
|
||||||
|
queryKey: key,
|
||||||
|
queryFn: () => productionStatus(agentId, isLive(queryClient.getQueryData<ProductionStatus>(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 }) {
|
function ProductionLine({ agentId, active }: { agentId: number; active: boolean }) {
|
||||||
const status = useProduction(agentId)
|
const status = useProduction(agentId)
|
||||||
@@ -218,6 +225,8 @@ function ProducePanel({ schedule: { agent, rule }, onClose }: { schedule: Schedu
|
|||||||
const start = useMutation({
|
const start = useMutation({
|
||||||
mutationFn: (plan: ContentPlan) => confirmProduction(agent.id, plan),
|
mutationFn: (plan: ContentPlan) => confirmProduction(agent.id, plan),
|
||||||
onSuccess: () => {
|
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: ['production', agent.id] })
|
||||||
queryClient.invalidateQueries({ queryKey: ['results'] })
|
queryClient.invalidateQueries({ queryKey: ['results'] })
|
||||||
onClose()
|
onClose()
|
||||||
|
|||||||
Reference in New Issue
Block a user