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