import React, { useState } from 'react'; import { useTranslation } from 'react-i18next'; import { apiPost, apiGet, apiPut, fetchWithInterceptors } from '@/utils/fetchInterceptor'; import { getApiUrl } from '@/utils/runtime'; import ConfirmDialog from '@/components/ui/ConfirmDialog'; interface DxtUploadFormProps { onSuccess: (serverConfig: any) => void; onCancel: () => void; } interface DxtUploadResponse { success: boolean; data?: { manifest: any; extractDir: string; }; message?: string; } const DxtUploadForm: React.FC = ({ onSuccess, onCancel }) => { const { t } = useTranslation(); const [isDragging, setIsDragging] = useState(false); const [isUploading, setIsUploading] = useState(false); const [error, setError] = useState(null); const [selectedFile, setSelectedFile] = useState(null); const [showServerForm, setShowServerForm] = useState(false); const [manifestData, setManifestData] = useState(null); const [extractDir, setExtractDir] = useState(''); const [showConfirmDialog, setShowConfirmDialog] = useState(false); const [pendingServerName, setPendingServerName] = useState(''); const handleDragOver = (e: React.DragEvent) => { e.preventDefault(); setIsDragging(true); }; const handleDragLeave = (e: React.DragEvent) => { e.preventDefault(); setIsDragging(false); }; const handleDrop = (e: React.DragEvent) => { e.preventDefault(); setIsDragging(false); const files = e.dataTransfer.files; if (files.length > 0) { const file = files[0]; if (file.name.endsWith('.dxt')) { setSelectedFile(file); setError(null); } else { setError(t('dxt.invalidFileType')); } } }; const handleFileSelect = (e: React.ChangeEvent) => { const files = e.target.files; if (files && files.length > 0) { const file = files[0]; if (file.name.endsWith('.dxt')) { setSelectedFile(file); setError(null); } else { setError(t('dxt.invalidFileType')); } } }; const handleUpload = async () => { if (!selectedFile) { setError(t('dxt.noFileSelected')); return; } setIsUploading(true); setError(null); try { const formData = new FormData(); formData.append('dxtFile', selectedFile); const response = await fetchWithInterceptors(getApiUrl('/dxt/upload'), { method: 'POST', body: formData, }); const result: DxtUploadResponse = await response.json(); if (!response.ok) { throw new Error(result.message || `HTTP error! Status: ${response.status}`); } if (result.success && result.data) { setManifestData(result.data.manifest); setExtractDir(result.data.extractDir); setShowServerForm(true); } else { throw new Error(result.message || t('dxt.uploadFailed')); } } catch (err) { console.error('DXT upload error:', err); setError(err instanceof Error ? err.message : t('dxt.uploadFailed')); } finally { setIsUploading(false); } }; const handleInstallServer = async (serverName: string, forceOverride: boolean = false) => { setIsUploading(true); setError(null); try { // Convert DXT manifest to MCPHub stdio server configuration const serverConfig = convertDxtToMcpConfig(manifestData, extractDir, serverName); // First, check if server exists if (!forceOverride) { const checkResult = await apiGet('/servers'); if (checkResult.success) { const existingServer = checkResult.data?.find((server: any) => server.name === serverName); if (existingServer) { // Server exists, show confirmation dialog setPendingServerName(serverName); setShowConfirmDialog(true); setIsUploading(false); return; } } } // Install or override the server let result; if (forceOverride) { result = await apiPut(`/servers/${encodeURIComponent(serverName)}`, { name: serverName, config: serverConfig, }); } else { result = await apiPost('/servers', { name: serverName, config: serverConfig, }); } if (result.success) { onSuccess(serverConfig); } else { throw new Error(result.message || t('dxt.installFailed')); } } catch (err) { console.error('DXT install error:', err); setError(err instanceof Error ? err.message : t('dxt.installFailed')); setIsUploading(false); } }; const handleConfirmOverride = () => { setShowConfirmDialog(false); if (pendingServerName) { handleInstallServer(pendingServerName, true); } }; const handleCancelOverride = () => { setShowConfirmDialog(false); setPendingServerName(''); setIsUploading(false); }; const convertDxtToMcpConfig = (manifest: any, extractPath: string, _serverName: string) => { const mcpConfig = manifest.server?.mcp_config || {}; // Convert DXT manifest to MCPHub stdio configuration const config: any = { type: 'stdio', command: mcpConfig.command || 'node', args: (mcpConfig.args || []).map((arg: string) => arg.replace('${__dirname}', extractPath) ), }; // Add environment variables if they exist if (mcpConfig.env && Object.keys(mcpConfig.env).length > 0) { config.env = { ...mcpConfig.env }; // Replace ${__dirname} in environment variables Object.keys(config.env).forEach(key => { if (typeof config.env[key] === 'string') { config.env[key] = config.env[key].replace('${__dirname}', extractPath); } }); } return config; }; if (showServerForm && manifestData) { return ( <>

{t('dxt.installServer')}

{error && (

{error}

)}
{/* Extension Info */}

{t('dxt.extensionInfo')}

{t('dxt.name')}: {manifestData.display_name || manifestData.name}
{t('dxt.version')}: {manifestData.version}
{t('dxt.description')}: {manifestData.description}
{manifestData.author && (
{t('dxt.author')}: {manifestData.author.name}
)} {manifestData.tools && manifestData.tools.length > 0 && (
{t('dxt.tools')}:
    {manifestData.tools.map((tool: any, index: number) => (
  • {tool.name} - {tool.description}
  • ))}
)}
{/* Server Configuration */}
{/* Action Buttons */}
); } return (

{t('dxt.uploadTitle')}

{error && (

{error}

)} {/* File Drop Zone */}
{selectedFile ? (

{selectedFile.name}

{(selectedFile.size / 1024 / 1024).toFixed(2)} MB

) : (

{t('dxt.dropFileHere')}

{t('dxt.orClickToSelect')}

)}
); }; export default DxtUploadForm;