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()}
)}
{(() => {
// Reflect the real runtime status, not just the enabled flag.
// A server can be enabled but still CONNECTING or in ERROR — showing
// "Connected" in those cases is misleading.
const runtime = cardVO.enabled
? (cardVO.runtimeStatus ?? 'connecting')
: 'disabled';
const badgeClass: Record
= {
connected: 'border-green-400 text-green-600 dark:text-green-400',
connecting: 'border-amber-400 text-amber-600 dark:text-amber-400',
error: 'border-red-400 text-red-600 dark:text-red-400',
disabled: 'border-gray-400 text-gray-600 dark:text-gray-300',
};
const badgeLabel: Record = {
connected: t('mcp.statusConnected'),
connecting: t('mcp.connecting'),
error: t('mcp.statusError'),
disabled: t('mcp.statusDisabled'),
};
return (
{badgeLabel[runtime] ?? badgeLabel.disabled}
);
})()}
{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 ? (
) : (

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')}
>
);
}