diff --git a/src/components/overlay-scroller/index.tsx b/src/components/overlay-scroller/index.tsx index aa98d84b..e4fbcfc1 100644 --- a/src/components/overlay-scroller/index.tsx +++ b/src/components/overlay-scroller/index.tsx @@ -11,7 +11,7 @@ export type { OverlayScrollerOptions }; export const OverlayScroller: React.FC< OverlayScrollerOptions & { - maxHeight?: number; + maxHeight?: number | string; style?: React.CSSProperties; styles?: { wrapper?: React.CSSProperties; diff --git a/src/components/page-tools/filters-button.less b/src/components/page-tools/filters-button.less new file mode 100644 index 00000000..5dcb0fd2 --- /dev/null +++ b/src/components/page-tools/filters-button.less @@ -0,0 +1,47 @@ +.wrapper { + display: flex; + align-items: center; + gap: 8px; + cursor: pointer; + + .count { + display: flex; + width: 16px; + height: 16px; + justify-content: center; + align-items: center; + border-radius: 50%; + font-size: 12px; + font-weight: 500; + background-color: var(--ant-color-bg-text-active); + color: var(--ant-color-text-secondary); + } +} + +.buttonWrapper { + display: flex; + align-items: center; + justify-content: center; + position: relative; + + .close-btn { + border-radius: 50%; + color: var(--ant-color-text-quaternary); + display: none; + font-size: 16px; + + &:hover { + color: var(--ant-color-text-tertiary); + } + } + + &:hover { + .count { + display: none; + } + + .close-btn { + display: block; + } + } +} diff --git a/src/components/page-tools/index.tsx b/src/components/page-tools/index.tsx index 8f08f3dd..eb5a2e4e 100644 --- a/src/components/page-tools/index.tsx +++ b/src/components/page-tools/index.tsx @@ -1,5 +1,6 @@ import DropDownActions from '@/components/drop-down-actions'; import { + CloseCircleFilled, DeleteOutlined, DownOutlined, PlusOutlined, @@ -9,7 +10,9 @@ import { import { useIntl } from '@umijs/max'; import { Button, Input, Space } from 'antd'; import React, { useMemo } from 'react'; +import IconFont from '../icon-font'; import BaseSelect from '../seal-form/base/select'; +import filtersButtonCss from './filters-button.less'; import './index.less'; type PageToolsProps = { @@ -20,6 +23,49 @@ type PageToolsProps = { style?: React.CSSProperties; }; +export const FiltersButton = ({ + onClick, + onClear, + count +}: { + onClick: () => void; + onClear: () => void; + count?: number; +}) => { + const handleClear = (e: React.MouseEvent) => { + e.stopPropagation(); + onClear(); + }; + const intl = useIntl(); + + return ( +
+ +
+ ); +}; + const PageTools: React.FC = (props) => { const { left, @@ -77,6 +123,12 @@ interface FilterBarProps { showDeleteButton?: boolean; right?: React.ReactNode; left?: React.ReactNode; + filtersButtonProps?: { + show: boolean; + count: number; + onClick: () => void; + onClear: () => void; + }; widths?: { input?: number; select?: number; @@ -103,13 +155,21 @@ export const FilterBar: React.FC = (props) => { selectHolder, right, left, - widths + widths, + filtersButtonProps } = props; const intl = useIntl(); const renderLeft = () => { return ( + {filtersButtonProps?.show && ( + + )} ` + width: 100%; --ant-table-cell-padding-inline: ${(props) => props.$token.cellPaddingInline}px; --ant-table-cell-padding-block: ${(props) => props.$token.cellPaddingBlock}px; diff --git a/src/locales/en-US/models.ts b/src/locales/en-US/models.ts index e65499ac..2cffff15 100644 --- a/src/locales/en-US/models.ts +++ b/src/locales/en-US/models.ts @@ -281,5 +281,6 @@ export default { 'Not enabled yet. Will be enabled after deployment. ', 'models.table.instance.benchmark': 'Run Benchmark', 'models.table.modelView': 'Model List', - 'models.table.instanceView': 'Instance List' + 'models.table.instanceView': 'Instance List', + 'models.table.category': 'Category' }; diff --git a/src/locales/ja-JP/models.ts b/src/locales/ja-JP/models.ts index bbc24c0e..d5b58f74 100644 --- a/src/locales/ja-JP/models.ts +++ b/src/locales/ja-JP/models.ts @@ -281,7 +281,8 @@ export default { 'Not enabled yet. Will be enabled after deployment. ', 'models.table.instance.benchmark': 'Run Benchmark', 'models.table.modelView': 'Model List', - 'models.table.instanceView': 'Instance List' + 'models.table.instanceView': 'Instance List', + 'models.table.category': 'Category' }; // ========== To-Do: Translate Keys (Remove After Translation) ========== @@ -383,5 +384,6 @@ export default { // 77. 'models.form.enableModelRoute': 'Enable Model Route', // 78. 'models.form.enableModelRoute.tips': 'Enable Model Route', // 79. 'models.table.modelView': 'Model View', -// 80. 'models.table.instanceView': 'Instance View' +// 80. 'models.table.instanceView': 'Instance View', +// 81. 'models.table.category': 'Category' // ========== End of To-Do List ========== diff --git a/src/locales/ru-RU/models.ts b/src/locales/ru-RU/models.ts index b3d368ce..bef59099 100644 --- a/src/locales/ru-RU/models.ts +++ b/src/locales/ru-RU/models.ts @@ -285,7 +285,8 @@ export default { 'Not enabled yet. Will be enabled after deployment. ', 'models.table.instance.benchmark': 'Run Benchmark', 'models.table.modelView': 'Model List', - 'models.table.instanceView': 'Instance List' + 'models.table.instanceView': 'Instance List', + 'models.table.category': 'Category' }; // ========== To-Do: Translate Keys (Remove After Translation) ========== @@ -297,5 +298,6 @@ export default { // 4. 'models.form.backend.vllm': 'Built-in support for NVIDIA, AMD, Ascend, Hygon, Moore Threads, Iluvatar, MetaX, T-Head PPU devices.', // 5. 'models.form.backend.sglang': 'Built-in support for NVIDIA, AMD, Ascend, Moore Threads, MetaX, T-Head PPU devices.', // 6. 'models.table.modelView': 'Model List', -// 7. 'models.table.instanceView': 'Instance List' +// 7. 'models.table.instanceView': 'Instance List', +// 8. 'models.table.category': 'Category' // ========== End of To-Do List ========== diff --git a/src/locales/tr-TR/models.ts b/src/locales/tr-TR/models.ts index a98946b3..52e16765 100644 --- a/src/locales/tr-TR/models.ts +++ b/src/locales/tr-TR/models.ts @@ -281,10 +281,12 @@ export default { 'Henüz etkinleştirilmedi. Dağıtımdan sonra etkinleştirilecektir. ', 'models.table.instance.benchmark': 'Kıyaslama Çalıştır', 'models.table.modelView': 'Model List', - 'models.table.instanceView': 'Instance List' + 'models.table.instanceView': 'Instance List', + 'models.table.category': 'Category' }; // ========== To-Do: Translate Keys (Remove After Translation) ========== // 1. 'models.table.modelView': 'Model List', // 2. 'models.table.instanceView': 'Instance List', +// 3. 'models.table.category': 'Category' // ========== End of To-Do List ========== diff --git a/src/locales/zh-CN/models.ts b/src/locales/zh-CN/models.ts index 996b2071..9979ee41 100644 --- a/src/locales/zh-CN/models.ts +++ b/src/locales/zh-CN/models.ts @@ -265,5 +265,6 @@ export default { 'models.form.backend.helperText': '该社区后端暂未启用,部署后将自动启用', 'models.table.instance.benchmark': '运行基准测试', 'models.table.modelView': '模型列表', - 'models.table.instanceView': '实例列表' + 'models.table.instanceView': '实例列表', + 'models.table.category': '类别' }; diff --git a/src/pages/_components/filter-form.tsx b/src/pages/_components/filter-form.tsx deleted file mode 100644 index cc7149ca..00000000 --- a/src/pages/_components/filter-form.tsx +++ /dev/null @@ -1,238 +0,0 @@ -import IconFont from '@/components/icon-font'; -import OverlayScroller from '@/components/overlay-scroller'; -import { CloseCircleFilled } from '@ant-design/icons'; -import { useIntl } from '@umijs/max'; -import { Button, Divider, Form, Popover } from 'antd'; -import React, { useState } from 'react'; -import styled from 'styled-components'; - -const Container = styled.div` - padding: 12px 8px; - .title { - font-weight: 500; - margin-bottom: 12px; - } - .btn-wrapper { - display: flex; - justify-content: space-between; - align-items: center; - padding-top: 24px; - padding-right: 8px; - } - .buttons { - display: flex; - gap: 8px; - justify-content: flex-end; - } -`; - -const Filters = styled.span` - display: flex; - align-items: center; - gap: 8px; - font-size: 0; - cursor: pointer; - .count { - display: flex; - width: 16px; - height: 16px; - justify-content: center; - align-items: center; - border-radius: 50%; - font-size: 12px; - font-weight: 500; - background-color: var(--ant-color-bg-text-active); - color: var(--ant-color-text-secondary); - } -`; - -const ButtonWrapper = styled.div` - display: flex; - align-items: center; - justify-content: center; - position: relative; - .close-btn { - border-radius: 50%; - color: var(--ant-color-text-quaternary); - &:hover { - color: var(--ant-color-text-tertiary); - } - } - &:hover { - .close-btn { - display: block; - } - } -`; - -const FilterForm: React.FC< - React.PropsWithChildren & { - width?: number; - contentHeight?: number; - initialValues?: any; - hasFilters?: boolean; - placement?: 'bottomLeft' | 'bottomRight' | 'topLeft' | 'topRight'; - onClose?: () => void; - onValuesChange?: (ChangeValues: any, allValues: any) => void; - } -> = ({ - children, - width = 300, - contentHeight = 400, - initialValues = {}, - placement = 'bottomLeft', - onClose, - onValuesChange -}) => { - const intl = useIntl(); - const [open, setOpen] = useState(false); - const [hasFilters, setHasFilters] = useState(false); - const [form] = Form.useForm(); - - const handleToggle = () => { - setOpen(!open); - }; - - const handleOpenChange = (isOpen: boolean) => { - setOpen(isOpen); - }; - - const handleOnReset = () => { - form.resetFields(Object.keys(initialValues)); - setOpen(false); - setHasFilters(false); - form.resetFields(); - onValuesChange?.({}, form.getFieldsValue()); - }; - - const handleOnClose = () => { - setOpen(false); - onClose?.(); - }; - - const handleOnValuesChange = (changedValues: any, allValues: any) => { - onValuesChange?.(changedValues, allValues); - const hasActiveFilters = Object.values(allValues).some( - (value) => value !== undefined && value !== null && value !== '' - ); - setHasFilters(hasActiveFilters); - }; - - const handleOnClear = () => { - handleOnReset(); - }; - - const filtersCount = Object.values(form.getFieldsValue()).filter( - (value) => value !== undefined && value !== null && value !== '' - ).length; - - const renderFooter = () => { - return ( -
- -
- -
-
- ); - }; - - return ( - - -
- {children} -
-
- - } - onOpenChange={handleOpenChange} - styles={{ - container: { - padding: 8, - border: '1px solid var(--ant-color-split)' - }, - root: { - width: width - } - }} - > - - - - -
- ); -}; - -export default FilterForm; diff --git a/src/pages/_components/filter-form/index.tsx b/src/pages/_components/filter-form/index.tsx new file mode 100644 index 00000000..6e68fb58 --- /dev/null +++ b/src/pages/_components/filter-form/index.tsx @@ -0,0 +1,155 @@ +import OverlayScroller from '@/components/overlay-scroller'; +import { CloseOutlined } from '@ant-design/icons'; +import { useIntl } from '@umijs/max'; +import { Button, Form } from 'antd'; +import classNames from 'classnames'; +import React, { forwardRef, useImperativeHandle } from 'react'; +import filterFormCss from '../styles/filter-form.less'; + +const FilterForm: React.FC< + React.PropsWithChildren & { + ref?: any; + width?: number; + contentHeight?: number | string; + initialValues?: any; + hasFilters?: boolean; + open?: boolean; + onClear?: () => void; + onClose?: () => void; + onValuesChange?: (ChangeValues: any, allValues: any) => void; + styles?: { + container?: React.CSSProperties; + wrapper?: React.CSSProperties; + }; + } +> = forwardRef( + ( + { + children, + open, + width = 300, + contentHeight = 400, + initialValues = {}, + styles, + onClose, + onClear, + onValuesChange + }, + ref + ) => { + const intl = useIntl(); + const [form] = Form.useForm(); + + const handleOnReset = () => { + form.resetFields(Object.keys(initialValues)); + form.resetFields(); + onValuesChange?.({}, form.getFieldsValue()); + }; + + const handleOnClose = () => { + onClose?.(); + }; + + const handleOnValuesChange = (changedValues: any, allValues: any) => { + onValuesChange?.(changedValues, allValues); + }; + + const handleOnClear = () => { + handleOnReset(); + }; + + const filtersCount = Object.values(form.getFieldsValue()).filter( + (value) => value !== undefined && value !== null && value !== '' + ).length; + + const renderFooter = () => { + return ( +
+ +
+ +
+
+ ); + }; + + useImperativeHandle(ref, () => ({ + form, + reset: handleOnReset, + getValues: () => form.getFieldsValue(), + setValues: (values: any) => form.setFieldsValue(values) + })); + + return ( +
+
+
+ + {intl.formatMessage({ id: 'common.filter.label' })} + + +
+ +
+ {children} +
+
+
+
+ ); + } +); + +export default FilterForm; diff --git a/src/pages/_components/page-box.tsx b/src/pages/_components/page-box.tsx index 8daa456b..e6ba8368 100644 --- a/src/pages/_components/page-box.tsx +++ b/src/pages/_components/page-box.tsx @@ -74,8 +74,11 @@ export const PageContainerInner: React.FC< ); }; -const PageBox: React.FC<{ children: React.ReactNode }> = ({ children }) => { - return
{children}
; +const PageBox: React.FC<{ + children: React.ReactNode; + style?: React.CSSProperties; +}> = ({ children, style }) => { + return
{children}
; }; export default PageBox; diff --git a/src/pages/_components/styles/filter-form.less b/src/pages/_components/styles/filter-form.less new file mode 100644 index 00000000..9bbdb3e6 --- /dev/null +++ b/src/pages/_components/styles/filter-form.less @@ -0,0 +1,41 @@ +.wrapper { + flex-shrink: 0; + overflow: hidden; + width: 0; + transition: all var(--ant-motion-duration-slow) var(--ant-motion-ease-in-out); + border-color: var(--ant-color-split); + + &.show { + width: 232px; + border-right: 1px solid var(--ant-color-split); + } +} + +.container { + padding: 0 16px; + + .title { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 16px; + margin-top: 8px; + padding-inline: 8px; + font-weight: 500; + color: var(--ant-color-text-tertiary); + } + + .btn-wrapper { + display: flex; + justify-content: space-between; + align-items: center; + padding-top: 24px; + padding-right: 8px; + } + + .buttons { + display: flex; + gap: 8px; + justify-content: flex-end; + } +} diff --git a/src/pages/backends/index.tsx b/src/pages/backends/index.tsx index 9428c8db..91f28a3b 100644 --- a/src/pages/backends/index.tsx +++ b/src/pages/backends/index.tsx @@ -233,7 +233,7 @@ const BackendList = () => { marginBottom={22} marginTop={30} widths={{ - input: 300 + input: 230 }} actionItems={addActions} actionType="dropdown" diff --git a/src/pages/benchmark/components/left-actions.tsx b/src/pages/benchmark/components/left-actions.tsx index c8bc39d2..2bff3045 100644 --- a/src/pages/benchmark/components/left-actions.tsx +++ b/src/pages/benchmark/components/left-actions.tsx @@ -1,11 +1,10 @@ -import BaseSelect from '@/components/seal-form/base/select'; +import { FiltersButton } from '@/components/page-tools'; import { modelCategoriesMap } from '@/pages/llmodels/config'; import { SearchOutlined, SyncOutlined } from '@ant-design/icons'; import { useIntl } from '@umijs/max'; import { Button, Input, Space } from 'antd'; import _ from 'lodash'; import React from 'react'; -import { profileOptions } from '../config'; export interface RightActionsProps { handleInputChange: (e: React.ChangeEvent) => void; @@ -13,12 +12,18 @@ export interface RightActionsProps { handleQueryChange: (value: any, option?: any) => void; modelList?: Global.BaseOption[]; datasetList?: Global.BaseOption[]; + toggleFilters: () => void; + count?: number; + onClear: () => void; } const RightActions: React.FC = ({ handleInputChange, handleSearch, handleQueryChange, + toggleFilters, + count, + onClear, modelList, datasetList }) => { @@ -44,6 +49,11 @@ const RightActions: React.FC = ({ return ( + = ({ placeholder={intl.formatMessage({ id: 'common.filter.name' })} - style={{ width: 200 }} + style={{ width: 300 }} allowClear onChange={handleInputChange} > - = ({ page: 1 }) } - > + > */}