Skip to main content
Glama
result-formatter.tsx19.6 kB
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Separator } from "@/components/ui/separator"; import { CheckCircle, AlertCircle, FileText, Code, User, Calendar, GitBranch, Hash, ExternalLink, Star, GitFork, Eye, Clock, AlertTriangle, Zap, MessageSquare, GitCommit, GitPullRequest, Bug } from "lucide-react"; interface ResultFormatterProps { result: any; toolName: string; } export default function ResultFormatter({ result, toolName }: ResultFormatterProps) { // Extract content from MCP response structure const getContent = () => { if (result?.content && Array.isArray(result.content)) { return result.content; } return [{ text: JSON.stringify(result, null, 2) }]; }; const content = getContent(); // Helper function to parse JSON content const parseContent = (text: string) => { try { return JSON.parse(text); } catch { return text; } }; // Helper function to format dates const formatDate = (dateString: string) => { if (!dateString) return ''; const date = new Date(dateString); return date.toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }); }; // Helper function to truncate text const truncateText = (text: string, maxLength: number = 100) => { if (!text || text.length <= maxLength) return text; return text.substring(0, maxLength) + '...'; }; // Format arrays of data const formatArray = (data: any[], type: string) => { if (!Array.isArray(data) || data.length === 0) { return ( <Card className="bg-muted/30"> <CardContent className="pt-6"> <p className="text-sm text-muted-foreground text-center">No {type} found</p> </CardContent> </Card> ); } return ( <div className="space-y-3"> <h3 className="text-lg font-semibold flex items-center gap-2"> <Badge variant="secondary">{data.length}</Badge> {type.charAt(0).toUpperCase() + type.slice(1)} </h3> {data.map((item: any, index: number) => formatSingleItem(item, type, index))} </div> ); }; // Format individual items based on their type const formatSingleItem = (data: any, type: string = '', index: number = 0) => { // Repository if (data.full_name && data.owner) { return ( <Card key={index} className="bg-muted/30 hover:bg-muted/40 transition-colors"> <CardHeader className="pb-3"> <div className="flex items-start justify-between"> <div 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"> <GitBranch className="h-5 w-5 text-white" /> </div> <div> <CardTitle className="text-base">{data.name}</CardTitle> <p className="text-sm text-muted-foreground">{data.full_name}</p> </div> </div> <div className="flex gap-2"> {data.private && <Badge variant="destructive">Private</Badge>} {data.fork && <Badge variant="outline">Fork</Badge>} {data.archived && <Badge variant="secondary">Archived</Badge>} </div> </div> </CardHeader> <CardContent className="space-y-3"> {data.description && ( <p className="text-sm text-muted-foreground">{data.description}</p> )} <div className="flex flex-wrap gap-2"> {data.language && <Badge variant="default">{data.language}</Badge>} {data.topics && data.topics.slice(0, 3).map((topic: string, i: number) => ( <Badge key={i} variant="outline" className="text-xs">{topic}</Badge> ))} </div> <div className="grid grid-cols-2 sm:grid-cols-4 gap-3 text-sm"> <div className="flex items-center gap-2"> <Star className="h-4 w-4 text-yellow-500" /> <span className="font-medium">{data.stargazers_count || 0}</span> </div> <div className="flex items-center gap-2"> <GitFork className="h-4 w-4 text-blue-500" /> <span className="font-medium">{data.forks_count || 0}</span> </div> <div className="flex items-center gap-2"> <Eye className="h-4 w-4 text-green-500" /> <span className="font-medium">{data.watchers_count || 0}</span> </div> <div className="flex items-center gap-2"> <AlertTriangle className="h-4 w-4 text-red-500" /> <span className="font-medium">{data.open_issues_count || 0}</span> </div> </div> {(data.updated_at || data.pushed_at) && ( <p className="text-xs text-muted-foreground"> Updated: {formatDate(data.updated_at || data.pushed_at)} </p> )} {data.html_url && ( <a href={data.html_url} target="_blank" rel="noopener noreferrer" className="inline-flex items-center gap-2 text-primary hover:text-primary/80 text-sm"> <ExternalLink className="h-4 w-4" /> View Repository </a> )} </CardContent> </Card> ); } // User/Organization if (data.login && (data.type === 'User' || data.type === 'Organization')) { return ( <Card key={index} className="bg-muted/30 hover:bg-muted/40 transition-colors"> <CardHeader className="pb-3"> <div className="flex items-center gap-3"> <div className="w-12 h-12 bg-gradient-to-r from-purple-600 to-blue-600 rounded-full flex items-center justify-center"> <User className="h-6 w-6 text-white" /> </div> <div> <CardTitle className="text-lg">{data.name || data.login}</CardTitle> <p className="text-sm text-muted-foreground">@{data.login}</p> {data.type && <Badge variant="outline" className="text-xs">{data.type}</Badge>} </div> </div> </CardHeader> <CardContent className="space-y-3"> {data.bio && <p className="text-sm text-muted-foreground">{data.bio}</p>} <div className="grid grid-cols-2 gap-4 text-sm"> {data.public_repos !== undefined && ( <div className="flex items-center gap-2"> <GitBranch className="h-4 w-4 text-muted-foreground" /> <span>{data.public_repos} repositories</span> </div> )} {data.followers !== undefined && ( <div className="flex items-center gap-2"> <User className="h-4 w-4 text-muted-foreground" /> <span>{data.followers} followers</span> </div> )} {data.company && ( <div className="flex items-center gap-2"> <Hash className="h-4 w-4 text-muted-foreground" /> <span>{data.company}</span> </div> )} {data.location && ( <div className="flex items-center gap-2"> <Calendar className="h-4 w-4 text-muted-foreground" /> <span>{data.location}</span> </div> )} </div> {data.html_url && ( <a href={data.html_url} target="_blank" rel="noopener noreferrer" className="inline-flex items-center gap-2 text-primary hover:text-primary/80 text-sm"> <ExternalLink className="h-4 w-4" /> View Profile </a> )} </CardContent> </Card> ); } // Issue or Pull Request if (data.number && (data.title || data.pull_request !== undefined)) { const isPR = data.pull_request !== undefined; return ( <Card key={index} className="bg-muted/30 hover:bg-muted/40 transition-colors"> <CardHeader className="pb-3"> <div className="flex items-start justify-between"> <div className="flex items-start gap-3"> <div className={`w-10 h-10 rounded-lg flex items-center justify-center ${ isPR ? 'bg-gradient-to-r from-purple-600 to-blue-600' : 'bg-gradient-to-r from-green-600 to-teal-600' }`}> {isPR ? <GitPullRequest className="h-5 w-5 text-white" /> : <Bug className="h-5 w-5 text-white" />} </div> <div className="flex-1"> <CardTitle className="text-base">{truncateText(data.title, 80)}</CardTitle> <p className="text-sm text-muted-foreground">#{data.number}</p> </div> </div> <div className="flex gap-2"> <Badge variant={data.state === 'open' ? 'default' : data.state === 'closed' ? 'destructive' : 'secondary'}> {data.state} </Badge> {isPR && <Badge variant="outline">PR</Badge>} </div> </div> </CardHeader> <CardContent className="space-y-3"> {data.body && ( <p className="text-sm text-muted-foreground">{truncateText(data.body, 150)}</p> )} <div className="flex flex-wrap gap-2"> {data.labels && data.labels.slice(0, 3).map((label: any, i: number) => ( <Badge key={i} variant="outline" className="text-xs" style={{backgroundColor: `#${label.color}20`}}> {label.name} </Badge> ))} </div> <div className="grid grid-cols-2 gap-4 text-sm"> {data.user && ( <div className="flex items-center gap-2"> <User className="h-4 w-4 text-muted-foreground" /> <span>{data.user.login}</span> </div> )} {data.created_at && ( <div className="flex items-center gap-2"> <Clock className="h-4 w-4 text-muted-foreground" /> <span>{formatDate(data.created_at)}</span> </div> )} {data.comments !== undefined && ( <div className="flex items-center gap-2"> <MessageSquare className="h-4 w-4 text-muted-foreground" /> <span>{data.comments} comments</span> </div> )} {data.assignee && ( <div className="flex items-center gap-2"> <User className="h-4 w-4 text-muted-foreground" /> <span>@{data.assignee.login}</span> </div> )} </div> {data.html_url && ( <a href={data.html_url} target="_blank" rel="noopener noreferrer" className="inline-flex items-center gap-2 text-primary hover:text-primary/80 text-sm"> <ExternalLink className="h-4 w-4" /> View {isPR ? 'Pull Request' : 'Issue'} </a> )} </CardContent> </Card> ); } // Commit if (data.sha && data.commit) { return ( <Card key={index} className="bg-muted/30 hover:bg-muted/40 transition-colors"> <CardHeader className="pb-3"> <div className="flex items-start gap-3"> <div className="w-10 h-10 bg-gradient-to-r from-orange-600 to-red-600 rounded-lg flex items-center justify-center"> <GitCommit className="h-5 w-5 text-white" /> </div> <div className="flex-1"> <CardTitle className="text-base">{truncateText(data.commit.message, 80)}</CardTitle> <p className="text-sm text-muted-foreground font-mono">{data.sha.substring(0, 7)}</p> </div> </div> </CardHeader> <CardContent className="space-y-3"> <div className="grid grid-cols-2 gap-4 text-sm"> {data.commit.author && ( <div className="flex items-center gap-2"> <User className="h-4 w-4 text-muted-foreground" /> <span>{data.commit.author.name}</span> </div> )} {data.commit.author?.date && ( <div className="flex items-center gap-2"> <Clock className="h-4 w-4 text-muted-foreground" /> <span>{formatDate(data.commit.author.date)}</span> </div> )} </div> {data.stats && ( <div className="flex gap-4 text-sm"> <span className="text-green-600">+{data.stats.additions}</span> <span className="text-red-600">-{data.stats.deletions}</span> <span className="text-muted-foreground">{data.stats.total} changes</span> </div> )} {data.html_url && ( <a href={data.html_url} target="_blank" rel="noopener noreferrer" className="inline-flex items-center gap-2 text-primary hover:text-primary/80 text-sm"> <ExternalLink className="h-4 w-4" /> View Commit </a> )} </CardContent> </Card> ); } // Generic object formatting with better structure if (typeof data === 'object' && data !== null) { const entries = Object.entries(data).filter(([key, value]) => value !== null && value !== undefined && value !== '' ); return ( <Card key={index} className="bg-muted/30"> <CardHeader className="pb-3"> <CardTitle className="text-base flex items-center gap-2"> <Code className="h-4 w-4" /> {type || 'Data'} </CardTitle> </CardHeader> <CardContent className="space-y-2"> {entries.map(([key, value]) => ( <div key={key} className="flex justify-between items-start gap-4 py-1"> <span className="text-sm font-medium text-muted-foreground min-w-fit"> {key.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase())}: </span> <span className="text-sm text-right flex-1"> {typeof value === 'boolean' ? ( <Badge variant={value ? 'default' : 'secondary'}> {value ? 'Yes' : 'No'} </Badge> ) : typeof value === 'object' ? ( <code className="text-xs bg-muted px-2 py-1 rounded"> {JSON.stringify(value, null, 2)} </code> ) : key.includes('url') || key.includes('_url') ? ( <a href={String(value)} target="_blank" rel="noopener noreferrer" className="text-primary hover:text-primary/80 text-xs"> <ExternalLink className="h-3 w-3 inline" /> </a> ) : key.includes('date') || key.includes('_at') ? ( formatDate(String(value)) ) : ( truncateText(String(value), 50) )} </span> </div> ))} </CardContent> </Card> ); } return null; }; // Main formatting function const formatGitHubContent = (data: any) => { if (typeof data === 'string') { const parsed = parseContent(data); if (typeof parsed === 'object') { data = parsed; } } // Handle arrays (repositories, issues, commits, etc.) if (Array.isArray(data)) { // Determine the type based on array content if (data.length > 0) { const firstItem = data[0]; let type = 'items'; if (firstItem.full_name && firstItem.owner) type = 'repositories'; else if (firstItem.login) type = 'users'; else if (firstItem.number && firstItem.title) type = 'issues'; else if (firstItem.sha && firstItem.commit) type = 'commits'; else if (firstItem.name && firstItem.path) type = 'files'; return formatArray(data, type); } } // Handle single objects const formatted = formatSingleItem(data); if (formatted) return formatted; return null; }; return ( <div className="space-y-4"> {content.map((item: any, index: number) => { const text = item.text || item; const parsedData = parseContent(text); // Check if this is GitHub-related data const isGitHubData = toolName.includes('github') || toolName.includes('repo') || toolName.includes('user'); if (isGitHubData && (typeof parsedData === 'object' || Array.isArray(parsedData))) { const formatted = formatGitHubContent(parsedData); if (formatted) { return <div key={index}>{formatted}</div>; } } // For text responses, format them nicely if (typeof text === 'string') { // Try to detect if it's structured data if (text.includes('{') && text.includes('}')) { try { const jsonData = JSON.parse(text); const formatted = formatGitHubContent(jsonData); if (formatted) { return <div key={index}>{formatted}</div>; } } catch { // Fall through to plain text formatting } } return ( <Card key={index} className="bg-muted/30"> <CardContent className="pt-6"> <div className="flex items-start gap-3"> <div className="w-8 h-8 bg-gradient-to-r from-blue-600 to-cyan-600 rounded-lg flex items-center justify-center flex-shrink-0"> <FileText className="h-4 w-4 text-white" /> </div> <div className="flex-1"> <pre className="text-sm whitespace-pre-wrap text-foreground font-mono bg-muted/50 p-3 rounded-md overflow-x-auto"> {text} </pre> </div> </div> </CardContent> </Card> ); } // Generic object display with better formatting return ( <Card key={index} className="bg-muted/30"> <CardContent className="pt-6"> <div className="flex items-start gap-3"> <div className="w-8 h-8 bg-gradient-to-r from-purple-600 to-blue-600 rounded-lg flex items-center justify-center flex-shrink-0"> <Code className="h-4 w-4 text-white" /> </div> <div className="flex-1"> <pre className="text-sm whitespace-pre-wrap overflow-x-auto text-foreground font-mono bg-muted/50 p-3 rounded-md"> {JSON.stringify(parsedData, null, 2)} </pre> </div> </div> </CardContent> </Card> ); })} </div> ); }

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