Skip to main content
Glama
MiniStepCard.tsx11.8 kB
import { Card } from '@/src/components/ui/card'; import { cn, getIntegrationIcon, getSimpleIcon } from '@/src/lib/general-utils'; import { Integration } from '@superglue/client'; import { FileJson, FilePlay, Globe, OctagonAlert, RotateCw } from 'lucide-react'; import React from 'react'; const ACTIVE_CARD_STYLE = "ring-1 shadow-lg" as const; const ACTIVE_CARD_INLINE_STYLE = { borderColor: '#FFA500', boxShadow: '0 10px 15px -3px rgba(255, 165, 0, 0.1), 0 4px 6px -4px rgba(255, 165, 0, 0.1), 0 0 0 1px #FFA500' }; const getStatusInfo = (isRunning: boolean, isFailed: boolean, isCompleted: boolean) => { if (isRunning) return { text: "Running", color: "text-amber-600 dark:text-amber-400", dotColor: "bg-amber-600 dark:bg-amber-400", animate: true }; if (isFailed) return { text: "Failed", color: "text-red-600 dark:text-red-400", dotColor: "bg-red-600 dark:bg-red-400", animate: false }; if (isCompleted) return { text: "Completed", color: "text-muted-foreground", dotColor: "bg-green-600 dark:bg-green-400", animate: false }; return { text: "Pending", color: "text-gray-500 dark:text-gray-400", dotColor: "bg-gray-500 dark:bg-gray-400", animate: false }; }; export const MiniStepCard = React.memo(({ step, index, isActive, onClick, stepId, isPayload = false, isTransform = false, isRunningAll = false, isTesting = false, completedSteps = [], failedSteps = [], isFirstCard = false, isLastCard = false, integrations = [], hasTransformCompleted = false, isPayloadValid = true, payloadData }: { step: any; index: number; isActive: boolean; onClick: () => void; stepId?: string | null; isPayload?: boolean; isTransform?: boolean; isRunningAll?: boolean; isTesting?: boolean; completedSteps?: string[]; failedSteps?: string[]; isFirstCard?: boolean; isLastCard?: boolean; integrations?: Integration[]; hasTransformCompleted?: boolean; isPayloadValid?: boolean; payloadData?: any; }) => { if (isPayload) { return ( <div className={cn("cursor-pointer transition-all duration-300 ease-out transform flex items-center", "opacity-90 hover:opacity-100 hover:scale-[1.01]")} onClick={onClick} style={{ height: '100%' }}> <Card className={cn( "w-[180px] h-[110px] flex-shrink-0", isActive ? "pt-3 px-3 pb-3" : "pt-3 px-3 pb-[18px]", isActive && ACTIVE_CARD_STYLE, isFirstCard && "rounded-l-2xl bg-gradient-to-br from-primary/5 to-transparent" )} style={isActive ? ACTIVE_CARD_INLINE_STYLE : undefined} > <div className="h-[88px] flex flex-col items-center justify-between leading-tight"> <div className="flex-1 flex flex-col items-center justify-center"> <div className={cn( "p-2 rounded-full", !isPayloadValid ? "bg-amber-500/20" : "bg-primary/10" )}> {!isPayloadValid ? ( <svg className="h-4 w-4 text-amber-600 dark:text-amber-400" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"> <path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" /> <line x1="12" y1="9" x2="12" y2="13" /> <line x1="12" y1="17" x2="12.01" y2="17" /> </svg> ) : ( <FileJson className="h-4 w-4 text-primary" /> )} </div> <span className="text-[11px] font-semibold mt-1.5">Tool Input</span> <span className="text-[9px] text-muted-foreground">Add payload</span> </div> <div className="flex items-center gap-1 mt-1"> {!isPayloadValid ? ( <span className="text-[10px] font-semibold text-amber-600 dark:text-amber-400 flex items-center gap-1"> <span className="w-1.5 h-1.5 rounded-full bg-amber-600 dark:bg-amber-400 animate-pulse" /> Input Required </span> ) : (() => { const isEmptyPayload = !payloadData || (typeof payloadData === 'object' && Object.keys(payloadData).length === 0) || (typeof payloadData === 'string' && (!payloadData.trim() || payloadData.trim() === '{}')); if (isEmptyPayload) { return <span className="text-[9px] font-medium text-muted-foreground">No Input</span>; } else { return <span className="text-[9px] font-medium text-muted-foreground">Input Provided</span>; } })()} </div> </div> </Card> </div> ); } if (isTransform) { const isCompleted = completedSteps.includes('__final_transform__'); const isFailed = failedSteps.includes('__final_transform__'); const isRunning = isTesting || isRunningAll; const statusInfo = getStatusInfo(isRunning, isFailed, isCompleted); return ( <div className={cn("cursor-pointer transition-all duration-300 ease-out transform", "opacity-90 hover:opacity-100 hover:scale-[1.01]")} onClick={onClick} style={{ height: '100%' }}> <Card className={cn( "w-[180px] h-[110px] flex-shrink-0", isActive ? "pt-3 px-3 pb-3" : "pt-3 px-3 pb-[18px]", isActive && ACTIVE_CARD_STYLE, isLastCard && !hasTransformCompleted && "rounded-r-2xl bg-gradient-to-bl from-purple-500/5 to-transparent" )} style={isActive ? ACTIVE_CARD_INLINE_STYLE : undefined} > <div className="h-[88px] flex flex-col items-center justify-between leading-tight"> <div className="flex-1 flex flex-col items-center justify-center"> <div className="p-2 rounded-full bg-primary/10"> <FilePlay className="h-4 w-4 text-primary" /> </div> <span className="text-[11px] font-semibold mt-1.5">Tool Result</span> <span className="text-[9px] text-muted-foreground">Transform</span> </div> <div className="flex items-center gap-1 mt-1"> <span className={cn("text-[10px] font-semibold flex items-center gap-1.5", statusInfo.color)}> <span className={cn( "w-2 h-2 rounded-full", statusInfo.dotColor, statusInfo.animate && "animate-pulse" )} /> {statusInfo.text} </span> </div> </div> </Card> </div> ); } const isCompleted = stepId ? completedSteps.includes(stepId) : false; const isFailed = stepId ? failedSteps.includes(stepId) : false; const isRunning = isTesting || (isRunningAll && !!stepId); const statusInfo = getStatusInfo(isRunning, isFailed, isCompleted); const linkedIntegration = step.integrationId && integrations ? integrations.find(integration => integration.id === step.integrationId) : undefined; const iconName = linkedIntegration ? getIntegrationIcon(linkedIntegration) : null; const simpleIcon = iconName ? getSimpleIcon(iconName) : null; return ( <div className={cn("cursor-pointer transition-all duration-300 ease-out transform", "opacity-90 hover:opacity-100 hover:scale-[1.01]")} onClick={onClick}> <Card className={cn( "w-[180px] h-[110px] flex-shrink-0", isActive ? "pt-3 px-3 pb-3" : "pt-3 px-3 pb-[18px]", isActive && ACTIVE_CARD_STYLE )} style={isActive ? ACTIVE_CARD_INLINE_STYLE : undefined} > <div className="h-[88px] flex flex-col relative"> <div className="absolute top-0 left-0 flex items-center h-5"> <span className="text-[10px] px-1.5 py-0.5 rounded font-medium bg-primary/10 text-primary"> {index} </span> </div> <div className="absolute top-0 right-0 flex items-center gap-1 h-5"> {(step?.modify === true) && ( <OctagonAlert className="h-4 w-4 text-amber-500 dark:text-amber-400" aria-label="Modifies data" /> )} {step?.executionMode === 'LOOP' && ( <RotateCw className="h-3 w-3 text-muted-foreground" aria-label="Loop step" /> )} </div> <div className="flex-1 flex flex-col items-center justify-between leading-tight"> <div className="flex-1 flex flex-col items-center justify-center"> <div className="p-2 rounded-full bg-white dark:bg-gray-100 border border-border/50"> {simpleIcon ? ( <svg width="16" height="16" viewBox="0 0 24 24" fill={`#${simpleIcon.hex}`} className="flex-shrink-0"> <path d={simpleIcon.path} /> </svg> ) : ( <Globe className="h-4 w-4 text-muted-foreground" /> )} </div> <span className="text-[11px] font-semibold mt-1.5 truncate max-w-[140px]" title={step.id || `Step ${index}`}>{step.id || `Step ${index}`}</span> {linkedIntegration && ( <span className="text-[9px] text-muted-foreground">{linkedIntegration.id}</span> )} </div> <div className="flex items-center gap-1 mt-1"> <span className={cn("text-[10px] font-semibold flex items-center gap-1.5", statusInfo.color)}> <span className={cn( "w-2 h-2 rounded-full", statusInfo.dotColor, statusInfo.animate && "animate-pulse" )} /> {statusInfo.text} </span> </div> </div> </div> </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/superglue-ai/superglue'

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