feat(settings): implement Tools tab with detail drawer
Replaces stub ToolsTab with a grouped-by-module grid of clickable tool cards. Adds ToolDetailDrawer (Sheet) showing name, description, module, source_location, and params_schema for the selected tool. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
65
web-ui/src/app/settings/_components/tool-detail-drawer.tsx
Normal file
65
web-ui/src/app/settings/_components/tool-detail-drawer.tsx
Normal file
@@ -0,0 +1,65 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
Sheet,
|
||||
SheetContent,
|
||||
SheetHeader,
|
||||
SheetTitle,
|
||||
SheetDescription,
|
||||
} from "@/components/ui/sheet";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import type { McpTool } from "@/lib/api/settings";
|
||||
|
||||
type Props = {
|
||||
tool: McpTool | null;
|
||||
open: boolean;
|
||||
onOpenChange: (o: boolean) => void;
|
||||
};
|
||||
|
||||
export function ToolDetailDrawer({ tool, open, onOpenChange }: Props) {
|
||||
return (
|
||||
<Sheet open={open} onOpenChange={onOpenChange}>
|
||||
<SheetContent side="left" className="sm:max-w-xl overflow-y-auto">
|
||||
{tool && (
|
||||
<>
|
||||
<SheetHeader>
|
||||
<SheetTitle dir="ltr" className="font-mono text-navy">
|
||||
{tool.name}
|
||||
</SheetTitle>
|
||||
<SheetDescription>{tool.description || "—"}</SheetDescription>
|
||||
</SheetHeader>
|
||||
<div className="space-y-4 mt-4 px-4 pb-6">
|
||||
<div>
|
||||
<div className="text-[0.72rem] text-ink-muted uppercase mb-1">
|
||||
Module
|
||||
</div>
|
||||
<Badge variant="outline" className="font-mono" dir="ltr">
|
||||
{tool.module}
|
||||
</Badge>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-[0.72rem] text-ink-muted uppercase mb-1">
|
||||
Source
|
||||
</div>
|
||||
<code dir="ltr" className="text-xs text-ink break-all">
|
||||
{tool.source_location || "—"}
|
||||
</code>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-[0.72rem] text-ink-muted uppercase mb-1">
|
||||
Parameters Schema
|
||||
</div>
|
||||
<pre
|
||||
dir="ltr"
|
||||
className="text-xs bg-rule-soft/40 border border-rule rounded-md p-3 overflow-x-auto"
|
||||
>
|
||||
{JSON.stringify(tool.params_schema, null, 2)}
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
);
|
||||
}
|
||||
@@ -1 +1,83 @@
|
||||
export function ToolsTab() { return <div>Tools tab — coming soon</div>; }
|
||||
"use client";
|
||||
|
||||
import { useState, useMemo } from "react";
|
||||
import { Wrench, AlertCircle } from "lucide-react";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { useMcpTools, type McpTool } from "@/lib/api/settings";
|
||||
import { ToolDetailDrawer } from "./tool-detail-drawer";
|
||||
|
||||
export function ToolsTab() {
|
||||
const { data, isPending, error } = useMcpTools();
|
||||
const [selected, setSelected] = useState<McpTool | null>(null);
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const grouped = useMemo(() => {
|
||||
if (!data?.tools) return new Map<string, McpTool[]>();
|
||||
const m = new Map<string, McpTool[]>();
|
||||
for (const t of data.tools) {
|
||||
const mod = t.module.split(".").pop() || "other";
|
||||
const arr = m.get(mod) ?? [];
|
||||
arr.push(t);
|
||||
m.set(mod, arr);
|
||||
}
|
||||
return m;
|
||||
}, [data]);
|
||||
|
||||
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>שגיאה בטעינת tools: {error.message}</span>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
if (!data) return null;
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-2 text-sm text-ink-muted">
|
||||
<Wrench className="w-4 h-4" />
|
||||
סה"כ {data.count} tools
|
||||
</div>
|
||||
{[...grouped.entries()].sort().map(([mod, tools]) => (
|
||||
<Card key={mod} className="bg-surface border-rule">
|
||||
<CardContent className="px-6 py-5">
|
||||
<h2 className="text-navy text-lg mb-3 flex items-center gap-2">
|
||||
<code dir="ltr">{mod}</code>
|
||||
<Badge variant="outline" className="text-[0.7rem]">
|
||||
{tools.length}
|
||||
</Badge>
|
||||
</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
|
||||
{tools.map((t) => (
|
||||
<button
|
||||
key={t.name}
|
||||
onClick={() => {
|
||||
setSelected(t);
|
||||
setOpen(true);
|
||||
}}
|
||||
className="text-start rounded-md border border-rule px-3 py-2 hover:bg-rule-soft/40 transition-colors"
|
||||
>
|
||||
<code dir="ltr" className="font-mono text-sm text-navy">
|
||||
{t.name}
|
||||
</code>
|
||||
{t.description && (
|
||||
<p className="text-[0.78rem] text-ink-muted mt-0.5 line-clamp-2">
|
||||
{t.description}
|
||||
</p>
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
<ToolDetailDrawer tool={selected} open={open} onOpenChange={setOpen} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user