feat: add pagination for searching models

This commit is contained in:
jialin
2025-06-10 19:24:13 +08:00
parent 0eaa3aeeb7
commit 281d8bddc1
7 changed files with 183 additions and 64 deletions
+1
View File
@@ -1,3 +1,4 @@
PORT=9000 PORT=9000
UMI_DEV_SERVER_COMPRESS=none UMI_DEV_SERVER_COMPRESS=none
DID_YOU_KNOW=none
+1 -1
View File
@@ -46,7 +46,7 @@ const MixLineBarChart: React.FC<
grid: { grid: {
...grid, ...grid,
top: 20, top: 20,
bottom: 20 bottom: 10
}, },
tooltip: { tooltip: {
...tooltip, ...tooltip,
+4 -4
View File
@@ -191,11 +191,11 @@ export async function queryModelScopeModels(
'Content-Type': 'application/json' 'Content-Type': 'application/json'
}, },
body: JSON.stringify({ body: JSON.stringify({
PageSize: 10,
PageNumber: 1,
...params, ...params,
...Criterion, ...Criterion,
Name: `${params.Name}`, Name: `${params.Name}`
PageSize: 10,
PageNumber: 1
}) })
}); });
if (!res.ok) { if (!res.ok) {
@@ -256,7 +256,7 @@ export async function queryHuggingfaceModels(
for await (const model of listModels({ for await (const model of listModels({
...params, ...params,
...options, ...options,
limit: 10, limit: 500,
additionalFields: ['sha', 'tags'], additionalFields: ['sha', 'tags'],
fetch(_url: string, config: any) { fetch(_url: string, config: any) {
const url = params.search.sort const url = params.search.sort
+22 -3
View File
@@ -129,6 +129,15 @@ const AddModal: FC<AddModalProps> = (props) => {
const evaluateStateRef = useRef<{ state: EvaluateProccessType }>({ const evaluateStateRef = useRef<{ state: EvaluateProccessType }>({
state: 'form' state: 'form'
}); });
const requestModelIdRef = useRef<number>(0);
/**
* Update the request model id to distinguish
* the evaluate request.
*/
const updateRequestModelId = () => {
requestModelIdRef.current += 1;
};
/** /**
* *
@@ -194,12 +203,19 @@ const AddModal: FC<AddModalProps> = (props) => {
}); });
if (item.fakeName) { if (item.fakeName) {
const currentModelId = requestModelIdRef.current;
setEvaluteState(EvaluateProccess.file); setEvaluteState(EvaluateProccess.file);
const evaluateRes = await handleEvaluateOnChange?.({ const evaluateRes = await handleEvaluateOnChange?.({
changedValues: {}, changedValues: {},
allValues: form.current?.form?.getFieldsValue?.(), allValues: form.current?.form?.getFieldsValue?.(),
source: props.source source: props.source
}); });
if (currentModelId !== requestModelIdRef.current) {
// if the request model id has changed, do not update the form
return;
}
const defaultSpec = getDefaultSpec({ const defaultSpec = getDefaultSpec({
evaluateResult: evaluateRes evaluateResult: evaluateRes
}); });
@@ -224,7 +240,11 @@ const AddModal: FC<AddModalProps> = (props) => {
}; };
const handleOnSelectModel = (item: any, evaluate?: boolean) => { const handleOnSelectModel = (item: any, evaluate?: boolean) => {
// when select a model not from the evaluate result, /**
* evaluate: false means select a new model
* evaluate: true means select a model file from the evaluate result
*/
updateRequestModelId();
if (!evaluate) { if (!evaluate) {
setEvaluteState(EvaluateProccess.model); setEvaluteState(EvaluateProccess.model);
setSelectedModel(item); setSelectedModel(item);
@@ -241,7 +261,7 @@ const AddModal: FC<AddModalProps> = (props) => {
const modelInfo = onSelectModel(item, props.source); const modelInfo = onSelectModel(item, props.source);
if ( if (
!isHolderRef.current.model && !isHolderRef.current.model &&
evaluateStateRef.current.state === 'model' evaluateStateRef.current.state === EvaluateProccess.model
) { ) {
handleShowCompatibleAlert(item.evaluateResult); handleShowCompatibleAlert(item.evaluateResult);
form.current?.setFieldsValue?.({ form.current?.setFieldsValue?.({
@@ -268,7 +288,6 @@ const AddModal: FC<AddModalProps> = (props) => {
}; };
const handleSetIsGGUF = async (flag: boolean) => { const handleSetIsGGUF = async (flag: boolean) => {
console.log('handleSetIsGGUF', flag);
setIsGGUF(flag); setIsGGUF(flag);
await new Promise((resolve) => { await new Promise((resolve) => {
setTimeout(() => { setTimeout(() => {
@@ -54,13 +54,7 @@ const includeReg = /\.(safetensors|gguf)$/i;
const filterRegGGUF = /\.(gguf)$/i; const filterRegGGUF = /\.(gguf)$/i;
const HFModelFile: React.FC<HFModelFileProps> = forwardRef((props, ref) => { const HFModelFile: React.FC<HFModelFileProps> = forwardRef((props, ref) => {
const { const { collapsed, modelSource, isDownload, displayEvaluateStatus } = props;
collapsed,
modelSource,
isDownload,
gpuOptions,
displayEvaluateStatus
} = props;
const intl = useIntl(); const intl = useIntl();
const [isEvaluating, setIsEvaluating] = useState(false); const [isEvaluating, setIsEvaluating] = useState(false);
const [dataSource, setDataSource] = useState<any>({ const [dataSource, setDataSource] = useState<any>({
+152 -48
View File
@@ -1,15 +1,9 @@
import { createAxiosToken } from '@/hooks/use-chunk-request'; import { createAxiosToken } from '@/hooks/use-chunk-request';
import { QuestionCircleOutlined } from '@ant-design/icons'; import { QuestionCircleOutlined } from '@ant-design/icons';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { Checkbox, Select, Tooltip } from 'antd'; import { Checkbox, Pagination, Select, Tooltip } from 'antd';
import _ from 'lodash'; import _ from 'lodash';
import React, { import React, { useEffect, useMemo, useRef, useState } from 'react';
useCallback,
useEffect,
useMemo,
useRef,
useState
} from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import { import {
evaluationsModelSpec, evaluationsModelSpec,
@@ -86,6 +80,12 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
const filterGGUFRef = useRef<boolean | undefined>(!hasLinuxWorker); const filterGGUFRef = useRef<boolean | undefined>(!hasLinuxWorker);
const filterTaskRef = useRef<string>(''); const filterTaskRef = useRef<string>('');
const timer = useRef<any>(null); const timer = useRef<any>(null);
const requestIdRef = useRef<number>(0);
const [query, setQuery] = useState({
page: 1,
perPage: 10,
total: 100
});
const modelFilesSortOptions = useRef<any[]>([ const modelFilesSortOptions = useRef<any[]>([
{ {
label: intl.formatMessage({ id: 'models.sort.trending' }), label: intl.formatMessage({ id: 'models.sort.trending' }),
@@ -105,6 +105,11 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
} }
]); ]);
const updateRequestId = () => {
requestIdRef.current += 1;
return requestIdRef.current;
};
const checkIsGGUF = (item: any) => { const checkIsGGUF = (item: any) => {
const isGGUF = _.some(item.tags, (tag: string) => { const isGGUF = _.some(item.tags, (tag: string) => {
return tag.toLowerCase() === 'gguf'; return tag.toLowerCase() === 'gguf';
@@ -122,7 +127,7 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
}; };
// huggeface // huggeface
const getModelsFromHuggingface = useCallback(async (sort: string) => { const getModelsFromHuggingface = async (sort: string) => {
try { try {
const task: any = searchInputRef.current ? '' : 'text-generation'; const task: any = searchInputRef.current ? '' : 'text-generation';
const params = { const params = {
@@ -149,10 +154,14 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
} catch (error) { } catch (error) {
return []; return [];
} }
}, []); };
// modelscope // modelscope, only modelscope has page and perPage
const getModelsFromModelscope = useCallback(async (sort: string) => { const getModelsFromModelscope = async (queryParams: {
sortType: string;
page: number;
perPage?: number;
}) => {
try { try {
const params = { const params = {
Name: `${searchInputRef.current}`, Name: `${searchInputRef.current}`,
@@ -160,7 +169,9 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
tasks: filterTaskRef.current tasks: filterTaskRef.current
? ([ModelscopeTaskMap[filterTaskRef.current]] as string[]) ? ([ModelscopeTaskMap[filterTaskRef.current]] as string[])
: [], : [],
SortBy: ModelScopeSortType[sort] SortBy: ModelScopeSortType[queryParams.sortType],
PageNumber: queryParams.page,
PageSize: queryParams.perPage
}; };
const data = await queryModelScopeModels(params, { const data = await queryModelScopeModels(params, {
signal: axiosTokenRef.current.signal signal: axiosTokenRef.current.signal
@@ -179,18 +190,28 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
task: item.Tasks?.map((sItem: any) => sItem.Name).join(','), task: item.Tasks?.map((sItem: any) => sItem.Name).join(','),
tags: item.Tags, tags: item.Tags,
libraries: item.Libraries, libraries: item.Libraries,
isGGUF: checkIsGGUF({ tags: item.Tags, libraries: item.Libraries }), isGGUF: checkIsGGUF({
tags: item.Tags,
libraries: item.Libraries
}),
source: modelSource source: modelSource
}; };
}); });
setQuery((prev) => {
return {
...prev,
page: queryParams.page,
total: _.get(data, 'Data.Model.TotalCount', 0)
};
});
return list; return list;
} catch (error) { } catch (error) {
return []; return [];
} }
}, []); };
const getEvaluateResults = useCallback(async (repoList: any[]) => { const getEvaluateResults = async (repoList: any[]) => {
try { try {
checkTokenRef.current?.cancel?.(); checkTokenRef.current?.cancel?.();
checkTokenRef.current = createAxiosToken(); checkTokenRef.current = createAxiosToken();
@@ -206,12 +227,13 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
} catch (error) { } catch (error) {
return []; return [];
} }
}, []); };
const handleEvaluate = async (list: any[]) => { const handleEvaluate = async (list: any[]) => {
if (isDownload) { if (isDownload) {
return; return;
} }
const currentRequestId = updateRequestId();
try { try {
const repoList = list.map((item) => { const repoList = list.map((item) => {
const res = handleRecognizeAudioModel(item, modelSource); const res = handleRecognizeAudioModel(item, modelSource);
@@ -241,8 +263,12 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
}) })
}; };
}); });
setIsEvaluating(true); setIsEvaluating(true);
const evaluations = await getEvaluateResults(repoList); const evaluations = await getEvaluateResults(repoList);
if (requestIdRef.current !== currentRequestId) {
return;
}
const resultList = list.map((item, index) => { const resultList = list.map((item, index) => {
return { return {
...item, ...item,
@@ -262,7 +288,10 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
(item) => item.id === currentRef.current (item) => item.id === currentRef.current
); );
// if item is GGUF, the evaluating would be do after selecting the model file. /**
* if item is GGUF, the evaluating would be do after selecting the model file, Or the evaluate status of model would be overrided the
* file evaluate status.
*/
if (currentItem && !currentItem.isGGUF) { if (currentItem && !currentItem.isGGUF) {
displayEvaluateStatus?.({ displayEvaluateStatus?.({
show: false, show: false,
@@ -276,11 +305,23 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
handleOnSelectModel(currentItem, true); handleOnSelectModel(currentItem, true);
} }
} catch (error) { } catch (error) {
setIsEvaluating(false); if (requestIdRef.current === currentRequestId) {
setIsEvaluating(false);
}
} }
}; };
const handleOnSearchRepo = async (sortType?: string) => { const getCurrentPage = (page: number) => {
const start = (page - 1) * query.perPage;
const end = start + query.perPage;
return cacheRepoOptions.current.slice(start, end);
};
const handleOnSearchRepo = async (params: {
sortType: string;
page: number;
perPage: number;
}) => {
if (!SUPPORTEDSOURCE.includes(modelSource)) { if (!SUPPORTEDSOURCE.includes(modelSource)) {
return; return;
} }
@@ -290,7 +331,7 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
if (timer.current) { if (timer.current) {
clearTimeout(timer.current); clearTimeout(timer.current);
} }
const sort = sortType ?? dataSource.sortType; const sort = params.sortType;
try { try {
setDataSource((pre) => { setDataSource((pre) => {
pre.loading = true; pre.loading = true;
@@ -300,11 +341,22 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
cacheRepoOptions.current = []; cacheRepoOptions.current = [];
let list: any[] = []; let list: any[] = [];
if (modelSource === modelSourceMap.huggingface_value) { if (modelSource === modelSourceMap.huggingface_value) {
list = await getModelsFromHuggingface(sort); const resultList = await getModelsFromHuggingface(sort);
cacheRepoOptions.current = resultList;
// hf has no page and perPage, so we need to slice the resultList
list = getCurrentPage(params.page);
setQuery((prev) => {
return {
...prev,
page: params.page,
total: resultList.length
};
});
} else if (modelSource === modelSourceMap.modelscope_value) { } else if (modelSource === modelSourceMap.modelscope_value) {
list = await getModelsFromModelscope(sort); list = await getModelsFromModelscope(params);
cacheRepoOptions.current = list;
} }
cacheRepoOptions.current = list;
setDataSource({ setDataSource({
repoOptions: list, repoOptions: list,
@@ -340,11 +392,18 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
cacheRepoOptions.current = []; cacheRepoOptions.current = [];
} }
}; };
const handleSearchInputChange = useCallback((e: any) => { const handleSearchInputChange = (e: any) => {
searchInputRef.current = e.target.value; searchInputRef.current = e.target.value;
}, []); };
const handlerSearchModels = _.debounce(
const handlerSearchModels = _.debounce(() => handleOnSearchRepo(), 100); () =>
handleOnSearchRepo({
sortType: dataSource.sortType,
page: 1,
perPage: query.perPage
}),
100
);
const handleOnOpen = () => { const handleOnOpen = () => {
if ( if (
@@ -352,17 +411,62 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
!cacheRepoOptions.current.length && !cacheRepoOptions.current.length &&
SUPPORTEDSOURCE.includes(modelSource) SUPPORTEDSOURCE.includes(modelSource)
) { ) {
handleOnSearchRepo(); handleOnSearchRepo({
sortType: dataSource.sortType,
page: 1,
perPage: query.perPage
});
} }
}; };
const handleSortChange = (value: string) => { const handleSortChange = (value: string) => {
handleOnSearchRepo(value || ''); handleOnSearchRepo({
sortType: value,
page: 1,
perPage: query.perPage
});
}; };
const handleFilterGGUFChange = (e: any) => { const handleFilterGGUFChange = (e: any) => {
filterGGUFRef.current = e.target.checked; filterGGUFRef.current = e.target.checked;
handleOnSearchRepo(); handleOnSearchRepo({
sortType: dataSource.sortType,
page: 1,
perPage: query.perPage
});
};
const handleOnPageChange = (page: number) => {
if (modelSource === modelSourceMap.huggingface_value) {
const currentList = getCurrentPage(page);
setQuery((prev) => {
return {
...prev,
page: page
};
});
setDataSource((pre) => {
return {
...pre,
repoOptions: currentList
};
});
displayEvaluateStatus?.({
show: true,
flag: {
model: true
}
});
console.log('isEvaluating:', isEvaluating);
handleOnSelectModel(currentList[0]);
handleEvaluate(currentList);
} else if (modelSource === modelSourceMap.modelscope_value) {
handleOnSearchRepo({
sortType: dataSource.sortType,
page: page,
perPage: query.perPage
});
}
}; };
const renderGGUFTips = useMemo(() => { const renderGGUFTips = useMemo(() => {
@@ -402,23 +506,6 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
</div> </div>
<div className={SearchStyle.filter}> <div className={SearchStyle.filter}>
<span> <span>
<span className="value">
{intl.formatMessage(
{ id: 'models.search.result' },
{ count: dataSource.repoOptions.length }
)}
</span>
</span>
<span>
<span id="filterGGUF">
<Checkbox
onChange={handleFilterGGUFChange}
className="m-r-5"
checked={filterGGUFRef.current}
>
{renderGGUFTips}
</Checkbox>
</span>
<Select <Select
value={dataSource.sortType} value={dataSource.sortType}
onChange={handleSortChange} onChange={handleSortChange}
@@ -433,7 +520,23 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
size="middle" size="middle"
style={{ width: '150px' }} style={{ width: '150px' }}
></Select> ></Select>
<Checkbox
onChange={handleFilterGGUFChange}
className="m-l-8"
checked={filterGGUFRef.current}
>
{renderGGUFTips}
</Checkbox>
</span> </span>
<Pagination
simple={{ readOnly: true }}
total={query.total}
current={query.page}
pageSize={query.perPage}
onChange={handleOnPageChange}
showSizeChanger={false}
hideOnSinglePage={query.total <= query.perPage}
></Pagination>
</div> </div>
</> </>
); );
@@ -457,6 +560,7 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
return ( return (
<div style={{ flex: 1 }}> <div style={{ flex: 1 }}>
<div className={SearchStyle['search-bar']}>{renderHFSearch()}</div> <div className={SearchStyle['search-bar']}>{renderHFSearch()}</div>
<SearchResult <SearchResult
loading={dataSource.loading} loading={dataSource.loading}
resultList={dataSource.repoOptions} resultList={dataSource.repoOptions}
+2 -1
View File
@@ -470,8 +470,9 @@ export const useCheckCompatibility = () => {
if (currentRequestId === requestIdRef.current) { if (currentRequestId === requestIdRef.current) {
handleShowCompatibleAlert?.(evalutionData); handleShowCompatibleAlert?.(evalutionData);
return evalutionData;
} }
return evalutionData; return null;
}; };
const checkRequiredValue = (allValues: any) => { const checkRequiredValue = (allValues: any) => {