Files
legal-ai/web-ui/src/lib/api/feedback.ts
Chaim 4174217179 feat(feedback): סימון "יושם" מפעיל CEO לקיפול הלקח לקובץ הנכון
סוגר את לולאת פידבק-יו"ר→ידע-סוכנים. עד כה resolve רק עדכן את ה-DB; עכשיו
לחיצה ב-/feedback מעירה את ה-CEO שמקפל את הלקח לקובץ לפי הקטגוריה.

- paperclip_client.py: wake_ceo_for_feedback_fold() — יוצר issue ב-Paperclip
  עם הלקח + rubric ניתוב (style→SKILL.md, wrong_structure→block-schema,
  אחר→lessons.md), מעיר CEO. משכפל את דפוס wake_for_precedent_extraction
- db.py: get_chair_feedback(id) — שליפת הערה בודדת עם case_number/appeal_type
- app.py: resolve endpoint מקבל fold (ברירת מחדל true); BackgroundTask
  fire-and-forget; guard — רק עם lesson_extracted. מחזיר fold_queued
- legal-ceo.md: dispatch ל-feedback_fold_ + סעיף "קיפול הערת יו"ר" עם rubric
- frontend: useResolveFeedback מקבל fold; /feedback שולח fold=true עם toast;
  drafts-panel שולח fold=false (bookkeeping per-case, בלי קיפול כפול)

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

145 lines
4.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Chair feedback hooks — recording and managing Dafna's feedback on drafts.
*/
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { apiRequest } from "./client";
export type FeedbackCategory =
| "missing_content"
| "wrong_tone"
| "wrong_structure"
| "factual_error"
| "style"
| "other";
export type ChairFeedback = {
id: string;
case_id: string | null;
case_number: string;
block_id: string;
category: FeedbackCategory;
feedback_text: string;
lesson_extracted: string;
resolved: boolean;
applied_to: string[];
created_at: string | null;
};
export type CreateFeedbackInput = {
case_number?: string;
block_id?: string;
feedback_text: string;
category?: FeedbackCategory;
lesson_extracted?: string;
};
const feedbackKeys = {
all: ["feedback"] as const,
list: (filters: { category?: string; unresolved_only?: boolean }) =>
[...feedbackKeys.all, "list", filters] as const,
};
export function useFeedbackList(filters: {
category?: string;
unresolved_only?: boolean;
} = {}) {
const params = new URLSearchParams();
if (filters.category) params.set("category", filters.category);
if (filters.unresolved_only) params.set("unresolved_only", "true");
const qs = params.toString();
return useQuery({
queryKey: feedbackKeys.list(filters),
queryFn: ({ signal }) =>
apiRequest<ChairFeedback[]>(`/api/feedback${qs ? `?${qs}` : ""}`, { signal }),
});
}
/** Feedback filtered by case number */
export function useCaseFeedback(caseNumber: string | undefined) {
const params = caseNumber ? `?case_number=${caseNumber}` : "";
return useQuery({
queryKey: [...feedbackKeys.all, "case", caseNumber ?? ""] as const,
queryFn: ({ signal }) =>
apiRequest<ChairFeedback[]>(`/api/feedback${params}`, { signal }),
enabled: Boolean(caseNumber),
staleTime: 5_000,
refetchInterval: 5_000,
});
}
export function useCreateFeedback() {
const qc = useQueryClient();
return useMutation({
mutationFn: (data: CreateFeedbackInput) =>
apiRequest<{ id: string; status: string }>("/api/feedback/json", {
method: "POST",
body: data,
}),
onSuccess: () => {
qc.invalidateQueries({ queryKey: feedbackKeys.all });
},
});
}
export function useResolveFeedback() {
const qc = useQueryClient();
return useMutation({
mutationFn: ({
feedbackId,
applied_to,
fold,
}: {
feedbackId: string;
applied_to: string[];
/** When true (default server-side), wakes the CEO to fold the lesson
* into the right knowledge file. Pass false for pure bookkeeping. */
fold?: boolean;
}) =>
apiRequest<{ status: string; fold_queued: boolean }>(
`/api/feedback/${feedbackId}/resolve`,
{ method: "PATCH", body: { applied_to, ...(fold === undefined ? {} : { fold }) } },
),
onSuccess: () => {
qc.invalidateQueries({ queryKey: feedbackKeys.all });
},
});
}
/** Hebrew labels for feedback categories */
export const CATEGORY_LABELS: Record<FeedbackCategory, string> = {
missing_content: "תוכן חסר",
wrong_tone: "טון שגוי",
wrong_structure: "מבנה שגוי",
factual_error: "שגיאה עובדתית",
style: "סגנון",
other: "אחר",
};
/** Tailwind color classes per category */
export const CATEGORY_COLORS: Record<FeedbackCategory, string> = {
missing_content: "bg-amber-100 text-amber-800 border-amber-200",
wrong_tone: "bg-purple-100 text-purple-800 border-purple-200",
wrong_structure: "bg-blue-100 text-blue-800 border-blue-200",
factual_error: "bg-red-100 text-red-800 border-red-200",
style: "bg-emerald-100 text-emerald-800 border-emerald-200",
other: "bg-gray-100 text-gray-800 border-gray-200",
};
/** Block ID labels */
export const BLOCK_LABELS: Record<string, string> = {
"block-alef": "א — כותרת מוסדית",
"block-bet": "ב — הרכב הוועדה",
"block-gimel": "ג — צדדים",
"block-dalet": "ד — החלטה",
"block-he": "ה — פתיחה",
"block-vav": "ו — רקע עובדתי",
"block-zayin": "ז — טענות הצדדים",
"block-chet": "ח — הליכים",
"block-tet": "ט — תכניות חלות",
"block-yod": "י — דיון והכרעה",
"block-yod-alef": "יא — סיכום",
"block-yod-bet": "יב — חתימות",
};