diff --git a/web/src/assets/images/common/arrow_right_dark.svg b/web/src/assets/images/common/arrow_right_dark.svg new file mode 100644 index 00000000..b20a440c --- /dev/null +++ b/web/src/assets/images/common/arrow_right_dark.svg @@ -0,0 +1,18 @@ + + + 编组 5 + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/web/src/assets/images/common/delete_red.svg b/web/src/assets/images/common/delete_red.svg new file mode 100644 index 00000000..58ad4d41 --- /dev/null +++ b/web/src/assets/images/common/delete_red.svg @@ -0,0 +1,30 @@ + + + 编组 33 + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/web/src/assets/images/common/global_outline.svg b/web/src/assets/images/common/global_outline.svg new file mode 100644 index 00000000..86301a0e --- /dev/null +++ b/web/src/assets/images/common/global_outline.svg @@ -0,0 +1,20 @@ + + + 互联网 + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/web/src/assets/images/common/plus_grey.svg b/web/src/assets/images/common/plus_grey.svg new file mode 100644 index 00000000..05fb64e3 --- /dev/null +++ b/web/src/assets/images/common/plus_grey.svg @@ -0,0 +1,13 @@ + + + 形状结合@2x + + + + + + + + + + \ No newline at end of file diff --git a/web/src/assets/images/index/apps.svg b/web/src/assets/images/index/apps.svg index 58907fd6..b49bda51 100644 --- a/web/src/assets/images/index/apps.svg +++ b/web/src/assets/images/index/apps.svg @@ -1,14 +1,14 @@ 编组 34 - - - - + + + + - + diff --git a/web/src/assets/images/index/arrow_down.svg b/web/src/assets/images/index/arrow_down.svg index b77a3f8a..366e5848 100644 --- a/web/src/assets/images/index/arrow_down.svg +++ b/web/src/assets/images/index/arrow_down.svg @@ -1,10 +1,10 @@ 箭头_向上 - - - - + + + + diff --git a/web/src/assets/images/index/arrow_down_d.svg b/web/src/assets/images/index/arrow_down_d.svg index 40e5d94b..7393ca80 100644 --- a/web/src/assets/images/index/arrow_down_d.svg +++ b/web/src/assets/images/index/arrow_down_d.svg @@ -1,10 +1,10 @@ 编组 30 - - - - + + + + diff --git a/web/src/assets/images/index/arrow_up.svg b/web/src/assets/images/index/arrow_up.svg index 62aeee96..8a8bae53 100644 --- a/web/src/assets/images/index/arrow_up.svg +++ b/web/src/assets/images/index/arrow_up.svg @@ -1,10 +1,10 @@ 箭头_向上 - - - - + + + + diff --git a/web/src/assets/images/index/arrow_up_d.svg b/web/src/assets/images/index/arrow_up_d.svg index 3c19fef3..3529a291 100644 --- a/web/src/assets/images/index/arrow_up_d.svg +++ b/web/src/assets/images/index/arrow_up_d.svg @@ -1,10 +1,10 @@ 编组 30 - - - - + + + + diff --git a/web/src/assets/images/index/guide_bg@2x.png b/web/src/assets/images/index/guide_bg@2x.png index 3b7490fb..fbf452e6 100644 Binary files a/web/src/assets/images/index/guide_bg@2x.png and b/web/src/assets/images/index/guide_bg@2x.png differ diff --git a/web/src/assets/images/index/help_center.svg b/web/src/assets/images/index/help_center.svg index 6d272121..28595b0a 100644 --- a/web/src/assets/images/index/help_center.svg +++ b/web/src/assets/images/index/help_center.svg @@ -1,13 +1,13 @@ 编组 17 - - - - - - - + + + + + + + diff --git a/web/src/assets/images/index/index_bg@2x.png b/web/src/assets/images/index/index_bg@2x.png index d20ee4d3..fbf30083 100644 Binary files a/web/src/assets/images/index/index_bg@2x.png and b/web/src/assets/images/index/index_bg@2x.png differ diff --git a/web/src/assets/images/index/model_mgt.svg b/web/src/assets/images/index/model_mgt.svg index 89e13ec3..536f8877 100644 --- a/web/src/assets/images/index/model_mgt.svg +++ b/web/src/assets/images/index/model_mgt.svg @@ -1,26 +1,26 @@ 编组 25 - - - - - - + + + + + + - - - - - - - - - - - - - + + + + + + + + + + + + + diff --git a/web/src/assets/images/index/models.svg b/web/src/assets/images/index/models.svg index 890f240a..60863681 100644 --- a/web/src/assets/images/index/models.svg +++ b/web/src/assets/images/index/models.svg @@ -1,9 +1,9 @@ 编组 14 - - - + + + diff --git a/web/src/assets/images/index/space_mgt.svg b/web/src/assets/images/index/space_mgt.svg index af1db66c..a71f7431 100644 --- a/web/src/assets/images/index/space_mgt.svg +++ b/web/src/assets/images/index/space_mgt.svg @@ -1,13 +1,13 @@ 编组 26 - - - - - - - + + + + + + + diff --git a/web/src/assets/images/index/spaces.svg b/web/src/assets/images/index/spaces.svg index 1c61bc6b..e79eb113 100644 --- a/web/src/assets/images/index/spaces.svg +++ b/web/src/assets/images/index/spaces.svg @@ -7,10 +7,10 @@ - - - - + + + + diff --git a/web/src/assets/images/index/user_mgt.svg b/web/src/assets/images/index/user_mgt.svg index d53a97b9..4ec237aa 100644 --- a/web/src/assets/images/index/user_mgt.svg +++ b/web/src/assets/images/index/user_mgt.svg @@ -1,13 +1,13 @@ 编组 24 - - - - - - - + + + + + + + diff --git a/web/src/assets/images/index/users.svg b/web/src/assets/images/index/users.svg index 545d9636..bfb37872 100644 --- a/web/src/assets/images/index/users.svg +++ b/web/src/assets/images/index/users.svg @@ -1,10 +1,10 @@ 编组 33 - - - - + + + + diff --git a/web/src/assets/images/tool/market.png b/web/src/assets/images/tool/market.png new file mode 100644 index 00000000..9639e253 Binary files /dev/null and b/web/src/assets/images/tool/market.png differ diff --git a/web/src/components/Chat/ChatInput.tsx b/web/src/components/Chat/ChatInput.tsx index 4221c426..aab75426 100644 --- a/web/src/components/Chat/ChatInput.tsx +++ b/web/src/components/Chat/ChatInput.tsx @@ -127,23 +127,23 @@ const ChatInput: FC = ({ className={clsx( "rb:size-5 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/conversation/pdf_disabled.svg')]", file.type.includes('pdf') - ? "rb:bg-[url('src/assets/images/file/pdf.svg')]" + ? "rb:bg-[url('@/assets/images/file/pdf.svg')]" : (file.type.includes('excel') || file.type.includes('spreadsheetml.sheet')) - ? "rb:bg-[url('src/assets/images/file/excel.svg')]" + ? "rb:bg-[url('@/assets/images/file/excel.svg')]" : file.type.includes('csv') - ? "rb:bg-[url('src/assets/images/file/csv.svg')]" + ? "rb:bg-[url('@/assets/images/file/csv.svg')]" : file.type.includes('html') - ? "rb:bg-[url('src/assets/images/file/html.svg')]" + ? "rb:bg-[url('@/assets/images/file/html.svg')]" : file.type.includes('json') - ? "rb:bg-[url('src/assets/images/file/json.svg')]" + ? "rb:bg-[url('@/assets/images/file/json.svg')]" : file.type.includes('ppt') - ? "rb:bg-[url('src/assets/images/file/ppt.svg')]" + ? "rb:bg-[url('@/assets/images/file/ppt.svg')]" : file.type.includes('text') - ? "rb:bg-[url('src/assets/images/file/txt.svg')]" + ? "rb:bg-[url('@/assets/images/file/txt.svg')]" : file.type.includes('markdown') - ? "rb:bg-[url('src/assets/images/file/md.svg')]" + ? "rb:bg-[url('@/assets/images/file/md.svg')]" : (file.type.includes('doc') || file.type.includes('docx') || file.type.includes('word') || file.type.includes('wordprocessingml.document')) - ? "rb:bg-[url('src/assets/images/file/word.svg')]" + ? "rb:bg-[url('@/assets/images/file/word.svg')]" : null )} > diff --git a/web/src/components/RbCard/index.tsx b/web/src/components/RbCard/index.tsx index 7b1f0d63..ec2b7301 100644 --- a/web/src/components/RbCard/index.tsx +++ b/web/src/components/RbCard/index.tsx @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-02-02 15:21:14 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-02-24 14:59:53 + * @Last Modified time: 2026-03-20 20:24:43 */ /** * RbCard Component @@ -23,6 +23,7 @@ import clsx from 'clsx'; /** Props interface for RbCard component */ interface RbCardProps extends CardProps { + isNeedTooltip?: boolean; children?: ReactNode; /** Custom avatar component */ avatarText?: string; @@ -32,16 +33,22 @@ interface RbCardProps extends CardProps { /** Click handler */ onClick?: () => void; footer?: ReactNode; + headerClassName?: string; + titleClassName?: string; } /** Custom card component with flexible styling and header options */ const RbCard: FC = ({ + isNeedTooltip = true, title, children, avatarText, avatarClassName, avatarUrl, footer, + headerClassName, + titleClassName, + className, ...props }) => { return ( @@ -54,17 +61,23 @@ const RbCard: FC = ({ : avatarText ? {avatarText} : null } - -
+ {isNeedTooltip + ? +
+ {title} +
+
+ :
{title}
- + } + } classNames={{ - header: 'rb:text-[16px] rb:p-[16px_16px_8px_16px]! rb:border-0!', - body: 'rb:p-4! rb:bg-white!', + header: `rb:text-[16px] rb:p-[16px_16px_8px_16px]! rb:border-0! ${headerClassName}`, + body: 'rb:p-4! rb:pt-2! rb:bg-white!', }} - className="rb:hover:shadow-[0px_2px_8px_0px_rgba(23,23,25,0.16)]! rb:group" + className={`rb:hover:shadow-[0px_2px_8px_0px_rgba(23,23,25,0.16)]! rb:group ${className}`} > {children} diff --git a/web/src/components/SearchInput/index.tsx b/web/src/components/SearchInput/index.tsx index c421b518..baecdc5b 100644 --- a/web/src/components/SearchInput/index.tsx +++ b/web/src/components/SearchInput/index.tsx @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-02-02 15:24:23 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-02-10 14:59:39 + * @Last Modified time: 2026-03-23 11:35:33 */ /** * SearchInput Component @@ -22,7 +22,7 @@ import { Input, type InputProps } from 'antd'; import { useTranslation } from 'react-i18next'; /** Props interface for SearchInput component */ -interface SearchInputProps { +interface SearchInputProps extends InputProps { /** Placeholder text */ placeholder?: string; /** Callback fired when search value changes */ diff --git a/web/src/i18n/en.ts b/web/src/i18n/en.ts index e740aa6b..4d6859c3 100644 --- a/web/src/i18n/en.ts +++ b/web/src/i18n/en.ts @@ -467,6 +467,7 @@ export const en = { notAllSpaces: 'Cannot be all spaces', download: 'Download', view: 'View', + updated_at: 'Updated At', }, model: { searchPlaceholder: 'search model…', @@ -1918,6 +1919,9 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re refreshFailed: 'Refresh Failed', // Market related + mcpMarket: 'MCP Market', + availableMcp: 'Available MCP Services', + descEmpty: 'There is currently no introduction available …', marketSelectTitle: 'Select an MCP Market', marketSelectDesc: 'Choose a market source from the left, configure the connection to browse MCP services', marketRefreshSuccess: 'List refreshed', @@ -1966,6 +1970,8 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re timeout: 'Timeout (seconds)', sseReadTimeout: 'SSE Read Timeout (seconds)', saveAndTest: 'Save and Test', + testLink: 'Connection Test', + noTags: 'There are no tags here…', timeFormat: 'Time Formatting', timeZoneConversion: 'Time Zone Conversion', diff --git a/web/src/i18n/zh.ts b/web/src/i18n/zh.ts index cb81eb03..fce622bf 100644 --- a/web/src/i18n/zh.ts +++ b/web/src/i18n/zh.ts @@ -1104,6 +1104,7 @@ export const zh = { notAllSpaces: '不能是纯空格', download: '下载', view: '查看', + updated_at: '更新时间', }, model: { searchPlaceholder: '搜索模型…', @@ -1914,6 +1915,9 @@ export const zh = { refreshFailed: '刷新失败', // Market 相关 + mcpMarket: 'MCP Market', + availableMcp: '可用的 MCP 服务', + descEmpty: '暂无介绍…', marketSelectTitle: '选择一个 MCP 市场', marketSelectDesc: '从左侧选择一个市场源,配置连接后即可浏览该市场的 MCP 服务', marketRefreshSuccess: '列表已刷新', @@ -1962,6 +1966,8 @@ export const zh = { timeout: '超时时间(秒)', sseReadTimeout: 'SSE 读取超时时间(秒)', saveAndTest: '保存并测试', + testLink: '连接测试', + noTags: '暂无标签…', timeFormat: '时间格式化', timeZoneConversion: '时区转换', diff --git a/web/src/views/Conversation/index.tsx b/web/src/views/Conversation/index.tsx index 70dc5a2b..55d17073 100644 --- a/web/src/views/Conversation/index.tsx +++ b/web/src/views/Conversation/index.tsx @@ -299,7 +299,7 @@ const Conversation: FC = () => {
-
+
{t('memoryConversation.chatTitle')}
diff --git a/web/src/views/Index/components/GuideCard.tsx b/web/src/views/Index/components/GuideCard.tsx index a8560136..f1cbc06c 100644 --- a/web/src/views/Index/components/GuideCard.tsx +++ b/web/src/views/Index/components/GuideCard.tsx @@ -9,10 +9,8 @@ import React, { useState, useRef } from 'react'; import { useTranslation } from 'react-i18next'; import { useNavigate } from 'react-router-dom'; -import guideBgImg from '@/assets/images/index/guide_bg@2x.png' import { Button, Tour } from 'antd'; import type { TourProps } from 'antd'; -import arrowRight from '@/assets/images/index/arrow_right_blue.svg' const GuideCard: React.FC = () => { const { t } = useTranslation(); @@ -67,22 +65,18 @@ const GuideCard: React.FC = () => { return ( <> -
-
- { t('index.getStarted')} +
+
+ { t('index.getStarted')}
-
+
{ t('index.startedDesc')}
-
- - {/* */} +
+
diff --git a/web/src/views/Index/components/QuickActions.tsx b/web/src/views/Index/components/QuickActions.tsx index 063014df..e9f1f4f8 100644 --- a/web/src/views/Index/components/QuickActions.tsx +++ b/web/src/views/Index/components/QuickActions.tsx @@ -1,5 +1,6 @@ import { type FC } from 'react'; import { useTranslation } from 'react-i18next'; +import { Flex } from 'antd' import modelIcon from '@/assets/images/index/model_mgt.svg' import spaceIcon from '@/assets/images/index/space_mgt.svg' @@ -89,25 +90,30 @@ const QuickActions: FC = ({ onNavigate }) => { ]; return ( -
-
- { t('quickActions.title') } -
-
- - {quickActions.map((action) => ( -
- -
- {action.title} -
-
- ))} -
-
); +
+
+ { t('quickActions.title') } +
+
+ {quickActions.map((action) => ( + + +
+ {action.title} +
+
+ ))} +
+
+ ); }; export default QuickActions; diff --git a/web/src/views/Index/components/TopCardList/index.tsx b/web/src/views/Index/components/TopCardList/index.tsx index 97ced240..82cfe21b 100644 --- a/web/src/views/Index/components/TopCardList/index.tsx +++ b/web/src/views/Index/components/TopCardList/index.tsx @@ -1,20 +1,15 @@ import { type FC } from 'react' import { useTranslation } from 'react-i18next' -import totalModels from '@/assets/images/index/models.svg'; -import totalSpaces from '@/assets/images/index/spaces.svg'; -import totalUsers from '@/assets/images/index/users.svg'; -import totalApps from '@/assets/images/index/apps.svg'; -import arrowUpDb from '@/assets/images/index/arrow_up_d.svg' -import arrowDownDb from '@/assets/images/index/arrow_down_d.svg' -import arrowUp from '@/assets/images/index/arrow_up.svg' -import arrowDown from '@/assets/images/index/arrow_down.svg' -import styles from './index.module.css' +import { Flex } from 'antd'; +import clsx from 'clsx'; + import { type DataResponse } from '@/api/common' +import Tag from '@/components/Tag' const list = [ { key: 'models', - icon: totalModels, + icon: 'rb:bg-[url("@/assets/images/index/models.svg")]', value: '24', // trendValue: '12.5%', trend: 'up', @@ -25,7 +20,7 @@ const list = [ }, { key: 'spaces', - icon: totalSpaces, + icon: 'rb:bg-[url("@/assets/images/index/spaces.svg")]', value: '156', trendValue: '+8', trend: 'down', @@ -36,7 +31,7 @@ const list = [ }, { key: 'users', - icon: totalUsers, + icon: 'rb:bg-[url("@/assets/images/index/users.svg")]', value: '1,248', trendValue: '+42', trend: 'up', @@ -47,7 +42,7 @@ const list = [ }, { key: 'running_apps', - icon: totalApps, + icon: 'rb:bg-[url("@/assets/images/index/apps.svg")]', value: '12.8k', trendValue: '98.7%', trend: 'up', @@ -60,70 +55,106 @@ const list = [ const TopCardList: FC<{data?: DataResponse}> = ({ data }) => { const { t } = useTranslation() return ( -
+
{list.map((item) => { return ( -
-
-
{t(`dashboard.${'total_' + item.key}`)}
-
-
+
+ +
{t(`dashboard.${'total_' + item.key}`)}
+
+
-
+
{item.key === 'spaces' && String(data?.active_workspaces)} {item.key === 'running_apps' && String(data?.[`${item.key}` as keyof DataResponse] || item.value || 0)} {item.key !== 'spaces' && item.key !== 'running_apps' && String(data?.[`total_${item.key}` as keyof DataResponse] || item.value || 0)}
-
- {item.key === 'models' ? ( -
- {t(`dashboard.${'desc_' + item.key}`, { account: data?.total_llm, nums: data?.total_embedding })} +
+ {item.key === 'models' + ? ( +
+ {t(`dashboard.${'desc_' + item.key}`, { account: data?.total_llm, nums: data?.total_embedding })}
- ) : (<> + ) + : (<>
{item.key === 'spaces' && (<> - = 0 ? arrowUpDb : arrowDownDb} className='rb:size-3'/> - = 0 ? 'rb:text-[#369F21]' : 'rb:text-[#FF5D34]'}>{Number(data?.new_workspaces_this_week || 0) >= 0 ? '+' : '-'}{Math.abs(Number(data?.new_workspaces_this_week || 0))} +
= 0, + "rb:bg-[url('@/assets/images/index/arrow_down_d.svg')]": Number(data?.new_workspaces_this_week || 0) < 0, + })}>
+ = 0, + "rb:text-[#FF5D34]": Number(data?.new_workspaces_this_week || 0) < 0, + })}>{Number(data?.new_workspaces_this_week || 0) >= 0 ? '+' : '-'}{Math.abs(Number(data?.new_workspaces_this_week || 0))} )} {item.key === 'users' && (<> - = 0 ? arrowUpDb : arrowDownDb} className='rb:size-3'/> - = 0 ? 'rb:text-[#369F21]' : 'rb:text-[#FF5D34]'}>{Number(data?.new_users_this_week || 0) >= 0 ? '+' : '-'}{Math.abs(Number(data?.new_users_this_week || 0))} +
= 0, + "rb:bg-[url('@/assets/images/index/arrow_down_d.svg')]": Number(data?.new_users_this_week || 0) < 0, + })}>
+ = 0, + "rb:text-[#FF5D34]": Number(data?.new_users_this_week || 0) < 0, + })}>{Number(data?.new_users_this_week || 0) >= 0 ? '+' : '-'}{Math.abs(Number(data?.new_users_this_week || 0))} )} {item.key === 'running_apps' && (<> - = 0 ? arrowUpDb : arrowDownDb} className='rb:size-3'/> - = 0 ? 'rb:text-[#369F21]' : 'rb:text-[#FF5D34]'}>{Number(data?.new_apps_this_week || 0) >= 0 ? '+' : '-'}{Math.abs(Number(data?.new_apps_this_week || 0))} +
= 0, + "rb:bg-[url('@/assets/images/index/arrow_down_d.svg')]": Number(data?.new_apps_this_week || 0) < 0, + })}>
+ = 0, + "rb:text-[#FF5D34]": Number(data?.new_apps_this_week || 0) < 0, + })}>{Number(data?.new_apps_this_week || 0) >= 0 ? '+' : '-'}{Math.abs(Number(data?.new_apps_this_week || 0))} )} -
-
+
{t(`dashboard.${'desc_' + item.key}`)}
- )} + ) + }
- {item.key === 'models' && (
= 0 ? 'rb:text-[#369F21] rb:border-[#369F21] rb:bg-[rgba(54, 159, 33, 0.25)]' : 'rb:text-[#FF5D34] rb:border-[#FF5D34] rb:bg-[rgba(255, 93, 52, 0.25)]'}`}> - = 0 ? arrowUp : arrowDown} className='rb:size-3'/> - {Math.abs(Number(data?.model_week_growth_rate || 0))}% {t('dashboard.thisWeek')} -
)} - {item.key === 'spaces' && (
= 0 ? 'rb:text-[#369F21] rb:border-[#369F21] rb:bg-[rgba(54, 159, 33, 0.25)]' : 'rb:text-[#FF5D34] rb:border-[#FF5D34] rb:bg-[rgba(255, 93, 52, 0.25)]'}`}> - = 0 ? arrowUp : arrowDown} className='rb:size-3'/> - {Math.abs(Number(data?.workspace_week_growth_rate || 0))}% {t('dashboard.thisWeek')} -
)} - {item.key === 'users' && (
= 0 ? 'rb:text-[#369F21] rb:border-[#369F21] rb:bg-[rgba(54, 159, 33, 0.25)]' : 'rb:text-[#FF5D34] rb:border-[#FF5D34] rb:bg-[rgba(255, 93, 52, 0.25)]'}`}> - = 0 ? arrowUp : arrowDown} className='rb:size-3'/> - {Math.abs(Number(data?.user_week_growth_rate || 0))}% {t('dashboard.thisWeek')} -
)} - {item.key === 'running_apps' && (
= 0 ? 'rb:text-[#369F21] rb:border-[#369F21] rb:bg-[rgba(54, 159, 33, 0.25)]' : 'rb:text-[#FF5D34] rb:border-[#FF5D34] rb:bg-[rgba(255, 93, 52, 0.25)]'}`}> - = 0 ? arrowUp : arrowDown} className='rb:size-3'/> - {Math.abs(Number(data?.app_week_growth_rate || 0))}% {t('dashboard.thisWeek')} -
)} - + {item.key === 'models' && (= 0 ? "success" : "warning"} className="rb:mt-2"> + +
= 0, + "rb:bg-[url('@/assets/images/index/arrow_down.svg')]": Number(data?.model_week_growth_rate || 0) < 0, + })}>
+ {Math.abs(Number(data?.model_week_growth_rate || 0))}% {t('dashboard.thisWeek')} +
+
)} + {item.key === 'spaces' && (= 0 ? "success" : "warning"} className="rb:mt-2"> + +
= 0, + "rb:bg-[url('@/assets/images/index/arrow_down.svg')]": Number(data?.workspace_week_growth_rate || 0) < 0, + })}>
+ {Math.abs(Number(data?.workspace_week_growth_rate || 0))}% {t('dashboard.thisWeek')} +
+
)} + {item.key === 'users' && (= 0 ? "success" : "warning"} className="rb:mt-2"> + +
= 0, + "rb:bg-[url('@/assets/images/index/arrow_down.svg')]": Number(data?.user_week_growth_rate || 0) < 0, + })}>
+ {Math.abs(Number(data?.user_week_growth_rate || 0))}% {t('dashboard.thisWeek')} +
+
)} + {item.key === 'running_apps' && (= 0 ? "success" : "warning"} className="rb:mt-2"> + +
= 0, + "rb:bg-[url('@/assets/images/index/arrow_down.svg')]": Number(data?.app_week_growth_rate || 0) < 0, + })}>
+ {Math.abs(Number(data?.app_week_growth_rate || 0))}% {t('dashboard.thisWeek')} +
+
)}
) })} diff --git a/web/src/views/Index/components/VersionCard.tsx b/web/src/views/Index/components/VersionCard.tsx index 702b6520..c0382c7e 100644 --- a/web/src/views/Index/components/VersionCard.tsx +++ b/web/src/views/Index/components/VersionCard.tsx @@ -8,11 +8,11 @@ */ import React, { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { Divider } from 'antd'; -// import arrowRight from '@/assets/images/index/arrow_right.svg' +import { Flex } from 'antd'; + import { getVersion, type versionResponse } from '@/api/common' -const GuideCard: React.FC = () => { +const VersionCard: React.FC = () => { const { t, i18n } = useTranslation(); const [versionInfo, setVersionInfo] = useState(null); @@ -23,13 +23,6 @@ const GuideCard: React.FC = () => { return currentLang === 'zh' ? versionInfo.introduction : (versionInfo.introduction_en || versionInfo.introduction); }; - // 解析换行符和HTML的方法 - const parseContent = (text: string) => { - if (!text) return ''; - // 将 \n 转换为
标签 - return text.replace(/\\n/g, '
'); - }; - useEffect(() => { const fetchVersion = async () => { try { @@ -44,58 +37,36 @@ const GuideCard: React.FC = () => { }, []); return ( -
-
- { 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.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 ? ( - icon + icon ) : ( -
+
{record.name?.charAt(0)?.toUpperCase() || '?'}
) @@ -84,7 +83,7 @@ const Index = () => { width: 100, render: (_, record) => ( - + ), }, @@ -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={} > - {formatData(item)?.map((description: DescriptionItem) => ( -
- {(description.label as string)} - {(description.children as string)} -
- ))} - + + {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={ +
+ + + + + + } > - {item.tags.map(tag => {t(`modelNew.${tag}`)})} -
- -
- - - - - - - ))} 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}`)}
- -
- -
- {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 - ? - : - } - + : <> + + {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`)}} + +
+ -
- - ))} -
-
- )) - } + } + 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 - ? - : - } - - -
-
- ))} -
- } -
-
- ); -}); - -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!" - /> - - - - - - - - - + + 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 : ( + + + )} + navigate(-1)}> +
+ {t('common.return')} +
} - subTitle={
{data.scene_description}
} - extra={data.is_system_default ? undefined : ( - - - )} /> -
- +
+
{ 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')}
- - + } + >
)} 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 ( -
- -
- - - - - - + <> ( - // {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 ? ( - {source.name} { - 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 && ( - - )} - - } - placeholder={t('tool.marketSearchPlaceholder')} - value={searchKeyword} - onChange={(e) => handleSearchChange(e.target.value)} - allowClear - style={{ width: 200 }} - - /> - -
- - -
-
+ + -
-
- {!loading && mcpList.length === 0 ? ( - - ) : ( - -
- {mcpList.map(mcp => ( -
+
+ {!loading && mcpList.length === 0 ? ( + + ) : ( + + + {mcpList.map(mcp => ( +
-
-
- {mcp.logo_url ? ( - {getLocaleField(mcp, { - e.currentTarget.style.display = 'none'; - const parent = e.currentTarget.parentElement; - if (parent) { - parent.innerHTML = '🔧'; - parent.style.fontSize = '24px'; - } - }} - /> - ) : ( - 🔧 - )} -
- {mcp.categories?.[0] && ( - - {mcp.categories[0]} - - )} -
-

{getLocaleField(mcp, 'name')}

- {mcp.publisher && ( -
- {mcp.publisher.startsWith('@') ? mcp.publisher : `@${mcp.publisher}`} -
- )} -

{getLocaleField(mcp, 'description')}

-
- {mcp.view_count != null && ( - - {mcp.view_count.toLocaleString()} - - )} -
-
-
- {mcp.activated && {t('tool.marketActivated')}} - {mcp.inDatabase && {t('tool.marketInDatabase')}} -
- -
- + + + +
{getLocaleField(mcp, 'name')}
+
+ + {mcp.categories?.[0] && ( + {mcp.categories[0]} + )} + {mcp.activated && {t('tool.marketActivated')}} + {mcp.inDatabase && {t('tool.marketInDatabase')}} + +
+ + + } + isNeedTooltip={false} + footer={ + {mcp.publisher && {mcp.publisher.startsWith('@') ? mcp.publisher : `@${mcp.publisher}`}} + {mcp.view_count && +
+ {mcp.view_count.toLocaleString()} +
} +
} + > + {getLocaleField(mcp, 'description') ? + +
{getLocaleField(mcp, 'description')}
+
+ :
{t('tool.descEmpty')}
+ } +
+ ))} - - - )} - + + + )} + ); }; 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 ? ( - {source.name} { - 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 ? ( + {source.name} { + 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 ( -
- -
- - - - - - + <> 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)} - >
- -
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 === 'custom' && } + + } +
+ {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