mirror of
https://github.com/APIParkLab/APIPark.git
synced 2026-07-06 16:11:56 +08:00
Merge branch 'feature/1.7-cxx' into 'main'
feature/1.7-MCP See merge request apipark/APIPark!305
This commit is contained in:
@@ -86,9 +86,9 @@ export default function ApiRequestSetting() {
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item<ApiRequestSettingFieldType>
|
||||
label={$t('集成地址')}
|
||||
label={$t('OpenAPI & MCP 调用地址')}
|
||||
name="sitePrefix"
|
||||
rules={[{ whitespace: true }]}
|
||||
rules={[{ required: true, whitespace: true }]}
|
||||
extra={$t('与外部平台集成时,获取 API 市场中文档信息的域名')}
|
||||
>
|
||||
<Input className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)} />
|
||||
|
||||
@@ -10,6 +10,7 @@ import { useConnection } from './hook/useConnection'
|
||||
import { ClientRequest, Tool, ListToolsResultSchema } from '@modelcontextprotocol/sdk/types.js'
|
||||
import { z } from 'zod'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { ServiceDetailType } from '@market/const/serviceHub/type'
|
||||
import useCopyToClipboard from '@common/hooks/copy'
|
||||
|
||||
type ConfigList = {
|
||||
@@ -34,12 +35,20 @@ type ApiKeyItem = {
|
||||
|
||||
const IntegrationAIContainer = ({
|
||||
type,
|
||||
handleToolsChange
|
||||
handleToolsChange,
|
||||
customClassName,
|
||||
service,
|
||||
serviceId,
|
||||
currentTab
|
||||
}: {
|
||||
type: 'global' | 'service'
|
||||
handleToolsChange: (value: Tool[]) => void
|
||||
customClassName?: string
|
||||
service?: ServiceDetailType
|
||||
serviceId?: string
|
||||
currentTab?: string
|
||||
}) => {
|
||||
const [activeTab, setActiveTab] = useState('mcp')
|
||||
const [activeTab, setActiveTab] = useState(type === 'service' ? 'openApi' : 'mcp')
|
||||
const { message } = App.useApp()
|
||||
const [configContent, setConfigContent] = useState<string>('')
|
||||
const [apiKey, setApiKey] = useState<string>('')
|
||||
@@ -66,14 +75,14 @@ const IntegrationAIContainer = ({
|
||||
const params: ConfigList = {
|
||||
mcp: {
|
||||
title: $t('MCP 配置'),
|
||||
configContent: '',
|
||||
configContent: service?.mcpAccessConfig || '',
|
||||
apiKeys: []
|
||||
}
|
||||
}
|
||||
if (type === 'global') {
|
||||
if (type === 'service') {
|
||||
params.openApi = {
|
||||
title: $t('Open API 文档'),
|
||||
configContent: '',
|
||||
configContent: service?.openapiAddress || '',
|
||||
apiKeys: []
|
||||
}
|
||||
}
|
||||
@@ -131,7 +140,7 @@ const IntegrationAIContainer = ({
|
||||
* 获取 API Key 列表
|
||||
*/
|
||||
const getKeysList = () => {
|
||||
fetchData<BasicResponse<null>>(type === 'global' ? 'simple/system/apikeys' : '', {
|
||||
fetchData<BasicResponse<null>>(type === 'global' ? 'simple/system/apikeys' : 'my/apikeys', {
|
||||
method: 'GET'
|
||||
})
|
||||
.then((response) => {
|
||||
@@ -212,28 +221,34 @@ const IntegrationAIContainer = ({
|
||||
// 使用 useRef 保存最新的连接状态和断开函数
|
||||
const connectionStatusRef = useRef(connectionStatus)
|
||||
const disconnectFnRef = useRef(disconnectMcpServer)
|
||||
|
||||
|
||||
// 当连接状态或断开函数变化时更新 ref
|
||||
useEffect(() => {
|
||||
connectionStatusRef.current = connectionStatus
|
||||
disconnectFnRef.current = disconnectMcpServer
|
||||
}, [connectionStatus, disconnectMcpServer])
|
||||
|
||||
|
||||
const setupComponent = () => {
|
||||
initTabsData()
|
||||
if (type === 'global') {
|
||||
getGlobalMcpConfig()
|
||||
setMcpServerUrl('mcp/global/sse')
|
||||
} else {
|
||||
service?.basic.enableMcp && setMcpServerUrl(`mcp/service/${serviceId}/sse`)
|
||||
}
|
||||
getKeysList()
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setupComponent()
|
||||
}, [service])
|
||||
useEffect(() => {
|
||||
if (type === 'service') {
|
||||
currentTab === 'MCP' ? setActiveTab('mcp') : setActiveTab('openApi')
|
||||
}
|
||||
}, [currentTab])
|
||||
// 仅在组件加载时执行初始化逻辑
|
||||
useEffect(() => {
|
||||
// 局部函数,仅在此 effect 执行期间存在
|
||||
const setupComponent = () => {
|
||||
if (type === 'global') {
|
||||
getGlobalMcpConfig()
|
||||
setMcpServerUrl('mcp/global/sse')
|
||||
}
|
||||
initTabsData()
|
||||
getKeysList()
|
||||
}
|
||||
|
||||
// 执行初始化
|
||||
setupComponent()
|
||||
|
||||
// 返回清理函数,只会在组件卸载时执行
|
||||
return () => {
|
||||
try {
|
||||
@@ -242,7 +257,6 @@ const IntegrationAIContainer = ({
|
||||
if (disconnectFn) {
|
||||
disconnectFn()
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
console.error('断开连接时出错:', err)
|
||||
}
|
||||
@@ -250,11 +264,11 @@ const IntegrationAIContainer = ({
|
||||
}, [type])
|
||||
useEffect(() => {
|
||||
if (activeTab === 'openApi' && tabContent?.openApi?.configContent) {
|
||||
setConfigContent(tabContent?.openApi?.configContent?.replace('{your_api_key}', apiKey || '{your_api_key}'))
|
||||
setConfigContent(tabContent?.openApi?.configContent)
|
||||
} else if (activeTab === 'mcp' && tabContent?.mcp?.configContent) {
|
||||
setConfigContent(tabContent.mcp.configContent?.replace('{your_api_key}', apiKey || '{your_api_key}'))
|
||||
}
|
||||
}, [apiKey, activeTab, tabContent])
|
||||
}, [service, apiKey, activeTab, tabContent])
|
||||
|
||||
useEffect(() => {
|
||||
if (mcpServerUrl) {
|
||||
@@ -274,7 +288,7 @@ const IntegrationAIContainer = ({
|
||||
<>
|
||||
<Card
|
||||
style={{ borderRadius: '10px' }}
|
||||
className="w-[400px] h-fit"
|
||||
className={`w-[400px] h-fit ${customClassName}`}
|
||||
classNames={{
|
||||
body: 'p-[10px]'
|
||||
}}
|
||||
@@ -289,7 +303,7 @@ const IntegrationAIContainer = ({
|
||||
{$t('AI 代理集成')}
|
||||
</p>
|
||||
<div className="tab-container mt-3">
|
||||
{type === 'service' && (
|
||||
{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]'}`}
|
||||
@@ -310,21 +324,39 @@ const IntegrationAIContainer = ({
|
||||
</div>
|
||||
{/* 标签页内容区域 */}
|
||||
<div className="bg-[#0a0b21] text-white p-4 rounded-md my-2 font-mono text-sm overflow-auto relative">
|
||||
<ReactJson
|
||||
src={configContent ? JSON.parse(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' ? (
|
||||
<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)}
|
||||
|
||||
@@ -2,12 +2,12 @@ import { Icon } from '@iconify/react/dist/iconify.js'
|
||||
import { Tool } from '@modelcontextprotocol/sdk/types.js'
|
||||
import { Card } from 'antd'
|
||||
|
||||
const McpToolsContainer = ({ tools = [] }: { tools: Tool[] }) => {
|
||||
const McpToolsContainer = ({ tools = [], customClassName }: { tools: Tool[]; customClassName?: string }) => {
|
||||
return (
|
||||
<>
|
||||
<Card
|
||||
style={{ borderRadius: '10px' }}
|
||||
className={`w-full flex-1 mr-[10px]`}
|
||||
className={`w-full flex-1 mr-[10px] ${customClassName}`}
|
||||
classNames={{
|
||||
body: 'p-[10px]'
|
||||
}}
|
||||
|
||||
@@ -284,7 +284,6 @@ export function useConnection({
|
||||
// if (token) {
|
||||
// headers["Authorization"] = `Bearer ${token}`;
|
||||
// }
|
||||
|
||||
// 创建SSE客户端传输层
|
||||
const clientTransport = new SSEClientTransport(mcpProxyServerUrl, {
|
||||
eventSourceInit: {
|
||||
|
||||
@@ -17,6 +17,8 @@ export type ServiceBasicInfoType = {
|
||||
approvalType: 'auto' | 'manual'
|
||||
serviceKind: 'ai' | 'rest'
|
||||
sitePrefix?: string
|
||||
enableMcp: boolean
|
||||
invokeCount: number
|
||||
}
|
||||
|
||||
export type ServiceDetailType = {
|
||||
@@ -25,6 +27,9 @@ export type ServiceDetailType = {
|
||||
basic: ServiceBasicInfoType
|
||||
apiDoc: string
|
||||
applied: boolean
|
||||
mcpServerAddress?: string
|
||||
mcpAccessConfig?: string
|
||||
openapiAddress?: string
|
||||
}
|
||||
|
||||
export type ServiceHubCategoryConfigFieldType = {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ApiFilled, ArrowLeftOutlined } from '@ant-design/icons'
|
||||
import { ApiFilled, ApiOutlined, ArrowLeftOutlined } from '@ant-design/icons'
|
||||
import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const.tsx'
|
||||
import { EntityItem, RouterParams } from '@common/const/type.ts'
|
||||
import { useBreadcrumb } from '@common/contexts/BreadcrumbContext.tsx'
|
||||
@@ -6,15 +6,26 @@ import { useFetch } from '@common/hooks/http.ts'
|
||||
import { $t } from '@common/locales/index.ts'
|
||||
import { Icon } from '@iconify/react/dist/iconify.js'
|
||||
import { approvalTypeTranslate } from '@market/const/serviceHub/const.tsx'
|
||||
import { App, Avatar, Button, Descriptions, Divider, Tabs } from 'antd'
|
||||
import { App, Avatar, Button, Card, Descriptions, Divider, Tabs, Tag, Tooltip } from 'antd'
|
||||
import { DefaultOptionType } from 'antd/es/cascader'
|
||||
import DOMPurify from 'dompurify'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { Link, useNavigate, useParams } from 'react-router-dom'
|
||||
import { ApplyServiceHandle, ServiceBasicInfoType, ServiceDetailType } from '../../const/serviceHub/type.ts'
|
||||
import { ApplyServiceModal } from './ApplyServiceModal.tsx'
|
||||
import ServiceHubApiDocument from './ServiceHubApiDocument.tsx'
|
||||
import Integrate from './integrate.tsx'
|
||||
import { SERVICE_KIND_OPTIONS } from '@core/const/system/const.tsx'
|
||||
import IntegrationAIContainer 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'
|
||||
|
||||
type TabItemType = {
|
||||
key: string
|
||||
label: string
|
||||
children: React.ReactNode
|
||||
icon?: React.ReactNode
|
||||
}
|
||||
|
||||
const ServiceHubDetail = () => {
|
||||
const { serviceId } = useParams<RouterParams>()
|
||||
@@ -28,6 +39,14 @@ const ServiceHubDetail = () => {
|
||||
const { modal, message } = App.useApp()
|
||||
const [mySystemOptionList, setMySystemOptionList] = useState<DefaultOptionType[]>()
|
||||
const [service, setService] = useState<ServiceDetailType>()
|
||||
const [serviceMetrics, setServiceMetrics] = useState<{ title: string; icon: React.ReactNode; value: string }[]>([])
|
||||
const [serviceTags, setServiceTags] = useState<
|
||||
{ color: string; textColor: string; title: string; content: React.ReactNode }[]
|
||||
>([])
|
||||
const [tools, setTools] = useState<Tool[]>([])
|
||||
const [tabItem, setTabItem] = useState<TabItemType[]>([])
|
||||
const [currentTab, setCurrentTab] = useState('')
|
||||
const { state } = useGlobalContext()
|
||||
const navigate = useNavigate()
|
||||
|
||||
const modifyApiDoc = (apiDoc: string, apiPrefix: string) => {
|
||||
@@ -60,7 +79,11 @@ const ServiceHubDetail = () => {
|
||||
'invoke_address',
|
||||
'approval_type',
|
||||
'service_kind',
|
||||
'site_prefix'
|
||||
'site_prefix',
|
||||
'enable_mcp',
|
||||
'mcp_server_address',
|
||||
'mcp_access_config',
|
||||
'openapi_address'
|
||||
]
|
||||
}).then((response) => {
|
||||
const { code, data, msg } = response
|
||||
@@ -73,12 +96,66 @@ const ServiceHubDetail = () => {
|
||||
setServiceName(data.service.name)
|
||||
setServiceDesc(data.service.description)
|
||||
setServiceDoc(DOMPurify.sanitize(data.service.document))
|
||||
setServiceMetricsList(data.service.basic)
|
||||
setTabItemList(data.service.basic)
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handleTabChange = (value: any) => {
|
||||
setCurrentTab(value)
|
||||
}
|
||||
|
||||
const setServiceMetricsList = (serviceBasicInfo: ServiceBasicInfoType) => {
|
||||
// 设置服务指标数据
|
||||
setServiceMetrics([
|
||||
{
|
||||
title: 'API 数量',
|
||||
icon: <ApiOutlined className="mr-[1px] text-[14px] h-[14px] w-[14px]" />,
|
||||
value: serviceBasicInfo.apiNum.toString()
|
||||
},
|
||||
{
|
||||
title: '接入消费者数量',
|
||||
icon: <Icon icon="tabler:api-app" width="14" height="14" />,
|
||||
value: serviceBasicInfo.appNum.toString()
|
||||
},
|
||||
{
|
||||
title: '30天内调用次数',
|
||||
icon: <Icon icon="iconoir:graph-up" width="14" height="14" />,
|
||||
value: formatInvokeCount(serviceBasicInfo.invokeCount ?? 0)
|
||||
}
|
||||
])
|
||||
// 设置服务标签数据
|
||||
const tags = [
|
||||
{
|
||||
color: '#7371fc1b',
|
||||
textColor: 'text-theme',
|
||||
title: serviceBasicInfo?.catalogue?.name || '-',
|
||||
content: serviceBasicInfo?.catalogue?.name || '-'
|
||||
},
|
||||
{
|
||||
color: '#fbe5e5',
|
||||
textColor: 'text-[#000]',
|
||||
title: serviceBasicInfo?.serviceKind || '-',
|
||||
content: SERVICE_KIND_OPTIONS.find((x) => x.value === serviceBasicInfo?.serviceKind)?.label || '-'
|
||||
}
|
||||
]
|
||||
|
||||
// 如果启用了MCP,添加MCP标签
|
||||
if (serviceBasicInfo?.enableMcp) {
|
||||
tags.push({
|
||||
color: '#ffc107',
|
||||
textColor: 'text-[#000]',
|
||||
title: 'MCP',
|
||||
content: 'MCP'
|
||||
})
|
||||
}
|
||||
|
||||
setServiceTags(tags)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!serviceId) {
|
||||
console.warn('缺少serviceId')
|
||||
@@ -134,120 +211,234 @@ const ServiceHubDetail = () => {
|
||||
})
|
||||
}
|
||||
|
||||
const items = [
|
||||
{
|
||||
key: 'introduction',
|
||||
label: $t('介绍'),
|
||||
children: (
|
||||
<>
|
||||
<div
|
||||
className="p-btnbase preview-document mb-PAGE_INSIDE_B"
|
||||
dangerouslySetInnerHTML={{ __html: serviceDoc || '' }}
|
||||
></div>
|
||||
</>
|
||||
),
|
||||
icon: <Icon icon="ic:baseline-space-dashboard" width="14" height="14" />
|
||||
},
|
||||
{
|
||||
key: 'api-document',
|
||||
label: $t('API 文档'),
|
||||
children: (
|
||||
<div
|
||||
className={`p-btnbase ${serviceBasicInfo?.serviceKind?.toLocaleLowerCase() === 'ai' ? 'ai-service-api-preview' : ''}`}
|
||||
>
|
||||
<ServiceHubApiDocument service={service!} />
|
||||
</div>
|
||||
),
|
||||
icon: <ApiFilled />
|
||||
},
|
||||
{
|
||||
key: 'api-integrate',
|
||||
label: $t('集成'),
|
||||
children: (
|
||||
<div
|
||||
className={`p-btnbase ${serviceBasicInfo?.serviceKind?.toLocaleLowerCase() === 'ai' ? 'ai-service-api-preview' : ''}`}
|
||||
>
|
||||
<Integrate service={service!} />
|
||||
</div>
|
||||
),
|
||||
icon: <Icon icon="icon-park-solid:whole-site-accelerator" width="15" height="15" />
|
||||
const handleToolsChange = (value: Tool[]) => {
|
||||
setTools(value)
|
||||
}
|
||||
// 格式化调用次数,添加K和M单位
|
||||
const formatInvokeCount = (count: number | null | undefined): string => {
|
||||
if (count === null || count === undefined) return '-'
|
||||
if (count >= 1000000) {
|
||||
const value = Math.floor(count / 100000) / 10
|
||||
return `${value}M`
|
||||
}
|
||||
]
|
||||
if (count >= 1000) {
|
||||
const value = Math.floor(count / 100) / 10
|
||||
return `${value}K`
|
||||
}
|
||||
return count.toString()
|
||||
}
|
||||
|
||||
/**
|
||||
* 定义一个更新标签项的函数,在serviceBasicInfo或tools变化时调用
|
||||
*/
|
||||
const updateTabItems = useCallback(() => {
|
||||
if (!serviceBasicInfo) return
|
||||
const descriptionItem = [
|
||||
{
|
||||
label: $t('供应方'),
|
||||
value: serviceBasicInfo?.team?.name || '-',
|
||||
className: 'pb-[10px]'
|
||||
},
|
||||
{
|
||||
label: $t('版本'),
|
||||
value: serviceBasicInfo?.version || '-',
|
||||
className: 'pb-[10px]'
|
||||
},
|
||||
{
|
||||
label: $t('更新时间'),
|
||||
value: serviceBasicInfo?.updateTime || '-',
|
||||
className: 'pb-[10px]',
|
||||
isTimeString: true
|
||||
},
|
||||
{
|
||||
label: $t('审核'),
|
||||
value: serviceBasicInfo?.approvalType ? $t(approvalTypeTranslate[serviceBasicInfo?.approvalType] || '-') : '-',
|
||||
className: 'pb-[0px]'
|
||||
}
|
||||
]
|
||||
const items: TabItemType[] = [
|
||||
{
|
||||
key: 'introduction',
|
||||
label: $t('介绍'),
|
||||
children: (
|
||||
<>
|
||||
<Card
|
||||
style={{
|
||||
borderRadius: '10px'
|
||||
}}
|
||||
className="w-full h-[calc(100vh-420px)] overflow-auto"
|
||||
classNames={{
|
||||
body: 'p-[10px]'
|
||||
}}
|
||||
>
|
||||
<Card
|
||||
style={{
|
||||
borderRadius: '10px'
|
||||
}}
|
||||
className={`w-full`}
|
||||
classNames={{
|
||||
body: 'p-[15px] h-auto bg-[#f8f8f8]'
|
||||
}}
|
||||
>
|
||||
<Descriptions column={1}>
|
||||
{descriptionItem.map((item, index) => (
|
||||
<Descriptions.Item key={index} label={item.label} className={item.className}>
|
||||
{item.isTimeString ? (
|
||||
<span className="truncate" title={item.value}>
|
||||
{item.value}
|
||||
</span>
|
||||
) : (
|
||||
item.value
|
||||
)}
|
||||
</Descriptions.Item>
|
||||
))}
|
||||
</Descriptions>
|
||||
</Card>
|
||||
<div
|
||||
className="p-btnbase preview-document mb-PAGE_INSIDE_B"
|
||||
dangerouslySetInnerHTML={{ __html: serviceDoc || '' }}
|
||||
></div>
|
||||
</Card>
|
||||
</>
|
||||
),
|
||||
icon: <Icon icon="ic:baseline-space-dashboard" width="14" height="14" />
|
||||
},
|
||||
{
|
||||
key: 'api-document',
|
||||
label: $t('API'),
|
||||
children: (
|
||||
<Card
|
||||
style={{
|
||||
borderRadius: '10px'
|
||||
}}
|
||||
className="w-full h-[calc(100vh-420px)] overflow-auto"
|
||||
classNames={{
|
||||
body: 'p-[10px] pt-[0px]'
|
||||
}}
|
||||
>
|
||||
<ServiceHubApiDocument service={service!} />
|
||||
</Card>
|
||||
),
|
||||
icon: <ApiFilled />
|
||||
}
|
||||
]
|
||||
if (serviceBasicInfo.enableMcp) {
|
||||
items.push({
|
||||
key: 'MCP',
|
||||
label: 'MCP',
|
||||
children: <McpToolsContainer tools={tools} customClassName="h-[calc(100vh-420px)] overflow-auto" />,
|
||||
icon: <Icon icon="ph:network-x-fill" width="15" height="15" />
|
||||
})
|
||||
}
|
||||
setTabItem(items)
|
||||
}, [serviceBasicInfo, serviceDoc, service, tools, state.language])
|
||||
|
||||
/**
|
||||
* 当初始化serviceBasicInfo时调用的函数
|
||||
* @param _serviceBasicInfo
|
||||
*/
|
||||
const setTabItemList = (_serviceBasicInfo: ServiceBasicInfoType) => {
|
||||
// 只调用更新函数,更新将由useEffect处理
|
||||
updateTabItems()
|
||||
}
|
||||
useEffect(() => {
|
||||
if (serviceBasicInfo) {
|
||||
updateTabItems()
|
||||
}
|
||||
}, [tools, updateTabItems, serviceBasicInfo])
|
||||
|
||||
return (
|
||||
<section className="grid grid-cols-5 h-full mr-PAGE_INSIDE_X">
|
||||
<section className="col-span-4 border-0 border-r-[1px] border-solid border-BORDER flex flex-col overflow-hidden">
|
||||
<section className="flex flex-col gap-btnbase p-btnbase">
|
||||
<div className="text-[18px] leading-[25px] pb-[12px]">
|
||||
<Button type="text" onClick={() => navigate(`/serviceHub/list`)}>
|
||||
<ArrowLeftOutlined className="max-h-[14px]" />
|
||||
{$t('返回')}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex">
|
||||
{/* <Avatar shape="square" size={50} className=" bg-[linear-gradient(135deg,white,#f0f0f0)] text-[#333] rounded-[12px]" > {service?.name?.substring(0,1)}</Avatar> */}
|
||||
<Avatar
|
||||
shape="square"
|
||||
size={50}
|
||||
className={`rounded-[12px] border-none rounded-[12px] ${serviceBasicInfo?.logo ? 'bg-[linear-gradient(135deg,white,#f0f0f0)]' : 'bg-theme'}`}
|
||||
src={
|
||||
serviceBasicInfo?.logo ? (
|
||||
<img
|
||||
src={serviceBasicInfo?.logo}
|
||||
alt="Logo"
|
||||
style={{ maxWidth: '200px', width: '45px', height: '45px', objectFit: 'unset' }}
|
||||
/>
|
||||
) : undefined
|
||||
}
|
||||
icon={serviceBasicInfo?.logo ? '' : <iconpark-icon name="auto-generate-api"></iconpark-icon>}
|
||||
>
|
||||
{' '}
|
||||
</Avatar>
|
||||
|
||||
<div className="pl-[20px] w-[calc(100%-50px)]">
|
||||
<p className="text-[14px] h-[20px] leading-[20px] truncate font-bold flex items-center gap-[4px]">
|
||||
<div className="pr-[40px]">
|
||||
<header>
|
||||
<Button type="text" onClick={() => navigate(`/serviceHub/list`)}>
|
||||
<ArrowLeftOutlined className="max-h-[14px]" />
|
||||
{$t('返回')}
|
||||
</Button>
|
||||
</header>
|
||||
<Card
|
||||
style={{
|
||||
borderRadius: '10px',
|
||||
background: 'linear-gradient(35deg, rgb(246, 246, 260) 0%, rgb(255, 255, 255) 40%)'
|
||||
}}
|
||||
className={`w-full mt-[20px]`}
|
||||
classNames={{
|
||||
body: 'p-[15px] h-[180px]'
|
||||
}}
|
||||
>
|
||||
<div className="service-info">
|
||||
<div className="flex items-center">
|
||||
<div>
|
||||
<Avatar
|
||||
shape="square"
|
||||
size={50}
|
||||
className={`rounded-[12px] border-none rounded-[12px] ${serviceBasicInfo?.logo ? 'bg-[linear-gradient(135deg,white,#f0f0f0)]' : 'bg-theme'}`}
|
||||
src={
|
||||
serviceBasicInfo?.logo ? (
|
||||
<img
|
||||
src={serviceBasicInfo?.logo}
|
||||
alt="Logo"
|
||||
style={{ maxWidth: '200px', width: '45px', height: '45px', objectFit: 'unset' }}
|
||||
/>
|
||||
) : undefined
|
||||
}
|
||||
icon={serviceBasicInfo?.logo ? '' : <Icon icon="tabler:api-app" />}
|
||||
>
|
||||
{' '}
|
||||
</Avatar>
|
||||
</div>
|
||||
<div className="pl-[20px] w-[calc(100%-50px)] overflow-hidden">
|
||||
<p className="text-[14px] h-[20px] leading-[20px] truncate font-bold w-full flex items-center gap-[4px]">
|
||||
{serviceName}
|
||||
</p>
|
||||
<div className="mt-[10px] flex flex-col gap-btnrbase font-normal">
|
||||
<p>{serviceDesc || '-'}</p>
|
||||
<p className="flex items-center gap-[4px]">
|
||||
<Icon icon="ic:baseline-link" width="18" height="18" />
|
||||
<span className="font-bold">{$t('Base URL')}</span>: {serviceBasicInfo?.invokeAddress || '-'}
|
||||
</p>
|
||||
<div>
|
||||
<Button type="primary" onClick={() => openModal('apply')}>
|
||||
{$t('申请')}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="mt-[5px] h-[20px] flex items-center font-normal">
|
||||
{serviceTags.map((tag, index) => (
|
||||
<Tag
|
||||
key={index}
|
||||
color={tag.color}
|
||||
className={`${tag.textColor} font-normal border-0 mr-[12px] max-w-[150px] truncate`}
|
||||
bordered={false}
|
||||
title={tag.title}
|
||||
>
|
||||
{tag.content}
|
||||
</Tag>
|
||||
))}
|
||||
{serviceMetrics.map((item, index) => (
|
||||
<Tooltip key={index} title={$t(item.title)}>
|
||||
<span className="mr-[12px] flex items-center">
|
||||
<span className="h-[14px] mr-[4px] flex items-center">{item.icon}</span>
|
||||
<span className="font-normal text-[14px]">{item.value}</span>
|
||||
</span>
|
||||
</Tooltip>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<Tabs className="p-btnbase pr-0 overflow-hidden [&>.ant-tabs-content-holder]:overflow-auto" items={items} />
|
||||
</section>
|
||||
<section className="col-span-1 p-btnbase px-btnrbase">
|
||||
<Descriptions title={$t('服务信息')} column={1} size={'small'}>
|
||||
<Descriptions.Item label={$t('接入消费者')}>{serviceBasicInfo?.appNum ?? '-'}</Descriptions.Item>
|
||||
<Descriptions.Item label={$t('供应方')}>{serviceBasicInfo?.team?.name || '-'}</Descriptions.Item>
|
||||
<Descriptions.Item label={$t('审核')}>
|
||||
{serviceBasicInfo?.approvalType ? $t(approvalTypeTranslate[serviceBasicInfo?.approvalType] || '-') : '-'}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label={$t('分类')}>{serviceBasicInfo?.catalogue?.name || '-'}</Descriptions.Item>
|
||||
<Descriptions.Item label={$t('标签')}>
|
||||
{serviceBasicInfo?.tags?.map((x) => x.name)?.join(',') || '-'}
|
||||
</Descriptions.Item>
|
||||
</Descriptions>
|
||||
<Divider />
|
||||
<Descriptions column={1}>
|
||||
<Descriptions.Item label={$t('版本')}>{serviceBasicInfo?.version || '-'}</Descriptions.Item>
|
||||
<Descriptions.Item label={$t('更新时间')}>
|
||||
<span className="truncate" title={serviceBasicInfo?.updateTime}>
|
||||
{serviceBasicInfo?.updateTime || '-'}
|
||||
</span>
|
||||
</Descriptions.Item>
|
||||
</Descriptions>
|
||||
</section>
|
||||
</section>
|
||||
<span className="line-clamp-2 mt-[15px] text-[12px] text-[#666]" title={serviceDesc}>
|
||||
{serviceDesc || $t('暂无服务描述')}
|
||||
</span>
|
||||
</div>
|
||||
<div className="absolute bottom-[15px]">
|
||||
<Button type="primary" onClick={() => openModal('apply')}>
|
||||
{$t('申请')}
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
<div className="flex">
|
||||
<Tabs
|
||||
className="p-btnbase pr-0 overflow-hidden [&>.ant-tabs-content-holder]:overflow-auto w-full flex-1 mr-[10px]"
|
||||
onChange={handleTabChange}
|
||||
items={tabItem}
|
||||
/>
|
||||
<IntegrationAIContainer
|
||||
service={service}
|
||||
currentTab={currentTab}
|
||||
serviceId={serviceId}
|
||||
customClassName="mt-[70px] max-h-[calc(100vh-420px)] overflow-auto"
|
||||
type={'service'}
|
||||
handleToolsChange={handleToolsChange}
|
||||
></IntegrationAIContainer>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -160,7 +160,7 @@ const ServiceHubList: FC = () => {
|
||||
classNames={{ header: 'border-b-[0px] p-[20px] ', body: 'pt-0 h-[110px]' }}
|
||||
onClick={() => showDocumentDetail(item)}
|
||||
>
|
||||
<span className="line-clamp-3 text-[12px] text-[#666] " style={{ 'word-break': 'auto-phrase' }}>
|
||||
<span className="line-clamp-3 text-[12px] text-[#666]" title={item.description}>
|
||||
{item.description || $t('暂无服务描述')}
|
||||
</span>
|
||||
<CardAction service={item} />
|
||||
@@ -283,7 +283,7 @@ const CardAction = (props: { service: ServiceHubTableListItem }) => {
|
||||
<Tooltip title={$t('接入消费者数量')}>
|
||||
<span className="mr-[12px] flex items-center">
|
||||
<span className="h-[14px] mr-[4px] flex items-center ">
|
||||
<iconpark-icon size="14px" name="auto-generate-api"></iconpark-icon>
|
||||
<Icon icon="tabler:api-app" width="14" height="14" />
|
||||
</span>
|
||||
<span className="font-normal text-[14px]">{service.subscriberNum ?? '-'}</span>
|
||||
</span>
|
||||
@@ -293,7 +293,7 @@ const CardAction = (props: { service: ServiceHubTableListItem }) => {
|
||||
<span className="h-[14px] mr-[4px] flex items-center ">
|
||||
<Icon icon="iconoir:graph-up" width="14" height="14" />
|
||||
</span>
|
||||
<span className="font-normal text-[14px]">{formatInvokeCount(service.invokeCount)}</span>
|
||||
<span className="font-normal text-[14px]">{formatInvokeCount(service.invokeCount ?? 0)}</span>
|
||||
</span>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user