Merge branch 'feature/1.7-cxx' into 'main'

feature/1.7-MCP

See merge request apipark/APIPark!296
This commit is contained in:
lichunxian
2025-04-10 16:11:01 +08:00
2 changed files with 123 additions and 99 deletions
@@ -1,4 +1,4 @@
import { App, Card, Select } from 'antd'
import { App, Button, Card, Empty, Select } from 'antd'
import { $t } from '@common/locales/index.ts'
import { Icon } from '@iconify/react/dist/iconify.js'
import { useEffect, useState } from 'react'
@@ -8,7 +8,8 @@ import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const'
import { useFetch } from '@common/hooks/http'
import { useConnection } from './hook/useConnection'
import { ClientRequest, Tool, ListToolsResultSchema } from '@modelcontextprotocol/sdk/types.js'
import { z } from "zod";
import { z } from 'zod'
import { useNavigate } from 'react-router-dom'
type ConfigList = {
openApi?: {
@@ -30,19 +31,26 @@ type ApiKeyItem = {
value: string
}
const IntegrationAIContainer = ({ type, handleToolsChange }: { type: 'global' | 'service'; handleToolsChange: (value: Tool[]) => void }) => {
const IntegrationAIContainer = ({
type,
handleToolsChange
}: {
type: 'global' | 'service'
handleToolsChange: (value: Tool[]) => void
}) => {
const [activeTab, setActiveTab] = useState('mcp')
const { message } = App.useApp()
const [configContent, setConfigContent] = useState<string>('')
const [apiKey, setApiKey] = useState<string>('')
const [apiKeyList, setApiKeyList] = useState<{ value: string; label: string }[]>([])
const [mcpServerUrl, setMcpServerUrl] = useState<string>('')
const navigator = useNavigate()
const [errors, setErrors] = useState<Record<string, string | null>>({
resources: null,
prompts: null,
tools: null,
});
tools: null
})
const [tabContent, setTabContent] = useState<ConfigList>({
mcp: {
title: $t('MCP 配置'),
@@ -113,6 +121,10 @@ const IntegrationAIContainer = ({ type, handleToolsChange }: { type: 'global' |
})
}
const addKey = () => {
navigator('/mcpKey')
}
/**
* 获取 API Key 列表
*/
@@ -144,43 +156,39 @@ const IntegrationAIContainer = ({ type, handleToolsChange }: { type: 'global' |
}
const clearError = (tabKey: keyof typeof errors) => {
setErrors((prev) => ({ ...prev, [tabKey]: null }));
};
setErrors((prev) => ({ ...prev, [tabKey]: null }))
}
const makeRequest = async <T extends z.ZodType>(
request: ClientRequest,
schema: T,
tabKey?: keyof typeof errors,
) => {
const makeRequest = async <T extends z.ZodType>(request: ClientRequest, schema: T, tabKey?: keyof typeof errors) => {
try {
const response = await makeConnectionRequest(request, schema);
const response = await makeConnectionRequest(request, schema)
if (tabKey !== undefined) {
clearError(tabKey);
clearError(tabKey)
}
return response;
return response
} catch (e) {
const errorString = (e as Error).message ?? String(e);
const errorString = (e as Error).message ?? String(e)
if (tabKey !== undefined) {
setErrors((prev) => ({
...prev,
[tabKey]: errorString,
}));
[tabKey]: errorString
}))
}
throw e;
throw e
}
};
}
const listTools = async () => {
const response = await makeRequest(
{
method: "tools/list" as const,
params: {},
method: 'tools/list' as const,
params: {}
},
ListToolsResultSchema,
"tools",
);
'tools'
)
handleToolsChange(response.tools)
};
}
const {
connectionStatus,
@@ -192,13 +200,13 @@ const IntegrationAIContainer = ({ type, handleToolsChange }: { type: 'global' |
handleCompletion,
completionsSupported,
connect: connectMcpServer,
disconnect: disconnectMcpServer,
disconnect: disconnectMcpServer
} = useConnection({
transportType: 'sse',
sseUrl: '',
proxyServerUrl: mcpServerUrl,
requestTimeout: 1000,
});
requestTimeout: 1000
})
useEffect(() => {
if (type === 'global') {
@@ -209,12 +217,10 @@ const IntegrationAIContainer = ({ type, handleToolsChange }: { type: 'global' |
getKeysList()
}, [])
useEffect(() => {
if (apiKey) {
if (activeTab === 'openApi' && tabContent?.openApi?.configContent) {
setConfigContent(tabContent?.openApi?.configContent?.replace('{your_api_key}', apiKey) || '')
} else if (activeTab === 'mcp' && tabContent?.mcp?.configContent) {
setConfigContent(tabContent.mcp.configContent?.replace('{your_api_key}', apiKey) || '')
}
if (activeTab === 'openApi' && tabContent?.openApi?.configContent) {
setConfigContent(tabContent?.openApi?.configContent?.replace('{your_api_key}', apiKey || '{your_api_key}'))
} else if (activeTab === 'mcp' && tabContent?.mcp?.configContent) {
setConfigContent(tabContent.mcp.configContent?.replace('{your_api_key}', apiKey || '{your_api_key}'))
}
}, [apiKey, activeTab, tabContent])
@@ -308,34 +314,42 @@ const IntegrationAIContainer = ({ type, handleToolsChange }: { type: 'global' |
{activeTab === 'mcp' && (
<>
<div className="tab-content font-semibold my-[10px]">API Key</div>
<Select value={apiKey} className="w-full" onChange={handleChange} options={apiKeyList} />
<Card
style={{ borderRadius: '5px' }}
className="w-full mt-[5px] "
classNames={{
body: 'p-[5px]'
}}
>
<div className="relative h-[25px]">
{apiKey}
<IconButton
name="copy"
onClick={() => handleCopy(configContent)}
sx={{
position: 'absolute',
top: '0px',
right: '5px',
color: '#999',
transition: 'none',
'&.MuiButtonBase-root:hover': {
background: 'transparent',
color: '#3D46F2',
transition: 'none'
}
{apiKeyList.length ? (
<>
<Select value={apiKey} className="w-full" onChange={handleChange} options={apiKeyList} />
<Card
style={{ borderRadius: '5px' }}
className="w-full mt-[5px] "
classNames={{
body: 'p-[5px]'
}}
></IconButton>
</div>
</Card>
>
<div className="relative h-[25px]">
{apiKey}
<IconButton
name="copy"
onClick={() => handleCopy(configContent)}
sx={{
position: 'absolute',
top: '0px',
right: '5px',
color: '#999',
transition: 'none',
'&.MuiButtonBase-root:hover': {
background: 'transparent',
color: '#3D46F2',
transition: 'none'
}
}}
></IconButton>
</div>
</Card>
</>
) : (
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description={""}>
<Button onClick={addKey} type="primary">{$t('新增 API Key')}</Button>
</Empty>
)}
</>
)}
</Card>
@@ -1,7 +1,7 @@
import InsidePage from '@common/components/aoplatform/InsidePage'
import { IconButton } from '@common/components/postcat/api/IconButton'
import { $t } from '@common/locales/index.ts'
import { Button, Card, App } from 'antd'
import { Button, Card, App, Empty } from 'antd'
import { useFetch } from '@common/hooks/http'
import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const'
import { useEffect, useRef, useState } from 'react'
@@ -139,17 +139,36 @@ const McpKeyContainer = () => {
{$t('新增 API Key')}
</Button>
<div className="api-key-container mt-[20px]">
{keys.map((key, index) => (
<Card style={{ width: 600, borderRadius: '10px' }} key={index} className="mt-[10px]">
<div className="flex">
<div className="flex-1">
<p className="text-[14px] font-bold">{key.name}</p>
<div className="flex">
<span className="h-[26px] leading-[28px]">{key.value}</span>
{keys.length ? (
keys.map((key, index) => (
<Card style={{ width: 600, borderRadius: '10px' }} key={index} className="mt-[10px]">
<div className="flex">
<div className="flex-1">
<p className="text-[14px] font-bold">{key.name}</p>
<div className="flex">
<span className="h-[26px] leading-[28px]">{key.value}</span>
<IconButton
name="copy"
onClick={() => {
copyCode(key?.value)
}}
sx={{
color: '#333',
transition: 'none',
'&.MuiButtonBase-root:hover': {
background: 'transparent',
color: '#3D46F2',
transition: 'none'
}
}}
></IconButton>
</div>
</div>
<div className="w-[30px] flex justify-center items-center">
<IconButton
name="copy"
name="edit"
onClick={() => {
copyCode(key?.value)
editKey(key)
}}
sx={{
color: '#333',
@@ -161,35 +180,26 @@ const McpKeyContainer = () => {
}
}}
></IconButton>
<IconButton
name="delete"
onClick={() => {
deleteKey(key.id)
}}
sx={{
color: '#333',
transition: 'none',
'&.MuiButtonBase-root:hover': { background: 'transparent', color: 'red', transition: 'none' }
}}
></IconButton>
</div>
</div>
<div className="w-[30px] flex justify-center items-center">
<IconButton
name="edit"
onClick={() => {
editKey(key)
}}
sx={{
color: '#333',
transition: 'none',
'&.MuiButtonBase-root:hover': { background: 'transparent', color: '#3D46F2', transition: 'none' }
}}
></IconButton>
<IconButton
name="delete"
onClick={() => {
deleteKey(key.id)
}}
sx={{
color: '#333',
transition: 'none',
'&.MuiButtonBase-root:hover': { background: 'transparent', color: 'red', transition: 'none' }
}}
></IconButton>
</div>
</div>
</Card>
))}
</Card>
))
) : (
<>
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE}/>
</>
)}
</div>
</InsidePage>
</>