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>
This commit is contained in:
92
web-ui/src/lib/api/generate.ts
Normal file
92
web-ui/src/lib/api/generate.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* 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.
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user