"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 (
โ {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
);
}
// โโ 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")}
ืขืจืื ื-Gitea
);
}
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/.
ืืืื ืืืื ืืืืฉืจ ืืื ืืช โ ืืื ืฉืื ืื ืืืืืืื ืืคืจืืืคื.
);
}