import { BulbOutlined, QuestionCircleOutlined } from '@ant-design/icons'; import { useIntl } from '@umijs/max'; import { Checkbox, Select, Tooltip } from 'antd'; import _ from 'lodash'; import React, { useCallback, useEffect, useRef, useState } from 'react'; import { queryHuggingfaceModels, queryModelScopeModels } from '../apis'; import { HuggingFaceTaskMap, ModelScopeSortType, ModelSortType, ModelscopeTaskMap, modelSourceMap, ollamaModelOptions } from '../config'; import SearchStyle from '../style/search-result.less'; import SearchInput from './search-input'; import SearchResult from './search-result'; interface SearchInputProps { modelSource: string; setLoadingModel?: (flag: boolean) => void; onSourceChange?: (source: string) => void; onSelectModel: (model: any) => void; } const SearchModel: React.FC = (props) => { const intl = useIntl(); const { modelSource, setLoadingModel, onSelectModel } = props; const [dataSource, setDataSource] = useState<{ repoOptions: any[]; loading: boolean; networkError: boolean; sortType: string; }>({ repoOptions: [], loading: false, networkError: false, sortType: ModelSortType.trendingScore }); const SUPPORTEDSOURCE = [ modelSourceMap.huggingface_value, modelSourceMap.modelscope_value ]; const [current, setCurrent] = useState(''); const cacheRepoOptions = useRef([]); const axiosTokenRef = useRef(null); const searchInputRef = useRef(''); const filterGGUFRef = useRef(); const filterTaskRef = useRef(''); const modelFilesSortOptions = useRef([ { 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' }), value: ModelSortType.downloads }, { label: intl.formatMessage({ id: 'models.sort.updated' }), value: ModelSortType.lastModified } ]); const handleOnSelectModel = useCallback((item: any) => { onSelectModel(item); setCurrent(item.id); }, []); // huggeface const getModelsFromHuggingface = useCallback(async (sort: string) => { try { const task: any = searchInputRef.current ? '' : 'text-generation'; const params = { search: { query: searchInputRef.current || '', sort: sort, tags: filterGGUFRef.current ? ['gguf'] : [], task: HuggingFaceTaskMap[filterTaskRef.current] || task } }; const data = await queryHuggingfaceModels(params, { signal: axiosTokenRef.current.signal }); let list = _.map(data || [], (item: any) => { return { ...item, value: item.name, label: item.name }; }); return list; } catch (error) { return []; } }, []); // modelscope const getModelsFromModelscope = useCallback(async (sort: string) => { try { const params = { Name: `${searchInputRef.current}`, tags: filterGGUFRef.current ? ['gguf'] : [], tasks: filterTaskRef.current ? ([ModelscopeTaskMap[filterTaskRef.current]] as string[]) : [], SortBy: ModelScopeSortType[sort] }; const data = await queryModelScopeModels(params, { signal: axiosTokenRef.current.signal }); let list = _.map(_.get(data, 'Data.Model.Models') || [], (item: any) => { return { path: item.Path, name: `${item.Path}/${item.Name}`, downloads: item.Downloads, id: `${item.Path}/${item.Name}`, updatedAt: item.LastUpdatedTime * 1000, likes: item.Stars, value: item.Name, label: item.Name, revision: item.Revision, task: item.Tasks?.map((sItem: any) => sItem.Name).join(',') }; }); return list; } catch (error) { return []; } }, []); const handleOnSearchRepo = useCallback( async (sortType?: string) => { if (!SUPPORTEDSOURCE.includes(modelSource)) { return; } axiosTokenRef.current?.abort?.(); axiosTokenRef.current = new AbortController(); const sort = sortType ?? dataSource.sortType; try { setDataSource((pre) => { pre.loading = true; return { ...pre }; }); setLoadingModel?.(true); cacheRepoOptions.current = []; let list: any[] = []; if (modelSource === modelSourceMap.huggingface_value) { list = await getModelsFromHuggingface(sort); } else if (modelSource === modelSourceMap.modelscope_value) { list = await getModelsFromModelscope(sort); } cacheRepoOptions.current = list; setDataSource({ repoOptions: list, loading: false, networkError: false, sortType: sort }); setLoadingModel?.(false); handleOnSelectModel(list[0]); } catch (error: any) { setDataSource({ repoOptions: [], loading: false, sortType: sort, networkError: error?.message === 'Failed to fetch' }); setLoadingModel?.(false); handleOnSelectModel({}); cacheRepoOptions.current = []; } }, [dataSource] ); const handleSearchInputChange = useCallback((e: any) => { searchInputRef.current = e.target.value; console.log('change:', searchInputRef.current); }, []); const handlerSearchModels = useCallback( async (e: any) => { setTimeout(() => { handleOnSearchRepo(); }, 100); }, [handleOnSearchRepo] ); const handleOnOpen = () => { if ( !dataSource.repoOptions.length && !cacheRepoOptions.current.length && SUPPORTEDSOURCE.includes(modelSource) ) { handleOnSearchRepo(); } if (modelSourceMap.ollama_library_value === modelSource) { setDataSource({ repoOptions: ollamaModelOptions, loading: false, networkError: false, sortType: dataSource.sortType }); cacheRepoOptions.current = ollamaModelOptions; handleOnSelectModel(ollamaModelOptions[0]); } }; const handleSortChange = (value: string) => { handleOnSearchRepo(value || ''); }; const handleFilterGGUFChange = (e: any) => { filterGGUFRef.current = e.target.checked; handleOnSearchRepo(); }; const handleFilterTaskChange = useCallback((value: string) => { filterTaskRef.current = value; handleOnSearchRepo(); }, []); const renderHFSearch = () => { return ( <>
{intl.formatMessage({ id: 'models.form.search.gguftips' })}
{intl.formatMessage( { id: 'models.search.result' }, { count: dataSource.repoOptions.length } )}
  • {intl.formatMessage({ id: 'models.search.gguf.tips' })}
  • {intl.formatMessage({ id: 'models.search.vllm.tips' })}
  • {intl.formatMessage({ id: 'models.search.voxbox.tips' })}
  • } > GGUF
    ); }; useEffect(() => { handleOnOpen(); console.log('SearchModel useEffect', modelSource); }, [modelSource]); useEffect(() => { return () => { axiosTokenRef.current?.abort?.(); }; }, []); return (
    {SUPPORTEDSOURCE.includes(modelSource) ? ( renderHFSearch() ) : (
    {intl.formatMessage( { id: 'model.form.ollamatips' }, { name: intl.formatMessage({ id: 'model.form.ollama.model' }) } )}
    )}
    { }
    ); }; export default React.memo(SearchModel);