feat(settings): add Blocks tab — 12-block decision schema reference
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 1m35s
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 1m35s
Read-only display of BLOCK_CONFIG from block_writer.py with CREAC role and JWM functional-purpose annotations per block (sourced from docs/block-schema.md). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
128
web-ui/src/app/settings/_components/blocks-tab.tsx
Normal file
128
web-ui/src/app/settings/_components/blocks-tab.tsx
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Layers, AlertCircle } from "lucide-react";
|
||||||
|
import { Card, CardContent } from "@/components/ui/card";
|
||||||
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
import { useMcpBlocks, type McpBlock } from "@/lib/api/settings";
|
||||||
|
|
||||||
|
const GEN_TYPE_LABEL: Record<string, string> = {
|
||||||
|
"template-fill": "מילוי תבנית",
|
||||||
|
"paraphrase": "פרפרזה",
|
||||||
|
"reproduction": "שעתוק",
|
||||||
|
"guided-synthesis": "סינתזה מודרכת",
|
||||||
|
"rhetorical-construction": "בניה רטורית",
|
||||||
|
};
|
||||||
|
|
||||||
|
const GEN_TYPE_TONE: Record<string, string> = {
|
||||||
|
"template-fill": "text-ink-muted border-rule",
|
||||||
|
"paraphrase": "text-info border-info/40",
|
||||||
|
"reproduction": "text-info border-info/40",
|
||||||
|
"guided-synthesis": "text-warn border-warn/40",
|
||||||
|
"rhetorical-construction": "text-gold-deep border-gold/40",
|
||||||
|
};
|
||||||
|
|
||||||
|
function BlockRow({ block }: { block: McpBlock }) {
|
||||||
|
const isLLM = block.model !== "script";
|
||||||
|
return (
|
||||||
|
<div className="rounded-md border border-rule p-4 bg-rule-soft/20 hover:bg-rule-soft/40 transition-colors">
|
||||||
|
<div className="flex items-start gap-3">
|
||||||
|
<div className="flex-shrink-0 w-10 h-10 rounded-md bg-navy/5 border border-navy/20 flex items-center justify-center">
|
||||||
|
<span className="text-navy text-sm font-semibold tabular-nums">
|
||||||
|
{block.index}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex-1 min-w-0 space-y-2">
|
||||||
|
<div className="flex items-center gap-2 flex-wrap">
|
||||||
|
<h3 className="text-navy font-medium">{block.title}</h3>
|
||||||
|
<code dir="ltr" className="font-mono text-[0.72rem] text-ink-muted">
|
||||||
|
{block.id}
|
||||||
|
</code>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2 flex-wrap">
|
||||||
|
<Badge
|
||||||
|
variant="outline"
|
||||||
|
className={`text-[0.7rem] ${GEN_TYPE_TONE[block.gen_type] ?? ""}`}
|
||||||
|
>
|
||||||
|
{GEN_TYPE_LABEL[block.gen_type] ?? block.gen_type}
|
||||||
|
</Badge>
|
||||||
|
<Badge variant="outline" className="text-[0.7rem] font-mono" dir="ltr">
|
||||||
|
{block.model}
|
||||||
|
</Badge>
|
||||||
|
{isLLM && block.temperature !== null && (
|
||||||
|
<Badge variant="outline" className="text-[0.7rem]">
|
||||||
|
temp <span className="tabular-nums">{block.temperature}</span>
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
{block.max_tokens !== null && (
|
||||||
|
<Badge variant="outline" className="text-[0.7rem]">
|
||||||
|
max <span className="tabular-nums">{block.max_tokens}</span>
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{(block.creac_role || block.jwm_purpose) && (
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-4 gap-y-1 text-[0.78rem] text-ink-muted pt-1">
|
||||||
|
{block.creac_role && (
|
||||||
|
<div>
|
||||||
|
<span className="text-[0.7rem] uppercase tracking-wide me-1">
|
||||||
|
CREAC:
|
||||||
|
</span>
|
||||||
|
<span dir="ltr">{block.creac_role}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{block.jwm_purpose && (
|
||||||
|
<div>
|
||||||
|
<span className="text-[0.7rem] uppercase tracking-wide me-1">
|
||||||
|
JWM:
|
||||||
|
</span>
|
||||||
|
<span dir="ltr">{block.jwm_purpose}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function BlocksTab() {
|
||||||
|
const { data, isPending, error } = useMcpBlocks();
|
||||||
|
|
||||||
|
if (isPending) return <Skeleton className="h-96 w-full" />;
|
||||||
|
if (error) {
|
||||||
|
return (
|
||||||
|
<Card className="bg-surface border-danger/40">
|
||||||
|
<CardContent className="p-6 flex items-center gap-3 text-danger">
|
||||||
|
<AlertCircle className="w-5 h-5" />
|
||||||
|
<span>שגיאה בטעינת בלוקים: {error.message}</span>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (!data) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<Card className="bg-surface border-rule">
|
||||||
|
<CardContent className="px-6 py-5">
|
||||||
|
<div className="flex items-center gap-2 mb-4 text-ink-muted text-sm">
|
||||||
|
<Layers className="w-4 h-4" />
|
||||||
|
<span>
|
||||||
|
ארכיטקטורת 12 הבלוקים של החלטת ועדת ערר. מקור הסכימה:{" "}
|
||||||
|
<code dir="ltr" className="font-mono text-[0.78rem]">
|
||||||
|
docs/block-schema.md
|
||||||
|
</code>
|
||||||
|
.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-3">
|
||||||
|
{data.blocks.map((b) => (
|
||||||
|
<BlockRow key={b.id} block={b} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,13 +1,14 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { Server, Wrench, Plug, Building2 } from "lucide-react";
|
import { Server, Wrench, Plug, Building2, Layers } from "lucide-react";
|
||||||
import { AppShell } from "@/components/app-shell";
|
import { AppShell } from "@/components/app-shell";
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
import { PaperclipTab } from "./_components/paperclip-tab";
|
import { PaperclipTab } from "./_components/paperclip-tab";
|
||||||
import { EnvironmentTab } from "./_components/environment-tab";
|
import { EnvironmentTab } from "./_components/environment-tab";
|
||||||
import { ToolsTab } from "./_components/tools-tab";
|
import { ToolsTab } from "./_components/tools-tab";
|
||||||
import { RegistrationsTab } from "./_components/registrations-tab";
|
import { RegistrationsTab } from "./_components/registrations-tab";
|
||||||
|
import { BlocksTab } from "./_components/blocks-tab";
|
||||||
|
|
||||||
export default function SettingsPage() {
|
export default function SettingsPage() {
|
||||||
return (
|
return (
|
||||||
@@ -43,6 +44,10 @@ export default function SettingsPage() {
|
|||||||
<Wrench className="w-4 h-4" data-icon="inline-start" />
|
<Wrench className="w-4 h-4" data-icon="inline-start" />
|
||||||
Tools
|
Tools
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
|
<TabsTrigger value="blocks">
|
||||||
|
<Layers className="w-4 h-4" data-icon="inline-start" />
|
||||||
|
Blocks
|
||||||
|
</TabsTrigger>
|
||||||
<TabsTrigger value="registrations">
|
<TabsTrigger value="registrations">
|
||||||
<Plug className="w-4 h-4" data-icon="inline-start" />
|
<Plug className="w-4 h-4" data-icon="inline-start" />
|
||||||
Registrations
|
Registrations
|
||||||
@@ -52,6 +57,7 @@ export default function SettingsPage() {
|
|||||||
<TabsContent value="paperclip"><PaperclipTab /></TabsContent>
|
<TabsContent value="paperclip"><PaperclipTab /></TabsContent>
|
||||||
<TabsContent value="environment"><EnvironmentTab /></TabsContent>
|
<TabsContent value="environment"><EnvironmentTab /></TabsContent>
|
||||||
<TabsContent value="tools"><ToolsTab /></TabsContent>
|
<TabsContent value="tools"><ToolsTab /></TabsContent>
|
||||||
|
<TabsContent value="blocks"><BlocksTab /></TabsContent>
|
||||||
<TabsContent value="registrations"><RegistrationsTab /></TabsContent>
|
<TabsContent value="registrations"><RegistrationsTab /></TabsContent>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -169,3 +169,27 @@ export function useMcpRegistrations() {
|
|||||||
staleTime: 60_000,
|
staleTime: 60_000,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type McpBlock = {
|
||||||
|
id: string;
|
||||||
|
index: number;
|
||||||
|
title: string;
|
||||||
|
gen_type: string;
|
||||||
|
model: string;
|
||||||
|
temperature: number | null;
|
||||||
|
max_tokens: number | null;
|
||||||
|
creac_role: string | null;
|
||||||
|
jwm_purpose: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function useMcpBlocks() {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ["settings", "mcp-blocks"] as const,
|
||||||
|
queryFn: ({ signal }) =>
|
||||||
|
apiRequest<{ blocks: McpBlock[]; count: number }>(
|
||||||
|
"/api/settings/mcp/blocks",
|
||||||
|
{ signal },
|
||||||
|
),
|
||||||
|
staleTime: 5 * 60_000, // 5 minutes — static reference data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
47
web/app.py
47
web/app.py
@@ -2830,6 +2830,53 @@ async def api_mcp_registrations():
|
|||||||
return list_registrations()
|
return list_registrations()
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/api/settings/mcp/blocks")
|
||||||
|
async def api_mcp_blocks():
|
||||||
|
"""List the 12-block decision schema (read-only reference)."""
|
||||||
|
from legal_mcp.services.block_writer import BLOCK_CONFIG
|
||||||
|
|
||||||
|
# CREAC role per block (from docs/block-schema.md). Static map —
|
||||||
|
# kept here rather than in BLOCK_CONFIG to avoid coupling LLM
|
||||||
|
# generation config to documentation metadata.
|
||||||
|
CREAC_ROLE = {
|
||||||
|
"block-alef": None, "block-bet": None, "block-gimel": None,
|
||||||
|
"block-dalet": None, "block-yod-bet": None,
|
||||||
|
"block-he": "Conclusion (preview)",
|
||||||
|
"block-vav": "Facts (R-context)",
|
||||||
|
"block-zayin": "Arguments",
|
||||||
|
"block-chet": "Procedural record",
|
||||||
|
"block-tet": "Rule (R)",
|
||||||
|
"block-yod": "C → R → E → A → C (full CREAC)",
|
||||||
|
"block-yod-alef": "Conclusion (final)",
|
||||||
|
}
|
||||||
|
# JWM functional purpose (Federal Judicial Center mapping)
|
||||||
|
JWM_PURPOSE = {
|
||||||
|
"block-alef": "Orientation", "block-bet": "Orientation",
|
||||||
|
"block-gimel": "Orientation", "block-dalet": "Orientation",
|
||||||
|
"block-he": "Orientation",
|
||||||
|
"block-vav": "Framing", "block-zayin": "Argumentation",
|
||||||
|
"block-chet": "Procedural record",
|
||||||
|
"block-tet": "Deliberation (rules)",
|
||||||
|
"block-yod": "Deliberation (analysis)",
|
||||||
|
"block-yod-alef": "Disposition",
|
||||||
|
"block-yod-bet": "Disposition (signatures)",
|
||||||
|
}
|
||||||
|
blocks = []
|
||||||
|
for block_id, cfg in sorted(BLOCK_CONFIG.items(), key=lambda kv: kv[1]["index"]):
|
||||||
|
blocks.append({
|
||||||
|
"id": block_id,
|
||||||
|
"index": cfg["index"],
|
||||||
|
"title": cfg["title"],
|
||||||
|
"gen_type": cfg["gen_type"],
|
||||||
|
"model": cfg["model"],
|
||||||
|
"temperature": cfg.get("temp"),
|
||||||
|
"max_tokens": cfg.get("max_tokens"),
|
||||||
|
"creac_role": CREAC_ROLE.get(block_id),
|
||||||
|
"jwm_purpose": JWM_PURPOSE.get(block_id),
|
||||||
|
})
|
||||||
|
return {"blocks": blocks, "count": len(blocks)}
|
||||||
|
|
||||||
|
|
||||||
# ── Settings: Tag → Company Mappings ──────────────────────────────
|
# ── Settings: Tag → Company Mappings ──────────────────────────────
|
||||||
|
|
||||||
@app.get("/api/settings/paperclip-companies")
|
@app.get("/api/settings/paperclip-companies")
|
||||||
|
|||||||
Reference in New Issue
Block a user