Skip to main content
Glama
ToolCallDetailDialog.jsx11.6 kB
import { useState } from 'react' import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog' import { Button } from '@/components/ui/button' import { Badge } from '@/components/ui/badge' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' import { ScrollArea } from '@/components/ui/scroll-area' import { Copy, Clock, AlertCircle, CheckCircle, Calendar, User, Settings } from 'lucide-react' import { useToast } from '@/hooks/use-toast' export default function ToolCallDetailDialog({ toolCall, isOpen, onOpenChange }) { const { toast } = useToast() if (!toolCall) return null const formatDate = (dateString) => { return new Date(dateString).toLocaleString() } const formatDuration = (ms) => { if (ms === null || ms === undefined) return 'N/A' if (ms === 0) return '<1ms' if (ms < 1000) return `${ms}ms` return `${(ms / 1000).toFixed(1)}s` } const copyToClipboard = async (text, description) => { try { await navigator.clipboard.writeText(text) toast({ title: "Copied to clipboard", description: description, }) } catch (error) { toast({ title: "Failed to copy", description: "Could not copy to clipboard.", variant: "destructive", }) } } const formatJsonData = (data) => { if (!data) return 'null' return JSON.stringify(data, null, 2) } const renderJsonData = (data, title) => { if (!data) { return ( <div className="text-muted-foreground text-center py-4"> No {title.toLowerCase()} data </div> ) } const jsonString = formatJsonData(data) return ( <div className="space-y-3"> <div className="flex items-center justify-between"> <h4 className="text-sm font-medium">{title}</h4> <Button variant="outline" size="sm" onClick={() => copyToClipboard(jsonString, `${title} copied to clipboard`)} > <Copy className="h-3 w-3 mr-1" /> Copy </Button> </div> <div className="relative"> <ScrollArea className="h-48 w-full rounded-md border"> <pre className="text-xs p-4 font-mono whitespace-pre-wrap break-words"> {jsonString} </pre> </ScrollArea> </div> </div> ) } const renderOutputData = () => { if (toolCall.error_message) { return ( <div className="text-muted-foreground text-center py-4"> No output data due to error </div> ) } // Handle JSON output (structured data) if (toolCall.output_json) { return ( <div className="space-y-3"> <div className="flex items-center justify-between"> <h4 className="text-sm font-medium flex items-center"> <span className="bg-blue-100 text-blue-800 px-2 py-1 rounded text-xs mr-2">JSON</span> Structured Output Data </h4> <Button variant="outline" size="sm" onClick={() => copyToClipboard(formatJsonData(toolCall.output_json), "JSON output copied to clipboard")} > <Copy className="h-3 w-3 mr-1" /> Copy </Button> </div> <div className="relative"> <ScrollArea className="h-48 w-full rounded-md border"> <pre className="text-xs p-4 font-mono whitespace-pre-wrap break-words"> {formatJsonData(toolCall.output_json)} </pre> </ScrollArea> </div> </div> ) } // Handle text output (extract text from content array) if (toolCall.output_text) { // Extract text content from the content array let textContent = "" if (Array.isArray(toolCall.output_text)) { // Find text content in the array const textItem = toolCall.output_text.find(item => item.type === "text") textContent = textItem ? textItem.text : formatJsonData(toolCall.output_text) } else { textContent = formatJsonData(toolCall.output_text) } return ( <div className="space-y-3"> <div className="flex items-center justify-between"> <h4 className="text-sm font-medium flex items-center"> <span className="bg-gray-100 text-gray-800 px-2 py-1 rounded text-xs mr-2">TEXT</span> Text Output Data </h4> <Button variant="outline" size="sm" onClick={() => copyToClipboard(textContent, "Text output copied to clipboard")} > <Copy className="h-3 w-3 mr-1" /> Copy </Button> </div> <div className="relative"> <ScrollArea className="h-48 w-full rounded-md border"> <pre className="text-xs p-4 font-mono whitespace-pre-wrap break-words"> {textContent} </pre> </ScrollArea> </div> </div> ) } return ( <div className="text-muted-foreground text-center py-4"> No output data </div> ) } return ( <Dialog open={isOpen} onOpenChange={onOpenChange}> <DialogContent className="w-[95vw] max-w-4xl max-h-[90vh] overflow-hidden flex flex-col"> <DialogHeader className="flex-shrink-0"> <DialogTitle className="flex items-center space-x-2"> <Settings className="h-5 w-5" /> <span>Tool Call Details</span> </DialogTitle> <DialogDescription> Detailed information about tool execution #{toolCall.id} </DialogDescription> </DialogHeader> <div className="flex-1 overflow-y-auto pr-2"> <div className="space-y-6 pb-6"> {/* Overview Cards */} <div className="grid grid-cols-1 md:grid-cols-3 gap-4"> {/* Status Card */} <Card> <CardHeader className="pb-2"> <CardTitle className="text-sm font-medium">Status</CardTitle> </CardHeader> <CardContent> {toolCall.error_message ? ( <Badge variant="destructive" className="flex items-center w-fit"> <AlertCircle className="mr-1 h-3 w-3" /> Failed </Badge> ) : ( <Badge variant="default" className="flex items-center w-fit bg-green-100 text-green-800"> <CheckCircle className="mr-1 h-3 w-3" /> Success </Badge> )} </CardContent> </Card> {/* Execution Time Card */} <Card> <CardHeader className="pb-2"> <CardTitle className="text-sm font-medium flex items-center"> <Clock className="mr-1 h-4 w-4" /> Duration </CardTitle> </CardHeader> <CardContent> <div className="text-lg font-semibold"> {formatDuration(toolCall.execution_time_ms)} </div> </CardContent> </Card> {/* Timestamp Card */} <Card> <CardHeader className="pb-2"> <CardTitle className="text-sm font-medium flex items-center"> <Calendar className="mr-1 h-4 w-4" /> Executed </CardTitle> </CardHeader> <CardContent> <div className="text-sm"> {formatDate(toolCall.created_at)} </div> </CardContent> </Card> </div> {/* Tool Information */} <Card> <CardHeader> <CardTitle className="text-base">Tool Information</CardTitle> </CardHeader> <CardContent className="space-y-3"> <div className="grid grid-cols-1 md:grid-cols-2 gap-4"> <div> <label className="text-sm font-medium text-muted-foreground">Tool Name</label> <div className="mt-1"> <Badge variant="outline" className="text-sm"> {toolCall.tool_name} </Badge> </div> </div> <div> <label className="text-sm font-medium text-muted-foreground">Client ID</label> <div className="mt-1 font-mono text-sm"> {toolCall.client_id} </div> </div> </div> </CardContent> </Card> {/* Data Tabs */} <Card> <CardHeader> <CardTitle className="text-base">Execution Data</CardTitle> </CardHeader> <CardContent> <Tabs defaultValue="input" className="w-full"> <TabsList className="grid w-full grid-cols-3"> <TabsTrigger value="input">Input Data</TabsTrigger> <TabsTrigger value="output" disabled={!toolCall.output_json && !toolCall.output_text}> Output Data </TabsTrigger> <TabsTrigger value="error" disabled={!toolCall.error_message}> Error Details </TabsTrigger> </TabsList> <TabsContent value="input" className="mt-4"> {renderJsonData(toolCall.input_data, "Input Data")} </TabsContent> <TabsContent value="output" className="mt-4"> {renderOutputData()} </TabsContent> <TabsContent value="error" className="mt-4"> {toolCall.error_message ? ( <div className="space-y-3"> <div className="flex items-center justify-between"> <h4 className="text-sm font-medium">Error Message</h4> <Button variant="outline" size="sm" onClick={() => copyToClipboard(toolCall.error_message, "Error message copied to clipboard")} > <Copy className="h-3 w-3 mr-1" /> Copy </Button> </div> <Card className="border-destructive/20 bg-destructive/5"> <CardContent className="p-4"> <ScrollArea className="h-24 w-full"> <pre className="text-sm text-destructive whitespace-pre-wrap break-words"> {toolCall.error_message} </pre> </ScrollArea> </CardContent> </Card> </div> ) : ( <div className="text-muted-foreground text-center py-4"> No error information </div> )} </TabsContent> </Tabs> </CardContent> </Card> </div> </div> </DialogContent> </Dialog> ) }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/GeorgeStrakhov/mcpeasy'

If you have feedback or need assistance with the MCP directory API, please join our Discord server