import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { toast } from "sonner"; import { apiRequest } from "./client"; export type OpsService = { name: string; status: string; restarts: number; uptime_ms: number; cpu: number; memory_bytes: number; cron: string; autorestart: boolean; disabled?: boolean; // cron drain switched off via the dashboard }; export type CourtFetchJob = { case_number_norm: string; citation_raw: string; tier: string; status: string; error: string; updated_at: string; }; export type IngestedRow = { case_number: string; court: string; source_url: string; created_at: string; }; /** The uniform per-pipeline shape every background drain reports. */ export type PipelineStats = { pending: number; // backlog: rows not yet processed (status default) processing: number; // being worked right now done: number; // completed failed: number; // terminal failures (court_fetch folds in 'manual') queued: number; // explicitly enqueued for the next drain run running_now: string[]; // human labels of the items currently processing by_status: Record; // raw counts, for the curious }; export type OperationsSnapshot = { services: OpsService[]; services_error: string | null; pipelines: { court_fetch: PipelineStats & { recent: CourtFetchJob[] }; metadata_extraction: PipelineStats; halacha_extraction: PipelineStats; digests: PipelineStats & { total: number; linked: number }; halacha_review: { by_status: Record }; missing_precedents: { by_status: Record }; ingested_recent: IngestedRow[]; }; }; export function useOperations() { return useQuery({ queryKey: ["operations"], queryFn: ({ signal }) => apiRequest("/api/operations", { signal }), refetchInterval: 5000, // live view of background work staleTime: 3000, }); } export type ServiceAction = "restart" | "stop" | "start" | "run-now"; /** Control a background service (daemon restart/stop/start, or run a drain now). */ export function useServiceAction() { const qc = useQueryClient(); return useMutation({ mutationFn: ({ name, action }: { name: string; action: ServiceAction }) => apiRequest(`/api/operations/services/${name}/${action}`, { method: "POST" }), onSuccess: (_d, { action }) => { const labels: Record = { "run-now": "הופעל עכשיו", restart: "הופעל מחדש", stop: "נעצר", start: "הופעל", }; toast.success(labels[action]); qc.invalidateQueries({ queryKey: ["operations"] }); }, onError: (e) => toast.error(`הפעולה נכשלה: ${String(e)}`), }); } /** Switch a cron drain on/off (its "startup type"). */ export function useDrainToggle() { const qc = useQueryClient(); return useMutation({ mutationFn: ({ name, disabled }: { name: string; disabled: boolean }) => apiRequest(`/api/operations/drains/${name}/disabled`, { method: "POST", body: { disabled }, }), onSuccess: (_d, { disabled }) => { toast.success(disabled ? "התזמון כובה" : "התזמון הופעל"); qc.invalidateQueries({ queryKey: ["operations"] }); }, onError: (e) => toast.error(`העדכון נכשל: ${String(e)}`), }); } // ── Live agents — which agent is working now + its output + controls ─────── export type AgentRun = { run_id: string; agent_id: string; agent_name: string; company_id: string; company_label: string; status: string; // running | queued | ... invocation_source: string; trigger_detail: string; issue_id: string | null; adapter_type: string; started_at: string | null; created_at: string | null; last_output_at: string | null; continuation_attempt: number; silence_level: string; // "" | ok | suspicion | critical silence_age_ms: number; }; export type AgentRunsResponse = { runs: AgentRun[]; running: number; queued: number; errors: string[]; }; export type RunLog = { runId: string; store: string; logRef: string; content: string; // NDJSON stream the adapter captured }; /** Queued + running heartbeat runs across all companies. */ export function useAgentRuns() { return useQuery({ queryKey: ["operations", "agents"], queryFn: ({ signal }) => apiRequest("/api/operations/agents", { signal }), refetchInterval: 4000, // live view of who's working now staleTime: 2000, }); } /** Full output log of one run — fetched on demand (drawer open). */ export function useRunLog(runId: string | null) { return useQuery({ queryKey: ["operations", "agents", "log", runId], queryFn: ({ signal }) => apiRequest(`/api/operations/agents/runs/${runId}/log`, { signal }), enabled: !!runId, refetchInterval: runId ? 4000 : false, // live tail while open }); } /** Gracefully cancel a queued/running run (not a raw kill). */ export function useCancelRun() { const qc = useQueryClient(); return useMutation({ mutationFn: (runId: string) => apiRequest(`/api/operations/agents/runs/${runId}/cancel`, { method: "POST" }), onSuccess: () => { toast.success("בקשת עצירה נשלחה"); qc.invalidateQueries({ queryKey: ["operations", "agents"] }); }, onError: (e) => toast.error(`העצירה נכשלה: ${String(e)}`), }); } /** Reset a wedged agent session so its next wakeup starts clean. */ export function useResetAgentSession() { const qc = useQueryClient(); return useMutation({ mutationFn: (agentId: string) => apiRequest(`/api/operations/agents/${agentId}/reset-session`, { method: "POST" }), onSuccess: () => { toast.success("ה-session אופס"); qc.invalidateQueries({ queryKey: ["operations", "agents"] }); }, onError: (e) => toast.error(`האיפוס נכשל: ${String(e)}`), }); }