diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts index 767d34f..6b3da3f 100644 --- a/frontend/src/api/client.ts +++ b/frontend/src/api/client.ts @@ -40,6 +40,11 @@ async function request(path: string, init?: RequestInit): Promise { export const api = { health: () => request<{ status: string; queue_depth: number }>('/healthz'), + recompute: (body?: Record) => + request<{ status: string; depth: number }>('/recompute', { + method: 'POST', + body: JSON.stringify(body ?? {}), + }), networth: { current: () => request<{ diff --git a/frontend/src/pages/Scenarios.tsx b/frontend/src/pages/Scenarios.tsx index 83eb978..43cb4d3 100644 --- a/frontend/src/pages/Scenarios.tsx +++ b/frontend/src/pages/Scenarios.tsx @@ -5,7 +5,7 @@ * The Cartesian set is whatever the latest /recompute produced (default * 120 scenarios). User scenarios survive recomputes. */ -import { useQuery } from '@tanstack/react-query'; +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { Link, useNavigate } from 'react-router-dom'; import { useState } from 'react'; @@ -18,12 +18,35 @@ export function Scenarios() { const [filter, setFilter] = useState('all'); const [selected, setSelected] = useState>(new Set()); const navigate = useNavigate(); + const qc = useQueryClient(); const scenarios = useQuery({ queryKey: ['scenarios', filter], queryFn: () => api.scenarios.list(filter === 'all' ? undefined : filter), }); + // Poll healthz once a recompute is in flight so the user sees the + // queue empty in roughly real time. + const health = useQuery({ + queryKey: ['health'], + queryFn: api.health, + refetchInterval: (q) => { + const data = q.state.data as { queue_depth: number } | undefined; + return data && data.queue_depth > 0 ? 3000 : false; + }, + }); + + const recompute = useMutation({ + mutationFn: () => api.recompute(), + onSuccess: () => { + qc.invalidateQueries({ queryKey: ['health'] }); + qc.invalidateQueries({ queryKey: ['scenarios'] }); + }, + }); + + const queueDepth = health.data?.queue_depth ?? 0; + const isComputing = queueDepth > 0; + const toggle = (id: number) => setSelected((prev) => { const next = new Set(prev); @@ -57,6 +80,19 @@ export function Scenarios() { Compare {selected.size} )} + + {recompute.isError && ( +
+ {String((recompute.error as Error)?.message ?? recompute.error)} +
+ )} {scenarios.isLoading ? (

Loading…