feat(ui): view_logs/ToolsSection to shadcn

- ToolsSection: antd Collapse \u2192 shadcn Accordion (single-collapsible).
  Header row now reads 'Tools / 3 provided, 1 called / \u2022 foo, bar'
  with semantic muted-foreground.
- ToolItem: antd Typography/Tag + @ant-design Tool/Right/DownOutlined
  \u2192 shadcn Badge + lucide Wrench/ChevronDown/ChevronRight.
  Expand-click toggles a bg-muted highlight row. File added to
  overrides for the categorical blue 'called' badge.
- ToolExpandedContent: antd Radio.Group \u2192 shadcn ToggleGroup for the
  Formatted / JSON switch. Label uses text-muted-foreground.

Gates: TS \u2713 | Build \u2713.

Co-authored-by: yuneng-jiang <yuneng-berri@users.noreply.github.com>
This commit is contained in:
Cursor Agent 2026-04-23 11:34:30 +00:00
parent 55ca23278a
commit dbca20ce4b
No known key found for this signature in database
4 changed files with 75 additions and 106 deletions

View file

@ -55,6 +55,7 @@
"src/components/permissions/AgentPermissions.tsx",
"src/components/permissions/MCPServerPermissions.tsx",
"src/components/survey/ClaudeCodeModal.tsx",
"src/components/view_logs/ToolsSection/ToolItem.tsx",
"src/components/policies/impact_preview_alert.tsx",
"src/components/policies/impact_popover.tsx",
"src/components/DeletedKeysPage/DeletedKeysPage.tsx",

View file

