-
- { t('index.latestUpdate')}
-
- {versionInfo?.version}
-
-
-
- {versionInfo && (() => {
- const introduction = getIntroduction();
- return introduction ? (<>
-
-
-
- {t('version.releaseDate')}: {introduction.releaseDate}
-
-
-
- {t('version.name')}: {introduction.codeName}
-
-
-
- {introduction.coreUpgrades?.map((item: string, index: number) => (
-
- ))}
- >) : null;
- })()}
- {/* {loading ? (
- t('index.loading')
- ) : (
- versionInfo?.introduction || t('index.latestUpdateDesc')
- )} */}
-
- {/*
-
- { t('index.viewDetails')}
-
-
-
- { t('index.changeLog')}
-
-
-
*/}
+
+
+ {t('index.latestUpdate')}
+
+ {versionInfo?.version}
+
+
+ {versionInfo && (() => {
+ const introduction = getIntroduction();
+ return introduction ? (<>
+
+ {t('version.releaseDate')}: {introduction.releaseDate} | {t('version.name')}: {introduction.codeName}
+
+
+
+ {introduction.coreUpgrades?.map((item: string, index: number) => (
+
+ ))}
+
+ >) : null;
+ })()}
);
};
-export default GuideCard;
\ No newline at end of file
+export default VersionCard;
\ No newline at end of file
diff --git a/web/src/views/Index/index.tsx b/web/src/views/Index/index.tsx
index 3cab0276..11526b31 100644
--- a/web/src/views/Index/index.tsx
+++ b/web/src/views/Index/index.tsx
@@ -1,12 +1,12 @@
import { useEffect, useState, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
-import { Space, Button } from 'antd';
+import { Space, Button, Row, Col, Flex } from 'antd';
+
import TopCardList from './components/TopCardList';
import GuideCard from './components/GuideCard';
import VersionCard from './components/VersionCard';
import QuickActions from './components/QuickActions';
-import bgImg from '@/assets/images/index/index_bg@2x.png'
import Table, { type TableRef } from '@/components/Table'
import type { ColumnsType } from 'antd/es/table';
import { formatDateTime } from '@/utils/format';
@@ -42,16 +42,15 @@ const Index = () => {
dataIndex: 'name',
key: 'name',
},
-
{
title: t('space.spaceIcon'),
dataIndex: 'icon',
key: 'icon',
render:(value: string, record: any) => {
return value ? (
-
+
) : (
-
+
{record.name?.charAt(0)?.toUpperCase() || '?'}
)
@@ -84,7 +83,7 @@ const Index = () => {
width: 100,
render: (_, record) => (
- handleJump(record.id)} color="primary" variant="text">{t('space.enterSpace')}
+ handleJump(record.id)}>{t('space.enterSpace')}
),
},
@@ -99,44 +98,40 @@ const Index = () => {
return (
-
-
-
-
-
- { t('index.spaceTitle' )}
-
-
- { t('index.spaceSubTitle' )}
-
+
+
+
+
+
+ {t('index.spaceTitle')}
+
+
+ {t('index.spaceSubTitle')}
+
{/* 统计卡片 */}
-
-
- {/* 引导 */}
-
-
-
-
- {/* 快捷操作 */}
-
-
-
-
-
-
-
-
+
+
+
+
+ {/* 引导 */}
+
+
+
+
+
);
}
diff --git a/web/src/views/ModelManagement/Group.tsx b/web/src/views/ModelManagement/Group.tsx
index 292db860..e8a29fc4 100644
--- a/web/src/views/ModelManagement/Group.tsx
+++ b/web/src/views/ModelManagement/Group.tsx
@@ -1,8 +1,8 @@
/*
* @Author: ZhaoYing
* @Date: 2026-02-03 16:50:00
- * @Last Modified by: ZhaoYing
- * @Last Modified time: 2026-02-03 16:50:00
+ * @Last Modified by: ZhaoYing
+ * @Last Modified time: 2026-03-20 18:50:41
*/
/**
* Group Model View
@@ -12,14 +12,15 @@
import { useState, useEffect, forwardRef, useImperativeHandle } from 'react';
import clsx from 'clsx'
-import { Button } from 'antd'
+import { Button, Flex, Tooltip, Space } from 'antd'
import { useTranslation } from 'react-i18next';
import type { ProviderModelItem, ModelListItem, DescriptionItem, BaseRef } from './types'
-import RbCard from '@/components/RbCard/Card'
+import RbCard from '@/components/RbCard'
import { getModelNewList } from '@/api/models'
import PageEmpty from '@/components/Empty/PageEmpty';
import { formatDateTime } from '@/utils/format';
+import Tag from '@/components/Tag'
/**
* Group model list component
@@ -50,11 +51,6 @@ const Group = forwardRef
:(
-
+
{list.map(item => (
- {item.name[0]}
-
- }
+ avatarText={item.name[0]}
+ title={
+
+ {item.name}
+
+
+ {item.is_active ? t(`common.statusEnabled`) : t(`common.statusDisabled`)}
+
+ }
+ isNeedTooltip={false}
+ footer={
handleEdit(item)}>{t('modelNew.configureBtn')} }
>
- {formatData(item)?.map((description: DescriptionItem) => (
-
- {(description.label as string)}
- {(description.children as string)}
-
- ))}
-
handleEdit(item)}>{t('modelNew.configureBtn')}
+
+ {formatData(item)?.map((description: DescriptionItem) => (
+
+ {(description.label as string)}
+ {(description.children as string)}
+
+ ))}
+
))}
diff --git a/web/src/views/ModelManagement/List.tsx b/web/src/views/ModelManagement/List.tsx
index ce4d61aa..345e8546 100644
--- a/web/src/views/ModelManagement/List.tsx
+++ b/web/src/views/ModelManagement/List.tsx
@@ -2,7 +2,7 @@
* @Author: ZhaoYing
* @Date: 2026-02-03 16:50:10
* @Last Modified by: ZhaoYing
- * @Last Modified time: 2026-02-27 10:20:51
+ * @Last Modified time: 2026-03-20 18:51:27
*/
/**
* Model List View
@@ -11,11 +11,11 @@
*/
import { useRef, useState, useEffect, forwardRef, useImperativeHandle } from 'react';
-import { Button, Flex, Row, Col } from 'antd'
+import { Button, Flex, Row, Col, Tooltip, Space } from 'antd'
import { useTranslation } from 'react-i18next';
import type { ProviderModelItem, KeyConfigModalRef, ModelListDetailRef, ModelListItem, BaseRef } from './types'
-import RbCard from '@/components/RbCard/Card'
+import RbCard from '@/components/RbCard'
import { getModelNewList } from '@/api/models'
import PageEmpty from '@/components/Empty/PageEmpty';
import Tag from '@/components/Tag';
@@ -69,26 +69,26 @@ const ModelList = forwardRef
(
- {item.provider[0].toUpperCase()}
-
- }
- bodyClassName="rb:relative rb:pb-[64px]! rb:h-[calc(100%-64px)]!"
+ avatarText={item.provider[0].toUpperCase()}
+ title={
+
+ {t(`modelNew.${item.provider}`)}
+
+
+ {item.tags.map(tag => {t(`modelNew.${tag}`)} )}
+
+ }
+ isNeedTooltip={false}
+ footer={
+
+ handleShowModel(item)}>{t('modelNew.showModel')}
+
+
+ handleKeyConfig(item)}>{t('modelNew.keyConfig')}
+
+
}
>
-
{item.tags.map(tag => {t(`modelNew.${tag}`)} )}
-
-
-
- handleShowModel(item)}>{t('modelNew.showModel')}
-
-
- handleKeyConfig(item)}>{t('modelNew.keyConfig')}
-
-
-
))}
diff --git a/web/src/views/ModelManagement/Square.tsx b/web/src/views/ModelManagement/Square.tsx
index a9b345a1..6f663a99 100644
--- a/web/src/views/ModelManagement/Square.tsx
+++ b/web/src/views/ModelManagement/Square.tsx
@@ -1,8 +1,8 @@
/*
* @Author: ZhaoYing
* @Date: 2026-02-03 16:50:14
- * @Last Modified by: ZhaoYing
- * @Last Modified time: 2026-02-03 16:50:14
+ * @Last Modified by: ZhaoYing
+ * @Last Modified time: 2026-03-23 11:33:44
*/
/**
* Model Square View
@@ -10,17 +10,17 @@
* Allows adding models and viewing details
*/
-import { useRef, useState, useEffect, forwardRef, useImperativeHandle } from 'react';
-import { Button, Space, App, Divider, Flex, Tooltip } from 'antd'
+import { useState, useEffect, forwardRef, useImperativeHandle } from 'react';
+import { Button, Space, App, Flex, Tooltip } from 'antd'
import { UsergroupAddOutlined } from '@ant-design/icons';
import { useTranslation } from 'react-i18next';
+import clsx from 'clsx';
-import type { ModelPlaza, ModelPlazaItem, ModelSquareDetailRef, BaseRef } from './types'
-import RbCard from '@/components/RbCard/Card'
+import type { ModelPlaza, ModelPlazaItem, BaseRef } from './types'
+import RbCard from '@/components/RbCard'
import { getModelPlaza, addModelPlaza } from '@/api/models'
import PageEmpty from '@/components/Empty/PageEmpty';
import Tag from '@/components/Tag';
-import ModelSquareDetail from './components/ModelSquareDetail'
import { getLogoUrl } from './utils'
/**
@@ -29,7 +29,6 @@ import { getLogoUrl } from './utils'
const ModelSquare = forwardRef
(({ query }, ref) => {
const { t } = useTranslation();
const { message } = App.useApp()
- const modelSquareDetailRef = useRef(null)
const [list, setList] = useState([])
useEffect(() => {
getList()
@@ -38,14 +37,12 @@ const ModelSquare = forwardRef (({ query }, ref) => {
const getList = () => {
getModelPlaza(query)
.then(res => {
- setList((res as ModelPlaza[]) || [])
+ const response = res as ModelPlaza[]
+ setList(response || [])
+ setActiveProvider(response[0]?.provider || null)
})
}
- /** Open model detail drawer */
- const handleMore = (vo: ModelPlaza) => {
- modelSquareDetailRef.current?.handleOpen(vo)
- }
/** Add model to workspace */
const handleAdd = (item: ModelPlazaItem) => {
addModelPlaza(item.id)
@@ -59,61 +56,86 @@ const ModelSquare = forwardRef (({ query }, ref) => {
useImperativeHandle(ref, () => ({
getList,
}));
+
+ const [activeProvider, setActiveProvider] = useState(null)
return (
<>
{list.length === 0
?
- : list.map(vo => (
-
-
-
{t(`modelNew.${vo.provider}`)}
-
handleMore(vo)}>{t('modelNew.viewAll')}({t(`modelNew.modelCount`, { count: vo.models.length })})>
-
-
-
- {vo.models.slice(0, 6).map(item => (
-
- {t(`modelNew.${item.type}`)}
- {item.is_official && {t(`modelNew.official`)} }
- }
- avatarUrl={getLogoUrl(item.logo)}
- avatar={
-
- {item.name[0]}
-
- }
- bodyClassName="rb:relative rb:pb-[80px]! rb:h-[calc(100%-64px)]!"
- >
-
- {item.description}
-
- {item.tags.map((tag, tagIndex) => {tag} )}
-
-
-
- {item.add_count}
-
- {item.is_added
- ? {t('modelNew.added')}
- : handleAdd(item)}>{item.is_deprecated ? t('modelNew.deprecated') : `+ ${t('common.add')}`}
- }
-
+ : <>
+
+ {list.map(vo => (
+ setActiveProvider(vo.provider)}
+ >{t(`modelNew.${vo.provider}`)}
+ ))}
+
+ {list.filter(vo => vo.provider === activeProvider).map(vo => (
+
+
+ {vo.models.map(item => (
+
+
+
+ {item.name}
+
+
+ {t(`modelNew.${item.type}`)}
+ {item.is_official && {t(`modelNew.official`)} }
+
+
+ handleAdd(item)}
+ >{item.is_deprecated ? t('modelNew.deprecated') : '+'}
-
-
- ))}
-
-
- ))
- }
+ }
+ isNeedTooltip={false}
+ footer={
+ @{t(`modelNew.${vo.provider}`)}
+ {item.add_count}
+ }
+ >
+
+ {item.description}
+
-
+
+
+ {item.tags?.slice(0, 2).map((type, i) => (
+ {type}
+ ))}
+
+ {item.tags.length > 2 && (
+ {item.tags?.slice(2, item.tags.length).map((type, i) => (
+ {type}
+ ))} }
+ color="white"
+ placement="bottom"
+ >
+ +{item.tags.length - 2}
+
+ )}
+
+
+ ))}
+
+
+ ))}
+ >
+ }
>
)
})
diff --git a/web/src/views/ModelManagement/components/ModelSquareDetail.tsx b/web/src/views/ModelManagement/components/ModelSquareDetail.tsx
deleted file mode 100644
index 6826e9f5..00000000
--- a/web/src/views/ModelManagement/components/ModelSquareDetail.tsx
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * @Author: ZhaoYing
- * @Date: 2026-02-03 16:49:49
- * @Last Modified by: ZhaoYing
- * @Last Modified time: 2026-03-04 11:50:31
- */
-/**
- * Model Square Detail Drawer
- * Displays all models from a specific provider in the model square
- * Allows adding models and editing custom models
- */
-
-import { useState, useImperativeHandle, forwardRef } from 'react';
-import { useTranslation } from 'react-i18next';
-import { Button, Space, App, Flex, Tooltip, Divider } from 'antd'
-import { UsergroupAddOutlined } from '@ant-design/icons';
-
-import type { ModelPlaza, ModelPlazaItem, ModelSquareDetailRef } from '../types';
-import RbDrawer from '@/components/RbDrawer';
-import { getModelPlaza, addModelPlaza } from '@/api/models'
-import RbCard from '@/components/RbCard/Card'
-import Tag from '@/components/Tag';
-import PageEmpty from '@/components/Empty/PageEmpty';
-import { getLogoUrl } from '../utils'
-
-/**
- * Component props
- */
-interface ModelSquareDetailProps {
- /** Callback to refresh parent list */
- refresh: () => void;
-}
-
-/**
- * Model square detail drawer component
- */
-const ModelSquareDetail = forwardRef(({ refresh }, ref) => {
- const { t } = useTranslation();
- const { message } = App.useApp()
- const [model, setModel] = useState({} as ModelPlaza)
- const [open, setOpen] = useState(false);
-
- const [list, setList] = useState([])
-
- /** Open drawer with model plaza data */
- const handleOpen = (vo: ModelPlaza) => {
- setModel(vo)
- setOpen(true)
- getList(vo)
- }
- /** Close drawer */
- const handleClose = () => {
- setOpen(false)
- refresh()
- }
- /** Fetch model list for provider */
- const getList = (vo: ModelPlaza) => {
- getModelPlaza({ provider: vo.provider })
- .then(res => {
- const response = res as ModelPlaza[]
- setList(response.length > 0 ? response[0].models : [])
- })
- }
- /** Add model to workspace */
- const handleAdd = (item: ModelPlazaItem) => {
- addModelPlaza(item.id)
- .then(() => {
- message.success(`${item.name}${t('modelNew.addSuccess')}`)
- getList(model)
- })
- }
-
- /** Expose methods to parent component */
- useImperativeHandle(ref, () => ({
- handleOpen,
- }));
-
- return (
- {t(`modelNew.${model.provider}`)} {t('modelNew.modelList')} ({list.length}{t('modelNew.item')})>}
- open={open}
- onClose={handleClose}
- >
-
- {list.length === 0
- ?
- :
- {list.map(item => (
-
- {t(`modelNew.${item.type}`)}
- {item.is_official && {t(`modelNew.official`)} }
- {item.capability?.filter(item => item !== 'video').map(vo => {t(`modelNew.${vo}`)} )}
- }
- avatarUrl={getLogoUrl(item.logo)}
- avatar={
-
- {item.name[0]}
-
- }
- bodyClassName="rb:relative rb:pb-[80px]! rb:h-[calc(100%-64px)]!"
- >
-
- {item.description}
-
- {item.tags.map((tag, tagIndex) => {tag} )}
-
-
-
- {item.add_count}
-
- {item.is_added
- ? {t('modelNew.added')}
- : handleAdd(item)}>{item.is_deprecated ? t('modelNew.deprecated') : `+ ${t('common.add')}`}
- }
-
-
-
-
- ))}
-
- }
-
-
- );
-});
-
-export default ModelSquareDetail;
\ No newline at end of file
diff --git a/web/src/views/ModelManagement/index.tsx b/web/src/views/ModelManagement/index.tsx
index 539ff5e3..15ed1424 100644
--- a/web/src/views/ModelManagement/index.tsx
+++ b/web/src/views/ModelManagement/index.tsx
@@ -1,8 +1,8 @@
/*
* @Author: ZhaoYing
* @Date: 2026-02-03 16:50:05
- * @Last Modified by: ZhaoYing
- * @Last Modified time: 2026-02-03 16:50:05
+ * @Last Modified by: ZhaoYing
+ * @Last Modified time: 2026-03-20 19:02:31
*/
/**
* Model Management Main Page
@@ -84,7 +84,7 @@ const tabKeys = ['group', 'list', 'square']
}
return (
- <>
+
items.map((item) => ({ label: t(`modelNew.${item}`), value: String(item) }))}
- className="rb:w-30"
+ className="rb:w-40"
allowClear={true}
placeholder={t('modelNew.type')}
/>
}
- {(activeTab === 'list' || activeTab === 'square') &&
+ {activeTab === 'list' &&
items.map((item) => ({ label: t(`modelNew.${item}`), value: String(item) }))}
- className="rb:w-30"
+ className="rb:w-40"
allowClear={true}
placeholder={t('modelNew.provider')}
/>
@@ -123,7 +123,6 @@ const tabKeys = ['group', 'list', 'square']
}
@@ -133,7 +132,7 @@ const tabKeys = ['group', 'list', 'square']
-
+
{activeTab === 'group' &&
}
{activeTab === 'list' &&
}
{activeTab === 'square' &&
}
@@ -146,7 +145,7 @@ const tabKeys = ['group', 'list', 'square']
ref={customModelModalRef}
refresh={handleRefresh}
/>
- >
+
)
}
diff --git a/web/src/views/ModelManagement/types.ts b/web/src/views/ModelManagement/types.ts
index 325a5d94..a44b5ce1 100644
--- a/web/src/views/ModelManagement/types.ts
+++ b/web/src/views/ModelManagement/types.ts
@@ -2,7 +2,7 @@
* @Author: ZhaoYing
* @Date: 2026-02-03 16:50:18
* @Last Modified by: ZhaoYing
- * @Last Modified time: 2026-03-07 16:14:25
+ * @Last Modified time: 2026-03-20 20:21:45
*/
/**
* Type definitions for Model Management
@@ -270,14 +270,6 @@ export interface ModelPlazaItem {
is_omni?: boolean;
}
-/**
- * Model square detail ref interface
- */
-export interface ModelSquareDetailRef {
- /** Open detail drawer with model plaza data */
- handleOpen: (vo: ModelPlaza) => void;
-}
-
/**
* Custom model form data
*/
diff --git a/web/src/views/Ontology/index.tsx b/web/src/views/Ontology/index.tsx
index bee4ebe6..834dca80 100644
--- a/web/src/views/Ontology/index.tsx
+++ b/web/src/views/Ontology/index.tsx
@@ -2,23 +2,25 @@
* @Author: ZhaoYing
* @Date: 2026-02-03 14:10:15
* @Last Modified by: ZhaoYing
- * @Last Modified time: 2026-03-06 10:56:44
+ * @Last Modified time: 2026-03-20 16:36:02
*/
-import { type FC, useState, useRef, type MouseEvent } from 'react';
+import { type FC, useState, useRef } from 'react';
+import type { MenuInfo } from 'rc-menu/lib/interface';
import { useNavigate } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
-import { Row, Col, Button, Flex, Divider, Space, App, Tooltip } from 'antd'
+import { Row, Col, Flex, Space, App, Tooltip, Dropdown } from 'antd'
import SearchInput from '@/components/SearchInput';
import OntologyModal from './components/OntologyModal'
import type { OntologyModalRef, OntologyItem, Query, OntologyImportModalRef, OntologyExportModalRef } from './types'
-import RbCard from '@/components/RbCard/Card'
+import RbCard from '@/components/RbCard'
import Tag from '@/components/Tag'
import PageScrollList, { type PageScrollListRef } from '@/components/PageScrollList'
import { getOntologyScenesUrl, deleteOntologyScene } from '@/api/ontology'
import { formatDateTime } from '@/utils/format'
import OntologyImportModal from './components/OntologyImportModal'
import OntologyExportModal from './components/OntologyExportModal'
+import RbButton from '@/components/RbButton'
/**
* Ontology management page component
@@ -51,20 +53,18 @@ const Ontology: FC = () => {
* @param record - The ontology item to edit
* @param e - Mouse event to prevent propagation
*/
- const handleEdit = (record: OntologyItem, e: MouseEvent) => {
- e.preventDefault();
- e.stopPropagation();
+ const handleEdit = (record: OntologyItem, e: MenuInfo) => {
+ e.domEvent.stopPropagation();
entityModalRef.current?.handleOpen(record)
}
/**
* Delete an ontology scene with confirmation
* @param item - The ontology item to delete
- * @param e - Mouse event to prevent propagation
+ * @param e - Menu click info
*/
- const handleDelete = (item: OntologyItem, e: MouseEvent) => {
- e.preventDefault();
- e.stopPropagation();
+ const handleDelete = (item: OntologyItem, e: MenuInfo) => {
+ e.domEvent.stopPropagation();
modal.confirm({
title: t('common.confirmDeleteDesc', { name: item.scene_name }),
okText: t('common.delete'),
@@ -111,28 +111,23 @@ const Ontology: FC = () => {
return (
<>
-
-
- setQuery({ scene_name: value })}
- className="rb:w-full!"
- />
-
-
-
-
- {t('ontology.export')}
-
-
- {t('ontology.import')}
-
-
- + {t('ontology.create')}
-
-
-
-
+
+ setQuery({ scene_name: value })}
+ />
+
+
+ {t('ontology.export')}
+
+
+ {t('ontology.import')}
+
+
+ + {t('ontology.create')}
+
+
+
ref={scrollListRef}
@@ -141,58 +136,70 @@ const Ontology: FC = () => {
column={3}
renderItem={(item) =>(
{item.type_num} {t('ontology.typeCount')}}
- onClick={() => handleJump(item)}
- className="rb:cursor-pointer rb:relative"
- >
- {item.is_system_default &&
-
- {t('common.default')}
-
+ title={
+
+
+ {item.scene_name}
+
+ {item.type_num} {t('ontology.typeCount')}
+ {item.is_system_default && {t('common.default')} }
+
+
+ ,
+ label: t('common.edit'),
+ onClick: (e: MenuInfo) => handleEdit(item, e),
+ },
+ {
+ key: 'delete',
+ icon:
,
+ label: t('common.delete'),
+ onClick: (e: MenuInfo) => handleDelete(item, e),
+ },
+ ]
+ }}
+ placement="bottomRight"
+ >
+
+
+
}
-
- {t(`ontology.scene_description`)}
-
- {item.scene_description}
-
-
- {(['created_at', 'updated_at'] as const).map(key => (
-
- {t(`ontology.${key}`)}
- {formatDateTime(item[key])}
-
- ))}
-
-
- {t('ontology.entityTypes')}:
-
+ isNeedTooltip={false}
+ headerClassName="rb:pb-0!"
+ onClick={() => handleJump(item)}
+ className="rb:cursor-pointer!"
+ >
+
+ {item.scene_description}
+
+
+
+
{item.entity_type?.map((type, i) => (
- {type}
+ {type}
))}
-
+
{item.type_num > 3 && (
- +{item.type_num - 3}
+ +{item.type_num - 3}
)}
-
- {!item.is_system_default &&
- handleEdit(item, e)}
- >
- handleDelete(item, e)}
- >
- }
-
+
+ {(['created_at', 'updated_at'] as const).map(key => (
+
+ {t(`ontology.${key}`)}
+ {formatDateTime(item[key])}
+
+ ))}
+
)}
/>
diff --git a/web/src/views/Ontology/pages/Detail.tsx b/web/src/views/Ontology/pages/Detail.tsx
index 25609083..ac886277 100644
--- a/web/src/views/Ontology/pages/Detail.tsx
+++ b/web/src/views/Ontology/pages/Detail.tsx
@@ -2,17 +2,17 @@
* @Author: ZhaoYing
* @Date: 2026-02-03 14:10:20
* @Last Modified by: ZhaoYing
- * @Last Modified time: 2026-03-06 11:26:49
+ * @Last Modified time: 2026-03-20 16:35:14
*/
import { type FC, useEffect, useState, useRef } from 'react'
-import { useParams } from 'react-router-dom';
+import { useParams, useNavigate } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
-import { App, Row, Col, Tooltip, Space, Button } from 'antd'
+import { App, Row, Col, Tooltip, Space, Button, Flex } from 'antd'
-import PageHeader from '../components/PageHeader'
+import PageHeader from '@/components/Layout/PageHeader'
import { getOntologyClassList, deleteOntologyClass } from '@/api/ontology'
import type { OntologyClassData, OntologyClassModalRef, OntologyClassExtractModalRef, OntologyClassItem } from '@/views/Ontology/types'
-import RbCard from '@/components/RbCard/Card';
+import RbCard from '@/components/RbCard';
import OntologyClassModal from '../components/OntologyClassModal'
import SearchInput from '@/components/SearchInput';
import OntologyClassExtractModal from '../components/OntologyClassExtractModal'
@@ -26,6 +26,7 @@ import Tag from '@/components/Tag'
const Detail: FC = () => {
// Hooks
const { t } = useTranslation();
+ const navigate = useNavigate()
const { id } = useParams()
const { modal, message } = App.useApp()
@@ -100,19 +101,29 @@ const Detail: FC = () => {
return (
<>
+ title={
{data.scene_name}
{data.is_system_default ? {t('common.default')} : undefined}
+
+
+
+ }
+ extra={
+ {data.is_system_default ? undefined : (
+ + {t('ontology.addClass')}
+ + {t('ontology.extract')}
+ )}
+ navigate(-1)}>
+
+ {t('common.return')}
+
}
- subTitle={{data.scene_description}
}
- extra={data.is_system_default ? undefined : (
- + {t('ontology.addClass')}
- + {t('ontology.extract')}
- )}
/>
-
-
+
+
{
handleDelete(item)}
>
)}
- className="rb:bg-transparent!"
>
- {item.class_description}
+ {item.class_description}
diff --git a/web/src/views/Skills/index.tsx b/web/src/views/Skills/index.tsx
index 1522a3c8..f5115df5 100644
--- a/web/src/views/Skills/index.tsx
+++ b/web/src/views/Skills/index.tsx
@@ -1,8 +1,8 @@
/*
* @Author: ZhaoYing
* @Date: 2026-02-05 10:43:49
- * @Last Modified by: ZhaoYing
- * @Last Modified time: 2026-02-05 10:43:49
+ * @Last Modified by: ZhaoYing
+ * @Last Modified time: 2026-03-20 20:28:44
*/
import React, { useRef } from 'react';
import { Button, Tooltip } from 'antd';
@@ -10,9 +10,10 @@ import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import type { Skill } from './types'
-import RbCard from '@/components/RbCard/Card'
+import RbCard from '@/components/RbCard'
import { getSkillListUrl } from '@/api/skill'
import PageScrollList, { type PageScrollListRef } from '@/components/PageScrollList'
+import { formatDateTime } from '@/utils/format'
/**
* Skills List Page Component
@@ -66,14 +67,15 @@ const Skills: React.FC = () => {
return (
{item.name[0]}
}
className="rb:cursor-pointer"
+ titleClassName="rb:line-clamp-1!"
onClick={() => handleView(item)}
>
{/* Skill description with tooltip */}
- {item.description}
+ {item.description}
+ {t('common.updated_at')}: {formatDateTime(item.updated_at)}
);
}}
diff --git a/web/src/views/SpaceManagement/index.tsx b/web/src/views/SpaceManagement/index.tsx
index 40f3d2b0..a11c9d9e 100644
--- a/web/src/views/SpaceManagement/index.tsx
+++ b/web/src/views/SpaceManagement/index.tsx
@@ -1,8 +1,8 @@
/*
* @Author: ZhaoYing
* @Date: 2026-02-03 17:48:59
- * @Last Modified by: ZhaoYing
- * @Last Modified time: 2026-02-03 17:48:59
+ * @Last Modified by: ZhaoYing
+ * @Last Modified time: 2026-03-20 18:49:51
*/
/**
* Space Management Page
@@ -11,13 +11,12 @@
import React, { useEffect, useState, useRef } from 'react';
import { useNavigate } from 'react-router-dom';
-import clsx from 'clsx';
import { useTranslation } from 'react-i18next';
-import { List, Button } from 'antd';
+import { List, Button, Flex, Space as AntSpace, Tooltip } from 'antd';
import type { Space, SpaceModalRef } from './types';
import SpaceModal from './components/SpaceModal';
-import RbCard from '@/components/RbCard/Card'
+import RbCard from '@/components/RbCard'
import { getWorkspaces, switchWorkspace } from '@/api/workspaces'
import BodyWrapper from '@/components/Empty/BodyWrapper'
import Tag from '@/components/Tag'
@@ -76,20 +75,21 @@ const SpaceManagement: React.FC = () => {
- {item.name[0]}
- }
- title={item.name}
- subTitle={
{t(`space.${item.storage_type || 'neo4j'}`)} }
- >
-
{item.is_active ? t('space.associated') : t('space.notAssociated')}
-
-
handleJump(item.id)}>
+ avatarText={item.name[0]}
+ title={
+
+ {item.name}
+
+
+ {t(`space.${item.storage_type || 'neo4j'}`)}
+ {item.is_active ? t('space.associated') : t('space.notAssociated')}
+
+ }
+ isNeedTooltip={false}
+ footer={ handleJump(item.id)}>
{t('space.enterSpace')}
-
+ }
+ >
)}
diff --git a/web/src/views/ToolManagement/Custom.tsx b/web/src/views/ToolManagement/Custom.tsx
index 269b726e..6b0daeb7 100644
--- a/web/src/views/ToolManagement/Custom.tsx
+++ b/web/src/views/ToolManagement/Custom.tsx
@@ -1,37 +1,40 @@
-import React, { useState, useRef, useEffect, type ReactNode } from 'react';
+import { useState, useRef, useEffect, forwardRef, useImperativeHandle, type ReactNode } from 'react';
import {
- Button,
Row,
Col,
App,
List,
- Space
+ Space,
+ Flex,
+ Tooltip,
+ Dropdown,
} from 'antd';
import { useTranslation } from 'react-i18next';
-import dayjs from 'dayjs';
-import type { ToolItem, Query, CustomToolModalRef } from './types';
+import type { ToolItem, CustomToolModalRef, CustomRef } from './types';
import CustomToolModal from './components/CustomToolModal';
-import SearchInput from '@/components/SearchInput'
import BodyWrapper from '@/components/Empty/BodyWrapper'
-import RbCard from '@/components/RbCard/Card'
+import RbCard from '@/components/RbCard'
import { getTools, deleteTool } from '@/api/tools'
+import { formatDateTime } from '@/utils/format'
-const Custom: React.FC<{ getStatusTag: (status: string) => ReactNode }> = ({ getStatusTag }) => {
+const Custom = forwardRef
ReactNode; keyword?: string | undefined }>(({ getStatusTag, keyword }, ref) => {
const { t } = useTranslation();
const { message, modal } = App.useApp()
const [loading, setLoading] = useState(false);
const [data, setData] = useState([]);
- const [query, setQuery] = useState({ name: undefined, tool_type: 'custom' });
const customToolModalRef = useRef(null);
useEffect(() => {
getData()
- }, [query.name])
+ }, [keyword])
const getData = () => {
setLoading(true)
- getTools(query)
+ getTools({
+ tool_type: 'custom',
+ name: keyword
+ })
.then((res) => {
setData(res as ToolItem[])
})
@@ -39,15 +42,14 @@ const Custom: React.FC<{ getStatusTag: (status: string) => ReactNode }> = ({ get
setLoading(false)
})
}
- const handleSearch = (value?: string) => {
- setQuery(prev => ({ ...prev, name: value }))
- }
// 打开添加服务弹窗
const handleEdit = (data?: ToolItem) => {
customToolModalRef.current?.handleOpen(data);
};
+ useImperativeHandle(ref, () => ({ handleEdit }));
+
// 删除服务
const handleDeleteService = (item: ToolItem) => {
modal.confirm({
@@ -65,71 +67,80 @@ const Custom: React.FC<{ getStatusTag: (status: string) => ReactNode }> = ({ get
};
return (
-
-
-
-
-
-
- {handleEdit()}}>{t('tool.addCustom')}
-
-
+ <>
(
- // {item.name[0]}
- //
- // }
title={
-
- {item.name}
- {/*
xx个工具
*/}
-
- }
- extra={getStatusTag(item.status)}
- >
-
- {['auth_type', 'tags', 'created_at'].map(key => (
-
-
{t(`tool.${key}`)}
-
- {key === 'created_at' && item[key]
- ? dayjs(item[key]).format('YYYY-MM-DD HH:mm:ss')
- : key === 'auth_type'
- ? t(`tool.${(item.config_data as any)?.[key]}`)
- : key === 'tags'
- ? (item[key] as string[]).join('、')
- : (item.config_data as any)?.[key] || '-'
- }
-
-
- ))}
-
-
- handleEdit(item)}
- >
- handleDeleteService(item)}
- >
+
+
+
+ {item.name}
+
+ {getStatusTag(item.status)}
-
-
+ ,
+ label: t('common.edit'),
+ onClick: () => handleEdit(item),
+ },
+ {
+ key: 'delete',
+ className: 'rb:text-[#FF5D34]!',
+ icon:
,
+ label: t('common.delete'),
+ onClick: () => handleDeleteService(item),
+ },
+ ]
+ }}
+ placement="bottomRight"
+ >
+
+
+
+ }
+ isNeedTooltip={false}
+ >
+
+ {item.tags?.length > 0
+ ?
+
+ {item.tags?.slice(0, 2).map((type, i) => (
+ {type}
+ ))}
+
+ {item.tags.length > 2 && (
+ {item.tags?.slice(2, item.tags.length).map((type, i) => (
+ {type}
+ ))} }
+ color="white"
+ placement="bottom"
+ >
+ +{item.tags.length - 2}
+
+ )}
+
+ : {t('tool.noTags')}
+ }
+
+
+ {t('tool.auth_type')}
+ {(item.config_data as any)?.auth_type}
+
+
+ {t('tool.created_at')}
+ {formatDateTime(item.created_at)}
+
+
)}
@@ -142,8 +153,8 @@ const Custom: React.FC<{ getStatusTag: (status: string) => ReactNode }> = ({ get
ref={customToolModalRef}
refresh={getData}
/>
-
+ >
);
-};
+});
export default Custom;
\ No newline at end of file
diff --git a/web/src/views/ToolManagement/Inner.tsx b/web/src/views/ToolManagement/Inner.tsx
index 6f85e1f7..689ce43e 100644
--- a/web/src/views/ToolManagement/Inner.tsx
+++ b/web/src/views/ToolManagement/Inner.tsx
@@ -1,30 +1,28 @@
import React, { useState, useRef, useEffect, type ReactNode } from 'react';
import {
+ List,
+ Flex,
+ Space,
+ Tooltip,
Row,
Col,
- Tag,
- List,
- Flex
} from 'antd';
-import { EyeOutlined } from '@ant-design/icons';
import { useTranslation } from 'react-i18next';
import dayjs, { type Dayjs } from 'dayjs'
-import type { Query, ToolItem, TimeToolModalRef, JsonToolModalRef, InnerToolModalRef } from './types';
-import SearchInput from '@/components/SearchInput'
+import type { ToolItem, TimeToolModalRef, JsonToolModalRef, InnerToolModalRef } from './types';
import BodyWrapper from '@/components/Empty/BodyWrapper'
-import RbCard from '@/components/RbCard/Card'
+import RbCard from '@/components/RbCard'
import TimeToolModal from './components/TimeToolModal'
import JsonToolModal from './components/JsonToolModal'
import InnerToolModal from './components/InnerToolModal'
import { getTools } from '@/api/tools'
import { InnerConfigData } from './constant'
-const Inner: React.FC<{ getStatusTag: (status: string) => ReactNode }> = ({ getStatusTag }) => {
+const Inner: React.FC<{ getStatusTag: (status: string) => ReactNode; keyword?: string | undefined }> = ({ getStatusTag, keyword }) => {
const { t } = useTranslation();
const [loading, setLoading] = useState(false);
const [data, setData] = useState([]);
- const [query, setQuery] = useState({ name: undefined, tool_type: 'builtin' });
const [curTime, setCurTime] = useState(dayjs())
const timeToolModalRef = useRef(null)
const jsonToolModalRef = useRef(null)
@@ -38,11 +36,14 @@ const Inner: React.FC<{ getStatusTag: (status: string) => ReactNode }> = ({ getS
return () => {
clearInterval(timer)
}
- }, [query.name])
+ }, [keyword])
const getData = () => {
setLoading(true)
- getTools(query)
+ getTools({
+ tool_type: 'builtin',
+ name: keyword
+ })
.then((res) => {
setData(res as ToolItem[])
})
@@ -50,9 +51,6 @@ const Inner: React.FC<{ getStatusTag: (status: string) => ReactNode }> = ({ getS
setLoading(false)
})
}
- const handleSearch = (value?: string) => {
- setQuery(prev => ({ ...prev, name: value }))
- }
// 打开添加服务弹窗
const handleEdit = (data: ToolItem) => {
@@ -71,78 +69,77 @@ const Inner: React.FC<{ getStatusTag: (status: string) => ReactNode }> = ({ getS
return (
-
-
-
-
-
(
- // {item.name[0]}
- //
- // }
- title={item.name}
- extra={getStatusTag(item.status)}
- bodyClassName='rb:h-[calc(100%-40px)]'
- >
-
-
- {t(`tool.${item.config_data.tool_class}_features`)}
-
- {InnerConfigData[item.config_data.tool_class].features.map(vo => { t(`tool.${vo}`) } ) }
+ title={
+
+
+
+ {item.name}
+
+ {getStatusTag(item.status)}
+
+
+ handleEdit(item)}
+ />
+
+ }
+ isNeedTooltip={false}
+ >
+
+ {t(`tool.${item.config_data.tool_class}_features`)}
+
- {item.config_data.tool_class === 'DateTimeTool'
- ?
- {t('tool.currentTime')}
-
- {curTime.format('YYYY-MM-DD HH:mm:ss')}
-
- {t('tool.timestamp')}
-
- {curTime.unix()}
-
-
- :item.config_data.tool_class === 'JsonTool'
- ?
- {t('tool.jsonEg')}
-
- {InnerConfigData[item.config_data.tool_class].eg}
-
-
- :
- {t('tool.configStatus')}
-
- {t(`tool.${item.status}_desc`)}
-
-
- }
-
+
+
+ {InnerConfigData[item.config_data.tool_class].features?.slice(0, 2).map((type, i) => (
+ {type}
+ ))}
+
+ {InnerConfigData[item.config_data.tool_class].features.length > 2 && (
+ {InnerConfigData[item.config_data.tool_class].features?.slice(2, InnerConfigData[item.config_data.tool_class].features.length).map((type, i) => (
+ {type}
+ ))} }
+ color="white"
+ placement="bottom"
+ >
+ +{InnerConfigData[item.config_data.tool_class].features.length - 2}
+
+ )}
+
-
- {item.config_data.tool_class === 'DateTimeTool' || item.config_data.tool_class === 'JsonTool' ?
-
handleEdit(item)} />
- : handleEdit(item)}
- >
- }
-
-
+
+ {item.config_data.tool_class === 'DateTimeTool'
+ ? <>
+
+ {t('tool.currentTime')}
+ {curTime.format('YYYY-MM-DD HH:mm:ss')}
+
+
+ {t('tool.timestamp')}
+ {curTime.unix()}
+
+ >
+ : item.config_data.tool_class === 'JsonTool'
+ ?
+ {t('tool.jsonEg')}
+ {InnerConfigData[item.config_data.tool_class].eg}
+
+ :
+ {t('configStatus')}
+ {t(`tool.${item.status}_desc`)}
+
+ }
+
)}
diff --git a/web/src/views/ToolManagement/Market.tsx b/web/src/views/ToolManagement/Market.tsx
index 310637d7..3e2ca456 100644
--- a/web/src/views/ToolManagement/Market.tsx
+++ b/web/src/views/ToolManagement/Market.tsx
@@ -1,14 +1,20 @@
import React, { useState, useRef, useEffect, useCallback, type ReactNode } from 'react';
-import { Input, Button, App, Card, Space, Skeleton, Tag } from 'antd';
-import { SearchOutlined, SettingOutlined, GlobalOutlined, SyncOutlined } from '@ant-design/icons';
+import { Button, App, Space, Row, Col, Flex, Tooltip } from 'antd';
import { useTranslation } from 'react-i18next';
import InfiniteScroll from 'react-infinite-scroll-component';
+import clsx from 'clsx'
+
import MarketConfigModal, { type MarketConfigModalRef } from './components/MarketConfigModal';
import McpServiceModal from './components/McpServiceModal';
import type { McpServiceModalRef } from './types';
import pageEmptyIcon from '@/assets/images/empty/pageEmpty.png'
import Empty from '@/components/Empty/index'
import { getMarketTools, getMarketConfig, getMarketMCPs, getMarketMCPDetail, getMarketMCPsActivated, getTools } from '@/api/tools';
+import SearchInput from '@/components/SearchInput';
+import RbCard from '@/components/RbCard'
+import Tag from '@/components/Tag'
+import marketIcon from '@/assets/images/tool/market.png'
+
interface MarketSource {
id: string;
name: string;
@@ -97,6 +103,9 @@ const Market: React.FC<{ getStatusTag?: (status: string) => ReactNode }> = () =>
});
setCategories(Array.from(categoryMap.values()));
+ if (response.items[0]?.id) {
+ handleSelectSource(response.items[0]?.id)
+ }
}
} catch (error) {
console.error('获取市场数据失败:', error);
@@ -223,6 +232,7 @@ const Market: React.FC<{ getStatusTag?: (status: string) => ReactNode }> = () =>
};
const handleSelectSource = async (sourceId: string) => {
+ if (sourceId === selectedSource) return
setSelectedSource(sourceId);
setSearchKeyword('');
setCurrentPage(1);
@@ -235,21 +245,6 @@ const Market: React.FC<{ getStatusTag?: (status: string) => ReactNode }> = () =>
await fetchMcpList(sourceId, 1);
};
- const handleRefresh = async (sourceId: string) => {
- // 清除缓存,重新从第一页加载
- setMcpCache(prev => {
- const next = { ...prev };
- delete next[sourceId];
- return next;
- });
- setCurrentPage(1);
- await fetchMcpList(sourceId, 1);
- const source = marketSources.find(s => s.id === sourceId);
- if (source) {
- message.success(`${source.name} ${t('tool.marketRefreshSuccess')}`);
- }
- };
-
const handleOpenConfig = async (sourceId: string) => {
const source = marketSources.find(s => s.id === sourceId);
if (!source) return;
@@ -329,13 +324,13 @@ const Market: React.FC<{ getStatusTag?: (status: string) => ReactNode }> = () =>
if (!selectedSource) {
return (
-
+
);
@@ -348,230 +343,170 @@ const Market: React.FC<{ getStatusTag?: (status: string) => ReactNode }> = () =>
return (
<>
-
-
-
+
+
+
{source.logo_url ? (
- {
- e.currentTarget.style.display = 'none';
- const parent = e.currentTarget.parentElement;
- if (parent) {
- parent.innerHTML = '🏪';
- parent.style.fontSize = '48px';
- }
+ e.currentTarget.src = marketIcon
}}
/>
) : (
- 🏪
+
)}
+
+
+
{source.name}
+
{t('tool.availableMcp')} ({mcpTotal})
-
-
{source.name}
- 可用 MCP 服务
({mcpTotal})
- {/*
{source.description}
*/}
-
-
+
-
-
- {source.connected && (
- } onClick={() => handleRefresh(selectedSource)}>
- {t('tool.marketRefresh')}
-
- )}
-
- }
- placeholder={t('tool.marketSearchPlaceholder')}
- value={searchKeyword}
- onChange={(e) => handleSearchChange(e.target.value)}
- allowClear
- style={{ width: 200 }}
-
- />
-
-
-
} onClick={() => handleOpenConfig(selectedSource)}>
+
+ handleSearchChange(value)}
+ allowClear
+ style={{ width: 200 }}
+ />
+ handleOpenConfig(selectedSource)}>
{t('tool.marketConfigBtn')}
- } onClick={() => window.open(source.url, '_blank')}>
+ window.open(source.url, '_blank')}>
{t('tool.marketVisit')}
-
-
+
+
-
>
);
};
return (
-
- {/* 左侧市场源列表 */}
-
-
+
+
+
+ {t('tool.mcpMarket')}
{categories.map(cat => (
-
- {cat.name}
-
- }
- classNames={{
- body: "rb:p-[10px]!",
- header: "rb:bg-[#F6F8FC]!"
- }}
- >
-
- {marketSources
- .filter(s => s.category === cat.id)
- .map(source => (
- handleSelectSource(source.id)}
- >
-
- {source.logo_url ? (
-
{
- e.currentTarget.style.display = 'none';
- const parent = e.currentTarget.parentElement;
- if (parent) {
- parent.innerHTML = '🏪';
- parent.style.fontSize = '16px';
- }
- }}
- />
- ) : (
-
🏪
- )}
-
-
- {source.name}
-
- {/*
- {source.mcp_count}
- */}
- {source.connected && (
-
●
+
+
+ {cat.name}
+
+ {marketSources
+ .filter(s => s.category === cat.id)
+ .map(source => (
+ handleSelectSource(source.id)}
+ >
+
+ {source.logo_url ? (
+
{
+ e.currentTarget.src = marketIcon;
+ }}
+ />
+ ) : (
+
)}
- ))}
-
-
+
+ {source.name}
+
+
+ ))}
+
))}
-
-
-
- {/* 右侧内容区 */}
-
-
- {renderSourceDetail()}
-
-
-
+
+
+
+ {renderSourceDetail()}
+
{/* 配置弹窗 */}
ReactNode }> = () =>
ref={mcpServiceModalRef}
refresh={handleRefreshAfterAdd}
/>
-
+
);
};
diff --git a/web/src/views/ToolManagement/Mcp.tsx b/web/src/views/ToolManagement/Mcp.tsx
index 90883b36..eb9b45ad 100644
--- a/web/src/views/ToolManagement/Mcp.tsx
+++ b/web/src/views/ToolManagement/Mcp.tsx
@@ -1,37 +1,38 @@
-import React, { useState, useRef, useEffect, type ReactNode } from 'react';
+import { useState, useRef, useEffect, forwardRef, useImperativeHandle, type ReactNode } from 'react';
import {
- Button,
- Row,
- Col,
App,
List,
Space,
+ Tooltip,
+ Dropdown,
+ Flex,
} from 'antd';
-import { LinkOutlined } from '@ant-design/icons';
import { useTranslation } from 'react-i18next';
-import type { ToolItem, Query, McpServiceModalRef } from './types';
+import type { ToolItem, McpServiceModalRef, McpRef } from './types';
import McpServiceModal from './components/McpServiceModal';
-import SearchInput from '@/components/SearchInput'
import BodyWrapper from '@/components/Empty/BodyWrapper'
-import RbCard from '@/components/RbCard/Card'
+import RbCard from '@/components/RbCard'
import { getTools, deleteTool, testConnection } from '@/api/tools'
+import { formatDateTime } from '@/utils/format'
-const Mcp: React.FC<{ getStatusTag: (status: string) => ReactNode }> = ({ getStatusTag }) => {
+const Mcp = forwardRef
ReactNode; keyword?: string | undefined }>(({ getStatusTag, keyword }, ref) => {
const { t } = useTranslation();
const { message, modal } = App.useApp()
const [loading, setLoading] = useState(false);
const [data, setData] = useState([]);
- const [query, setQuery] = useState({ name: undefined, tool_type: 'mcp' });
const addServiceModalRef = useRef(null);
useEffect(() => {
getData()
- }, [query.name])
+ }, [keyword])
const getData = () => {
setLoading(true)
- getTools(query)
+ getTools({
+ tool_type: 'mcp',
+ name: keyword
+ })
.then((res) => {
setData(res as ToolItem[])
})
@@ -39,9 +40,8 @@ const Mcp: React.FC<{ getStatusTag: (status: string) => ReactNode }> = ({ getSta
setLoading(false)
})
}
- const handleSearch = (value?: string) => {
- setQuery(prev => ({ ...prev, name: value }))
- }
+
+ useImperativeHandle(ref, () => ({ handleEdit, getData }));
// 打开添加服务弹窗
const handleEdit = (data?: ToolItem) => {
@@ -82,19 +82,7 @@ const Mcp: React.FC<{ getStatusTag: (status: string) => ReactNode }> = ({ getSta
};
return (
-
-
-
-
-
-
- {handleEdit()}}>{t('tool.addService')}
-
-
+ <>
ReactNode }> = ({ getSta
renderItem={(item) => (
- // {item.name[0]}
- //
- // }
- title={item.name}
- extra={getStatusTag(item.status)}
- >
-
- {[
- 'server_url',
- 'last_health_check',
- ].map(key => {
- const value = item.config_data?.[key as keyof typeof item.config_data];
- let displayValue: React.ReactNode;
-
- if (key === 'last_health_check') {
- displayValue = value ? new Date(value as number).toLocaleString() : '-';
- } else if (typeof value === 'string' || typeof value === 'number') {
- displayValue = value;
- } else {
- displayValue = '-';
- }
-
- return (
-
-
{t(`tool.${key}`)}
-
{displayValue}
-
- );
- })}
-
-
- handleEdit(item)}
- >
- } onClick={() => handleTestConnection(item)}>
- handleDeleteService(item)}
- >
+ title={
+
+
+
+ {item.name}
+
+ {getStatusTag(item.status)}
+ ,
+ label: t('common.edit'),
+ onClick: () => handleEdit(item),
+ },
+ {
+ key: 'link',
+ icon:
,
+ label: t('tool.testLink'),
+ onClick: () => handleTestConnection(item),
+ },
+ {
+ key: 'delete',
+ className: 'rb:text-[#FF5D34]!',
+ icon:
,
+ label: t('common.delete'),
+ onClick: () => handleDeleteService(item),
+ },
+ ]
+ }}
+ placement="bottomRight"
+ >
+
+
+
+ }
+ isNeedTooltip={false}
+ >
+
+ {t(`tool.server_url`)}
+
+ {item.config_data?.server_url}
-
+
+
{t('tool.last_health_check')}: {formatDateTime(item.config_data?.last_health_check)}
+
)}
- className="rb:h-[calc(100vh-178px)] rb:overflow-y-auto rb:overflow-x-hidden"
+ className="rb:h-[calc(100vh-124px)] rb:overflow-y-auto rb:overflow-x-hidden"
/>
@@ -162,8 +150,8 @@ const Mcp: React.FC<{ getStatusTag: (status: string) => ReactNode }> = ({ getSta
ref={addServiceModalRef}
refresh={getData}
/>
-
+ >
);
-};
+});
export default Mcp;
\ No newline at end of file
diff --git a/web/src/views/ToolManagement/components/InnerToolModal.tsx b/web/src/views/ToolManagement/components/InnerToolModal.tsx
index d3912ced..9d3b1cb2 100644
--- a/web/src/views/ToolManagement/components/InnerToolModal.tsx
+++ b/web/src/views/ToolManagement/components/InnerToolModal.tsx
@@ -94,7 +94,7 @@ const InnerToolModal = forwardRef(({
confirmLoading={loading}
>
{editVo?.config_data?.tool_class && config && <>
-
+
{t('tool.configDesc')}
{t(`tool.${editVo?.config_data?.tool_class}_config_desc`)}
diff --git a/web/src/views/ToolManagement/index.tsx b/web/src/views/ToolManagement/index.tsx
index e790c7ba..fda22ed9 100644
--- a/web/src/views/ToolManagement/index.tsx
+++ b/web/src/views/ToolManagement/index.tsx
@@ -6,8 +6,8 @@
* @LastEditors: yujiangping
* @LastEditTime: 2026-03-06 15:11:31
*/
-import React, { useState } from 'react';
-import { Tabs } from 'antd';
+import React, { useState, useRef } from 'react';
+import { type SegmentedProps, Flex, Space, Form, Button } from 'antd';
import { useTranslation } from 'react-i18next';
import Mcp from './Mcp';
@@ -15,20 +15,28 @@ import Inner from './Inner';
import Custom from './Custom';
import Market from './Market';
import Tag from '@/components/Tag'
+import PageTabs from '@/components/PageTabs'
+import SearchInput from '@/components/SearchInput'
+import type { McpRef, CustomRef } from './types'
const tabKeys = ['mcp', 'inner', 'custom', 'market'] //
const ToolManagement: React.FC = () => {
const { t } = useTranslation();
- const [activeTab, setActiveTab] = useState('mcp');
+ const [activeTab, setActiveTab] = useState
('mcp');
+ const mcpRef = useRef(null);
+ const customRef = useRef(null);
+ const [form] = Form.useForm();
+ const name = Form.useWatch(['name'], form)
const formatTabItems = () => {
- return tabKeys.map(key => ({
- key,
- label: t(`tool.${key}`),
+ return tabKeys.map(value => ({
+ value,
+ label: t(`tool.${value}`),
}))
}
- const handleChangeTab = (key: string) => {
+ const handleChangeTab = (key: SegmentedProps['value']) => {
setActiveTab(key);
+ form.resetFields()
}
// 获取状态标签
const getStatusTag = (status: string) => {
@@ -45,17 +53,36 @@ const ToolManagement: React.FC = () => {
};
return (
-
-
- {activeTab === 'mcp' &&
}
- {activeTab === 'inner' &&
}
- {activeTab === 'custom' &&
}
+ <>
+
+
+
+ {activeTab !== 'market' && }
+
+ {activeTab === 'mcp' &&
}
+ {activeTab === 'inner' &&
}
+ {activeTab === 'custom' &&
}
{activeTab === 'market' &&
}
-
+ >
);
};
diff --git a/web/src/views/ToolManagement/types.ts b/web/src/views/ToolManagement/types.ts
index 621b52fa..95576a5f 100644
--- a/web/src/views/ToolManagement/types.ts
+++ b/web/src/views/ToolManagement/types.ts
@@ -147,4 +147,11 @@ export interface MarketQuery {
page?: number;
pagesize?: number;
keywords?: string;
+}
+export interface McpRef {
+ handleEdit: (data?: ToolItem) => void;
+ getData: () => void;
+}
+export interface CustomRef {
+ handleEdit: (data?: ToolItem) => void;
}
\ No newline at end of file