/** * PluginDetailDialog — full detail view for a single plugin with install controls. */ import { useEffect, useState } from 'react'; import { api } from '@renderer/api'; import { MarkdownViewer } from '@renderer/components/chat/viewers/MarkdownViewer'; import { Badge } from '@renderer/components/ui/badge'; import { Button } from '@renderer/components/ui/button'; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from '@renderer/components/ui/dialog'; import { Label } from '@renderer/components/ui/label'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@renderer/components/ui/select'; import { useStore } from '@renderer/store'; import { getCapabilityLabel, getInstallationSummaryLabel, getPluginOperationKey, hasInstallationInScope, inferCapabilities, normalizeCategory, } from '@shared/utils/extensionNormalizers'; import { ExternalLink, Loader2, Mail } from 'lucide-react'; import { useShallow } from 'zustand/react/shallow'; import { InstallButton } from '../common/InstallButton'; import { InstallCountBadge } from '../common/InstallCountBadge'; import { SourceBadge } from '../common/SourceBadge'; import type { EnrichedPlugin, InstallScope } from '@shared/types/extensions'; interface PluginDetailDialogProps { plugin: EnrichedPlugin | null; open: boolean; onClose: () => void; projectPath: string | null; } const SCOPE_OPTIONS: { value: InstallScope; label: string }[] = [ { value: 'user', label: 'User (global)' }, { value: 'project', label: 'Project (shared)' }, { value: 'local', label: 'Local (gitignored)' }, ]; export const PluginDetailDialog = ({ plugin, open, onClose, projectPath, }: PluginDetailDialogProps): React.JSX.Element => { const { fetchPluginReadme, readmes, readmeLoading, installPlugin, uninstallPlugin } = useStore( useShallow((s) => ({ fetchPluginReadme: s.fetchPluginReadme, readmes: s.pluginReadmes, readmeLoading: s.pluginReadmeLoading, installPlugin: s.installPlugin, uninstallPlugin: s.uninstallPlugin, })) ); const [scope, setScope] = useState('user'); const projectScopeAvailable = Boolean(projectPath); useEffect(() => { if (plugin && open) { fetchPluginReadme(plugin.pluginId); } }, [plugin, open, fetchPluginReadme]); useEffect(() => { if (open) { setScope('user'); } }, [open, plugin?.pluginId]); useEffect(() => { if (scope !== 'user' && !projectScopeAvailable) { setScope('user'); } }, [projectScopeAvailable, scope]); const operationKey = plugin ? getPluginOperationKey(plugin.pluginId, scope, scope !== 'user' ? projectPath : undefined) : null; const installProgress = useStore( (s) => (operationKey ? s.pluginInstallProgress[operationKey] : undefined) ?? 'idle' ); const installError = useStore((s) => (operationKey ? s.installErrors[operationKey] : undefined)); if (!plugin) return <>; const capabilities = inferCapabilities(plugin); const category = normalizeCategory(plugin.category); const readme = readmes[plugin.pluginId]; const isReadmeLoading = readmeLoading[plugin.pluginId] ?? false; const isInstalledForScope = hasInstallationInScope(plugin.installations, scope); const installSummaryLabel = getInstallationSummaryLabel(plugin.installations); return ( !o && onClose()}>
{plugin.name} {plugin.description}
{installSummaryLabel && ( {installSummaryLabel} )}
{/* Metadata grid */}
Author

{plugin.author?.name ?? 'Unknown'}

Category

{category}

Source

{plugin.source}

{plugin.version && (
Version

{plugin.version}

)}
Capabilities
{capabilities.map((cap) => ( {getCapabilityLabel(cap)} ))}
Installs
{/* Install controls */}
installPlugin({ pluginId: plugin.pluginId, scope, ...(scope !== 'user' && projectPath ? { projectPath } : {}), }) } onUninstall={() => uninstallPlugin( plugin.pluginId, scope, scope !== 'user' ? (projectPath ?? undefined) : undefined ) } size="default" errorMessage={installError} />
{/* Links */}
{plugin.homepage && ( )} {plugin.author?.email && ( )}
{/* README */}
{isReadmeLoading && (
Loading README...
)} {!isReadmeLoading && readme && ( )} {!isReadmeLoading && !readme && (

No README available.

)}
); };