import { useState, useEffect } from 'react' import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, } from '@/components/ui/dialog' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { IconPlus, IconTrash, IconGripVertical } from '@tabler/icons-react' import { MCPServerConfig } from '@/hooks/useMCPServers' import { useTranslation } from '@/i18n/react-i18next-compat' import { DndContext, closestCenter, useSensor, useSensors, PointerSensor, KeyboardSensor, } from '@dnd-kit/core' import { SortableContext, verticalListSortingStrategy, arrayMove, useSortable, } from '@dnd-kit/sortable' import { CSS } from '@dnd-kit/utilities' import { cn } from '@/lib/utils' // Sortable argument item component function SortableArgItem({ id, value, onChange, onRemove, canRemove, placeholder, }: { id: number value: string onChange: (value: string) => void onRemove: () => void canRemove: boolean placeholder: string }) { const { attributes, listeners, setNodeRef, transform, transition, isDragging, } = useSortable({ id }) const style = { transform: CSS.Transform.toString(transform), transition, opacity: isDragging ? 0.5 : 1, } return (
onChange(e.target.value)} placeholder={placeholder} className="flex-1" /> {canRemove && (
)}
) } interface AddEditMCPServerProps { open: boolean onOpenChange: (open: boolean) => void editingKey: string | null initialData?: MCPServerConfig onSave: (name: string, config: MCPServerConfig) => void } export default function AddEditMCPServer({ open, onOpenChange, editingKey, initialData, onSave, }: AddEditMCPServerProps) { const { t } = useTranslation() const [serverName, setServerName] = useState('') const [command, setCommand] = useState('') const [args, setArgs] = useState(['']) const [envKeys, setEnvKeys] = useState(['']) const [envValues, setEnvValues] = useState(['']) // Reset form when modal opens/closes or editing key changes useEffect(() => { if (open && editingKey && initialData) { setServerName(editingKey) setCommand(initialData.command) setArgs(initialData.args?.length > 0 ? initialData.args : ['']) if (initialData.env) { // Convert env object to arrays of keys and values const keys = Object.keys(initialData.env) const values = keys.map((key) => initialData.env[key]) setEnvKeys(keys.length > 0 ? keys : ['']) setEnvValues(values.length > 0 ? values : ['']) } } else if (open) { // Add mode - reset form resetForm() } }, [open, editingKey, initialData]) const resetForm = () => { setServerName('') setCommand('') setArgs(['']) setEnvKeys(['']) setEnvValues(['']) } const handleAddArg = () => { setArgs([...args, '']) } const handleRemoveArg = (index: number) => { const newArgs = [...args] newArgs.splice(index, 1) setArgs(newArgs.length > 0 ? newArgs : ['']) } const handleArgChange = (index: number, value: string) => { const newArgs = [...args] newArgs[index] = value setArgs(newArgs) } const handleReorderArgs = (oldIndex: number, newIndex: number) => { setArgs(arrayMove(args, oldIndex, newIndex)) } // Sensors for drag and drop const sensors = useSensors( useSensor(PointerSensor, { activationConstraint: { delay: 100, tolerance: 5, }, }), useSensor(KeyboardSensor) ) const handleAddEnv = () => { setEnvKeys([...envKeys, '']) setEnvValues([...envValues, '']) } const handleRemoveEnv = (index: number) => { const newKeys = [...envKeys] const newValues = [...envValues] newKeys.splice(index, 1) newValues.splice(index, 1) setEnvKeys(newKeys.length > 0 ? newKeys : ['']) setEnvValues(newValues.length > 0 ? newValues : ['']) } const handleEnvKeyChange = (index: number, value: string) => { const newKeys = [...envKeys] newKeys[index] = value setEnvKeys(newKeys) } const handleEnvValueChange = (index: number, value: string) => { const newValues = [...envValues] newValues[index] = value setEnvValues(newValues) } const handleSave = () => { // Convert env arrays to object const envObj: Record = {} envKeys.forEach((key, index) => { const keyName = key.trim() if (keyName !== '') { envObj[keyName] = envValues[index]?.trim() || '' } }) // Filter out empty args const filteredArgs = args.map((arg) => arg.trim()).filter((arg) => arg) const config: MCPServerConfig = { command: command.trim(), args: filteredArgs, env: envObj, } if (serverName.trim() !== '') { onSave(serverName.trim(), config) onOpenChange(false) resetForm() } } return ( {editingKey ? t('mcp-servers:editServer') : t('mcp-servers:addServer')}
setServerName(e.target.value)} placeholder={t('mcp-servers:enterServerName')} autoFocus />
setCommand(e.target.value)} placeholder={t('mcp-servers:enterCommand')} />
{ const { active, over } = event if (active.id !== over?.id) { const oldIndex = parseInt(active.id.toString()) const newIndex = parseInt(over?.id.toString() || '0') handleReorderArgs(oldIndex, newIndex) } }} > index)} strategy={verticalListSortingStrategy} > {args.map((arg, index) => ( handleArgChange(index, value)} onRemove={() => handleRemoveArg(index)} canRemove={args.length > 1} placeholder={t('mcp-servers:argument', { index: index + 1, })} /> ))}
{envKeys.map((key, index) => (
handleEnvKeyChange(index, e.target.value)} placeholder={t('mcp-servers:key')} className="flex-1" /> handleEnvValueChange(index, e.target.value)} placeholder={t('mcp-servers:value')} className="flex-1" /> {envKeys.length > 1 && (
handleRemoveEnv(index)} >
)}
))}
) }