mirror of
https://github.com/langbot-app/LangBot.git
synced 2025-11-25 19:37:36 +08:00
feat: add dialog
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -333,6 +333,8 @@ const zhHans = {
|
||||
installFromGithub: '从Github安装MCP服务器',
|
||||
onlySupportGithub: '目前仅支持从Github安装MCP服务器',
|
||||
enterGithubLink: '输入Github仓库链接',
|
||||
add: '添加',
|
||||
name:'名称',
|
||||
},
|
||||
pipelines: {
|
||||
title: '流水线',
|
||||
|
||||
Reference in New Issue
Block a user