Files
legal-ai/web-ui/src/lib/api/generate.ts
Chaim 3ea5d3b536
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 4s
Lint — undefined names / undefined-names (pull_request) Successful in 11s
feat(case-ui): כרטיס "הפקת מסמכים" עם 3 כפתורי-הפק דטרמיניסטיים (#214)
מוסיף כרטיס "הפקת מסמכים" בראש טאב "טיוטות והערות" (mockup 26 — תוספת ל-18h),
תוספת לעמוד הקיים, כל הסקשנים נשמרים.

- כפתור 1 "הפק סיכום מנהלים" → endpoint חדש POST /generate/party-claims-summary
  → CEO wakeup סטרוקטורלי action="party_claims_summary" (שלב H2, host-side).
  Polling: GET /research/party-claims-summary (200=מוכן → פתח/DOCX).
- כפתור 2 "הפק טיוטה של טענות הצדדים" → endpoint חדש POST /generate/interim-draft
  → CEO wakeup action="interim_draft" (שלב H, host-side).
  Polling: רשימת ה-exports עד שמופיע טיוטת-ביניים-{case}-vN.docx.
- כפתור 3 "הפק טיוטת החלטה מלאה" → ה-useExportDocx הקיים (in-container, ללא LLM);
  הועבר מהבאנר (כפתור "הפק DOCX" הוסר משם — אין כפילות). disabled+title כשאין
  בלוקים כתובים (isDraftReady).

מסלול-עירור יחיד דרך ה-Platform Port (pc_wake_ceo_for_action) → API helper
/api/agents/{id}/wakeup, לעולם לא DB insert. Israel-time + cache-invalidation.

Invariants: G12 (מגע-Paperclip רק ב-paperclip_client + agent_platform_port;
leak-guard ירוק) · G2 (אין מסלול-עירור מקביל — שימוש חוזר ב-pc_request הקיים) ·
G10 (הפקה מגודרת-יו"ר/CEO) · INV-UI9 (Asia/Jerusalem) · X6 (UI↔API contract).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 19:54:54 +00:00

93 lines
3.4 KiB
TypeScript

/**
* 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}-vN.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;
};
export const generateKeys = {
all: ["generate"] as const,
partyClaimsSummary: (caseNumber: string) =>
[...generateKeys.all, "party-claims-summary", caseNumber] as const,
};
/* ── 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<PartyClaimsSummary>({
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<GenerateTriggerResult>(
`/api/cases/${caseNumber}/generate/party-claims-summary`,
{ method: "POST" },
),
onSuccess: () => {
// Start polling fresh — the file will appear after the host run completes.
qc.invalidateQueries({
queryKey: generateKeys.partyClaimsSummary(caseNumber),
});
},
});
}
/* ── Trigger: interim ("party-claims") partial draft (button 2) ── */
export function useGenerateInterimDraft(caseNumber: string) {
return useMutation({
mutationFn: () =>
apiRequest<GenerateTriggerResult>(
`/api/cases/${caseNumber}/generate/interim-draft`,
{ method: "POST" },
),
// The result lands in the exports list (טיוטת-ביניים-*.docx); useExports
// already polls on a 5s interval, so no extra invalidation is needed here.
});
}