@ -1,15 +1,12 @@
/**
* Expanded content for a tool with view mode toggle
*/
import { useState } from "react";
import { Typography, Radio } from "antd";
import {
ToggleGroup,
ToggleGroupItem,
} from "@/components/ui/toggle-group";
import { ParsedTool } from "./types";
import { FormattedToolView } from "./FormattedToolView";
import { JsonToolView } from "./JsonToolView";
const { Text } = Typography;
type ViewMode = "formatted" | "json";
interface ToolExpandedContentProps {
@ -21,25 +18,20 @@ export function ToolExpandedContent({ tool }: ToolExpandedContentProps) {
return (
<div>
{/* View Mode Toggle - Top Right */}
<div
style={{
display: "flex",
justifyContent: "space-between",
marginBottom: 12,
}}
>
<Text type="secondary" style={{ fontSize: 12 }}>
Description
</Text>
<Radio.Group
size="small"
<div className="flex justify-between mb-3">
<span className="text-xs text-muted-foreground">Description</span>
<ToggleGroup
type="single"
size="sm"
value={viewMode}
onChange={(e) => setViewMode(e.target.value)}
onValueChange={(v) => {
if (!v) return;
setViewMode(v as ViewMode);
}}
>
<Radio.Button value="formatted">Formatted</Radio.Button>
<Radio.Button value="json">JSON</Radio.Button>
</Radio.Group>
<ToggleGroupItem value="formatted">Formatted</ToggleGroupItem>
<ToggleGroupItem value="json">JSON</ToggleGroupItem>
</ToggleGroup>
</div>
{viewMode === "formatted" ? (

View file

@ -1,15 +1,10 @@
/**
* Individual tool item component with expandable details
*/
import { useState } from "react";
import { Typography, Tag } from "antd";
import { ToolOutlined, RightOutlined, DownOutlined } from "@ant-design/icons";
import { Badge } from "@/components/ui/badge";
import { ChevronDown, ChevronRight, Wrench } from "lucide-react";
import { cn } from "@/lib/utils";
import { ParsedTool } from "./types";
import { ToolExpandedContent } from "./ToolExpandedContent";
const { Text } = Typography;
interface ToolItemProps {
tool: ParsedTool;
}
@ -18,54 +13,42 @@ export function ToolItem({ tool }: ToolItemProps) {
const [expanded, setExpanded] = useState(false);
return (
<div
style={{
border: "1px solid #f0f0f0",
borderRadius: 8,
overflow: "hidden",
}}
>
{/* Header Row - Always Visible */}
<div className="border border-border rounded-lg overflow-hidden">
<div
onClick={() => setExpanded(!expanded)}
style={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
padding: "12px 16px",
cursor: "pointer",
background: expanded ? "#fafafa" : "#fff",
transition: "background 0.2s",
}}
className={cn(
"flex items-center justify-between px-4 py-3 cursor-pointer transition-colors",
expanded ? "bg-muted" : "bg-background",
)}
>
<div style={{ display: "flex", alignItems: "center", gap: 10 }}>
<ToolOutlined style={{ color: "#8c8c8c", fontSize: 14 }} />
<Text style={{ fontSize: 14 }}>
<div className="flex items-center gap-2.5">
<Wrench className="h-3.5 w-3.5 text-muted-foreground" />
<span className="text-sm">
{tool.index}. {tool.name}
</Text>
</span>
</div>
<div style={{ display: "flex", alignItems: "center", gap: 8 }}>
<Tag color={tool.called ? "blue" : "default"}>
<div className="flex items-center gap-2">
<Badge
className={cn(
"text-xs",
tool.called
? "bg-blue-100 text-blue-700 dark:bg-blue-950 dark:text-blue-300"
: "bg-muted text-muted-foreground",
)}
>
{tool.called ? "called" : "not called"}
</Tag>
</Badge>
{expanded ? (
<DownOutlined style={{ fontSize: 12, color: "#8c8c8c" }} />
<ChevronDown className="h-3 w-3 text-muted-foreground" />
) : (
<RightOutlined style={{ fontSize: 12, color: "#8c8c8c" }} />
<ChevronRight className="h-3 w-3 text-muted-foreground" />
)}
</div>
</div>
{/* Expanded Content */}
{expanded && (
<div
style={{
padding: "16px",
borderTop: "1px solid #f0f0f0",
background: "#fff",
}}
>
<div className="p-4 border-t border-border bg-background">
<ToolExpandedContent tool={tool} />
</div>
)}

View file

@ -1,15 +1,13 @@
/**
* Tools section component that displays all available tools from the request
* and indicates which ones were actually called in the response
*/
import { Collapse, Typography } from "antd";
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/components/ui/accordion";
import { LogEntry } from "../columns";
import { parseToolsFromLog } from "./utils";
import { ToolItem } from "./ToolItem";
const { Text } = Typography;
interface ToolsSectionProps {
log: LogEntry;
}
@ -17,14 +15,11 @@ interface ToolsSectionProps {
export function ToolsSection({ log }: ToolsSectionProps) {
const tools = parseToolsFromLog(log);
// Don't render if no tools
if (tools.length === 0) return null;
// Calculate summary stats
const totalTools = tools.length;
const calledTools = tools.filter((t) => t.called).length;
// Get preview of first 2 tool names
const toolNamePreview = tools
.slice(0, 2)
.map((t) => t.name)
@ -32,34 +27,32 @@ export function ToolsSection({ log }: ToolsSectionProps) {
const hasMoreTools = tools.length > 2;
return (
<div className="bg-white rounded-lg shadow w-full max-w-full overflow-hidden mb-6">
<Collapse
expandIconPosition="start"
items={[
{
key: "1",
label: (
<div style={{ display: "flex", alignItems: "center", gap: 12, flexWrap: "wrap" }}>
<h3 className="text-lg font-medium text-gray-900">Tools</h3>
<Text type="secondary" style={{ fontSize: 14 }}>
{totalTools} provided, {calledTools} called
</Text>
<Text type="secondary" style={{ fontSize: 14 }}>
• {toolNamePreview}
{hasMoreTools && "..."}
</Text>
</div>
),
children: (
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
{tools.map((tool) => (
<ToolItem key={tool.name} tool={tool} />
))}
</div>
),
},
]}
/>
<div className="bg-background rounded-lg shadow w-full max-w-full overflow-hidden mb-6">
<Accordion type="single" collapsible>
<AccordionItem value="tools">
<AccordionTrigger>
<div className="flex items-center gap-3 flex-wrap pr-2">
<h3 className="text-lg font-medium text-foreground m-0">
Tools
</h3>
<span className="text-sm text-muted-foreground">
{totalTools} provided, {calledTools} called
</span>
<span className="text-sm text-muted-foreground">
• {toolNamePreview}
{hasMoreTools && "..."}
</span>
</div>
</AccordionTrigger>
<AccordionContent>
<div className="flex flex-col gap-2">
{tools.map((tool) => (
<ToolItem key={tool.name} tool={tool} />
))}
</div>
</AccordionContent>
</AccordionItem>
</Accordion>
</div>
);
}