/** * Document-generation domain hooks (TaskMaster #214). * * The "הפקת מסמכים" card in the case "טיוטות והערות" tab fires two deterministic, * host-side generation actions through the CEO (structured-action wakeup): * • party_claims_summary → an executive summary of the parties' claims * • interim_draft → a partial draft (blocks ה–ט, up to the discussion) * * Generation runs on the host (claude_session → claude -p), NOT in the container, * so the trigger endpoints return 202-accepted and the UI POLLS for completion: * • summary → GET /api/cases/{n}/research/party-claims-summary (200 = ready) * • interim draft → the exports list grows a "טיוטה-טענות_הצדדים_{N}.docx" file * (polled via useExports — see exports.ts). * * The full-decision export ("הפק טיוטת החלטה מלאה") is NOT here — it reuses the * existing in-container useExportDocx (exports.ts), no LLM, no wakeup. */ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { ApiError, apiRequest } from "./client"; /** Shape returned by the structured-action wakeup endpoints (#214). */ export type GenerateTriggerResult = { status: "ok" | "skipped" | "error"; action?: string; issue_id?: string; ceo_id?: string; reason?: string; error?: string; }; /** The two host-side background generation actions (poll target of the status chip). */ export type GenerateAction = "party_claims_summary" | "interim_draft"; export const generateKeys = { all: ["generate"] as const, partyClaimsSummary: (caseNumber: string) => [...generateKeys.all, "party-claims-summary", caseNumber] as const, status: (caseNumber: string, action: GenerateAction) => [...generateKeys.all, "status", action, caseNumber] as const, }; /** * Server-derived background-run status (#227 ג). Reconstructed on the backend * from the CEO child issue + its heartbeat_run + the output artifact — so it is * correct after navigating away and back; it never depends on the browser having * stayed on the page. `started_at`/`finished_at` are server ISO-8601 UTC stamps, * so the client renders a correct elapsed time on return. */ export type GenerateState = "idle" | "queued" | "running" | "done" | "failed"; export type GenerateStatus = { case_number: string; action: GenerateAction; state: GenerateState; started_at: string | null; finished_at: string | null; output_ready: boolean; detail: string; issue_id: string | null; }; export function useGenerateStatus( caseNumber: string | undefined, action: GenerateAction, ) { return useQuery({ queryKey: generateKeys.status(caseNumber ?? "", action), queryFn: ({ signal }) => apiRequest( `/api/cases/${caseNumber}/generate/${action}/status`, { signal }, ), enabled: Boolean(caseNumber), // Poll only while a run is in flight; a terminal state stops the interval. refetchInterval: (query) => { const s = query.state.data?.state; return s === "running" || s === "queued" ? 5_000 : false; }, staleTime: 2_000, }); } /* ── Read: party-claims executive summary (poll target for button 1) ── 404 (not generated yet) is a normal "not ready" state, not an error — we coerce it to null so the panel renders the empty/idle state cleanly. */ export type PartyClaimsSummary = { markdown: string } | null; export function usePartyClaimsSummary(caseNumber: string | undefined) { return useQuery({ queryKey: generateKeys.partyClaimsSummary(caseNumber ?? ""), queryFn: async ({ signal }) => { try { return await apiRequest<{ markdown: string }>( `/api/cases/${caseNumber}/research/party-claims-summary`, { signal }, ); } catch (e) { if (e instanceof ApiError && e.status === 404) return null; throw e; } }, enabled: Boolean(caseNumber), staleTime: 5_000, retry: false, }); } /* ── Trigger: party-claims executive summary (button 1) ── */ export function useGeneratePartyClaimsSummary(caseNumber: string) { const qc = useQueryClient(); return useMutation({ mutationFn: () => apiRequest( `/api/cases/${caseNumber}/generate/party-claims-summary`, { method: "POST" }, ), onSuccess: () => { // Start polling fresh — the file will appear after the host run completes, // and the status chip should pick up the new queued/running run. qc.invalidateQueries({ queryKey: generateKeys.partyClaimsSummary(caseNumber), }); qc.invalidateQueries({ queryKey: generateKeys.status(caseNumber, "party_claims_summary"), }); }, }); } /* ── Trigger: interim ("party-claims") partial draft (button 2) ── */ export function useGenerateInterimDraft(caseNumber: string) { const qc = useQueryClient(); return useMutation({ mutationFn: () => apiRequest( `/api/cases/${caseNumber}/generate/interim-draft`, { method: "POST" }, ), // The result lands in the exports list (טיוטה-טענות_הצדדים_*.docx, polled by // useExports); re-arm the status chip so it shows the new run immediately. onSuccess: () => { qc.invalidateQueries({ queryKey: generateKeys.status(caseNumber, "interim_draft"), }); }, }); }