'use client'; import { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { backendClient } from '@/app/infra/http'; import { Button } from '@/components/ui/button'; import { toast } from 'sonner'; import { Skeleton } from '@/components/ui/skeleton'; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, } from '@/components/ui/dialog'; import { Checkbox } from '@/components/ui/checkbox'; import { Plus, X, Server, Wrench } from 'lucide-react'; import { Badge } from '@/components/ui/badge'; import { Plugin } from '@/app/infra/entities/plugin'; import { MCPServer } from '@/app/infra/entities/api'; import PluginComponentList from '@/app/home/plugins/components/plugin-installed/PluginComponentList'; export default function PipelineExtension({ pipelineId, }: { pipelineId: string; }) { const { t } = useTranslation(); const [loading, setLoading] = useState(true); const [selectedPlugins, setSelectedPlugins] = useState([]); const [allPlugins, setAllPlugins] = useState([]); const [selectedMCPServers, setSelectedMCPServers] = useState([]); const [allMCPServers, setAllMCPServers] = useState([]); const [pluginDialogOpen, setPluginDialogOpen] = useState(false); const [mcpDialogOpen, setMcpDialogOpen] = useState(false); const [tempSelectedPluginIds, setTempSelectedPluginIds] = useState( [], ); const [tempSelectedMCPIds, setTempSelectedMCPIds] = useState([]); useEffect(() => { loadExtensions(); }, [pipelineId]); const getPluginId = (plugin: Plugin): string => { const author = plugin.manifest.manifest.metadata.author; const name = plugin.manifest.manifest.metadata.name; return `${author}/${name}`; }; const loadExtensions = async () => { try { setLoading(true); const data = await backendClient.getPipelineExtensions(pipelineId); const boundPluginIds = new Set( data.bound_plugins.map((p) => `${p.author}/${p.name}`), ); const selected = data.available_plugins.filter((plugin) => boundPluginIds.has(getPluginId(plugin)), ); setSelectedPlugins(selected); setAllPlugins(data.available_plugins); // Load MCP servers const boundMCPServerIds = new Set(data.bound_mcp_servers || []); const selectedMCP = data.available_mcp_servers.filter((server) => boundMCPServerIds.has(server.uuid || ''), ); setSelectedMCPServers(selectedMCP); setAllMCPServers(data.available_mcp_servers); } catch (error) { console.error('Failed to load extensions:', error); toast.error(t('pipelines.extensions.loadError')); } finally { setLoading(false); } }; const saveToBackend = async (plugins: Plugin[], mcpServers: MCPServer[]) => { try { const boundPluginsArray = plugins.map((plugin) => { const metadata = plugin.manifest.manifest.metadata; return { author: metadata.author || '', name: metadata.name, }; }); const boundMCPServerIds = mcpServers.map((server) => server.uuid || ''); await backendClient.updatePipelineExtensions( pipelineId, boundPluginsArray, boundMCPServerIds, ); toast.success(t('pipelines.extensions.saveSuccess')); } catch (error) { console.error('Failed to save extensions:', error); toast.error(t('pipelines.extensions.saveError')); // Reload on error to restore correct state loadExtensions(); } }; const handleRemovePlugin = async (pluginId: string) => { const newPlugins = selectedPlugins.filter( (p) => getPluginId(p) !== pluginId, ); setSelectedPlugins(newPlugins); await saveToBackend(newPlugins, selectedMCPServers); }; const handleRemoveMCPServer = async (serverUuid: string) => { const newServers = selectedMCPServers.filter((s) => s.uuid !== serverUuid); setSelectedMCPServers(newServers); await saveToBackend(selectedPlugins, newServers); }; const handleOpenPluginDialog = () => { setTempSelectedPluginIds(selectedPlugins.map((p) => getPluginId(p))); setPluginDialogOpen(true); }; const handleOpenMCPDialog = () => { setTempSelectedMCPIds(selectedMCPServers.map((s) => s.uuid || '')); setMcpDialogOpen(true); }; const handleTogglePlugin = (pluginId: string) => { setTempSelectedPluginIds((prev) => prev.includes(pluginId) ? prev.filter((id) => id !== pluginId) : [...prev, pluginId], ); }; const handleToggleMCPServer = (serverUuid: string) => { setTempSelectedMCPIds((prev) => prev.includes(serverUuid) ? prev.filter((id) => id !== serverUuid) : [...prev, serverUuid], ); }; const handleConfirmPluginSelection = async () => { const newSelected = allPlugins.filter((p) => tempSelectedPluginIds.includes(getPluginId(p)), ); setSelectedPlugins(newSelected); setPluginDialogOpen(false); await saveToBackend(newSelected, selectedMCPServers); }; const handleConfirmMCPSelection = async () => { const newSelected = allMCPServers.filter((s) => tempSelectedMCPIds.includes(s.uuid || ''), ); setSelectedMCPServers(newSelected); setMcpDialogOpen(false); await saveToBackend(selectedPlugins, newSelected); }; if (loading) { return (
); } return (
{/* Plugins Section */}

{t('pipelines.extensions.pluginsTitle')}

{selectedPlugins.length === 0 ? (

{t('pipelines.extensions.noPluginsSelected')}

) : (
{selectedPlugins.map((plugin) => { const pluginId = getPluginId(plugin); const metadata = plugin.manifest.manifest.metadata; return (
{metadata.name}
{metadata.name}
{metadata.author} • v{metadata.version}
{!plugin.enabled && ( {t('pipelines.extensions.disabled')} )}
); })}
)}
{/* MCP Servers Section */}

{t('pipelines.extensions.mcpServersTitle')}

{selectedMCPServers.length === 0 ? (

{t('pipelines.extensions.noMCPServersSelected')}

) : (
{selectedMCPServers.map((server) => (
{server.name}
{server.mode}
{server.runtime_info && server.runtime_info.status === 'connected' && ( {t('pipelines.extensions.toolCount', { count: server.runtime_info.tool_count || 0, })} )}
{!server.enable && ( {t('pipelines.extensions.disabled')} )}
))}
)}
{/* Plugin Selection Dialog */} {t('pipelines.extensions.selectPlugins')}
{allPlugins.map((plugin) => { const pluginId = getPluginId(plugin); const metadata = plugin.manifest.manifest.metadata; const isSelected = tempSelectedPluginIds.includes(pluginId); return (
handleTogglePlugin(pluginId)} > {metadata.name}
{metadata.name}
{metadata.author} • v{metadata.version}
{!plugin.enabled && ( {t('pipelines.extensions.disabled')} )}
); })}
{/* MCP Server Selection Dialog */} {t('pipelines.extensions.selectMCPServers')}
{allMCPServers.map((server) => { const isSelected = tempSelectedMCPIds.includes(server.uuid || ''); return (
handleToggleMCPServer(server.uuid || '')} >
{server.name}
{server.mode}
{server.runtime_info && server.runtime_info.status === 'connected' && ( {t('pipelines.extensions.toolCount', { count: server.runtime_info.tool_count || 0, })} )}
{!server.enable && ( {t('pipelines.extensions.disabled')} )}
); })}
); }