fix: query models sort

This commit is contained in:
jialin
2024-08-22 13:59:02 +08:00
parent abb87e71ed
commit 76444f28cb
12 changed files with 151 additions and 89 deletions
+8
View File
@@ -26,6 +26,10 @@
margin-right: 5px; margin-right: 5px;
} }
.m-r-2 {
margin-right: 2px;
}
.m-r-8 { .m-r-8 {
margin-right: 8px; margin-right: 8px;
} }
@@ -67,6 +71,10 @@
flex-direction: column; flex-direction: column;
} }
.gap-5 {
gap: 5px;
}
.relative { .relative {
position: relative; position: relative;
} }
+1 -1
View File
@@ -1,7 +1,7 @@
import { createFromIconfontCN } from '@ant-design/icons'; import { createFromIconfontCN } from '@ant-design/icons';
const IconFont = createFromIconfontCN({ const IconFont = createFromIconfontCN({
scriptUrl: '//at.alicdn.com/t/c/font_4613488_orlwzwe9x4k.js' scriptUrl: '//at.alicdn.com/t/c/font_4613488_m179wc500g.js'
}); });
export default IconFont; export default IconFont;
+6 -1
View File
@@ -22,11 +22,16 @@ export default {
'models.sort.name': 'Name', 'models.sort.name': 'Name',
'models.sort.size': 'Size', 'models.sort.size': 'Size',
'models.sort.likes': 'Likes', 'models.sort.likes': 'Likes',
'models.sort.trending': 'Trending',
'models.sort.downloads': 'Downloads', 'models.sort.downloads': 'Downloads',
'models.sort.updated': 'Updated', 'models.sort.updated': 'Updated',
'models.search.result': '{count} results', 'models.search.result': '{count} results',
'models.data.card': 'Model Card', 'models.data.card': 'Model Card',
'models.available.files': 'Available Files', 'models.available.files': 'Available Files',
'models.viewin.hf': 'View in Hugging Face', 'models.viewin.hf': 'View in Hugging Face',
'models.architecture': 'Architecture' 'models.architecture': 'Architecture',
'models.search.noresult': 'No related models found',
'models.search.nofiles': 'No available files',
'models.search.networkerror': 'Network connection exception!',
'models.search.hfvisit': 'Please make sure you can visit'
}; };
+6 -1
View File
@@ -22,11 +22,16 @@ export default {
'models.sort.name': '名称', 'models.sort.name': '名称',
'models.sort.size': '大小', 'models.sort.size': '大小',
'models.sort.likes': '喜欢', 'models.sort.likes': '喜欢',
'models.sort.trending': '趋势',
'models.sort.downloads': '下载', 'models.sort.downloads': '下载',
'models.sort.updated': '更新时间', 'models.sort.updated': '更新时间',
'models.search.result': '{count} 个结果', 'models.search.result': '{count} 个结果',
'models.data.card': '模型简介', 'models.data.card': '模型简介',
'models.available.files': '可用文件', 'models.available.files': '可用文件',
'models.viewin.hf': '在 Hugging Face 中查看', 'models.viewin.hf': '在 Hugging Face 中查看',
'models.architecture': '架构' 'models.architecture': '架构',
'models.search.noresult': '未找到相关模型',
'models.search.nofiles': '无可用文件',
'models.search.networkerror': '网络连接异常!',
'models.search.hfvisit': '请确保您可以访问'
}; };
+4 -3
View File
@@ -134,6 +134,7 @@ export async function queryHuggingfaceModels(
search: { search: {
query: string; query: string;
tags: string[]; tags: string[];
sort?: string;
task?: PipelineType; task?: PipelineType;
}; };
}, },
@@ -143,16 +144,15 @@ export async function queryHuggingfaceModels(
for await (const model of listModels({ for await (const model of listModels({
...params, ...params,
...options, ...options,
limit: 500, limit: 100,
additionalFields: ['sha'], additionalFields: ['sha'],
fetch(url: string, config: any) { fetch(url: string, config: any) {
try { try {
return fetch(url, { return fetch(`${url}&sort=${params.search.sort}`, {
...config, ...config,
signal: options.signal signal: options.signal
}); });
} catch (error) { } catch (error) {
console.log('queryHuggingfaceModels error===', error);
// ignore // ignore
return []; return [];
} }
@@ -170,6 +170,7 @@ export async function queryHuggingfaceModelFiles(
const result = []; const result = [];
for await (const fileInfo of listFiles({ for await (const fileInfo of listFiles({
...params, ...params,
recursive: true,
fetch(url: string, config: any) { fetch(url: string, config: any) {
try { try {
return fetch(url, { return fetch(url, {
+11 -10
View File
@@ -1,5 +1,4 @@
import { convertFileSize } from '@/utils'; import { convertFileSize } from '@/utils';
import { SearchOutlined } from '@ant-design/icons';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { Col, Empty, Row, Select, Space, Spin, Tag } from 'antd'; import { Col, Empty, Row, Select, Space, Spin, Tag } from 'antd';
import classNames from 'classnames'; import classNames from 'classnames';
@@ -62,7 +61,11 @@ const HFModelFile: React.FC<HFModelFileProps> = (props) => {
signal: axiosTokenRef.current.signal signal: axiosTokenRef.current.signal
} }
); );
const list = _.filter(res, (file: any) => { const fileList = _.filter(res, (file: any) => {
return file.type === 'file';
});
const list = _.filter(fileList, (file: any) => {
return _.endsWith(file.path, '.gguf') || _.includes(file.path, '.gguf'); return _.endsWith(file.path, '.gguf') || _.includes(file.path, '.gguf');
}); });
const sortList = _.sortBy(list, (item: any) => { const sortList = _.sortBy(list, (item: any) => {
@@ -182,16 +185,14 @@ const HFModelFile: React.FC<HFModelFileProps> = (props) => {
})} })}
</Row> </Row>
) : ( ) : (
!dataSource.loading && ( !dataSource.loading &&
!loadingModel && (
<Empty <Empty
imageStyle={{ height: 'auto', marginTop: '20px' }} imageStyle={{ height: 'auto', marginTop: '20px' }}
image={ image={Empty.PRESENTED_IMAGE_SIMPLE}
<SearchOutlined description={intl.formatMessage({
className="font-size-16" id: 'models.search.nofiles'
style={{ color: 'var(--ant-color-text-tertiary)' }} })}
></SearchOutlined>
}
description="No files found"
/> />
) )
)} )}
+3 -2
View File
@@ -19,8 +19,9 @@ const ModelCard: React.FC<{
repo: string; repo: string;
onCollapse: (flag: boolean) => void; onCollapse: (flag: boolean) => void;
collapsed: boolean; collapsed: boolean;
loadingModel?: boolean;
}> = (props) => { }> = (props) => {
const { repo, onCollapse, collapsed } = props; const { repo, onCollapse, collapsed, loadingModel } = props;
const intl = useIntl(); const intl = useIntl();
const requestSource = useRequestToken(); const requestSource = useRequestToken();
const [modelData, setModelData] = useState<any>({}); const [modelData, setModelData] = useState<any>({});
@@ -138,7 +139,7 @@ const ModelCard: React.FC<{
> >
<span className="mkd-title" onClick={handleCollapse}> <span className="mkd-title" onClick={handleCollapse}>
<span> <span>
<FileTextOutlined className="m-r-5" /> README.md <FileTextOutlined className="m-r-2" /> README.md
</span> </span>
<span> <span>
{collapsed ? <DownOutlined /> : <RightOutlined />} {collapsed ? <DownOutlined /> : <RightOutlined />}
+46 -51
View File
@@ -1,11 +1,10 @@
import IconFont from '@/components/icon-font';
import { BulbOutlined } from '@ant-design/icons'; import { BulbOutlined } from '@ant-design/icons';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { Button, Input, Select } from 'antd'; import { Button, Input, Select } from 'antd';
import _ from 'lodash'; import _ from 'lodash';
import React, { useCallback, useEffect, useRef, useState } from 'react'; import React, { useCallback, useEffect, useRef, useState } from 'react';
import { queryHuggingfaceModels } from '../apis'; import { queryHuggingfaceModels } from '../apis';
import { modelSourceMap, ollamaModelOptions } from '../config'; import { ModelSortType, modelSourceMap, ollamaModelOptions } from '../config';
import SearchStyle from '../style/search-result.less'; import SearchStyle from '../style/search-result.less';
import SearchInput from './search-input'; import SearchInput from './search-input';
import SearchResult from './search-result'; import SearchResult from './search-result';
@@ -17,21 +16,6 @@ interface SearchInputProps {
onSelectModel: (model: any) => void; onSelectModel: (model: any) => void;
} }
const sourceList = [
{
label: (
<IconFont type="icon-huggingface" className="font-size-14"></IconFont>
),
value: 'huggingface',
key: 'huggingface'
},
{
label: <IconFont type="icon-ollama" className="font-size-14"></IconFont>,
value: 'ollama_library',
key: 'ollama_library'
}
];
const SearchModel: React.FC<SearchInputProps> = (props) => { const SearchModel: React.FC<SearchInputProps> = (props) => {
console.log('SearchModel======'); console.log('SearchModel======');
const intl = useIntl(); const intl = useIntl();
@@ -39,24 +23,35 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
const [dataSource, setDataSource] = useState<{ const [dataSource, setDataSource] = useState<{
repoOptions: any[]; repoOptions: any[];
loading: boolean; loading: boolean;
networkError: boolean;
sortType: string;
}>({ }>({
repoOptions: [], repoOptions: [],
loading: false loading: false,
networkError: false,
sortType: ModelSortType.trendingScore
}); });
const [current, setCurrent] = useState<string>(''); const [current, setCurrent] = useState<string>('');
const [sortType, setSortType] = useState<string>('downloads');
const cacheRepoOptions = useRef<any[]>([]); const cacheRepoOptions = useRef<any[]>([]);
const axiosTokenRef = useRef<any>(null); const axiosTokenRef = useRef<any>(null);
const customOllamaModelRef = useRef<any>(null); const customOllamaModelRef = useRef<any>(null);
const searchInputRef = useRef<any>('');
const modelFilesSortOptions = useRef<any[]>([ const modelFilesSortOptions = useRef<any[]>([
{ label: intl.formatMessage({ id: 'models.sort.likes' }), value: 'likes' }, {
label: intl.formatMessage({ id: 'models.sort.trending' }),
value: ModelSortType.trendingScore
},
{
label: intl.formatMessage({ id: 'models.sort.likes' }),
value: ModelSortType.likes
},
{ {
label: intl.formatMessage({ id: 'models.sort.downloads' }), label: intl.formatMessage({ id: 'models.sort.downloads' }),
value: 'downloads' value: ModelSortType.downloads
}, },
{ {
label: intl.formatMessage({ id: 'models.sort.updated' }), label: intl.formatMessage({ id: 'models.sort.updated' }),
value: 'updatedAt' value: ModelSortType.lastModified
} }
]); ]);
@@ -66,7 +61,7 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
}, []); }, []);
const handleOnSearchRepo = useCallback( const handleOnSearchRepo = useCallback(
async (text: string) => { async (sortType?: string) => {
axiosTokenRef.current?.abort?.(); axiosTokenRef.current?.abort?.();
axiosTokenRef.current = new AbortController(); axiosTokenRef.current = new AbortController();
if (dataSource.loading) return; if (dataSource.loading) return;
@@ -79,7 +74,8 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
cacheRepoOptions.current = []; cacheRepoOptions.current = [];
const params = { const params = {
search: { search: {
query: text, query: searchInputRef.current || '',
sort: sortType || dataSource.sortType,
tags: ['gguf'] tags: ['gguf']
} }
}; };
@@ -93,22 +89,22 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
label: item.name label: item.name
}; };
}); });
const sortedList = _.sortBy(
list, cacheRepoOptions.current = list;
(item: any) => item[sortType]
).reverse();
cacheRepoOptions.current = sortedList;
setDataSource({ setDataSource({
repoOptions: sortedList, repoOptions: list,
loading: false loading: false,
networkError: false,
sortType: sortType || dataSource.sortType
}); });
setLoadingModel?.(false); setLoadingModel?.(false);
handleOnSelectModel(sortedList[0]); handleOnSelectModel(list[0]);
} catch (error) { } catch (error: any) {
console.log('queryHuggingfaceModels error===', error);
setDataSource({ setDataSource({
repoOptions: [], repoOptions: [],
loading: false loading: false,
sortType: sortType || dataSource.sortType,
networkError: error?.message === 'Failed to fetch'
}); });
setLoadingModel?.(false); setLoadingModel?.(false);
handleOnSelectModel({}); handleOnSelectModel({});
@@ -120,8 +116,8 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
const handlerSearchModels = useCallback( const handlerSearchModels = useCallback(
async (e: any) => { async (e: any) => {
const text = e.target.value; searchInputRef.current = e.target.value;
handleOnSearchRepo(text); handleOnSearchRepo();
}, },
[handleOnSearchRepo] [handleOnSearchRepo]
); );
@@ -132,12 +128,14 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
!cacheRepoOptions.current.length && !cacheRepoOptions.current.length &&
modelSource === modelSourceMap.huggingface_value modelSource === modelSourceMap.huggingface_value
) { ) {
handleOnSearchRepo(''); handleOnSearchRepo();
} }
if (modelSourceMap.ollama_library_value === modelSource) { if (modelSourceMap.ollama_library_value === modelSource) {
setDataSource({ setDataSource({
repoOptions: ollamaModelOptions, repoOptions: ollamaModelOptions,
loading: false loading: false,
networkError: false,
sortType: dataSource.sortType
}); });
cacheRepoOptions.current = ollamaModelOptions; cacheRepoOptions.current = ollamaModelOptions;
handleOnSelectModel(ollamaModelOptions[0]); handleOnSelectModel(ollamaModelOptions[0]);
@@ -151,7 +149,9 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
}); });
setDataSource({ setDataSource({
repoOptions: list, repoOptions: list,
loading: false loading: false,
networkError: false,
sortType: dataSource.sortType
}); });
}; };
@@ -164,7 +164,9 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
onSourceChange?.(source); onSourceChange?.(source);
setDataSource({ setDataSource({
repoOptions: [], repoOptions: [],
loading: false loading: false,
networkError: false,
sortType: dataSource.sortType
}); });
cacheRepoOptions.current = []; cacheRepoOptions.current = [];
}; };
@@ -186,15 +188,7 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
}; };
const handleSortChange = (value: string) => { const handleSortChange = (value: string) => {
const sortedList = _.sortBy( handleOnSearchRepo(value);
dataSource.repoOptions,
(item: any) => item[value]
).reverse();
setSortType(value);
setDataSource({
repoOptions: sortedList,
loading: false
});
}; };
const renderHFSearch = () => { const renderHFSearch = () => {
return ( return (
@@ -210,7 +204,7 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
</span> </span>
</span> </span>
<Select <Select
value={sortType} value={dataSource.sortType}
onChange={handleSortChange} onChange={handleSortChange}
labelRender={({ label }) => { labelRender={({ label }) => {
return ( return (
@@ -275,6 +269,7 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
<SearchResult <SearchResult
loading={dataSource.loading} loading={dataSource.loading}
resultList={dataSource.repoOptions} resultList={dataSource.repoOptions}
networkError={dataSource.networkError}
current={current} current={current}
source={modelSource} source={modelSource}
onSelect={handleOnSelectModel} onSelect={handleOnSelectModel}
+58 -14
View File
@@ -1,5 +1,7 @@
import IconFont from '@/components/icon-font';
import { SearchOutlined } from '@ant-design/icons'; import { SearchOutlined } from '@ant-design/icons';
import { Col, Empty, Row, Spin } from 'antd'; import { useIntl } from '@umijs/max';
import { Button, Col, Empty, Row, Spin } from 'antd';
import React from 'react'; import React from 'react';
import SimpleBar from 'simplebar-react'; import SimpleBar from 'simplebar-react';
import 'simplebar-react/dist/simplebar.min.css'; import 'simplebar-react/dist/simplebar.min.css';
@@ -13,11 +15,13 @@ interface SearchResultProps {
source?: string; source?: string;
style?: React.CSSProperties; style?: React.CSSProperties;
loading?: boolean; loading?: boolean;
networkError?: boolean;
} }
const SearchResult: React.FC<SearchResultProps> = (props) => { const SearchResult: React.FC<SearchResultProps> = (props) => {
console.log('SearchResult======'); console.log('SearchResult======');
const { resultList, onSelect, source } = props; const { resultList, onSelect, source, networkError } = props;
const intl = useIntl();
const handleSelect = (e: any, item: any) => { const handleSelect = (e: any, item: any) => {
e.stopPropagation(); e.stopPropagation();
@@ -29,6 +33,57 @@ const SearchResult: React.FC<SearchResultProps> = (props) => {
onSelect?.(item); onSelect?.(item);
} }
}; };
const renderEmpty = () => {
if (networkError) {
return (
<Empty
imageStyle={{ height: 'auto', marginTop: '20px' }}
image={
<IconFont
type="icon-networkerror"
style={{
color: 'var(--ant-color-text-tertiary)',
fontSize: '66px'
}}
></IconFont>
}
description={
<div className="flex-column gap-5">
<span>
{intl.formatMessage({ id: 'models.search.networkerror' })}
</span>
<span>
<span>
{intl.formatMessage({ id: 'models.search.hfvisit' })}
</span>
<Button
type="link"
size="small"
href="https://huggingface.co/"
target="_blank"
>
Hugging Face
</Button>
</span>
</div>
}
/>
);
}
return (
<Empty
imageStyle={{ height: 'auto', marginTop: '20px' }}
image={
<SearchOutlined
className="font-size-16"
style={{ color: 'var(--ant-color-text-tertiary)' }}
></SearchOutlined>
}
description={intl.formatMessage({ id: 'models.search.noresult' })}
/>
);
};
return ( return (
<SimpleBar style={{ height: 'calc(100vh - 194px)' }}> <SimpleBar style={{ height: 'calc(100vh - 194px)' }}>
<div style={{ ...props.style }} className="search-result-wrap"> <div style={{ ...props.style }} className="search-result-wrap">
@@ -58,18 +113,7 @@ const SearchResult: React.FC<SearchResultProps> = (props) => {
))} ))}
</Row> </Row>
) : ( ) : (
!props.loading && ( !props.loading && renderEmpty()
<Empty
imageStyle={{ height: 'auto', marginTop: '20px' }}
image={
<SearchOutlined
className="font-size-16"
style={{ color: 'var(--ant-color-text-tertiary)' }}
></SearchOutlined>
}
description="No models found"
/>
)
)} )}
</div> </div>
</Spin> </Spin>
+6 -6
View File
@@ -141,9 +141,9 @@ export const ActionList = [
} }
]; ];
export const modelFilesSortOptions = [ export const ModelSortType = {
// { label: 'Trending', value: 'trendingScore' }, trendingScore: 'trendingScore',
{ label: 'models.sort.likes', value: 'likes' }, likes: 'likes',
{ label: 'models.sort.downloads', value: 'downloads' }, downloads: 'downloads',
{ label: 'models.sort.updated', value: 'updatedAt' } lastModified: 'lastModified'
]; };
@@ -6,6 +6,7 @@
padding: 10px; padding: 10px;
border: 1px solid var(--ant-color-border); border: 1px solid var(--ant-color-border);
border-radius: var(--border-radius-base); border-radius: var(--border-radius-base);
cursor: pointer;
&:hover { &:hover {
background-color: var(--color-fill-sider); background-color: var(--color-fill-sider);
@@ -6,6 +6,7 @@
border: 1px solid var(--ant-color-border); border: 1px solid var(--ant-color-border);
border-radius: var(--border-radius-base); border-radius: var(--border-radius-base);
padding: 12px; padding: 12px;
cursor: pointer;
&:hover { &:hover {
background-color: var(--color-fill-sider); background-color: var(--color-fill-sider);