chore: check compatibility ux
This commit is contained in:
@@ -12,6 +12,7 @@ interface AlertInfoProps {
|
|||||||
icon?: React.ReactNode;
|
icon?: React.ReactNode;
|
||||||
ellipsis?: boolean;
|
ellipsis?: boolean;
|
||||||
style?: React.CSSProperties;
|
style?: React.CSSProperties;
|
||||||
|
contentStyle?: React.CSSProperties;
|
||||||
title: React.ReactNode;
|
title: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,7 +31,15 @@ const ContentWrapper = styled.div<{ $hasTitle: boolean }>`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const AlertInfo: React.FC<AlertInfoProps> = (props) => {
|
const AlertInfo: React.FC<AlertInfoProps> = (props) => {
|
||||||
const { message, type, rows = 1, ellipsis, style, title } = props;
|
const {
|
||||||
|
message,
|
||||||
|
type,
|
||||||
|
rows = 1,
|
||||||
|
ellipsis,
|
||||||
|
style,
|
||||||
|
title,
|
||||||
|
contentStyle
|
||||||
|
} = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -51,7 +60,7 @@ const AlertInfo: React.FC<AlertInfoProps> = (props) => {
|
|||||||
<WarningFilled className={classNames('info-icon', type)} />
|
<WarningFilled className={classNames('info-icon', type)} />
|
||||||
</div>
|
</div>
|
||||||
{title && <TitleWrapper>{title}</TitleWrapper>}
|
{title && <TitleWrapper>{title}</TitleWrapper>}
|
||||||
<OverlayScroller maxHeight={80}>
|
<OverlayScroller maxHeight={80} style={{ ...contentStyle }}>
|
||||||
<ContentWrapper $hasTitle={!!title}>{message}</ContentWrapper>
|
<ContentWrapper $hasTitle={!!title}>{message}</ContentWrapper>
|
||||||
</OverlayScroller>
|
</OverlayScroller>
|
||||||
</Typography.Paragraph>
|
</Typography.Paragraph>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { Button, Space } from 'antd';
|
import { Button, Space } from 'antd';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
type ModalFooterProps = {
|
type ModalFooterProps = {
|
||||||
onOk?: () => void;
|
onOk?: () => void;
|
||||||
@@ -9,10 +10,12 @@ type ModalFooterProps = {
|
|||||||
htmlType?: 'button' | 'submit';
|
htmlType?: 'button' | 'submit';
|
||||||
okBtnProps?: any;
|
okBtnProps?: any;
|
||||||
cancelBtnProps?: any;
|
cancelBtnProps?: any;
|
||||||
align?: 'start' | 'end' | 'center' | 'baseline';
|
|
||||||
form?: any;
|
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
style?: React.CSSProperties;
|
style?: React.CSSProperties;
|
||||||
|
showOkBtn?: boolean;
|
||||||
|
showCancelBtn?: boolean;
|
||||||
|
extra?: React.ReactNode;
|
||||||
|
form?: any;
|
||||||
};
|
};
|
||||||
const ModalFooter: React.FC<ModalFooterProps> = ({
|
const ModalFooter: React.FC<ModalFooterProps> = ({
|
||||||
onOk,
|
onOk,
|
||||||
@@ -23,8 +26,9 @@ const ModalFooter: React.FC<ModalFooterProps> = ({
|
|||||||
cancelBtnProps,
|
cancelBtnProps,
|
||||||
loading,
|
loading,
|
||||||
htmlType = 'button',
|
htmlType = 'button',
|
||||||
align = 'end',
|
|
||||||
style,
|
style,
|
||||||
|
showOkBtn = true,
|
||||||
|
extra,
|
||||||
form
|
form
|
||||||
}) => {
|
}) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
@@ -33,16 +37,19 @@ const ModalFooter: React.FC<ModalFooterProps> = ({
|
|||||||
<Button onClick={onCancel} style={{ width: '88px' }} {...cancelBtnProps}>
|
<Button onClick={onCancel} style={{ width: '88px' }} {...cancelBtnProps}>
|
||||||
{cancelText || intl.formatMessage({ id: 'common.button.cancel' })}
|
{cancelText || intl.formatMessage({ id: 'common.button.cancel' })}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
{extra}
|
||||||
type="primary"
|
{showOkBtn && (
|
||||||
onClick={onOk}
|
<Button
|
||||||
style={{ width: '88px' }}
|
type="primary"
|
||||||
loading={loading}
|
onClick={onOk}
|
||||||
htmlType={htmlType}
|
style={{ width: '88px' }}
|
||||||
{...okBtnProps}
|
loading={loading}
|
||||||
>
|
htmlType={htmlType}
|
||||||
{okText || intl.formatMessage({ id: 'common.button.save' })}
|
{...okBtnProps}
|
||||||
</Button>
|
>
|
||||||
|
{okText || intl.formatMessage({ id: 'common.button.save' })}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</Space>
|
</Space>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,7 +10,12 @@ const Wrapper = styled.div<{ $maxHeight?: number }>`
|
|||||||
padding-inline: 10px;
|
padding-inline: 10px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const OverlayScroller: React.FC<any> = ({ children, maxHeight, theme }) => {
|
const OverlayScroller: React.FC<any> = ({
|
||||||
|
children,
|
||||||
|
maxHeight,
|
||||||
|
theme,
|
||||||
|
style
|
||||||
|
}) => {
|
||||||
const scroller = React.useRef<any>(null);
|
const scroller = React.useRef<any>(null);
|
||||||
const { initialize } = useOverlayScroller({
|
const { initialize } = useOverlayScroller({
|
||||||
options: {
|
options: {
|
||||||
@@ -25,7 +30,15 @@ const OverlayScroller: React.FC<any> = ({ children, maxHeight, theme }) => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Wrapper ref={scroller} $maxHeight={maxHeight || '100%'} hidden={false}>
|
<Wrapper
|
||||||
|
ref={scroller}
|
||||||
|
$maxHeight={maxHeight || '100%'}
|
||||||
|
hidden={false}
|
||||||
|
as="div"
|
||||||
|
style={{
|
||||||
|
...style
|
||||||
|
}}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -121,10 +121,8 @@ export default {
|
|||||||
'models.form.keyvalue.paste':
|
'models.form.keyvalue.paste':
|
||||||
'Paste multiple lines of text, with each line containing a key-value pair. The key and value are separated by an = sign, and different key-value pairs are separated by newline characters.',
|
'Paste multiple lines of text, with each line containing a key-value pair. The key and value are separated by an = sign, and different key-value pairs are separated by newline characters.',
|
||||||
'models.form.files': 'files',
|
'models.form.files': 'files',
|
||||||
'models.table.status': 'Status'
|
'models.table.status': 'Status',
|
||||||
|
'models.form.submit.anyway': 'Submit Anyway',
|
||||||
|
'models.form.evaluating': 'Evaluating...',
|
||||||
|
'models.form.incompatible': 'Incompatible'
|
||||||
};
|
};
|
||||||
|
|
||||||
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
|
||||||
// 1. 'models.form.files',
|
|
||||||
// 2. 'models.table.status',
|
|
||||||
// ========== End of To-Do List ==========
|
|
||||||
|
|||||||
@@ -118,10 +118,16 @@ export default {
|
|||||||
'models.form.keyvalue.paste':
|
'models.form.keyvalue.paste':
|
||||||
'複数行のテキストを貼り付けます。各行にはキーと値のペアが含まれ、キーと値は=記号で区切られ、異なるキーと値のペアは改行文字で区切られます。',
|
'複数行のテキストを貼り付けます。各行にはキーと値のペアが含まれ、キーと値は=記号で区切られ、異なるキーと値のペアは改行文字で区切られます。',
|
||||||
'models.form.files': 'files',
|
'models.form.files': 'files',
|
||||||
'models.table.status': 'Status'
|
'models.table.status': 'Status',
|
||||||
|
'models.form.submit.anyway': 'Submit Anyway',
|
||||||
|
'models.form.evaluating': 'Evaluating...',
|
||||||
|
'models.form.incompatible': 'Incompatible'
|
||||||
};
|
};
|
||||||
|
|
||||||
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
||||||
// 1. 'models.form.files',
|
// 1. 'models.form.files',
|
||||||
// 2. 'models.table.status'
|
// 2. 'models.table.status',
|
||||||
|
// 3. 'models.form.submit.anyway',
|
||||||
|
// 4. 'models.form.evaluating',
|
||||||
|
// 5. 'models.form.incompatible',
|
||||||
// ========== End of To-Do List ==========
|
// ========== End of To-Do List ==========
|
||||||
|
|||||||
@@ -121,9 +121,15 @@ export default {
|
|||||||
'models.form.keyvalue.paste':
|
'models.form.keyvalue.paste':
|
||||||
'Вставьте несколько строк текста, где каждая строка содержит пару ключ-значение. Ключ и значение разделяются знаком равенства (=), а разные пары — символами новой строки.',
|
'Вставьте несколько строк текста, где каждая строка содержит пару ключ-значение. Ключ и значение разделяются знаком равенства (=), а разные пары — символами новой строки.',
|
||||||
'models.form.files': 'файлы',
|
'models.form.files': 'файлы',
|
||||||
'models.table.status': 'Status'
|
'models.table.status': 'Status',
|
||||||
|
'models.form.submit.anyway': 'Submit Anyway',
|
||||||
|
'models.form.evaluating': 'Evaluating...',
|
||||||
|
'models.form.incompatible': 'Incompatible'
|
||||||
};
|
};
|
||||||
|
|
||||||
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
||||||
// 1. models.table.status
|
// 1. models.table.status,
|
||||||
|
// 2. 'models.form.submit.anyway',
|
||||||
|
// 3. 'models.form.evaluating',
|
||||||
|
// 4. 'models.form.incompatible',
|
||||||
// ========== End of To-Do List ==========
|
// ========== End of To-Do List ==========
|
||||||
|
|||||||
@@ -114,5 +114,8 @@ export default {
|
|||||||
'models.form.keyvalue.paste':
|
'models.form.keyvalue.paste':
|
||||||
'粘贴多行文本,每行包含一个键值对,键和值之间用 = 号分隔,不同的键值对之间用换行符分隔。',
|
'粘贴多行文本,每行包含一个键值对,键和值之间用 = 号分隔,不同的键值对之间用换行符分隔。',
|
||||||
'models.form.files': '文件',
|
'models.form.files': '文件',
|
||||||
'models.table.status': '状态'
|
'models.table.status': '状态',
|
||||||
|
'models.form.submit.anyway': '仍然提交',
|
||||||
|
'models.form.evaluating': '评估中...',
|
||||||
|
'models.form.incompatible': '不兼容'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
const MODEL_EVALUATIONS = '/model-evaluations';
|
||||||
|
|
||||||
|
let controller = new AbortController();
|
||||||
|
let signal = controller.signal;
|
||||||
|
|
||||||
|
self.onmessage = async (event) => {
|
||||||
|
const { list, modelSource, modelSourceMap } = event.data;
|
||||||
|
|
||||||
|
const repoList = list.map((item: any) => ({
|
||||||
|
source: modelSource,
|
||||||
|
...(modelSource === modelSourceMap.huggingface_value
|
||||||
|
? { huggingface_repo_id: item.name }
|
||||||
|
: { model_scope_model_id: item.name })
|
||||||
|
}));
|
||||||
|
|
||||||
|
try {
|
||||||
|
controller?.abort();
|
||||||
|
controller = new AbortController();
|
||||||
|
signal = controller.signal;
|
||||||
|
const response = await fetch(`v1/${MODEL_EVALUATIONS}`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
signal,
|
||||||
|
body: JSON.stringify({ model_specs: repoList })
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! Status: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const evaluations = await response.json();
|
||||||
|
const { results } = evaluations;
|
||||||
|
const resultList = list.map((item: any, index: number) => {
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
evaluateResult: results[index] || null
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
self.postMessage({ success: true, resultList });
|
||||||
|
} catch (error) {
|
||||||
|
self.postMessage({ success: false, resultList: list });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
self.onmessage = (event) => {
|
||||||
|
if (event.data === 'abort') {
|
||||||
|
controller.abort();
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -379,3 +379,17 @@ export async function evaluationsModelSpec(
|
|||||||
cancelToken: options?.token
|
cancelToken: options?.token
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// export const evaluationsModelSpec = async (
|
||||||
|
// data: {
|
||||||
|
// model_specs: EvaluateSpec[];
|
||||||
|
// },
|
||||||
|
// options: { token: any }
|
||||||
|
// ) => {
|
||||||
|
// const response = await fetch(`v1/${MODEL_EVALUATIONS}`, {
|
||||||
|
// method: 'POST',
|
||||||
|
// headers: { 'Content-Type': 'application/json' },
|
||||||
|
// body: JSON.stringify(data)
|
||||||
|
// });
|
||||||
|
// return response.json();
|
||||||
|
// };
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import AlertBlockInfo from '@/components/alert-info/block';
|
import AlertBlockInfo from '@/components/alert-info/block';
|
||||||
|
import { CloseOutlined } from '@ant-design/icons';
|
||||||
import { isArray } from 'lodash';
|
import { isArray } from 'lodash';
|
||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
@@ -10,12 +11,23 @@ interface CompatibilityAlertProps {
|
|||||||
isHtml?: boolean;
|
isHtml?: boolean;
|
||||||
message: string | string[];
|
message: string | string[];
|
||||||
};
|
};
|
||||||
|
contentStyle?: React.CSSProperties;
|
||||||
|
showClose?: boolean;
|
||||||
|
onClose?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DivWrapper = styled.div`
|
const DivWrapper = styled.div`
|
||||||
|
position: relative;
|
||||||
padding-inline: 12px;
|
padding-inline: 12px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const CloseWrapper = styled.div`
|
||||||
|
position: absolute;
|
||||||
|
top: 6px;
|
||||||
|
right: 18px;
|
||||||
|
cursor: pointer;
|
||||||
|
`;
|
||||||
|
|
||||||
const MessageWrapper = styled.div`
|
const MessageWrapper = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -24,7 +36,7 @@ const MessageWrapper = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const CompatibilityAlert: React.FC<CompatibilityAlertProps> = (props) => {
|
const CompatibilityAlert: React.FC<CompatibilityAlertProps> = (props) => {
|
||||||
const { warningStatus } = props;
|
const { warningStatus, contentStyle, showClose, onClose } = props;
|
||||||
const { title, show, message, isHtml } = warningStatus;
|
const { title, show, message, isHtml } = warningStatus;
|
||||||
|
|
||||||
const renderMessage = useMemo(() => {
|
const renderMessage = useMemo(() => {
|
||||||
@@ -62,8 +74,14 @@ const CompatibilityAlert: React.FC<CompatibilityAlertProps> = (props) => {
|
|||||||
ellipsis={false}
|
ellipsis={false}
|
||||||
message={renderMessage}
|
message={renderMessage}
|
||||||
title={title}
|
title={title}
|
||||||
|
contentStyle={contentStyle}
|
||||||
type="warning"
|
type="warning"
|
||||||
></AlertBlockInfo>
|
></AlertBlockInfo>
|
||||||
|
{showClose && (
|
||||||
|
<CloseWrapper onClick={onClose}>
|
||||||
|
<CloseOutlined />
|
||||||
|
</CloseWrapper>
|
||||||
|
)}
|
||||||
</DivWrapper>
|
</DivWrapper>
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,65 +1,45 @@
|
|||||||
import IconFont from '@/components/icon-font';
|
|
||||||
import SealAutoComplete from '@/components/seal-form/auto-complete';
|
|
||||||
import SealInput from '@/components/seal-form/seal-input';
|
import SealInput from '@/components/seal-form/seal-input';
|
||||||
import SealSelect from '@/components/seal-form/seal-select';
|
import SealSelect from '@/components/seal-form/seal-select';
|
||||||
import TooltipList from '@/components/tooltip-list';
|
import TooltipList from '@/components/tooltip-list';
|
||||||
import { PageAction } from '@/config';
|
import { PageAction } from '@/config';
|
||||||
import { PageActionType } from '@/config/types';
|
import { PageActionType } from '@/config/types';
|
||||||
import useAppUtils from '@/hooks/use-app-utils';
|
import useAppUtils from '@/hooks/use-app-utils';
|
||||||
import { createAxiosToken } from '@/hooks/use-chunk-request';
|
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { Form, Typography } from 'antd';
|
import { Form } from 'antd';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import React, {
|
import React, { forwardRef, useImperativeHandle, useState } from 'react';
|
||||||
forwardRef,
|
|
||||||
useImperativeHandle,
|
|
||||||
useMemo,
|
|
||||||
useRef,
|
|
||||||
useState
|
|
||||||
} from 'react';
|
|
||||||
import { evaluationsModelSpec } from '../apis';
|
|
||||||
import {
|
import {
|
||||||
HuggingFaceTaskMap,
|
HuggingFaceTaskMap,
|
||||||
ModelscopeTaskMap,
|
ModelscopeTaskMap,
|
||||||
backendOptionsMap,
|
backendOptionsMap,
|
||||||
backendTipsList,
|
backendTipsList,
|
||||||
excludeFields,
|
|
||||||
getSourceRepoConfigValue,
|
|
||||||
localPathTipsList,
|
|
||||||
modelSourceMap,
|
modelSourceMap,
|
||||||
modelTaskMap,
|
modelTaskMap,
|
||||||
ollamaModelOptions,
|
|
||||||
sourceOptions
|
sourceOptions
|
||||||
} from '../config';
|
} from '../config';
|
||||||
import { identifyModelTask } from '../config/audio-catalog';
|
import { identifyModelTask } from '../config/audio-catalog';
|
||||||
import { EvaluateResult, FormData } from '../config/types';
|
import { FormInnerContext } from '../config/form-context';
|
||||||
|
import { FormData, SourceType } from '../config/types';
|
||||||
|
import CatalogFrom from '../forms/catalog';
|
||||||
|
import HuggingFaceForm from '../forms/hugging-face';
|
||||||
|
import LocalPathForm from '../forms/local-path';
|
||||||
|
import OllamaForm from '../forms/ollama-library';
|
||||||
import AdvanceConfig from './advance-config';
|
import AdvanceConfig from './advance-config';
|
||||||
|
|
||||||
interface DataFormProps {
|
interface DataFormProps {
|
||||||
initialValues?: any;
|
initialValues?: any;
|
||||||
ref?: any;
|
ref?: any;
|
||||||
source: string;
|
source: SourceType;
|
||||||
action: PageActionType;
|
action: PageActionType;
|
||||||
selectedModel: any;
|
selectedModel: any;
|
||||||
isGGUF: boolean;
|
isGGUF: boolean;
|
||||||
sizeOptions?: Global.BaseOption<number>[];
|
|
||||||
quantizationOptions?: Global.BaseOption<string>[];
|
|
||||||
sourceDisable?: boolean;
|
sourceDisable?: boolean;
|
||||||
byBuiltIn?: boolean;
|
|
||||||
backendOptions?: Global.BaseOption<string>[];
|
backendOptions?: Global.BaseOption<string>[];
|
||||||
sourceList?: Global.BaseOption<string>[];
|
sourceList?: Global.BaseOption<string>[];
|
||||||
gpuOptions: any[];
|
gpuOptions: any[];
|
||||||
modelFileOptions?: any[];
|
modelFileOptions?: any[];
|
||||||
fields?: string[];
|
fields?: string[];
|
||||||
handleUpdateWarning?: (params: {
|
|
||||||
backend: string;
|
|
||||||
localPath: string;
|
|
||||||
source: string;
|
|
||||||
}) => any;
|
|
||||||
handleShowCompatibleAlert?: (data: EvaluateResult | null) => void;
|
|
||||||
onValuesChange?: (changedValues: any, allValues: any) => void;
|
onValuesChange?: (changedValues: any, allValues: any) => void;
|
||||||
onSizeChange?: (val: number) => void;
|
|
||||||
onQuantizationChange?: (val: string) => void;
|
|
||||||
onSourceChange?: (value: string) => void;
|
onSourceChange?: (value: string) => void;
|
||||||
onOk: (values: FormData) => void;
|
onOk: (values: FormData) => void;
|
||||||
onBackendChange?: (value: string) => void;
|
onBackendChange?: (value: string) => void;
|
||||||
@@ -78,15 +58,11 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
|
|||||||
sourceDisable = true,
|
sourceDisable = true,
|
||||||
backendOptions,
|
backendOptions,
|
||||||
sourceList,
|
sourceList,
|
||||||
byBuiltIn,
|
|
||||||
gpuOptions = [],
|
gpuOptions = [],
|
||||||
modelFileOptions = [],
|
modelFileOptions = [],
|
||||||
sizeOptions = [],
|
|
||||||
quantizationOptions = [],
|
|
||||||
fields = ['source'],
|
fields = ['source'],
|
||||||
handleUpdateWarning,
|
|
||||||
handleShowCompatibleAlert,
|
|
||||||
onSourceChange,
|
onSourceChange,
|
||||||
|
onValuesChange,
|
||||||
onOk
|
onOk
|
||||||
} = props;
|
} = props;
|
||||||
console.log('modelFileOptions--------', modelFileOptions);
|
console.log('modelFileOptions--------', modelFileOptions);
|
||||||
@@ -99,8 +75,6 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
|
|||||||
text2speech: false,
|
text2speech: false,
|
||||||
speech2text: false
|
speech2text: false
|
||||||
});
|
});
|
||||||
const checkTokenRef = useRef<any>(null);
|
|
||||||
const localPathCache = useRef<string>('');
|
|
||||||
|
|
||||||
const handleSumit = () => {
|
const handleSumit = () => {
|
||||||
form.submit();
|
form.submit();
|
||||||
@@ -153,268 +127,6 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleOnFocus = () => {
|
|
||||||
localPathCache.current = form.getFieldValue('local_path');
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleEvaluate = async (data: any) => {
|
|
||||||
try {
|
|
||||||
checkTokenRef.current?.cancel();
|
|
||||||
checkTokenRef.current = createAxiosToken();
|
|
||||||
const evalution = await evaluationsModelSpec(
|
|
||||||
{
|
|
||||||
model_specs: [
|
|
||||||
{
|
|
||||||
..._.omit(data, ['scheduleType']),
|
|
||||||
categories: data.categories ? [data.categories] : []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
token: checkTokenRef.current.token
|
|
||||||
}
|
|
||||||
);
|
|
||||||
return evalution.results?.[0];
|
|
||||||
} catch (error) {
|
|
||||||
console.log('error=====', error);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// trigger from local_path change or backend change
|
|
||||||
const handleBackendChangeHook = async () => {
|
|
||||||
const localPath = form.getFieldValue?.('local_path');
|
|
||||||
const backend = form.getFieldValue?.('backend');
|
|
||||||
|
|
||||||
const res = handleUpdateWarning?.({
|
|
||||||
backend,
|
|
||||||
localPath: localPath,
|
|
||||||
source: props.source
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!res.show) {
|
|
||||||
const values = form.getFieldsValue?.();
|
|
||||||
const data = getSourceRepoConfigValue(props.source, values);
|
|
||||||
const evalutionData = await handleEvaluate(data.values);
|
|
||||||
handleShowCompatibleAlert?.(evalutionData);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleLocalPathBlur = (e: any) => {
|
|
||||||
const value = e.target.value;
|
|
||||||
console.log('handleLocalPathBlur:', e, localPathCache.current);
|
|
||||||
if (value === localPathCache.current && value) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const isEndwithGGUF = _.endsWith(value, '.gguf');
|
|
||||||
const isBlobFile = value.split('/').pop().includes('sha256');
|
|
||||||
let backend = backendOptionsMap.llamaBox;
|
|
||||||
if (!isEndwithGGUF && !isBlobFile) {
|
|
||||||
backend = backendOptionsMap.vllm;
|
|
||||||
}
|
|
||||||
form.setFieldValue('backend', backend);
|
|
||||||
handleBackendChangeHook();
|
|
||||||
props.onBackendChange?.(backend);
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderHuggingfaceFields = () => {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Form.Item<FormData>
|
|
||||||
name="repo_id"
|
|
||||||
key="repo_id"
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
message: getRuleMessage('input', 'models.form.repoid')
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<SealInput.Input
|
|
||||||
label={intl.formatMessage({ id: 'models.form.repoid' })}
|
|
||||||
required
|
|
||||||
disabled={true}
|
|
||||||
></SealInput.Input>
|
|
||||||
</Form.Item>
|
|
||||||
{isGGUF && (
|
|
||||||
<Form.Item<FormData>
|
|
||||||
name="file_name"
|
|
||||||
key="file_name"
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
message: getRuleMessage('input', 'models.form.filename')
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<SealInput.Input
|
|
||||||
label={intl.formatMessage({ id: 'models.form.filename' })}
|
|
||||||
required
|
|
||||||
disabled={true}
|
|
||||||
></SealInput.Input>
|
|
||||||
</Form.Item>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderOllamaModelFields = () => {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Form.Item<FormData>
|
|
||||||
name="ollama_library_model_name"
|
|
||||||
key="ollama_library_model_name"
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
message: getRuleMessage('input', 'models.table.name')
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<SealAutoComplete
|
|
||||||
allowClear
|
|
||||||
filterOption
|
|
||||||
defaultActiveFirstOption
|
|
||||||
disabled={false}
|
|
||||||
options={ollamaModelOptions}
|
|
||||||
description={
|
|
||||||
<span>
|
|
||||||
<span>
|
|
||||||
{intl.formatMessage({ id: 'models.form.ollamalink' })}
|
|
||||||
</span>
|
|
||||||
<Typography.Link
|
|
||||||
className="flex-center"
|
|
||||||
href="https://www.ollama.com/library"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
<IconFont
|
|
||||||
type="icon-external-link"
|
|
||||||
className="font-size-14"
|
|
||||||
></IconFont>
|
|
||||||
</Typography.Link>
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
label={intl.formatMessage({ id: 'model.form.ollama.model' })}
|
|
||||||
placeholder={intl.formatMessage({ id: 'model.form.ollamaholder' })}
|
|
||||||
required
|
|
||||||
></SealAutoComplete>
|
|
||||||
</Form.Item>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderLocalPathFields = () => {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Form.Item<FormData>
|
|
||||||
name="local_path"
|
|
||||||
key="local_path"
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
message: getRuleMessage('input', 'models.form.filePath')
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<SealAutoComplete
|
|
||||||
allowClear
|
|
||||||
required
|
|
||||||
filterOption
|
|
||||||
defaultActiveFirstOption
|
|
||||||
options={modelFileOptions}
|
|
||||||
onBlur={handleLocalPathBlur}
|
|
||||||
onFocus={handleOnFocus}
|
|
||||||
label={intl.formatMessage({ id: 'models.form.filePath' })}
|
|
||||||
description={<TooltipList list={localPathTipsList}></TooltipList>}
|
|
||||||
></SealAutoComplete>
|
|
||||||
</Form.Item>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSizeChange = (val: any) => {
|
|
||||||
props.onSizeChange?.(val);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleOnQuantizationChange = (val: any) => {
|
|
||||||
props.onQuantizationChange?.(val);
|
|
||||||
};
|
|
||||||
|
|
||||||
// from catalog
|
|
||||||
const renderFieldsFromCatalog = useMemo(() => {
|
|
||||||
if (!byBuiltIn && !sizeOptions?.length && !quantizationOptions?.length) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{sizeOptions?.length > 0 && (
|
|
||||||
<Form.Item<FormData>
|
|
||||||
name="size"
|
|
||||||
key="size"
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
message: getRuleMessage('input', 'size', false)
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<SealSelect
|
|
||||||
filterOption
|
|
||||||
onChange={handleSizeChange}
|
|
||||||
defaultActiveFirstOption
|
|
||||||
disabled={false}
|
|
||||||
options={sizeOptions}
|
|
||||||
label="Size"
|
|
||||||
required
|
|
||||||
></SealSelect>
|
|
||||||
</Form.Item>
|
|
||||||
)}
|
|
||||||
{quantizationOptions?.length > 0 && (
|
|
||||||
<Form.Item<FormData>
|
|
||||||
name="quantization"
|
|
||||||
key="quantization"
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
message: getRuleMessage('select', 'quantization', false)
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<SealSelect
|
|
||||||
filterOption
|
|
||||||
defaultActiveFirstOption
|
|
||||||
disabled={false}
|
|
||||||
options={quantizationOptions}
|
|
||||||
onChange={handleOnQuantizationChange}
|
|
||||||
label="Quantization"
|
|
||||||
required
|
|
||||||
></SealSelect>
|
|
||||||
</Form.Item>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}, [sizeOptions, quantizationOptions, byBuiltIn]);
|
|
||||||
|
|
||||||
const renderFieldsBySource = useMemo(() => {
|
|
||||||
// from catalog
|
|
||||||
if (byBuiltIn) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (SEARCH_SOURCE.includes(props.source)) {
|
|
||||||
return renderHuggingfaceFields();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (props.source === modelSourceMap.ollama_library_value) {
|
|
||||||
return renderOllamaModelFields();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (props.source === modelSourceMap.local_path_value) {
|
|
||||||
return renderLocalPathFields();
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}, [props.source, isGGUF, byBuiltIn, intl]);
|
|
||||||
|
|
||||||
const handleSetGPUIds = (backend: string) => {
|
const handleSetGPUIds = (backend: string) => {
|
||||||
if (backend === backendOptionsMap.llamaBox) {
|
if (backend === backendOptionsMap.llamaBox) {
|
||||||
return;
|
return;
|
||||||
@@ -441,7 +153,6 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
|
|||||||
}
|
}
|
||||||
form.setFieldsValue(updates);
|
form.setFieldsValue(updates);
|
||||||
handleSetGPUIds(val);
|
handleSetGPUIds(val);
|
||||||
handleBackendChangeHook();
|
|
||||||
props.onBackendChange?.(val);
|
props.onBackendChange?.(val);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -474,7 +185,7 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
|
|||||||
return {};
|
return {};
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleOk = (formdata: FormData) => {
|
const handleOk = async (formdata: FormData) => {
|
||||||
let data = _.cloneDeep(formdata);
|
let data = _.cloneDeep(formdata);
|
||||||
if (data.categories) {
|
if (data.categories) {
|
||||||
data.categories = [data.categories];
|
data.categories = [data.categories];
|
||||||
@@ -482,10 +193,12 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
|
|||||||
data.categories = [];
|
data.categories = [];
|
||||||
}
|
}
|
||||||
const gpuSelector = generateGPUIds(data);
|
const gpuSelector = generateGPUIds(data);
|
||||||
onOk({
|
const allValues = {
|
||||||
..._.omit(data, ['scheduleType']),
|
..._.omit(data, ['scheduleType']),
|
||||||
...gpuSelector
|
...gpuSelector
|
||||||
});
|
};
|
||||||
|
|
||||||
|
onOk(allValues);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleOnSourceChange = (val: string) => {
|
const handleOnSourceChange = (val: string) => {
|
||||||
@@ -493,19 +206,7 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleOnValuesChange = async (changedValues: any, allValues: any) => {
|
const handleOnValuesChange = async (changedValues: any, allValues: any) => {
|
||||||
const keys = Object.keys(changedValues);
|
onValuesChange?.(changedValues, allValues);
|
||||||
const isExcludeField = keys.some((key) => excludeFields.includes(key));
|
|
||||||
if (
|
|
||||||
!isExcludeField &&
|
|
||||||
!_.has(changedValues, 'backend') &&
|
|
||||||
!_.has(changedValues, 'local_path')
|
|
||||||
) {
|
|
||||||
const values = form.getFieldsValue?.();
|
|
||||||
const data = getSourceRepoConfigValue(props.source, values);
|
|
||||||
|
|
||||||
const evalutionData = await handleEvaluate(data.values);
|
|
||||||
handleShowCompatibleAlert?.(evalutionData);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
useImperativeHandle(
|
useImperativeHandle(
|
||||||
@@ -595,7 +296,15 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{renderFieldsBySource}
|
<FormInnerContext.Provider
|
||||||
|
value={{
|
||||||
|
onBackendChange: handleBackendChange
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<HuggingFaceForm></HuggingFaceForm>
|
||||||
|
<OllamaForm></OllamaForm>
|
||||||
|
<LocalPathForm></LocalPathForm>
|
||||||
|
</FormInnerContext.Provider>
|
||||||
<Form.Item name="backend" rules={[{ required: true }]}>
|
<Form.Item name="backend" rules={[{ required: true }]}>
|
||||||
<SealSelect
|
<SealSelect
|
||||||
required
|
required
|
||||||
@@ -637,7 +346,7 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
|
|||||||
}
|
}
|
||||||
></SealSelect>
|
></SealSelect>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
{renderFieldsFromCatalog}
|
<CatalogFrom></CatalogFrom>
|
||||||
<Form.Item<FormData>
|
<Form.Item<FormData>
|
||||||
name="replicas"
|
name="replicas"
|
||||||
rules={[
|
rules={[
|
||||||
|
|||||||
@@ -2,18 +2,26 @@ import ModalFooter from '@/components/modal-footer';
|
|||||||
import { PageActionType } from '@/config/types';
|
import { PageActionType } from '@/config/types';
|
||||||
import { createAxiosToken } from '@/hooks/use-chunk-request';
|
import { createAxiosToken } from '@/hooks/use-chunk-request';
|
||||||
import { CloseOutlined } from '@ant-design/icons';
|
import { CloseOutlined } from '@ant-design/icons';
|
||||||
|
import { useIntl } from '@umijs/max';
|
||||||
import { Button, Drawer } from 'antd';
|
import { Button, Drawer } from 'antd';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import React, { memo, useCallback, useEffect, useRef, useState } from 'react';
|
import React, { memo, useCallback, useEffect, useRef, useState } from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
import { queryCatalogItemSpec } from '../apis';
|
import { queryCatalogItemSpec } from '../apis';
|
||||||
import {
|
import {
|
||||||
backendOptionsMap,
|
backendOptionsMap,
|
||||||
|
excludeFields,
|
||||||
modelCategoriesMap,
|
modelCategoriesMap,
|
||||||
sourceOptions
|
sourceOptions
|
||||||
} from '../config';
|
} from '../config';
|
||||||
import { CatalogSpec, FormData, ListItem } from '../config/types';
|
import { FormContext } from '../config/form-context';
|
||||||
import { useGenerateFormEditInitialValues } from '../hooks';
|
import { CatalogSpec, FormData, ListItem, SourceType } from '../config/types';
|
||||||
|
import {
|
||||||
|
useCheckCompatibility,
|
||||||
|
useGenerateFormEditInitialValues
|
||||||
|
} from '../hooks';
|
||||||
import ColumnWrapper from './column-wrapper';
|
import ColumnWrapper from './column-wrapper';
|
||||||
|
import CompatibilityAlert from './compatible-alert';
|
||||||
import DataForm from './data-form';
|
import DataForm from './data-form';
|
||||||
|
|
||||||
type AddModalProps = {
|
type AddModalProps = {
|
||||||
@@ -21,13 +29,20 @@ type AddModalProps = {
|
|||||||
action: PageActionType;
|
action: PageActionType;
|
||||||
open: boolean;
|
open: boolean;
|
||||||
data?: ListItem;
|
data?: ListItem;
|
||||||
source: string;
|
source: SourceType;
|
||||||
width?: string | number;
|
width?: string | number;
|
||||||
current?: any;
|
current?: any;
|
||||||
onOk: (values: FormData) => void;
|
onOk: (values: FormData) => void;
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const FormWrapper = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
height: 100%;
|
||||||
|
maxwidth: 100%;
|
||||||
|
`;
|
||||||
|
|
||||||
const backendOptions = [
|
const backendOptions = [
|
||||||
{
|
{
|
||||||
label: `llama-box`,
|
label: `llama-box`,
|
||||||
@@ -64,6 +79,13 @@ const AddModal: React.FC<AddModalProps> = (props) => {
|
|||||||
current,
|
current,
|
||||||
width = 600
|
width = 600
|
||||||
} = props || {};
|
} = props || {};
|
||||||
|
const {
|
||||||
|
handleShowCompatibleAlert,
|
||||||
|
setWarningStatus,
|
||||||
|
handleEvaluate,
|
||||||
|
warningStatus
|
||||||
|
} = useCheckCompatibility();
|
||||||
|
const intl = useIntl();
|
||||||
const { getGPUList } = useGenerateFormEditInitialValues();
|
const { getGPUList } = useGenerateFormEditInitialValues();
|
||||||
const form = useRef<any>({});
|
const form = useRef<any>({});
|
||||||
const [gpuOptions, setGpuOptions] = useState<any[]>([]);
|
const [gpuOptions, setGpuOptions] = useState<any[]>([]);
|
||||||
@@ -76,11 +98,26 @@ const AddModal: React.FC<AddModalProps> = (props) => {
|
|||||||
const axiosToken = useRef<any>(null);
|
const axiosToken = useRef<any>(null);
|
||||||
const selectSpecRef = useRef<CatalogSpec>({} as CatalogSpec);
|
const selectSpecRef = useRef<CatalogSpec>({} as CatalogSpec);
|
||||||
const specListRef = useRef<any[]>([]);
|
const specListRef = useRef<any[]>([]);
|
||||||
|
const submitAnyway = useRef<any>(null);
|
||||||
|
|
||||||
const handleSumit = () => {
|
const handleSumit = () => {
|
||||||
form.current?.submit?.();
|
form.current?.submit?.();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSubmitAnyway = async () => {
|
||||||
|
submitAnyway.current = true;
|
||||||
|
form.current?.submit?.();
|
||||||
|
};
|
||||||
|
|
||||||
|
const generateSubmitData = (formData: FormData) => {
|
||||||
|
const data = {
|
||||||
|
..._.omit(selectSpecRef.current, ['name']),
|
||||||
|
...formData
|
||||||
|
};
|
||||||
|
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
const getDefaultQuant = (data: { category: string; quantOption: string }) => {
|
const getDefaultQuant = (data: { category: string; quantOption: string }) => {
|
||||||
if (
|
if (
|
||||||
data.category === modelCategoriesMap.embedding ||
|
data.category === modelCategoriesMap.embedding ||
|
||||||
@@ -181,6 +218,20 @@ const AddModal: React.FC<AddModalProps> = (props) => {
|
|||||||
return backendList;
|
return backendList;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleCheckCompatibility = async (formData: FormData) => {
|
||||||
|
const evalutionData = await handleEvaluate(formData);
|
||||||
|
|
||||||
|
if (evalutionData?.compatible) {
|
||||||
|
setWarningStatus({
|
||||||
|
show: false,
|
||||||
|
message: ''
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
handleShowCompatibleAlert?.(evalutionData);
|
||||||
|
}
|
||||||
|
return evalutionData;
|
||||||
|
};
|
||||||
|
|
||||||
const handleSourceChange = (source: string) => {
|
const handleSourceChange = (source: string) => {
|
||||||
const defaultSpec = _.get(sourceGroupMap.current, `${source}.0`, {});
|
const defaultSpec = _.get(sourceGroupMap.current, `${source}.0`, {});
|
||||||
initFormDataBySource(defaultSpec);
|
initFormDataBySource(defaultSpec);
|
||||||
@@ -193,6 +244,8 @@ const AddModal: React.FC<AddModalProps> = (props) => {
|
|||||||
});
|
});
|
||||||
// set form value
|
// set form value
|
||||||
initFormDataBySource(defaultSpec);
|
initFormDataBySource(defaultSpec);
|
||||||
|
const data = generateSubmitData(form.current.getFieldsValue());
|
||||||
|
handleCheckCompatibility(data);
|
||||||
};
|
};
|
||||||
|
|
||||||
const checkSize = (list: any[]) => {
|
const checkSize = (list: any[]) => {
|
||||||
@@ -222,6 +275,16 @@ const AddModal: React.FC<AddModalProps> = (props) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleOnValuesChange = async (changedValues: any, allValues: any) => {
|
||||||
|
const keys = Object.keys(changedValues);
|
||||||
|
const isExcludeField = keys.some((key) => excludeFields.includes(key));
|
||||||
|
if (!isExcludeField) {
|
||||||
|
const values = form.current?.form.getFieldsValue?.();
|
||||||
|
const data = generateSubmitData(values);
|
||||||
|
handleCheckCompatibility(data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleBackendChange = (backend: string) => {
|
const handleBackendChange = (backend: string) => {
|
||||||
if (backend === backendOptionsMap.vllm) {
|
if (backend === backendOptionsMap.vllm) {
|
||||||
setIsGGUF(false);
|
setIsGGUF(false);
|
||||||
@@ -252,6 +315,7 @@ const AddModal: React.FC<AddModalProps> = (props) => {
|
|||||||
form.current.setFieldsValue({
|
form.current.setFieldsValue({
|
||||||
...data
|
...data
|
||||||
});
|
});
|
||||||
|
handleCheckCompatibility(data);
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchSpecData = async () => {
|
const fetchSpecData = async () => {
|
||||||
@@ -323,6 +387,8 @@ const AddModal: React.FC<AddModalProps> = (props) => {
|
|||||||
form.current.setFieldsValue({
|
form.current.setFieldsValue({
|
||||||
...data
|
...data
|
||||||
});
|
});
|
||||||
|
const allValues = generateSubmitData(data);
|
||||||
|
handleCheckCompatibility(allValues);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleOnSizeChange = (val: number) => {
|
const handleOnSizeChange = (val: number) => {
|
||||||
@@ -343,13 +409,21 @@ const AddModal: React.FC<AddModalProps> = (props) => {
|
|||||||
form.current.setFieldsValue({
|
form.current.setFieldsValue({
|
||||||
...data
|
...data
|
||||||
});
|
});
|
||||||
|
const allValues = generateSubmitData(data);
|
||||||
|
handleCheckCompatibility(allValues);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleOk = (values: FormData) => {
|
const handleOk = async (values: FormData) => {
|
||||||
onOk({
|
const data = generateSubmitData(values);
|
||||||
..._.omit(selectSpecRef.current, ['name']),
|
|
||||||
...values
|
if (submitAnyway.current) {
|
||||||
});
|
onOk(data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const evaluateResult = await handleCheckCompatibility(data);
|
||||||
|
if (evaluateResult?.compatible) {
|
||||||
|
onOk(data);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCancel = useCallback(() => {
|
const handleCancel = useCallback(() => {
|
||||||
@@ -363,6 +437,11 @@ const AddModal: React.FC<AddModalProps> = (props) => {
|
|||||||
}
|
}
|
||||||
return () => {
|
return () => {
|
||||||
axiosToken.current?.cancel?.();
|
axiosToken.current?.cancel?.();
|
||||||
|
setWarningStatus({
|
||||||
|
show: false,
|
||||||
|
title: '',
|
||||||
|
message: []
|
||||||
|
});
|
||||||
};
|
};
|
||||||
}, [open, current]);
|
}, [open, current]);
|
||||||
|
|
||||||
@@ -409,44 +488,85 @@ const AddModal: React.FC<AddModalProps> = (props) => {
|
|||||||
width={width}
|
width={width}
|
||||||
footer={false}
|
footer={false}
|
||||||
>
|
>
|
||||||
<div style={{ display: 'flex', height: '100%' }}>
|
<FormContext.Provider
|
||||||
<ColumnWrapper
|
value={{
|
||||||
footer={
|
isGGUF: isGGUF,
|
||||||
<ModalFooter
|
byBuiltIn: true,
|
||||||
onCancel={handleCancel}
|
sizeOptions: sizeOptions,
|
||||||
onOk={handleSumit}
|
quantizationOptions: quantizationOptions,
|
||||||
style={{
|
onSizeChange: handleOnSizeChange,
|
||||||
padding: '16px 24px',
|
onQuantizationChange: handleOnQuantizationChange
|
||||||
display: 'flex',
|
}}
|
||||||
justifyContent: 'flex-end'
|
>
|
||||||
}}
|
<FormWrapper>
|
||||||
></ModalFooter>
|
<ColumnWrapper
|
||||||
}
|
paddingBottom={
|
||||||
>
|
warningStatus.show
|
||||||
<>
|
? Array.isArray(warningStatus.message)
|
||||||
<DataForm
|
? 150
|
||||||
fields={[]}
|
: 125
|
||||||
source={source}
|
: 50
|
||||||
action={action}
|
}
|
||||||
selectedModel={{}}
|
footer={
|
||||||
onOk={handleOk}
|
<>
|
||||||
ref={form}
|
<CompatibilityAlert
|
||||||
isGGUF={isGGUF}
|
showClose={true}
|
||||||
byBuiltIn={true}
|
onClose={() => {
|
||||||
sourceDisable={false}
|
setWarningStatus({
|
||||||
backendOptions={backendList}
|
show: false,
|
||||||
sourceList={sourceList}
|
message: ''
|
||||||
quantizationOptions={quantizationOptions}
|
});
|
||||||
sizeOptions={sizeOptions}
|
}}
|
||||||
gpuOptions={gpuOptions}
|
warningStatus={warningStatus}
|
||||||
onBackendChange={handleBackendChange}
|
contentStyle={{ paddingInline: 0 }}
|
||||||
onSourceChange={handleSourceChange}
|
></CompatibilityAlert>
|
||||||
onQuantizationChange={handleOnQuantizationChange}
|
<ModalFooter
|
||||||
onSizeChange={handleOnSizeChange}
|
onCancel={handleCancel}
|
||||||
></DataForm>
|
onOk={handleSumit}
|
||||||
</>
|
showOkBtn={!warningStatus.show}
|
||||||
</ColumnWrapper>
|
extra={
|
||||||
</div>
|
warningStatus.show && (
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={handleSubmitAnyway}
|
||||||
|
style={{ width: '130px' }}
|
||||||
|
>
|
||||||
|
{intl.formatMessage({
|
||||||
|
id: 'models.form.submit.anyway'
|
||||||
|
})}
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
style={{
|
||||||
|
padding: '16px 24px',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'flex-end'
|
||||||
|
}}
|
||||||
|
></ModalFooter>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<>
|
||||||
|
<DataForm
|
||||||
|
fields={[]}
|
||||||
|
source={source}
|
||||||
|
action={action}
|
||||||
|
selectedModel={{}}
|
||||||
|
onOk={handleOk}
|
||||||
|
ref={form}
|
||||||
|
isGGUF={isGGUF}
|
||||||
|
sourceDisable={false}
|
||||||
|
backendOptions={backendList}
|
||||||
|
sourceList={sourceList}
|
||||||
|
gpuOptions={gpuOptions}
|
||||||
|
onBackendChange={handleBackendChange}
|
||||||
|
onSourceChange={handleSourceChange}
|
||||||
|
onValuesChange={handleOnValuesChange}
|
||||||
|
></DataForm>
|
||||||
|
</>
|
||||||
|
</ColumnWrapper>
|
||||||
|
</FormWrapper>
|
||||||
|
</FormContext.Provider>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,11 +3,17 @@ 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 { debounce } from 'lodash';
|
import _, { debounce } from 'lodash';
|
||||||
import { FC, useCallback, useEffect, useRef, useState } from 'react';
|
import { FC, useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { backendOptionsMap, modelSourceMap } from '../config';
|
import {
|
||||||
import { FormData } from '../config/types';
|
backendOptionsMap,
|
||||||
|
excludeFields,
|
||||||
|
getSourceRepoConfigValue,
|
||||||
|
modelSourceMap
|
||||||
|
} from '../config';
|
||||||
|
import { FormContext } from '../config/form-context';
|
||||||
|
import { FormData, SourceType } from '../config/types';
|
||||||
import { useCheckCompatibility } from '../hooks';
|
import { useCheckCompatibility } from '../hooks';
|
||||||
import ColumnWrapper from './column-wrapper';
|
import ColumnWrapper from './column-wrapper';
|
||||||
import CompatibilityAlert from './compatible-alert';
|
import CompatibilityAlert from './compatible-alert';
|
||||||
@@ -18,6 +24,12 @@ import SearchModel from './search-model';
|
|||||||
import Separator from './separator';
|
import Separator from './separator';
|
||||||
import TitleWrapper from './title-wrapper';
|
import TitleWrapper from './title-wrapper';
|
||||||
|
|
||||||
|
const ModalFooterStyle = {
|
||||||
|
padding: '16px 24px',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'flex-end'
|
||||||
|
};
|
||||||
|
|
||||||
const ColWrapper = styled.div`
|
const ColWrapper = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
@@ -34,7 +46,7 @@ type AddModalProps = {
|
|||||||
title: string;
|
title: string;
|
||||||
action: PageActionType;
|
action: PageActionType;
|
||||||
open: boolean;
|
open: boolean;
|
||||||
source: string;
|
source: SourceType;
|
||||||
isGGUF?: boolean;
|
isGGUF?: boolean;
|
||||||
width?: string | number;
|
width?: string | number;
|
||||||
gpuOptions: any[];
|
gpuOptions: any[];
|
||||||
@@ -62,14 +74,21 @@ const AddModal: FC<AddModalProps> = (props) => {
|
|||||||
modelSourceMap.modelscope_value
|
modelSourceMap.modelscope_value
|
||||||
];
|
];
|
||||||
|
|
||||||
const { handleShowCompatibleAlert, handleUpdateWarning, warningStatus } =
|
const {
|
||||||
useCheckCompatibility();
|
handleShowCompatibleAlert,
|
||||||
|
handleUpdateWarning,
|
||||||
|
setWarningStatus,
|
||||||
|
handleEvaluate,
|
||||||
|
checkTokenRef,
|
||||||
|
warningStatus
|
||||||
|
} = useCheckCompatibility();
|
||||||
const form = useRef<any>({});
|
const form = useRef<any>({});
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const [selectedModel, setSelectedModel] = useState<any>({});
|
const [selectedModel, setSelectedModel] = useState<any>({});
|
||||||
const [collapsed, setCollapsed] = useState<boolean>(false);
|
const [collapsed, setCollapsed] = useState<boolean>(false);
|
||||||
const [isGGUF, setIsGGUF] = useState<boolean>(props.isGGUF || false);
|
const [isGGUF, setIsGGUF] = useState<boolean>(props.isGGUF || false);
|
||||||
const modelFileRef = useRef<any>(null);
|
const modelFileRef = useRef<any>(null);
|
||||||
|
const submitAnyway = useRef<boolean>(false);
|
||||||
|
|
||||||
const handleSelectModelFile = useCallback((item: any) => {
|
const handleSelectModelFile = useCallback((item: any) => {
|
||||||
form.current?.setFieldsValue?.({
|
form.current?.setFieldsValue?.({
|
||||||
@@ -81,9 +100,31 @@ const AddModal: FC<AddModalProps> = (props) => {
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleOnSelectModel = (item: any) => {
|
const handleOnSelectModel = (item: any, isgguf?: boolean) => {
|
||||||
setSelectedModel(item);
|
setSelectedModel(item);
|
||||||
form.current?.handleOnSelectModel?.(item);
|
form.current?.handleOnSelectModel?.(item);
|
||||||
|
console.log('isgguf+++++++', isgguf, item);
|
||||||
|
if (!isgguf) {
|
||||||
|
handleShowCompatibleAlert(item.evaluateResult);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOnOk = async (allValues: FormData) => {
|
||||||
|
if (submitAnyway.current) {
|
||||||
|
onOk(allValues);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const result = getSourceRepoConfigValue(props.source, allValues);
|
||||||
|
const evalutionData = await handleEvaluate(result.values);
|
||||||
|
handleShowCompatibleAlert?.(evalutionData);
|
||||||
|
if (evalutionData?.compatible) {
|
||||||
|
onOk(allValues);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmitAnyway = async () => {
|
||||||
|
submitAnyway.current = true;
|
||||||
|
form.current?.submit?.();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSumit = () => {
|
const handleSumit = () => {
|
||||||
@@ -98,12 +139,37 @@ const AddModal: FC<AddModalProps> = (props) => {
|
|||||||
setIsGGUF(flag);
|
setIsGGUF(flag);
|
||||||
if (flag) {
|
if (flag) {
|
||||||
debounceFetchModelFiles();
|
debounceFetchModelFiles();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// trigger from local_path change or backend change
|
||||||
|
const handleBackendChangeHook = async () => {
|
||||||
|
const localPath = form.current.form.getFieldValue?.('local_path');
|
||||||
|
const backend = form.current.form.getFieldValue?.('backend');
|
||||||
|
|
||||||
|
const res = handleUpdateWarning?.({
|
||||||
|
backend,
|
||||||
|
localPath: localPath,
|
||||||
|
source: props.source
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!res.show) {
|
||||||
|
const values = form.current.form.getFieldsValue?.();
|
||||||
|
const data = getSourceRepoConfigValue(props.source, values);
|
||||||
|
const evalutionData = await handleEvaluate(
|
||||||
|
_.omit(data.values, [
|
||||||
|
'cpu_offloading',
|
||||||
|
'distributed_inference_across_workers'
|
||||||
|
])
|
||||||
|
);
|
||||||
|
handleShowCompatibleAlert?.(evalutionData);
|
||||||
} else {
|
} else {
|
||||||
handleShowCompatibleAlert(selectedModel.evaluateResult);
|
setWarningStatus?.(res);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleBackendChange = async (backend: string) => {
|
const handleBackendChange = async (backend: string) => {
|
||||||
|
handleBackendChangeHook();
|
||||||
if (backend === backendOptionsMap.vllm) {
|
if (backend === backendOptionsMap.vllm) {
|
||||||
setIsGGUF(false);
|
setIsGGUF(false);
|
||||||
}
|
}
|
||||||
@@ -113,6 +179,40 @@ const AddModal: FC<AddModalProps> = (props) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleOnValuesChange = async (changedValues: any, allValues: any) => {
|
||||||
|
const keys = Object.keys(changedValues);
|
||||||
|
const isExcludeField = keys.some((key) => excludeFields.includes(key));
|
||||||
|
const hasValue = keys.every((key) => {
|
||||||
|
return !!changedValues[key];
|
||||||
|
});
|
||||||
|
|
||||||
|
// let hasExcludeField = false;
|
||||||
|
// let allFieldsHaveValue = true;
|
||||||
|
|
||||||
|
// for (const key of keys) {
|
||||||
|
// if (excludeFields.includes(key)) {
|
||||||
|
// hasExcludeField = true;
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
// if (!changedValues[key]) {
|
||||||
|
// allFieldsHaveValue = false;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
if (
|
||||||
|
!isExcludeField &&
|
||||||
|
hasValue &&
|
||||||
|
!_.has(changedValues, 'backend') &&
|
||||||
|
!_.has(changedValues, 'local_path')
|
||||||
|
) {
|
||||||
|
const values = form.current?.form.getFieldsValue?.();
|
||||||
|
const data = getSourceRepoConfigValue(props.source, values);
|
||||||
|
|
||||||
|
const evalutionData = await handleEvaluate(data.values);
|
||||||
|
handleShowCompatibleAlert?.(evalutionData);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleCancel = useCallback(() => {
|
const handleCancel = useCallback(() => {
|
||||||
onCancel?.();
|
onCancel?.();
|
||||||
}, [onCancel]);
|
}, [onCancel]);
|
||||||
@@ -127,14 +227,24 @@ const AddModal: FC<AddModalProps> = (props) => {
|
|||||||
});
|
});
|
||||||
setIsGGUF(props.isGGUF || false);
|
setIsGGUF(props.isGGUF || false);
|
||||||
} else {
|
} else {
|
||||||
form.current?.setFieldValue?.('backend', backendOptionsMap.vllm);
|
const backend =
|
||||||
setIsGGUF(false);
|
source === modelSourceMap.ollama_library_value
|
||||||
|
? backendOptionsMap.llamaBox
|
||||||
|
: backendOptionsMap.vllm;
|
||||||
|
form.current?.setFieldValue?.('backend', backend);
|
||||||
|
setIsGGUF(backend === backendOptionsMap.llamaBox);
|
||||||
}
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
setSelectedModel({});
|
setSelectedModel({});
|
||||||
|
setWarningStatus({
|
||||||
|
show: false,
|
||||||
|
title: '',
|
||||||
|
message: []
|
||||||
|
});
|
||||||
|
checkTokenRef.current?.cancel();
|
||||||
};
|
};
|
||||||
}, [open, props.isGGUF, props.initialValues, props.deploymentType]);
|
}, [open, props.isGGUF, source, props.initialValues, props.deploymentType]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Drawer
|
<Drawer
|
||||||
@@ -202,50 +312,80 @@ const AddModal: FC<AddModalProps> = (props) => {
|
|||||||
</ColWrapper>
|
</ColWrapper>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<FormWrapper>
|
<FormContext.Provider
|
||||||
<ColumnWrapper
|
value={{
|
||||||
paddingBottom={warningStatus.show ? 125 : 50}
|
isGGUF: isGGUF,
|
||||||
footer={
|
modelFileOptions: props.modelFileOptions
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormWrapper>
|
||||||
|
<ColumnWrapper
|
||||||
|
paddingBottom={
|
||||||
|
warningStatus.show
|
||||||
|
? Array.isArray(warningStatus.message)
|
||||||
|
? 150
|
||||||
|
: 125
|
||||||
|
: 50
|
||||||
|
}
|
||||||
|
footer={
|
||||||
|
<>
|
||||||
|
<CompatibilityAlert
|
||||||
|
showClose={true}
|
||||||
|
onClose={() => {
|
||||||
|
setWarningStatus({
|
||||||
|
show: false,
|
||||||
|
message: ''
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
warningStatus={warningStatus}
|
||||||
|
contentStyle={{ paddingInline: 0 }}
|
||||||
|
></CompatibilityAlert>
|
||||||
|
<ModalFooter
|
||||||
|
onCancel={handleCancel}
|
||||||
|
onOk={handleSumit}
|
||||||
|
showOkBtn={!warningStatus.show}
|
||||||
|
extra={
|
||||||
|
warningStatus.show && (
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={handleSubmitAnyway}
|
||||||
|
style={{ width: '130px' }}
|
||||||
|
>
|
||||||
|
{intl.formatMessage({
|
||||||
|
id: 'models.form.submit.anyway'
|
||||||
|
})}
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
style={ModalFooterStyle}
|
||||||
|
></ModalFooter>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
>
|
||||||
<>
|
<>
|
||||||
<CompatibilityAlert
|
{SEARCH_SOURCE.includes(source) &&
|
||||||
warningStatus={warningStatus}
|
deploymentType === 'modelList' && (
|
||||||
></CompatibilityAlert>
|
<TitleWrapper>
|
||||||
<ModalFooter
|
{intl.formatMessage({ id: 'models.form.configurations' })}
|
||||||
onCancel={handleCancel}
|
</TitleWrapper>
|
||||||
onOk={handleSumit}
|
)}
|
||||||
style={{
|
<DataForm
|
||||||
padding: '16px 24px',
|
initialValues={initialValues}
|
||||||
display: 'flex',
|
source={source}
|
||||||
justifyContent: 'flex-end'
|
action={action}
|
||||||
}}
|
selectedModel={selectedModel}
|
||||||
></ModalFooter>
|
onOk={handleOnOk}
|
||||||
|
ref={form}
|
||||||
|
isGGUF={isGGUF}
|
||||||
|
gpuOptions={props.gpuOptions}
|
||||||
|
modelFileOptions={props.modelFileOptions}
|
||||||
|
onBackendChange={handleBackendChange}
|
||||||
|
onValuesChange={handleOnValuesChange}
|
||||||
|
></DataForm>
|
||||||
</>
|
</>
|
||||||
}
|
</ColumnWrapper>
|
||||||
>
|
</FormWrapper>
|
||||||
<>
|
</FormContext.Provider>
|
||||||
{SEARCH_SOURCE.includes(source) &&
|
|
||||||
deploymentType === 'modelList' && (
|
|
||||||
<TitleWrapper>
|
|
||||||
{intl.formatMessage({ id: 'models.form.configurations' })}
|
|
||||||
</TitleWrapper>
|
|
||||||
)}
|
|
||||||
<DataForm
|
|
||||||
initialValues={initialValues}
|
|
||||||
source={source}
|
|
||||||
action={action}
|
|
||||||
selectedModel={selectedModel}
|
|
||||||
onOk={onOk}
|
|
||||||
ref={form}
|
|
||||||
isGGUF={isGGUF}
|
|
||||||
gpuOptions={props.gpuOptions}
|
|
||||||
modelFileOptions={props.modelFileOptions}
|
|
||||||
handleShowCompatibleAlert={handleShowCompatibleAlert}
|
|
||||||
handleUpdateWarning={handleUpdateWarning}
|
|
||||||
onBackendChange={handleBackendChange}
|
|
||||||
></DataForm>
|
|
||||||
</>
|
|
||||||
</ColumnWrapper>
|
|
||||||
</FormWrapper>
|
|
||||||
</div>
|
</div>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import IncompatiableInfo from './incompatiable-info';
|
|||||||
import TitleWrapper from './title-wrapper';
|
import TitleWrapper from './title-wrapper';
|
||||||
|
|
||||||
interface HFModelFileProps {
|
interface HFModelFileProps {
|
||||||
|
isDownload?: boolean;
|
||||||
selectedModel: any;
|
selectedModel: any;
|
||||||
collapsed?: boolean;
|
collapsed?: boolean;
|
||||||
loadingModel?: boolean;
|
loadingModel?: boolean;
|
||||||
@@ -73,8 +74,9 @@ const FilePartsTag = (props: { parts: any[] }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const HFModelFile: React.FC<HFModelFileProps> = forwardRef((props, ref) => {
|
const HFModelFile: React.FC<HFModelFileProps> = forwardRef((props, ref) => {
|
||||||
const { collapsed, modelSource } = props;
|
const { collapsed, modelSource, isDownload } = props;
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
const [isEvaluating, setIsEvaluating] = useState(false);
|
||||||
const [dataSource, setDataSource] = useState<any>({
|
const [dataSource, setDataSource] = useState<any>({
|
||||||
fileList: [],
|
fileList: [],
|
||||||
loading: false
|
loading: false
|
||||||
@@ -93,6 +95,7 @@ const HFModelFile: React.FC<HFModelFileProps> = forwardRef((props, ref) => {
|
|||||||
]);
|
]);
|
||||||
const axiosTokenRef = useRef<any>(null);
|
const axiosTokenRef = useRef<any>(null);
|
||||||
const checkTokenRef = useRef<any>(null);
|
const checkTokenRef = useRef<any>(null);
|
||||||
|
const timer = useRef<any>(null);
|
||||||
|
|
||||||
const handleSelectModelFile = (item: any) => {
|
const handleSelectModelFile = (item: any) => {
|
||||||
props.onSelectFile?.(item);
|
props.onSelectFile?.(item);
|
||||||
@@ -241,12 +244,51 @@ const HFModelFile: React.FC<HFModelFileProps> = forwardRef((props, ref) => {
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const handleEvaluate = async (list: any[]) => {
|
||||||
|
if (isDownload) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const evaluateFileList = list.map((item: any) => {
|
||||||
|
return {
|
||||||
|
source: modelSource,
|
||||||
|
...(modelSource === modelSourceMap.huggingface_value
|
||||||
|
? {
|
||||||
|
huggingface_repo_id: props.selectedModel.name,
|
||||||
|
huggingface_filename: item.fakeName
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
model_scope_model_id: props.selectedModel.name,
|
||||||
|
model_scope_file_path: item.fakeName
|
||||||
|
})
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
setIsEvaluating(true);
|
||||||
|
|
||||||
|
const evaluationList = await getEvaluateResults(evaluateFileList);
|
||||||
|
|
||||||
|
const resultList = _.map(list, (item: any, index: number) => {
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
evaluateResult: evaluationList[index]
|
||||||
|
};
|
||||||
|
});
|
||||||
|
handleSelectModelFile(resultList[0]);
|
||||||
|
setDataSource({ fileList: resultList, loading: false });
|
||||||
|
setIsEvaluating(false);
|
||||||
|
} catch (error) {
|
||||||
|
setIsEvaluating(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleFetchModelFiles = async () => {
|
const handleFetchModelFiles = async () => {
|
||||||
if (!props.selectedModel.name) {
|
if (!props.selectedModel.name) {
|
||||||
setDataSource({ fileList: [], loading: false });
|
setDataSource({ fileList: [], loading: false });
|
||||||
handleSelectModelFile({});
|
handleSelectModelFile({});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
checkTokenRef.current?.cancel?.();
|
||||||
axiosTokenRef.current?.abort?.();
|
axiosTokenRef.current?.abort?.();
|
||||||
axiosTokenRef.current = new AbortController();
|
axiosTokenRef.current = new AbortController();
|
||||||
setDataSource({ ...dataSource, loading: true });
|
setDataSource({ ...dataSource, loading: true });
|
||||||
@@ -264,32 +306,16 @@ const HFModelFile: React.FC<HFModelFileProps> = forwardRef((props, ref) => {
|
|||||||
return sortType === 'size' ? item.size : item.path;
|
return sortType === 'size' ? item.size : item.path;
|
||||||
});
|
});
|
||||||
|
|
||||||
const evaluateFileList = sortList.map((item: any) => {
|
handleSelectModelFile(sortList[0]);
|
||||||
return {
|
setDataSource({ fileList: sortList, loading: false });
|
||||||
source: modelSource,
|
|
||||||
...(modelSource === modelSourceMap.huggingface_value
|
|
||||||
? {
|
|
||||||
huggingface_repo_id: props.selectedModel.name,
|
|
||||||
huggingface_filename: item.fakeName
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
model_scope_model_id: props.selectedModel.name,
|
|
||||||
model_scope_file_path: item.fakeName
|
|
||||||
})
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const evaluationList = await getEvaluateResults(evaluateFileList);
|
// if (timer.current) {
|
||||||
|
// clearTimeout(timer.current);
|
||||||
const resultList = _.map(sortList, (item: any, index: number) => {
|
// }
|
||||||
return {
|
// timer.current = setTimeout(() => {
|
||||||
...item,
|
// handleEvaluate(sortList);
|
||||||
evaluateResult: evaluationList[index]
|
// }, 200);
|
||||||
};
|
handleEvaluate(sortList);
|
||||||
});
|
|
||||||
|
|
||||||
handleSelectModelFile(resultList[0]);
|
|
||||||
setDataSource({ fileList: resultList, loading: false });
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setDataSource({ fileList: [], loading: false });
|
setDataSource({ fileList: [], loading: false });
|
||||||
handleSelectModelFile({});
|
handleSelectModelFile({});
|
||||||
@@ -341,12 +367,15 @@ const HFModelFile: React.FC<HFModelFileProps> = forwardRef((props, ref) => {
|
|||||||
setDataSource({ fileList: [], loading: false });
|
setDataSource({ fileList: [], loading: false });
|
||||||
handleSelectModelFile({});
|
handleSelectModelFile({});
|
||||||
}
|
}
|
||||||
}, [props.selectedModel.name]);
|
}, [props.selectedModel?.name]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
axiosTokenRef.current?.abort?.();
|
axiosTokenRef.current?.abort?.();
|
||||||
checkTokenRef.current?.cancel?.();
|
checkTokenRef.current?.cancel?.();
|
||||||
|
if (timer.current) {
|
||||||
|
clearTimeout(timer.current);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -415,6 +444,7 @@ const HFModelFile: React.FC<HFModelFileProps> = forwardRef((props, ref) => {
|
|||||||
<FilePartsTag parts={item.parts}></FilePartsTag>
|
<FilePartsTag parts={item.parts}></FilePartsTag>
|
||||||
</span>
|
</span>
|
||||||
<IncompatiableInfo
|
<IncompatiableInfo
|
||||||
|
isEvaluating={isEvaluating}
|
||||||
data={item.evaluateResult}
|
data={item.evaluateResult}
|
||||||
></IncompatiableInfo>
|
></IncompatiableInfo>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import classNames from 'classnames';
|
|||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { modelSourceMap } from '../config';
|
|
||||||
import { EvaluateResult } from '../config/types';
|
import { EvaluateResult } from '../config/types';
|
||||||
import '../style/hf-model-item.less';
|
import '../style/hf-model-item.less';
|
||||||
import IncompatiableInfo from './incompatiable-info';
|
import IncompatiableInfo from './incompatiable-info';
|
||||||
@@ -26,16 +25,12 @@ interface HFModelItemProps {
|
|||||||
source?: string;
|
source?: string;
|
||||||
tags?: string[];
|
tags?: string[];
|
||||||
evaluateResult?: EvaluateResult;
|
evaluateResult?: EvaluateResult;
|
||||||
|
isEvaluating?: boolean;
|
||||||
}
|
}
|
||||||
const warningTask = ['video'];
|
const warningTask = ['video'];
|
||||||
|
|
||||||
const SUPPORTEDSOURCE = [
|
|
||||||
modelSourceMap.huggingface_value,
|
|
||||||
modelSourceMap.modelscope_value
|
|
||||||
];
|
|
||||||
|
|
||||||
const HFModelItem: React.FC<HFModelItemProps> = (props) => {
|
const HFModelItem: React.FC<HFModelItemProps> = (props) => {
|
||||||
const { evaluateResult } = props;
|
const { evaluateResult, isEvaluating } = props;
|
||||||
console.log('evaluateResult', evaluateResult);
|
console.log('evaluateResult', evaluateResult);
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const isExcludeTask = useMemo(() => {
|
const isExcludeTask = useMemo(() => {
|
||||||
@@ -80,7 +75,12 @@ const HFModelItem: React.FC<HFModelItemProps> = (props) => {
|
|||||||
{formatNumber(props.downloads)}
|
{formatNumber(props.downloads)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{<IncompatiableInfo data={evaluateResult}></IncompatiableInfo>}
|
{
|
||||||
|
<IncompatiableInfo
|
||||||
|
data={evaluateResult}
|
||||||
|
isEvaluating={isEvaluating}
|
||||||
|
></IncompatiableInfo>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import OverlayScroller from '@/components/overlay-scroller';
|
import OverlayScroller from '@/components/overlay-scroller';
|
||||||
import { WarningOutlined } from '@ant-design/icons';
|
import { WarningOutlined } from '@ant-design/icons';
|
||||||
|
import { useIntl } from '@umijs/max';
|
||||||
import { Tag, Tooltip } from 'antd';
|
import { Tag, Tooltip } from 'antd';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
@@ -7,6 +8,7 @@ import { EvaluateResult } from '../config/types';
|
|||||||
|
|
||||||
interface IncompatiableInfoProps {
|
interface IncompatiableInfoProps {
|
||||||
data?: EvaluateResult;
|
data?: EvaluateResult;
|
||||||
|
isEvaluating?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const CompatibleTag = styled(Tag)`
|
const CompatibleTag = styled(Tag)`
|
||||||
@@ -46,8 +48,17 @@ const SMTitle = styled.div<{ $isTitle?: boolean }>`
|
|||||||
font-size: var(--font-size-small);
|
font-size: var(--font-size-small);
|
||||||
`;
|
`;
|
||||||
const IncompatiableInfo: React.FC<IncompatiableInfoProps> = (props) => {
|
const IncompatiableInfo: React.FC<IncompatiableInfoProps> = (props) => {
|
||||||
const { data } = props;
|
const { data, isEvaluating } = props;
|
||||||
if (data?.compatible) {
|
const intl = useIntl();
|
||||||
|
console.log('IncompatiableInfo', data, isEvaluating);
|
||||||
|
if (isEvaluating) {
|
||||||
|
return (
|
||||||
|
<CompatibleTag color="warning">
|
||||||
|
{intl.formatMessage({ id: 'models.form.evaluating' })}
|
||||||
|
</CompatibleTag>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (!data || data?.compatible) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
@@ -71,7 +82,7 @@ const IncompatiableInfo: React.FC<IncompatiableInfoProps> = (props) => {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<CompatibleTag icon={<WarningOutlined />} color="warning">
|
<CompatibleTag icon={<WarningOutlined />} color="warning">
|
||||||
Incompatible
|
{intl.formatMessage({ id: 'models.form.incompatible' })}
|
||||||
</CompatibleTag>
|
</CompatibleTag>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -213,7 +213,7 @@ const ModelCard: React.FC<{
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getModelCardData = async () => {
|
const getModelCardData = async () => {
|
||||||
if (!props.selectedModel.name) {
|
if (!props.selectedModel?.name) {
|
||||||
setModelData(null);
|
setModelData(null);
|
||||||
setReadmeText(null);
|
setReadmeText(null);
|
||||||
handleOnCollapse(null);
|
handleOnCollapse(null);
|
||||||
@@ -292,7 +292,7 @@ const ModelCard: React.FC<{
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getModelCardData();
|
getModelCardData();
|
||||||
}, [props.selectedModel.name]);
|
}, [props.selectedModel?.name]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
|
|||||||
@@ -20,8 +20,7 @@ import {
|
|||||||
ModelScopeSortType,
|
ModelScopeSortType,
|
||||||
ModelSortType,
|
ModelSortType,
|
||||||
ModelscopeTaskMap,
|
ModelscopeTaskMap,
|
||||||
modelSourceMap,
|
modelSourceMap
|
||||||
ollamaModelOptions
|
|
||||||
} from '../config';
|
} 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';
|
||||||
@@ -29,14 +28,15 @@ import SearchResult from './search-result';
|
|||||||
|
|
||||||
interface SearchInputProps {
|
interface SearchInputProps {
|
||||||
modelSource: string;
|
modelSource: string;
|
||||||
|
isDownload?: boolean;
|
||||||
setLoadingModel?: (flag: boolean) => void;
|
setLoadingModel?: (flag: boolean) => void;
|
||||||
onSourceChange?: (source: string) => void;
|
onSourceChange?: (source: string) => void;
|
||||||
onSelectModel: (model: any) => void;
|
onSelectModel: (model: any, isGGUF?: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SearchModel: React.FC<SearchInputProps> = (props) => {
|
const SearchModel: React.FC<SearchInputProps> = (props) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const { modelSource, setLoadingModel, onSelectModel } = props;
|
const { modelSource, isDownload, setLoadingModel, onSelectModel } = props;
|
||||||
const [dataSource, setDataSource] = useState<{
|
const [dataSource, setDataSource] = useState<{
|
||||||
repoOptions: any[];
|
repoOptions: any[];
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
@@ -52,13 +52,17 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
|
|||||||
modelSourceMap.huggingface_value,
|
modelSourceMap.huggingface_value,
|
||||||
modelSourceMap.modelscope_value
|
modelSourceMap.modelscope_value
|
||||||
];
|
];
|
||||||
|
const [isEvaluating, setIsEvaluating] = useState<boolean>(false);
|
||||||
const [current, setCurrent] = useState<string>('');
|
const [current, setCurrent] = useState<string>('');
|
||||||
const cacheRepoOptions = useRef<any[]>([]);
|
const cacheRepoOptions = useRef<any[]>([]);
|
||||||
const axiosTokenRef = useRef<any>(null);
|
const axiosTokenRef = useRef<any>(null);
|
||||||
const checkTokenRef = useRef<any>(null);
|
const checkTokenRef = useRef<any>(null);
|
||||||
|
const evaluateTokenRef = useRef<any>(null);
|
||||||
const searchInputRef = useRef<any>('');
|
const searchInputRef = useRef<any>('');
|
||||||
const filterGGUFRef = useRef<boolean | undefined>();
|
const filterGGUFRef = useRef<boolean | undefined>();
|
||||||
const filterTaskRef = useRef<string>('');
|
const filterTaskRef = useRef<string>('');
|
||||||
|
const timer = useRef<any>(null);
|
||||||
|
const workerRef = useRef<any>(null);
|
||||||
const modelFilesSortOptions = useRef<any[]>([
|
const modelFilesSortOptions = useRef<any[]>([
|
||||||
{
|
{
|
||||||
label: intl.formatMessage({ id: 'models.sort.trending' }),
|
label: intl.formatMessage({ id: 'models.sort.trending' }),
|
||||||
@@ -78,10 +82,20 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
|
|||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const handleOnSelectModel = useCallback((item: any) => {
|
const checkIsGGUF = (item: any) => {
|
||||||
onSelectModel(item);
|
const isGGUF = _.some(item.tags, (tag: string) => {
|
||||||
|
return tag.toLowerCase() === 'gguf';
|
||||||
|
});
|
||||||
|
const isGGUFFromMs = _.some(item.libraries, (tag: string) => {
|
||||||
|
return tag.toLowerCase() === 'gguf';
|
||||||
|
});
|
||||||
|
return isGGUF || isGGUFFromMs;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOnSelectModel = (item: any) => {
|
||||||
|
onSelectModel(item, checkIsGGUF(item));
|
||||||
setCurrent(item.id);
|
setCurrent(item.id);
|
||||||
}, []);
|
};
|
||||||
|
|
||||||
// huggeface
|
// huggeface
|
||||||
const getModelsFromHuggingface = useCallback(async (sort: string) => {
|
const getModelsFromHuggingface = useCallback(async (sort: string) => {
|
||||||
@@ -136,7 +150,9 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
|
|||||||
value: item.Name,
|
value: item.Name,
|
||||||
label: item.Name,
|
label: item.Name,
|
||||||
revision: item.Revision,
|
revision: item.Revision,
|
||||||
task: item.Tasks?.map((sItem: any) => sItem.Name).join(',')
|
task: item.Tasks?.map((sItem: any) => sItem.Name).join(','),
|
||||||
|
tags: item.Tags,
|
||||||
|
libraries: item.Libraries
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -164,70 +180,131 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleOnSearchRepo = useCallback(
|
const handleEvaluate = async (list: any[]) => {
|
||||||
async (sortType?: string) => {
|
if (isDownload) {
|
||||||
if (!SUPPORTEDSOURCE.includes(modelSource)) {
|
return;
|
||||||
return;
|
}
|
||||||
}
|
try {
|
||||||
axiosTokenRef.current?.abort?.();
|
const repoList = list.map((item) => {
|
||||||
axiosTokenRef.current = new AbortController();
|
return {
|
||||||
const sort = sortType ?? dataSource.sortType;
|
source: modelSource,
|
||||||
try {
|
...(modelSource === modelSourceMap.huggingface_value
|
||||||
|
? {
|
||||||
|
huggingface_repo_id: item.name
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
model_scope_model_id: item.name
|
||||||
|
})
|
||||||
|
};
|
||||||
|
});
|
||||||
|
setIsEvaluating(true);
|
||||||
|
const evaluations = await getEvaluateResults(repoList);
|
||||||
|
const resultList = list.map((item, index) => {
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
evaluateResult: evaluations[index] || null
|
||||||
|
};
|
||||||
|
});
|
||||||
|
setIsEvaluating(false);
|
||||||
|
setDataSource((pre) => {
|
||||||
|
return {
|
||||||
|
...pre,
|
||||||
|
loading: false,
|
||||||
|
repoOptions: resultList
|
||||||
|
};
|
||||||
|
});
|
||||||
|
handleOnSelectModel(resultList[0]);
|
||||||
|
} catch (error) {
|
||||||
|
setIsEvaluating(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEvaluateWorker = (params: {
|
||||||
|
list: any[];
|
||||||
|
modelSource: string;
|
||||||
|
modelSourceMap: any;
|
||||||
|
}) => {
|
||||||
|
console.log('handleEvaluateWorker=======');
|
||||||
|
const { list, modelSource, modelSourceMap } = params;
|
||||||
|
workerRef.current?.terminate();
|
||||||
|
setIsEvaluating(true);
|
||||||
|
workerRef.current = new Worker(
|
||||||
|
// @ts-ignore
|
||||||
|
new URL('../apis/evaluateWorker.ts', import.meta.url)
|
||||||
|
);
|
||||||
|
workerRef.current.postMessage({
|
||||||
|
list,
|
||||||
|
modelSource,
|
||||||
|
modelSourceMap
|
||||||
|
});
|
||||||
|
workerRef.current.onmessage = function (event: any) {
|
||||||
|
const { success, resultList } = event.data;
|
||||||
|
if (success) {
|
||||||
setDataSource((pre) => {
|
setDataSource((pre) => {
|
||||||
pre.loading = true;
|
|
||||||
return { ...pre };
|
|
||||||
});
|
|
||||||
setLoadingModel?.(true);
|
|
||||||
cacheRepoOptions.current = [];
|
|
||||||
let list: any[] = [];
|
|
||||||
if (modelSource === modelSourceMap.huggingface_value) {
|
|
||||||
list = await getModelsFromHuggingface(sort);
|
|
||||||
} else if (modelSource === modelSourceMap.modelscope_value) {
|
|
||||||
list = await getModelsFromModelscope(sort);
|
|
||||||
}
|
|
||||||
cacheRepoOptions.current = list;
|
|
||||||
const repoList = list.map((item) => {
|
|
||||||
return {
|
return {
|
||||||
source: modelSource,
|
...pre,
|
||||||
...(modelSource === modelSourceMap.huggingface_value
|
repoOptions: resultList
|
||||||
? {
|
|
||||||
huggingface_repo_id: item.name
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
model_scope_model_id: item.name
|
|
||||||
})
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
const evaluations = await getEvaluateResults(repoList);
|
|
||||||
list = list.map((item, index) => {
|
|
||||||
return {
|
|
||||||
...item,
|
|
||||||
evaluateResult: evaluations[index]
|
|
||||||
};
|
|
||||||
});
|
|
||||||
console.log('list:', evaluations);
|
|
||||||
setDataSource({
|
|
||||||
repoOptions: list,
|
|
||||||
loading: false,
|
|
||||||
networkError: false,
|
|
||||||
sortType: sort
|
|
||||||
});
|
|
||||||
setLoadingModel?.(false);
|
|
||||||
handleOnSelectModel(list[0]);
|
|
||||||
} catch (error: any) {
|
|
||||||
setDataSource({
|
|
||||||
repoOptions: [],
|
|
||||||
loading: false,
|
|
||||||
sortType: sort,
|
|
||||||
networkError: error?.message === 'Failed to fetch'
|
|
||||||
});
|
|
||||||
setLoadingModel?.(false);
|
|
||||||
handleOnSelectModel({});
|
|
||||||
cacheRepoOptions.current = [];
|
|
||||||
}
|
}
|
||||||
},
|
setIsEvaluating(false);
|
||||||
[dataSource.sortType, modelSource]
|
handleOnSelectModel(resultList[0]);
|
||||||
);
|
workerRef.current.terminate();
|
||||||
|
workerRef.current = null;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOnSearchRepo = async (sortType?: string) => {
|
||||||
|
if (!SUPPORTEDSOURCE.includes(modelSource)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
axiosTokenRef.current?.abort?.();
|
||||||
|
axiosTokenRef.current = new AbortController();
|
||||||
|
checkTokenRef.current?.cancel?.();
|
||||||
|
if (timer.current) {
|
||||||
|
clearTimeout(timer.current);
|
||||||
|
}
|
||||||
|
const sort = sortType ?? dataSource.sortType;
|
||||||
|
try {
|
||||||
|
setDataSource((pre) => {
|
||||||
|
pre.loading = true;
|
||||||
|
return { ...pre };
|
||||||
|
});
|
||||||
|
setLoadingModel?.(true);
|
||||||
|
cacheRepoOptions.current = [];
|
||||||
|
let list: any[] = [];
|
||||||
|
if (modelSource === modelSourceMap.huggingface_value) {
|
||||||
|
list = await getModelsFromHuggingface(sort);
|
||||||
|
} else if (modelSource === modelSourceMap.modelscope_value) {
|
||||||
|
list = await getModelsFromModelscope(sort);
|
||||||
|
}
|
||||||
|
cacheRepoOptions.current = list;
|
||||||
|
console.log('list=========', list);
|
||||||
|
|
||||||
|
setDataSource({
|
||||||
|
repoOptions: list,
|
||||||
|
loading: false,
|
||||||
|
networkError: false,
|
||||||
|
sortType: sort
|
||||||
|
});
|
||||||
|
handleOnSelectModel(list[0]);
|
||||||
|
setLoadingModel?.(false);
|
||||||
|
|
||||||
|
timer.current = setTimeout(() => {
|
||||||
|
handleEvaluate(list);
|
||||||
|
}, 200);
|
||||||
|
} catch (error: any) {
|
||||||
|
setDataSource({
|
||||||
|
repoOptions: [],
|
||||||
|
loading: false,
|
||||||
|
sortType: sort,
|
||||||
|
networkError: error?.message === 'Failed to fetch'
|
||||||
|
});
|
||||||
|
setLoadingModel?.(false);
|
||||||
|
handleOnSelectModel({});
|
||||||
|
cacheRepoOptions.current = [];
|
||||||
|
}
|
||||||
|
};
|
||||||
const handleSearchInputChange = useCallback((e: any) => {
|
const handleSearchInputChange = useCallback((e: any) => {
|
||||||
searchInputRef.current = e.target.value;
|
searchInputRef.current = e.target.value;
|
||||||
console.log('change:', searchInputRef.current);
|
console.log('change:', searchInputRef.current);
|
||||||
@@ -243,16 +320,6 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
|
|||||||
) {
|
) {
|
||||||
handleOnSearchRepo();
|
handleOnSearchRepo();
|
||||||
}
|
}
|
||||||
if (modelSourceMap.ollama_library_value === modelSource) {
|
|
||||||
setDataSource({
|
|
||||||
repoOptions: ollamaModelOptions,
|
|
||||||
loading: false,
|
|
||||||
networkError: false,
|
|
||||||
sortType: dataSource.sortType
|
|
||||||
});
|
|
||||||
cacheRepoOptions.current = ollamaModelOptions;
|
|
||||||
handleOnSelectModel(ollamaModelOptions[0]);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSortChange = (value: string) => {
|
const handleSortChange = (value: string) => {
|
||||||
@@ -264,11 +331,6 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
|
|||||||
handleOnSearchRepo();
|
handleOnSearchRepo();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFilterTaskChange = useCallback((value: string) => {
|
|
||||||
filterTaskRef.current = value;
|
|
||||||
handleOnSearchRepo();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const renderGGUFTips = useMemo(() => {
|
const renderGGUFTips = useMemo(() => {
|
||||||
return (
|
return (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
@@ -345,13 +407,16 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
handleOnOpen();
|
handleOnOpen();
|
||||||
console.log('SearchModel useEffect', modelSource);
|
|
||||||
}, [modelSource]);
|
}, [modelSource]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
axiosTokenRef.current?.abort?.();
|
axiosTokenRef.current?.abort?.();
|
||||||
checkTokenRef.current?.cancel?.();
|
checkTokenRef.current?.cancel?.();
|
||||||
|
// workerRef.current?.terminate();
|
||||||
|
if (timer.current) {
|
||||||
|
clearTimeout(timer.current);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -364,6 +429,7 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
|
|||||||
networkError={dataSource.networkError}
|
networkError={dataSource.networkError}
|
||||||
current={current}
|
current={current}
|
||||||
source={modelSource}
|
source={modelSource}
|
||||||
|
isEvaluating={isEvaluating}
|
||||||
onSelect={handleOnSelectModel}
|
onSelect={handleOnSelectModel}
|
||||||
></SearchResult>
|
></SearchResult>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -17,10 +17,11 @@ interface SearchResultProps {
|
|||||||
style?: React.CSSProperties;
|
style?: React.CSSProperties;
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
networkError?: boolean;
|
networkError?: boolean;
|
||||||
|
isEvaluating?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SearchResult: React.FC<SearchResultProps> = (props) => {
|
const SearchResult: React.FC<SearchResultProps> = (props) => {
|
||||||
const { resultList, onSelect, source, networkError } = props;
|
const { resultList, onSelect, isEvaluating, source, networkError } = props;
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
const handleSelect = (e: any, item: any) => {
|
const handleSelect = (e: any, item: any) => {
|
||||||
@@ -111,6 +112,7 @@ const SearchResult: React.FC<SearchResultProps> = (props) => {
|
|||||||
updatedAt={item.updatedAt}
|
updatedAt={item.updatedAt}
|
||||||
evaluateResult={item.evaluateResult}
|
evaluateResult={item.evaluateResult}
|
||||||
active={item.id === props.current}
|
active={item.id === props.current}
|
||||||
|
isEvaluating={isEvaluating}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Col>
|
</Col>
|
||||||
|
|||||||
@@ -71,7 +71,12 @@ import {
|
|||||||
setModelActionList,
|
setModelActionList,
|
||||||
sourceOptions
|
sourceOptions
|
||||||
} from '../config/button-actions';
|
} from '../config/button-actions';
|
||||||
import { FormData, ListItem, ModelInstanceListItem } from '../config/types';
|
import {
|
||||||
|
FormData,
|
||||||
|
ListItem,
|
||||||
|
ModelInstanceListItem,
|
||||||
|
SourceType
|
||||||
|
} from '../config/types';
|
||||||
import { useGenerateFormEditInitialValues } from '../hooks';
|
import { useGenerateFormEditInitialValues } from '../hooks';
|
||||||
import DeployModal from './deploy-modal';
|
import DeployModal from './deploy-modal';
|
||||||
import Instances from './instances';
|
import Instances from './instances';
|
||||||
@@ -174,13 +179,13 @@ const Models: React.FC<ModelsProps> = ({
|
|||||||
const [openDeployModal, setOpenDeployModal] = useState<{
|
const [openDeployModal, setOpenDeployModal] = useState<{
|
||||||
show: boolean;
|
show: boolean;
|
||||||
width: number | string;
|
width: number | string;
|
||||||
source: string;
|
source: SourceType;
|
||||||
gpuOptions: any[];
|
gpuOptions: any[];
|
||||||
modelFileOptions?: any[];
|
modelFileOptions?: any[];
|
||||||
}>({
|
}>({
|
||||||
show: false,
|
show: false,
|
||||||
width: 600,
|
width: 600,
|
||||||
source: modelSourceMap.huggingface_value,
|
source: modelSourceMap.huggingface_value as SourceType,
|
||||||
gpuOptions: [],
|
gpuOptions: [],
|
||||||
modelFileOptions: []
|
modelFileOptions: []
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
interface FormContextProps {
|
||||||
|
isGGUF: boolean;
|
||||||
|
byBuiltIn?: boolean;
|
||||||
|
sizeOptions?: Global.BaseOption<number>[];
|
||||||
|
quantizationOptions?: Global.BaseOption<string>[];
|
||||||
|
modelFileOptions?: any[];
|
||||||
|
onSizeChange?: (val: number) => void;
|
||||||
|
onQuantizationChange?: (val: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FormInnerContextProps {
|
||||||
|
onBackendChange?: (backend: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FormContext = React.createContext<FormContextProps>(
|
||||||
|
{} as FormContextProps
|
||||||
|
);
|
||||||
|
|
||||||
|
export const FormInnerContext = React.createContext<FormInnerContextProps>(
|
||||||
|
{} as FormInnerContextProps
|
||||||
|
);
|
||||||
|
|
||||||
|
export const useFormContext = () => {
|
||||||
|
const context = React.useContext(FormContext);
|
||||||
|
if (!context) {
|
||||||
|
throw new Error('useFormContext must be used within a FormProvider');
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useFormInnerContext = () => {
|
||||||
|
const context = React.useContext(FormInnerContext);
|
||||||
|
if (!context) {
|
||||||
|
throw new Error(
|
||||||
|
'useFormInnerContext must be used within a FormInnerProvider'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
};
|
||||||
@@ -177,14 +177,22 @@ export const AudioModeTypeMap = {
|
|||||||
Whisper: ['Whisper', 'whisper'],
|
Whisper: ['Whisper', 'whisper'],
|
||||||
CosyVoice: ['CosyVoice', 'cosyvoice', 'cosy-voice', 'cosy_voice']
|
CosyVoice: ['CosyVoice', 'cosyvoice', 'cosy-voice', 'cosy_voice']
|
||||||
};
|
};
|
||||||
|
interface ModelSource {
|
||||||
|
huggingface: string;
|
||||||
|
huggingface_value: string;
|
||||||
|
ollama_library: string;
|
||||||
|
ollama_library_value: string;
|
||||||
|
modelScope: string;
|
||||||
|
modelscope_value: string;
|
||||||
|
local_path: string;
|
||||||
|
local_path_value: string;
|
||||||
|
}
|
||||||
|
|
||||||
export const modelSourceMap: Record<string, string> = {
|
export const modelSourceMap: ModelSource = {
|
||||||
huggingface: 'Hugging Face',
|
huggingface: 'Hugging Face',
|
||||||
ollama_library: 'Ollama Library',
|
|
||||||
s3: 'S3',
|
|
||||||
huggingface_value: 'huggingface',
|
huggingface_value: 'huggingface',
|
||||||
|
ollama_library: 'Ollama Library',
|
||||||
ollama_library_value: 'ollama_library',
|
ollama_library_value: 'ollama_library',
|
||||||
s3_value: 's3',
|
|
||||||
modelScope: 'ModelScope',
|
modelScope: 'ModelScope',
|
||||||
modelscope_value: 'model_scope',
|
modelscope_value: 'model_scope',
|
||||||
local_path: 'Local Path',
|
local_path: 'Local Path',
|
||||||
@@ -194,7 +202,6 @@ export const modelSourceMap: Record<string, string> = {
|
|||||||
export const modelSourceValueMap = {
|
export const modelSourceValueMap = {
|
||||||
[modelSourceMap.huggingface_value]: modelSourceMap.huggingface,
|
[modelSourceMap.huggingface_value]: modelSourceMap.huggingface,
|
||||||
[modelSourceMap.ollama_library_value]: modelSourceMap.ollama_library,
|
[modelSourceMap.ollama_library_value]: modelSourceMap.ollama_library,
|
||||||
[modelSourceMap.s3_value]: modelSourceMap.s3,
|
|
||||||
[modelSourceMap.modelscope_value]: modelSourceMap.modelScope,
|
[modelSourceMap.modelscope_value]: modelSourceMap.modelScope,
|
||||||
[modelSourceMap.local_path_value]: modelSourceMap.local_path
|
[modelSourceMap.local_path_value]: modelSourceMap.local_path
|
||||||
};
|
};
|
||||||
@@ -459,5 +466,7 @@ export const excludeFields = [
|
|||||||
'name',
|
'name',
|
||||||
'description',
|
'description',
|
||||||
'env',
|
'env',
|
||||||
'source'
|
'source',
|
||||||
|
'quantization',
|
||||||
|
'size'
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -29,6 +29,12 @@ export interface ListItem {
|
|||||||
worker_selector?: object;
|
worker_selector?: object;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type SourceType =
|
||||||
|
| 'huggingface'
|
||||||
|
| 'model_scope'
|
||||||
|
| 'local_path'
|
||||||
|
| 'ollama_library';
|
||||||
|
|
||||||
export interface FormData {
|
export interface FormData {
|
||||||
backend: string;
|
backend: string;
|
||||||
env?: Record<string, any>;
|
env?: Record<string, any>;
|
||||||
|
|||||||
@@ -174,6 +174,7 @@ const DownloadModel: React.FC<AddModalProps> = (props) => {
|
|||||||
<SearchModel
|
<SearchModel
|
||||||
modelSource={props.source}
|
modelSource={props.source}
|
||||||
onSelectModel={handleOnSelectModel}
|
onSelectModel={handleOnSelectModel}
|
||||||
|
isDownload={true}
|
||||||
></SearchModel>
|
></SearchModel>
|
||||||
</ColumnWrapper>
|
</ColumnWrapper>
|
||||||
<Separator></Separator>
|
<Separator></Separator>
|
||||||
@@ -200,6 +201,7 @@ const DownloadModel: React.FC<AddModalProps> = (props) => {
|
|||||||
modelSource={props.source}
|
modelSource={props.source}
|
||||||
onSelectFile={handleSelectModelFile}
|
onSelectFile={handleSelectModelFile}
|
||||||
collapsed={collapsed}
|
collapsed={collapsed}
|
||||||
|
isDownload={true}
|
||||||
></HFModelFile>
|
></HFModelFile>
|
||||||
)}
|
)}
|
||||||
</ColumnWrapper>
|
</ColumnWrapper>
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
import SealSelect from '@/components/seal-form/seal-select';
|
||||||
|
import useAppUtils from '@/hooks/use-app-utils';
|
||||||
|
import { Form } from 'antd';
|
||||||
|
import React from 'react';
|
||||||
|
import { useFormContext } from '../config/form-context';
|
||||||
|
import { FormData } from '../config/types';
|
||||||
|
|
||||||
|
const CatalogForm: React.FC = () => {
|
||||||
|
const formCtx = useFormContext();
|
||||||
|
const { getRuleMessage } = useAppUtils();
|
||||||
|
const {
|
||||||
|
isGGUF,
|
||||||
|
byBuiltIn,
|
||||||
|
sizeOptions,
|
||||||
|
quantizationOptions,
|
||||||
|
onSizeChange,
|
||||||
|
onQuantizationChange
|
||||||
|
} = formCtx;
|
||||||
|
const source = Form.useWatch('source');
|
||||||
|
|
||||||
|
console.log('HuggingFaceForm', { source, isGGUF });
|
||||||
|
|
||||||
|
if (!byBuiltIn && !sizeOptions?.length && !quantizationOptions?.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSizeChange = (val: any) => {
|
||||||
|
onSizeChange?.(val);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOnQuantizationChange = (val: any) => {
|
||||||
|
onQuantizationChange?.(val);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{sizeOptions && sizeOptions?.length > 0 && (
|
||||||
|
<Form.Item<FormData>
|
||||||
|
name="size"
|
||||||
|
key="size"
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: getRuleMessage('input', 'size', false)
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<SealSelect
|
||||||
|
filterOption
|
||||||
|
onChange={handleSizeChange}
|
||||||
|
defaultActiveFirstOption
|
||||||
|
disabled={false}
|
||||||
|
options={sizeOptions}
|
||||||
|
label="Size"
|
||||||
|
required
|
||||||
|
></SealSelect>
|
||||||
|
</Form.Item>
|
||||||
|
)}
|
||||||
|
{quantizationOptions && quantizationOptions?.length > 0 && (
|
||||||
|
<Form.Item<FormData>
|
||||||
|
name="quantization"
|
||||||
|
key="quantization"
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: getRuleMessage('select', 'quantization', false)
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<SealSelect
|
||||||
|
filterOption
|
||||||
|
defaultActiveFirstOption
|
||||||
|
disabled={false}
|
||||||
|
options={quantizationOptions}
|
||||||
|
onChange={handleOnQuantizationChange}
|
||||||
|
label="Quantization"
|
||||||
|
required
|
||||||
|
></SealSelect>
|
||||||
|
</Form.Item>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CatalogForm;
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
import SealInput from '@/components/seal-form/seal-input';
|
||||||
|
import useAppUtils from '@/hooks/use-app-utils';
|
||||||
|
import { useIntl } from '@umijs/max';
|
||||||
|
import { Form } from 'antd';
|
||||||
|
import React from 'react';
|
||||||
|
import { modelSourceMap } from '../config';
|
||||||
|
import { useFormContext } from '../config/form-context';
|
||||||
|
import { FormData } from '../config/types';
|
||||||
|
|
||||||
|
const HuggingFaceForm: React.FC = () => {
|
||||||
|
const formCtx = useFormContext();
|
||||||
|
const { getRuleMessage } = useAppUtils();
|
||||||
|
const intl = useIntl();
|
||||||
|
const { isGGUF, byBuiltIn } = formCtx;
|
||||||
|
const source = Form.useWatch('source');
|
||||||
|
|
||||||
|
console.log('HuggingFaceForm', { source, isGGUF });
|
||||||
|
|
||||||
|
if (
|
||||||
|
![
|
||||||
|
modelSourceMap.huggingface_value,
|
||||||
|
modelSourceMap.modelscope_value
|
||||||
|
].includes(source) ||
|
||||||
|
byBuiltIn
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Form.Item<FormData>
|
||||||
|
name="repo_id"
|
||||||
|
key="repo_id"
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: getRuleMessage('input', 'models.form.repoid')
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<SealInput.Input
|
||||||
|
label={intl.formatMessage({ id: 'models.form.repoid' })}
|
||||||
|
required
|
||||||
|
disabled={true}
|
||||||
|
></SealInput.Input>
|
||||||
|
</Form.Item>
|
||||||
|
{isGGUF && (
|
||||||
|
<Form.Item<FormData>
|
||||||
|
name="file_name"
|
||||||
|
key="file_name"
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: getRuleMessage('input', 'models.form.filename')
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<SealInput.Input
|
||||||
|
label={intl.formatMessage({ id: 'models.form.filename' })}
|
||||||
|
required
|
||||||
|
disabled={true}
|
||||||
|
></SealInput.Input>
|
||||||
|
</Form.Item>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default HuggingFaceForm;
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
import SealAutoComplete from '@/components/seal-form/auto-complete';
|
||||||
|
import TooltipList from '@/components/tooltip-list';
|
||||||
|
import useAppUtils from '@/hooks/use-app-utils';
|
||||||
|
import { useIntl } from '@umijs/max';
|
||||||
|
import { Form } from 'antd';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import React, { useRef } from 'react';
|
||||||
|
import {
|
||||||
|
backendOptionsMap,
|
||||||
|
localPathTipsList,
|
||||||
|
modelSourceMap
|
||||||
|
} from '../config';
|
||||||
|
import { useFormContext, useFormInnerContext } from '../config/form-context';
|
||||||
|
import { FormData } from '../config/types';
|
||||||
|
|
||||||
|
const LocalPathForm: React.FC = () => {
|
||||||
|
const form = Form.useFormInstance();
|
||||||
|
const formCtx = useFormContext();
|
||||||
|
const formInnerCtx = useFormInnerContext();
|
||||||
|
const source = Form.useWatch('source', form);
|
||||||
|
const { onBackendChange } = formInnerCtx;
|
||||||
|
const { modelFileOptions, byBuiltIn } = formCtx;
|
||||||
|
const { getRuleMessage } = useAppUtils();
|
||||||
|
const intl = useIntl();
|
||||||
|
const localPathCache = useRef<string>(form.getFieldValue('local_path'));
|
||||||
|
|
||||||
|
if (![modelSourceMap.local_path_value].includes(source) || byBuiltIn) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleLocalPathBlur = (e: any) => {
|
||||||
|
const value = e.target.value;
|
||||||
|
if (value === localPathCache.current && value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const isEndwithGGUF = _.endsWith(value, '.gguf');
|
||||||
|
const isBlobFile = value.split('/').pop().includes('sha256');
|
||||||
|
let backend = backendOptionsMap.llamaBox;
|
||||||
|
if (!isEndwithGGUF && !isBlobFile) {
|
||||||
|
backend = backendOptionsMap.vllm;
|
||||||
|
}
|
||||||
|
form.setFieldValue('backend', backend);
|
||||||
|
|
||||||
|
onBackendChange?.(backend);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOnFocus = () => {
|
||||||
|
localPathCache.current = form.getFieldValue('local_path');
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Form.Item<FormData>
|
||||||
|
name="local_path"
|
||||||
|
key="local_path"
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: getRuleMessage('input', 'models.form.filePath')
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<SealAutoComplete
|
||||||
|
allowClear
|
||||||
|
required
|
||||||
|
filterOption
|
||||||
|
defaultActiveFirstOption
|
||||||
|
options={modelFileOptions}
|
||||||
|
onBlur={handleLocalPathBlur}
|
||||||
|
onFocus={handleOnFocus}
|
||||||
|
label={intl.formatMessage({ id: 'models.form.filePath' })}
|
||||||
|
description={<TooltipList list={localPathTipsList}></TooltipList>}
|
||||||
|
></SealAutoComplete>
|
||||||
|
</Form.Item>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LocalPathForm;
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import IconFont from '@/components/icon-font';
|
||||||
|
import SealAutoComplete from '@/components/seal-form/auto-complete';
|
||||||
|
import useAppUtils from '@/hooks/use-app-utils';
|
||||||
|
import { useIntl } from '@umijs/max';
|
||||||
|
import { Form, Typography } from 'antd';
|
||||||
|
import React from 'react';
|
||||||
|
import { modelSourceMap, ollamaModelOptions } from '../config';
|
||||||
|
import { useFormContext, useFormInnerContext } from '../config/form-context';
|
||||||
|
import { FormData } from '../config/types';
|
||||||
|
|
||||||
|
const OllamaForm: React.FC = () => {
|
||||||
|
const formCtx = useFormContext();
|
||||||
|
const formInnerCtx = useFormInnerContext();
|
||||||
|
const { byBuiltIn } = formCtx;
|
||||||
|
const { getRuleMessage } = useAppUtils();
|
||||||
|
const intl = useIntl();
|
||||||
|
const source = Form.useWatch('source');
|
||||||
|
const formInstance = Form.useFormInstance();
|
||||||
|
|
||||||
|
if (![modelSourceMap.ollama_library_value].includes(source) || byBuiltIn) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Form.Item<FormData>
|
||||||
|
name="ollama_library_model_name"
|
||||||
|
key="ollama_library_model_name"
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: getRuleMessage('input', 'models.table.name')
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<SealAutoComplete
|
||||||
|
allowClear
|
||||||
|
filterOption
|
||||||
|
defaultActiveFirstOption
|
||||||
|
disabled={false}
|
||||||
|
options={ollamaModelOptions}
|
||||||
|
description={
|
||||||
|
<span>
|
||||||
|
<span>
|
||||||
|
{intl.formatMessage({ id: 'models.form.ollamalink' })}
|
||||||
|
</span>
|
||||||
|
<Typography.Link
|
||||||
|
className="flex-center"
|
||||||
|
href="https://www.ollama.com/library"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
<IconFont
|
||||||
|
type="icon-external-link"
|
||||||
|
className="font-size-14"
|
||||||
|
></IconFont>
|
||||||
|
</Typography.Link>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
label={intl.formatMessage({ id: 'model.form.ollama.model' })}
|
||||||
|
placeholder={intl.formatMessage({ id: 'model.form.ollamaholder' })}
|
||||||
|
required
|
||||||
|
></SealAutoComplete>
|
||||||
|
</Form.Item>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default OllamaForm;
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
|
import { createAxiosToken } from '@/hooks/use-chunk-request';
|
||||||
import { queryModelFilesList } from '@/pages/resources/apis';
|
import { queryModelFilesList } from '@/pages/resources/apis';
|
||||||
import { ListItem as WorkerListItem } from '@/pages/resources/config/types';
|
import { ListItem as WorkerListItem } from '@/pages/resources/config/types';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import { queryGPUList } from '../apis';
|
import { evaluationsModelSpec, queryGPUList } from '../apis';
|
||||||
import {
|
import {
|
||||||
backendOptionsMap,
|
backendOptionsMap,
|
||||||
modelSourceMap,
|
modelSourceMap,
|
||||||
@@ -185,6 +186,7 @@ export const useGenerateModelFileOptions = () => {
|
|||||||
export const useCheckCompatibility = () => {
|
export const useCheckCompatibility = () => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
|
const checkTokenRef = useRef<any>(null);
|
||||||
const [warningStatus, setWarningStatus] = useState<{
|
const [warningStatus, setWarningStatus] = useState<{
|
||||||
show: boolean;
|
show: boolean;
|
||||||
title?: string;
|
title?: string;
|
||||||
@@ -195,6 +197,38 @@ export const useCheckCompatibility = () => {
|
|||||||
message: []
|
message: []
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const handleEvaluate = async (data: any) => {
|
||||||
|
try {
|
||||||
|
checkTokenRef.current?.cancel();
|
||||||
|
checkTokenRef.current = createAxiosToken();
|
||||||
|
setWarningStatus({
|
||||||
|
show: true,
|
||||||
|
title: '',
|
||||||
|
message: 'Evaluating compatibility...'
|
||||||
|
});
|
||||||
|
const evalution = await evaluationsModelSpec(
|
||||||
|
{
|
||||||
|
model_specs: [
|
||||||
|
{
|
||||||
|
..._.omit(data, ['scheduleType']),
|
||||||
|
categories: Array.isArray(data.categories)
|
||||||
|
? data.categories
|
||||||
|
: data.categories
|
||||||
|
? [data.categories]
|
||||||
|
: []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
token: checkTokenRef.current.token
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return evalution.results?.[0];
|
||||||
|
} catch (error) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleCheckCompatibility = (evaluateResult: EvaluateResult | null) => {
|
const handleCheckCompatibility = (evaluateResult: EvaluateResult | null) => {
|
||||||
if (!evaluateResult) {
|
if (!evaluateResult) {
|
||||||
return {
|
return {
|
||||||
@@ -277,13 +311,22 @@ export const useCheckCompatibility = () => {
|
|||||||
source: string;
|
source: string;
|
||||||
}) => {
|
}) => {
|
||||||
const warningMessage = updateShowWarning(params);
|
const warningMessage = updateShowWarning(params);
|
||||||
setWarningStatus(warningMessage);
|
|
||||||
return warningMessage;
|
return warningMessage;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
checkTokenRef.current?.cancel();
|
||||||
|
checkTokenRef.current = null;
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
handleShowCompatibleAlert,
|
handleShowCompatibleAlert,
|
||||||
handleUpdateWarning,
|
handleUpdateWarning,
|
||||||
warningStatus
|
warningStatus,
|
||||||
|
checkTokenRef,
|
||||||
|
handleEvaluate,
|
||||||
|
setWarningStatus
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
color: var(--ant-color-text-tertiary);
|
color: var(--ant-color-text-tertiary);
|
||||||
font-size: var(--font-size-small);
|
font-size: var(--font-size-small);
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
height: 22px;
|
||||||
|
|
||||||
.info-item {
|
.info-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
Reference in New Issue
Block a user