fix(precedents): Anthropic SDK fallback, format() crash, UI refresh
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 1m31s

Three fixes to the precedent library after the first end-to-end test on
403-17 surfaced runtime issues:

1. Anthropic SDK fallback in claude_session. The legal-ai Docker container
   does not ship the `claude` CLI, so every halacha and metadata extraction
   was failing with "Claude CLI not found." Module now tries the CLI first
   (zero-cost local path) and falls back to the Anthropic SDK with
   ANTHROPIC_API_KEY when the binary is absent. Default model is
   claude-sonnet-4-6, overridable via CLAUDE_SDK_MODEL env. The system
   message gets cache_control: ephemeral so multi-chunk runs reuse the
   cached instruction prefix at ~10% read cost. Adds `anthropic` to
   pyproject deps.

2. precedent_metadata_extractor crashed with KeyError because the JSON
   example inside the prompt template contained literal { } characters
   that str.format() interpreted as placeholders. Switched to f-string
   concatenation; the prompt template no longer needs format() at all.

3. Library list query stays stale after upload because the upload
   mutation's onSuccess fires when the POST returns task_id, not when
   SSE reports completion. Added a second invalidate inside the SSE
   watcher in PrecedentUploadSheet so the new row appears with up-to-date
   chunk and halachot counts the moment processing finishes.

Halacha and metadata extractors now route the long static prompt through
the new `system=` parameter so the SDK path actually caches it; the CLI
path concatenates and behaves as before.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-03 10:52:31 +00:00
parent 73a79ea7e8
commit 5d836ca414
5 changed files with 198 additions and 55 deletions

View File

@@ -3,6 +3,7 @@
import { useEffect, useState } from "react";
import { Upload, Loader2, CheckCircle2, AlertCircle } from "lucide-react";
import { toast } from "sonner";
import { useQueryClient } from "@tanstack/react-query";
import {
Sheet, SheetContent, SheetHeader, SheetTitle, SheetDescription,
} from "@/components/ui/sheet";
@@ -14,7 +15,10 @@ import {
Select, SelectContent, SelectItem, SelectTrigger, SelectValue,
} from "@/components/ui/select";
import { Progress } from "@/components/ui/progress";
import { useUploadPrecedent, type PracticeArea, type SourceType } from "@/lib/api/precedent-library";
import {
useUploadPrecedent, libraryKeys,
type PracticeArea, type SourceType,
} from "@/lib/api/precedent-library";
import { useProgress } from "@/lib/api/documents";
import {
PRACTICE_AREAS, PRECEDENT_LEVELS, SOURCE_TYPES,
@@ -44,6 +48,7 @@ export function PrecedentUploadSheet({ open, onOpenChange }: Props) {
const [taskId, setTaskId] = useState<string | null>(null);
const upload = useUploadPrecedent();
const progress = useProgress(taskId);
const qc = useQueryClient();
// Reset form when the sheet closes — fields, file input, and any in-flight
// task subscription. We accept the cascade-render warning because resetting
@@ -60,17 +65,23 @@ export function PrecedentUploadSheet({ open, onOpenChange }: Props) {
setHeadnote(""); setIsBinding(true); setTaskId(null);
}, [open]);
// Auto-close on completion
// Auto-close on completion + refresh library list/stats so the new
// row appears with up-to-date counts (halachot, approved). The mutation's
// onSuccess fires when POST returns the task_id; we need a second
// invalidation when SSE reports terminal status, otherwise the table
// shows stale data.
useEffect(() => {
if (progress?.status === "completed") {
qc.invalidateQueries({ queryKey: libraryKeys.all });
toast.success("הפסיקה הוכנסה לקורפוס. ההלכות ממתינות לאישור.");
const t = window.setTimeout(() => onOpenChange(false), 1200);
return () => window.clearTimeout(t);
}
if (progress?.status === "failed") {
qc.invalidateQueries({ queryKey: libraryKeys.all });
toast.error(`כשל בעיבוד: ${progress.error || "שגיאה לא ידועה"}`);
}
}, [progress, onOpenChange]);
}, [progress, onOpenChange, qc]);
const onSubmit = async (e: React.FormEvent) => {
e.preventDefault();