refactor: table filters ux

This commit is contained in:
jialin
2026-04-08 18:54:06 +08:00
committed by jialin
parent 144914110a
commit a2b0236ea3
23 changed files with 700 additions and 396 deletions
+1 -1
View File
@@ -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;
@@ -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;
}
}
}
+61 -1
View File
@@ -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 (
<div className={filtersButtonCss.buttonWrapper}>
<Button
onClick={onClick}
style={{
color: 'var(--ant-color-text-tertiary)'
}}
icon={
<IconFont type="icon-filter-list" style={{ fontSize: 14 }}></IconFont>
}
>
<span className={filtersButtonCss.wrapper}>
<span>{intl.formatMessage({ id: 'common.filter.label' })}</span>
{!!count && <span className={filtersButtonCss.count}>{count}</span>}
{!!count && (
<span
className={filtersButtonCss['close-btn']}
onClick={handleClear}
>
<CloseCircleFilled />
</span>
)}
</span>
</Button>
</div>
);
};
const PageTools: React.FC<PageToolsProps> = (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<FilterBarProps> = (props) => {
selectHolder,
right,
left,
widths
widths,
filtersButtonProps
} = props;
const intl = useIntl();
const renderLeft = () => {
return (
<Space>
{filtersButtonProps?.show && (
<FiltersButton
onClear={filtersButtonProps.onClear}
onClick={filtersButtonProps.onClick}
count={filtersButtonProps.count}
></FiltersButton>
)}
<Input
prefix={
<SearchOutlined
+1
View File
@@ -10,6 +10,7 @@ import { SealColumnProps, SealTableProps } from './types';
import useSorter from './use-sorter';
const Wrapper = styled.div<{ $token: any }>`
width: 100%;
--ant-table-cell-padding-inline: ${(props) =>
props.$token.cellPaddingInline}px;
--ant-table-cell-padding-block: ${(props) => props.$token.cellPaddingBlock}px;
+2 -1
View File
@@ -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'
};
+4 -2
View File
@@ -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 ==========
+4 -2
View File
@@ -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 ==========
+3 -1
View File
@@ -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 ==========
+2 -1
View File
@@ -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': '类别'
};
-238
View File
@@ -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 (
<div className="btn-wrapper">
<Button size="middle" onClick={handleOnReset}>
{intl.formatMessage({ id: 'common.button.reset' })}
</Button>
<div className="buttons">
<Button size="middle" type="primary" onClick={handleOnClose}>
{intl.formatMessage({ id: 'common.button.close' })}
</Button>
</div>
</div>
);
};
return (
<Popover
open={open}
trigger={['click', 'hover']}
arrow={false}
placement={placement}
content={
<Container>
<OverlayScroller
maxHeight={contentHeight}
styles={{
wrapper: {
paddingInline: 8
}
}}
>
<Form
onValuesChange={handleOnValuesChange}
initialValues={initialValues}
form={form}
layout="vertical"
styles={{
label: {
lineHeight: 1,
height: 'auto',
marginBottom: 8,
fontWeight: 500
},
content: {
minHeight: 0
}
}}
>
{children}
</Form>
</OverlayScroller>
</Container>
}
onOpenChange={handleOpenChange}
styles={{
container: {
padding: 8,
border: '1px solid var(--ant-color-split)'
},
root: {
width: width
}
}}
>
<ButtonWrapper>
<Divider orientation="vertical" style={{ height: 16 }} />
<Button
size="small"
type="text"
variant="filled"
color="default"
shape="round"
>
<Filters>
<span
style={{
fontSize: 12,
color: 'var(--ant-color-text-secondary)',
fontWeight: 400
}}
>
More Filters
</span>
{filtersCount > 0 ? (
<>
<span className="count">{filtersCount}</span>
<span className="close-btn">
<CloseCircleFilled
style={{ fontSize: 16 }}
onClick={(e) => {
e.stopPropagation();
handleOnClear();
}}
/>
</span>
</>
) : (
<IconFont
type="icon-filter-list"
style={{ fontSize: 14 }}
></IconFont>
)}
</Filters>
</Button>
</ButtonWrapper>
</Popover>
);
};
export default FilterForm;
+155
View File
@@ -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 (
<div className={filterFormCss['btn-wrapper']}>
<Button size="middle" onClick={handleOnReset}>
{intl.formatMessage({ id: 'common.button.reset' })}
</Button>
<div className={filterFormCss.buttons}>
<Button size="middle" type="primary" onClick={handleOnClose}>
{intl.formatMessage({ id: 'common.button.close' })}
</Button>
</div>
</div>
);
};
useImperativeHandle(ref, () => ({
form,
reset: handleOnReset,
getValues: () => form.getFieldsValue(),
setValues: (values: any) => form.setFieldsValue(values)
}));
return (
<div
className={classNames(filterFormCss.wrapper, {
[filterFormCss.show]: open
})}
style={{
width: open ? width : 0,
height: contentHeight,
...styles?.wrapper
}}
>
<div
style={{ width: width, ...styles?.container }}
className={filterFormCss.container}
>
<div className={filterFormCss.title}>
<span
style={{
fontWeight: 500,
color: 'var(--ant-color-text-tertiary)'
}}
>
{intl.formatMessage({ id: 'common.filter.label' })}
</span>
<Button
size="small"
icon={<CloseOutlined />}
onClick={handleOnClose}
type="text"
style={{
color: 'var(--ant-color-text-secondary)'
}}
></Button>
</div>
<OverlayScroller
maxHeight={contentHeight}
styles={{
wrapper: {
paddingInline: 8
}
}}
>
<Form
onValuesChange={handleOnValuesChange}
initialValues={initialValues}
form={form}
layout="vertical"
styles={{
label: {
lineHeight: 1,
height: 'auto',
marginBottom: 8,
fontWeight: 500
},
content: {
minHeight: 0
}
}}
>
{children}
</Form>
</OverlayScroller>
</div>
</div>
);
}
);
export default FilterForm;
+5 -2
View File
@@ -74,8 +74,11 @@ export const PageContainerInner: React.FC<
);
};
const PageBox: React.FC<{ children: React.ReactNode }> = ({ children }) => {
return <div>{children}</div>;
const PageBox: React.FC<{
children: React.ReactNode;
style?: React.CSSProperties;
}> = ({ children, style }) => {
return <div style={style}>{children}</div>;
};
export default PageBox;
@@ -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;
}
}
+1 -1
View File
@@ -233,7 +233,7 @@ const BackendList = () => {
marginBottom={22}
marginTop={30}
widths={{
input: 300
input: 230
}}
actionItems={addActions}
actionType="dropdown"
@@ -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<HTMLInputElement>) => void;
@@ -13,12 +12,18 @@ export interface RightActionsProps {
handleQueryChange: (value: any, option?: any) => void;
modelList?: Global.BaseOption<number, { categories: string[] }>[];
datasetList?: Global.BaseOption<string | number>[];
toggleFilters: () => void;
count?: number;
onClear: () => void;
}
const RightActions: React.FC<RightActionsProps> = ({
handleInputChange,
handleSearch,
handleQueryChange,
toggleFilters,
count,
onClear,
modelList,
datasetList
}) => {
@@ -44,6 +49,11 @@ const RightActions: React.FC<RightActionsProps> = ({
return (
<Space>
<FiltersButton
onClick={toggleFilters}
count={count}
onClear={onClear}
></FiltersButton>
<Input
prefix={
<SearchOutlined
@@ -53,11 +63,11 @@ const RightActions: React.FC<RightActionsProps> = ({
placeholder={intl.formatMessage({
id: 'common.filter.name'
})}
style={{ width: 200 }}
style={{ width: 300 }}
allowClear
onChange={handleInputChange}
></Input>
<Input
{/* <Input
prefix={
<SearchOutlined
style={{ color: 'var(--ant-color-text-placeholder)' }}
@@ -107,7 +117,7 @@ const RightActions: React.FC<RightActionsProps> = ({
page: 1
})
}
></BaseSelect>
></BaseSelect> */}
<Button
type="text"
style={{ color: 'var(--ant-color-text-tertiary)' }}
+138
View File
@@ -0,0 +1,138 @@
import BaseSelect from '@/components/seal-form/base/select';
import FilterForm from '@/pages/_components/filter-form';
import { modelCategoriesMap } from '@/pages/llmodels/config';
import { SearchOutlined } from '@ant-design/icons';
import { useIntl } from '@umijs/max';
import { Form, Input } from 'antd';
import { forwardRef, useImperativeHandle, useRef } from 'react';
import styled from 'styled-components';
import { profileOptions } from '../config';
const Content = styled.div`
display: flex;
flex-direction: column;
`;
const Label = styled.span`
display: inline-flex;
align-items: center;
font-size: 13px;
margin-block: 12px 8px;
color: var(--ant-color-text-tertiary);
`;
interface FilterFormContentProps {
clusterList?: Global.BaseOption<number>[];
initialValues?: any;
open?: boolean;
ref?: any;
modelList?: Global.BaseOption<number, { categories: string[] }>[];
onClose?: () => void;
onClear?: () => void;
onValuesChange: (values: any) => void;
}
const FilterFormContent: React.FC<FilterFormContentProps> = forwardRef(
(
{ initialValues, onClose, onClear, onValuesChange, open, modelList },
ref
) => {
const intl = useIntl();
const filterRef = useRef<any>(null);
const handleOnValuesChange = (changedValues: any, allValues: any) => {
onValuesChange?.(allValues);
};
const modelOptions = modelList
?.filter((item) => {
return item.categories?.includes(modelCategoriesMap.llm);
})
.map((item) => ({
label: item.label,
value: item.label
}));
useImperativeHandle(ref, () => ({
reset: () => {
filterRef.current?.reset();
}
}));
return (
<FilterForm
ref={filterRef}
width={232}
contentHeight={'calc(100vh - 122px)'}
open={open}
onClose={onClose}
onClear={onClear}
onValuesChange={handleOnValuesChange}
initialValues={initialValues}
styles={{
container: {
paddingInline: '0 8px',
marginLeft: '-8px'
},
wrapper: {
marginRight: open ? 24 : 0,
marginTop: '-24px'
}
}}
>
<Content>
<Label style={{ marginTop: 0 }}>GPU</Label>
<Form.Item name="gpu_summary" noStyle>
<Input
prefix={
<SearchOutlined
style={{ color: 'var(--ant-color-text-placeholder)' }}
></SearchOutlined>
}
placeholder={intl.formatMessage({
id: 'benchmark.table.filter.bygpu'
})}
allowClear
></Input>
</Form.Item>
<Label>{intl.formatMessage({ id: 'benchmark.form.profile' })}</Label>
<Form.Item noStyle name="profile">
<BaseSelect
allowClear
placeholder={intl.formatMessage({
id: 'benchmark.table.filter.byProfile'
})}
options={[
...profileOptions,
{
label: 'backend.custom',
locale: true,
value: 'Custom'
}
].map((item) => ({
label: item.locale
? intl.formatMessage({ id: item.label })
: item.label,
value: item.value
}))}
></BaseSelect>
</Form.Item>
<Label>{intl.formatMessage({ id: 'benchmark.table.model' })}</Label>
<Form.Item noStyle name="model_name">
{/* <PillButtonGroup
options={modelCategories.filter((item) => item.value)}
></PillButtonGroup> */}
<BaseSelect
allowClear
placeholder={intl.formatMessage({
id: 'benchmark.table.filter.bymodel'
})}
options={modelOptions}
></BaseSelect>
</Form.Item>
</Content>
</FilterForm>
);
}
);
export default FilterFormContent;
+41 -5
View File
@@ -7,10 +7,10 @@ import useTableFetch from '@/hooks/use-table-fetch';
import { useBenchmarkTargetInstance } from '@/pages/llmodels/hooks/use-run-benchmark';
import { useQueryModelList } from '@/pages/llmodels/services/use-query-model-list';
import { useIntl, useNavigate } from '@umijs/max';
import { useMemoizedFn } from 'ahooks';
import { useMemoizedFn, useToggle } from 'ahooks';
import { ConfigProvider, Table, message } from 'antd';
import _ from 'lodash';
import { useEffect } from 'react';
import { useEffect, useRef, useState } from 'react';
import NoResult from '../_components/no-result';
import PageBox from '../_components/page-box';
import { useQueryClusterList } from '../cluster-management/services/use-query-cluster-list';
@@ -26,6 +26,7 @@ import LeftActions from './components/left-actions';
import RightActions from './components/right-actions';
import ViewLogsModal from './components/view-logs-modal';
import { FormData, BenchmarkListItem as ListItem } from './config/types';
import Filters from './filters';
import useBenchmarkColumns from './hooks/use-benchmark-columns';
import useColumnSettings from './hooks/use-column-settings';
import useCreateBenchmark from './hooks/use-create-benchmark';
@@ -86,6 +87,9 @@ const Benchmark: React.FC = () => {
clusterList,
profileOptions: profilesOptions
});
const [filtersVisible, { toggle: toggleFilters }] = useToggle();
const filterRef = useRef<any>(null);
const [filterValues, setFilterValues] = useState<any>({});
useEffect(() => {
fetchModelList({ page: -1 });
@@ -205,9 +209,38 @@ const Benchmark: React.FC = () => {
exportData(rowSelection.selectedRowKeys);
};
const handleOnFilterChange = (filters: any) => {
handleQueryChange({
page: 1,
...filters
});
setFilterValues(filters);
};
const handleOnClearFilters = () => {
filterRef.current?.reset();
};
const filtersCount = Object.values(filterValues).filter(
(value) => value !== undefined && value !== null && value !== ''
);
return (
<>
<PageBox>
<div
style={{
display: 'flex',
alignItems: 'flex-start'
}}
>
<Filters
ref={filterRef}
open={filtersVisible}
onValuesChange={handleOnFilterChange}
onClose={toggleFilters}
onClear={handleOnClearFilters}
></Filters>
<PageBox style={{ flex: 1 }}>
<FilterBar
showSelect={false}
marginBottom={22}
@@ -223,6 +256,9 @@ const Benchmark: React.FC = () => {
handleSearch={handleSearch}
handleQueryChange={handleQueryChange}
handleInputChange={handleNameChange}
count={filtersCount.length}
toggleFilters={toggleFilters}
onClear={handleOnClearFilters}
></LeftActions>
}
right={
@@ -284,7 +320,7 @@ const Benchmark: React.FC = () => {
onCancel={closeViewLogsModal}
></ViewLogsModal>
<DeleteModal ref={modalRef}></DeleteModal>
</>
</div>
);
};
+14 -48
View File
@@ -1,9 +1,8 @@
import { FiltersButton } from '@/components/page-tools/index';
import { SearchOutlined, SyncOutlined } from '@ant-design/icons';
import { useIntl } from '@umijs/max';
import { Button, Input, Space } from 'antd';
import React from 'react';
import Filters from '../filters';
import useFilterStatus from '../hooks/use-filter-status';
interface LeftFiltersProps {
handleNameChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
@@ -13,6 +12,9 @@ interface LeftFiltersProps {
handleCategoryChange: (value: string) => void;
onFilterChange: (allValues: any) => void;
clusterList: Global.BaseOption<number>[];
toggleFilters: () => void;
onClear: () => void;
count?: number;
}
const LeftFilters: React.FC<LeftFiltersProps> = (props) => {
@@ -23,13 +25,20 @@ const LeftFilters: React.FC<LeftFiltersProps> = (props) => {
handleSearch,
handleCategoryChange,
onFilterChange,
clusterList
clusterList,
toggleFilters,
onClear,
count
} = props;
const { labelRender, optionRender, statusOptions } = useFilterStatus();
const intl = useIntl();
return (
<Space>
<FiltersButton
onClick={toggleFilters}
count={count}
onClear={onClear}
></FiltersButton>
<Input
prefix={
<SearchOutlined
@@ -37,54 +46,11 @@ const LeftFilters: React.FC<LeftFiltersProps> = (props) => {
></SearchOutlined>
}
placeholder={intl.formatMessage({ id: 'common.filter.name' })}
style={{ width: 400 }}
style={{ width: 300 }}
size="large"
allowClear
suffix={
<Filters
clusterList={clusterList}
onValuesChange={onFilterChange}
></Filters>
}
onChange={handleNameChange}
></Input>
{/* <BaseSelect
allowClear
showSearch={false}
placeholder={intl.formatMessage({
id: 'models.filter.category'
})}
style={{ width: 160 }}
size="large"
maxTagCount={1}
onChange={handleCategoryChange}
options={modelCategories.filter((item) => item.value)}
></BaseSelect>
<BaseSelect
allowClear
showSearch={false}
placeholder={intl.formatMessage({
id: 'clusters.filterBy.cluster'
})}
style={{ width: 160 }}
size="large"
maxTagCount={1}
onChange={handleClusterChange}
options={clusterList}
></BaseSelect>
<BaseSelect
allowClear
showSearch={false}
placeholder={intl.formatMessage({ id: 'common.filter.status' })}
style={{ width: 180 }}
size="large"
maxTagCount={1}
optionRender={optionRender}
labelRender={labelRender}
options={statusOptions}
onChange={handleStatusChange}
></BaseSelect> */}
<Button
type="text"
style={{ color: 'var(--ant-color-text-tertiary)' }}
+36 -9
View File
@@ -12,7 +12,6 @@ import useBodyScroll from '@/hooks/use-body-scroll';
import useExpandedRowKeys from '@/hooks/use-expanded-row-keys';
import useTableRowSelection from '@/hooks/use-table-row-selection';
import useWatchList from '@/hooks/use-watch-list';
import PageBox from '@/pages/_components/page-box';
import useNoResourceResult from '@/pages/llmodels/hooks/use-no-resource-result';
import { MODEL_ROUTE_TARGETS } from '@/pages/model-routes/apis';
import { TargetStatusValueMap } from '@/pages/model-routes/config';
@@ -21,7 +20,7 @@ import useGranfanaLink from '@/pages/resources/hooks/use-grafana-link';
import { handleBatchRequest } from '@/utils';
import { DownOutlined } from '@ant-design/icons';
import { useIntl, useNavigate, useSearchParams } from '@umijs/max';
import { useMemoizedFn } from 'ahooks';
import { useMemoizedFn, useToggle } from 'ahooks';
import { Button, Space, message } from 'antd';
import { useAtom } from 'jotai';
import _ from 'lodash';
@@ -53,6 +52,7 @@ import {
ModelInstanceListItem,
SourceType
} from '../config/types';
import Filters from '../filters';
import useEditDeployment from '../hooks/use-edit-deployment';
import useModelsColumns from '../hooks/use-models-columns';
import useViewInstanceLogs from '../hooks/use-view-instance-logs';
@@ -77,7 +77,7 @@ interface ModelsProps {
onTableSort?: (order: TableOrder | Array<TableOrder>) => void;
onStatusChange: (value?: any) => void;
onDeleteInstanceFromCache?: (instanceId: number) => void;
onFilterChange?: (filters: any) => void;
onFilterChange: (filters: any) => void;
sortOrder: string[];
queryParams: {
page: number;
@@ -90,6 +90,7 @@ interface ModelsProps {
loading: boolean;
loadend: boolean;
total: number;
filterValues?: Record<string, any>;
}
const getFormattedData = (record: any, extraData = {}) => ({
@@ -128,7 +129,8 @@ const Models: React.FC<ModelsProps> = ({
queryParams,
loading,
loadend,
total
total,
filterValues = {}
}) => {
const { generateFormValues, clusterList, workerList } =
useDeploymentsContext();
@@ -177,6 +179,8 @@ const Models: React.FC<ModelsProps> = ({
source: modelSourceMap.huggingface_value as SourceType
});
const modalRef = useRef<any>(null);
const [filtersVisible, { toggle: toggleFilters }] = useToggle();
const filterRef = useRef<any>(null);
useEffect(() => {
if (deleteIds?.length) {
@@ -540,6 +544,14 @@ const Models: React.FC<ModelsProps> = ({
}
});
const handleOnClearFilters = () => {
filterRef.current?.reset();
};
const filtersCount = Object.values(filterValues).filter(
(value) => value !== undefined && value !== null && value !== ''
);
useEffect(() => {
if (modelsSession.source && loadend) {
handleClickDropdown({
@@ -552,13 +564,29 @@ const Models: React.FC<ModelsProps> = ({
}, [loadend]);
return (
<>
<PageBox>
<div
style={{
display: 'flex',
alignItems: 'flex-start'
}}
>
<Filters
ref={filterRef}
open={filtersVisible}
onValuesChange={onFilterChange}
clusterList={clusterList}
onClose={toggleFilters}
onClear={handleOnClearFilters}
></Filters>
<div style={{ flex: 1, padding: '24px' }}>
<PageTools
marginBottom={22}
marginTop={0}
left={
<LeftFilters
count={filtersCount.length}
toggleFilters={toggleFilters}
onClear={handleOnClearFilters}
handleNameChange={handleNameChange}
handleClusterChange={handleClusterChange}
handleCategoryChange={handleCategoryChange}
@@ -603,7 +631,6 @@ const Models: React.FC<ModelsProps> = ({
</Space>
}
></PageTools>
<SealTable
columns={columns}
sortDirections={TABLE_SORT_DIRECTIONS}
@@ -636,7 +663,7 @@ const Models: React.FC<ModelsProps> = ({
onChange: handlePageChange
}}
></SealTable>
</PageBox>
</div>
<UpdateModelModal
open={openEditModalStatus.open}
action={openEditModalStatus.action}
@@ -668,7 +695,7 @@ const Models: React.FC<ModelsProps> = ({
onCancel={handleLogModalCancel}
></ViewLogsModal>
<DeleteModal ref={modalRef}></DeleteModal>
</>
</div>
);
};
+5
View File
@@ -87,6 +87,8 @@ const Models = forwardRef((props, ref) => {
setDataList: setModelInstances
});
const [filterValues, setFilterValues] = useState<any>({});
const getAllModelInstances = useMemoizedFn(async () => {
try {
instancesToken.current?.cancel?.();
@@ -368,6 +370,8 @@ const Models = forwardRef((props, ref) => {
search: queryParams.search,
...filters
});
setFilterValues(filters);
};
const handleDeleteInstanceFromCache = (id: number) => {
@@ -464,6 +468,7 @@ const Models = forwardRef((props, ref) => {
loadend={dataSource.loadend}
total={dataSource.total}
deleteIds={dataSource.deletedIds}
filterValues={filterValues}
></TableList>
</TableContext.Provider>
);
+114 -76
View File
@@ -1,8 +1,8 @@
import BaseSelect from '@/components/seal-form/base/select';
import FilterForm from '@/pages/_components/filter-form';
import PillButtonGroup from '@/pages/_components/pill-button-group';
import { useIntl } from '@umijs/max';
import { Form } from 'antd';
import { forwardRef, useImperativeHandle, useRef } from 'react';
import styled from 'styled-components';
import { modelCategories } from '../config';
import useFilterStatus from '../hooks/use-filter-status';
@@ -10,98 +10,136 @@ import useFilterStatus from '../hooks/use-filter-status';
const Content = styled.div`
display: flex;
flex-direction: column;
// gap: 16px;
`;
const Label = styled.span`
display: inline-flex;
align-items: center;
font-size: 14px;
margin-block: 16px;
font-size: 13px;
margin-block: 12px 8px;
color: var(--ant-color-text-tertiary);
`;
interface FilterFormContentProps {
clusterList: Global.BaseOption<number>[];
initialValues?: any;
onValuesChange?: (values: any) => void;
open?: boolean;
ref?: any;
onClose?: () => void;
onClear: () => void;
onValuesChange: (values: any) => void;
}
const FilterFormContent: React.FC<FilterFormContentProps> = ({
clusterList,
initialValues,
onValuesChange
}) => {
const intl = useIntl();
const { labelRender, optionRender, statusOptions } = useFilterStatus();
const FilterFormContent: React.FC<FilterFormContentProps> = forwardRef(
(
{ clusterList, initialValues, onClose, onClear, onValuesChange, open },
ref
) => {
const intl = useIntl();
const { labelRender, optionRender, statusOptions } = useFilterStatus();
const filterRef = useRef<any>(null);
const handleOnValuesChange = (changedValues: any, allValues: any) => {
onValuesChange?.(allValues);
};
const handleOnValuesChange = (changedValues: any, allValues: any) => {
onValuesChange?.(allValues);
};
return (
<FilterForm
width={430}
onValuesChange={handleOnValuesChange}
initialValues={initialValues}
>
<Content>
<Label style={{ marginTop: 0 }}>
{intl.formatMessage({
id: 'clusters.filterBy.cluster'
})}
</Label>
<Form.Item
noStyle
name="cluster_id"
label={intl.formatMessage({
id: 'clusters.filterBy.cluster'
})}
>
<BaseSelect
allowClear
showSearch={false}
placeholder={intl.formatMessage({
useImperativeHandle(ref, () => ({
reset: () => {
filterRef.current?.reset();
}
}));
return (
<FilterForm
ref={filterRef}
width={232}
contentHeight={'calc(100vh - 74px)'}
open={open}
onClose={onClose}
onClear={onClear}
onValuesChange={handleOnValuesChange}
initialValues={initialValues}
styles={{
wrapper: {
marginTop: 0
}
}}
>
<Content>
<Label style={{ marginTop: 0 }}>
{intl.formatMessage({
id: 'clusters.title'
})}
</Label>
<Form.Item
noStyle
name="cluster_id"
label={intl.formatMessage({
id: 'clusters.filterBy.cluster'
})}
size="large"
maxTagCount={1}
options={clusterList}
></BaseSelect>
</Form.Item>
<Label>
{intl.formatMessage({
id: 'models.filter.category'
})}
</Label>
<Form.Item
noStyle
name="categories"
label={intl.formatMessage({
id: 'models.filter.category'
})}
>
<PillButtonGroup
>
<BaseSelect
allowClear
showSearch={false}
placeholder={intl.formatMessage({
id: 'clusters.filterBy.cluster'
})}
size="large"
maxTagCount={1}
options={clusterList}
></BaseSelect>
</Form.Item>
<Label>{intl.formatMessage({ id: 'models.table.category' })}</Label>
<Form.Item
noStyle
name="categories"
label={intl.formatMessage({
id: 'models.filter.category'
})}
>
{/* <PillButtonGroup
options={modelCategories.filter((item) => item.value)}
></PillButtonGroup>
</Form.Item>
></PillButtonGroup> */}
<BaseSelect
allowClear
showSearch={false}
placeholder={intl.formatMessage({
id: 'models.filter.category'
})}
size="large"
maxTagCount={1}
// onChange={handleCategoryChange}
options={modelCategories.filter((item) => item.value)}
></BaseSelect>
</Form.Item>
<Label>
{intl.formatMessage({
id: 'common.filter.status'
})}
</Label>
<Form.Item
noStyle
name="state"
label={intl.formatMessage({
id: 'common.filter.status'
})}
>
<PillButtonGroup options={statusOptions}></PillButtonGroup>
</Form.Item>
</Content>
</FilterForm>
);
};
<Label>
{intl.formatMessage({
id: 'models.table.status'
})}
</Label>
<Form.Item
noStyle
name="state"
label={intl.formatMessage({
id: 'models.table.status'
})}
>
{/* <PillButtonGroup options={statusOptions}></PillButtonGroup> */}
<BaseSelect
allowClear
showSearch={false}
placeholder={intl.formatMessage({ id: 'common.filter.status' })}
size="large"
maxTagCount={1}
optionRender={optionRender}
labelRender={labelRender}
options={statusOptions}
></BaseSelect>
</Form.Item>
</Content>
</FilterForm>
);
}
);
export default FilterFormContent;
+8 -1
View File
@@ -105,7 +105,14 @@ const LLModels: React.FC = () => {
}, [activeKey]);
return (
<PageContainerInner leftContent={title}>
<PageContainerInner
leftContent={title}
styles={{
containerWrapper: {
padding: 0
}
}}
>
<DeploymentsContext.Provider
value={{
generateFormValues,
+2 -2
View File
@@ -126,7 +126,7 @@ const InstanceView = forwardRef((props, ref) => {
});
return (
<>
<div style={{ padding: 24 }}>
<PageBox>
<FilterBar
showSelect={false}
@@ -186,7 +186,7 @@ const InstanceView = forwardRef((props, ref) => {
onCancel={closeViewLogsModal}
></ViewLogsModal>
<DeleteModal ref={modalRef}></DeleteModal>
</>
</div>
);
});