feat(ui): מתאמי-סוכנים לטבלה + אימוץ עיצוב-תגים בתור-ההלכות (קטגוריה B #2/#3)
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 5s
Lint — undefined names / undefined-names (pull_request) Successful in 10s

הכרעות-מוצר מדוח-הנאמנות:
- agent-adapters-panel: כרטיסונים → טבלה (6 עמודות: תפקיד+תת-תפקיד / מתאם /
  מודל / העבר-ל / פעולה / מצב) לפי מוקאפ 02d. כל הלוגיקה נשמרה (preflight-dialog,
  revert, relax-tools, fallback-bar, דגל-א-סימטרי). עמודת "מצב": תקין/מועבר·
  fallback/⚠ א-סימטרי. תת-תפקיד דו-לשוני (analyst/writer/qa…).
- halacha review card: אימוץ שפת-התגים/צבעים ממוקאפ 19 — תג-זהב "הלכה" בפתח
  שורת-המטא, ורקע gold-wash לכרטיסים עם התלבטות-פאנל (כמו .rc מול .rc.plain).
  מבנה-האקורדיון נשמר (החלטת חיים).

יישור-קוד למוקאפים-מאושרים — ללא סבב-עיצוב חדש.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-17 03:33:37 +00:00
parent b4cb0a69c3
commit 85493502f0
2 changed files with 108 additions and 54 deletions

View File

@@ -27,6 +27,14 @@ import {
SelectContent, SelectContent,
SelectItem, SelectItem,
} from "@/components/ui/select"; } from "@/components/ui/select";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { import {
Dialog, Dialog,
DialogContent, DialogContent,
@@ -51,6 +59,18 @@ const ADAPTERS = [
{ value: "deepseek_local", label: "DeepSeek", cls: "bg-gold-wash text-gold-deep border-gold/40" }, { value: "deepseek_local", label: "DeepSeek", cls: "bg-gold-wash text-gold-deep border-gold/40" },
] as const; ] as const;
// Bilingual role subtitle under the agent name (mockup 02d) — display-only,
// keyed by the agent's Hebrew name. Falls back to none for unmapped agents.
const ROLE_SUBTITLE: Record<string, string> = {
"עוזר משפטי": "CEO · מתזמר",
"מנתח משפטי": "analyst",
"כותב החלטה": "writer",
"מנהל ידע": "אוצֵר-הלכות",
"בודק איכות": "qa",
"הגהת מסמכים": "proofreader",
"שטן מליץ": "red-team (Gemini)",
};
function adapterCls(a: string | null | undefined): string { function adapterCls(a: string | null | undefined): string {
return ADAPTERS.find((x) => x.value === a)?.cls ?? "bg-rule-soft text-ink-muted border-rule"; return ADAPTERS.find((x) => x.value === a)?.cls ?? "bg-rule-soft text-ink-muted border-rule";
} }
@@ -157,36 +177,44 @@ export function AgentAdaptersPanel() {
{isLoading || !data ? ( {isLoading || !data ? (
<Skeleton className="h-40 w-full" /> <Skeleton className="h-40 w-full" />
) : ( ) : (
<div className="grid gap-2"> <div className="overflow-x-auto rounded-md border border-rule-soft">
<Table>
<TableHeader>
<TableRow className="bg-parchment hover:bg-parchment border-rule">
<TableHead className="text-start text-[0.72rem] font-medium text-ink-muted">תפקיד</TableHead>
<TableHead className="text-start text-[0.72rem] font-medium text-ink-muted">מתאם נוכחי</TableHead>
<TableHead className="text-start text-[0.72rem] font-medium text-ink-muted">מודל</TableHead>
<TableHead className="text-start text-[0.72rem] font-medium text-ink-muted">העבר ל־</TableHead>
<TableHead className="text-start text-[0.72rem] font-medium text-ink-muted" aria-label="פעולה" />
<TableHead className="text-start text-[0.72rem] font-medium text-ink-muted">מצב</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{data.pairs.map((p) => { {data.pairs.map((p) => {
const cur = currentOf(p); const cur = currentOf(p);
const tgt = targetFor(p); const tgt = targetFor(p);
const migrated = cur !== "claude_local" && cur !== null; const migrated = cur !== "claude_local" && cur !== null;
const asym = isAsymmetric(p); const asym = isAsymmetric(p);
const subtitle = ROLE_SUBTITLE[p.name];
return ( return (
<div key={p.name} <TableRow key={p.name} className={`border-rule-soft ${asym ? "bg-danger-bg/25" : ""}`}>
className="flex items-center justify-between gap-3 rounded-md border border-rule-soft bg-rule-soft/30 px-3 py-2 flex-wrap"> <TableCell className="py-2.5 align-middle">
<div className="min-w-0"> <div className="text-[0.85rem] text-navy font-semibold leading-tight">{p.name}</div>
<div className="flex items-center gap-2 flex-wrap"> {subtitle ? (
<span className="text-[0.85rem] text-navy font-semibold">{p.name}</span> <div className="text-[0.68rem] text-ink-muted leading-tight">{subtitle}</div>
) : null}
</TableCell>
<TableCell className="py-2.5 align-middle">
<Badge variant="outline" className={`font-normal ${adapterCls(cur)}`}> <Badge variant="outline" className={`font-normal ${adapterCls(cur)}`}>
{adapterLabel(cur)} {adapterLabel(cur)}
</Badge> </Badge>
<span className="text-[0.66rem] text-ink-muted font-mono" dir="ltr"> </TableCell>
<TableCell className="py-2.5 align-middle">
<span className="text-[0.68rem] text-ink-soft font-mono" dir="ltr">
{modelOf(p) || "(default)"} {modelOf(p) || "(default)"}
</span> </span>
{asym ? ( </TableCell>
<Badge variant="outline" className="font-normal bg-danger-bg text-danger border-danger/40"> <TableCell className="py-2.5 align-middle">
א-סימטרי בין החברות
</Badge>
) : migrated ? (
<Badge variant="outline" className="font-normal bg-warn-bg text-warn border-warn/40">
מועבר · fallback
</Badge>
) : null}
</div>
</div>
<div className="flex items-center gap-1.5 shrink-0">
<Select value={tgt} onValueChange={(v) => setTargets((t) => ({ ...t, [p.name]: v }))}> <Select value={tgt} onValueChange={(v) => setTargets((t) => ({ ...t, [p.name]: v }))}>
<SelectTrigger size="sm" className="w-[8.5rem]"> <SelectTrigger size="sm" className="w-[8.5rem]">
<SelectValue /> <SelectValue />
@@ -197,6 +225,9 @@ export function AgentAdaptersPanel() {
))} ))}
</SelectContent> </SelectContent>
</Select> </Select>
</TableCell>
<TableCell className="py-2.5 align-middle">
<div className="flex items-center gap-1.5">
<Button size="xs" variant="default" disabled={busy || tgt === cur} <Button size="xs" variant="default" disabled={busy || tgt === cur}
onClick={() => openMigrate(p.name, tgt, `העברת "${p.name}" → ${adapterLabel(tgt)}`)}> onClick={() => openMigrate(p.name, tgt, `העברת "${p.name}" → ${adapterLabel(tgt)}`)}>
העבר העבר
@@ -208,9 +239,27 @@ export function AgentAdaptersPanel() {
</Button> </Button>
) : null} ) : null}
</div> </div>
</div> </TableCell>
<TableCell className="py-2.5 align-middle">
{asym ? (
<Badge variant="outline" className="font-normal bg-danger-bg text-danger border-danger/40 whitespace-nowrap">
א-סימטרי
</Badge>
) : migrated ? (
<Badge variant="outline" className="font-normal bg-warn-bg text-warn border-warn/40 whitespace-nowrap">
מועבר · fallback
</Badge>
) : (
<Badge variant="outline" className="font-normal bg-success-bg text-success border-success/40">
תקין
</Badge>
)}
</TableCell>
</TableRow>
); );
})} })}
</TableBody>
</Table>
</div> </div>
)} )}
</CardContent> </CardContent>

View File

@@ -185,11 +185,16 @@ function HalachaCard({
<div <div
data-halacha-id={h.id} data-halacha-id={h.id}
className={` className={`
rounded-lg border bg-surface p-4 space-y-3 transition-colors rounded-lg border p-4 space-y-3 transition-colors
${h.panel_round ? "bg-gold-wash" : "bg-surface"}
${focused ? "border-gold ring-2 ring-gold/40 shadow-md" : "border-rule"} ${focused ? "border-gold ring-2 ring-gold/40 shadow-md" : "border-rule"}
`} `}
> >
<div className="flex items-start gap-2 text-[0.78rem] text-ink-muted flex-wrap"> <div className="flex items-start gap-2 text-[0.78rem] text-ink-muted flex-wrap">
{/* gold "הלכה" tag opening the meta row (mockup 19 `.b-hal`) */}
<Badge className="rounded bg-gold text-white border-0 text-[0.62rem] font-bold tracking-wide">
הלכה
</Badge>
{h.page_reference && ( {h.page_reference && (
<span className="text-[0.7rem]">{h.page_reference}</span> <span className="text-[0.7rem]">{h.page_reference}</span>
)} )}