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
+333 -48
View File
@@ -34,7 +34,7 @@ import {
DialogFooter, DialogFooter,
} from '@/components/ui/dialog'; } from '@/components/ui/dialog';
import { Input } from '@/components/ui/input'; 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 { httpClient } from '@/app/infra/http/HttpClient';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@@ -60,11 +60,16 @@ export default function PluginConfigPage() {
// const [mcpModalOpen, setMcpModalOpen] = useState(false); // const [mcpModalOpen, setMcpModalOpen] = useState(false);
const [mcpMarketInstallModalOpen, setMcpMarketInstallModalOpen] = const [mcpMarketInstallModalOpen, setMcpMarketInstallModalOpen] =
useState(false); useState(false);
const [mcpSSEInstallModalOpen, setMcpSSEInstallModalOpen] = useState(false);
const [mcpDescription,setMcpDescription] = useState('');
const [pluginInstallStatus, setPluginInstallStatus] = const [pluginInstallStatus, setPluginInstallStatus] =
useState<PluginInstallStatus>(PluginInstallStatus.WAIT_INPUT); useState<PluginInstallStatus>(PluginInstallStatus.WAIT_INPUT);
const [mcpInstallStatus, setMcpInstallStatus] = useState<PluginInstallStatus>( const [mcpInstallStatus, setMcpInstallStatus] = useState<PluginInstallStatus>(
PluginInstallStatus.WAIT_INPUT, PluginInstallStatus.WAIT_INPUT,
); );
const [mcpSSEHeaders,setMcpSSEHeaders] = useState('')
const [mcpName,setMcpName] = useState('')
const [mcpTimeout,setMcpTimeout] = useState('')
const [installError, setInstallError] = useState<string | null>(null); const [installError, setInstallError] = useState<string | null>(null);
const [mcpInstallError, setMcpInstallError] = useState<string | null>(null); const [mcpInstallError, setMcpInstallError] = useState<string | null>(null);
const [githubURL, setGithubURL] = useState(''); const [githubURL, setGithubURL] = useState('');
@@ -119,6 +124,8 @@ export default function PluginConfigPage() {
}, 1000); }, 1000);
} }
const [mcpGithubURL, setMcpGithubURL] = useState(''); const [mcpGithubURL, setMcpGithubURL] = useState('');
const [mcpSSEURL, setMcpSSEURL] = useState('');
const [mcpInstallConfig, setMcpInstallConfig] = useState<Record<string, any> | null>(null);
const pluginInstalledRef = useRef<PluginInstalledComponentRef>(null); const pluginInstalledRef = useRef<PluginInstalledComponentRef>(null);
const mcpComponentRef = useRef<MCPComponentRef>(null); const mcpComponentRef = useRef<MCPComponentRef>(null);
@@ -309,10 +316,12 @@ export default function PluginConfigPage() {
return renderPluginConnectionErrorState(); return renderPluginConnectionErrorState();
} }
function installMcpServer(url: string) { function installMcpServer(url: string, config?: Record<string, any>) {
setMcpInstallStatus(PluginInstallStatus.INSTALLING); setMcpInstallStatus(PluginInstallStatus.INSTALLING);
httpClient // NOTE: backend currently only accepts url. If backend accepts config in future,
.installMCPServerFromGithub(url) // replace this call with: httpClient.installMCPServerFromGithub(url, config)
console.log('installing mcp server with config:', config);
httpClient.installMCPServerFromGithub(url)
.then((resp) => { .then((resp) => {
const taskId = resp.task_id; const taskId = resp.task_id;
@@ -374,9 +383,9 @@ export default function PluginConfigPage() {
{t('plugins.marketplace')} {t('plugins.marketplace')}
</TabsTrigger> </TabsTrigger>
)} )}
<TabsTrigger value="mcp" className="px-6 py-4 cursor-pointer"> <TabsTrigger value="mcp-market" className="px-6 py-4 cursor-pointer">
MCP {t('mcp.marketplace')}
</TabsTrigger> </TabsTrigger>
</TabsList> </TabsList>
<div className="flex flex-row justify-end items-center"> <div className="flex flex-row justify-end items-center">
@@ -393,24 +402,52 @@ export default function PluginConfigPage() {
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<Button variant="default" className="px-6 py-4 cursor-pointer"> <Button variant="default" className="px-6 py-4 cursor-pointer">
<PlusIcon className="w-4 h-4" /> <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" /> <ChevronDownIcon className="ml-2 w-4 h-4" />
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="end"> <DropdownMenuContent align="end">
<DropdownMenuItem onClick={handleFileSelect}> {activeTab === 'mcp-market' ? (
<UploadIcon className="w-4 h-4" /> <>
{t('plugins.uploadLocal')} {/* <DropdownMenuItem
</DropdownMenuItem> onClick={() => {
{systemInfo.enable_marketplace && ( setActiveTab('mcp-market');
<DropdownMenuItem setMcpMarketInstallModalOpen(true);
onClick={() => { setMcpInstallStatus(PluginInstallStatus.WAIT_INPUT);
setActiveTab('market'); setMcpInstallError(null);
}} setMcpGithubURL('');
> }}
<StoreIcon className="w-4 h-4" /> >
{t('plugins.marketplace')} <PlusIcon className="w-4 h-4" />
</DropdownMenuItem> {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')}
</DropdownMenuItem>
{systemInfo.enable_marketplace && (
<DropdownMenuItem
onClick={() => {
setActiveTab('market');
}}
>
<StoreIcon className="w-4 h-4" />
{t('plugins.marketplace')}
</DropdownMenuItem>
)}
</>
)} )}
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
@@ -530,30 +567,91 @@ export default function PluginConfigPage() {
pluginInstalledRef.current?.refreshPluginList(); pluginInstalledRef.current?.refreshPluginList();
}} }}
/> */} /> */}
{/* MCP Server 安装对话框 */} {/* 通过sse安装MCP服务器 */}
{/* <Dialog <Dialog
open={mcpMarketInstallModalOpen} open={mcpSSEInstallModalOpen}
onOpenChange={setMcpMarketInstallModalOpen} onOpenChange={setMcpSSEInstallModalOpen}
> >
<DialogContent className="w-[500px] p-6"> <DialogContent className="w-[520px] p-6 bg-white dark:bg-[#1a1a1e]">
<DialogHeader> <DialogHeader>
<DialogTitle className="flex items-center gap-4"> <DialogTitle className="flex items-center gap-4">
<GithubIcon className="size-6" /> <Download className="size-6" />
<span>{t('mcp.installFromGithub')}</span> <span>{t('mcp.installFromSSE')}</span>
</DialogTitle> </DialogTitle>
</DialogHeader> </DialogHeader>
{mcpInstallStatus === PluginInstallStatus.WAIT_INPUT && (
<div className="mt-4"> <div>
<p className="mb-2">{t('mcp.onlySupportGithub')}</p> <div>
<label className='text-sm text-muted-foreground block mb-1'>
{t('mcp.name')}
</label>
<Input
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 <Input
placeholder={t('mcp.enterGithubLink')} placeholder={t('mcp.descriptionExplain')}
value={mcpGithubURL} value={mcpDescription}
onChange={(e) => setMcpGithubURL(e.target.value)} onChange={(e) => setMcpDescription(e.target.value)}
className="mb-4" className='mb-1'
/> />
</div> </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 && ( {mcpInstallStatus === PluginInstallStatus.INSTALLING && (
<div className="mt-4"> <div className="mt-4">
<p className="mb-2">{t('mcp.installing')}</p> <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> <p className="mb-2 text-red-500">{mcpInstallError}</p>
</div> </div>
)} )}
<DialogFooter> <DialogFooter>
{mcpInstallStatus === PluginInstallStatus.WAIT_INPUT && ( {(mcpInstallStatus === PluginInstallStatus.WAIT_INPUT ||
mcpInstallStatus === PluginInstallStatus.ERROR) && (
<> <>
<Button <Button
variant="outline" variant="outline"
onClick={() => setMcpMarketInstallModalOpen(false)} onClick={() => {
setMcpSSEInstallModalOpen(false)
setMcpInstallStatus(PluginInstallStatus.WAIT_INPUT);
setMcpInstallError(null);
setMcpInstallConfig(null);
setMcpSSEURL('')
}}
> >
{t('common.cancel')} {t('common.cancel')}
</Button> </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')} {t('common.confirm')}
</Button> </Button>
</> </>
)} )}
{mcpInstallStatus === PluginInstallStatus.ERROR && ( </DialogFooter>
<Button </DialogContent>
variant="default" </Dialog>
onClick={() => setMcpMarketInstallModalOpen(false)}
> {/* MCP Server 从github安装对话框(表单) */}
{t('common.close')} <Dialog
</Button> open={mcpMarketInstallModalOpen}
onOpenChange={setMcpMarketInstallModalOpen}
>
<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> </DialogFooter>
</DialogContent> </DialogContent>
</Dialog> */} </Dialog>
</div> </div>
); );
} }
+2
View File
@@ -333,6 +333,8 @@ const zhHans = {
installFromGithub: '从Github安装MCP服务器', installFromGithub: '从Github安装MCP服务器',
onlySupportGithub: '目前仅支持从Github安装MCP服务器', onlySupportGithub: '目前仅支持从Github安装MCP服务器',
enterGithubLink: '输入Github仓库链接', enterGithubLink: '输入Github仓库链接',
add: '添加',
name:'名称',
}, },
pipelines: { pipelines: {
title: '流水线', title: '流水线',