feat: add dialog

This commit is contained in:
WangCham
2025-10-13 18:21:46 +08:00
parent 0f39a31648
commit d65f862c36
2 changed files with 335 additions and 48 deletions

View File

@@ -34,7 +34,7 @@ import {
DialogFooter,
} from '@/components/ui/dialog';
import { Input } from '@/components/ui/input';
import { useState, useRef, useCallback, useEffect } from 'react';
import React, { useState, useRef, useCallback, useEffect, use } from 'react';
import { httpClient } from '@/app/infra/http/HttpClient';
import { toast } from 'sonner';
import { useTranslation } from 'react-i18next';
@@ -60,11 +60,16 @@ export default function PluginConfigPage() {
// const [mcpModalOpen, setMcpModalOpen] = useState(false);
const [mcpMarketInstallModalOpen, setMcpMarketInstallModalOpen] =
useState(false);
const [mcpSSEInstallModalOpen, setMcpSSEInstallModalOpen] = useState(false);
const [mcpDescription,setMcpDescription] = useState('');
const [pluginInstallStatus, setPluginInstallStatus] =
useState<PluginInstallStatus>(PluginInstallStatus.WAIT_INPUT);
const [mcpInstallStatus, setMcpInstallStatus] = useState<PluginInstallStatus>(
PluginInstallStatus.WAIT_INPUT,
);
const [mcpSSEHeaders,setMcpSSEHeaders] = useState('')
const [mcpName,setMcpName] = useState('')
const [mcpTimeout,setMcpTimeout] = useState('')
const [installError, setInstallError] = useState<string | null>(null);
const [mcpInstallError, setMcpInstallError] = useState<string | null>(null);
const [githubURL, setGithubURL] = useState('');
@@ -119,6 +124,8 @@ export default function PluginConfigPage() {
}, 1000);
}
const [mcpGithubURL, setMcpGithubURL] = useState('');
const [mcpSSEURL, setMcpSSEURL] = useState('');
const [mcpInstallConfig, setMcpInstallConfig] = useState<Record<string, any> | null>(null);
const pluginInstalledRef = useRef<PluginInstalledComponentRef>(null);
const mcpComponentRef = useRef<MCPComponentRef>(null);
@@ -309,10 +316,12 @@ export default function PluginConfigPage() {
return renderPluginConnectionErrorState();
}
function installMcpServer(url: string) {
function installMcpServer(url: string, config?: Record<string, any>) {
setMcpInstallStatus(PluginInstallStatus.INSTALLING);
httpClient
.installMCPServerFromGithub(url)
// NOTE: backend currently only accepts url. If backend accepts config in future,
// replace this call with: httpClient.installMCPServerFromGithub(url, config)
console.log('installing mcp server with config:', config);
httpClient.installMCPServerFromGithub(url)
.then((resp) => {
const taskId = resp.task_id;
@@ -374,8 +383,8 @@ export default function PluginConfigPage() {
{t('plugins.marketplace')}
</TabsTrigger>
)}
<TabsTrigger value="mcp" className="px-6 py-4 cursor-pointer">
MCP
<TabsTrigger value="mcp-market" className="px-6 py-4 cursor-pointer">
{t('mcp.marketplace')}
</TabsTrigger>
</TabsList>
@@ -393,11 +402,37 @@ export default function PluginConfigPage() {
<DropdownMenuTrigger asChild>
<Button variant="default" className="px-6 py-4 cursor-pointer">
<PlusIcon className="w-4 h-4" />
{t('plugins.install')}
{activeTab === 'mcp-market' ? t('mcp.add') : t('plugins.install')}
<ChevronDownIcon className="ml-2 w-4 h-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
{activeTab === 'mcp-market' ? (
<>
{/* <DropdownMenuItem
onClick={() => {
setActiveTab('mcp-market');
setMcpMarketInstallModalOpen(true);
setMcpInstallStatus(PluginInstallStatus.WAIT_INPUT);
setMcpInstallError(null);
setMcpGithubURL('');
}}
>
<PlusIcon className="w-4 h-4" />
{t('mcp.installFromGithub')}
</DropdownMenuItem> */}
<DropdownMenuItem
onClick={() => {
setActiveTab('mcp-market');
// setMcpMarketInstallModalOpen(true);
}}
>
<PlusIcon className="w-4 h-4" />
{t('mcp.createServer')}
</DropdownMenuItem>
</>
) : (
<>
<DropdownMenuItem onClick={handleFileSelect}>
<UploadIcon className="w-4 h-4" />
{t('plugins.uploadLocal')}
@@ -412,6 +447,8 @@ export default function PluginConfigPage() {
{t('plugins.marketplace')}
</DropdownMenuItem>
)}
</>
)}
</DropdownMenuContent>
</DropdownMenu>
</div>
@@ -531,29 +568,90 @@ export default function PluginConfigPage() {
}}
/> */}
{/* MCP Server 安装对话框 */}
{/* <Dialog
open={mcpMarketInstallModalOpen}
onOpenChange={setMcpMarketInstallModalOpen}
{/* 通过sse安装MCP服务器 */}
<Dialog
open={mcpSSEInstallModalOpen}
onOpenChange={setMcpSSEInstallModalOpen}
>
<DialogContent className="w-[500px] p-6">
<DialogContent className="w-[520px] p-6 bg-white dark:bg-[#1a1a1e]">
<DialogHeader>
<DialogTitle className="flex items-center gap-4">
<GithubIcon className="size-6" />
<span>{t('mcp.installFromGithub')}</span>
<Download className="size-6" />
<span>{t('mcp.installFromSSE')}</span>
</DialogTitle>
</DialogHeader>
{mcpInstallStatus === PluginInstallStatus.WAIT_INPUT && (
<div className="mt-4">
<p className="mb-2">{t('mcp.onlySupportGithub')}</p>
<div>
<div>
<label className='text-sm text-muted-foreground block mb-1'>
{t('mcp.name')}
</label>
<Input
placeholder={t('mcp.enterGithubLink')}
value={mcpGithubURL}
onChange={(e) => setMcpGithubURL(e.target.value)}
className="mb-4"
placeholder='mcp.nameExplained'
value={mcpName}
onChange={(e) => setMcpName(e.target.value)}
className='mb-1'
/>
</div>
)}
</div>
<div>
<div>
<label className="text-sm text-muted-foreground block mb-1">
{t('mcp.description')}
</label>
<Input
placeholder={t('mcp.descriptionExplain')}
value={mcpDescription}
onChange={(e) => setMcpDescription(e.target.value)}
className='mb-1'
/>
</div>
</div>
{/* form fields */}
<div className="mt-4 space-y-3">
<div>
<label className="text-sm text-muted-foreground block mb-1">
{t('mcp.sseUrl')}
</label>
<Input
placeholder={t('mcp.enterSSELink')}
value={mcpSSEURL}
onChange={(e) => setMcpSSEURL(e.target.value)}
className="mb-1"
/>
</div>
</div>
<div className="mt-4">
<div>
<label className="text-sm text-muted-foreground block mb-1">
{t('mcp.headers')}
</label>
<Input
placeholder={t('mcp.headers')}
value={mcpSSEHeaders}
onChange={(e) => setMcpSSEHeaders(e.target.value)}
className="mb-1"
/>
</div>
</div>
<div className='mt-4'>
<div>
<label className='text-sm text-muted-foreground block mb-1'>
{t('mcp.timeout')}
</label>
<Input
placeholder={t('mcp.enterTimeout')}
value={mcpTimeout || '60'}
onChange={(e) => setMcpTimeout(e.target.value)}
className="mb-1"
/>
</div>
</div>
{mcpInstallStatus === PluginInstallStatus.INSTALLING && (
<div className="mt-4">
<p className="mb-2">{t('mcp.installing')}</p>
@@ -565,31 +663,218 @@ export default function PluginConfigPage() {
<p className="mb-2 text-red-500">{mcpInstallError}</p>
</div>
)}
<DialogFooter>
{mcpInstallStatus === PluginInstallStatus.WAIT_INPUT && (
{(mcpInstallStatus === PluginInstallStatus.WAIT_INPUT ||
mcpInstallStatus === PluginInstallStatus.ERROR) && (
<>
<Button
variant="outline"
onClick={() => setMcpMarketInstallModalOpen(false)}
onClick={() => {
setMcpSSEInstallModalOpen(false)
setMcpInstallStatus(PluginInstallStatus.WAIT_INPUT);
setMcpInstallError(null);
setMcpInstallConfig(null);
setMcpSSEURL('')
}}
>
{t('common.cancel')}
</Button>
<Button onClick={handleMcpModalConfirm}>
<Button
onClick={() => {
// basic validation
if (!mcpSSEURL) {
toast.error(t('mcp.urlRequired'));
return;
}
if (!mcpName) (
toast.error(t(''))
)
const configToSend = {
};
handleMcpModalConfirm();
// call installer (for now installMcpServer will log config and call backend with url only)
installMcpServer(mcpGithubURL, configToSend);
}}
>
{t('common.confirm')}
</Button>
</>
)}
{mcpInstallStatus === PluginInstallStatus.ERROR && (
<Button
variant="default"
onClick={() => setMcpMarketInstallModalOpen(false)}
</DialogFooter>
</DialogContent>
</Dialog>
{/* MCP Server 从github安装对话框表单 */}
<Dialog
open={mcpMarketInstallModalOpen}
onOpenChange={setMcpMarketInstallModalOpen}
>
{t('common.close')}
<DialogContent className="w-[520px] p-6 bg-white dark:bg-[#1a1a1e]">
<DialogHeader>
<DialogTitle className="flex items-center gap-4">
<Download className="size-6" />
<span>{t('mcp.installFromGithub')}</span>
</DialogTitle>
</DialogHeader>
{/* form fields */}
<div className="mt-4 space-y-3">
<div>
<label className="text-sm text-muted-foreground block mb-1">
{t('mcp.githubUrl')}
</label>
<Input
placeholder={t('mcp.enterGithubLink')}
value={mcpGithubURL}
onChange={(e) => setMcpGithubURL(e.target.value)}
className="mb-1"
/>
</div>
<div>
<label className="text-sm text-muted-foreground block mb-1">
{t('mcp.displayName', 'Display Name')}
</label>
<Input
placeholder={t('mcp.displayNamePlaceholder', 'My MCP Server')}
value={(mcpInstallConfig as any)?.displayName || ''}
onChange={(e) =>
setMcpInstallConfig((c) => ({ ...(c || {}), displayName: e.target.value }))
}
className="mb-1"
/>
</div>
<div className="flex gap-2">
<div className="flex-1">
<label className="text-sm text-muted-foreground block mb-1">
{t('mcp.port', 'Port')}
</label>
<Input
placeholder="8080"
value={(mcpInstallConfig as any)?.port || ''}
onChange={(e) =>
setMcpInstallConfig((c) => ({ ...(c || {}), port: e.target.value }))
}
/>
</div>
<div className="flex-1">
<label className="text-sm text-muted-foreground block mb-1">
{t('mcp.env', 'Environment')}
</label>
<Input
placeholder="production"
value={(mcpInstallConfig as any)?.env || ''}
onChange={(e) =>
setMcpInstallConfig((c) => ({ ...(c || {}), env: e.target.value }))
}
/>
</div>
</div>
<div>
<label className="text-sm text-muted-foreground block mb-1">
{t('mcp.adminToken', 'Admin Token')}
</label>
<Input
placeholder={t('mcp.adminTokenPlaceholder', 'secret-token')}
value={(mcpInstallConfig as any)?.adminToken || ''}
onChange={(e) =>
setMcpInstallConfig((c) => ({ ...(c || {}), adminToken: e.target.value }))
}
/>
</div>
<div>
<label className="text-sm text-muted-foreground block mb-1">
{t('mcp.extraConfig', 'Extra JSON Config')}
</label>
<Input
placeholder='{"key":"value"}'
value={(mcpInstallConfig as any)?.extraConfig || ''}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
setMcpInstallConfig((c) => ({ ...(c || {}), extraConfig: e.target.value }))
}
/>
<p className="text-xs text-muted-foreground mt-1">
{t('mcp.extraConfigHint', 'Optional JSON string for advanced config')}
</p>
</div>
</div>
{mcpInstallStatus === PluginInstallStatus.INSTALLING && (
<div className="mt-4">
<p className="mb-2">{t('mcp.installing')}</p>
</div>
)}
{mcpInstallStatus === PluginInstallStatus.ERROR && (
<div className="mt-4">
<p className="mb-2">{t('mcp.installFailed')}</p>
<p className="mb-2 text-red-500">{mcpInstallError}</p>
</div>
)}
<DialogFooter>
{(mcpInstallStatus === PluginInstallStatus.WAIT_INPUT ||
mcpInstallStatus === PluginInstallStatus.ERROR) && (
<>
<Button
variant="outline"
onClick={() => {
setMcpMarketInstallModalOpen(false);
setMcpInstallStatus(PluginInstallStatus.WAIT_INPUT);
setMcpInstallError(null);
setMcpInstallConfig(null);
setMcpGithubURL('');
}}
>
{t('common.cancel')}
</Button>
<Button
onClick={() => {
// basic validation
if (!mcpGithubURL) {
toast.error(t('mcp.urlRequired'));
return;
}
// try parse extraConfig JSON
let parsedExtra: any = undefined;
try {
if (mcpInstallConfig?.extraConfig) {
parsedExtra = JSON.parse(mcpInstallConfig.extraConfig);
}
} catch (err) {
toast.error(t('mcp.extraConfigInvalid'));
return;
}
const configToSend = {
displayName: mcpInstallConfig?.displayName,
port: mcpInstallConfig?.port,
env: mcpInstallConfig?.env,
adminToken: mcpInstallConfig?.adminToken,
extraConfig: parsedExtra,
};
handleMcpModalConfirm();
// call installer (for now installMcpServer will log config and call backend with url only)
installMcpServer(mcpGithubURL, configToSend);
}}
>
{t('common.confirm')}
</Button>
</>
)}
</DialogFooter>
</DialogContent>
</Dialog> */}
</Dialog>
</div>
);
}

View File

@@ -333,6 +333,8 @@ const zhHans = {
installFromGithub: '从Github安装MCP服务器',
onlySupportGithub: '目前仅支持从Github安装MCP服务器',
enterGithubLink: '输入Github仓库链接',
add: '添加',
name:'名称',
},
pipelines: {
title: '流水线',