import { ExtensionCardVO, ExtensionType } from './ExtensionCardVO'; import { useState } from 'react'; import { Badge } from '@/components/ui/badge'; import { useTranslation } from 'react-i18next'; import { BugIcon, ExternalLink, Ellipsis, Trash, ArrowUp, Server, Sparkles, Puzzle, } from 'lucide-react'; import { getCloudServiceClientSync, systemInfo } from '@/app/infra/http'; import { httpClient } from '@/app/infra/http/HttpClient'; import { Button } from '@/components/ui/button'; import { Card } from '@/components/ui/card'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; type ExtensionCardComponentProps = { cardVO: ExtensionCardVO; onCardClick: () => void; onDeleteClick: (cardVO: ExtensionCardVO) => void; onUpgradeClick?: (cardVO: ExtensionCardVO) => void; }; export default function ExtensionCardComponent({ cardVO, onCardClick, onDeleteClick, onUpgradeClick, }: ExtensionCardComponentProps) { const { t } = useTranslation(); const [dropdownOpen, setDropdownOpen] = useState(false); const [iconFailed, setIconFailed] = useState(false); const FallbackIcon = cardVO.type === 'mcp' ? Server : cardVO.type === 'skill' ? Sparkles : Puzzle; const iconSrc = cardVO.iconURL || httpClient.getPluginIconURL(cardVO.author, cardVO.name); const showFallback = iconFailed || !iconSrc; const getTypeLabel = (type: ExtensionType) => { switch (type) { case 'mcp': return 'MCP'; case 'skill': return t('common.skill'); default: return t('market.typePlugin'); } }; const getTypeIcon = (type: ExtensionType) => { switch (type) { case 'mcp': return Server; case 'skill': return Sparkles; default: return Puzzle; } }; const renderTypeBadge = (type: ExtensionType) => { const TypeIcon = getTypeIcon(type); return ( {getTypeLabel(type)} ); }; const renderPluginContent = () => ( <>
{cardVO.author} / {cardVO.name}
{cardVO.label}
v{cardVO.version} {renderTypeBadge(cardVO.type)} {cardVO.debug && ( {t('plugins.debugging')} )} {!cardVO.debug && ( <> {cardVO.install_source === 'github' && ( {t('plugins.fromGithub')} )} {cardVO.install_source === 'local' && ( {t('plugins.fromLocal')} )} {cardVO.install_source === 'marketplace' && ( {t('plugins.fromMarketplace')} )} )}
{cardVO.description}
); const renderMCPContent = () => ( <>
MCP Server
{cardVO.label}
{renderTypeBadge('mcp')} {cardVO.mode && ( {cardVO.mode.toUpperCase()} )} {cardVO.enabled ? t('mcp.statusConnected') : t('mcp.statusDisabled')}
{cardVO.description || (cardVO.tools !== undefined && cardVO.tools > 0 ? t('mcp.toolCount', { count: cardVO.tools }) : t('mcp.noToolsFound'))}
); const renderSkillContent = () => ( <>
Skill
{cardVO.label}
{renderTypeBadge('skill')}
{cardVO.description}
); return ( <> onCardClick()} >
{showFallback ? (
) : ( extension icon setIconFailed(true)} /> )}
{cardVO.type === 'plugin' && renderPluginContent()} {cardVO.type === 'mcp' && renderMCPContent()} {cardVO.type === 'skill' && renderSkillContent()}
e.stopPropagation()} >
{ setDropdownOpen(open); }} >
{cardVO.hasUpdate && (
)}
{cardVO.type === 'plugin' && cardVO.install_source === 'marketplace' && ( { e.stopPropagation(); if (onUpgradeClick) { onUpgradeClick(cardVO); } setDropdownOpen(false); }} > {t('plugins.update')} {cardVO.hasUpdate && ( {t('plugins.new')} )} )} {cardVO.type === 'plugin' && (cardVO.install_source === 'github' || cardVO.install_source === 'marketplace') && ( { e.stopPropagation(); if (cardVO.install_source === 'github') { window.open( cardVO.install_info?.github_url as string, '_blank', ); } else if (cardVO.install_source === 'marketplace') { window.open( getCloudServiceClientSync().getPluginMarketplaceURL( systemInfo.cloud_service_url, cardVO.author, cardVO.name, ), '_blank', ); } setDropdownOpen(false); }} > {t('plugins.viewSource')} )} { e.stopPropagation(); onDeleteClick(cardVO); setDropdownOpen(false); }} > {cardVO.type === 'mcp' ? t('mcp.deleteServer') : cardVO.type === 'skill' ? t('skills.delete') : t('plugins.delete')}
); }