feat: vllm support
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
import LabelSelector from '@/components/label-selector';
|
||||
import ListInput from '@/components/list-input';
|
||||
import SealSelect from '@/components/seal-form/seal-select';
|
||||
import { PageAction } from '@/config';
|
||||
import { PageActionType } from '@/config/types';
|
||||
import { InfoCircleOutlined, RightOutlined } from '@ant-design/icons';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import {
|
||||
@@ -13,22 +16,28 @@ import {
|
||||
} from 'antd';
|
||||
import _ from 'lodash';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { placementStrategyOptions } from '../config';
|
||||
import { backendOptionsMap, placementStrategyOptions } from '../config';
|
||||
import llamaConfig from '../config/llama-config';
|
||||
import { FormData } from '../config/types';
|
||||
import vllmConfig from '../config/vllm-config';
|
||||
import dataformStyles from '../style/data-form.less';
|
||||
import GPUCard from './gpu-card';
|
||||
|
||||
interface AdvanceConfigProps {
|
||||
isGGUF: boolean;
|
||||
form: FormInstance;
|
||||
gpuOptions: Array<any>;
|
||||
action: PageActionType;
|
||||
}
|
||||
|
||||
const AdvanceConfig: React.FC<AdvanceConfigProps> = (props) => {
|
||||
const { form, gpuOptions } = props;
|
||||
const { form, gpuOptions, isGGUF, action } = props;
|
||||
|
||||
const intl = useIntl();
|
||||
const wokerSelector = Form.useWatch('worker_selector', form);
|
||||
const scheduleType = Form.useWatch('scheduleType', form);
|
||||
const backend = Form.useWatch('backend', form);
|
||||
const [params, setParams] = React.useState<string[]>([]);
|
||||
|
||||
const placementStrategyTips = [
|
||||
{
|
||||
@@ -64,6 +73,10 @@ const AdvanceConfig: React.FC<AdvanceConfigProps> = (props) => {
|
||||
}
|
||||
];
|
||||
|
||||
const paramsConfig = useMemo(() => {
|
||||
return backend === backendOptionsMap.llamaBox ? llamaConfig : vllmConfig;
|
||||
}, [backend]);
|
||||
|
||||
const renderSelectTips = (list: Array<{ title: string; tips: string }>) => {
|
||||
return (
|
||||
<div>
|
||||
@@ -96,6 +109,10 @@ const AdvanceConfig: React.FC<AdvanceConfigProps> = (props) => {
|
||||
[]
|
||||
);
|
||||
|
||||
const handleBackendParametersChange = useCallback((list: string[]) => {
|
||||
form.setFieldValue('backend_parameters', list);
|
||||
}, []);
|
||||
|
||||
const collapseItems = useMemo(() => {
|
||||
const children = (
|
||||
<>
|
||||
@@ -176,6 +193,33 @@ const AdvanceConfig: React.FC<AdvanceConfigProps> = (props) => {
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
<Form.Item name="backend">
|
||||
<SealSelect
|
||||
label={intl.formatMessage({ id: 'models.form.backend' })}
|
||||
options={[
|
||||
{
|
||||
label: `llama-box(llama.cpp)`,
|
||||
value: backendOptionsMap.llamaBox,
|
||||
disabled: !isGGUF
|
||||
},
|
||||
{
|
||||
label: 'vLLM',
|
||||
value: backendOptionsMap.vllm,
|
||||
disabled: isGGUF
|
||||
}
|
||||
]}
|
||||
disabled={action === PageAction.EDIT}
|
||||
></SealSelect>
|
||||
</Form.Item>
|
||||
<Form.Item<FormData> name="backend_parameters">
|
||||
<ListInput
|
||||
btnText="common.button.addParams"
|
||||
label={intl.formatMessage({ id: 'models.form.backend_parameters' })}
|
||||
dataList={form.getFieldValue('backend_parameters') || []}
|
||||
onChange={handleBackendParametersChange}
|
||||
options={paramsConfig}
|
||||
></ListInput>
|
||||
</Form.Item>
|
||||
{scheduleType === 'manual' && (
|
||||
<Form.Item<FormData>
|
||||
name="gpu_selector"
|
||||
@@ -202,34 +246,36 @@ const AdvanceConfig: React.FC<AdvanceConfigProps> = (props) => {
|
||||
</SealSelect>
|
||||
</Form.Item>
|
||||
)}
|
||||
<div style={{ paddingBottom: 22, paddingLeft: 10 }}>
|
||||
<Form.Item<FormData>
|
||||
name="cpu_offloading"
|
||||
valuePropName="checked"
|
||||
style={{ padding: '0 10px', marginBottom: 0 }}
|
||||
noStyle
|
||||
>
|
||||
<Checkbox className="p-l-6">
|
||||
<Tooltip
|
||||
trigger={['click']}
|
||||
title={intl.formatMessage({
|
||||
id: 'models.form.partialoffload.tips'
|
||||
})}
|
||||
>
|
||||
<span style={{ color: 'var(--ant-color-text-tertiary)' }}>
|
||||
{intl.formatMessage({
|
||||
id: 'resources.form.enablePartialOffload'
|
||||
{isGGUF && (
|
||||
<div style={{ paddingBottom: 22, paddingLeft: 10 }}>
|
||||
<Form.Item<FormData>
|
||||
name="cpu_offloading"
|
||||
valuePropName="checked"
|
||||
style={{ padding: '0 10px', marginBottom: 0 }}
|
||||
noStyle
|
||||
>
|
||||
<Checkbox className="p-l-6">
|
||||
<Tooltip
|
||||
trigger={['click']}
|
||||
title={intl.formatMessage({
|
||||
id: 'models.form.partialoffload.tips'
|
||||
})}
|
||||
</span>
|
||||
<InfoCircleOutlined
|
||||
className="m-l-4"
|
||||
style={{ color: 'var(--ant-color-text-tertiary)' }}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Checkbox>
|
||||
</Form.Item>
|
||||
</div>
|
||||
{scheduleType === 'auto' && (
|
||||
>
|
||||
<span style={{ color: 'var(--ant-color-text-tertiary)' }}>
|
||||
{intl.formatMessage({
|
||||
id: 'resources.form.enablePartialOffload'
|
||||
})}
|
||||
</span>
|
||||
<InfoCircleOutlined
|
||||
className="m-l-4"
|
||||
style={{ color: 'var(--ant-color-text-tertiary)' }}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Checkbox>
|
||||
</Form.Item>
|
||||
</div>
|
||||
)}
|
||||
{scheduleType === 'auto' && isGGUF && (
|
||||
<div style={{ paddingBottom: 22, paddingLeft: 10 }}>
|
||||
<Form.Item<FormData>
|
||||
name="distributed_inference_across_workers"
|
||||
@@ -270,7 +316,15 @@ const AdvanceConfig: React.FC<AdvanceConfigProps> = (props) => {
|
||||
children
|
||||
}
|
||||
];
|
||||
}, [form, intl, gpuOptions, scheduleType, wokerSelector]);
|
||||
}, [
|
||||
form,
|
||||
intl,
|
||||
gpuOptions,
|
||||
paramsConfig,
|
||||
scheduleType,
|
||||
wokerSelector,
|
||||
isGGUF
|
||||
]);
|
||||
|
||||
return (
|
||||
<Collapse
|
||||
@@ -289,4 +343,4 @@ const AdvanceConfig: React.FC<AdvanceConfigProps> = (props) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default AdvanceConfig;
|
||||
export default React.memo(AdvanceConfig);
|
||||
|
||||
@@ -10,7 +10,7 @@ const ColumnWrapper: React.FC<any> = ({ children, footer, height }) => {
|
||||
<div className="column-wrapper">
|
||||
<SimpleBar
|
||||
style={{
|
||||
height: height || 'calc(100vh - 89px)',
|
||||
maxHeight: height || 'calc(100vh - 89px)',
|
||||
paddingBottom: '50px'
|
||||
}}
|
||||
>
|
||||
@@ -23,7 +23,7 @@ const ColumnWrapper: React.FC<any> = ({ children, footer, height }) => {
|
||||
}
|
||||
return (
|
||||
<div className="column-wrapper">
|
||||
<SimpleBar style={{ height: height || 'calc(100vh - 89px)' }}>
|
||||
<SimpleBar style={{ maxHeight: height || 'calc(100vh - 89px)' }}>
|
||||
{children}
|
||||
</SimpleBar>
|
||||
</div>
|
||||
|
||||
@@ -14,7 +14,11 @@ import React, {
|
||||
useState
|
||||
} from 'react';
|
||||
import { queryGPUList } from '../apis';
|
||||
import { modelSourceMap, ollamaModelOptions } from '../config';
|
||||
import {
|
||||
backendOptionsMap,
|
||||
modelSourceMap,
|
||||
ollamaModelOptions
|
||||
} from '../config';
|
||||
import { FormData, GPUListItem } from '../config/types';
|
||||
import AdvanceConfig from './advance-config';
|
||||
|
||||
@@ -23,6 +27,7 @@ interface DataFormProps {
|
||||
source: string;
|
||||
action: PageActionType;
|
||||
selectedModel: any;
|
||||
isGGUF: boolean;
|
||||
onOk: (values: FormData) => void;
|
||||
}
|
||||
|
||||
@@ -49,7 +54,7 @@ const SEARCH_SOURCE = [
|
||||
];
|
||||
|
||||
const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
|
||||
const { action, onOk } = props;
|
||||
const { action, isGGUF, onOk } = props;
|
||||
const [form] = Form.useForm();
|
||||
const intl = useIntl();
|
||||
const [gpuOptions, setGpuOptions] = useState<
|
||||
@@ -140,27 +145,29 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
|
||||
disabled={true}
|
||||
></SealInput.Input>
|
||||
</Form.Item>
|
||||
<Form.Item<FormData>
|
||||
name="file_name"
|
||||
key="file_name"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: intl.formatMessage(
|
||||
{
|
||||
id: 'common.form.rule.input'
|
||||
},
|
||||
{ name: intl.formatMessage({ id: 'models.form.filename' }) }
|
||||
)
|
||||
}
|
||||
]}
|
||||
>
|
||||
<SealInput.Input
|
||||
label={intl.formatMessage({ id: 'models.form.filename' })}
|
||||
required
|
||||
disabled={true}
|
||||
></SealInput.Input>
|
||||
</Form.Item>
|
||||
{isGGUF && (
|
||||
<Form.Item<FormData>
|
||||
name="file_name"
|
||||
key="file_name"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: intl.formatMessage(
|
||||
{
|
||||
id: 'common.form.rule.input'
|
||||
},
|
||||
{ name: intl.formatMessage({ id: 'models.form.filename' }) }
|
||||
)
|
||||
}
|
||||
]}
|
||||
>
|
||||
<SealInput.Input
|
||||
label={intl.formatMessage({ id: 'models.form.filename' })}
|
||||
required
|
||||
disabled={true}
|
||||
></SealInput.Input>
|
||||
</Form.Item>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -238,7 +245,7 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
|
||||
}
|
||||
|
||||
return null;
|
||||
}, [props.source]);
|
||||
}, [props.source, isGGUF]);
|
||||
|
||||
const handleOk = (formdata: FormData) => {
|
||||
const gpu = _.find(gpuOptions, (item: any) => {
|
||||
@@ -260,6 +267,14 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (action === PageAction.CREATE) {
|
||||
form.setFieldValue(
|
||||
'backend',
|
||||
isGGUF ? backendOptionsMap.llamaBox : backendOptionsMap.vllm
|
||||
);
|
||||
}
|
||||
}, [isGGUF]);
|
||||
useEffect(() => {
|
||||
handleOnSelectModel();
|
||||
}, [props.selectedModel.name]);
|
||||
@@ -360,7 +375,12 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
|
||||
})}
|
||||
></SealInput.TextArea>
|
||||
</Form.Item>
|
||||
<AdvanceConfig form={form} gpuOptions={gpuOptions}></AdvanceConfig>
|
||||
<AdvanceConfig
|
||||
form={form}
|
||||
gpuOptions={gpuOptions}
|
||||
isGGUF={isGGUF}
|
||||
action={action}
|
||||
></AdvanceConfig>
|
||||
</Form>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -45,6 +45,8 @@ const AddModal: React.FC<AddModalProps> = (props) => {
|
||||
const [selectedModel, setSelectedModel] = useState<any>({});
|
||||
const [collapsed, setCollapsed] = useState<boolean>(false);
|
||||
const [loadingModel, setLoadingModel] = useState<boolean>(false);
|
||||
const [isGGUF, setIsGGUF] = useState<boolean>(false);
|
||||
const modelFileRef = useRef<any>(null);
|
||||
|
||||
const handleSelectModelFile = useCallback((item: any) => {
|
||||
form.current?.setFieldValue?.('file_name', item.fakeName);
|
||||
@@ -58,6 +60,15 @@ const AddModal: React.FC<AddModalProps> = (props) => {
|
||||
form.current?.submit?.();
|
||||
};
|
||||
|
||||
const handleSetIsGGUF = (flag: boolean) => {
|
||||
setIsGGUF(flag);
|
||||
if (flag) {
|
||||
setTimeout(() => {
|
||||
modelFileRef.current?.fetchModelFiles?.();
|
||||
}, 50);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
setSelectedModel({});
|
||||
@@ -121,13 +132,17 @@ const AddModal: React.FC<AddModalProps> = (props) => {
|
||||
onCollapse={setCollapsed}
|
||||
collapsed={collapsed}
|
||||
modelSource={props.source}
|
||||
setIsGGUF={handleSetIsGGUF}
|
||||
></ModelCard>
|
||||
<HFModelFile
|
||||
selectedModel={selectedModel}
|
||||
modelSource={props.source}
|
||||
onSelectFile={handleSelectModelFile}
|
||||
collapsed={collapsed}
|
||||
></HFModelFile>
|
||||
{isGGUF && (
|
||||
<HFModelFile
|
||||
ref={modelFileRef}
|
||||
selectedModel={selectedModel}
|
||||
modelSource={props.source}
|
||||
onSelectFile={handleSelectModelFile}
|
||||
collapsed={collapsed}
|
||||
></HFModelFile>
|
||||
)}
|
||||
</ColumnWrapper>
|
||||
<Separator></Separator>
|
||||
</div>
|
||||
@@ -159,6 +174,7 @@ const AddModal: React.FC<AddModalProps> = (props) => {
|
||||
selectedModel={selectedModel}
|
||||
onOk={onOk}
|
||||
ref={form}
|
||||
isGGUF={isGGUF}
|
||||
></DataForm>
|
||||
</>
|
||||
</ColumnWrapper>
|
||||
|
||||
@@ -4,7 +4,15 @@ import { useIntl } from '@umijs/max';
|
||||
import { Col, Empty, Row, Select, Spin, Tag, Tooltip } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import _ from 'lodash';
|
||||
import { memo, useCallback, useEffect, useRef, useState } from 'react';
|
||||
import {
|
||||
forwardRef,
|
||||
memo,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useImperativeHandle,
|
||||
useRef,
|
||||
useState
|
||||
} from 'react';
|
||||
import SimpleBar from 'simplebar-react';
|
||||
import 'simplebar-react/dist/simplebar.min.css';
|
||||
import { queryHuggingfaceModelFiles, queryModelScopeModelFiles } from '../apis';
|
||||
@@ -19,12 +27,16 @@ interface HFModelFileProps {
|
||||
collapsed?: boolean;
|
||||
loadingModel?: boolean;
|
||||
modelSource: string;
|
||||
ref: any;
|
||||
onSelectFile?: (file: any) => void;
|
||||
}
|
||||
|
||||
const pattern = /^(.*)-(\d+)-of-(\d+)\.gguf$/;
|
||||
const pattern = /^(.*)-(\d+)-of-(\d+)\.(.*)$/;
|
||||
|
||||
const HFModelFile: React.FC<HFModelFileProps> = (props) => {
|
||||
const filterReg = /\.(safetensors|gguf)$/i;
|
||||
const includeReg = /\.(safetensors|gguf)$/i;
|
||||
|
||||
const HFModelFile: React.FC<HFModelFileProps> = forwardRef((props, ref) => {
|
||||
const { collapsed, modelSource } = props;
|
||||
const intl = useIntl();
|
||||
const [dataSource, setDataSource] = useState<any>({
|
||||
@@ -58,7 +70,8 @@ const HFModelFile: React.FC<HFModelFileProps> = (props) => {
|
||||
return {
|
||||
filename: match[1],
|
||||
part: parseInt(match[2], 10),
|
||||
total: parseInt(match[3], 10)
|
||||
total: parseInt(match[3], 10),
|
||||
extension: match[4]
|
||||
};
|
||||
} else {
|
||||
return null;
|
||||
@@ -101,7 +114,7 @@ const HFModelFile: React.FC<HFModelFileProps> = (props) => {
|
||||
(value: any[], filename: string) => {
|
||||
return {
|
||||
path: filename,
|
||||
fakeName: `${filename}*.gguf`,
|
||||
fakeName: `${filename}*.${_.get(value, '[0].extension')}`,
|
||||
size: _.sumBy(value, 'size'),
|
||||
parts: value
|
||||
};
|
||||
@@ -125,8 +138,9 @@ const HFModelFile: React.FC<HFModelFileProps> = (props) => {
|
||||
});
|
||||
|
||||
const list = _.filter(fileList, (file: any) => {
|
||||
return _.endsWith(file.path, '.gguf') || _.includes(file.path, '.gguf');
|
||||
return filterReg.test(file.path) || _.includes(includeReg, file.path);
|
||||
});
|
||||
|
||||
return list;
|
||||
} catch (error) {
|
||||
return [];
|
||||
@@ -145,7 +159,7 @@ const HFModelFile: React.FC<HFModelFileProps> = (props) => {
|
||||
}
|
||||
);
|
||||
const fileList = _.filter(_.get(data, ['Data', 'Files']), (file: any) => {
|
||||
return _.endsWith(file.Path, '.gguf') || _.includes(file.Path, '.gguf');
|
||||
return filterReg.test(file.path) || _.includes(includeReg, file.path);
|
||||
});
|
||||
const list = _.map(fileList, (item: any) => {
|
||||
return {
|
||||
@@ -226,10 +240,13 @@ const HFModelFile: React.FC<HFModelFileProps> = (props) => {
|
||||
handleSelectModelFile(item);
|
||||
}
|
||||
};
|
||||
useImperativeHandle(ref, () => ({
|
||||
fetchModelFiles: handleFetchModelFiles
|
||||
}));
|
||||
|
||||
useEffect(() => {
|
||||
handleFetchModelFiles();
|
||||
}, [props.selectedModel.name]);
|
||||
// useEffect(() => {
|
||||
// handleFetchModelFiles();
|
||||
// }, [props.selectedModel.name]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
@@ -340,6 +357,6 @@ const HFModelFile: React.FC<HFModelFileProps> = (props) => {
|
||||
</SimpleBar>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
export default memo(HFModelFile);
|
||||
|
||||
@@ -23,7 +23,7 @@ interface HFModelItemProps {
|
||||
source?: string;
|
||||
tags?: string[];
|
||||
}
|
||||
const warningTask = ['image', 'audio', 'video'];
|
||||
const warningTask = ['audio', 'video'];
|
||||
|
||||
const SUPPORTEDSOURCE = [
|
||||
modelSourceMap.huggingface_value,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import HighlightCode from '@/components/highlight-code';
|
||||
import IconFont from '@/components/icon-font';
|
||||
import MarkdownViewer from '@/components/markdown-viewer';
|
||||
import useRequestToken from '@/hooks/use-request-token';
|
||||
import {
|
||||
DownOutlined,
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
RightOutlined
|
||||
} from '@ant-design/icons';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { Button, Empty, Tag, Tooltip } from 'antd';
|
||||
import { Button, Empty, Spin, Tag, Tooltip } from 'antd';
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import SimpleBar from 'simplebar-react';
|
||||
import 'simplebar-react/dist/simplebar.min.css';
|
||||
@@ -21,19 +21,22 @@ import '../style/model-card.less';
|
||||
import TitleWrapper from './title-wrapper';
|
||||
|
||||
const ModelCard: React.FC<{
|
||||
selectedModel: any;
|
||||
onCollapse: (flag: boolean) => void;
|
||||
setIsGGUF: (flag: boolean) => void;
|
||||
selectedModel: any;
|
||||
collapsed: boolean;
|
||||
loadingModel?: boolean;
|
||||
modelSource: string;
|
||||
}> = (props) => {
|
||||
const { onCollapse, collapsed, modelSource } = props;
|
||||
const { onCollapse, setIsGGUF, collapsed, modelSource } = props;
|
||||
const intl = useIntl();
|
||||
const requestSource = useRequestToken();
|
||||
const [modelData, setModelData] = useState<any>({});
|
||||
const [readmeText, setReadmeText] = useState<string | null>(null);
|
||||
const requestToken = useRef<any>(null);
|
||||
const axiosTokenRef = useRef<any>(null);
|
||||
const [isGGUFModel, setIsGGUFModel] = useState<boolean>(false);
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
|
||||
const loadFile = async (repo: string, sha: string) => {
|
||||
try {
|
||||
@@ -70,9 +73,13 @@ const ModelCard: React.FC<{
|
||||
|
||||
setModelData(modelcard);
|
||||
setReadmeText(readme);
|
||||
setIsGGUF(modelcard.tags?.includes('gguf'));
|
||||
setIsGGUFModel(modelcard.tags?.includes('gguf'));
|
||||
} catch (error) {
|
||||
setModelData({});
|
||||
setReadmeText(null);
|
||||
setIsGGUF(false);
|
||||
setIsGGUFModel(false);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -86,15 +93,18 @@ const ModelCard: React.FC<{
|
||||
token: requestToken.current.token
|
||||
}
|
||||
);
|
||||
console.log('detaildata==========', data);
|
||||
setModelData({
|
||||
...data?.Data,
|
||||
name: `${data.Data?.Path}/${data.Data?.Name}`
|
||||
});
|
||||
setReadmeText(data?.Data?.ReadMeContent);
|
||||
setIsGGUF(data.Data?.Tags?.includes('gguf'));
|
||||
setIsGGUFModel(data.Data?.Tags?.includes('gguf'));
|
||||
} catch (error) {
|
||||
setModelData({});
|
||||
setReadmeText(null);
|
||||
setIsGGUF(false);
|
||||
setIsGGUFModel(false);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -105,11 +115,13 @@ const ModelCard: React.FC<{
|
||||
}
|
||||
requestToken.current?.cancel?.();
|
||||
requestToken.current = requestSource();
|
||||
setLoading(true);
|
||||
if (modelSource === modelSourceMap.huggingface_value) {
|
||||
getHuggingfaceModelDetail();
|
||||
await getHuggingfaceModelDetail();
|
||||
} else if (modelSource === modelSourceMap.modelscope_value) {
|
||||
getModelScopeModelDetail();
|
||||
await getModelScopeModelDetail();
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
const handleCollapse = useCallback(() => {
|
||||
@@ -191,7 +203,7 @@ const ModelCard: React.FC<{
|
||||
</Tag>
|
||||
)}
|
||||
</div>
|
||||
{readmeText && (
|
||||
{readmeText && isGGUFModel && (
|
||||
<div
|
||||
style={{
|
||||
borderRadius: 4,
|
||||
@@ -213,12 +225,10 @@ const ModelCard: React.FC<{
|
||||
maxHeight: collapsed ? 300 : 0
|
||||
}}
|
||||
>
|
||||
<HighlightCode
|
||||
code={readmeText}
|
||||
lang="markdown"
|
||||
copyable={false}
|
||||
<MarkdownViewer
|
||||
content={readmeText}
|
||||
theme="light"
|
||||
></HighlightCode>
|
||||
></MarkdownViewer>
|
||||
</SimpleBar>
|
||||
</div>
|
||||
)}
|
||||
@@ -230,6 +240,21 @@ const ModelCard: React.FC<{
|
||||
></Empty>
|
||||
)}
|
||||
</div>
|
||||
{!isGGUFModel && readmeText && (
|
||||
<div>
|
||||
<TitleWrapper>
|
||||
<div className="title">README.md</div>
|
||||
</TitleWrapper>
|
||||
<div className="card-wrapper">
|
||||
<Spin spinning={loading}>
|
||||
<MarkdownViewer
|
||||
content={readmeText}
|
||||
theme="light"
|
||||
></MarkdownViewer>
|
||||
</Spin>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { BulbOutlined } from '@ant-design/icons';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { Select } from 'antd';
|
||||
import { Checkbox, Select } from 'antd';
|
||||
import _ from 'lodash';
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { queryHuggingfaceModels, queryModelScopeModels } from '../apis';
|
||||
@@ -44,6 +44,7 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
|
||||
const cacheRepoOptions = useRef<any[]>([]);
|
||||
const axiosTokenRef = useRef<any>(null);
|
||||
const searchInputRef = useRef<any>('');
|
||||
const filterGGUFRef = useRef<boolean>(true);
|
||||
const modelFilesSortOptions = useRef<any[]>([
|
||||
{
|
||||
label: intl.formatMessage({ id: 'models.sort.trending' }),
|
||||
@@ -77,7 +78,7 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
|
||||
search: {
|
||||
query: searchInputRef.current || '',
|
||||
sort: sort,
|
||||
tags: ['gguf'],
|
||||
tags: filterGGUFRef.current ? ['gguf'] : [],
|
||||
task
|
||||
}
|
||||
};
|
||||
@@ -101,7 +102,9 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
|
||||
const getModelsFromModelscope = useCallback(async (sort: string) => {
|
||||
try {
|
||||
const params = {
|
||||
Name: searchInputRef.current || '',
|
||||
Name: filterGGUFRef.current
|
||||
? `${searchInputRef.current} gguf`
|
||||
: searchInputRef.current || '',
|
||||
SortBy: ModelScopeSortType[sort]
|
||||
};
|
||||
const data = await queryModelScopeModels(params, {
|
||||
@@ -205,6 +208,12 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
|
||||
handleOnSearchRepo(value || '');
|
||||
};
|
||||
|
||||
const handleFilterGGUFChange = (e: any) => {
|
||||
console.log('filterggufChange:', e.target.checked);
|
||||
filterGGUFRef.current = e.target.checked;
|
||||
handleOnSearchRepo();
|
||||
};
|
||||
|
||||
const renderHFSearch = () => {
|
||||
return (
|
||||
<>
|
||||
@@ -221,21 +230,30 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
|
||||
)}
|
||||
</span>
|
||||
</span>
|
||||
<Select
|
||||
allowClear
|
||||
value={dataSource.sortType}
|
||||
onChange={handleSortChange}
|
||||
labelRender={({ label }) => {
|
||||
return (
|
||||
<span>
|
||||
{intl.formatMessage({ id: 'model.deploy.sort' })}: {label}
|
||||
</span>
|
||||
);
|
||||
}}
|
||||
options={modelFilesSortOptions.current}
|
||||
size="middle"
|
||||
style={{ width: '150px' }}
|
||||
></Select>
|
||||
<span>
|
||||
<Checkbox
|
||||
onChange={handleFilterGGUFChange}
|
||||
className="m-r-5"
|
||||
checked={filterGGUFRef.current}
|
||||
>
|
||||
GGUF
|
||||
</Checkbox>
|
||||
<Select
|
||||
allowClear
|
||||
value={dataSource.sortType}
|
||||
onChange={handleSortChange}
|
||||
labelRender={({ label }) => {
|
||||
return (
|
||||
<span>
|
||||
{intl.formatMessage({ id: 'model.deploy.sort' })}: {label}
|
||||
</span>
|
||||
);
|
||||
}}
|
||||
options={modelFilesSortOptions.current}
|
||||
size="middle"
|
||||
style={{ width: '150px' }}
|
||||
></Select>
|
||||
</span>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import IconFont from '@/components/icon-font';
|
||||
import { SearchOutlined } from '@ant-design/icons';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { Col, Empty, Row, Spin } from 'antd';
|
||||
import { Button, Col, Empty, Row, Spin } from 'antd';
|
||||
import React from 'react';
|
||||
import SimpleBar from 'simplebar-react';
|
||||
import 'simplebar-react/dist/simplebar.min.css';
|
||||
import { modelSourceMap } from '../config';
|
||||
import '../style/search-result.less';
|
||||
import HFModelItem from './hf-model-item';
|
||||
|
||||
@@ -49,24 +50,26 @@ const SearchResult: React.FC<SearchResultProps> = (props) => {
|
||||
></IconFont>
|
||||
}
|
||||
description={
|
||||
<div className="flex-column gap-5">
|
||||
<span>
|
||||
{intl.formatMessage({ id: 'models.search.networkerror' })}
|
||||
</span>
|
||||
{/* <span>
|
||||
source === modelSourceMap.huggingface_value ? (
|
||||
<div className="flex-column gap-5">
|
||||
<span>
|
||||
{intl.formatMessage({ id: 'models.search.hfvisit' })}
|
||||
{intl.formatMessage({ id: 'models.search.networkerror' })}
|
||||
</span>
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
href="https://huggingface.co/"
|
||||
target="_blank"
|
||||
>
|
||||
Hugging Face
|
||||
</Button>
|
||||
</span> */}
|
||||
</div>
|
||||
<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>
|
||||
) : null
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -418,10 +418,10 @@ const Models: React.FC<ModelsProps> = ({
|
||||
|
||||
const generateSource = useCallback((record: ListItem) => {
|
||||
if (record.source === modelSourceMap.modelscope_value) {
|
||||
return `${modelSourceMap.modelScope} / ${record.model_scope_file_path}`;
|
||||
return `${modelSourceMap.modelScope} / ${record.model_scope_file_path || record.model_scope_model_id}`;
|
||||
}
|
||||
if (record.source === modelSourceMap.huggingface_value) {
|
||||
return `${modelSourceMap.huggingface} / ${record.huggingface_filename}`;
|
||||
return `${modelSourceMap.huggingface} / ${record.huggingface_filename || record.huggingface_repo_id}`;
|
||||
}
|
||||
return `${modelSourceMap.ollama_library} / ${record.ollama_library_model_name}`;
|
||||
}, []);
|
||||
|
||||
@@ -12,7 +12,11 @@ import { memo, useEffect, useMemo, useState } from 'react';
|
||||
import SimpleBar from 'simplebar-react';
|
||||
import 'simplebar-react/dist/simplebar.min.css';
|
||||
import { queryGPUList, queryHuggingfaceModelFiles } from '../apis';
|
||||
import { modelSourceMap, setSourceRepoConfigValue } from '../config';
|
||||
import {
|
||||
backendOptionsMap,
|
||||
modelSourceMap,
|
||||
setSourceRepoConfigValue
|
||||
} from '../config';
|
||||
import { FormData, GPUListItem, ListItem } from '../config/types';
|
||||
import AdvanceConfig from './advance-config';
|
||||
|
||||
@@ -160,29 +164,31 @@ const UpdateModal: React.FC<AddModalProps> = (props) => {
|
||||
disabled={true}
|
||||
></SealInput.Input>
|
||||
</Form.Item>
|
||||
<Form.Item<FormData>
|
||||
name="file_name"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: intl.formatMessage(
|
||||
{
|
||||
id: 'common.form.rule.input'
|
||||
},
|
||||
{ name: intl.formatMessage({ id: 'models.form.filename' }) }
|
||||
)
|
||||
}
|
||||
]}
|
||||
>
|
||||
<SealAutoComplete
|
||||
filterOption
|
||||
label={intl.formatMessage({ id: 'models.form.filename' })}
|
||||
required
|
||||
options={fileOptions}
|
||||
loading={loading}
|
||||
disabled={action === PageAction.EDIT}
|
||||
></SealAutoComplete>
|
||||
</Form.Item>
|
||||
{form.getFieldValue('file_name') && (
|
||||
<Form.Item<FormData>
|
||||
name="file_name"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: intl.formatMessage(
|
||||
{
|
||||
id: 'common.form.rule.input'
|
||||
},
|
||||
{ name: intl.formatMessage({ id: 'models.form.filename' }) }
|
||||
)
|
||||
}
|
||||
]}
|
||||
>
|
||||
<SealAutoComplete
|
||||
filterOption
|
||||
label={intl.formatMessage({ id: 'models.form.filename' })}
|
||||
required
|
||||
options={fileOptions}
|
||||
loading={loading}
|
||||
disabled={action === PageAction.EDIT}
|
||||
></SealAutoComplete>
|
||||
</Form.Item>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -418,7 +424,14 @@ const UpdateModal: React.FC<AddModalProps> = (props) => {
|
||||
></SealInput.TextArea>
|
||||
</Form.Item>
|
||||
|
||||
<AdvanceConfig form={form} gpuOptions={gpuOptions}></AdvanceConfig>
|
||||
<AdvanceConfig
|
||||
form={form}
|
||||
gpuOptions={gpuOptions}
|
||||
action={PageAction.EDIT}
|
||||
isGGUF={
|
||||
form.getFieldValue('backend') === backendOptionsMap.llamaBox
|
||||
}
|
||||
></AdvanceConfig>
|
||||
</Form>
|
||||
</SimpleBar>
|
||||
</Modal>
|
||||
|
||||
Reference in New Issue
Block a user