chore: add cancel query model
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
import hljs from 'highlight.js';
|
import hljs from 'highlight.js';
|
||||||
import 'highlight.js/styles/atom-one-dark.css';
|
import 'highlight.js/styles/atom-one-dark.css';
|
||||||
|
import { memo, useMemo } from 'react';
|
||||||
import CopyButton from '../copy-button';
|
import CopyButton from '../copy-button';
|
||||||
import { escapeHtml } from './utils';
|
import { escapeHtml } from './utils';
|
||||||
|
|
||||||
@@ -19,7 +20,7 @@ const CodeViewer: React.FC<CodeViewerProps> = (props) => {
|
|||||||
copyable = true
|
copyable = true
|
||||||
} = props || {};
|
} = props || {};
|
||||||
|
|
||||||
const renderCode = () => {
|
const highlightedCode = useMemo(() => {
|
||||||
const autodetectLang = autodetect && !lang;
|
const autodetectLang = autodetect && !lang;
|
||||||
const cannotDetectLanguage = !autodetectLang && !hljs.getLanguage(lang);
|
const cannotDetectLanguage = !autodetectLang && !hljs.getLanguage(lang);
|
||||||
let className = '';
|
let className = '';
|
||||||
@@ -52,9 +53,7 @@ const CodeViewer: React.FC<CodeViewerProps> = (props) => {
|
|||||||
value: result.value,
|
value: result.value,
|
||||||
className: className
|
className: className
|
||||||
};
|
};
|
||||||
};
|
}, [code, lang, autodetect, ignoreIllegals]);
|
||||||
|
|
||||||
const highlightedCode = renderCode();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<pre className="code-pre">
|
<pre className="code-pre">
|
||||||
@@ -64,13 +63,15 @@ const CodeViewer: React.FC<CodeViewerProps> = (props) => {
|
|||||||
__html: highlightedCode.value
|
__html: highlightedCode.value
|
||||||
}}
|
}}
|
||||||
></code>
|
></code>
|
||||||
<CopyButton
|
{copyable && (
|
||||||
text={code}
|
<CopyButton
|
||||||
size="small"
|
text={code}
|
||||||
style={{ color: '#abb2bf' }}
|
size="small"
|
||||||
></CopyButton>
|
style={{ color: '#abb2bf' }}
|
||||||
|
></CopyButton>
|
||||||
|
)}
|
||||||
</pre>
|
</pre>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default CodeViewer;
|
export default memo(CodeViewer);
|
||||||
|
|||||||
@@ -4,12 +4,13 @@ import './style.less';
|
|||||||
const HighlightCode: React.FC<{
|
const HighlightCode: React.FC<{
|
||||||
code: string;
|
code: string;
|
||||||
lang?: string;
|
lang?: string;
|
||||||
|
copyable?: boolean;
|
||||||
}> = (props) => {
|
}> = (props) => {
|
||||||
const { code, lang = 'bash' } = props;
|
const { code, lang = 'bash', copyable = true } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="high-light-wrapper">
|
<div className="high-light-wrapper">
|
||||||
<CodeViewer lang={lang} code={code} />
|
<CodeViewer lang={lang} code={code} copyable={copyable} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -16,5 +16,13 @@
|
|||||||
top: 0;
|
top: 0;
|
||||||
border-radius: 0 8px 0 4px;
|
border-radius: 0 8px 0 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.simplebar-scrollbar::before {
|
||||||
|
background: var(--color-scroll-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.simplebar-content {
|
||||||
|
width: max-content;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,16 @@
|
|||||||
font-size: var(--font-size-base);
|
font-size: var(--font-size-base);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
|
.txt {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
height: 20px;
|
||||||
|
|
||||||
|
&.err {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&.download {
|
&.download {
|
||||||
border: 1px solid #93ecc5 !important;
|
border: 1px solid #93ecc5 !important;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import { InfoCircleOutlined } from '@ant-design/icons';
|
|||||||
import { Tooltip } from 'antd';
|
import { Tooltip } from 'antd';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
|
import SimpleBar from 'simplebar-react';
|
||||||
|
import 'simplebar-react/dist/simplebar.min.css';
|
||||||
import CopyButton from '../copy-button';
|
import CopyButton from '../copy-button';
|
||||||
import CopyStyle from './copy.less';
|
import CopyStyle from './copy.less';
|
||||||
import './index.less';
|
import './index.less';
|
||||||
@@ -70,7 +72,14 @@ const StatusTag: React.FC<StatusTagProps> = ({
|
|||||||
size="small"
|
size="small"
|
||||||
></CopyButton>
|
></CopyButton>
|
||||||
</div>
|
</div>
|
||||||
<div>{statusValue.message}</div>
|
|
||||||
|
<SimpleBar style={{ maxHeight: 200 }}>
|
||||||
|
<div
|
||||||
|
style={{ width: 'max-content', maxWidth: 250, paddingInline: 10 }}
|
||||||
|
>
|
||||||
|
{statusValue.message}
|
||||||
|
</div>
|
||||||
|
</SimpleBar>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}, [statusValue]);
|
}, [statusValue]);
|
||||||
@@ -87,15 +96,18 @@ const StatusTag: React.FC<StatusTagProps> = ({
|
|||||||
{statusValue.message ? (
|
{statusValue.message ? (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
title={renderTitle}
|
title={renderTitle}
|
||||||
overlayInnerStyle={{ maxHeight: 200, overflow: 'auto' }}
|
destroyTooltipOnHide={true}
|
||||||
|
overlayInnerStyle={{ paddingInline: 0 }}
|
||||||
>
|
>
|
||||||
<span className="m-r-5">
|
<span className="txt err">
|
||||||
<InfoCircleOutlined />
|
<span className="m-r-5">
|
||||||
|
<InfoCircleOutlined />
|
||||||
|
</span>
|
||||||
|
{renderContent()}
|
||||||
</span>
|
</span>
|
||||||
{renderContent()}
|
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
) : (
|
) : (
|
||||||
renderContent()
|
<span className="txt">{renderContent()}</span>
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -11,5 +11,7 @@ export default {
|
|||||||
EDIT: ['ctrl+e', 'meta+e'],
|
EDIT: ['ctrl+e', 'meta+e'],
|
||||||
SEARCH: ['ctrl+f', 'meta+f'],
|
SEARCH: ['ctrl+f', 'meta+f'],
|
||||||
RESET: ['ctrl+shift+r', 'meta+shift+r'],
|
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']
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ html {
|
|||||||
--color-fill-1: var(--ant-color-fill-tertiary);
|
--color-fill-1: var(--ant-color-fill-tertiary);
|
||||||
// --color-fill-1: #fff;
|
// --color-fill-1: #fff;
|
||||||
--ant-color-text: #000;
|
--ant-color-text: #000;
|
||||||
|
--color-scroll-bg: #d9d9d9;
|
||||||
--color-fill-2: #fff;
|
--color-fill-2: #fff;
|
||||||
--color-fill-3: #f3f6fa;
|
--color-fill-3: #f3f6fa;
|
||||||
--color-logs-bg: #1e1e1e;
|
--color-logs-bg: #1e1e1e;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
// This file is generated by Umi automatically
|
|
||||||
// DO NOT CHANGE IT MANUALLY!
|
|
||||||
import { history, useIntl, type IRoute } from '@umijs/max';
|
import { history, useIntl, type IRoute } from '@umijs/max';
|
||||||
import { Button, Result } from 'antd';
|
import { Button, Result } from 'antd';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|||||||
@@ -18,5 +18,15 @@ export default {
|
|||||||
'model.deploy.sort': 'Sort',
|
'model.deploy.sort': 'Sort',
|
||||||
'model.deploy.search.placeholder': 'Search models from Hugging Face',
|
'model.deploy.search.placeholder': 'Search models from Hugging Face',
|
||||||
'model.form.ollamatips':
|
'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'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -18,5 +18,15 @@ export default {
|
|||||||
'model.deploy.sort': '排序',
|
'model.deploy.sort': '排序',
|
||||||
'model.deploy.search.placeholder': '从 Hugging Face 搜索模型',
|
'model.deploy.search.placeholder': '从 Hugging Face 搜索模型',
|
||||||
'model.form.ollamatips':
|
'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': '架构'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { listFiles, listModels } from '@huggingface/hub';
|
import { downloadFile, listFiles, listModels } from '@huggingface/hub';
|
||||||
import { PipelineType } from '@huggingface/tasks';
|
import { PipelineType } from '@huggingface/tasks';
|
||||||
import { request } from '@umijs/max';
|
import { request } from '@umijs/max';
|
||||||
import {
|
import {
|
||||||
@@ -119,9 +119,13 @@ export async function callHuggingfaceQuickSearch(params: any) {
|
|||||||
|
|
||||||
const HUGGINGFACE_API = 'https://huggingface.co/api/models';
|
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}`, {
|
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({
|
for await (const model of listModels({
|
||||||
...params,
|
...params,
|
||||||
...options,
|
...options,
|
||||||
limit: 100,
|
limit: 500,
|
||||||
additionalFields: ['sha'],
|
additionalFields: ['sha'],
|
||||||
fetch(url: string, config: any) {
|
fetch(url: string, config: any) {
|
||||||
try {
|
try {
|
||||||
@@ -148,6 +152,7 @@ export async function queryHuggingfaceModels(
|
|||||||
signal: options.signal
|
signal: options.signal
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.log('queryHuggingfaceModels error===', error);
|
||||||
// ignore
|
// ignore
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@@ -158,12 +163,48 @@ export async function queryHuggingfaceModels(
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function queryHuggingfaceModelFiles(params: { repo: string }) {
|
export async function queryHuggingfaceModelFiles(
|
||||||
|
params: { repo: string },
|
||||||
|
options?: any
|
||||||
|
) {
|
||||||
const result = [];
|
const result = [];
|
||||||
for await (const fileInfo of listFiles({
|
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);
|
result.push(fileInfo);
|
||||||
}
|
}
|
||||||
return result;
|
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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,13 +3,16 @@ import SimpleBar from 'simplebar-react';
|
|||||||
import 'simplebar-react/dist/simplebar.min.css';
|
import 'simplebar-react/dist/simplebar.min.css';
|
||||||
import '../style/column-wrapper.less';
|
import '../style/column-wrapper.less';
|
||||||
|
|
||||||
const ColumnWrapper: React.FC<any> = ({ children, footer }) => {
|
const ColumnWrapper: React.FC<any> = ({ children, footer, height }) => {
|
||||||
if (footer) {
|
if (footer) {
|
||||||
return (
|
return (
|
||||||
<div className="column-wrapper-footer">
|
<div className="column-wrapper-footer">
|
||||||
<div className="column-wrapper">
|
<div className="column-wrapper">
|
||||||
<SimpleBar
|
<SimpleBar
|
||||||
style={{ height: 'calc(100vh - 89px)', paddingBottom: '50px' }}
|
style={{
|
||||||
|
height: height || 'calc(100vh - 89px)',
|
||||||
|
paddingBottom: '50px'
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</SimpleBar>
|
</SimpleBar>
|
||||||
@@ -20,7 +23,9 @@ const ColumnWrapper: React.FC<any> = ({ children, footer }) => {
|
|||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div className="column-wrapper">
|
<div className="column-wrapper">
|
||||||
<SimpleBar style={{ height: 'calc(100vh - 89px)' }}>{children}</SimpleBar>
|
<SimpleBar style={{ height: height || 'calc(100vh - 89px)' }}>
|
||||||
|
{children}
|
||||||
|
</SimpleBar>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { PageActionType } from '@/config/types';
|
|||||||
import { CloseOutlined } from '@ant-design/icons';
|
import { CloseOutlined } from '@ant-design/icons';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { Button, Drawer } from 'antd';
|
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 { modelSourceMap } from '../config';
|
||||||
import { FormData, ListItem } from '../config/types';
|
import { FormData, ListItem } from '../config/types';
|
||||||
import ColumnWrapper from './column-wrapper';
|
import ColumnWrapper from './column-wrapper';
|
||||||
@@ -38,6 +38,8 @@ const AddModal: React.FC<AddModalProps> = (props) => {
|
|||||||
const form = useRef<any>({});
|
const form = useRef<any>({});
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const [huggingfaceRepoId, setHuggingfaceRepoId] = useState<string>('');
|
const [huggingfaceRepoId, setHuggingfaceRepoId] = useState<string>('');
|
||||||
|
const [collapsed, setCollapsed] = useState<boolean>(false);
|
||||||
|
const [loadingModel, setLoadingModel] = useState<boolean>(false);
|
||||||
|
|
||||||
const handleSelectModelFile = useCallback((item: any) => {
|
const handleSelectModelFile = useCallback((item: any) => {
|
||||||
form.current?.setFieldValue?.('huggingface_filename', item.path);
|
form.current?.setFieldValue?.('huggingface_filename', item.path);
|
||||||
@@ -100,15 +102,22 @@ const AddModal: React.FC<AddModalProps> = (props) => {
|
|||||||
<SearchModel
|
<SearchModel
|
||||||
modelSource={props.source}
|
modelSource={props.source}
|
||||||
onSelectModel={handleOnSelectModel}
|
onSelectModel={handleOnSelectModel}
|
||||||
|
setLoadingModel={setLoadingModel}
|
||||||
></SearchModel>
|
></SearchModel>
|
||||||
</ColumnWrapper>
|
</ColumnWrapper>
|
||||||
)}
|
)}
|
||||||
{props.source === modelSourceMap.huggingface_value && (
|
{props.source === modelSourceMap.huggingface_value && (
|
||||||
<ColumnWrapper>
|
<ColumnWrapper>
|
||||||
<ModelCard repo={huggingfaceRepoId}></ModelCard>
|
<ModelCard
|
||||||
|
repo={huggingfaceRepoId}
|
||||||
|
onCollapse={setCollapsed}
|
||||||
|
collapsed={collapsed}
|
||||||
|
></ModelCard>
|
||||||
<HFModelFile
|
<HFModelFile
|
||||||
repo={huggingfaceRepoId}
|
repo={huggingfaceRepoId}
|
||||||
onSelectFile={handleSelectModelFile}
|
onSelectFile={handleSelectModelFile}
|
||||||
|
collapsed={collapsed}
|
||||||
|
loadingModel={loadingModel}
|
||||||
></HFModelFile>
|
></HFModelFile>
|
||||||
</ColumnWrapper>
|
</ColumnWrapper>
|
||||||
)}
|
)}
|
||||||
@@ -126,9 +135,11 @@ const AddModal: React.FC<AddModalProps> = (props) => {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<>
|
<>
|
||||||
<TitleWrapper>
|
{source === modelSourceMap.huggingface_value && (
|
||||||
{intl.formatMessage({ id: 'models.form.configurations' })}
|
<TitleWrapper>
|
||||||
</TitleWrapper>
|
{intl.formatMessage({ id: 'models.form.configurations' })}
|
||||||
|
</TitleWrapper>
|
||||||
|
)}
|
||||||
<DataForm
|
<DataForm
|
||||||
source={source}
|
source={source}
|
||||||
action={action}
|
action={action}
|
||||||
@@ -143,4 +154,4 @@ const AddModal: React.FC<AddModalProps> = (props) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default AddModal;
|
export default memo(AddModal);
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import { convertFileSize } from '@/utils';
|
import { convertFileSize } from '@/utils';
|
||||||
import { SearchOutlined } from '@ant-design/icons';
|
import { SearchOutlined } from '@ant-design/icons';
|
||||||
import { useIntl } from '@umijs/max';
|
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 classNames from 'classnames';
|
||||||
import _ from 'lodash';
|
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 { queryHuggingfaceModelFiles } from '../apis';
|
||||||
import FileType from '../config/file-type';
|
import FileType from '../config/file-type';
|
||||||
import '../style/hf-model-file.less';
|
import '../style/hf-model-file.less';
|
||||||
@@ -12,16 +14,31 @@ import TitleWrapper from './title-wrapper';
|
|||||||
|
|
||||||
interface HFModelFileProps {
|
interface HFModelFileProps {
|
||||||
repo: string;
|
repo: string;
|
||||||
|
collapsed?: boolean;
|
||||||
|
loadingModel?: boolean;
|
||||||
onSelectFile?: (file: any) => void;
|
onSelectFile?: (file: any) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const HFModelFile: React.FC<HFModelFileProps> = (props) => {
|
const HFModelFile: React.FC<HFModelFileProps> = (props) => {
|
||||||
|
const { collapsed, loadingModel } = props;
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const [dataSource, setDataSource] = useState<any>({
|
const [dataSource, setDataSource] = useState<any>({
|
||||||
fileList: [],
|
fileList: [],
|
||||||
loading: false
|
loading: false
|
||||||
});
|
});
|
||||||
|
const [sortType, setSortType] = useState<string>('size');
|
||||||
const [current, setCurrent] = useState<string>('');
|
const [current, setCurrent] = useState<string>('');
|
||||||
|
const modelFilesSortOptions = useRef<any[]>([
|
||||||
|
{
|
||||||
|
label: intl.formatMessage({ id: 'models.sort.size' }),
|
||||||
|
value: 'size'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: intl.formatMessage({ id: 'models.sort.name' }),
|
||||||
|
value: 'name'
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
const axiosTokenRef = useRef<any>(null);
|
||||||
|
|
||||||
const handleSelectModelFile = (item: any) => {
|
const handleSelectModelFile = (item: any) => {
|
||||||
props.onSelectFile?.(item);
|
props.onSelectFile?.(item);
|
||||||
@@ -34,14 +51,23 @@ const HFModelFile: React.FC<HFModelFileProps> = (props) => {
|
|||||||
handleSelectModelFile({});
|
handleSelectModelFile({});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
axiosTokenRef.current?.abort?.();
|
||||||
|
axiosTokenRef.current = new AbortController();
|
||||||
setDataSource({ ...dataSource, loading: true });
|
setDataSource({ ...dataSource, loading: true });
|
||||||
setCurrent('');
|
setCurrent('');
|
||||||
try {
|
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) => {
|
const list = _.filter(res, (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) => item.size);
|
const sortList = _.sortBy(list, (item: any) => {
|
||||||
|
return sortType === 'size' ? item.size : item.path;
|
||||||
|
});
|
||||||
setDataSource({ fileList: sortList, loading: false });
|
setDataSource({ fileList: sortList, loading: false });
|
||||||
handleSelectModelFile(list[0]);
|
handleSelectModelFile(list[0]);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -50,6 +76,14 @@ const HFModelFile: React.FC<HFModelFileProps> = (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 getModelQuantizationType = (item: any) => {
|
||||||
const name = _.split(item.path, '.').slice(0, -1).join('.');
|
const name = _.split(item.path, '.').slice(0, -1).join('.');
|
||||||
let quanType = _.toUpper(name.split('-').slice(-1)[0]);
|
let quanType = _.toUpper(name.split('-').slice(-1)[0]);
|
||||||
@@ -77,76 +111,93 @@ const HFModelFile: React.FC<HFModelFileProps> = (props) => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
handleFetchModelFiles();
|
handleFetchModelFiles();
|
||||||
}, [props.repo]);
|
}, [props.repo]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
axiosTokenRef.current?.abort?.();
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<TitleWrapper>
|
<TitleWrapper>
|
||||||
<span>Available Files ({dataSource.fileList.length || 0})</span>
|
<span>
|
||||||
|
{intl.formatMessage({ id: 'models.available.files' })} (
|
||||||
|
{dataSource.fileList.length || 0})
|
||||||
|
</span>
|
||||||
|
<Select
|
||||||
|
value={sortType}
|
||||||
|
onChange={handleSortChange}
|
||||||
|
labelRender={({ label }) => {
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
{intl.formatMessage({ id: 'model.deploy.sort' })}: {label}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
options={modelFilesSortOptions.current}
|
||||||
|
size="middle"
|
||||||
|
style={{ width: '120px' }}
|
||||||
|
></Select>
|
||||||
</TitleWrapper>
|
</TitleWrapper>
|
||||||
<div style={{ padding: '16px 24px' }}>
|
<SimpleBar
|
||||||
<Spin spinning={dataSource.loading} style={{ minHeight: 100 }}>
|
style={{ maxHeight: collapsed ? 'max-content' : 'calc(100vh - 330px)' }}
|
||||||
{dataSource.fileList.length ? (
|
>
|
||||||
<Row gutter={[16, 24]}>
|
<div style={{ padding: '16px 24px' }}>
|
||||||
{_.map(dataSource.fileList, (item: any) => {
|
<Spin
|
||||||
return (
|
spinning={dataSource.loading || loadingModel}
|
||||||
<Col span={24} key={item.path}>
|
style={{ minHeight: 100 }}
|
||||||
<div
|
>
|
||||||
className={classNames('hf-model-file', {
|
{dataSource.fileList.length ? (
|
||||||
active: item.path === current
|
<Row gutter={[16, 24]}>
|
||||||
})}
|
{_.map(dataSource.fileList, (item: any) => {
|
||||||
tabIndex={0}
|
return (
|
||||||
onClick={() => handleSelectModelFile(item)}
|
<Col span={24} key={item.path}>
|
||||||
onKeyDown={(e) => handleOnEnter(e, item)}
|
<div
|
||||||
>
|
className={classNames('hf-model-file', {
|
||||||
<div className="title">{item.path}</div>
|
active: item.path === current
|
||||||
<Space className="tags">
|
})}
|
||||||
{/* <span className="tag-item">
|
tabIndex={0}
|
||||||
{convertFileSize(item.size)}
|
onClick={() => handleSelectModelFile(item)}
|
||||||
</span> */}
|
onKeyDown={(e) => handleOnEnter(e, item)}
|
||||||
<Tag
|
>
|
||||||
className="tag-item"
|
<div className="title">{item.path}</div>
|
||||||
color="green"
|
<Space className="tags">
|
||||||
style={{
|
<Tag
|
||||||
marginRight: 0
|
className="tag-item"
|
||||||
}}
|
color="green"
|
||||||
>
|
style={{
|
||||||
<span style={{ opacity: 0.65 }}>
|
marginRight: 0
|
||||||
{convertFileSize(item.size)}
|
}}
|
||||||
</span>
|
>
|
||||||
</Tag>
|
<span style={{ opacity: 0.65 }}>
|
||||||
{getModelQuantizationType(item)}
|
{convertFileSize(item.size)}
|
||||||
</Space>
|
</span>
|
||||||
<div className="btn">
|
</Tag>
|
||||||
{/* <Button size="middle">
|
{getModelQuantizationType(item)}
|
||||||
{item.path === current
|
</Space>
|
||||||
? intl.formatMessage({
|
<div className="btn"></div>
|
||||||
id: 'common.button.selected'
|
|
||||||
})
|
|
||||||
: intl.formatMessage({
|
|
||||||
id: 'common.button.select'
|
|
||||||
})}
|
|
||||||
</Button> */}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Col>
|
||||||
</Col>
|
);
|
||||||
);
|
})}
|
||||||
})}
|
</Row>
|
||||||
</Row>
|
) : (
|
||||||
) : (
|
!dataSource.loading && (
|
||||||
!dataSource.loading && (
|
<Empty
|
||||||
<Empty
|
imageStyle={{ height: 'auto', marginTop: '20px' }}
|
||||||
imageStyle={{ height: 'auto', marginTop: '20px' }}
|
image={
|
||||||
image={
|
<SearchOutlined
|
||||||
<SearchOutlined
|
className="font-size-16"
|
||||||
className="font-size-16"
|
style={{ color: 'var(--ant-color-text-tertiary)' }}
|
||||||
style={{ color: 'var(--ant-color-text-tertiary)' }}
|
></SearchOutlined>
|
||||||
></SearchOutlined>
|
}
|
||||||
}
|
description="No files found"
|
||||||
description="No files found"
|
/>
|
||||||
/>
|
)
|
||||||
)
|
)}
|
||||||
)}
|
</Spin>
|
||||||
</Spin>
|
</div>
|
||||||
</div>
|
</SimpleBar>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ const HFModelItem: React.FC<HFModelItemProps> = (props) => {
|
|||||||
<div className="info">
|
<div className="info">
|
||||||
{props.source === modelSourceMap.huggingface_value ? (
|
{props.source === modelSourceMap.huggingface_value ? (
|
||||||
<Space size={16}>
|
<Space size={16}>
|
||||||
{props.task && (
|
{/* {props.task && (
|
||||||
<Tag
|
<Tag
|
||||||
className="tag-item"
|
className="tag-item"
|
||||||
color="gold"
|
color="gold"
|
||||||
@@ -50,7 +50,7 @@ const HFModelItem: React.FC<HFModelItemProps> = (props) => {
|
|||||||
>
|
>
|
||||||
<span style={{ opacity: 0.65 }}>{props.task}</span>
|
<span style={{ opacity: 0.65 }}>{props.task}</span>
|
||||||
</Tag>
|
</Tag>
|
||||||
)}
|
)} */}
|
||||||
<span>
|
<span>
|
||||||
{dayjs().to(
|
{dayjs().to(
|
||||||
dayjs(dayjs(props.updatedAt).format('YYYY-MM-DD HH:mm:ss'))
|
dayjs(dayjs(props.updatedAt).format('YYYY-MM-DD HH:mm:ss'))
|
||||||
|
|||||||
@@ -1,20 +1,51 @@
|
|||||||
|
import HighlightCode from '@/components/highlight-code';
|
||||||
import IconFont from '@/components/icon-font';
|
import IconFont from '@/components/icon-font';
|
||||||
import { downloadFile } from '@huggingface/hub';
|
import useRequestToken from '@/hooks/use-request-token';
|
||||||
import { Button, Empty, Tag } from 'antd';
|
import {
|
||||||
import React, { useEffect, useState } from 'react';
|
DownOutlined,
|
||||||
import { queryHuggingfaceModelDetail } from '../apis';
|
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 '../style/model-card.less';
|
||||||
import TitleWrapper from './title-wrapper';
|
import TitleWrapper from './title-wrapper';
|
||||||
|
|
||||||
const ModelCard: React.FC<{ repo: string }> = (props) => {
|
const ModelCard: React.FC<{
|
||||||
const { repo } = props;
|
repo: string;
|
||||||
|
onCollapse: (flag: boolean) => void;
|
||||||
|
collapsed: boolean;
|
||||||
|
}> = (props) => {
|
||||||
|
const { repo, onCollapse, collapsed } = props;
|
||||||
|
const intl = useIntl();
|
||||||
|
const requestSource = useRequestToken();
|
||||||
const [modelData, setModelData] = useState<any>({});
|
const [modelData, setModelData] = useState<any>({});
|
||||||
|
const [readmeText, setReadmeText] = useState<string | null>(null);
|
||||||
|
const requestToken = useRef<any>(null);
|
||||||
|
const axiosTokenRef = useRef<any>(null);
|
||||||
|
|
||||||
const loadFile = async (repo: string, sha: string) => {
|
const loadFile = async (repo: string, sha: string) => {
|
||||||
const res = await (
|
try {
|
||||||
await downloadFile({ repo, revision: sha, path: 'README.md' })
|
axiosTokenRef.current?.abort?.();
|
||||||
)?.text();
|
axiosTokenRef.current = new AbortController();
|
||||||
return res;
|
const res = await downloadModelFile(
|
||||||
|
{
|
||||||
|
repo,
|
||||||
|
revision: sha,
|
||||||
|
path: 'README.md'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
signal: axiosTokenRef.current.signal
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return res || '';
|
||||||
|
} catch (error) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getModelCardData = async () => {
|
const getModelCardData = async () => {
|
||||||
@@ -22,42 +53,110 @@ const ModelCard: React.FC<{ repo: string }> = (props) => {
|
|||||||
setModelData(null);
|
setModelData(null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
requestToken.current?.cancel?.();
|
||||||
|
requestToken.current = requestSource();
|
||||||
try {
|
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) {
|
} catch (error) {
|
||||||
setModelData({});
|
setModelData({});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleCollapse = useCallback(() => {
|
||||||
|
onCollapse(!collapsed);
|
||||||
|
}, [collapsed]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getModelCardData();
|
getModelCardData();
|
||||||
}, [repo]);
|
}, [repo]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!readmeText) {
|
||||||
|
onCollapse(false);
|
||||||
|
}
|
||||||
|
}, [readmeText]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
requestToken.current?.cancel?.();
|
||||||
|
axiosTokenRef.current?.abort?.();
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<TitleWrapper>
|
<TitleWrapper>
|
||||||
<span>Model Card</span>
|
<span>{intl.formatMessage({ id: 'models.data.card' })}</span>
|
||||||
</TitleWrapper>
|
</TitleWrapper>
|
||||||
<div className="wrapper">
|
<div className="wrapper">
|
||||||
{modelData ? (
|
{modelData ? (
|
||||||
<div className="model-card-wrap">
|
<div className="model-card-wrap">
|
||||||
<div className="title">{modelData.id}</div>
|
<div className="title">
|
||||||
<div className="flex-between flex-center">
|
{modelData.id}{' '}
|
||||||
<Tag className="tag-item">
|
<Tooltip title={intl.formatMessage({ id: 'models.viewin.hf' })}>
|
||||||
<span className="m-r-5">Architecture:</span>
|
<Button
|
||||||
{modelData.config?.model_type}
|
size="small"
|
||||||
</Tag>
|
type="link"
|
||||||
<Button
|
target="_blank"
|
||||||
type="link"
|
href={`https://huggingface.co/${modelData.id}`}
|
||||||
target="_blank"
|
>
|
||||||
href={`https://huggingface.co/${modelData.id}`}
|
<IconFont type="icon-external-link"></IconFont>
|
||||||
>
|
</Button>
|
||||||
View in Hugging Face
|
</Tooltip>
|
||||||
<IconFont type="icon-external-link"></IconFont>
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex-between flex-center">
|
||||||
|
{modelData.config?.model_type && (
|
||||||
|
<Tag className="tag-item" color="gold">
|
||||||
|
<span style={{ opacity: 0.65 }}>
|
||||||
|
<span className="m-r-5">
|
||||||
|
{intl.formatMessage({ id: 'models.architecture' })}:
|
||||||
|
</span>
|
||||||
|
{modelData.config?.model_type}
|
||||||
|
</span>
|
||||||
|
</Tag>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{readmeText && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
borderRadius: 4,
|
||||||
|
backgroundColor: '#282c34',
|
||||||
|
marginTop: 16,
|
||||||
|
overflow: 'hidden'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span className="mkd-title" onClick={handleCollapse}>
|
||||||
|
<span>
|
||||||
|
<FileTextOutlined className="m-r-5" /> README.md
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
{collapsed ? <DownOutlined /> : <RightOutlined />}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<SimpleBar
|
||||||
|
style={{
|
||||||
|
maxHeight: collapsed ? 300 : 0
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<HighlightCode
|
||||||
|
code={readmeText}
|
||||||
|
lang="markdown"
|
||||||
|
copyable={false}
|
||||||
|
></HighlightCode>
|
||||||
|
</SimpleBar>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE}></Empty>
|
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE}></Empty>
|
||||||
|
|||||||
@@ -5,17 +5,14 @@ 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 {
|
import { modelSourceMap, ollamaModelOptions } from '../config';
|
||||||
modelFilesSortOptions,
|
|
||||||
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';
|
||||||
|
|
||||||
interface SearchInputProps {
|
interface SearchInputProps {
|
||||||
modelSource: string;
|
modelSource: string;
|
||||||
|
setLoadingModel?: (flag: boolean) => void;
|
||||||
onSourceChange?: (source: string) => void;
|
onSourceChange?: (source: string) => void;
|
||||||
onSelectModel: (model: any) => void;
|
onSelectModel: (model: any) => void;
|
||||||
}
|
}
|
||||||
@@ -38,7 +35,7 @@ const sourceList = [
|
|||||||
const SearchModel: React.FC<SearchInputProps> = (props) => {
|
const SearchModel: React.FC<SearchInputProps> = (props) => {
|
||||||
console.log('SearchModel======');
|
console.log('SearchModel======');
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const { modelSource, onSourceChange, onSelectModel } = props;
|
const { modelSource, setLoadingModel, onSourceChange, onSelectModel } = props;
|
||||||
const [dataSource, setDataSource] = useState<{
|
const [dataSource, setDataSource] = useState<{
|
||||||
repoOptions: any[];
|
repoOptions: any[];
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
@@ -51,6 +48,17 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
|
|||||||
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 modelFilesSortOptions = useRef<any[]>([
|
||||||
|
{ 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) => {
|
const handleOnSelectModel = useCallback((item: any) => {
|
||||||
onSelectModel(item);
|
onSelectModel(item);
|
||||||
@@ -67,6 +75,7 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
|
|||||||
pre.loading = true;
|
pre.loading = true;
|
||||||
return { ...pre };
|
return { ...pre };
|
||||||
});
|
});
|
||||||
|
setLoadingModel?.(true);
|
||||||
cacheRepoOptions.current = [];
|
cacheRepoOptions.current = [];
|
||||||
const params = {
|
const params = {
|
||||||
search: {
|
search: {
|
||||||
@@ -93,12 +102,15 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
|
|||||||
repoOptions: sortedList,
|
repoOptions: sortedList,
|
||||||
loading: false
|
loading: false
|
||||||
});
|
});
|
||||||
|
setLoadingModel?.(false);
|
||||||
handleOnSelectModel(sortedList[0]);
|
handleOnSelectModel(sortedList[0]);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.log('queryHuggingfaceModels error===', error);
|
||||||
setDataSource({
|
setDataSource({
|
||||||
repoOptions: [],
|
repoOptions: [],
|
||||||
loading: false
|
loading: false
|
||||||
});
|
});
|
||||||
|
setLoadingModel?.(false);
|
||||||
handleOnSelectModel({});
|
handleOnSelectModel({});
|
||||||
cacheRepoOptions.current = [];
|
cacheRepoOptions.current = [];
|
||||||
}
|
}
|
||||||
@@ -184,15 +196,18 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
|
|||||||
loading: false
|
loading: false
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderHFSearch = () => {
|
const renderHFSearch = () => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<SearchInput onSearch={handlerSearchModels}></SearchInput>
|
<SearchInput onSearch={handlerSearchModels}></SearchInput>
|
||||||
<div className={SearchStyle.filter}>
|
<div className={SearchStyle.filter}>
|
||||||
<span>
|
<span>
|
||||||
<span className="value">{dataSource.repoOptions.length}</span>
|
<span className="value">
|
||||||
results
|
{intl.formatMessage(
|
||||||
|
{ id: 'models.search.result' },
|
||||||
|
{ count: dataSource.repoOptions.length }
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<Select
|
<Select
|
||||||
value={sortType}
|
value={sortType}
|
||||||
@@ -204,7 +219,7 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
|
|||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
options={modelFilesSortOptions}
|
options={modelFilesSortOptions.current}
|
||||||
size="middle"
|
size="middle"
|
||||||
style={{ width: '150px' }}
|
style={{ width: '150px' }}
|
||||||
></Select>
|
></Select>
|
||||||
@@ -233,10 +248,13 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
handleOnOpen();
|
handleOnOpen();
|
||||||
console.log('SearchModel useEffect', modelSource);
|
console.log('SearchModel useEffect', modelSource);
|
||||||
|
}, [modelSource]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
axiosTokenRef.current?.abort?.();
|
axiosTokenRef.current?.abort?.();
|
||||||
};
|
};
|
||||||
}, [modelSource]);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ flex: 1 }}>
|
<div style={{ flex: 1 }}>
|
||||||
|
|||||||
@@ -5,11 +5,12 @@ import PageTools from '@/components/page-tools';
|
|||||||
import SealTable from '@/components/seal-table';
|
import SealTable from '@/components/seal-table';
|
||||||
import SealColumn from '@/components/seal-table/components/seal-column';
|
import SealColumn from '@/components/seal-table/components/seal-column';
|
||||||
import { PageAction } from '@/config';
|
import { PageAction } from '@/config';
|
||||||
|
import HotKeys from '@/config/hotkeys';
|
||||||
import useExpandedRowKeys from '@/hooks/use-expanded-row-keys';
|
import useExpandedRowKeys from '@/hooks/use-expanded-row-keys';
|
||||||
import useTableRowSelection from '@/hooks/use-table-row-selection';
|
import useTableRowSelection from '@/hooks/use-table-row-selection';
|
||||||
import useTableSort from '@/hooks/use-table-sort';
|
import useTableSort from '@/hooks/use-table-sort';
|
||||||
import ViewCodeModal from '@/pages/playground/components/view-code-modal';
|
import ViewCodeModal from '@/pages/playground/components/view-code-modal';
|
||||||
import { handleBatchRequest } from '@/utils';
|
import { handleBatchRequest, platformCall } from '@/utils';
|
||||||
import {
|
import {
|
||||||
DeleteOutlined,
|
DeleteOutlined,
|
||||||
DownOutlined,
|
DownOutlined,
|
||||||
@@ -23,6 +24,7 @@ import { Button, Dropdown, Input, Space, Tag, message } from 'antd';
|
|||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { memo, useCallback, useRef, useState } from 'react';
|
import { memo, useCallback, useRef, useState } from 'react';
|
||||||
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
import {
|
import {
|
||||||
MODELS_API,
|
MODELS_API,
|
||||||
MODEL_INSTANCE_API,
|
MODEL_INSTANCE_API,
|
||||||
@@ -64,6 +66,7 @@ const Models: React.FC<ModelsProps> = ({
|
|||||||
total
|
total
|
||||||
}) => {
|
}) => {
|
||||||
console.log('model list====2');
|
console.log('model list====2');
|
||||||
|
const platform = platformCall();
|
||||||
const access = useAccess();
|
const access = useAccess();
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@@ -77,7 +80,6 @@ const Models: React.FC<ModelsProps> = ({
|
|||||||
params: {},
|
params: {},
|
||||||
show: false
|
show: false
|
||||||
});
|
});
|
||||||
const [openViewCodeModal, setOpenViewCodeModal] = useState(false);
|
|
||||||
const [openLogModal, setOpenLogModal] = useState(false);
|
const [openLogModal, setOpenLogModal] = useState(false);
|
||||||
const [openAddModal, setOpenAddModal] = useState(false);
|
const [openAddModal, setOpenAddModal] = useState(false);
|
||||||
const [openDeployModal, setOpenDeployModal] = useState<any>({
|
const [openDeployModal, setOpenDeployModal] = useState<any>({
|
||||||
@@ -92,9 +94,46 @@ const Models: React.FC<ModelsProps> = ({
|
|||||||
const [currentInstanceUrl, setCurrentInstanceUrl] = useState<string>('');
|
const [currentInstanceUrl, setCurrentInstanceUrl] = useState<string>('');
|
||||||
const modalRef = useRef<any>(null);
|
const modalRef = useRef<any>(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 = [
|
const sourceOptions = [
|
||||||
{
|
{
|
||||||
label: 'Hugging Face',
|
label: (
|
||||||
|
<span className="flex-center flex-between">
|
||||||
|
<span>Hugging Face</span>
|
||||||
|
<Tag style={{ marginRight: 0 }} className="m-l-10">
|
||||||
|
{platform.isMac ? (
|
||||||
|
<>
|
||||||
|
<IconFont type="icon-command"></IconFont> + 1
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>CTRL + 1</>
|
||||||
|
)}
|
||||||
|
</Tag>
|
||||||
|
</span>
|
||||||
|
),
|
||||||
value: modelSourceMap.huggingface_value,
|
value: modelSourceMap.huggingface_value,
|
||||||
key: 'huggingface',
|
key: 'huggingface',
|
||||||
icon: <IconFont type="icon-huggingface"></IconFont>,
|
icon: <IconFont type="icon-huggingface"></IconFont>,
|
||||||
@@ -107,7 +146,20 @@ const Models: React.FC<ModelsProps> = ({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Ollama Library',
|
label: (
|
||||||
|
<span className="flex-center flex-between">
|
||||||
|
<span>Ollama Library</span>
|
||||||
|
<Tag style={{ marginRight: 0 }} className="m-l-10">
|
||||||
|
{platform.isMac ? (
|
||||||
|
<>
|
||||||
|
<IconFont type="icon-command"></IconFont> + 2
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>CTRL + 2</>
|
||||||
|
)}
|
||||||
|
</Tag>
|
||||||
|
</span>
|
||||||
|
),
|
||||||
value: modelSourceMap.ollama_library_value,
|
value: modelSourceMap.ollama_library_value,
|
||||||
key: 'ollama_library',
|
key: 'ollama_library',
|
||||||
icon: <IconFont type="icon-ollama"></IconFont>,
|
icon: <IconFont type="icon-ollama"></IconFont>,
|
||||||
|
|||||||
@@ -2,11 +2,7 @@ import React from 'react';
|
|||||||
import '../style/title-wrapper.less';
|
import '../style/title-wrapper.less';
|
||||||
|
|
||||||
const TitleWrapper: React.FC<any> = ({ children }) => {
|
const TitleWrapper: React.FC<any> = ({ children }) => {
|
||||||
return (
|
return <h3 className="h3">{children}</h3>;
|
||||||
<h3 className="h3">
|
|
||||||
<span>{children}</span>
|
|
||||||
</h3>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default TitleWrapper;
|
export default TitleWrapper;
|
||||||
|
|||||||
@@ -44,13 +44,13 @@ export const ollamaModelOptions = [
|
|||||||
tags: ['7B'],
|
tags: ['7B'],
|
||||||
id: 'mistral'
|
id: 'mistral'
|
||||||
},
|
},
|
||||||
{
|
// {
|
||||||
label: 'llava',
|
// label: 'llava',
|
||||||
value: 'llava',
|
// value: 'llava',
|
||||||
name: 'llava',
|
// name: 'llava',
|
||||||
tags: ['7B', '13B', '34B'],
|
// tags: ['7B', '13B', '34B'],
|
||||||
id: 'llava'
|
// id: 'llava'
|
||||||
},
|
// },
|
||||||
{
|
{
|
||||||
label: 'qwen2',
|
label: 'qwen2',
|
||||||
value: 'qwen2',
|
value: 'qwen2',
|
||||||
@@ -59,11 +59,11 @@ export const ollamaModelOptions = [
|
|||||||
id: 'qwen2'
|
id: 'qwen2'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'phi3',
|
label: 'phi3.5',
|
||||||
value: 'phi3',
|
value: 'phi3.5',
|
||||||
name: 'phi3',
|
name: 'phi3.5',
|
||||||
tags: ['3B', '14B'],
|
tags: ['3B'],
|
||||||
id: 'phi3'
|
id: 'phi3.5'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'codellama',
|
label: 'codellama',
|
||||||
@@ -73,11 +73,11 @@ export const ollamaModelOptions = [
|
|||||||
id: 'codellama'
|
id: 'codellama'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'deepseek-coder',
|
label: 'deepseek-coder-v2',
|
||||||
value: 'deepseek-coder',
|
value: 'deepseek-coder-v2',
|
||||||
name: 'deepseek-coder',
|
name: 'deepseek-coder-v2',
|
||||||
tags: ['1B', '7B', '33B'],
|
tags: ['16B', '236B'],
|
||||||
id: 'deepseek-coder'
|
id: 'deepseek-coder-v2'
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -143,7 +143,7 @@ export const ActionList = [
|
|||||||
|
|
||||||
export const modelFilesSortOptions = [
|
export const modelFilesSortOptions = [
|
||||||
// { label: 'Trending', value: 'trendingScore' },
|
// { label: 'Trending', value: 'trendingScore' },
|
||||||
{ label: 'Likes', value: 'likes' },
|
{ label: 'models.sort.likes', value: 'likes' },
|
||||||
{ label: 'Downloads', value: 'downloads' },
|
{ label: 'models.sort.downloads', value: 'downloads' },
|
||||||
{ label: 'Updated', value: 'updatedAt' }
|
{ label: 'models.sort.updated', value: 'updatedAt' }
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ const Models: React.FC = () => {
|
|||||||
} else {
|
} else {
|
||||||
setDataSource({
|
setDataSource({
|
||||||
...dataSource,
|
...dataSource,
|
||||||
|
loading: !!res.items.length,
|
||||||
total: res.pagination.total
|
total: res.pagination.total
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,9 @@
|
|||||||
|
|
||||||
.title {
|
.title {
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tag-item {
|
.tag-item {
|
||||||
@@ -18,12 +21,24 @@
|
|||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
border: 1px solid var(--ant-color-border);
|
|
||||||
color: var(--ant-color-text-secondary);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn {
|
.btn {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
z-index: 100;
|
z-index: 100;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
padding: @padding;
|
padding: @padding;
|
||||||
padding-top: 0;
|
padding-top: 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user