From abb87e71edbbafad949dc96996139ee27e458e77 Mon Sep 17 00:00:00 2001 From: jialin Date: Wed, 21 Aug 2024 18:08:19 +0800 Subject: [PATCH] chore: add cancel query model --- src/components/highlight-code/code-viewer.tsx | 21 +- src/components/highlight-code/index.tsx | 5 +- src/components/status-tag/copy.less | 8 + src/components/status-tag/index.less | 10 + src/components/status-tag/index.tsx | 24 ++- src/config/hotkeys.ts | 4 +- src/global.less | 1 + src/layouts/Exception.tsx | 3 +- src/locales/en-US/models.ts | 12 +- src/locales/zh-CN/models.ts | 12 +- src/pages/llmodels/apis/index.ts | 53 ++++- .../llmodels/components/column-wrapper.tsx | 11 +- .../llmodels/components/deploy-modal.tsx | 23 ++- .../llmodels/components/hf-model-file.tsx | 191 +++++++++++------- .../llmodels/components/hf-model-item.tsx | 4 +- src/pages/llmodels/components/model-card.tsx | 151 +++++++++++--- .../llmodels/components/search-model.tsx | 40 +++- src/pages/llmodels/components/table-list.tsx | 60 +++++- .../llmodels/components/title-wrapper.tsx | 6 +- src/pages/llmodels/config/index.ts | 40 ++-- src/pages/llmodels/index.tsx | 1 + src/pages/llmodels/style/model-card.less | 19 +- src/pages/llmodels/style/title-wrapper.less | 1 + 23 files changed, 522 insertions(+), 178 deletions(-) diff --git a/src/components/highlight-code/code-viewer.tsx b/src/components/highlight-code/code-viewer.tsx index 174d2a4c..fdfd7c75 100644 --- a/src/components/highlight-code/code-viewer.tsx +++ b/src/components/highlight-code/code-viewer.tsx @@ -1,5 +1,6 @@ import hljs from 'highlight.js'; import 'highlight.js/styles/atom-one-dark.css'; +import { memo, useMemo } from 'react'; import CopyButton from '../copy-button'; import { escapeHtml } from './utils'; @@ -19,7 +20,7 @@ const CodeViewer: React.FC = (props) => { copyable = true } = props || {}; - const renderCode = () => { + const highlightedCode = useMemo(() => { const autodetectLang = autodetect && !lang; const cannotDetectLanguage = !autodetectLang && !hljs.getLanguage(lang); let className = ''; @@ -52,9 +53,7 @@ const CodeViewer: React.FC = (props) => { value: result.value, className: className }; - }; - - const highlightedCode = renderCode(); + }, [code, lang, autodetect, ignoreIllegals]); return (
@@ -64,13 +63,15 @@ const CodeViewer: React.FC = (props) => {
           __html: highlightedCode.value
         }}
       >
