chore: catalog card style
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 726 B |
@@ -204,6 +204,10 @@
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.m-r-16 {
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
.font-400 {
|
||||
font-weight: var(--font-weight-normal);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { createFromIconfontCN } from '@ant-design/icons';
|
||||
// import './iconfont/iconfont.js';
|
||||
|
||||
const IconFont = createFromIconfontCN({
|
||||
scriptUrl: '//at.alicdn.com/t/c/font_4613488_trmdczqx9z.js'
|
||||
scriptUrl: '//at.alicdn.com/t/c/font_4613488_nlj2pbwi.js'
|
||||
});
|
||||
|
||||
export default IconFont;
|
||||
|
||||
@@ -6,7 +6,8 @@ import './index.less';
|
||||
const TagsWrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
const wrapperRef = useRef<HTMLDivElement>(null);
|
||||
const observer = useRef<IntersectionObserver | null>(null);
|
||||
const [hiddenIndex, setHiddenIndex] = useState<number>(0);
|
||||
const resizeObserver = useRef<ResizeObserver | null>(null);
|
||||
const [hiddenIndex, setHiddenIndex] = useState<number>(4);
|
||||
const uid = useRef(0);
|
||||
|
||||
const updateUid = () => {
|
||||
@@ -17,55 +18,72 @@ const TagsWrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
const dropItems = useMemo(() => {
|
||||
return React.Children.toArray(children)
|
||||
.slice(hiddenIndex)
|
||||
.map((child, index) => ({
|
||||
.map((child) => ({
|
||||
label: child,
|
||||
key: updateUid()
|
||||
}));
|
||||
}, [children, hiddenIndex]);
|
||||
|
||||
const updateHiddenIndex = () => {
|
||||
if (!wrapperRef.current || !observer.current) return;
|
||||
|
||||
const childrenList = Array.from(wrapperRef.current.children);
|
||||
let newHiddenIndex = 0;
|
||||
|
||||
childrenList.forEach((child, index) => {
|
||||
const rect = child.getBoundingClientRect();
|
||||
// visible in wrapperRef
|
||||
const issivible =
|
||||
rect.top < wrapperRef.current!.clientHeight &&
|
||||
rect.bottom > 0 &&
|
||||
rect.left < wrapperRef.current!.clientWidth &&
|
||||
rect.right > 0;
|
||||
|
||||
if (!issivible) {
|
||||
newHiddenIndex = Math.max(newHiddenIndex, index + 1);
|
||||
}
|
||||
});
|
||||
|
||||
setHiddenIndex(newHiddenIndex);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!wrapperRef.current) return;
|
||||
|
||||
observer.current = new IntersectionObserver(
|
||||
(entries) => {
|
||||
let newHiddenIndex = 0;
|
||||
entries.forEach((entry, index) => {
|
||||
if (entry.intersectionRatio < 1) {
|
||||
newHiddenIndex = Math.min(newHiddenIndex, index);
|
||||
}
|
||||
console.log(
|
||||
'isIntersecting=======',
|
||||
entry.intersectionRatio,
|
||||
newHiddenIndex
|
||||
);
|
||||
});
|
||||
setHiddenIndex(() => newHiddenIndex);
|
||||
},
|
||||
{
|
||||
root: wrapperRef.current,
|
||||
threshold: 0
|
||||
}
|
||||
);
|
||||
// observer.current = new IntersectionObserver(
|
||||
// (entries) => {
|
||||
// const lastEntry = entries[entries.length - 1];
|
||||
// if (lastEntry && lastEntry.intersectionRatio < 1) {
|
||||
// setHiddenIndex((prev) =>
|
||||
// Math.max(prev, React.Children.count(children))
|
||||
// );
|
||||
// }
|
||||
// },
|
||||
// { root: wrapperRef.current, threshold: 1 }
|
||||
// );
|
||||
|
||||
const childrenList = wrapperRef.current.children;
|
||||
Array.from(childrenList).forEach((child) => {
|
||||
observer.current?.observe(child);
|
||||
// const childrenList = wrapperRef.current.children;
|
||||
// if (childrenList.length > 0) {
|
||||
// observer.current.observe(childrenList[childrenList.length - 1]);
|
||||
// }
|
||||
|
||||
resizeObserver.current = new ResizeObserver(() => {
|
||||
// updateHiddenIndex();
|
||||
});
|
||||
resizeObserver.current.observe(wrapperRef.current);
|
||||
|
||||
return () => {
|
||||
observer.current?.disconnect();
|
||||
resizeObserver.current?.disconnect();
|
||||
};
|
||||
}, [children]);
|
||||
|
||||
return (
|
||||
<div className="tags-wrapper" ref={wrapperRef}>
|
||||
<span>{hiddenIndex}</span>
|
||||
{React.Children.toArray(children).slice(
|
||||
0,
|
||||
React.Children.toArray(children).length - hiddenIndex
|
||||
)}
|
||||
{hiddenIndex > 0 && (
|
||||
{React.Children.toArray(children).slice(0, hiddenIndex)}
|
||||
{React.Children.toArray(children).length > 4 && (
|
||||
<Dropdown
|
||||
trigger={['hover']}
|
||||
menu={{
|
||||
items: dropItems
|
||||
}}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { request } from '@umijs/max';
|
||||
import qs from 'query-string';
|
||||
import {
|
||||
CatalogItem,
|
||||
CatalogSpec,
|
||||
FormData,
|
||||
GPUListItem,
|
||||
ListItem,
|
||||
@@ -322,15 +323,25 @@ export async function queryCatalogList(
|
||||
params: Global.SearchParams,
|
||||
options?: any
|
||||
) {
|
||||
return request<Global.PageResponse<CatalogItem>>(`/model-sets`, {
|
||||
methos: 'GET',
|
||||
...options,
|
||||
params
|
||||
});
|
||||
return request<Global.PageResponse<CatalogItem>>(
|
||||
`/model-sets?${qs.stringify(params)}`,
|
||||
{
|
||||
methos: 'GET',
|
||||
...options
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export async function queryCatalogItemSpec(id: number) {
|
||||
return request(`/model-sets/${id}/specs`, {
|
||||
method: 'GET'
|
||||
});
|
||||
export async function queryCatalogItemSpec(
|
||||
params: { id: number },
|
||||
options?: any
|
||||
) {
|
||||
return await request<Global.PageResponse<CatalogSpec>>(
|
||||
`/model-sets/${params.id}/specs`,
|
||||
{
|
||||
method: 'GET',
|
||||
...options,
|
||||
params
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,19 +3,29 @@ import { PageAction } from '@/config';
|
||||
import breakpoints from '@/config/breakpoints';
|
||||
import { SyncOutlined } from '@ant-design/icons';
|
||||
import { PageContainer } from '@ant-design/pro-components';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { Button, Col, Input, Pagination, Row, Space, message } from 'antd';
|
||||
import { useIntl, useNavigate } from '@umijs/max';
|
||||
import {
|
||||
Button,
|
||||
Col,
|
||||
Input,
|
||||
Pagination,
|
||||
Row,
|
||||
Select,
|
||||
Space,
|
||||
message
|
||||
} from 'antd';
|
||||
import _ from 'lodash';
|
||||
import ResizeObserver from 'rc-resize-observer';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { createModel, queryCatalogList } from './apis';
|
||||
import CatalogItem from './components/catalog-item';
|
||||
import DelopyBuiltInModal from './components/deploy-builtin-modal';
|
||||
import { getSourceRepoConfigValue, modelSourceMap } from './config';
|
||||
import { modelCategories, modelSourceMap } from './config';
|
||||
import { CatalogItem as CatalogItemType, FormData } from './config/types';
|
||||
|
||||
const Catalog: React.FC = () => {
|
||||
const intl = useIntl();
|
||||
const navigate = useNavigate();
|
||||
const [span, setSpan] = React.useState(8);
|
||||
const [activeId, setActiveId] = React.useState(-1);
|
||||
const [dataSource, setDataSource] = useState<{
|
||||
@@ -30,11 +40,13 @@ const Catalog: React.FC = () => {
|
||||
const [queryParams, setQueryParams] = useState({
|
||||
page: 1,
|
||||
perPage: 9,
|
||||
search: ''
|
||||
search: '',
|
||||
categories: []
|
||||
});
|
||||
const [openDeployModal, setOpenDeployModal] = useState<any>({
|
||||
show: false,
|
||||
width: 600,
|
||||
current: {},
|
||||
source: modelSourceMap.huggingface_value
|
||||
});
|
||||
|
||||
@@ -47,7 +59,7 @@ const Catalog: React.FC = () => {
|
||||
const params = {
|
||||
..._.pickBy(queryParams, (val: any) => !!val)
|
||||
};
|
||||
const res = await queryCatalogList(params);
|
||||
const res: any = await queryCatalogList(params);
|
||||
|
||||
setDataSource({
|
||||
dataList: res.items,
|
||||
@@ -94,6 +106,7 @@ const Catalog: React.FC = () => {
|
||||
setOpenDeployModal({
|
||||
show: true,
|
||||
source: modelSourceMap.huggingface_value,
|
||||
current: item,
|
||||
width: 600
|
||||
});
|
||||
}, []);
|
||||
@@ -103,12 +116,9 @@ const Catalog: React.FC = () => {
|
||||
try {
|
||||
console.log('data:', data, openDeployModal);
|
||||
|
||||
const result = getSourceRepoConfigValue(openDeployModal.source, data);
|
||||
|
||||
const modelData = await createModel({
|
||||
data: {
|
||||
...result.values,
|
||||
..._.omit(data, result.omits)
|
||||
..._.omit(data, ['size', 'quantization'])
|
||||
}
|
||||
});
|
||||
setOpenDeployModal({
|
||||
@@ -116,18 +126,22 @@ const Catalog: React.FC = () => {
|
||||
show: false
|
||||
});
|
||||
message.success(intl.formatMessage({ id: 'common.message.success' }));
|
||||
navigate('/models/list');
|
||||
} catch (error) {}
|
||||
},
|
||||
[openDeployModal]
|
||||
);
|
||||
|
||||
const handleOnPageChange = useCallback((page: number, pageSize?: number) => {
|
||||
setQueryParams({
|
||||
...queryParams,
|
||||
page,
|
||||
perPage: pageSize || 10
|
||||
});
|
||||
}, []);
|
||||
const handleOnPageChange = useCallback(
|
||||
(page: number, pageSize?: number) => {
|
||||
setQueryParams({
|
||||
...queryParams,
|
||||
page,
|
||||
perPage: pageSize || 10
|
||||
});
|
||||
},
|
||||
[queryParams]
|
||||
);
|
||||
|
||||
const handleSearch = (e: any) => {
|
||||
fetchData();
|
||||
@@ -136,10 +150,19 @@ const Catalog: React.FC = () => {
|
||||
const handleNameChange = (e: any) => {
|
||||
setQueryParams({
|
||||
...queryParams,
|
||||
page: 1,
|
||||
search: e.target.value
|
||||
});
|
||||
};
|
||||
|
||||
const handleCategoryChange = (value: any) => {
|
||||
setQueryParams({
|
||||
...queryParams,
|
||||
page: 1,
|
||||
categories: value
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
}, [queryParams]);
|
||||
@@ -159,11 +182,21 @@ const Catalog: React.FC = () => {
|
||||
<Space>
|
||||
<Input
|
||||
placeholder={intl.formatMessage({ id: 'common.filter.name' })}
|
||||
style={{ width: 300 }}
|
||||
style={{ width: 200 }}
|
||||
size="large"
|
||||
allowClear
|
||||
onChange={handleNameChange}
|
||||
></Input>
|
||||
<Select
|
||||
allowClear
|
||||
placeholder="Filter by category"
|
||||
style={{ width: 240 }}
|
||||
size="large"
|
||||
mode="multiple"
|
||||
maxTagCount={1}
|
||||
onChange={handleCategoryChange}
|
||||
options={modelCategories.filter((item) => item.value)}
|
||||
></Select>
|
||||
<Button
|
||||
type="text"
|
||||
style={{ color: 'var(--ant-color-text-tertiary)' }}
|
||||
@@ -190,6 +223,7 @@ const Catalog: React.FC = () => {
|
||||
</ResizeObserver>
|
||||
<div style={{ marginBlock: '32px 16px' }}>
|
||||
<Pagination
|
||||
pageSizeOptions={['9', '12', '36', '100']}
|
||||
hideOnSinglePage={queryParams.perPage === 9}
|
||||
align="end"
|
||||
defaultCurrent={1}
|
||||
@@ -205,6 +239,7 @@ const Catalog: React.FC = () => {
|
||||
title={intl.formatMessage({ id: 'models.button.deploy' })}
|
||||
source={openDeployModal.source}
|
||||
width={openDeployModal.width}
|
||||
current={openDeployModal.current}
|
||||
onCancel={handleDeployModalCancel}
|
||||
onOk={handleCreateModel}
|
||||
></DelopyBuiltInModal>
|
||||
|
||||
@@ -140,16 +140,6 @@ const AdvanceConfig: React.FC<AdvanceConfigProps> = (props) => {
|
||||
form.setFieldValue('backend_parameters', list);
|
||||
}, []);
|
||||
|
||||
const handleBackendChange = useCallback((val: string) => {
|
||||
if (val === backendOptionsMap.llamaBox) {
|
||||
form.setFieldsValue({
|
||||
distributed_inference_across_workers: true,
|
||||
cpu_offloading: true
|
||||
});
|
||||
}
|
||||
form.setFieldValue('backend_version', '');
|
||||
}, []);
|
||||
|
||||
const collapseItems = useMemo(() => {
|
||||
const children = (
|
||||
<>
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import fallbackImg from '@/assets/images/img.png';
|
||||
import IMG from '@/assets/images/small-logo-200x200.png';
|
||||
import AutoTooltip from '@/components/auto-tooltip';
|
||||
import IconFont from '@/components/icon-font';
|
||||
import { ClockCircleOutlined } from '@ant-design/icons';
|
||||
import { Divider, Tag, Typography } from 'antd';
|
||||
import TagWrapper from '@/components/tags-wrapper';
|
||||
import { Tag, Typography } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import _ from 'lodash';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { modelCategories } from '../config';
|
||||
import { CatalogItem as CatalogItemType } from '../config/types';
|
||||
import '../style/catalog-item.less';
|
||||
|
||||
@@ -26,13 +28,13 @@ const CatalogItem: React.FC<CatalogItemProps> = (props) => {
|
||||
const home = data.home?.replace(/\/$/, '');
|
||||
const icon = data.icon?.replace(/^\//, '');
|
||||
if (icon) {
|
||||
return `${home}/${icon}`;
|
||||
return data.icon;
|
||||
}
|
||||
return IMG;
|
||||
}, [data]);
|
||||
|
||||
const handleOnError = (e: any) => {
|
||||
e.target.src = IMG;
|
||||
e.target.src = fallbackImg;
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -43,35 +45,56 @@ const CatalogItem: React.FC<CatalogItemProps> = (props) => {
|
||||
<div className="content">
|
||||
<div className="title">
|
||||
<div className="img">
|
||||
<img src={icon} alt="" onError={handleOnError} />
|
||||
<img
|
||||
src={data.icon || fallbackImg}
|
||||
alt=""
|
||||
onError={handleOnError}
|
||||
/>
|
||||
</div>
|
||||
<AutoTooltip ghost style={{ flex: 1 }}>
|
||||
{data.name}
|
||||
</AutoTooltip>
|
||||
</div>
|
||||
<Typography.Paragraph className="desc" ellipsis={{ rows: 2 }}>
|
||||
<Typography.Paragraph
|
||||
className="desc"
|
||||
ellipsis={{
|
||||
rows: 2,
|
||||
tooltip: (
|
||||
<div
|
||||
className="custome-scrollbar"
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-start',
|
||||
maxHeight: 300,
|
||||
maxWidth: 300,
|
||||
overflow: 'auto'
|
||||
}}
|
||||
>
|
||||
{data.description}
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
>
|
||||
{data.description}
|
||||
</Typography.Paragraph>
|
||||
</div>
|
||||
<div className="item-footer">
|
||||
<div className="update-time">
|
||||
<span className="flex-center">
|
||||
<ClockCircleOutlined
|
||||
<IconFont
|
||||
type="icon-new_release_outlined"
|
||||
className="m-r-5"
|
||||
style={{ color: 'var(--ant-color-text-secondary)' }}
|
||||
/>
|
||||
></IconFont>
|
||||
{data.release_date}
|
||||
</span>
|
||||
<span className="flex-center">
|
||||
<IconFont type="icon-justice" className="m-r-5"></IconFont>
|
||||
{_.map(data.licenses, (license: string, index: number) => {
|
||||
return (
|
||||
<>
|
||||
<span key={license}>{license}</span>
|
||||
{index !== data.licenses.length - 1 && (
|
||||
<Divider type="vertical" />
|
||||
)}
|
||||
</>
|
||||
<span key={license} className="flex-center m-r-16">
|
||||
<IconFont type="icon-justice" className="m-r-5"></IconFont>
|
||||
<span>{license}</span>
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</span>
|
||||
@@ -79,21 +102,37 @@ const CatalogItem: React.FC<CatalogItemProps> = (props) => {
|
||||
<div className="tags">
|
||||
{data.categories.map((sItem, i) => {
|
||||
return (
|
||||
<Tag key={sItem} className="tag-item" color={COLORS[i]}>
|
||||
{sItem}
|
||||
<Tag key={sItem} className="tag-item" color="blue">
|
||||
{_.find(modelCategories, { value: sItem })?.label || sItem}
|
||||
</Tag>
|
||||
);
|
||||
})}
|
||||
{data.capabilities?.length > 0 &&
|
||||
data.capabilities.map((sItem, i) => {
|
||||
return (
|
||||
<Tag key={sItem} className="tag-item" color="purple">
|
||||
{_.map(_.split(sItem, '/'), (s: string) => {
|
||||
return _.split(s, '_').join(' ');
|
||||
})
|
||||
.reverse()
|
||||
.join(' ')}
|
||||
</Tag>
|
||||
);
|
||||
})}
|
||||
{data.sizes?.length > 0 && (
|
||||
<>
|
||||
<span className="dot"></span>
|
||||
{data.sizes.map((sItem, i) => {
|
||||
return (
|
||||
<Tag key={sItem} className="tag-item">
|
||||
{sItem}B
|
||||
</Tag>
|
||||
);
|
||||
})}
|
||||
<div className="box">
|
||||
<TagWrapper>
|
||||
{data.sizes.map((sItem, i) => {
|
||||
return (
|
||||
<Tag key={sItem} className="tag-item">
|
||||
{sItem}B
|
||||
</Tag>
|
||||
);
|
||||
})}
|
||||
</TagWrapper>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -23,7 +23,8 @@ import {
|
||||
backendOptionsMap,
|
||||
modelSourceMap,
|
||||
modelTaskMap,
|
||||
ollamaModelOptions
|
||||
ollamaModelOptions,
|
||||
sourceOptions
|
||||
} from '../config';
|
||||
import { HuggingFaceModels, ModelScopeModels } from '../config/audio-catalog';
|
||||
import { FormData, GPUListItem } from '../config/types';
|
||||
@@ -40,6 +41,10 @@ interface DataFormProps {
|
||||
sourceDisable?: boolean;
|
||||
byBuiltIn?: boolean;
|
||||
backendOptions?: Global.BaseOption<string>[];
|
||||
sourceList?: Global.BaseOption<string>[];
|
||||
onSizeChange?: (val: number) => void;
|
||||
onQuantizationChange?: (val: string) => void;
|
||||
onSourceChange?: (value: string) => void;
|
||||
onOk: (values: FormData) => void;
|
||||
onBackendChange?: (value: string) => void;
|
||||
}
|
||||
@@ -49,37 +54,17 @@ const SEARCH_SOURCE = [
|
||||
modelSourceMap.modelscope_value
|
||||
];
|
||||
|
||||
const sourceOptions = [
|
||||
{
|
||||
label: 'Hugging Face',
|
||||
value: modelSourceMap.huggingface_value,
|
||||
key: 'huggingface'
|
||||
},
|
||||
{
|
||||
label: 'Ollama Library',
|
||||
value: modelSourceMap.ollama_library_value,
|
||||
key: 'ollama_library'
|
||||
},
|
||||
{
|
||||
label: 'ModelScope',
|
||||
value: modelSourceMap.modelscope_value,
|
||||
key: 'model_scope'
|
||||
},
|
||||
{
|
||||
label: 'models.form.localPath',
|
||||
locale: true,
|
||||
value: modelSourceMap.local_path_value,
|
||||
key: 'local_path'
|
||||
}
|
||||
];
|
||||
|
||||
const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
|
||||
const {
|
||||
action,
|
||||
isGGUF,
|
||||
sourceDisable = true,
|
||||
backendOptions,
|
||||
sourceList,
|
||||
byBuiltIn,
|
||||
sizeOptions = [],
|
||||
quantizationOptions = [],
|
||||
onSourceChange,
|
||||
onOk
|
||||
} = props;
|
||||
const [form] = Form.useForm();
|
||||
@@ -349,71 +334,83 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
|
||||
};
|
||||
|
||||
const handleSizeChange = (val: any) => {
|
||||
form.setFieldsValue({
|
||||
quantization: ''
|
||||
});
|
||||
props.onSizeChange?.(val);
|
||||
};
|
||||
|
||||
const handleOnQuantizationChange = (val: any) => {
|
||||
props.onQuantizationChange?.(val);
|
||||
};
|
||||
|
||||
// from catalog
|
||||
const renderFieldsFromCatalog = useMemo(() => {
|
||||
if (!byBuiltIn) {
|
||||
if (!byBuiltIn && !sizeOptions?.length && !quantizationOptions?.length) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<Form.Item<FormData>
|
||||
name="size"
|
||||
key="size"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: intl.formatMessage(
|
||||
{
|
||||
id: 'common.form.rule.select'
|
||||
},
|
||||
{ name: 'size' }
|
||||
)
|
||||
}
|
||||
]}
|
||||
>
|
||||
<SealAutoComplete
|
||||
filterOption
|
||||
onChange={handleSizeChange}
|
||||
defaultActiveFirstOption
|
||||
disabled={false}
|
||||
options={props.sizeOptions}
|
||||
label="Size"
|
||||
required
|
||||
></SealAutoComplete>
|
||||
</Form.Item>
|
||||
<Form.Item<FormData>
|
||||
name="quantization"
|
||||
key="quantization"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: intl.formatMessage(
|
||||
{
|
||||
id: 'common.form.rule.select'
|
||||
},
|
||||
{ name: 'quantization' }
|
||||
)
|
||||
}
|
||||
]}
|
||||
>
|
||||
<SealAutoComplete
|
||||
filterOption
|
||||
defaultActiveFirstOption
|
||||
disabled={false}
|
||||
options={props.quantizationOptions}
|
||||
label="Quantization"
|
||||
required
|
||||
></SealAutoComplete>
|
||||
</Form.Item>
|
||||
{sizeOptions?.length > 0 && (
|
||||
<Form.Item<FormData>
|
||||
name="size"
|
||||
key="size"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: intl.formatMessage(
|
||||
{
|
||||
id: 'common.form.rule.select'
|
||||
},
|
||||
{ name: 'size' }
|
||||
)
|
||||
}
|
||||
]}
|
||||
>
|
||||
<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: intl.formatMessage(
|
||||
{
|
||||
id: 'common.form.rule.select'
|
||||
},
|
||||
{ name: 'quantization' }
|
||||
)
|
||||
}
|
||||
]}
|
||||
>
|
||||
<SealSelect
|
||||
filterOption
|
||||
defaultActiveFirstOption
|
||||
disabled={false}
|
||||
options={quantizationOptions}
|
||||
onChange={handleOnQuantizationChange}
|
||||
label="Quantization"
|
||||
required
|
||||
></SealSelect>
|
||||
</Form.Item>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}, [props.sizeOptions, props.quantizationOptions, byBuiltIn]);
|
||||
}, [sizeOptions, quantizationOptions, byBuiltIn]);
|
||||
|
||||
const renderFieldsBySource = useMemo(() => {
|
||||
// from catalog
|
||||
if (byBuiltIn) {
|
||||
return null;
|
||||
}
|
||||
if (SEARCH_SOURCE.includes(props.source)) {
|
||||
return renderHuggingfaceFields();
|
||||
}
|
||||
@@ -427,7 +424,7 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
|
||||
}
|
||||
|
||||
return null;
|
||||
}, [props.source, isGGUF, intl]);
|
||||
}, [props.source, isGGUF, byBuiltIn, intl]);
|
||||
|
||||
const handleBackendChange = useCallback((val: string) => {
|
||||
if (val === backendOptionsMap.llamaBox) {
|
||||
@@ -437,6 +434,7 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
|
||||
});
|
||||
}
|
||||
form.setFieldValue('backend_version', '');
|
||||
props.onBackendChange?.(val);
|
||||
}, []);
|
||||
|
||||
const handleOk = (formdata: FormData) => {
|
||||
@@ -451,6 +449,10 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
|
||||
});
|
||||
};
|
||||
|
||||
const handleOnSourceChange = (val: string) => {
|
||||
onSourceChange?.(val);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (action === PageAction.EDIT) return;
|
||||
if (modelTask.type === modelTaskMap.audio) {
|
||||
@@ -523,11 +525,12 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
|
||||
>
|
||||
{
|
||||
<SealSelect
|
||||
onChange={handleOnSourceChange}
|
||||
disabled={sourceDisable}
|
||||
label={intl.formatMessage({
|
||||
id: 'models.form.source'
|
||||
})}
|
||||
options={sourceOptions}
|
||||
options={sourceList ?? sourceOptions}
|
||||
required
|
||||
></SealSelect>
|
||||
}
|
||||
@@ -535,33 +538,9 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
|
||||
}
|
||||
|
||||
{renderFieldsBySource}
|
||||
<Form.Item<FormData>
|
||||
name="replicas"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: intl.formatMessage(
|
||||
{
|
||||
id: 'common.form.rule.input'
|
||||
},
|
||||
{
|
||||
name: intl.formatMessage({ id: 'models.form.replicas' })
|
||||
}
|
||||
)
|
||||
}
|
||||
]}
|
||||
>
|
||||
<SealInput.Number
|
||||
style={{ width: '100%' }}
|
||||
label={intl.formatMessage({
|
||||
id: 'models.form.replicas'
|
||||
})}
|
||||
required
|
||||
min={0}
|
||||
></SealInput.Number>
|
||||
</Form.Item>
|
||||
<Form.Item name="backend">
|
||||
<Form.Item name="backend" rules={[{ required: true }]}>
|
||||
<SealSelect
|
||||
required
|
||||
onChange={handleBackendChange}
|
||||
label={intl.formatMessage({ id: 'models.form.backend' })}
|
||||
description={
|
||||
@@ -609,6 +588,31 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
|
||||
></SealSelect>
|
||||
</Form.Item>
|
||||
{renderFieldsFromCatalog}
|
||||
<Form.Item<FormData>
|
||||
name="replicas"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: intl.formatMessage(
|
||||
{
|
||||
id: 'common.form.rule.input'
|
||||
},
|
||||
{
|
||||
name: intl.formatMessage({ id: 'models.form.replicas' })
|
||||
}
|
||||
)
|
||||
}
|
||||
]}
|
||||
>
|
||||
<SealInput.Number
|
||||
style={{ width: '100%' }}
|
||||
label={intl.formatMessage({
|
||||
id: 'models.form.replicas'
|
||||
})}
|
||||
required
|
||||
min={0}
|
||||
></SealInput.Number>
|
||||
</Form.Item>
|
||||
<Form.Item<FormData> name="description">
|
||||
<SealInput.TextArea
|
||||
label={intl.formatMessage({
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import ModalFooter from '@/components/modal-footer';
|
||||
import { PageActionType } from '@/config/types';
|
||||
import { createAxiosToken } from '@/hooks/use-chunk-request';
|
||||
import { CloseOutlined } from '@ant-design/icons';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { Button, Drawer } from 'antd';
|
||||
import { debounce } from 'lodash';
|
||||
import _ from 'lodash';
|
||||
import { memo, useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { backendOptionsMap, modelSourceMap } from '../config';
|
||||
import { FormData, ListItem } from '../config/types';
|
||||
import { queryCatalogItemSpec } from '../apis';
|
||||
import { backendOptionsMap, modelSourceMap, sourceOptions } from '../config';
|
||||
import { CatalogSpec, FormData, ListItem } from '../config/types';
|
||||
import ColumnWrapper from './column-wrapper';
|
||||
import DataForm from './data-form';
|
||||
|
||||
@@ -17,27 +19,27 @@ type AddModalProps = {
|
||||
data?: ListItem;
|
||||
source: string;
|
||||
width?: string | number;
|
||||
current?: any;
|
||||
onOk: (values: FormData) => void;
|
||||
onCancel: () => void;
|
||||
};
|
||||
|
||||
const steps = [
|
||||
const backendOptions = [
|
||||
{
|
||||
element: '#filterGGUF',
|
||||
popover: {
|
||||
title: '筛选模型',
|
||||
description: 'Select a model from the list'
|
||||
}
|
||||
label: `llama-box`,
|
||||
value: backendOptionsMap.llamaBox
|
||||
},
|
||||
{
|
||||
element: '#backend-field',
|
||||
popover: {
|
||||
title: '选择推理后端',
|
||||
description: 'Select a model from the list'
|
||||
}
|
||||
label: 'vLLM',
|
||||
value: backendOptionsMap.vllm
|
||||
},
|
||||
{
|
||||
label: 'vox-box',
|
||||
value: backendOptionsMap.voxBox
|
||||
}
|
||||
];
|
||||
|
||||
const defaultQuant = ['Q4_K_M'];
|
||||
const AddModal: React.FC<AddModalProps> = (props) => {
|
||||
const {
|
||||
title,
|
||||
@@ -46,41 +48,166 @@ const AddModal: React.FC<AddModalProps> = (props) => {
|
||||
onCancel,
|
||||
source,
|
||||
action,
|
||||
current,
|
||||
width = 600
|
||||
} = props || {};
|
||||
const SEARCH_SOURCE = [];
|
||||
|
||||
const form = useRef<any>({});
|
||||
const intl = useIntl();
|
||||
const [selectedModel, setSelectedModel] = useState<any>({});
|
||||
const [collapsed, setCollapsed] = useState<boolean>(false);
|
||||
const [loadingModel, setLoadingModel] = useState<boolean>(false);
|
||||
|
||||
const [isGGUF, setIsGGUF] = useState<boolean>(false);
|
||||
const modelFileRef = useRef<any>(null);
|
||||
const [loadfinish, setLoadfinish] = useState<boolean>(false);
|
||||
|
||||
const handleSelectModelFile = useCallback((item: any) => {
|
||||
form.current?.setFieldValue?.('file_name', item.fakeName);
|
||||
setLoadfinish(true);
|
||||
}, []);
|
||||
|
||||
const handleOnSelectModel = (item: any) => {
|
||||
setSelectedModel(item);
|
||||
};
|
||||
const [sourceList, setSourceList] = useState<any[]>([]);
|
||||
const [backendList, setBackendList] = useState<any[]>([]);
|
||||
const [sizeOptions, setSizeOptions] = useState<any[]>([]);
|
||||
const [quantizationOptions, setQuantizationOptions] = useState<any[]>([]);
|
||||
const sourceGroupMap = useRef<any>({});
|
||||
const axiosToken = useRef<any>(null);
|
||||
const selectSpecRef = useRef<CatalogSpec>({} as CatalogSpec);
|
||||
|
||||
const handleSumit = () => {
|
||||
form.current?.submit?.();
|
||||
};
|
||||
|
||||
const debounceFetchModelFiles = debounce(() => {
|
||||
modelFileRef.current?.fetchModelFiles?.();
|
||||
}, 300);
|
||||
|
||||
const handleSetIsGGUF = (flag: boolean) => {
|
||||
setIsGGUF(flag);
|
||||
if (flag) {
|
||||
debounceFetchModelFiles();
|
||||
const getModelFile = (spec: CatalogSpec) => {
|
||||
let modelInfo = {};
|
||||
if (spec.source === modelSourceMap.huggingface_value) {
|
||||
modelInfo = {
|
||||
huggingface_repo_id: spec?.huggingface_repo_id,
|
||||
huggingface_filename: spec?.huggingface_filename
|
||||
};
|
||||
}
|
||||
|
||||
if (spec.source === modelSourceMap.modelscope_value) {
|
||||
modelInfo = {
|
||||
model_scope_model_id: spec?.model_scope_model_id,
|
||||
model_scope_file_path: spec?.model_scope_file_path
|
||||
};
|
||||
}
|
||||
|
||||
if (spec.source === modelSourceMap.ollama_library_value) {
|
||||
modelInfo = {
|
||||
ollama_library_model_name: spec?.ollama_library_model_name
|
||||
};
|
||||
}
|
||||
|
||||
if (spec.source === modelSourceMap.local_path_value) {
|
||||
modelInfo = {
|
||||
local_path: spec?.local_path
|
||||
};
|
||||
}
|
||||
return modelInfo;
|
||||
};
|
||||
|
||||
const getModelSpec = (data: {
|
||||
source: string;
|
||||
backend: string;
|
||||
size: number;
|
||||
quantization: string;
|
||||
}) => {
|
||||
const groupList = sourceGroupMap.current[data.source];
|
||||
const spec = _.find(groupList, (item: CatalogSpec) => {
|
||||
if (data.size && data.quantization) {
|
||||
return (
|
||||
item.size === data.size &&
|
||||
item.backend === data.backend &&
|
||||
item.quantization === data.quantization
|
||||
);
|
||||
}
|
||||
if (data.size) {
|
||||
return item.size === data.size && item.backend === data.backend;
|
||||
}
|
||||
if (data.quantization) {
|
||||
return (
|
||||
item.quantization === data.quantization &&
|
||||
item.backend === data.backend
|
||||
);
|
||||
}
|
||||
return item.backend === data.backend;
|
||||
});
|
||||
selectSpecRef.current = spec;
|
||||
return {
|
||||
..._.omit(spec, ['name']),
|
||||
categories: _.get(spec, 'categories.0', null)
|
||||
};
|
||||
};
|
||||
|
||||
const initFormDataBySource = (data: CatalogSpec) => {
|
||||
selectSpecRef.current = data;
|
||||
form.current?.setFieldsValue({
|
||||
..._.omit(data, ['name']),
|
||||
categories: _.get(data, 'categories.0', null)
|
||||
});
|
||||
};
|
||||
|
||||
const handleSetSizeOptions = (data: { source: string; backend: string }) => {
|
||||
const groupList = sourceGroupMap.current[source];
|
||||
const list = _.filter(groupList, (item: CatalogSpec) => item.size);
|
||||
const sizeGroup = _.groupBy(
|
||||
_.filter(list, (item: CatalogSpec) => {
|
||||
return item.backend === data.backend;
|
||||
}),
|
||||
'size'
|
||||
);
|
||||
|
||||
const sizeList = _.keys(sizeGroup).map((size: string) => {
|
||||
return {
|
||||
label: `${size}B`,
|
||||
value: _.toNumber(size)
|
||||
};
|
||||
});
|
||||
setSizeOptions(sizeList);
|
||||
return sizeList;
|
||||
};
|
||||
|
||||
const handleSetQuantizationOptions = (data: {
|
||||
source: string;
|
||||
size: number;
|
||||
backend: string;
|
||||
}) => {
|
||||
const groupList = sourceGroupMap.current[data.source];
|
||||
console.log('groupList====', data, groupList);
|
||||
const sizeGroup = _.filter(groupList, (item: CatalogSpec) => {
|
||||
return item.size === data.size && item.backend === data.backend;
|
||||
});
|
||||
|
||||
const quantizationList = _.map(sizeGroup, (item: CatalogSpec) => {
|
||||
return {
|
||||
label: item.quantization,
|
||||
value: item.quantization
|
||||
};
|
||||
});
|
||||
setQuantizationOptions(quantizationList);
|
||||
return quantizationList;
|
||||
};
|
||||
|
||||
const handleSetBackendOptions = (source: string) => {
|
||||
const groupList = sourceGroupMap.current[source];
|
||||
const backendGroup = _.groupBy(groupList, 'backend');
|
||||
console.log('backendGroup====', backendGroup, source);
|
||||
|
||||
const backendList = _.filter(backendOptions, (item: any) => {
|
||||
return backendGroup[item.value];
|
||||
});
|
||||
setBackendList(backendList);
|
||||
return backendList;
|
||||
};
|
||||
|
||||
const handleSourceChange = (source: string) => {
|
||||
const defaultSpec = _.get(sourceGroupMap.current, `${source}.0`, {});
|
||||
console.log('source====', source, defaultSpec);
|
||||
initFormDataBySource(defaultSpec);
|
||||
handleSetSizeOptions({
|
||||
source: source,
|
||||
backend: defaultSpec.backend
|
||||
});
|
||||
handleSetQuantizationOptions({
|
||||
source: source,
|
||||
size: defaultSpec.size,
|
||||
backend: defaultSpec.backend
|
||||
});
|
||||
// set form value
|
||||
initFormDataBySource(defaultSpec);
|
||||
};
|
||||
|
||||
const handleBackendChange = (backend: string) => {
|
||||
@@ -91,29 +218,138 @@ const AddModal: React.FC<AddModalProps> = (props) => {
|
||||
if (backend === backendOptionsMap.llamaBox) {
|
||||
setIsGGUF(true);
|
||||
}
|
||||
const sizeList = handleSetSizeOptions({
|
||||
source: form.current.getFieldValue('source'),
|
||||
backend: backend
|
||||
});
|
||||
const quantizaList = handleSetQuantizationOptions({
|
||||
source: form.current.getFieldValue('source'),
|
||||
size: _.get(sizeList, '0.value', 0),
|
||||
backend: backend
|
||||
});
|
||||
|
||||
const data = getModelSpec({
|
||||
source: form.current.getFieldValue('source'),
|
||||
backend: backend,
|
||||
size: _.get(sizeList, '0.value', 0),
|
||||
quantization:
|
||||
_.find(quantizaList, (item: { label: string; value: string }) =>
|
||||
defaultQuant.includes(item.value)
|
||||
)?.value || _.get(quantizaList, '0.value', '')
|
||||
});
|
||||
|
||||
form.current.setFieldsValue({
|
||||
...data
|
||||
});
|
||||
};
|
||||
|
||||
const fetchSpecData = async () => {
|
||||
try {
|
||||
axiosToken.current?.cancel?.();
|
||||
axiosToken.current = createAxiosToken();
|
||||
const res: any = await queryCatalogItemSpec(
|
||||
{
|
||||
id: current.id
|
||||
},
|
||||
{
|
||||
token: axiosToken.current.token
|
||||
}
|
||||
);
|
||||
const groupList = _.groupBy(res.items, 'source');
|
||||
sourceGroupMap.current = groupList;
|
||||
|
||||
const sources = _.filter(sourceOptions, (item: any) => {
|
||||
return groupList[item.value];
|
||||
});
|
||||
const source = _.get(sources, '0.value', '');
|
||||
const defaultSpec =
|
||||
_.find(groupList[source], (item: CatalogSpec) => {
|
||||
return defaultQuant.includes(item.quantization);
|
||||
}) || _.get(groupList, `${source}.0`, {});
|
||||
|
||||
setSourceList(sources);
|
||||
handleSetBackendOptions(source);
|
||||
handleSetSizeOptions({
|
||||
source: source,
|
||||
backend: defaultSpec.backend
|
||||
});
|
||||
handleSetQuantizationOptions({
|
||||
source: source,
|
||||
size: defaultSpec.size,
|
||||
backend: defaultSpec.backend
|
||||
});
|
||||
initFormDataBySource(defaultSpec);
|
||||
form.current.setFieldValue(
|
||||
'name',
|
||||
_.toLower(current.name).replace(/\s/g, '-') || ''
|
||||
);
|
||||
if (defaultSpec.backend === backendOptionsMap.vllm) {
|
||||
setIsGGUF(false);
|
||||
}
|
||||
|
||||
if (defaultSpec.backend === backendOptionsMap.llamaBox) {
|
||||
setIsGGUF(true);
|
||||
}
|
||||
} catch (error) {
|
||||
// ignore
|
||||
}
|
||||
};
|
||||
|
||||
const handleOnQuantizationChange = (val: string) => {
|
||||
const data = getModelSpec({
|
||||
source: form.current.getFieldValue('source'),
|
||||
backend: form.current.getFieldValue('backend'),
|
||||
size: form.current.getFieldValue('size'),
|
||||
quantization: val
|
||||
});
|
||||
form.current.setFieldsValue({
|
||||
...data
|
||||
});
|
||||
};
|
||||
|
||||
const handleOnSizeChange = (val: number) => {
|
||||
const list = handleSetQuantizationOptions({
|
||||
source: form.current.getFieldValue('source'),
|
||||
backend: form.current.getFieldValue('backend'),
|
||||
size: val
|
||||
});
|
||||
|
||||
const data = getModelSpec({
|
||||
source: form.current.getFieldValue('source'),
|
||||
backend: form.current.getFieldValue('backend'),
|
||||
size: val,
|
||||
quantization:
|
||||
_.find(list, (item: { label: string; value: string }) =>
|
||||
defaultQuant.includes(item.value)
|
||||
)?.value || _.get(list, '0.value', '')
|
||||
});
|
||||
|
||||
// set form data
|
||||
form.current.setFieldsValue({
|
||||
...data
|
||||
});
|
||||
};
|
||||
|
||||
const handleOk = (values: FormData) => {
|
||||
onOk({
|
||||
...values,
|
||||
...getModelFile(selectSpecRef.current)
|
||||
});
|
||||
};
|
||||
|
||||
const handleCancel = useCallback(() => {
|
||||
onCancel?.();
|
||||
axiosToken.current?.cancel?.();
|
||||
}, [onCancel]);
|
||||
|
||||
useEffect(() => {
|
||||
handleSelectModelFile({ fakeName: '' });
|
||||
}, [selectedModel]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) {
|
||||
setIsGGUF(false);
|
||||
form.current?.setFieldValue?.('backend', backendOptionsMap.vllm);
|
||||
} else if (source === modelSourceMap.ollama_library_value) {
|
||||
form.current?.setFieldValue?.('backend', backendOptionsMap.llamaBox);
|
||||
setIsGGUF(true);
|
||||
if (open) {
|
||||
fetchSpecData();
|
||||
}
|
||||
|
||||
return () => {
|
||||
setSelectedModel({});
|
||||
axiosToken.current?.cancel?.();
|
||||
};
|
||||
}, [open, source]);
|
||||
}, [open, current]);
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
@@ -170,11 +406,20 @@ const AddModal: React.FC<AddModalProps> = (props) => {
|
||||
<DataForm
|
||||
source={source}
|
||||
action={action}
|
||||
selectedModel={selectedModel}
|
||||
onOk={onOk}
|
||||
selectedModel={{}}
|
||||
onOk={handleOk}
|
||||
ref={form}
|
||||
isGGUF={isGGUF}
|
||||
byBuiltIn={true}
|
||||
sourceDisable={false}
|
||||
backendOptions={backendList}
|
||||
sourceList={sourceList}
|
||||
quantizationOptions={quantizationOptions}
|
||||
sizeOptions={sizeOptions}
|
||||
onBackendChange={handleBackendChange}
|
||||
onSourceChange={handleSourceChange}
|
||||
onQuantizationChange={handleOnQuantizationChange}
|
||||
onSizeChange={handleOnSizeChange}
|
||||
></DataForm>
|
||||
</>
|
||||
</ColumnWrapper>
|
||||
|
||||
@@ -25,23 +25,6 @@ type AddModalProps = {
|
||||
onCancel: () => void;
|
||||
};
|
||||
|
||||
const steps = [
|
||||
{
|
||||
element: '#filterGGUF',
|
||||
popover: {
|
||||
title: '筛选模型',
|
||||
description: 'Select a model from the list'
|
||||
}
|
||||
},
|
||||
{
|
||||
element: '#backend-field',
|
||||
popover: {
|
||||
title: '选择推理后端',
|
||||
description: 'Select a model from the list'
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
const AddModal: React.FC<AddModalProps> = (props) => {
|
||||
const {
|
||||
title,
|
||||
|
||||
@@ -221,6 +221,12 @@ const Models: React.FC<ModelsProps> = ({
|
||||
}, [deleteIds]);
|
||||
|
||||
const sourceOptions = [
|
||||
{
|
||||
label: intl.formatMessage({ id: 'menu.models.modelCatalog' }),
|
||||
value: 'catalog',
|
||||
key: 'catalog',
|
||||
icon: <IconFont type="icon-catalog"></IconFont>
|
||||
},
|
||||
{
|
||||
label: 'Hugging Face',
|
||||
value: modelSourceMap.huggingface_value,
|
||||
@@ -705,6 +711,9 @@ const Models: React.FC<ModelsProps> = ({
|
||||
source: modelSourceMap.local_path_value
|
||||
});
|
||||
}
|
||||
if (item.key === 'catalog') {
|
||||
navigate('/models/catalog');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -453,33 +453,9 @@ const UpdateModal: React.FC<AddModalProps> = (props) => {
|
||||
)}
|
||||
</Form.Item>
|
||||
{renderFieldsBySource}
|
||||
<Form.Item<FormData>
|
||||
name="replicas"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: intl.formatMessage(
|
||||
{
|
||||
id: 'common.form.rule.input'
|
||||
},
|
||||
{
|
||||
name: intl.formatMessage({ id: 'models.form.replicas' })
|
||||
}
|
||||
)
|
||||
}
|
||||
]}
|
||||
>
|
||||
<SealInput.Number
|
||||
style={{ width: '100%' }}
|
||||
label={intl.formatMessage({
|
||||
id: 'models.form.replicas'
|
||||
})}
|
||||
required
|
||||
min={0}
|
||||
></SealInput.Number>
|
||||
</Form.Item>
|
||||
<Form.Item name="backend">
|
||||
<Form.Item name="backend" rules={[{ required: true }]}>
|
||||
<SealSelect
|
||||
required
|
||||
onChange={handleBackendChange}
|
||||
label={intl.formatMessage({ id: 'models.form.backend' })}
|
||||
options={[
|
||||
@@ -512,6 +488,31 @@ const UpdateModal: React.FC<AddModalProps> = (props) => {
|
||||
}
|
||||
></SealSelect>
|
||||
</Form.Item>
|
||||
<Form.Item<FormData>
|
||||
name="replicas"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: intl.formatMessage(
|
||||
{
|
||||
id: 'common.form.rule.input'
|
||||
},
|
||||
{
|
||||
name: intl.formatMessage({ id: 'models.form.replicas' })
|
||||
}
|
||||
)
|
||||
}
|
||||
]}
|
||||
>
|
||||
<SealInput.Number
|
||||
style={{ width: '100%' }}
|
||||
label={intl.formatMessage({
|
||||
id: 'models.form.replicas'
|
||||
})}
|
||||
required
|
||||
min={0}
|
||||
></SealInput.Number>
|
||||
</Form.Item>
|
||||
<Form.Item<FormData> name="description">
|
||||
<SealInput.TextArea
|
||||
label={intl.formatMessage({
|
||||
|
||||
@@ -159,6 +159,30 @@ export const modelSourceValueMap = {
|
||||
[modelSourceMap.local_path_value]: modelSourceMap.local_path
|
||||
};
|
||||
|
||||
export const sourceOptions = [
|
||||
{
|
||||
label: 'Hugging Face',
|
||||
value: modelSourceMap.huggingface_value,
|
||||
key: 'huggingface'
|
||||
},
|
||||
{
|
||||
label: 'Ollama Library',
|
||||
value: modelSourceMap.ollama_library_value,
|
||||
key: 'ollama_library'
|
||||
},
|
||||
{
|
||||
label: 'ModelScope',
|
||||
value: modelSourceMap.modelscope_value,
|
||||
key: 'model_scope'
|
||||
},
|
||||
{
|
||||
label: 'models.form.localPath',
|
||||
locale: true,
|
||||
value: modelSourceMap.local_path_value,
|
||||
key: 'local_path'
|
||||
}
|
||||
];
|
||||
|
||||
export const InstanceStatusMap = {
|
||||
Initializing: 'initializing',
|
||||
Starting: 'starting',
|
||||
@@ -255,11 +279,11 @@ export const modelCategoriesMap = {
|
||||
export const modelCategories = [
|
||||
{ label: 'common.options.auto', value: null, locale: true },
|
||||
{ label: 'LLM', value: modelCategoriesMap.llm },
|
||||
{ label: 'Image', value: 'image' },
|
||||
{ label: 'Text-to-speech', value: 'text_to_speech' },
|
||||
{ label: 'Speech-to-text', value: 'speech_to_text' },
|
||||
{ label: 'Embedding', value: 'embedding' },
|
||||
{ label: 'Reranker', value: 'reranker' }
|
||||
{ label: 'Image', value: modelCategoriesMap.image },
|
||||
{ label: 'Text-to-Speech', value: modelCategoriesMap.text_to_speech },
|
||||
{ label: 'Speech-to-Text', value: modelCategoriesMap.speech_to_text },
|
||||
{ label: 'Embedding', value: modelCategoriesMap.embedding },
|
||||
{ label: 'Reranker', value: modelCategoriesMap.reranker }
|
||||
];
|
||||
|
||||
export const sourceRepoConfig = {
|
||||
|
||||
@@ -133,3 +133,31 @@ export interface CatalogItem {
|
||||
licenses: string[];
|
||||
release_date: string;
|
||||
}
|
||||
|
||||
export interface CatalogSpec {
|
||||
source: string;
|
||||
huggingface_repo_id: string;
|
||||
huggingface_filename: string;
|
||||
ollama_library_model_name: string;
|
||||
model_scope_model_id: string;
|
||||
model_scope_file_path: string;
|
||||
local_path: string;
|
||||
name: string;
|
||||
description: string;
|
||||
meta: Record<string, any>;
|
||||
replicas: number;
|
||||
ready_replicas: number;
|
||||
categories: any[];
|
||||
placement_strategy: string;
|
||||
cpu_offloading: boolean;
|
||||
distributed_inference_across_workers: boolean;
|
||||
worker_selector: Record<string, any>;
|
||||
gpu_selector: {
|
||||
gpu_ids: string[];
|
||||
};
|
||||
backend: string;
|
||||
backend_version: string;
|
||||
backend_parameters: any[];
|
||||
quantization: string;
|
||||
size: number;
|
||||
}
|
||||
|
||||
@@ -85,13 +85,19 @@
|
||||
border-radius: 50%;
|
||||
background-color: var(--ant-color-text-quaternary);
|
||||
margin-right: 8px;
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.box {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tags {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tag-item {
|
||||
|
||||
Reference in New Issue
Block a user