Skip to main content
Glama
tool-modal.tsx8.89 kB
import { useState } from "react"; import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; import { Label } from "@/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Badge } from "@/components/ui/badge"; import { Card, CardContent } from "@/components/ui/card"; import { Play, X, Copy, Clock, CheckCircle2 } from "lucide-react"; import { useMutation } from "@tanstack/react-query"; import { apiRequest } from "@/lib/queryClient"; import { useToast } from "@/hooks/use-toast"; import LoadingSpinner from "@/components/ui/loading-spinner"; import ResultFormatter from "@/components/result-formatter"; interface Tool { name: string; description: string; category?: string; inputSchema: { type: "object"; properties: Record<string, any>; required?: string[]; }; } interface ToolModalProps { tool: Tool; onClose: () => void; } export default function ToolModal({ tool, onClose }: ToolModalProps) { const [parameters, setParameters] = useState<Record<string, any>>({}); const [showResults, setShowResults] = useState(false); const [executionResult, setExecutionResult] = useState<any>(null); const { toast } = useToast(); const executeToolMutation = useMutation({ mutationFn: async (params: { toolName: string; parameters: Record<string, any> }) => { const response = await apiRequest("POST", "/api/tools/execute", params); return response.json(); }, onSuccess: (data) => { setExecutionResult(data.result); setShowResults(true); toast({ title: "Success!", description: "Tool executed successfully.", }); }, onError: (error: any) => { toast({ title: "Execution Failed", description: error.message || "Failed to execute tool. Please try again.", variant: "destructive", }); }, }); const handleParameterChange = (key: string, value: any) => { setParameters(prev => ({ ...prev, [key]: value })); }; const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); executeToolMutation.mutate({ toolName: tool.name, parameters }); }; const copyResults = async () => { if (executionResult) { try { await navigator.clipboard.writeText(JSON.stringify(executionResult, null, 2)); toast({ title: "Copied!", description: "Results copied to clipboard.", }); } catch (error) { toast({ title: "Copy Failed", description: "Failed to copy results to clipboard.", variant: "destructive", }); } } }; const renderParameterField = (key: string, property: any) => { const isRequired = tool.inputSchema.required?.includes(key) || false; const description = property.description || `${key} parameter`; if (property.enum) { return ( <div key={key} className="space-y-2"> <Label htmlFor={key} className="flex items-center gap-2"> {key.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase())} {isRequired && <span className="text-destructive">*</span>} </Label> <Select onValueChange={(value) => handleParameterChange(key, value)}> <SelectTrigger> <SelectValue placeholder={`Select ${key}`} /> </SelectTrigger> <SelectContent> {property.enum.map((option: string) => ( <SelectItem key={option} value={option}> {option} </SelectItem> ))} </SelectContent> </Select> <p className="text-xs text-muted-foreground">{description}</p> </div> ); } if (property.type === 'string' && (key.includes('description') || key.includes('body') || key.includes('content'))) { return ( <div key={key} className="space-y-2"> <Label htmlFor={key} className="flex items-center gap-2"> {key.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase())} {isRequired && <span className="text-destructive">*</span>} </Label> <Textarea id={key} placeholder={description} value={parameters[key] || ''} onChange={(e) => handleParameterChange(key, e.target.value)} rows={3} required={isRequired} /> <p className="text-xs text-muted-foreground">{description}</p> </div> ); } return ( <div key={key} className="space-y-2"> <Label htmlFor={key} className="flex items-center gap-2"> {key.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase())} {isRequired && <span className="text-destructive">*</span>} </Label> <Input id={key} type="text" placeholder={description} value={parameters[key] || ''} onChange={(e) => handleParameterChange(key, e.target.value)} required={isRequired} /> </div> ); }; if (showResults) { return ( <Dialog open={true} onOpenChange={() => setShowResults(false)}> <DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto"> <DialogHeader> <DialogTitle className="flex items-center gap-3"> <div className="w-10 h-10 bg-gradient-to-r from-green-600 to-teal-600 rounded-lg flex items-center justify-center"> <CheckCircle2 className="h-5 w-5 text-white" /> </div> <div> <h3 className="text-xl font-semibold">Execution Results</h3> <p className="text-sm text-muted-foreground">Tool executed successfully</p> </div> </DialogTitle> </DialogHeader> <div className="space-y-4"> <div className="max-h-[60vh] overflow-y-auto"> <ResultFormatter result={executionResult} toolName={tool.name} /> </div> <div className="flex items-center justify-between pt-4 border-t border-border"> <div className="flex items-center text-sm text-muted-foreground"> <CheckCircle2 className="mr-2 h-4 w-4 text-green-500" /> <span>Executed successfully</span> </div> <div className="flex space-x-3"> <Button variant="outline" onClick={copyResults}> <Copy className="mr-2 h-4 w-4" /> Copy Raw </Button> <Button onClick={() => setShowResults(false)}> Close </Button> </div> </div> </div> </DialogContent> </Dialog> ); } return ( <Dialog open={true} onOpenChange={onClose}> <DialogContent className="max-w-2xl max-h-[90vh] overflow-y-auto"> <DialogHeader> <DialogTitle className="flex items-center gap-3"> <div className="w-10 h-10 bg-gradient-to-r from-purple-600 to-blue-600 rounded-lg flex items-center justify-center"> <Play className="h-5 w-5 text-white" /> </div> <div> <h3 className="text-xl font-semibold"> {tool.name.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase())} </h3> <p className="text-sm text-muted-foreground">{tool.description}</p> </div> </DialogTitle> </DialogHeader> <form onSubmit={handleSubmit} className="space-y-6"> <div className="space-y-4"> {Object.entries(tool.inputSchema.properties || {}).map(([key, property]) => renderParameterField(key, property) )} </div> <div className="flex items-center justify-between pt-4 border-t border-border"> <Button type="button" variant="outline" onClick={onClose}> Cancel </Button> <Button type="submit" disabled={executeToolMutation.isPending} className="bg-gradient-to-r from-purple-600 to-blue-600 hover:from-purple-700 hover:to-blue-700" > {executeToolMutation.isPending ? ( <> <LoadingSpinner className="mr-2 h-4 w-4" /> Executing... </> ) : ( <> <Play className="mr-2 h-4 w-4" /> Execute Tool </> )} </Button> </div> </form> </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/Rohitkumar0056/GitHub-MCP'

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