-      
+      {copyable && (
+        
+      )}
     
); }; -export default CodeViewer; +export default memo(CodeViewer); diff --git a/src/components/highlight-code/index.tsx b/src/components/highlight-code/index.tsx index 1ddebefc..824b1c9a 100644 --- a/src/components/highlight-code/index.tsx +++ b/src/components/highlight-code/index.tsx @@ -4,12 +4,13 @@ import './style.less'; const HighlightCode: React.FC<{ code: string; lang?: string; + copyable?: boolean; }> = (props) => { - const { code, lang = 'bash' } = props; + const { code, lang = 'bash', copyable = true } = props; return (
- +
); }; diff --git a/src/components/status-tag/copy.less b/src/components/status-tag/copy.less index 8b54e76b..ba7abab8 100644 --- a/src/components/status-tag/copy.less +++ b/src/components/status-tag/copy.less @@ -16,5 +16,13 @@ top: 0; border-radius: 0 8px 0 4px; } + + .simplebar-scrollbar::before { + background: var(--color-scroll-bg); + } + + .simplebar-content { + width: max-content; + } } } diff --git a/src/components/status-tag/index.less b/src/components/status-tag/index.less index eefa8cf6..4fb258e1 100644 --- a/src/components/status-tag/index.less +++ b/src/components/status-tag/index.less @@ -11,6 +11,16 @@ font-size: var(--font-size-base); overflow: hidden; + .txt { + display: flex; + align-items: center; + height: 20px; + + &.err { + cursor: default; + } + } + &.download { border: 1px solid #93ecc5 !important; } diff --git a/src/components/status-tag/index.tsx b/src/components/status-tag/index.tsx index ae0844dd..bcabe3ff 100644 --- a/src/components/status-tag/index.tsx +++ b/src/components/status-tag/index.tsx @@ -4,6 +4,8 @@ import { InfoCircleOutlined } from '@ant-design/icons'; import { Tooltip } from 'antd'; import classNames from 'classnames'; import { useEffect, useMemo, useState } from 'react'; +import SimpleBar from 'simplebar-react'; +import 'simplebar-react/dist/simplebar.min.css'; import CopyButton from '../copy-button'; import CopyStyle from './copy.less'; import './index.less'; @@ -70,7 +72,14 @@ const StatusTag: React.FC = ({ size="small" > -
{statusValue.message}
+ + +
+ {statusValue.message} +
+
); }, [statusValue]); @@ -87,15 +96,18 @@ const StatusTag: React.FC = ({ {statusValue.message ? ( - - + + + + + {renderContent()} - {renderContent()} ) : ( - renderContent() + {renderContent()} )} ); diff --git a/src/config/hotkeys.ts b/src/config/hotkeys.ts index 3f92e0f8..e05010a8 100644 --- a/src/config/hotkeys.ts +++ b/src/config/hotkeys.ts @@ -11,5 +11,7 @@ export default { EDIT: ['ctrl+e', 'meta+e'], SEARCH: ['ctrl+f', 'meta+f'], RESET: ['ctrl+shift+r', 'meta+shift+r'], - INPUT: ['ctrl+k', 'meta+k'] + INPUT: ['ctrl+k', 'meta+k'], + NEW1: ['ctrl+1', 'meta+1'], + NEW2: ['ctrl+2', 'meta+2'] }; diff --git a/src/global.less b/src/global.less index 6cf8c010..d99ed5cc 100644 --- a/src/global.less +++ b/src/global.less @@ -12,6 +12,7 @@ html { --color-fill-1: var(--ant-color-fill-tertiary); // --color-fill-1: #fff; --ant-color-text: #000; + --color-scroll-bg: #d9d9d9; --color-fill-2: #fff; --color-fill-3: #f3f6fa; --color-logs-bg: #1e1e1e; diff --git a/src/layouts/Exception.tsx b/src/layouts/Exception.tsx index d4904dd0..de555935 100644 --- a/src/layouts/Exception.tsx +++ b/src/layouts/Exception.tsx @@ -1,6 +1,5 @@ // @ts-nocheck -// This file is generated by Umi automatically -// DO NOT CHANGE IT MANUALLY! + import { history, useIntl, type IRoute } from '@umijs/max'; import { Button, Result } from 'antd'; import React from 'react'; diff --git a/src/locales/en-US/models.ts b/src/locales/en-US/models.ts index 5e199bf7..3525b6ff 100644 --- a/src/locales/en-US/models.ts +++ b/src/locales/en-US/models.ts @@ -18,5 +18,15 @@ export default { 'model.deploy.sort': 'Sort', 'model.deploy.search.placeholder': 'Search models from Hugging Face', 'model.form.ollamatips': - 'Tip: The following are the preconfigured Ollama models in GPUStack. Please select the model you want, or directly enter the model you wish to deploy in the 【{name}】 input box on the right.' + 'Tip: The following are the preconfigured Ollama models in GPUStack. Please select the model you want, or directly enter the model you wish to deploy in the 【{name}】 input box on the right.', + 'models.sort.name': 'Name', + 'models.sort.size': 'Size', + 'models.sort.likes': 'Likes', + 'models.sort.downloads': 'Downloads', + 'models.sort.updated': 'Updated', + 'models.search.result': '{count} results', + 'models.data.card': 'Model Card', + 'models.available.files': 'Available Files', + 'models.viewin.hf': 'View in Hugging Face', + 'models.architecture': 'Architecture' }; diff --git a/src/locales/zh-CN/models.ts b/src/locales/zh-CN/models.ts index f97d99d5..a7697814 100644 --- a/src/locales/zh-CN/models.ts +++ b/src/locales/zh-CN/models.ts @@ -18,5 +18,15 @@ export default { 'model.deploy.sort': '排序', 'model.deploy.search.placeholder': '从 Hugging Face 搜索模型', 'model.form.ollamatips': - '提示:以下为 GPUStack 预设的 Ollama 模型,请选择你想要的模型或者直接在右侧表单 【{name}】 输入框中输入你要部署的模型。' + '提示:以下为 GPUStack 预设的 Ollama 模型,请选择你想要的模型或者直接在右侧表单 【{name}】 输入框中输入你要部署的模型。', + 'models.sort.name': '名称', + 'models.sort.size': '大小', + 'models.sort.likes': '喜欢', + 'models.sort.downloads': '下载', + 'models.sort.updated': '更新时间', + 'models.search.result': '{count} 个结果', + 'models.data.card': '模型简介', + 'models.available.files': '可用文件', + 'models.viewin.hf': '在 Hugging Face 中查看', + 'models.architecture': '架构' }; diff --git a/src/pages/llmodels/apis/index.ts b/src/pages/llmodels/apis/index.ts index 24028e5f..a9c901df 100644 --- a/src/pages/llmodels/apis/index.ts +++ b/src/pages/llmodels/apis/index.ts @@ -1,4 +1,4 @@ -import { listFiles, listModels } from '@huggingface/hub'; +import { downloadFile, listFiles, listModels } from '@huggingface/hub'; import { PipelineType } from '@huggingface/tasks'; import { request } from '@umijs/max'; import { @@ -119,9 +119,13 @@ export async function callHuggingfaceQuickSearch(params: any) { const HUGGINGFACE_API = 'https://huggingface.co/api/models'; -export async function queryHuggingfaceModelDetail(params: { repo: string }) { +export async function queryHuggingfaceModelDetail( + params: { repo: string }, + options?: any +) { return request(`${HUGGINGFACE_API}/${params.repo}`, { - method: 'GET' + method: 'GET', + cancelToken: options?.token }); } @@ -139,7 +143,7 @@ export async function queryHuggingfaceModels( for await (const model of listModels({ ...params, ...options, - limit: 100, + limit: 500, additionalFields: ['sha'], fetch(url: string, config: any) { try { @@ -148,6 +152,7 @@ export async function queryHuggingfaceModels( signal: options.signal }); } catch (error) { + console.log('queryHuggingfaceModels error===', error); // ignore return []; } @@ -158,12 +163,48 @@ export async function queryHuggingfaceModels( return result; } -export async function queryHuggingfaceModelFiles(params: { repo: string }) { +export async function queryHuggingfaceModelFiles( + params: { repo: string }, + options?: any +) { const result = []; for await (const fileInfo of listFiles({ - ...params + ...params, + fetch(url: string, config: any) { + try { + return fetch(url, { + ...config, + signal: options?.signal + }); + } catch (error) { + console.log('queryHuggingfaceModels error===', error); + // ignore + return []; + } + } })) { result.push(fileInfo); } return result; } + +export async function downloadModelFile( + params: { repo: string; revision: string; path: string }, + options?: any +) { + const { repo, revision, path } = params; + const res = await ( + await downloadFile({ + repo, + revision: revision, + path: path, + fetch(url: string, config: any) { + return fetch(url, { + ...config, + signal: options?.signal + }); + } + }) + )?.text(); + return res; +} diff --git a/src/pages/llmodels/components/column-wrapper.tsx b/src/pages/llmodels/components/column-wrapper.tsx index d92bedbb..368eebba 100644 --- a/src/pages/llmodels/components/column-wrapper.tsx +++ b/src/pages/llmodels/components/column-wrapper.tsx @@ -3,13 +3,16 @@ import SimpleBar from 'simplebar-react'; import 'simplebar-react/dist/simplebar.min.css'; import '../style/column-wrapper.less'; -const ColumnWrapper: React.FC = ({ children, footer }) => { +const ColumnWrapper: React.FC = ({ children, footer, height }) => { if (footer) { return (
{children} @@ -20,7 +23,9 @@ const ColumnWrapper: React.FC = ({ children, footer }) => { } return (
- {children} + + {children} +
); }; diff --git a/src/pages/llmodels/components/deploy-modal.tsx b/src/pages/llmodels/components/deploy-modal.tsx index db5d1124..72679448 100644 --- a/src/pages/llmodels/components/deploy-modal.tsx +++ b/src/pages/llmodels/components/deploy-modal.tsx @@ -3,7 +3,7 @@ import { PageActionType } from '@/config/types'; import { CloseOutlined } from '@ant-design/icons'; import { useIntl } from '@umijs/max'; import { Button, Drawer } from 'antd'; -import { useCallback, useEffect, useRef, useState } from 'react'; +import { memo, useCallback, useEffect, useRef, useState } from 'react'; import { modelSourceMap } from '../config'; import { FormData, ListItem } from '../config/types'; import ColumnWrapper from './column-wrapper'; @@ -38,6 +38,8 @@ const AddModal: React.FC = (props) => { const form = useRef({}); const intl = useIntl(); const [huggingfaceRepoId, setHuggingfaceRepoId] = useState(''); + const [collapsed, setCollapsed] = useState(false); + const [loadingModel, setLoadingModel] = useState(false); const handleSelectModelFile = useCallback((item: any) => { form.current?.setFieldValue?.('huggingface_filename', item.path); @@ -100,15 +102,22 @@ const AddModal: React.FC = (props) => { )} {props.source === modelSourceMap.huggingface_value && ( - + )} @@ -126,9 +135,11 @@ const AddModal: React.FC = (props) => { } > <> - - {intl.formatMessage({ id: 'models.form.configurations' })} - + {source === modelSourceMap.huggingface_value && ( + + {intl.formatMessage({ id: 'models.form.configurations' })} + + )} = (props) => { ); }; -export default AddModal; +export default memo(AddModal); diff --git a/src/pages/llmodels/components/hf-model-file.tsx b/src/pages/llmodels/components/hf-model-file.tsx index 4f817d28..c39630e8 100644 --- a/src/pages/llmodels/components/hf-model-file.tsx +++ b/src/pages/llmodels/components/hf-model-file.tsx @@ -1,10 +1,12 @@ import { convertFileSize } from '@/utils'; import { SearchOutlined } from '@ant-design/icons'; import { useIntl } from '@umijs/max'; -import { Col, Empty, Row, Space, Spin, Tag } from 'antd'; +import { Col, Empty, Row, Select, Space, Spin, Tag } from 'antd'; import classNames from 'classnames'; import _ from 'lodash'; -import { useEffect, useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; +import SimpleBar from 'simplebar-react'; +import 'simplebar-react/dist/simplebar.min.css'; import { queryHuggingfaceModelFiles } from '../apis'; import FileType from '../config/file-type'; import '../style/hf-model-file.less'; @@ -12,16 +14,31 @@ import TitleWrapper from './title-wrapper'; interface HFModelFileProps { repo: string; + collapsed?: boolean; + loadingModel?: boolean; onSelectFile?: (file: any) => void; } + const HFModelFile: React.FC = (props) => { + const { collapsed, loadingModel } = props; const intl = useIntl(); const [dataSource, setDataSource] = useState({ fileList: [], loading: false }); - + const [sortType, setSortType] = useState('size'); const [current, setCurrent] = useState(''); + const modelFilesSortOptions = useRef([ + { + label: intl.formatMessage({ id: 'models.sort.size' }), + value: 'size' + }, + { + label: intl.formatMessage({ id: 'models.sort.name' }), + value: 'name' + } + ]); + const axiosTokenRef = useRef(null); const handleSelectModelFile = (item: any) => { props.onSelectFile?.(item); @@ -34,14 +51,23 @@ const HFModelFile: React.FC = (props) => { handleSelectModelFile({}); return; } + axiosTokenRef.current?.abort?.(); + axiosTokenRef.current = new AbortController(); setDataSource({ ...dataSource, loading: true }); setCurrent(''); try { - const res = await queryHuggingfaceModelFiles({ repo: props.repo }); + const res = await queryHuggingfaceModelFiles( + { repo: props.repo }, + { + signal: axiosTokenRef.current.signal + } + ); const list = _.filter(res, (file: any) => { return _.endsWith(file.path, '.gguf') || _.includes(file.path, '.gguf'); }); - const sortList = _.sortBy(list, (item: any) => item.size); + const sortList = _.sortBy(list, (item: any) => { + return sortType === 'size' ? item.size : item.path; + }); setDataSource({ fileList: sortList, loading: false }); handleSelectModelFile(list[0]); } catch (error) { @@ -50,6 +76,14 @@ const HFModelFile: React.FC = (props) => { } }; + const handleSortChange = (value: string) => { + const list = _.sortBy(dataSource.fileList, (item: any) => { + return value === 'size' ? item.size : item.path; + }); + setSortType(value); + setDataSource({ ...dataSource, fileList: list }); + }; + const getModelQuantizationType = (item: any) => { const name = _.split(item.path, '.').slice(0, -1).join('.'); let quanType = _.toUpper(name.split('-').slice(-1)[0]); @@ -77,76 +111,93 @@ const HFModelFile: React.FC = (props) => { useEffect(() => { handleFetchModelFiles(); }, [props.repo]); + + useEffect(() => { + return () => { + axiosTokenRef.current?.abort?.(); + }; + }, []); return (
- Available Files ({dataSource.fileList.length || 0}) + + {intl.formatMessage({ id: 'models.available.files' })} ( + {dataSource.fileList.length || 0}) + + -
- - {dataSource.fileList.length ? ( - - {_.map(dataSource.fileList, (item: any) => { - return ( - -
handleSelectModelFile(item)} - onKeyDown={(e) => handleOnEnter(e, item)} - > -
{item.path}
- - {/* - {convertFileSize(item.size)} - */} - - - {convertFileSize(item.size)} - - - {getModelQuantizationType(item)} - -
- {/* */} + +
+ + {dataSource.fileList.length ? ( + + {_.map(dataSource.fileList, (item: any) => { + return ( + +
handleSelectModelFile(item)} + onKeyDown={(e) => handleOnEnter(e, item)} + > +
{item.path}
+ + + + {convertFileSize(item.size)} + + + {getModelQuantizationType(item)} + +
-
- - ); - })} - - ) : ( - !dataSource.loading && ( - - } - description="No files found" - /> - ) - )} - -
+ + ); + })} + + ) : ( + !dataSource.loading && ( + + } + description="No files found" + /> + ) + )} + +
+
); }; diff --git a/src/pages/llmodels/components/hf-model-item.tsx b/src/pages/llmodels/components/hf-model-item.tsx index 121f27fb..849bc962 100644 --- a/src/pages/llmodels/components/hf-model-item.tsx +++ b/src/pages/llmodels/components/hf-model-item.tsx @@ -40,7 +40,7 @@ const HFModelItem: React.FC = (props) => {
{props.source === modelSourceMap.huggingface_value ? ( - {props.task && ( + {/* {props.task && ( = (props) => { > {props.task} - )} + )} */} {dayjs().to( dayjs(dayjs(props.updatedAt).format('YYYY-MM-DD HH:mm:ss')) diff --git a/src/pages/llmodels/components/model-card.tsx b/src/pages/llmodels/components/model-card.tsx index 3fb998a9..02d0510e 100644 --- a/src/pages/llmodels/components/model-card.tsx +++ b/src/pages/llmodels/components/model-card.tsx @@ -1,20 +1,51 @@ +import HighlightCode from '@/components/highlight-code'; import IconFont from '@/components/icon-font'; -import { downloadFile } from '@huggingface/hub'; -import { Button, Empty, Tag } from 'antd'; -import React, { useEffect, useState } from 'react'; -import { queryHuggingfaceModelDetail } from '../apis'; +import useRequestToken from '@/hooks/use-request-token'; +import { + DownOutlined, + FileTextOutlined, + RightOutlined +} from '@ant-design/icons'; +import { useIntl } from '@umijs/max'; +import { Button, Empty, Tag, Tooltip } from 'antd'; +import React, { useCallback, useEffect, useRef, useState } from 'react'; +import SimpleBar from 'simplebar-react'; +import 'simplebar-react/dist/simplebar.min.css'; +import { downloadModelFile, queryHuggingfaceModelDetail } from '../apis'; import '../style/model-card.less'; import TitleWrapper from './title-wrapper'; -const ModelCard: React.FC<{ repo: string }> = (props) => { - const { repo } = props; +const ModelCard: React.FC<{ + repo: string; + onCollapse: (flag: boolean) => void; + collapsed: boolean; +}> = (props) => { + const { repo, onCollapse, collapsed } = props; + const intl = useIntl(); + const requestSource = useRequestToken(); const [modelData, setModelData] = useState({}); + const [readmeText, setReadmeText] = useState(null); + const requestToken = useRef(null); + const axiosTokenRef = useRef(null); const loadFile = async (repo: string, sha: string) => { - const res = await ( - await downloadFile({ repo, revision: sha, path: 'README.md' }) - )?.text(); - return res; + try { + axiosTokenRef.current?.abort?.(); + axiosTokenRef.current = new AbortController(); + const res = await downloadModelFile( + { + repo, + revision: sha, + path: 'README.md' + }, + { + signal: axiosTokenRef.current.signal + } + ); + return res || ''; + } catch (error) { + return ''; + } }; const getModelCardData = async () => { @@ -22,42 +53,110 @@ const ModelCard: React.FC<{ repo: string }> = (props) => { setModelData(null); return; } + requestToken.current?.cancel?.(); + requestToken.current = requestSource(); try { - const res = await queryHuggingfaceModelDetail({ repo }); + const [modelcard, readme] = await Promise.all([ + queryHuggingfaceModelDetail( + { repo }, + { + token: requestToken.current.token + } + ), + loadFile(repo, 'main') + ]); - setModelData(res); + setModelData(modelcard); + setReadmeText(readme); } catch (error) { setModelData({}); } }; + const handleCollapse = useCallback(() => { + onCollapse(!collapsed); + }, [collapsed]); + useEffect(() => { getModelCardData(); }, [repo]); + useEffect(() => { + if (!readmeText) { + onCollapse(false); + } + }, [readmeText]); + + useEffect(() => { + return () => { + requestToken.current?.cancel?.(); + axiosTokenRef.current?.abort?.(); + }; + }, []); + return ( <> - Model Card + {intl.formatMessage({ id: 'models.data.card' })}
{modelData ? (
-
{modelData.id}
+
+ {modelData.id}{' '} + + + +
- - Architecture: - {modelData.config?.model_type} - - + {modelData.config?.model_type && ( + + + + {intl.formatMessage({ id: 'models.architecture' })}: + + {modelData.config?.model_type} + + + )}
+ {readmeText && ( +
+ + + README.md + + + {collapsed ? : } + + + + + +
+ )}
) : ( diff --git a/src/pages/llmodels/components/search-model.tsx b/src/pages/llmodels/components/search-model.tsx index b93260b0..2cbf57c3 100644 --- a/src/pages/llmodels/components/search-model.tsx +++ b/src/pages/llmodels/components/search-model.tsx @@ -5,17 +5,14 @@ import { Button, Input, Select } from 'antd'; import _ from 'lodash'; import React, { useCallback, useEffect, useRef, useState } from 'react'; import { queryHuggingfaceModels } from '../apis'; -import { - modelFilesSortOptions, - modelSourceMap, - ollamaModelOptions -} from '../config'; +import { 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; } @@ -38,7 +35,7 @@ const sourceList = [ const SearchModel: React.FC = (props) => { console.log('SearchModel======'); const intl = useIntl(); - const { modelSource, onSourceChange, onSelectModel } = props; + const { modelSource, setLoadingModel, onSourceChange, onSelectModel } = props; const [dataSource, setDataSource] = useState<{ repoOptions: any[]; loading: boolean; @@ -51,6 +48,17 @@ const SearchModel: React.FC = (props) => { const cacheRepoOptions = useRef([]); const axiosTokenRef = useRef(null); const customOllamaModelRef = useRef(null); + const modelFilesSortOptions = useRef([ + { label: intl.formatMessage({ id: 'models.sort.likes' }), value: 'likes' }, + { + label: intl.formatMessage({ id: 'models.sort.downloads' }), + value: 'downloads' + }, + { + label: intl.formatMessage({ id: 'models.sort.updated' }), + value: 'updatedAt' + } + ]); const handleOnSelectModel = useCallback((item: any) => { onSelectModel(item); @@ -67,6 +75,7 @@ const SearchModel: React.FC = (props) => { pre.loading = true; return { ...pre }; }); + setLoadingModel?.(true); cacheRepoOptions.current = []; const params = { search: { @@ -93,12 +102,15 @@ const SearchModel: React.FC = (props) => { repoOptions: sortedList, loading: false }); + setLoadingModel?.(false); handleOnSelectModel(sortedList[0]); } catch (error) { + console.log('queryHuggingfaceModels error===', error); setDataSource({ repoOptions: [], loading: false }); + setLoadingModel?.(false); handleOnSelectModel({}); cacheRepoOptions.current = []; } @@ -184,15 +196,18 @@ const SearchModel: React.FC = (props) => { loading: false }); }; - const renderHFSearch = () => { return ( <>
- {dataSource.repoOptions.length} - results + + {intl.formatMessage( + { id: 'models.search.result' }, + { count: dataSource.repoOptions.length } + )} + @@ -233,10 +248,13 @@ const SearchModel: React.FC = (props) => { useEffect(() => { handleOnOpen(); console.log('SearchModel useEffect', modelSource); + }, [modelSource]); + + useEffect(() => { return () => { axiosTokenRef.current?.abort?.(); }; - }, [modelSource]); + }, []); return (
diff --git a/src/pages/llmodels/components/table-list.tsx b/src/pages/llmodels/components/table-list.tsx index ea024c00..2125c3eb 100644 --- a/src/pages/llmodels/components/table-list.tsx +++ b/src/pages/llmodels/components/table-list.tsx @@ -5,11 +5,12 @@ import PageTools from '@/components/page-tools'; import SealTable from '@/components/seal-table'; import SealColumn from '@/components/seal-table/components/seal-column'; import { PageAction } from '@/config'; +import HotKeys from '@/config/hotkeys'; import useExpandedRowKeys from '@/hooks/use-expanded-row-keys'; import useTableRowSelection from '@/hooks/use-table-row-selection'; import useTableSort from '@/hooks/use-table-sort'; import ViewCodeModal from '@/pages/playground/components/view-code-modal'; -import { handleBatchRequest } from '@/utils'; +import { handleBatchRequest, platformCall } from '@/utils'; import { DeleteOutlined, DownOutlined, @@ -23,6 +24,7 @@ import { Button, Dropdown, Input, Space, Tag, message } from 'antd'; import dayjs from 'dayjs'; import _ from 'lodash'; import { memo, useCallback, useRef, useState } from 'react'; +import { useHotkeys } from 'react-hotkeys-hook'; import { MODELS_API, MODEL_INSTANCE_API, @@ -64,6 +66,7 @@ const Models: React.FC = ({ total }) => { console.log('model list====2'); + const platform = platformCall(); const access = useAccess(); const intl = useIntl(); const navigate = useNavigate(); @@ -77,7 +80,6 @@ const Models: React.FC = ({ params: {}, show: false }); - const [openViewCodeModal, setOpenViewCodeModal] = useState(false); const [openLogModal, setOpenLogModal] = useState(false); const [openAddModal, setOpenAddModal] = useState(false); const [openDeployModal, setOpenDeployModal] = useState({ @@ -92,9 +94,46 @@ const Models: React.FC = ({ const [currentInstanceUrl, setCurrentInstanceUrl] = useState(''); const modalRef = useRef(null); + useHotkeys( + HotKeys.NEW1.join(','), + () => { + setOpenDeployModal({ + show: true, + width: 'calc(100vw - 220px)', + source: modelSourceMap.huggingface_value + }); + }, + { preventDefault: true } + ); + + useHotkeys( + HotKeys.NEW2.join(','), + () => { + setOpenDeployModal({ + show: true, + width: 600, + source: modelSourceMap.ollama_library_value + }); + }, + { preventDefault: true } + ); + const sourceOptions = [ { - label: 'Hugging Face', + label: ( + + Hugging Face + + {platform.isMac ? ( + <> + + 1 + + ) : ( + <>CTRL + 1 + )} + + + ), value: modelSourceMap.huggingface_value, key: 'huggingface', icon: , @@ -107,7 +146,20 @@ const Models: React.FC = ({ } }, { - label: 'Ollama Library', + label: ( + + Ollama Library + + {platform.isMac ? ( + <> + + 2 + + ) : ( + <>CTRL + 2 + )} + + + ), value: modelSourceMap.ollama_library_value, key: 'ollama_library', icon: , diff --git a/src/pages/llmodels/components/title-wrapper.tsx b/src/pages/llmodels/components/title-wrapper.tsx index 1efc637a..0e9698a3 100644 --- a/src/pages/llmodels/components/title-wrapper.tsx +++ b/src/pages/llmodels/components/title-wrapper.tsx @@ -2,11 +2,7 @@ import React from 'react'; import '../style/title-wrapper.less'; const TitleWrapper: React.FC = ({ children }) => { - return ( -

- {children} -

- ); + return

{children}

; }; export default TitleWrapper; diff --git a/src/pages/llmodels/config/index.ts b/src/pages/llmodels/config/index.ts index 14ed34be..c0bcf3e7 100644 --- a/src/pages/llmodels/config/index.ts +++ b/src/pages/llmodels/config/index.ts @@ -44,13 +44,13 @@ export const ollamaModelOptions = [ tags: ['7B'], id: 'mistral' }, - { - label: 'llava', - value: 'llava', - name: 'llava', - tags: ['7B', '13B', '34B'], - id: 'llava' - }, + // { + // label: 'llava', + // value: 'llava', + // name: 'llava', + // tags: ['7B', '13B', '34B'], + // id: 'llava' + // }, { label: 'qwen2', value: 'qwen2', @@ -59,11 +59,11 @@ export const ollamaModelOptions = [ id: 'qwen2' }, { - label: 'phi3', - value: 'phi3', - name: 'phi3', - tags: ['3B', '14B'], - id: 'phi3' + label: 'phi3.5', + value: 'phi3.5', + name: 'phi3.5', + tags: ['3B'], + id: 'phi3.5' }, { label: 'codellama', @@ -73,11 +73,11 @@ export const ollamaModelOptions = [ id: 'codellama' }, { - label: 'deepseek-coder', - value: 'deepseek-coder', - name: 'deepseek-coder', - tags: ['1B', '7B', '33B'], - id: 'deepseek-coder' + label: 'deepseek-coder-v2', + value: 'deepseek-coder-v2', + name: 'deepseek-coder-v2', + tags: ['16B', '236B'], + id: 'deepseek-coder-v2' } ]; @@ -143,7 +143,7 @@ export const ActionList = [ export const modelFilesSortOptions = [ // { label: 'Trending', value: 'trendingScore' }, - { label: 'Likes', value: 'likes' }, - { label: 'Downloads', value: 'downloads' }, - { label: 'Updated', value: 'updatedAt' } + { label: 'models.sort.likes', value: 'likes' }, + { label: 'models.sort.downloads', value: 'downloads' }, + { label: 'models.sort.updated', value: 'updatedAt' } ]; diff --git a/src/pages/llmodels/index.tsx b/src/pages/llmodels/index.tsx index 987c0521..ed1f170c 100644 --- a/src/pages/llmodels/index.tsx +++ b/src/pages/llmodels/index.tsx @@ -69,6 +69,7 @@ const Models: React.FC = () => { } else { setDataSource({ ...dataSource, + loading: !!res.items.length, total: res.pagination.total }); } diff --git a/src/pages/llmodels/style/model-card.less b/src/pages/llmodels/style/model-card.less index 9f3ecb09..ba93292a 100644 --- a/src/pages/llmodels/style/model-card.less +++ b/src/pages/llmodels/style/model-card.less @@ -8,6 +8,9 @@ .title { margin-bottom: 10px; + display: flex; + justify-content: space-between; + align-items: center; } .tag-item { @@ -18,12 +21,24 @@ border-radius: 4px; font-size: 12px; height: 20px; - border: 1px solid var(--ant-color-border); - color: var(--ant-color-text-secondary); } .btn { display: flex; justify-content: flex-end; } + + .mkd-title { + cursor: pointer; + color: rgba(255, 255, 255, 80%); + border-bottom: 1px solid rgba(255, 255, 255, 10%); + display: flex; + justify-content: space-between; + align-items: center; + padding: 10px; + } + + .simplebar-scrollbar::before { + background: var(--color-scroll-bg); + } } diff --git a/src/pages/llmodels/style/title-wrapper.less b/src/pages/llmodels/style/title-wrapper.less index 60fa5d39..9f613079 100644 --- a/src/pages/llmodels/style/title-wrapper.less +++ b/src/pages/llmodels/style/title-wrapper.less @@ -6,6 +6,7 @@ z-index: 100; display: flex; justify-content: space-between; + align-items: center; font-size: 14px; padding: @padding; padding-top: 0;