| import React, { useState, useMemo } from 'react'; |
| import { Button } from '@/components/ui/button'; |
| import { Card, CardContent } from '@/components/ui/card'; |
| import { Badge } from '@/components/ui/badge'; |
| import { |
| DropdownMenu, |
| DropdownMenuContent, |
| DropdownMenuItem, |
| DropdownMenuTrigger |
| } from '@/components/ui/dropdown-menu'; |
| import { Plus, Settings, Zap, Bot, ChevronDown, Star, CheckCircle } from 'lucide-react'; |
| import { cn } from '@/lib/utils'; |
| import { getCategoryEmoji } from '../utils'; |
| import type { AppCardProps } from '../types'; |
| import { usePipedreamProfiles } from '@/hooks/react-query/pipedream/use-pipedream-profiles'; |
| import { usePipedreamAppIcon } from '@/hooks/react-query/pipedream/use-pipedream'; |
|
|
| export const AppCard: React.FC<AppCardProps> = ({ |
| app, |
| compact = false, |
| mode = 'full', |
| currentAgentId, |
| agentName, |
| agentPipedreamProfiles = [], |
| onAppSelected, |
| onConnectApp, |
| onConfigureTools, |
| handleCategorySelect, |
| }) => { |
| const [isHovered, setIsHovered] = useState(false); |
| const { data: profiles } = usePipedreamProfiles(); |
| const { data: iconData } = usePipedreamAppIcon(app.name_slug, { |
| enabled: !app.img_src |
| }); |
|
|
| const connectedProfiles = useMemo(() => { |
| return profiles?.filter(p => p.app_slug === app.name_slug && p.is_connected) || []; |
| }, [profiles, app.name_slug]); |
|
|
| const agentProfiles = useMemo(() => { |
| return agentPipedreamProfiles?.filter(p => p.app_slug === app.name_slug) || []; |
| }, [agentPipedreamProfiles, app.name_slug]); |
|
|
| const totalToolsCount = useMemo(() => { |
| return agentProfiles.reduce((sum, profile) => { |
| return sum + (profile.toolsCount ?? profile.enabledTools?.length ?? 0); |
| }, 0); |
| }, [agentProfiles]); |
|
|
| const handleCategoryClick = (e: React.MouseEvent, category: string) => { |
| e.stopPropagation(); |
| handleCategorySelect?.(category); |
| }; |
|
|
| const handleConnectClick = () => { |
| if (mode === 'simple' && onAppSelected) { |
| onAppSelected({ app_slug: app.name_slug, app_name: app.name }); |
| } else if (onConnectApp) { |
| onConnectApp(app); |
| } |
| }; |
|
|
| const handleConfigureClick = (profile: any) => { |
| if (onConfigureTools) { |
| onConfigureTools(profile); |
| } |
| }; |
|
|
| const isConnected = connectedProfiles.length > 0; |
| const hasAgentTools = agentProfiles.length > 0; |
|
|
| return ( |
| <Card |
| className={cn( |
| "group relative overflow-hidden transition-all p-0 duration-300", |
| )} |
| onMouseEnter={() => setIsHovered(true)} |
| onMouseLeave={() => setIsHovered(false)} |
| > |
| <CardContent className="p-4 h-full flex flex-col"> |
| <div className="flex items-start gap-3 mb-3"> |
| <div className="flex-shrink-0 relative"> |
| <div className={cn( |
| "h-8 w-8 rounded-xl flex items-center justify-center text-primary font-semibold overflow-hidden transition-all duration-300" |
| )}> |
| {(app.img_src || iconData?.icon_url) ? ( |
| <img |
| src={app.img_src || iconData?.icon_url || ''} |
| alt={app.name} |
| className="w-full h-full object-cover rounded-xl" |
| onError={(e) => { |
| const target = e.target as HTMLImageElement; |
| target.style.display = 'none'; |
| target.nextElementSibling?.classList.remove('hidden'); |
| }} |
| /> |
| ) : null} |
| <span className={cn( |
| "font-bold text-lg", |
| (app.img_src || iconData?.icon_url) ? "hidden" : "block" |
| )}> |
| {app.name.charAt(0).toUpperCase()} |
| </span> |
| </div> |
| {isConnected && ( |
| <div className="absolute -top-1 -right-1 h-4 w-4 bg-green-500 rounded-full border-2 border-white dark:border-gray-800 flex items-center justify-center"> |
| <CheckCircle className="h-2 w-2 text-white" /> |
| </div> |
| )} |
| </div> |
| |
| <div className="flex-1 min-w-0"> |
| <h3 className="font-semibold text-base text-foreground mb-1 truncate group-hover:text-primary transition-colors"> |
| {app.name} |
| </h3> |
| <p className="text-sm text-muted-foreground line-clamp-2 leading-relaxed"> |
| {app.description} |
| </p> |
| </div> |
| </div> |
| {hasAgentTools && ( |
| <div className="mb-3"> |
| <div className="rounded-xl bg-muted py-2 border border"> |
| <DropdownMenu> |
| <DropdownMenuTrigger asChild> |
| <Button size="sm" variant="ghost" className="h-auto w-full justify-between p-0 hover:bg-transparent"> |
| <div className="flex items-center gap-2"> |
| <Bot className="h-4 w-4 text-primary" /> |
| <div className="text-left"> |
| <div className="text-sm font-medium text-foreground"> |
| {agentProfiles.length} {agentProfiles.length === 1 ? 'Profile' : 'Profiles'} |
| </div> |
| <div className="text-xs text-muted-foreground"> |
| {totalToolsCount} tools configured |
| </div> |
| </div> |
| </div> |
| <ChevronDown className="h-4 w-4 text-muted-foreground" /> |
| </Button> |
| </DropdownMenuTrigger> |
| <DropdownMenuContent align="start" className="w-full min-w-[200px]"> |
| {agentProfiles.map((profile) => ( |
| <DropdownMenuItem |
| key={profile.profile_id} |
| onClick={() => handleConfigureClick(profile)} |
| className="cursor-pointer" |
| > |
| <div className="flex items-center justify-between w-full"> |
| <div className="flex items-center gap-2"> |
| <Settings className="h-4 w-4" /> |
| <span className="font-medium">{profile.profile_name}</span> |
| </div> |
| <Badge variant="outline" className="text-xs"> |
| {profile.toolsCount ?? profile.enabledTools?.length ?? 0} tools |
| </Badge> |
| </div> |
| </DropdownMenuItem> |
| ))} |
| </DropdownMenuContent> |
| </DropdownMenu> |
| </div> |
| </div> |
| )} |
| <div className="flex-1" /> |
| <div className="mt-auto"> |
| <Button |
| size="sm" |
| onClick={handleConnectClick} |
| className={cn( |
| "w-full font-medium transition-all duration-200", |
| isConnected && !hasAgentTools |
| ? "bg-purple-600" |
| : "bg-primary", |
| )} |
| > |
| {mode === 'simple' ? ( |
| <> |
| <Plus className="h-4 w-4" /> |
| Connect |
| </> |
| ) : mode === 'profile-only' ? ( |
| <> |
| <Plus className="h-4 w-4" /> |
| {isConnected ? 'Add Profile' : 'Connect'} |
| </> |
| ) : ( |
| <> |
| {isConnected ? ( |
| <> |
| <Zap className="h-4 w-4" /> |
| Add Tools |
| </> |
| ) : ( |
| <> |
| <Plus className="h-4 w-4" /> |
| Connect |
| </> |
| )} |
| </> |
| )} |
| </Button> |
| </div> |
| </CardContent> |
| </Card> |
| ); |
| }; |