Files
gpustack-ui/src/pages/llmodels/catalog.tsx
T

291 lines
7.9 KiB
TypeScript

import { modelsExpandKeysAtom } from '@/atoms/models';
import PageTools from '@/components/page-tools';
import { PageAction } from '@/config';
import useBodyScroll from '@/hooks/use-body-scroll';
import { IS_FIRST_LOGIN, writeState } from '@/utils/localstore/index';
import { SyncOutlined } from '@ant-design/icons';
import { PageContainer } from '@ant-design/pro-components';
import { useIntl, useNavigate } from '@umijs/max';
import { Button, Input, Pagination, Select, Space, message } from 'antd';
import { useAtom } from 'jotai';
import _ from 'lodash';
import React, { useCallback, useEffect, useState } from 'react';
import { createModel, queryCatalogList } from './apis';
import CatalogList from './components/catalog-list';
import DelopyBuiltInModal from './components/deploy-builtin-modal';
import { modelCategories, modelSourceMap } from './config';
import { CatalogItem as CatalogItemType, FormData } from './config/types';
const Catalog: React.FC = () => {
const intl = useIntl();
const { saveScrollHeight, restoreScrollHeight } = useBodyScroll();
const navigate = useNavigate();
const [activeId, setActiveId] = React.useState(-1);
const [isFirst, setIsFirst] = React.useState(true);
const [dataSource, setDataSource] = useState<{
dataList: CatalogItemType[];
loading: boolean;
total: number;
}>({
dataList: [],
loading: false,
total: 0
});
const [queryParams, setQueryParams] = useState({
page: 1,
perPage: 100,
search: '',
categories: []
});
const [openDeployModal, setOpenDeployModal] = useState<any>({
show: false,
width: 600,
current: {},
source: modelSourceMap.huggingface_value
});
const [modelsExpandKeys, setModelsExpandKeys] = useAtom(modelsExpandKeysAtom);
const cacheData = React.useRef<CatalogItemType[]>([]);
const categoryOptions = [...modelCategories.filter((item) => item.value)];
const filterData = useCallback(
(data: { search: string; categories: string[] }) => {
const { search, categories } = data;
const dataList = cacheData.current.filter((item) => {
if (search && categories.length > 0) {
return (
_.toLower(item.name).includes(search) &&
categories.some((category) => item.categories.includes(category))
);
}
if (search) {
return _.toLower(item.name).includes(_.toLower(search));
}
if (categories.length > 0) {
return categories.some((category) =>
item.categories.includes(category)
);
}
return true;
});
return dataList;
},
[cacheData.current]
);
const fetchData = useCallback(async () => {
setDataSource((pre) => {
pre.loading = true;
return { ...pre };
});
try {
const params = {
..._.pick(queryParams, ['page', 'perPage'])
};
const res: any = await queryCatalogList(params);
cacheData.current = res.items || [];
const dataList = filterData({
search: queryParams.search,
categories: queryParams.categories
});
setDataSource({
dataList: dataList,
loading: false,
total: res.pagination.total
});
} catch (error) {
cacheData.current = [];
setDataSource({
dataList: [],
loading: false,
total: dataSource.total
});
console.log('error', error);
} finally {
setIsFirst(false);
}
}, [queryParams, cacheData.current]);
const handleDeployModalCancel = () => {
setOpenDeployModal({
...openDeployModal,
show: false
});
restoreScrollHeight();
setActiveId(-1);
};
const handleOnDeploy = useCallback((item: CatalogItemType) => {
saveScrollHeight();
setActiveId(item.id);
setOpenDeployModal({
show: true,
source: modelSourceMap.huggingface_value,
current: item,
width: 600
});
}, []);
const handleCreateModel = useCallback(
async (data: FormData) => {
try {
console.log('data:', data, openDeployModal);
const modelData = await createModel({
data: {
..._.omit(data, ['size', 'quantization'])
}
});
writeState(IS_FIRST_LOGIN, false);
setOpenDeployModal({
...openDeployModal,
show: false
});
message.success(intl.formatMessage({ id: 'common.message.success' }));
setModelsExpandKeys([modelData.id]);
navigate('/models/list');
} catch (error) {}
},
[openDeployModal]
);
const handleOnPageChange = useCallback(
(page: number, pageSize?: number) => {
setQueryParams({
...queryParams,
page,
perPage: pageSize || 10
});
},
[queryParams]
);
const handleSearch = (e: any) => {
fetchData();
};
const handleNameChange = _.debounce((e: any) => {
const dataList = filterData({
search: e.target.value,
categories: queryParams.categories
});
setQueryParams({
...queryParams,
page: 1,
search: e.target.value
});
setDataSource({
dataList,
loading: false,
total: dataSource.total
});
}, 200);
const handleCategoryChange = (value: any) => {
const dataList = filterData({
search: queryParams.search,
categories: value
});
setQueryParams({
...queryParams,
page: 1,
categories: value
});
setDataSource({
dataList,
loading: false,
total: dataSource.total
});
};
useEffect(() => {
fetchData();
}, []);
return (
<PageContainer
ghost
header={{
title: intl.formatMessage({ id: 'menu.modelCatalog' }),
style: {
paddingInline: 'var(--layout-content-header-inlinepadding)'
},
breadcrumb: {}
}}
extra={[]}
>
<PageTools
marginBottom={22}
left={
<Space>
<Input
placeholder={intl.formatMessage({ id: 'common.filter.name' })}
style={{ width: 230 }}
size="large"
allowClear
onClear={() =>
handleNameChange({
target: {
value: ''
}
})
}
onChange={handleNameChange}
></Input>
<Select
allowClear
showSearch={false}
placeholder={intl.formatMessage({ id: 'models.filter.category' })}
style={{ width: 230 }}
size="large"
mode="multiple"
maxTagCount={1}
onChange={handleCategoryChange}
options={categoryOptions}
></Select>
<Button
type="text"
style={{ color: 'var(--ant-color-text-tertiary)' }}
icon={<SyncOutlined></SyncOutlined>}
onClick={handleSearch}
></Button>
</Space>
}
></PageTools>
<CatalogList
dataList={dataSource.dataList}
loading={dataSource.loading}
onDeploy={handleOnDeploy}
activeId={-1}
isFirst={isFirst}
></CatalogList>
<div style={{ marginBlock: '32px 16px' }}>
<Pagination
hideOnSinglePage={queryParams.perPage === 100}
align="end"
defaultCurrent={1}
total={dataSource.total}
pageSize={queryParams.perPage}
showSizeChanger
onChange={handleOnPageChange}
/>
</div>
<DelopyBuiltInModal
open={openDeployModal.show}
action={PageAction.CREATE}
title={intl.formatMessage({ id: 'models.button.deploy' })}
source={openDeployModal.source}
width={openDeployModal.width}
current={openDeployModal.current}
onCancel={handleDeployModalCancel}
onOk={handleCreateModel}
></DelopyBuiltInModal>
</PageContainer>
);
};
export default Catalog;