Merge pull request 'feat(ui): מתאמי-סוכנים לטבלה + עיצוב-תגים בתור-ההלכות (קטגוריה B #2/#3)' (#280) from worktree-catB-adapters-halacha into main
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 45s
G12 Leak-Guard / leak-guard (push) Successful in 5s
Lint — undefined names / undefined-names (push) Successful in 10s

This commit was merged in pull request #280.
This commit is contained in:
2026-06-17 03:34:03 +00:00
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,60 +177,89 @@ 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">
{data.pairs.map((p) => { <Table>
const cur = currentOf(p); <TableHeader>
const tgt = targetFor(p); <TableRow className="bg-parchment hover:bg-parchment border-rule">
const migrated = cur !== "claude_local" && cur !== null; <TableHead className="text-start text-[0.72rem] font-medium text-ink-muted">תפקיד</TableHead>
const asym = isAsymmetric(p); <TableHead className="text-start text-[0.72rem] font-medium text-ink-muted">מתאם נוכחי</TableHead>
return ( <TableHead className="text-start text-[0.72rem] font-medium text-ink-muted">מודל</TableHead>
<div key={p.name} <TableHead className="text-start text-[0.72rem] font-medium text-ink-muted">העבר ל־</TableHead>
className="flex items-center justify-between gap-3 rounded-md border border-rule-soft bg-rule-soft/30 px-3 py-2 flex-wrap"> <TableHead className="text-start text-[0.72rem] font-medium text-ink-muted" aria-label="פעולה" />
<div className="min-w-0"> <TableHead className="text-start text-[0.72rem] font-medium text-ink-muted">מצב</TableHead>
<div className="flex items-center gap-2 flex-wrap"> </TableRow>
<span className="text-[0.85rem] text-navy font-semibold">{p.name}</span> </TableHeader>
<Badge variant="outline" className={`font-normal ${adapterCls(cur)}`}> <TableBody>
{adapterLabel(cur)} {data.pairs.map((p) => {
</Badge> const cur = currentOf(p);
<span className="text-[0.66rem] text-ink-muted font-mono" dir="ltr"> const tgt = targetFor(p);
{modelOf(p) || "(default)"} const migrated = cur !== "claude_local" && cur !== null;
</span> const asym = isAsymmetric(p);
{asym ? ( const subtitle = ROLE_SUBTITLE[p.name];
<Badge variant="outline" className="font-normal bg-danger-bg text-danger border-danger/40"> return (
א-סימטרי בין החברות <TableRow key={p.name} className={`border-rule-soft ${asym ? "bg-danger-bg/25" : ""}`}>
<TableCell className="py-2.5 align-middle">
<div className="text-[0.85rem] text-navy font-semibold leading-tight">{p.name}</div>
{subtitle ? (
<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)}`}>
{adapterLabel(cur)}
</Badge> </Badge>
) : migrated ? ( </TableCell>
<Badge variant="outline" className="font-normal bg-warn-bg text-warn border-warn/40"> <TableCell className="py-2.5 align-middle">
מועבר · fallback <span className="text-[0.68rem] text-ink-soft font-mono" dir="ltr">
</Badge> {modelOf(p) || "(default)"}
) : null} </span>
</div> </TableCell>
</div> <TableCell className="py-2.5 align-middle">
<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 /> </SelectTrigger>
</SelectTrigger> <SelectContent>
<SelectContent> {ADAPTERS.map((a) => (
{ADAPTERS.map((a) => ( <SelectItem key={a.value} value={a.value}>{a.label}</SelectItem>
<SelectItem key={a.value} value={a.value}>{a.label}</SelectItem> ))}
))} </SelectContent>
</SelectContent> </Select>
</Select> </TableCell>
<Button size="xs" variant="default" disabled={busy || tgt === cur} <TableCell className="py-2.5 align-middle">
onClick={() => openMigrate(p.name, tgt, `העברת "${p.name}" → ${adapterLabel(tgt)}`)}> <div className="flex items-center gap-1.5">
העבר <Button size="xs" variant="default" disabled={busy || tgt === cur}
</Button> onClick={() => openMigrate(p.name, tgt, `העברת "${p.name}" → ${adapterLabel(tgt)}`)}>
{migrated ? ( העבר
<Button size="xs" variant="ghost" disabled={busy} </Button>
onClick={() => openRevert(p.name, `החזרת "${p.name}" למצב-מקור`)}> {migrated ? (
החזר <Button size="xs" variant="ghost" disabled={busy}
</Button> onClick={() => openRevert(p.name, `החזרת "${p.name}" למצב-מקור`)}>
) : null} החזר
</div> </Button>
</div> ) : null}
); </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>
)} )}