"use client"; /* * Curator-Portrait tab โ€” shows everything about the agent that learns * Daphna's style: * 1. Snapshot stats (curator findings to date, % applied) * 2. Recent curator findings (last 10) โ€” linked by decision number * 3. The hermes-curator system prompt, rendered + linked to Gitea * 4. The style_analyzer training prompts (different lifecycle โ€” runs * over the corpus at training time, not per-decision) * 5. Propose-change form โ€” writes a markdown file to disk for chair * review (no auto-commit) * * The prompts are deliberately read-only here: they're symlinked into * Paperclip and load-bearing for every curator wake. Editing them from * the UI would silently fork the source of truth. */ import { useState } from "react"; import { Sparkles, ExternalLink, Send, Loader2, FileText, Brain, CheckCircle2, Clock, } from "lucide-react"; import { toast } from "sonner"; import { Card, CardContent } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import { Badge } from "@/components/ui/badge"; import { Skeleton } from "@/components/ui/skeleton"; import { ScrollArea } from "@/components/ui/scroll-area"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Markdown } from "@/components/ui/markdown"; import { useCuratorPrompt, useCuratorStats, useStyleAnalyzerPrompts, useSubmitCuratorProposal, } from "@/lib/api/training"; export function CuratorPortraitPanel() { return (
๐Ÿ”’
ืฉืขืจ-ื”ื™ื•ืดืจ (INV-LRN1 / G10): ื›ืœ ื”ืขืจื•ืฆื™ื ืžืฆื™ืขื™ื ื‘ืœื‘ื“. ืžืžืฆื ืื• ืœืงื— ืžืฉืคื™ืข ืขืœ ื”ื›ืชื™ื‘ื” ืจืง ืื—ืจื™ ืฉื“ืคื ื” ืื™ืฉืจื” ืื•ืชื•. ื”ืื•ืฆึตืจ ืขืฆืžื• read-only ืขืœ ื”ืชื•ื›ืŸ โ€” ืžื–ื”ื” ื“ืคื•ืกื™ื ื•ืžื’ื™ืฉ, ืœืขื•ืœื ืœื ืžืฉื ื” ืงื‘ืฆื™ื ืื• ืกื’ื ื•ืŸ ื‘ืขืฆืžื•.
ืคืจื•ืžืคื˜ ื”-Curator ืคืจื•ืžืคื˜ ืื™ืžื•ืŸ ื”ืกื’ื ื•ืŸ ื”ืฆืขืช ืฉื™ื ื•ื™
); } // โ”€โ”€ stats card โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ function SectionLabel({ children }: { children: React.ReactNode }) { return (
{children}
); } /** One of the three learning channels โ€” engine, backing store, count, flow gate. */ function ChannelCard({ letter, title, engine, target, store, value, unit, flows, flowLabel, meta, }: { letter: string; title: string; engine: string; target: string; store: string; value: number; unit: string; flows: boolean; flowLabel: string; meta: string; }) { return (
{letter}

{title}

{engine}
โ† {target} {store}
{value} {unit}
โ—{flowLabel}
{meta}
); } function StatsCard() { const { data, isPending } = useCuratorStats(); if (isPending) { return (
{[...Array(3)].map((_, i) => )}
); } if (!data) return null; const { distillation: a, panel: b, curator: c } = data.channels; return (
โ˜…

ื”ืœืžื™ื“ื” ื–ื•ืจืžืช ืœื›ื•ืชื‘ ื‘ืฉืœื•ืฉื” ืขืจื•ืฆื™ื ื ืคืจื“ื™ื, ื›ืœ ืื—ื“ ืžื•ืคืขืœ ื›ืฉื“ืคื ื” ืžืกืžื ืช ื”ื—ืœื˜ื” ื›ืกื•ืคื™ืช. ื”ื›ืจื˜ื™ืก ืžืจืื” ื›ืžื” ื›ืœ ืขืจื•ืฅ ืชืจื ื•ืžื” ื›ื‘ืจ ืขื•ื‘ืจ ื‘ืคื•ืขืœ ืœื›ื•ืชื‘ โ€” ืจืง ื™ื“ืข ืฉืื•ืฉืจ ื‘ืฉืขืจ-ื”ื™ื•ืดืจ ืžืฉืคื™ืข ืขืœ ื”ื˜ื™ื•ื˜ื•ืช (INV-LRN1).

ืฉืœื•ืฉืช ืขืจื•ืฆื™-ื”ื”ื–ื ื” ืœื›ื•ืชื‘
0} flowLabel="ื–ื•ืจื ืœื›ื•ืชื‘" meta={`${a.discussion_rules} ื›ืœืœื™-ื“ื™ื•ืŸ + ${a.transition_phrases} ื‘ื™ื˜ื•ื™ื™-ืžืขื‘ืจ ยท ืื•ืฉืจื• ื‘-${a.pairs_folded} ื–ื•ื’ื•ืช ื“ืจืš ื˜ืื‘ ืดืœืžื™ื“ื”ืด.`} /> 0} flowLabel={`${b.approved} ืื•ืฉืจื• ยท ${b.proposed} ืžืžืชื™ื ื™ื`} meta={`ืขืœ ืคื ื™ ${b.decisions} ื”ื—ืœื˜ื•ืช ยท ืื™ืฉื•ืจ per-ื”ื—ืœื˜ื” ื‘ื˜ืื‘ ื”ืงื•ืจืคื•ืก.`} /> 0} flowLabel={`${c.approved} ืื•ืฉืจื• ยท ${c.proposed} ืžืžืชื™ื ื™ื`} meta="3โ€“5 ื“ืคื•ืกื™ื ืžืชื•ื™ื’ื™ื ืœื”ื—ืœื˜ื” (ืกื’ื ื•ืŸ/ืžื‘ื ื”/ืœืงืกื™ืงื•ืŸ/ื˜ื‘ืœืื™) โ€” ื ืชืคืกื™ื ืžื‘ื ื™ืช, ืœื ืจืง ื›ื”ืขืจื”." />
ืขืจื•ืฅ ื”ืื•ืฆึตืจ โ€” ืžื‘ื˜ ืžืงืจื•ื‘
} /> } /> } /> } />
); } function Kpi({ label, value, icon, tone, }: { label: string; value: string | number; icon: React.ReactNode; tone?: "success" | "gold" }) { const valueCls = tone === "success" ? "text-success" : tone === "gold" ? "text-gold-deep" : "text-navy"; return (
{icon} {label}

{value}

); } // โ”€โ”€ recent findings โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ function RecentFindings() { const { data, isPending } = useCuratorStats(); if (isPending) { return ; } if (!data || data.recent_findings.length === 0) { return ( ืื™ืŸ ืขื“ื™ื™ืŸ ืžืžืฆืื™-ืื•ืฆึตืจ. ื›ืฉื“ืคื ื” ืžืกืžื ืช ื”ื—ืœื˜ื” ื›ืกื•ืคื™ืช, ื”ืื•ืฆึตืจ ืงื•ืจื ืืช ื”ืกื•ืคื™, ืžื–ื”ื” 3โ€“5 ื“ืคื•ืกื™-ืกื’ื ื•ืŸ, ื•ืฉื•ืžืจ ืื•ืชื ื›-decision_lessons (source="curator") ื”ืžืžืชื™ื ื™ื ืœืื™ืฉื•ืจื” โ€” ื‘ื ืคืจื“ ืžืœืงื—ื™-ื”ืคืื ืœ ื•ืžื›ืœืœื™-ื”ืžืชื•ื“ื•ืœื•ื’ื™ื” ืฉืœืžืขืœื”. ); } return (

