mirror of
https://github.com/APIParkLab/APIPark.git
synced 2026-06-26 16:01:56 +08:00
@@ -56,6 +56,7 @@ export const TranslateWord = () => {
|
||||
{$t('无需审核:允许任何消费者调用该服务')}
|
||||
{$t('人工审核:仅允许通过人工审核的消费者调用该服务')}
|
||||
{$t('开启:AI Agent 等产品能够通过 MCP 方式调用服务')}
|
||||
{$t('总览')}
|
||||
{$t('永久')}
|
||||
{$t('否')}
|
||||
{$t('是')}
|
||||
|
||||
@@ -234,12 +234,16 @@ export default function ApiEdit({
|
||||
<>
|
||||
<Space.Compact className="w-full mb-btnybase">
|
||||
<Select
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
className="w-[15%] min-w-[100px]"
|
||||
value={apiInfo?.protocol || 'HTTP'}
|
||||
disabled={true}
|
||||
options={protocolOptionList}
|
||||
/>
|
||||
<Select
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
className="w-[15%] min-w-[100px]"
|
||||
value={apiInfo?.method}
|
||||
disabled={true}
|
||||
|
||||
@@ -81,6 +81,7 @@ export type MatchItem = {
|
||||
export type EntityItem = {
|
||||
id: string
|
||||
name: string
|
||||
isSubscribed: boolean
|
||||
}
|
||||
|
||||
export type DynamicMenuItem = {
|
||||
|
||||
@@ -99,10 +99,31 @@ const mockData = [
|
||||
icon: 'ic:baseline-bar-chart',
|
||||
children: [
|
||||
{
|
||||
name: '运行视图',
|
||||
key: 'analytics',
|
||||
path: '/analytics',
|
||||
icon: 'ic:baseline-bar-chart',
|
||||
name: '总览',
|
||||
key: 'analyticsTotal',
|
||||
path: '/analytics/total',
|
||||
icon: 'material-symbols:bar-chart',
|
||||
access: 'system.analysis.run_view.view'
|
||||
},
|
||||
{
|
||||
name: '服务',
|
||||
key: 'analyticsSubscriber',
|
||||
path: '/analytics/subscriber/list',
|
||||
icon: 'ic:baseline-blinds-closed',
|
||||
access: 'system.analysis.run_view.view'
|
||||
},
|
||||
{
|
||||
name: '消费者',
|
||||
key: 'analyticsProvider',
|
||||
path: '/analytics/provider/list',
|
||||
icon: 'ic:baseline-apps',
|
||||
access: 'system.analysis.run_view.view'
|
||||
},
|
||||
{
|
||||
name: 'API',
|
||||
key: 'analyticsApi',
|
||||
path: '/analytics/api/list',
|
||||
icon: 'gravity-ui:plug-connection',
|
||||
access: 'system.analysis.run_view.view'
|
||||
}
|
||||
],
|
||||
|
||||
@@ -923,5 +923,9 @@
|
||||
"Kba68dfc1": "Add API Settings",
|
||||
"Kc84dbd1a": "API Portal",
|
||||
"Ke93388fd": "Edit API",
|
||||
"K84aabfd4": "Add API"
|
||||
"K84aabfd4": "Add API",
|
||||
"K71ed51fa": "Please subscribe to the service first",
|
||||
"K1bec8cbe": "Select API Key",
|
||||
"K5611e01e": "This consumer is already subscribed",
|
||||
"Kaf9e8011": "Overview"
|
||||
}
|
||||
|
||||
@@ -945,5 +945,9 @@
|
||||
"Kba68dfc1": "API設定の追加",
|
||||
"Kc84dbd1a": "APIポータル",
|
||||
"Ke93388fd": "APIの編集",
|
||||
"K84aabfd4": "APIの追加"
|
||||
"K84aabfd4": "APIの追加",
|
||||
"K71ed51fa": "このサービスに先にサブスクリプションしてください",
|
||||
"K1bec8cbe": "APIキーを選択してください",
|
||||
"K5611e01e": "この消費者はすでに購読しています",
|
||||
"Kaf9e8011": "概要"
|
||||
}
|
||||
|
||||
@@ -876,5 +876,9 @@
|
||||
"Kba68dfc1": "添加 API 设置",
|
||||
"Kc84dbd1a": "API 门户",
|
||||
"Ke93388fd": "编辑 API",
|
||||
"K84aabfd4": "添加 API"
|
||||
"K84aabfd4": "添加 API",
|
||||
"K71ed51fa": "请先订阅该服务",
|
||||
"K1bec8cbe": "选择 API Key",
|
||||
"K5611e01e": "该消费者已订阅",
|
||||
"Kaf9e8011": "总览"
|
||||
}
|
||||
|
||||
@@ -945,5 +945,9 @@
|
||||
"Kba68dfc1": "新增 API 設定",
|
||||
"Kc84dbd1a": "API 門戶",
|
||||
"Ke93388fd": "編輯 API",
|
||||
"K84aabfd4": "新增 API"
|
||||
"K84aabfd4": "新增 API",
|
||||
"K71ed51fa": "請先訂閱該服務",
|
||||
"K1bec8cbe": "選擇 API Key",
|
||||
"K5611e01e": "該消費者已訂閱",
|
||||
"Kaf9e8011": "總覽"
|
||||
}
|
||||
|
||||
@@ -359,7 +359,7 @@ export const SERVICE_APPROVAL_OPTIONS = [
|
||||
{ label: '人工审核:仅允许通过人工审核的消费者调用该服务', value: 'manual' }
|
||||
]
|
||||
export const MCP_OPTIONS = [
|
||||
{ label: '关闭', value: false },
|
||||
{ label: '禁用', value: false },
|
||||
{ label: '开启:AI Agent 等产品能够通过 MCP 方式调用服务', value: true }
|
||||
]
|
||||
export const SERVICE_KIND_OPTIONS = [
|
||||
|
||||
@@ -1109,6 +1109,11 @@ p{
|
||||
width: 16px !important;
|
||||
justify-content:center;
|
||||
}
|
||||
.ant-select .ant-select-clear {
|
||||
height:16px !important;
|
||||
width: 16px !important;
|
||||
top: 45%;
|
||||
}
|
||||
|
||||
.ant-table-wrapper .ant-table{
|
||||
scrollbar-color: none !important;
|
||||
|
||||
@@ -232,7 +232,7 @@ const AiServiceInsidePage: FC = () => {
|
||||
<InsidePage
|
||||
pageTitle={aiServiceInfo?.name || '-'}
|
||||
tagList={[
|
||||
...(aiServiceInfo?.enable_mcp ? [{ label: 'MCP', color: '#ffc107', className: 'text-[#000]' }] : []),
|
||||
...(aiServiceInfo?.enable_mcp ? [{ label: 'MCP', color: '#FFF0C1', className: 'text-[#000]' }] : []),
|
||||
{
|
||||
label: (
|
||||
<Paragraph className="mb-0" copyable={serviceId ? { text: serviceId } : false}>
|
||||
|
||||
@@ -163,6 +163,8 @@ const AiServiceRouterModelConfig = forwardRef<AiServiceRouterModelConfigHandle,
|
||||
>
|
||||
<Form.Item<AiServiceRouterModelConfigField> label={$t('模型类型')} name="type" rules={[{ required: true }]}>
|
||||
<Select
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
className="w-INPUT_NORMAL"
|
||||
placeholder={$t(PLACEHOLDER.select)}
|
||||
options={modelTypeList}
|
||||
|
||||
@@ -390,6 +390,8 @@ const AiSettingModalContent = forwardRef<AiSettingModalContentHandle, AiSettingM
|
||||
{source === 'guide' && (
|
||||
<Form.Item label={$t('所属团队')} name="team" className="mt-[16px]" rules={[{ required: true }]}>
|
||||
<Select
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
className="w-INPUT_NORMAL"
|
||||
placeholder={$t(PLACEHOLDER.input)}
|
||||
options={teamList}
|
||||
|
||||
@@ -181,6 +181,8 @@ const LocalAiDeploy = forwardRef<LocalAiDeployHandle, any>((props: any, ref: any
|
||||
</Form.Item>
|
||||
<Form.Item label={$t('所属团队')} name="team" className="mt-[16px]" rules={[{ required: true }]}>
|
||||
<Select
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
className="w-INPUT_NORMAL"
|
||||
placeholder={$t(PLACEHOLDER.input)}
|
||||
options={teamList}
|
||||
|
||||
@@ -106,6 +106,8 @@ const RestAIDeploy = forwardRef<RestAIDeployHandle, any>((props: any, ref: any)
|
||||
</Form.Item>
|
||||
<Form.Item label={$t('所属团队')} name="team" className="mt-[16px]" rules={[{ required: true }]}>
|
||||
<Select
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
className="w-INPUT_NORMAL"
|
||||
placeholder={$t(PLACEHOLDER.input)}
|
||||
options={teamList}
|
||||
|
||||
@@ -171,6 +171,8 @@ const AddLoadBalancingModel = forwardRef<LoadBalancingHandle>((props, ref: any)
|
||||
>
|
||||
<Form.Item<LoadModelDetailData> label={$t('模型类型')} name="type" rules={[{ required: true }]}>
|
||||
<Select
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
className="w-INPUT_NORMAL"
|
||||
placeholder={$t(PLACEHOLDER.select)}
|
||||
options={modelTypeList}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { App, Button, Card, Empty, Select } from 'antd'
|
||||
import { App, Button, Card, CascaderProps, Empty, Select } from 'antd'
|
||||
import { $t } from '@common/locales/index.ts'
|
||||
import { Icon } from '@iconify/react/dist/iconify.js'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react'
|
||||
import ReactJson from 'react-json-view'
|
||||
import { IconButton } from '@common/components/postcat/api/IconButton'
|
||||
import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const'
|
||||
@@ -13,6 +13,7 @@ import { useNavigate } from 'react-router-dom'
|
||||
import { ServiceDetailType } from '@market/const/serviceHub/type'
|
||||
import useCopyToClipboard from '@common/hooks/copy'
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext'
|
||||
import { Cascader } from 'antd/lib'
|
||||
|
||||
type ConfigList = {
|
||||
openApi?: {
|
||||
@@ -33,37 +34,70 @@ type ApiKeyItem = {
|
||||
name: string
|
||||
value: string
|
||||
}
|
||||
interface Option {
|
||||
value: string
|
||||
label: string
|
||||
children?: Option[]
|
||||
}
|
||||
|
||||
const IntegrationAIContainer = ({
|
||||
type,
|
||||
handleToolsChange,
|
||||
customClassName,
|
||||
service,
|
||||
serviceId,
|
||||
currentTab
|
||||
}: {
|
||||
type ServiceApiKeyList = {
|
||||
id: string
|
||||
name: string
|
||||
apikeys: Array<{
|
||||
id: string
|
||||
name: string
|
||||
value: string
|
||||
expired: number
|
||||
}>
|
||||
}
|
||||
export interface IntegrationAIContainerRef {
|
||||
getServiceKeysList: () => void;
|
||||
}
|
||||
export interface IntegrationAIContainerProps {
|
||||
type: 'global' | 'service'
|
||||
handleToolsChange: (value: Tool[]) => void
|
||||
customClassName?: string
|
||||
service?: ServiceDetailType
|
||||
serviceId?: string
|
||||
currentTab?: string
|
||||
}) => {
|
||||
openModal?: (type: 'apply') => void
|
||||
}
|
||||
export const IntegrationAIContainer = forwardRef<IntegrationAIContainerRef, IntegrationAIContainerProps>(
|
||||
({
|
||||
type,
|
||||
handleToolsChange,
|
||||
customClassName,
|
||||
service,
|
||||
serviceId,
|
||||
currentTab,
|
||||
openModal
|
||||
}: IntegrationAIContainerProps, ref) => {
|
||||
/** 当前激活的标签 */
|
||||
const [activeTab, setActiveTab] = useState(type === 'service' ? 'openApi' : 'mcp')
|
||||
/** 弹窗组件 */
|
||||
const { message } = App.useApp()
|
||||
/** 配置内容 */
|
||||
const [configContent, setConfigContent] = useState<string>('')
|
||||
/** 当前选中 API Key */
|
||||
const [apiKey, setApiKey] = useState<string>('')
|
||||
const [apiKeyList, setApiKeyList] = useState<{ value: string; label: string }[]>([])
|
||||
/** API Key 列表 */
|
||||
const [apiKeyList, setApiKeyList] = useState<any[]>([])
|
||||
/** Cascader Key 列表 */
|
||||
const [cascaderKeyList, setCascaderKeyList] = useState<string[]>([])
|
||||
/** MCP 服务器地址 */
|
||||
const [mcpServerUrl, setMcpServerUrl] = useState<string>('')
|
||||
/** 全局状态 */
|
||||
const { state } = useGlobalContext()
|
||||
const navigator = useNavigate()
|
||||
/** 复制组件 */
|
||||
const { copyToClipboard } = useCopyToClipboard()
|
||||
/** 错误提示 */
|
||||
const [errors, setErrors] = useState<Record<string, string | null>>({
|
||||
resources: null,
|
||||
prompts: null,
|
||||
tools: null
|
||||
})
|
||||
|
||||
/** 标签内容 */
|
||||
const [tabContent, setTabContent] = useState<ConfigList>({
|
||||
mcp: {
|
||||
title: $t('MCP 配置'),
|
||||
@@ -71,8 +105,12 @@ const IntegrationAIContainer = ({
|
||||
apiKeys: []
|
||||
}
|
||||
})
|
||||
/** HTTP 请求 */
|
||||
const { fetchData } = useFetch()
|
||||
|
||||
/**
|
||||
* 初始化标签数据
|
||||
*/
|
||||
const initTabsData = () => {
|
||||
const params: ConfigList = {
|
||||
mcp: {
|
||||
@@ -103,9 +141,21 @@ const IntegrationAIContainer = ({
|
||||
}
|
||||
}
|
||||
|
||||
const handleChange = (value: string) => {
|
||||
/**
|
||||
* 选择 API Key
|
||||
* @param value
|
||||
*/
|
||||
const handleSelectChange = (value: string) => {
|
||||
setApiKey(value)
|
||||
}
|
||||
/**
|
||||
* Cascader 选择
|
||||
* @param value
|
||||
*/
|
||||
const handleCascaderChange: CascaderProps<Option>['onChange'] = (value) => {
|
||||
setApiKey(value.at(-1) || '')
|
||||
setCascaderKeyList(value)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取全局 MCP 配置
|
||||
@@ -134,15 +184,18 @@ const IntegrationAIContainer = ({
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 全局 MCP 跳转
|
||||
*/
|
||||
const addKey = () => {
|
||||
navigator('/mcpKey')
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 API Key 列表
|
||||
* 获取全局 API Key 列表
|
||||
*/
|
||||
const getKeysList = () => {
|
||||
fetchData<BasicResponse<null>>(type === 'global' ? 'simple/system/apikeys' : `my/apikeys/${serviceId}`, {
|
||||
const getGlobalKeysList = () => {
|
||||
fetchData<BasicResponse<null>>('simple/system/apikeys', {
|
||||
method: 'GET'
|
||||
})
|
||||
.then((response) => {
|
||||
@@ -167,11 +220,58 @@ const IntegrationAIContainer = ({
|
||||
message.error(errorInfo || $t(RESPONSE_TIPS.error))
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 抛出获取服务 API Key 列表
|
||||
*/
|
||||
useImperativeHandle(ref, () => ({
|
||||
getServiceKeysList
|
||||
}))
|
||||
|
||||
/**
|
||||
* 获取服务 API Key 列表
|
||||
*/
|
||||
const getServiceKeysList = () => {
|
||||
fetchData<BasicResponse<null>>(`my/app/apikeys`, {
|
||||
method: 'GET',
|
||||
eoParams: { service: serviceId }
|
||||
})
|
||||
.then((response) => {
|
||||
const { code, msg, data } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
if (data.apps && data.apps.length > 0) {
|
||||
// 转换数据结构为 Cascader 所需格式
|
||||
const transformedData = data.apps.map((app: ServiceApiKeyList) => ({
|
||||
value: app.id,
|
||||
label: app.name,
|
||||
children: app.apikeys.map((key) => ({
|
||||
...key,
|
||||
label: key.name
|
||||
}))
|
||||
}))
|
||||
setApiKeyList(transformedData)
|
||||
if (data.apps[0].apikeys?.length) {
|
||||
setApiKey(data.apps[0].apikeys[0].value)
|
||||
setCascaderKeyList([data.apps[0].id, data.apps[0].apikeys[0].value])
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch((errorInfo) => {
|
||||
message.error(errorInfo || $t(RESPONSE_TIPS.error))
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除错误提示
|
||||
*/
|
||||
const clearError = (tabKey: keyof typeof errors) => {
|
||||
setErrors((prev) => ({ ...prev, [tabKey]: null }))
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送请求
|
||||
*/
|
||||
const makeRequest = async <T extends z.ZodType>(request: ClientRequest, schema: T, tabKey?: keyof typeof errors) => {
|
||||
try {
|
||||
const response = await makeConnectionRequest(request, schema)
|
||||
@@ -191,6 +291,9 @@ const IntegrationAIContainer = ({
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 MCP 的 tools
|
||||
*/
|
||||
const listTools = async () => {
|
||||
const response = await makeRequest(
|
||||
{
|
||||
@@ -203,6 +306,9 @@ const IntegrationAIContainer = ({
|
||||
handleToolsChange(response.tools)
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化连接 mcp
|
||||
*/
|
||||
const {
|
||||
connectionStatus,
|
||||
serverCapabilities,
|
||||
@@ -230,29 +336,44 @@ const IntegrationAIContainer = ({
|
||||
disconnectFnRef.current = disconnectMcpServer
|
||||
}, [connectionStatus, disconnectMcpServer])
|
||||
|
||||
/**
|
||||
* 初始化数据
|
||||
*/
|
||||
const setupComponent = () => {
|
||||
initTabsData()
|
||||
if (type === 'global') {
|
||||
getGlobalMcpConfig()
|
||||
setMcpServerUrl('mcp/global/sse')
|
||||
getGlobalKeysList()
|
||||
} else {
|
||||
service?.basic.enableMcp && setMcpServerUrl(`mcp/service/${serviceId}/sse`)
|
||||
getServiceKeysList()
|
||||
}
|
||||
getKeysList()
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化数据
|
||||
*/
|
||||
useEffect(() => {
|
||||
setupComponent()
|
||||
}, [service])
|
||||
/**
|
||||
* 初始化标签数据
|
||||
*/
|
||||
useEffect(() => {
|
||||
initTabsData()
|
||||
type === 'global' && getGlobalMcpConfig()
|
||||
}, [state.language])
|
||||
/**
|
||||
* 切换标签
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (type === 'service') {
|
||||
currentTab === 'MCP' ? setActiveTab('mcp') : setActiveTab('openApi')
|
||||
}
|
||||
}, [currentTab])
|
||||
// 仅在组件加载时执行初始化逻辑
|
||||
/**
|
||||
* 仅在组件加载时执行初始化逻辑
|
||||
*/
|
||||
useEffect(() => {
|
||||
// 返回清理函数,只会在组件卸载时执行
|
||||
return () => {
|
||||
@@ -267,6 +388,9 @@ const IntegrationAIContainer = ({
|
||||
}
|
||||
}
|
||||
}, [type])
|
||||
/**
|
||||
* 切换标签时更新配置内容
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (activeTab === 'openApi' && tabContent?.openApi?.configContent) {
|
||||
setConfigContent(tabContent?.openApi?.configContent)
|
||||
@@ -274,7 +398,9 @@ const IntegrationAIContainer = ({
|
||||
setConfigContent(tabContent.mcp.configContent?.replace('{your_api_key}', apiKey || '{your_api_key}'))
|
||||
}
|
||||
}, [service, apiKey, activeTab, tabContent])
|
||||
|
||||
/**
|
||||
* 连接 MCP 服务器
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (mcpServerUrl) {
|
||||
if (connectionStatus === 'connected') {
|
||||
@@ -282,7 +408,10 @@ const IntegrationAIContainer = ({
|
||||
}
|
||||
connectMcpServer()
|
||||
}
|
||||
}, [mcpServerUrl])
|
||||
}, [mcpServerUrl, ...(type === 'global' ? [state.language] : [])])
|
||||
/**
|
||||
* 获取 MCP tools
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (connectionStatus === 'connected') {
|
||||
listTools()
|
||||
@@ -307,125 +436,166 @@ const IntegrationAIContainer = ({
|
||||
/>
|
||||
{$t('AI 代理集成')}
|
||||
</p>
|
||||
<div className="tab-container mt-3">
|
||||
{type === 'service' && service?.basic.enableMcp && (
|
||||
<div className="tab-nav flex rounded-md overflow-hidden border border-solid border-[#3D46F2] w-fit">
|
||||
<div
|
||||
className={`tab-item px-5 py-1.5 cursor-pointer text-sm transition-colors ${activeTab === 'openApi' ? 'bg-[#3D46F2] text-white' : 'bg-white text-[#3D46F2]'}`}
|
||||
onClick={() => setActiveTab('openApi')}
|
||||
>
|
||||
Open API
|
||||
{type === 'service' && service?.basic.enableMcp && (
|
||||
<div className="mt-3 tab-nav flex rounded-md overflow-hidden border border-solid border-[#3D46F2] w-fit">
|
||||
<div
|
||||
className={`tab-item px-5 py-1.5 cursor-pointer text-sm transition-colors ${activeTab === 'openApi' ? 'bg-[#3D46F2] text-white' : 'bg-white text-[#3D46F2]'}`}
|
||||
onClick={() => setActiveTab('openApi')}
|
||||
>
|
||||
Open API
|
||||
</div>
|
||||
<div
|
||||
className={`tab-item px-5 py-1.5 cursor-pointer text-sm transition-colors ${activeTab === 'mcp' ? 'bg-[#3D46F2] text-white' : 'bg-white text-[#3D46F2]'}`}
|
||||
onClick={() => setActiveTab('mcp')}
|
||||
>
|
||||
MCP
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{type === 'service' && !apiKeyList.length ? (
|
||||
<>
|
||||
<Card
|
||||
style={{ borderRadius: '10px' }}
|
||||
className={`w-full mt-3`}
|
||||
classNames={{
|
||||
body: 'p-[10px]'
|
||||
}}
|
||||
>
|
||||
<div className="flex flex-col items-center justify-center py-3">
|
||||
<span className="text-[14px] mb-5">{$t('请先订阅该服务')}</span>
|
||||
<Button type="primary" onClick={() => openModal?.('apply')}>
|
||||
{$t('申请')}
|
||||
</Button>
|
||||
</div>
|
||||
<div
|
||||
className={`tab-item px-5 py-1.5 cursor-pointer text-sm transition-colors ${activeTab === 'mcp' ? 'bg-[#3D46F2] text-white' : 'bg-white text-[#3D46F2]'}`}
|
||||
onClick={() => setActiveTab('mcp')}
|
||||
>
|
||||
MCP
|
||||
</Card>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className="tab-container mt-3">
|
||||
<div className="tab-content font-semibold mt-[10px]">
|
||||
{activeTab === 'openApi' ? tabContent.openApi?.title : tabContent.mcp.title}
|
||||
</div>
|
||||
{/* 标签页内容区域 */}
|
||||
<div className="bg-[#0a0b21] text-white p-4 rounded-md my-2 font-mono text-sm overflow-auto relative">
|
||||
{activeTab === 'mcp' ? (
|
||||
<ReactJson
|
||||
src={
|
||||
configContent
|
||||
? typeof configContent === 'string'
|
||||
? (() => {
|
||||
try {
|
||||
return JSON.parse(configContent)
|
||||
} catch (e) {
|
||||
return {}
|
||||
}
|
||||
})()
|
||||
: configContent
|
||||
: {}
|
||||
}
|
||||
theme="monokai"
|
||||
indentWidth={2}
|
||||
displayDataTypes={false}
|
||||
displayObjectSize={false}
|
||||
name={false}
|
||||
collapsed={false}
|
||||
enableClipboard={false}
|
||||
style={{
|
||||
backgroundColor: 'transparent',
|
||||
wordBreak: 'break-word',
|
||||
whiteSpace: 'normal'
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<pre className="whitespace-pre-wrap break-words">{configContent || ''}</pre>
|
||||
</>
|
||||
)}
|
||||
<IconButton
|
||||
name="copy"
|
||||
onClick={() => handleCopy(configContent)}
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: '5px',
|
||||
right: '5px',
|
||||
color: '#999',
|
||||
transition: 'none',
|
||||
'&.MuiButtonBase-root:hover': {
|
||||
background: 'transparent',
|
||||
color: '#3D46F2',
|
||||
transition: 'none'
|
||||
}
|
||||
}}
|
||||
></IconButton>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="tab-content font-semibold mt-[10px]">
|
||||
{activeTab === 'openApi' ? tabContent.openApi?.title : tabContent.mcp.title}
|
||||
</div>
|
||||
{/* 标签页内容区域 */}
|
||||
<div className="bg-[#0a0b21] text-white p-4 rounded-md my-2 font-mono text-sm overflow-auto relative">
|
||||
{activeTab === 'mcp' ? (
|
||||
<ReactJson
|
||||
src={
|
||||
configContent
|
||||
? typeof configContent === 'string'
|
||||
? (() => {
|
||||
try {
|
||||
return JSON.parse(configContent);
|
||||
} catch (e) {
|
||||
return {};
|
||||
}
|
||||
})()
|
||||
: configContent
|
||||
: {}
|
||||
}
|
||||
theme="monokai"
|
||||
indentWidth={2}
|
||||
displayDataTypes={false}
|
||||
displayObjectSize={false}
|
||||
name={false}
|
||||
collapsed={false}
|
||||
enableClipboard={false}
|
||||
style={{
|
||||
backgroundColor: 'transparent',
|
||||
wordBreak: 'break-word',
|
||||
whiteSpace: 'normal'
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
{activeTab === 'mcp' && (
|
||||
<>
|
||||
<pre className="whitespace-pre-wrap break-words">{configContent || ''}</pre>
|
||||
<div className="tab-content font-semibold my-[10px]">API Key</div>
|
||||
{apiKeyList.length ? (
|
||||
<>
|
||||
{type === 'global' ? (
|
||||
<>
|
||||
<Select
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
value={apiKey}
|
||||
className="w-full"
|
||||
onChange={handleSelectChange}
|
||||
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(apiKey)}
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: '0px',
|
||||
right: '5px',
|
||||
color: '#999',
|
||||
transition: 'none',
|
||||
'&.MuiButtonBase-root:hover': {
|
||||
background: 'transparent',
|
||||
color: '#3D46F2',
|
||||
transition: 'none'
|
||||
}
|
||||
}}
|
||||
></IconButton>
|
||||
</div>
|
||||
</Card>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Cascader
|
||||
className='w-full'
|
||||
allowClear={false}
|
||||
options={apiKeyList}
|
||||
value={cascaderKeyList}
|
||||
onChange={handleCascaderChange}
|
||||
placeholder={$t('选择 API Key')}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description={''}>
|
||||
<Button onClick={addKey} type="primary">
|
||||
{$t('新增 API Key')}
|
||||
</Button>
|
||||
</Empty>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<IconButton
|
||||
name="copy"
|
||||
onClick={() => handleCopy(configContent)}
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: '5px',
|
||||
right: '5px',
|
||||
color: '#999',
|
||||
transition: 'none',
|
||||
'&.MuiButtonBase-root:hover': {
|
||||
background: 'transparent',
|
||||
color: '#3D46F2',
|
||||
transition: 'none'
|
||||
}
|
||||
}}
|
||||
></IconButton>
|
||||
</div>
|
||||
</div>
|
||||
{activeTab === 'mcp' && (
|
||||
<>
|
||||
<div className="tab-content font-semibold my-[10px]">API Key</div>
|
||||
{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]'
|
||||
}}
|
||||
>
|
||||
<div className="relative h-[25px]">
|
||||
{apiKey}
|
||||
<IconButton
|
||||
name="copy"
|
||||
onClick={() => handleCopy(apiKey)}
|
||||
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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default IntegrationAIContainer
|
||||
})
|
||||
@@ -1,6 +1,6 @@
|
||||
import InsidePage from "@common/components/aoplatform/InsidePage"
|
||||
import { $t } from '@common/locales/index.ts'
|
||||
import IntegrationAIContainer from "./IntegrationAIContainer"
|
||||
import { IntegrationAIContainer } from "./IntegrationAIContainer"
|
||||
import { Tool } from "@modelcontextprotocol/sdk/types.js"
|
||||
import { useEffect, useState } from "react"
|
||||
import McpToolsContainer from "./McpToolsContainer"
|
||||
|
||||
@@ -480,6 +480,8 @@ const MemberList = () => {
|
||||
render: (_, entity) => (
|
||||
<WithPermission access="system.organization.member.edit">
|
||||
<Select
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
className="w-full"
|
||||
mode="multiple"
|
||||
value={entity.roles?.map((x: EntityItem) => x.id)}
|
||||
|
||||
@@ -60,7 +60,13 @@ export type DashboardSettingEditProps = {
|
||||
name="driver"
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
<Select className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.select)} options={[...DASHBOARD_SETTING_DRIVER_OPTION_LIST]}/>
|
||||
<Select
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
className="w-INPUT_NORMAL"
|
||||
placeholder={$t(PLACEHOLDER.select)}
|
||||
options={[...DASHBOARD_SETTING_DRIVER_OPTION_LIST]}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item<PartitionDashboardConfigFieldType>
|
||||
|
||||
@@ -301,7 +301,13 @@ const FilterForm = forwardRef<FilterFormHandle, FilterFormProps>(
|
||||
return (
|
||||
<Form form={form} layout="vertical" onValuesChange={handleValuesChange}>
|
||||
<Form.Item name="name" label={$t('属性名称')} rules={[{ required: true }]}>
|
||||
<Select disabled={disabled} onChange={handleTypeChange} options={filterOptionsList} />
|
||||
<Select
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
disabled={disabled}
|
||||
onChange={handleTypeChange}
|
||||
options={filterOptionsList}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="values"
|
||||
|
||||
@@ -139,8 +139,13 @@ const DataMaskingConfig = forwardRef<DataMaskingConfigHandle>((_,ref) => {
|
||||
name="type"
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
<Select className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)} options={policyOptions} >
|
||||
</Select>
|
||||
<Select
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
className="w-INPUT_NORMAL"
|
||||
placeholder={$t(PLACEHOLDER.input)}
|
||||
options={policyOptions}
|
||||
></Select>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item<DataMaskingConfigFieldType>
|
||||
|
||||
@@ -146,6 +146,8 @@ const DataMaskRuleForm: React.FC<DataMaskRuleFormProps> = ({
|
||||
<Form form={form} layout="vertical" className="p-4">
|
||||
<Form.Item name={['match', 'type']} label={$t('匹配类型')} rules={[{ required: true }]}>
|
||||
<Select
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
placeholder={$t(PLACEHOLDER.select)}
|
||||
onChange={handleMatchTypeChange}
|
||||
options={matchRuleOptions}
|
||||
@@ -156,6 +158,8 @@ const DataMaskRuleForm: React.FC<DataMaskRuleFormProps> = ({
|
||||
<Form.Item name={['match', 'value']} label={$t('匹配值')} rules={[{ required: true }]}>
|
||||
{matchType === 'inner' ? (
|
||||
<Select
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
placeholder={$t(PLACEHOLDER.select)}
|
||||
onChange={handleMatchValueChange}
|
||||
options={dataFormatOptions}
|
||||
@@ -168,6 +172,8 @@ const DataMaskRuleForm: React.FC<DataMaskRuleFormProps> = ({
|
||||
|
||||
<Form.Item name={['mask', 'type']} label={$t('脱敏类型')} rules={[{ required: true }]}>
|
||||
<Select
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
placeholder={$t(PLACEHOLDER.select)}
|
||||
onChange={handleMaskTypeChange}
|
||||
options={
|
||||
@@ -197,6 +203,8 @@ const DataMaskRuleForm: React.FC<DataMaskRuleFormProps> = ({
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
<Select
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
placeholder={$t(PLACEHOLDER.select)}
|
||||
onChange={handleReplaceTypeChange}
|
||||
options={dataMaskReplaceStrOptions}
|
||||
|
||||
@@ -551,6 +551,8 @@ const SystemConfig = forwardRef<SystemConfigHandle>((_, ref) => {
|
||||
{!onEdit && (
|
||||
<Form.Item<SystemConfigFieldType> label={$t('所属团队')} name="team" rules={[{ required: true }]}>
|
||||
<Select
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
className="w-INPUT_NORMAL"
|
||||
disabled={onEdit}
|
||||
placeholder={$t(PLACEHOLDER.input)}
|
||||
@@ -627,6 +629,8 @@ const SystemConfig = forwardRef<SystemConfigHandle>((_, ref) => {
|
||||
|
||||
<Form.Item<SystemConfigFieldType> label={$t('标签')} name="tags">
|
||||
<Select
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
className="w-INPUT_NORMAL"
|
||||
mode="tags"
|
||||
placeholder={$t(PLACEHOLDER.select)}
|
||||
|
||||
@@ -234,7 +234,7 @@ const SystemInsidePage: FC = () => {
|
||||
<InsidePage
|
||||
pageTitle={systemInfo?.name || '-'}
|
||||
tagList={[
|
||||
...(systemInfo?.enable_mcp ? [{ label: 'MCP', color: '#ffc107', className: 'text-[#000]' }] : []),
|
||||
...(systemInfo?.enable_mcp ? [{ label: 'MCP', color: '#FFF0C1', className: 'text-[#000]' }] : []),
|
||||
{
|
||||
label: (
|
||||
<Paragraph className="mb-0" copyable={serviceId ? { text: serviceId } : false}>
|
||||
|
||||
@@ -180,10 +180,24 @@ const SystemList: FC = () => {
|
||||
<span
|
||||
className={`text-[13px] `}
|
||||
>
|
||||
{$t(SERVICE_KIND_OPTIONS.find((x) => x.value === record.service_kind)?.label || '-')}
|
||||
{record.enable_mcp && (
|
||||
<Tag color="#ffc107" className="text-[#000] ml-[5px]">MCP</Tag>
|
||||
)}
|
||||
<Tag
|
||||
color={`#${record.service_kind === 'ai' ? 'EADEFF' : 'DEFFE7'}`}
|
||||
className={`text-[#000] font-normal border-0 mr-[10px] max-w-[150px] truncate`}
|
||||
bordered={false}
|
||||
title={record.service_kind || '-'}
|
||||
>
|
||||
{SERVICE_KIND_OPTIONS.find((x) => x.value === record.service_kind)?.label || '-'}
|
||||
</Tag>
|
||||
{record?.enable_mcp && (
|
||||
<Tag
|
||||
color="#FFF0C1"
|
||||
className="text-[#000] font-normal border-0 mr-[12px] max-w-[150px] truncate"
|
||||
bordered={false}
|
||||
title={'MCP'}
|
||||
>
|
||||
MCP
|
||||
</Tag>
|
||||
)}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -30,7 +30,10 @@ const SystemInsideApiProxy = forwardRef<SystemInsideApiProxyHandle,SystemInsideA
|
||||
const ProxyHeadeerConfig = useMemo(()=>PROXY_HEADER_CONFIG.map((x)=>({
|
||||
...x,
|
||||
...(x.key === 'optType' ? {
|
||||
component: <Select className="w-INPUT_NORMAL" options={UPSTREAM_PROXY_HEADER_TYPE_OPTIONS.map((x)=>({...x, label:$t(x.label)}))}/>
|
||||
component: <Select
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
className="w-INPUT_NORMAL" options={UPSTREAM_PROXY_HEADER_TYPE_OPTIONS.map((x)=>({...x, label:$t(x.label)}))}/>
|
||||
} : {})
|
||||
}))
|
||||
,[state.language])
|
||||
|
||||
@@ -179,6 +179,8 @@ const SystemInsideRouterCreate = forwardRef<SystemInsideRouterCreateHandle, Syst
|
||||
...item,
|
||||
component: (
|
||||
<Select
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
className="w-INPUT_NORMAL"
|
||||
options={Object.entries(MatchPositionEnum)?.map(([key, value]) => {
|
||||
return { label: $t(value), value: key }
|
||||
@@ -192,6 +194,8 @@ const SystemInsideRouterCreate = forwardRef<SystemInsideRouterCreateHandle, Syst
|
||||
...item,
|
||||
component: (
|
||||
<Select
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
className="w-INPUT_NORMAL"
|
||||
options={Object.entries(MatchTypeEnum)?.map(([key, value]) => {
|
||||
return { label: $t(value), value: key }
|
||||
@@ -252,6 +256,8 @@ const SystemInsideRouterCreate = forwardRef<SystemInsideRouterCreateHandle, Syst
|
||||
|
||||
<Form.Item<SystemApiProxyFieldType> label={$t('请求协议')} name="protocols" rules={[{ required: true }]}>
|
||||
<Select
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
className="w-INPUT_NORMAL"
|
||||
placeholder={$t(PLACEHOLDER.select)}
|
||||
mode="multiple"
|
||||
@@ -271,6 +277,8 @@ const SystemInsideRouterCreate = forwardRef<SystemInsideRouterCreateHandle, Syst
|
||||
noStyle
|
||||
>
|
||||
<Select
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
placeholder={$t(PLACEHOLDER.select)}
|
||||
options={apiPathMatchRulesOptions}
|
||||
className="w-[30%] min-w-[100px]"
|
||||
@@ -307,6 +315,8 @@ const SystemInsideRouterCreate = forwardRef<SystemInsideRouterCreateHandle, Syst
|
||||
|
||||
<Form.Item<SystemApiProxyFieldType> label={$t('请求方式')} name="methods" rules={[{ required: true }]}>
|
||||
<Select
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
className="w-INPUT_NORMAL"
|
||||
placeholder={$t(PLACEHOLDER.select)}
|
||||
mode="multiple"
|
||||
|
||||
@@ -129,7 +129,7 @@ const globalConfigNodesRule: FormItemProps['rules'] = [
|
||||
const ProxyHeadeerConfig = useMemo(()=>PROXY_HEADER_CONFIG.map((x)=>({
|
||||
...x,
|
||||
...(x.key === 'optType' ? {
|
||||
component: <Select className="w-INPUT_NORMAL" options={UPSTREAM_PROXY_HEADER_TYPE_OPTIONS.map((x)=>({...x, label:$t(x.label)}))}/>
|
||||
component: <Select showSearch optionFilterProp="label" className="w-INPUT_NORMAL" options={UPSTREAM_PROXY_HEADER_TYPE_OPTIONS.map((x)=>({...x, label:$t(x.label)}))}/>
|
||||
} : {})
|
||||
}))
|
||||
,[state.language])
|
||||
@@ -175,7 +175,7 @@ const globalConfigNodesRule: FormItemProps['rules'] = [
|
||||
name="scheme"
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
<Select className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.select)} options={schemeOptions}>
|
||||
<Select showSearch optionFilterProp="label" className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.select)} options={schemeOptions}>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
@@ -192,7 +192,7 @@ const globalConfigNodesRule: FormItemProps['rules'] = [
|
||||
name="passHost"
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
<Select className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.select)} options={passHostOptions} onChange={(val)=>setFormShowHost(val === 'rewrite')}>
|
||||
<Select showSearch optionFilterProp="label" className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.select)} options={passHostOptions} onChange={(val)=>setFormShowHost(val === 'rewrite')}>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
|
||||
@@ -205,6 +205,8 @@ const TeamConfig = forwardRef<TeamConfigHandle, TeamConfigProps>((props, ref) =>
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
<Select
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
className="w-INPUT_NORMAL"
|
||||
placeholder={$t(PLACEHOLDER.select)}
|
||||
options={managerOption}
|
||||
|
||||
@@ -324,6 +324,8 @@ const TeamInsideMember: FC = () => {
|
||||
render: (_, entity) => (
|
||||
<WithPermission access="team.team.member.edit">
|
||||
<Select
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
className="w-full"
|
||||
mode="multiple"
|
||||
maxTagCount="responsive"
|
||||
|
||||
@@ -200,6 +200,8 @@ export default function MonitorApiPage(props: MonitorApiPageProps) {
|
||||
<div className="flex flex-nowrap items-center pt-btnybase mr-btnybase">
|
||||
<label className="inline-block whitespace-nowrap">{$t('服务')}:</label>
|
||||
<Select
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
className="w-[346px]"
|
||||
value={queryData?.services}
|
||||
options={projectOptionList}
|
||||
@@ -216,6 +218,8 @@ export default function MonitorApiPage(props: MonitorApiPageProps) {
|
||||
<div className="flex flex-nowrap items-center pt-btnybase mr-btnybase">
|
||||
<label className=" whitespace-nowrap inline-block w-[42px] text-right">API :</label>
|
||||
<Select
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
className="w-[346px]"
|
||||
value={queryData?.apis}
|
||||
options={apiOptionList}
|
||||
|
||||
@@ -176,6 +176,8 @@ export default function MonitorAppPage(props: MonitorAppPageProps) {
|
||||
<div>
|
||||
<label className="inline-block whitespace-nowrap">{$t('消费者')}:</label>
|
||||
<Select
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
className="w-[346px]"
|
||||
mode="multiple"
|
||||
maxTagCount={1}
|
||||
|
||||
@@ -181,6 +181,8 @@ export default function MonitorSubPage(props: MonitorSubPageProps) {
|
||||
<div>
|
||||
<label className="inline-block whitespace-nowrap">服务:</label>
|
||||
<Select
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
className="w-[346px]"
|
||||
mode="multiple"
|
||||
maxTagCount={1}
|
||||
|
||||
@@ -7,49 +7,16 @@ import { RouterParams } from '@common/const/type'
|
||||
|
||||
export default function DashboardTabPage() {
|
||||
const { dashboardType } = useParams<RouterParams>()
|
||||
const [activeKey, setActiveKey] = useState<string>('total')
|
||||
const navigateTo = useNavigate()
|
||||
|
||||
useEffect(() => {
|
||||
setActiveKey(dashboardType || 'total')
|
||||
const activeKey = dashboardType || 'total'
|
||||
navigateTo(`/analytics/${activeKey === 'total' ? activeKey : `${activeKey}/list`}`)
|
||||
}, [dashboardType])
|
||||
|
||||
const monitorTabItems: TabsProps['items'] = [
|
||||
{
|
||||
label: $t('监控总览'),
|
||||
key: 'total',
|
||||
children: <DashboardTotal />
|
||||
},
|
||||
{
|
||||
label: $t('服务被调用统计'),
|
||||
key: 'subscriber',
|
||||
children: <Outlet />
|
||||
},
|
||||
{
|
||||
label: $t('消费者调用统计'),
|
||||
key: 'provider',
|
||||
children: <Outlet />
|
||||
},
|
||||
{
|
||||
label: $t('API 调用统计'),
|
||||
key: 'api',
|
||||
children: <Outlet />
|
||||
}
|
||||
]
|
||||
|
||||
return (
|
||||
<>
|
||||
<Tabs
|
||||
activeKey={activeKey}
|
||||
onChange={(val) => {
|
||||
setActiveKey(val)
|
||||
navigateTo(`/analytics/${val === 'total' ? val : `${val}/list`}`)
|
||||
}}
|
||||
items={monitorTabItems}
|
||||
className="h-full overflow-hidden mt-[6px] [&>.ant-tabs-content-holder]:overflow-auto [&>.ant-tabs-content-holder]:pr-PAGE_INSIDE_X [&>.ant-tabs-content-holder>.ant-tabs-content]:h-full [&>.ant-tabs-content-holder>.ant-tabs-content>.ant-tabs-tabpane]:h-full"
|
||||
size="small"
|
||||
tabBarStyle={{ paddingLeft: '10px', marginTop: '0px', marginBottom: '0px' }}
|
||||
/>
|
||||
<Outlet />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1053,6 +1053,11 @@ p{
|
||||
.ant-table-wrapper .ant-table{
|
||||
scrollbar-color: none !important;
|
||||
}
|
||||
.ant-select .ant-select-clear {
|
||||
height:16px !important;
|
||||
width: 16px !important;
|
||||
top: 45%;
|
||||
}
|
||||
|
||||
.eo_page_drag .ant-table-body{
|
||||
overflow-y: auto !important;
|
||||
|
||||
@@ -2,7 +2,7 @@ import WithPermission from '@common/components/aoplatform/WithPermission'
|
||||
import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const'
|
||||
import { useFetch } from '@common/hooks/http'
|
||||
import { $t } from '@common/locales'
|
||||
import { App, Col, Form, Input, Row, Select } from 'antd'
|
||||
import { App, Col, Form, Input, Row, Select, Tooltip } from 'antd'
|
||||
import { forwardRef, useEffect, useImperativeHandle } from 'react'
|
||||
import { ApplyServiceHandle, ApplyServiceProps } from '../../const/serviceHub/type'
|
||||
|
||||
@@ -70,11 +70,23 @@ export const ApplyServiceModal = forwardRef<ApplyServiceHandle, ApplyServiceProp
|
||||
</Row>
|
||||
<Form.Item label={$t('消费者')} name="applications" rules={[{ required: true }]}>
|
||||
<Select
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
className="w-INPUT_NORMAL"
|
||||
disabled={reApply}
|
||||
placeholder={$t('搜索或选择消费者')}
|
||||
mode="multiple"
|
||||
options={mySystemOptionList?.filter((x) => x.value !== entity.id)}
|
||||
optionRender={(option) => {
|
||||
if (option.data.disabled) {
|
||||
return (
|
||||
<Tooltip title={$t('该消费者已订阅')}>
|
||||
<div>{option.data.label}</div>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
return <div>{option.data.label}</div>
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
{entity.approvalType === 'manual' && (
|
||||
|
||||
@@ -15,7 +15,7 @@ import { ApplyServiceHandle, ServiceBasicInfoType, ServiceDetailType } from '../
|
||||
import { ApplyServiceModal } from './ApplyServiceModal.tsx'
|
||||
import ServiceHubApiDocument from './ServiceHubApiDocument.tsx'
|
||||
import { SERVICE_KIND_OPTIONS } from '@core/const/system/const.tsx'
|
||||
import IntegrationAIContainer from '@core/pages/mcpService/IntegrationAIContainer.tsx'
|
||||
import { IntegrationAIContainer, IntegrationAIContainerRef } from '@core/pages/mcpService/IntegrationAIContainer.tsx'
|
||||
import { Tool } from '@modelcontextprotocol/sdk/types.js'
|
||||
import McpToolsContainer from '@core/pages/mcpService/McpToolsContainer.tsx'
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext.tsx'
|
||||
@@ -48,6 +48,7 @@ const ServiceHubDetail = () => {
|
||||
const [tabItem, setTabItem] = useState<TabItemType[]>([])
|
||||
const [currentTab, setCurrentTab] = useState('')
|
||||
const { state } = useGlobalContext()
|
||||
const integrationAIContainerRef = useRef<IntegrationAIContainerRef>(null)
|
||||
const navigate = useNavigate()
|
||||
|
||||
const modifyApiDoc = (apiDoc: string, apiPrefix: string) => {
|
||||
@@ -191,7 +192,7 @@ servers:
|
||||
content: serviceBasicInfo?.catalogue?.name || '-'
|
||||
},
|
||||
{
|
||||
color: '#fbe5e5',
|
||||
color: `#${serviceBasicInfo?.serviceKind === 'ai' ? 'EADEFF' : 'DEFFE7'}`,
|
||||
textColor: 'text-[#000]',
|
||||
title: serviceBasicInfo?.serviceKind || '-',
|
||||
content: SERVICE_KIND_OPTIONS.find((x) => x.value === serviceBasicInfo?.serviceKind)?.label || '-'
|
||||
@@ -201,7 +202,7 @@ servers:
|
||||
// 如果启用了MCP,添加MCP标签
|
||||
if (serviceBasicInfo?.enableMcp) {
|
||||
tags.push({
|
||||
color: '#ffc107',
|
||||
color: '#FFF0C1',
|
||||
textColor: 'text-[#000]',
|
||||
title: 'MCP',
|
||||
content: 'MCP'
|
||||
@@ -234,16 +235,28 @@ servers:
|
||||
|
||||
const getMySelectList = () => {
|
||||
setMySystemOptionList([])
|
||||
fetchData<BasicResponse<{ app: EntityItem[] }>>('apps/can_subscribe', { method: 'GET' }).then((response) => {
|
||||
fetchData<BasicResponse<{ app: EntityItem[] }>>('apps/can_subscribe', {
|
||||
method: 'GET',
|
||||
eoParams: { service: serviceId },
|
||||
eoTransformKeys: ['is_subscribed']
|
||||
}).then((response) => {
|
||||
const { code, data, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
setMySystemOptionList(
|
||||
data.app?.map((x: EntityItem) => {
|
||||
return {
|
||||
label: x.name,
|
||||
value: x.id
|
||||
}
|
||||
})
|
||||
data.app
|
||||
?.sort((a: EntityItem, b: EntityItem) => {
|
||||
// 已订阅的排在后面
|
||||
if (a.isSubscribed && !b.isSubscribed) return 1
|
||||
if (!a.isSubscribed && b.isSubscribed) return -1
|
||||
return 0
|
||||
})
|
||||
.map((x: EntityItem) => {
|
||||
return {
|
||||
label: x.name,
|
||||
value: x.id,
|
||||
disabled: x.isSubscribed // 已订阅的设为禁用
|
||||
}
|
||||
})
|
||||
)
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
@@ -263,7 +276,10 @@ servers:
|
||||
),
|
||||
onOk: () => {
|
||||
return applyRef.current?.apply().then((res) => {
|
||||
// if(res === true) setApplied(true)
|
||||
if (res === true) {
|
||||
integrationAIContainerRef.current?.getServiceKeysList()
|
||||
getMySelectList()
|
||||
}
|
||||
})
|
||||
},
|
||||
okText: $t('确认'),
|
||||
@@ -490,11 +506,13 @@ servers:
|
||||
items={tabItem}
|
||||
/>
|
||||
<IntegrationAIContainer
|
||||
ref={integrationAIContainerRef}
|
||||
service={service}
|
||||
currentTab={currentTab}
|
||||
serviceId={serviceId}
|
||||
customClassName="mt-[70px] max-h-[calc(100vh-420px)] overflow-auto"
|
||||
type={'service'}
|
||||
openModal={openModal}
|
||||
handleToolsChange={handleToolsChange}
|
||||
></IntegrationAIContainer>
|
||||
</div>
|
||||
|
||||
@@ -119,7 +119,7 @@ export const ServiceHubGroup = ({ children, filterOption, dispatch }: ServiceHub
|
||||
|
||||
return (
|
||||
<div className="flex flex-1 h-full">
|
||||
<div className="w-[220px] border-0 border-solid border-r-[1px] border-r-BORDER">
|
||||
<div className="w-[220px] border-0">
|
||||
<div className=" h-full">
|
||||
<Input
|
||||
className="rounded-SEARCH_RADIUS m-[10px] h-[40px] bg-[#f8f8f8] w-[200px]"
|
||||
|
||||
@@ -45,7 +45,7 @@ export const initialServiceHubListState = {
|
||||
selectedTag: [] as string[],
|
||||
keyword: '',
|
||||
getCateAndTagData: false,
|
||||
listLoading: false
|
||||
listLoading: true
|
||||
}
|
||||
|
||||
function reducer(state: typeof initialServiceHubListState, action: ServiceHubListActionType) {
|
||||
@@ -142,7 +142,13 @@ const ServiceHubList: FC = () => {
|
||||
<Spin
|
||||
className="h-full"
|
||||
wrapperClassName="h-full"
|
||||
indicator={<LoadingOutlined style={{ fontSize: 24 }} spin />}
|
||||
indicator={
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<div style={{ transform: 'scale(1.5)' }}>
|
||||
<LoadingOutlined style={{ fontSize: 30 }} spin />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
spinning={filterOption.listLoading}
|
||||
>
|
||||
{filterOption.showServicesList && filterOption.showServicesList.length > 0 ? (
|
||||
@@ -189,7 +195,12 @@ const ServiceHubList: FC = () => {
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
|
||||
<>
|
||||
{!filterOption.listLoading &&
|
||||
(!filterOption.showServicesList || filterOption.showServicesList.length === 0) && (
|
||||
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Spin>
|
||||
</div>
|
||||
@@ -225,7 +236,7 @@ const CardTitle = (service: ServiceHubTableListItem) => {
|
||||
<div className="mt-[10px] h-[20px] flex items-center font-normal">
|
||||
<Tag
|
||||
color="#7371fc1b"
|
||||
className="text-theme font-normal border-0 mr-[12px] max-w-[150px] truncate"
|
||||
className="text-theme font-normal border-0 mr-[12px] max-w-[100px] truncate"
|
||||
key={service.id}
|
||||
bordered={false}
|
||||
title={service.catalogue?.name || '-'}
|
||||
@@ -233,8 +244,8 @@ const CardTitle = (service: ServiceHubTableListItem) => {
|
||||
{service.catalogue?.name || '-'}
|
||||
</Tag>
|
||||
<Tag
|
||||
color="#fbe5e5"
|
||||
className="text-[#000] font-normal border-0 mr-[12px] max-w-[150px] truncate"
|
||||
color={`#${service.serviceKind === 'ai' ? 'EADEFF' : 'DEFFE7'}`}
|
||||
className={`text-[#000] font-normal border-0 mr-[12px] max-w-[150px] truncate`}
|
||||
bordered={false}
|
||||
title={service.serviceKind || '-'}
|
||||
>
|
||||
@@ -242,7 +253,7 @@ const CardTitle = (service: ServiceHubTableListItem) => {
|
||||
</Tag>
|
||||
{service?.enableMcp && (
|
||||
<Tag
|
||||
color="#ffc107"
|
||||
color="#FFF0C1"
|
||||
className="text-[#000] font-normal border-0 mr-[12px] max-w-[150px] truncate"
|
||||
bordered={false}
|
||||
title={'MCP'}
|
||||
|
||||
@@ -65,6 +65,8 @@ export const ManagementAuthorityConfig = forwardRef<ManagementAuthorityConfigHan
|
||||
const prefixSelector = (
|
||||
<Form.Item name="position" noStyle>
|
||||
<Select
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
style={{ width: 90 }}
|
||||
options={[
|
||||
{ label: 'Header', value: 'Header' },
|
||||
@@ -127,6 +129,8 @@ export const ManagementAuthorityConfig = forwardRef<ManagementAuthorityConfigHan
|
||||
|
||||
<Form.Item<EditAuthFieldType> label={$t('鉴权类型')} name="driver" rules={[{ required: true }]}>
|
||||
<Select
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
disabled={type === 'edit'}
|
||||
className="w-INPUT_NORMAL"
|
||||
options={[
|
||||
@@ -179,6 +183,8 @@ export const ManagementAuthorityConfig = forwardRef<ManagementAuthorityConfigHan
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
<Select
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
className="w-INPUT_NORMAL"
|
||||
options={ALGORITHM_ITEM}
|
||||
onChange={(value) => {
|
||||
@@ -205,6 +211,8 @@ export const ManagementAuthorityConfig = forwardRef<ManagementAuthorityConfigHan
|
||||
|
||||
<Form.Item<EditAuthFieldType> label={$t('校验字段')} name={['config', 'claimsToVerify']}>
|
||||
<Select
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
className="w-INPUT_NORMAL"
|
||||
mode="multiple"
|
||||
options={[
|
||||
|
||||
@@ -204,6 +204,8 @@ const ManagementConfig = forwardRef<ManagementConfigHandle, ManagementConfigProp
|
||||
{dataShowType === 'list' && (
|
||||
<Form.Item<ManagementConfigFieldType> label={$t('所属团队')} name="team" rules={[{ required: true }]}>
|
||||
<Select
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
className="w-INPUT_NORMAL"
|
||||
disabled={type === 'edit'}
|
||||
placeholder={$t(PLACEHOLDER.input)}
|
||||
|
||||
Reference in New Issue
Block a user