ืžืžืฆืื™ื ืื—ืจื•ื ื™ื ืฉืœ ื”-Curator

    {data.recent_findings.map((f) => (
  • {f.category} {f.decision_number || "โ€”"} {f.review_status === "approved" && ( ืžืื•ืฉืจ )} {new Date(f.created_at).toLocaleDateString("he-IL")}

    {f.lesson_text}

  • ))}
); } // โ”€โ”€ prompts โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ function CuratorPromptCard() { const { data, isPending, error } = useCuratorPrompt(); if (isPending) return ; if (error) { return ( {error.message} ); } if (!data) return null; return (

{data.filename}

{data.bytes.toLocaleString("he-IL")} ื‘ื™ื™ื˜ื™ื ยท ืขื•ื“ื›ืŸ: {new Date(data.last_modified * 1000).toLocaleString("he-IL")}

); } function StyleAnalyzerPromptCard() { const { data, isPending } = useStyleAnalyzerPrompts(); if (isPending) return ; if (!data) return null; return (

ืคืจื•ืžืคื˜ื™ื ืฉืœ style_analyzer.py

ืจืฅ ื‘-Claude Opus (1M context, ืขื“ {data.max_input_tokens.toLocaleString("he-IL")} tokens input) ื“ืจืš claude CLI ืžืงื•ืžื™ โ€” ื—ื™ื ืžื™, ืœืœื API. ื ืงืจื ืข"ื™ POST /api/training/analyze-style ื•ืžื›ื ื™ืก ื“ืคื•ืกื™ื ืœ-style_patterns.

Single-pass (ื›ืœ ื”ืงื•ืจืคื•ืก) Multi-pass (ื”ื—ืœื˜ื” ืื—ืช) Synthesis
); } function PromptBlock({ content }: { content: string }) { return (
        {content}
      
); } // โ”€โ”€ propose change form โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ function ProposeChangeForm() { const [title, setTitle] = useState(""); const [proposedChange, setProposedChange] = useState(""); const [rationale, setRationale] = useState(""); const submit = useSubmitCuratorProposal(); const onSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!title.trim() || !proposedChange.trim()) { toast.error("ื—ื•ื‘ื” ื›ื•ืชืจืช ื•ืฉื™ื ื•ื™ ืžื•ืฆืข"); return; } try { const r = await submit.mutateAsync({ title: title.trim(), proposed_change: proposedChange.trim(), rationale: rationale.trim(), }); toast.success(`ื ืฉืžืจื” ื”ืฆืขื”: ${r.filename}`); setTitle(""); setProposedChange(""); setRationale(""); } catch (e) { toast.error(e instanceof Error ? e.message : "ื›ืฉืœ ื‘ืฉืžื™ืจื”"); } }; return (

ื”ืฆืขืช ืฉื™ื ื•ื™ ืœืคืจื•ืžืคื˜ ื”-Curator

ื”ื”ืฆืขื” ืชื™ืฉืžืจ ื›ืงื•ื‘ืฅ Markdown ื‘- data/curator-proposals/. ื—ื™ื™ื ื™ื‘ื—ืŸ ื•ื™ืืฉืจ ื™ื“ื ื™ืช โ€” ืื™ืŸ ืฉื™ื ื•ื™ ืื•ื˜ื•ืžื˜ื™ ื‘ืคืจื•ืžืคื˜.

setTitle(e.target.value)} placeholder="ืœื“ื•ื’ืžื”: ื”ื•ืกืฃ ืงื˜ื’ื•ืจื™ื” [ืฆืณืงืœื™ืกื˜ ืชื•ื›ืŸ] ืœืžืžืฆืื™ ื”-curator" dir="rtl" />