chore: catalog api
This commit is contained in:
+10
-11
@@ -59,22 +59,21 @@ export default [
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'modelCatalog',
|
||||||
|
path: '/models/catalog',
|
||||||
|
key: 'modelsCatalog',
|
||||||
|
icon: 'icon-catalog',
|
||||||
|
access: 'canSeeAdmin',
|
||||||
|
component: './llmodels/catalog'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'models',
|
name: 'models',
|
||||||
path: '/models',
|
path: '/models/list',
|
||||||
key: 'models',
|
key: 'models',
|
||||||
icon: 'Block',
|
icon: 'Block',
|
||||||
access: 'canSeeAdmin',
|
access: 'canSeeAdmin',
|
||||||
component: './llmodels'
|
component: './llmodels/index'
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'modelsCatalog',
|
|
||||||
path: '/models/catalog',
|
|
||||||
key: 'modelsCatalog',
|
|
||||||
icon: 'Block',
|
|
||||||
access: 'canSeeAdmin',
|
|
||||||
hideInMenu: true,
|
|
||||||
component: './llmodels/catalog'
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'resources',
|
name: 'resources',
|
||||||
|
|||||||
@@ -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_bslq45omhet.js'
|
scriptUrl: '//at.alicdn.com/t/c/font_4613488_trmdczqx9z.js'
|
||||||
});
|
});
|
||||||
|
|
||||||
export default IconFont;
|
export default IconFont;
|
||||||
|
|||||||
@@ -8,8 +8,10 @@ export default {
|
|||||||
'menu.playground.text2images': 'Image',
|
'menu.playground.text2images': 'Image',
|
||||||
'menu.compare': 'Compare',
|
'menu.compare': 'Compare',
|
||||||
'menu.models': 'Models',
|
'menu.models': 'Models',
|
||||||
|
'menu.models.modelList': 'Deploy & Manage',
|
||||||
|
'menu.models.modelCatalog': 'Catalog',
|
||||||
'menu.models.catalog': 'Model Catalog',
|
'menu.models.catalog': 'Model Catalog',
|
||||||
'menu.modelsCatalog': 'Model Catalog',
|
'menu.modelCatalog': 'Catalog',
|
||||||
'menu.resources': 'Resources',
|
'menu.resources': 'Resources',
|
||||||
'menu.apikeys': 'API Keys',
|
'menu.apikeys': 'API Keys',
|
||||||
'menu.users': 'Users',
|
'menu.users': 'Users',
|
||||||
|
|||||||
@@ -8,8 +8,10 @@ export default {
|
|||||||
'menu.playground.text2images': '文生图',
|
'menu.playground.text2images': '文生图',
|
||||||
'menu.compare': '多模型对比',
|
'menu.compare': '多模型对比',
|
||||||
'menu.models': '模型',
|
'menu.models': '模型',
|
||||||
|
'menu.models.modelList': '部署与管理',
|
||||||
|
'menu.models.modelCatalog': '模型库',
|
||||||
|
'menu.modelCatalog': '模型库',
|
||||||
'menu.models.catalog': '模型库',
|
'menu.models.catalog': '模型库',
|
||||||
'menu.modelsCatalog': '模型库',
|
|
||||||
'menu.resources': '资源',
|
'menu.resources': '资源',
|
||||||
'menu.apikeys': 'API 密钥',
|
'menu.apikeys': 'API 密钥',
|
||||||
'menu.users': '用户',
|
'menu.users': '用户',
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { PipelineType } from '@huggingface/tasks';
|
|||||||
import { request } from '@umijs/max';
|
import { request } from '@umijs/max';
|
||||||
import qs from 'query-string';
|
import qs from 'query-string';
|
||||||
import {
|
import {
|
||||||
|
CatalogItem,
|
||||||
FormData,
|
FormData,
|
||||||
GPUListItem,
|
GPUListItem,
|
||||||
ListItem,
|
ListItem,
|
||||||
@@ -314,3 +315,22 @@ export async function downloadModelScopeModelfile(
|
|||||||
}
|
}
|
||||||
return res.json();
|
return res.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ===================== catalog =====================
|
||||||
|
|
||||||
|
export async function queryCatalogList(
|
||||||
|
params: Global.SearchParams,
|
||||||
|
options?: any
|
||||||
|
) {
|
||||||
|
return request<Global.PageResponse<CatalogItem>>(`/model-sets`, {
|
||||||
|
methos: 'GET',
|
||||||
|
...options,
|
||||||
|
params
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function queryCatalogItemSpec(id: number) {
|
||||||
|
return request(`/model-sets/${id}/specs`, {
|
||||||
|
method: 'GET'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,48 +8,68 @@ import { Button, Col, Input, Pagination, Row, 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, queryHuggingfaceModels } 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 { getSourceRepoConfigValue, modelSourceMap } from './config';
|
||||||
import testData from './config/test';
|
import { CatalogItem as CatalogItemType, FormData } from './config/types';
|
||||||
import { FormData } from './config/types';
|
|
||||||
|
|
||||||
const Catalog: React.FC = () => {
|
const Catalog: React.FC = () => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
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<{
|
||||||
|
dataList: CatalogItemType[];
|
||||||
|
loading: boolean;
|
||||||
|
total: number;
|
||||||
|
}>({
|
||||||
|
dataList: [],
|
||||||
|
loading: false,
|
||||||
|
total: 0
|
||||||
|
});
|
||||||
|
const [queryParams, setQueryParams] = useState({
|
||||||
|
page: 1,
|
||||||
|
perPage: 9,
|
||||||
|
search: ''
|
||||||
|
});
|
||||||
const [openDeployModal, setOpenDeployModal] = useState<any>({
|
const [openDeployModal, setOpenDeployModal] = useState<any>({
|
||||||
show: false,
|
show: false,
|
||||||
width: 600,
|
width: 600,
|
||||||
source: modelSourceMap.huggingface_value
|
source: modelSourceMap.huggingface_value
|
||||||
});
|
});
|
||||||
|
|
||||||
const [dataList, setDataList] = useState<any[]>([]);
|
const fetchData = useCallback(async () => {
|
||||||
|
setDataSource((pre) => {
|
||||||
const getModelsFromHuggingface = useCallback(async (sort: string) => {
|
pre.loading = true;
|
||||||
console.log('getModelsFromHuggingface:', sort);
|
return { ...pre };
|
||||||
|
});
|
||||||
try {
|
try {
|
||||||
const params = {
|
const params = {
|
||||||
search: {
|
..._.pickBy(queryParams, (val: any) => !!val)
|
||||||
query: 'gpustack',
|
|
||||||
tags: ['gguf']
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
const data = await queryHuggingfaceModels(params);
|
const res = await queryCatalogList(params);
|
||||||
let list = _.map(data || [], (item: any) => {
|
|
||||||
return {
|
setDataSource({
|
||||||
...item,
|
dataList: res.items,
|
||||||
value: item.name,
|
loading: false,
|
||||||
label: item.name
|
total: res.pagination.total
|
||||||
};
|
|
||||||
});
|
});
|
||||||
setDataList(list);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('error:', error);
|
setDataSource({
|
||||||
return [];
|
dataList: [],
|
||||||
|
loading: false,
|
||||||
|
total: dataSource.total
|
||||||
|
});
|
||||||
|
console.log('error', error);
|
||||||
}
|
}
|
||||||
}, []);
|
}, [queryParams]);
|
||||||
|
|
||||||
|
const handleDeployModalCancel = () => {
|
||||||
|
setOpenDeployModal({
|
||||||
|
...openDeployModal,
|
||||||
|
show: false
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const handleResize = useCallback(
|
const handleResize = useCallback(
|
||||||
_.throttle((size: { width: number; height: number }) => {
|
_.throttle((size: { width: number; height: number }) => {
|
||||||
@@ -65,29 +85,18 @@ const Catalog: React.FC = () => {
|
|||||||
} else {
|
} else {
|
||||||
setSpan(8);
|
setSpan(8);
|
||||||
}
|
}
|
||||||
console.log('size:', size);
|
|
||||||
}, 100),
|
}, 100),
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleDeployModalCancel = () => {
|
const handleOnDeploy = useCallback((item: CatalogItemType) => {
|
||||||
|
setActiveId(item.id);
|
||||||
setOpenDeployModal({
|
setOpenDeployModal({
|
||||||
...openDeployModal,
|
show: true,
|
||||||
show: false
|
source: modelSourceMap.huggingface_value,
|
||||||
|
width: 600
|
||||||
});
|
});
|
||||||
};
|
}, []);
|
||||||
|
|
||||||
const handleOnDeploy = useCallback(
|
|
||||||
(index: number) => {
|
|
||||||
setActiveId(index);
|
|
||||||
setOpenDeployModal({
|
|
||||||
show: true,
|
|
||||||
source: modelSourceMap.huggingface_value,
|
|
||||||
width: 600
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[openDeployModal]
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleCreateModel = useCallback(
|
const handleCreateModel = useCallback(
|
||||||
async (data: FormData) => {
|
async (data: FormData) => {
|
||||||
@@ -112,14 +121,34 @@ const Catalog: React.FC = () => {
|
|||||||
[openDeployModal]
|
[openDeployModal]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleOnPageChange = useCallback((page: number, pageSize?: number) => {
|
||||||
|
setQueryParams({
|
||||||
|
...queryParams,
|
||||||
|
page,
|
||||||
|
perPage: pageSize || 10
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleSearch = (e: any) => {
|
||||||
|
fetchData();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleNameChange = (e: any) => {
|
||||||
|
setQueryParams({
|
||||||
|
...queryParams,
|
||||||
|
search: e.target.value
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getModelsFromHuggingface('desc');
|
fetchData();
|
||||||
}, [getModelsFromHuggingface]);
|
}, [queryParams]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageContainer
|
<PageContainer
|
||||||
ghost
|
ghost
|
||||||
header={{
|
header={{
|
||||||
title: intl.formatMessage({ id: 'menu.models.catalog' }),
|
title: intl.formatMessage({ id: 'menu.modelCatalog' }),
|
||||||
breadcrumb: {}
|
breadcrumb: {}
|
||||||
}}
|
}}
|
||||||
extra={[]}
|
extra={[]}
|
||||||
@@ -133,25 +162,26 @@ const Catalog: React.FC = () => {
|
|||||||
style={{ width: 300 }}
|
style={{ width: 300 }}
|
||||||
size="large"
|
size="large"
|
||||||
allowClear
|
allowClear
|
||||||
|
onChange={handleNameChange}
|
||||||
></Input>
|
></Input>
|
||||||
<Button
|
<Button
|
||||||
type="text"
|
type="text"
|
||||||
style={{ color: 'var(--ant-color-text-tertiary)' }}
|
style={{ color: 'var(--ant-color-text-tertiary)' }}
|
||||||
icon={<SyncOutlined></SyncOutlined>}
|
icon={<SyncOutlined></SyncOutlined>}
|
||||||
|
onClick={handleSearch}
|
||||||
></Button>
|
></Button>
|
||||||
</Space>
|
</Space>
|
||||||
}
|
}
|
||||||
></PageTools>
|
></PageTools>
|
||||||
<ResizeObserver onResize={handleResize}>
|
<ResizeObserver onResize={handleResize}>
|
||||||
<Row gutter={[16, 16]}>
|
<Row gutter={[16, 16]}>
|
||||||
{testData.map((item, index) => {
|
{dataSource.dataList.map((item: CatalogItemType, index) => {
|
||||||
return (
|
return (
|
||||||
<Col span={span} key={index}>
|
<Col span={span} key={item.id}>
|
||||||
<CatalogItem
|
<CatalogItem
|
||||||
onDeploy={() => handleOnDeploy(index)}
|
onClick={handleOnDeploy}
|
||||||
activeId={activeId}
|
activeId={activeId}
|
||||||
data={item}
|
data={item}
|
||||||
itemId={index}
|
|
||||||
></CatalogItem>
|
></CatalogItem>
|
||||||
</Col>
|
</Col>
|
||||||
);
|
);
|
||||||
@@ -159,7 +189,15 @@ const Catalog: React.FC = () => {
|
|||||||
</Row>
|
</Row>
|
||||||
</ResizeObserver>
|
</ResizeObserver>
|
||||||
<div style={{ marginBlock: '32px 16px' }}>
|
<div style={{ marginBlock: '32px 16px' }}>
|
||||||
<Pagination align="end" defaultCurrent={1} total={50} showSizeChanger />
|
<Pagination
|
||||||
|
hideOnSinglePage={queryParams.perPage === 9}
|
||||||
|
align="end"
|
||||||
|
defaultCurrent={1}
|
||||||
|
total={dataSource.total}
|
||||||
|
pageSize={queryParams.perPage}
|
||||||
|
showSizeChanger
|
||||||
|
onChange={handleOnPageChange}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<DelopyBuiltInModal
|
<DelopyBuiltInModal
|
||||||
open={openDeployModal.show}
|
open={openDeployModal.show}
|
||||||
|
|||||||
@@ -1,31 +1,49 @@
|
|||||||
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 { Tag, Typography } from 'antd';
|
import IconFont from '@/components/icon-font';
|
||||||
|
import { ClockCircleOutlined } from '@ant-design/icons';
|
||||||
|
import { Divider, Tag, Typography } from 'antd';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import React from 'react';
|
import _ from 'lodash';
|
||||||
|
import React, { useCallback, useMemo } from 'react';
|
||||||
|
import { CatalogItem as CatalogItemType } from '../config/types';
|
||||||
import '../style/catalog-item.less';
|
import '../style/catalog-item.less';
|
||||||
|
|
||||||
const COLORS = ['blue', 'purple', 'orange'];
|
const COLORS = ['blue', 'purple', 'orange'];
|
||||||
interface CatalogItemProps {
|
interface CatalogItemProps {
|
||||||
activeId: number;
|
activeId: number;
|
||||||
itemId: number;
|
data: CatalogItemType;
|
||||||
data: any;
|
onClick: (data: CatalogItemType) => void;
|
||||||
onDeploy: () => void;
|
|
||||||
}
|
}
|
||||||
const CatalogItem: React.FC<CatalogItemProps> = (props) => {
|
const CatalogItem: React.FC<CatalogItemProps> = (props) => {
|
||||||
const { onDeploy, itemId, activeId, data } = props;
|
const { onClick, activeId, data } = props;
|
||||||
|
|
||||||
const handleOnDeploy = () => {
|
const handleOnClick = useCallback(() => {
|
||||||
onDeploy();
|
onClick(data);
|
||||||
|
}, [data, onClick]);
|
||||||
|
|
||||||
|
const icon = useMemo(() => {
|
||||||
|
const home = data.home?.replace(/\/$/, '');
|
||||||
|
const icon = data.icon?.replace(/^\//, '');
|
||||||
|
if (icon) {
|
||||||
|
return `${home}/${icon}`;
|
||||||
|
}
|
||||||
|
return IMG;
|
||||||
|
}, [data]);
|
||||||
|
|
||||||
|
const handleOnError = (e: any) => {
|
||||||
|
e.target.src = IMG;
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={classNames('catalog-item', { active: activeId === itemId })}
|
onClick={handleOnClick}
|
||||||
|
className={classNames('catalog-item', { active: activeId === data.id })}
|
||||||
>
|
>
|
||||||
<div className="content">
|
<div className="content">
|
||||||
<div className="title">
|
<div className="title">
|
||||||
<div className="img">
|
<div className="img">
|
||||||
<img src={IMG} alt="" />
|
<img src={icon} alt="" onError={handleOnError} />
|
||||||
</div>
|
</div>
|
||||||
<AutoTooltip ghost style={{ flex: 1 }}>
|
<AutoTooltip ghost style={{ flex: 1 }}>
|
||||||
{data.name}
|
{data.name}
|
||||||
@@ -36,34 +54,43 @@ const CatalogItem: React.FC<CatalogItemProps> = (props) => {
|
|||||||
</Typography.Paragraph>
|
</Typography.Paragraph>
|
||||||
</div>
|
</div>
|
||||||
<div className="item-footer">
|
<div className="item-footer">
|
||||||
|
<div className="update-time">
|
||||||
|
<span className="flex-center">
|
||||||
|
<ClockCircleOutlined
|
||||||
|
className="m-r-5"
|
||||||
|
style={{ color: 'var(--ant-color-text-secondary)' }}
|
||||||
|
/>
|
||||||
|
{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>
|
||||||
|
</div>
|
||||||
<div className="tags">
|
<div className="tags">
|
||||||
{data.tags.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={COLORS[i]}>
|
||||||
{sItem}
|
{sItem}
|
||||||
</Tag>
|
</Tag>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
{/* <Tag color="blue" className="tag-item">
|
{data.sizes?.length > 0 && (
|
||||||
Audio
|
|
||||||
</Tag>
|
|
||||||
<Tag color="purple" className="tag-item">
|
|
||||||
GGUF
|
|
||||||
</Tag>
|
|
||||||
<Tag color="orange" className="tag-item">
|
|
||||||
Qwen
|
|
||||||
</Tag> */}
|
|
||||||
{/* <span className="dot"></span>
|
|
||||||
<Tag className="tag-item">Audio</Tag>
|
|
||||||
<Tag className="tag-item">GGUF</Tag>
|
|
||||||
<Tag className="tag-item">Qwen</Tag> */}
|
|
||||||
{data.size?.length > 0 && (
|
|
||||||
<>
|
<>
|
||||||
<span className="dot"></span>
|
<span className="dot"></span>
|
||||||
{data.size.map((sItem, i) => {
|
{data.sizes.map((sItem, i) => {
|
||||||
return (
|
return (
|
||||||
<Tag key={sItem} className="tag-item">
|
<Tag key={sItem} className="tag-item">
|
||||||
{sItem}
|
{sItem}B
|
||||||
</Tag>
|
</Tag>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -35,6 +35,11 @@ interface DataFormProps {
|
|||||||
action: PageActionType;
|
action: PageActionType;
|
||||||
selectedModel: any;
|
selectedModel: any;
|
||||||
isGGUF: boolean;
|
isGGUF: boolean;
|
||||||
|
sizeOptions?: Global.BaseOption<number>[];
|
||||||
|
quantizationOptions?: Global.BaseOption<string>[];
|
||||||
|
sourceDisable?: boolean;
|
||||||
|
byBuiltIn?: boolean;
|
||||||
|
backendOptions?: Global.BaseOption<string>[];
|
||||||
onOk: (values: FormData) => void;
|
onOk: (values: FormData) => void;
|
||||||
onBackendChange?: (value: string) => void;
|
onBackendChange?: (value: string) => void;
|
||||||
}
|
}
|
||||||
@@ -69,7 +74,14 @@ const sourceOptions = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
|
const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
|
||||||
const { action, isGGUF, onOk } = props;
|
const {
|
||||||
|
action,
|
||||||
|
isGGUF,
|
||||||
|
sourceDisable = true,
|
||||||
|
backendOptions,
|
||||||
|
byBuiltIn,
|
||||||
|
onOk
|
||||||
|
} = props;
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const [gpuOptions, setGpuOptions] = useState<
|
const [gpuOptions, setGpuOptions] = useState<
|
||||||
@@ -259,34 +271,6 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderS3Fields = () => {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Form.Item<FormData>
|
|
||||||
name="s3_address"
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
message: intl.formatMessage(
|
|
||||||
{
|
|
||||||
id: 'common.form.rule.input'
|
|
||||||
},
|
|
||||||
{ name: intl.formatMessage({ id: 'models.form.s3address' }) }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<SealInput.Input
|
|
||||||
label={intl.formatMessage({
|
|
||||||
id: 'models.form.s3address'
|
|
||||||
})}
|
|
||||||
required
|
|
||||||
></SealInput.Input>
|
|
||||||
</Form.Item>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderOllamaModelFields = () => {
|
const renderOllamaModelFields = () => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -364,6 +348,71 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSizeChange = (val: any) => {
|
||||||
|
form.setFieldsValue({
|
||||||
|
quantization: ''
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderFieldsFromCatalog = useMemo(() => {
|
||||||
|
if (!byBuiltIn) {
|
||||||
|
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>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}, [props.sizeOptions, props.quantizationOptions, byBuiltIn]);
|
||||||
|
|
||||||
const renderFieldsBySource = useMemo(() => {
|
const renderFieldsBySource = useMemo(() => {
|
||||||
if (SEARCH_SOURCE.includes(props.source)) {
|
if (SEARCH_SOURCE.includes(props.source)) {
|
||||||
return renderHuggingfaceFields();
|
return renderHuggingfaceFields();
|
||||||
@@ -373,9 +422,6 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
|
|||||||
return renderOllamaModelFields();
|
return renderOllamaModelFields();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (props.source === modelSourceMap.s3_value) {
|
|
||||||
return renderS3Fields();
|
|
||||||
}
|
|
||||||
if (props.source === modelSourceMap.local_path_value) {
|
if (props.source === modelSourceMap.local_path_value) {
|
||||||
return renderLocalPathFields();
|
return renderLocalPathFields();
|
||||||
}
|
}
|
||||||
@@ -477,7 +523,7 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
|
|||||||
>
|
>
|
||||||
{
|
{
|
||||||
<SealSelect
|
<SealSelect
|
||||||
disabled={true}
|
disabled={sourceDisable}
|
||||||
label={intl.formatMessage({
|
label={intl.formatMessage({
|
||||||
id: 'models.form.source'
|
id: 'models.form.source'
|
||||||
})}
|
})}
|
||||||
@@ -531,35 +577,38 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
options={[
|
options={
|
||||||
{
|
backendOptions ?? [
|
||||||
label: `llama-box`,
|
{
|
||||||
value: backendOptionsMap.llamaBox,
|
label: `llama-box`,
|
||||||
disabled:
|
value: backendOptionsMap.llamaBox,
|
||||||
props.source === modelSourceMap.local_path_value
|
disabled:
|
||||||
? false
|
props.source === modelSourceMap.local_path_value
|
||||||
: !isGGUF
|
? false
|
||||||
},
|
: !isGGUF
|
||||||
{
|
},
|
||||||
label: 'vLLM',
|
{
|
||||||
value: backendOptionsMap.vllm,
|
label: 'vLLM',
|
||||||
disabled:
|
value: backendOptionsMap.vllm,
|
||||||
props.source === modelSourceMap.local_path_value
|
disabled:
|
||||||
? false
|
props.source === modelSourceMap.local_path_value
|
||||||
: isGGUF
|
? false
|
||||||
},
|
: isGGUF
|
||||||
{
|
},
|
||||||
label: 'vox-box',
|
{
|
||||||
value: backendOptionsMap.voxBox,
|
label: 'vox-box',
|
||||||
disabled: props.source === modelSourceMap.ollama_library_value
|
value: backendOptionsMap.voxBox,
|
||||||
}
|
disabled: props.source === modelSourceMap.ollama_library_value
|
||||||
]}
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
disabled={
|
disabled={
|
||||||
action === PageAction.EDIT &&
|
action === PageAction.EDIT &&
|
||||||
props.source !== modelSourceMap.local_path_value
|
props.source !== modelSourceMap.local_path_value
|
||||||
}
|
}
|
||||||
></SealSelect>
|
></SealSelect>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
{renderFieldsFromCatalog}
|
||||||
<Form.Item<FormData> name="description">
|
<Form.Item<FormData> name="description">
|
||||||
<SealInput.TextArea
|
<SealInput.TextArea
|
||||||
label={intl.formatMessage({
|
label={intl.formatMessage({
|
||||||
|
|||||||
@@ -9,11 +9,6 @@ import { backendOptionsMap, modelSourceMap } from '../config';
|
|||||||
import { FormData, ListItem } from '../config/types';
|
import { FormData, ListItem } from '../config/types';
|
||||||
import ColumnWrapper from './column-wrapper';
|
import ColumnWrapper from './column-wrapper';
|
||||||
import DataForm from './data-form';
|
import DataForm from './data-form';
|
||||||
import HFModelFile from './hf-model-file';
|
|
||||||
import ModelCard from './model-card';
|
|
||||||
import SearchModel from './search-model';
|
|
||||||
import Separator from './separator';
|
|
||||||
import TitleWrapper from './title-wrapper';
|
|
||||||
|
|
||||||
type AddModalProps = {
|
type AddModalProps = {
|
||||||
title: string;
|
title: string;
|
||||||
@@ -158,41 +153,6 @@ const AddModal: React.FC<AddModalProps> = (props) => {
|
|||||||
footer={false}
|
footer={false}
|
||||||
>
|
>
|
||||||
<div style={{ display: 'flex', height: '100%' }}>
|
<div style={{ display: 'flex', height: '100%' }}>
|
||||||
{SEARCH_SOURCE.includes(props.source) && (
|
|
||||||
<>
|
|
||||||
<div style={{ display: 'flex', flex: 1 }}>
|
|
||||||
<ColumnWrapper>
|
|
||||||
<SearchModel
|
|
||||||
modelSource={props.source}
|
|
||||||
onSelectModel={handleOnSelectModel}
|
|
||||||
setLoadingModel={setLoadingModel}
|
|
||||||
></SearchModel>
|
|
||||||
</ColumnWrapper>
|
|
||||||
<Separator></Separator>
|
|
||||||
</div>
|
|
||||||
<div style={{ display: 'flex', flex: 1 }}>
|
|
||||||
<ColumnWrapper>
|
|
||||||
<ModelCard
|
|
||||||
selectedModel={selectedModel}
|
|
||||||
onCollapse={setCollapsed}
|
|
||||||
collapsed={collapsed}
|
|
||||||
modelSource={props.source}
|
|
||||||
setIsGGUF={handleSetIsGGUF}
|
|
||||||
></ModelCard>
|
|
||||||
{isGGUF && (
|
|
||||||
<HFModelFile
|
|
||||||
ref={modelFileRef}
|
|
||||||
selectedModel={selectedModel}
|
|
||||||
modelSource={props.source}
|
|
||||||
onSelectFile={handleSelectModelFile}
|
|
||||||
collapsed={collapsed}
|
|
||||||
></HFModelFile>
|
|
||||||
)}
|
|
||||||
</ColumnWrapper>
|
|
||||||
<Separator></Separator>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
<ColumnWrapper
|
<ColumnWrapper
|
||||||
footer={
|
footer={
|
||||||
<ModalFooter
|
<ModalFooter
|
||||||
@@ -207,12 +167,6 @@ const AddModal: React.FC<AddModalProps> = (props) => {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<>
|
<>
|
||||||
{SEARCH_SOURCE.includes(source) && (
|
|
||||||
<TitleWrapper>
|
|
||||||
{intl.formatMessage({ id: 'models.form.configurations' })}
|
|
||||||
<span style={{ display: 'flex', height: 24 }}></span>
|
|
||||||
</TitleWrapper>
|
|
||||||
)}
|
|
||||||
<DataForm
|
<DataForm
|
||||||
source={source}
|
source={source}
|
||||||
action={action}
|
action={action}
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ import {
|
|||||||
DownOutlined,
|
DownOutlined,
|
||||||
EditOutlined,
|
EditOutlined,
|
||||||
ExperimentOutlined,
|
ExperimentOutlined,
|
||||||
GoldOutlined,
|
|
||||||
PictureOutlined,
|
PictureOutlined,
|
||||||
SyncOutlined,
|
SyncOutlined,
|
||||||
WechatWorkOutlined
|
WechatWorkOutlined
|
||||||
@@ -222,12 +221,6 @@ const Models: React.FC<ModelsProps> = ({
|
|||||||
}, [deleteIds]);
|
}, [deleteIds]);
|
||||||
|
|
||||||
const sourceOptions = [
|
const sourceOptions = [
|
||||||
{
|
|
||||||
label: 'Model Catalog',
|
|
||||||
value: 'model_catalog',
|
|
||||||
key: 'model_catalog',
|
|
||||||
icon: <GoldOutlined />
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
label: 'Hugging Face',
|
label: 'Hugging Face',
|
||||||
value: modelSourceMap.huggingface_value,
|
value: modelSourceMap.huggingface_value,
|
||||||
@@ -688,9 +681,6 @@ const Models: React.FC<ModelsProps> = ({
|
|||||||
source: modelSourceMap.huggingface_value
|
source: modelSourceMap.huggingface_value
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (item.key === 'model_catalog') {
|
|
||||||
navigate('/models/catalog');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.key === 'ollama_library') {
|
if (item.key === 'ollama_library') {
|
||||||
setOpenDeployModal({
|
setOpenDeployModal({
|
||||||
@@ -722,7 +712,8 @@ const Models: React.FC<ModelsProps> = ({
|
|||||||
<PageContainer
|
<PageContainer
|
||||||
ghost
|
ghost
|
||||||
header={{
|
header={{
|
||||||
title: intl.formatMessage({ id: 'models.title' })
|
title: intl.formatMessage({ id: 'models.title' }),
|
||||||
|
breadcrumb: {}
|
||||||
}}
|
}}
|
||||||
extra={[]}
|
extra={[]}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,84 +0,0 @@
|
|||||||
export default [
|
|
||||||
{
|
|
||||||
name: 'gpustack/bge-m3-GGUF',
|
|
||||||
tags: ['Embedding', 'GGUF', 'bert'],
|
|
||||||
description:
|
|
||||||
'It can simultaneously perform the three common retrieval functionalities of embedding model: dense retrieval, multi-vector retrieval, and sparse retrieval.',
|
|
||||||
size: ['567M']
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'gpustack/stable-diffusion-v3-5-medium-GGUF',
|
|
||||||
tags: ['Text-to-Image', 'GGUF'],
|
|
||||||
description:
|
|
||||||
'Stable Diffusion 3.5 Medium is a Multimodal Diffusion Transformer with improvements (MMDiT-X) text-to-image model that features improved performance in image quality, typography, complex prompt understanding, and resource-efficiency.',
|
|
||||||
size: ['8B']
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'gpustack/stable-diffusion-v3-5-large-GGUF',
|
|
||||||
tags: ['Text-to-Image', 'GGUF'],
|
|
||||||
description:
|
|
||||||
'Stable Diffusion 3.5 Large is a Multimodal Diffusion Transformer (MMDiT) text-to-image model that features improved performance in image quality, typography, complex prompt understanding, and resource-efficiency',
|
|
||||||
size: ['14B']
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'gpustack/jina-reranker-v1-tiny-en-GGUF',
|
|
||||||
tags: ['Rerank', 'GGUF', 'jina-bert-v2'],
|
|
||||||
description:
|
|
||||||
'his model is designed for blazing-fast reranking while maintaining competitive performance.',
|
|
||||||
size: ['Q8_0', 'Q4_0']
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'gpustack/stable-diffusion-v1-5-inpainting-GGUF',
|
|
||||||
tags: ['Text-to-Image', 'GGUF'],
|
|
||||||
description:
|
|
||||||
'Stable Diffusion Inpainting is a latent text-to-image diffusion model capable of generating photo-realistic images given any text input, with the extra capability of inpainting the pictures by using a mask',
|
|
||||||
size: ['1B', 'Q8_0', 'Q4_0']
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'gpustack/Llama-3.2-3B-Instruct-GGUF',
|
|
||||||
tags: ['Text-to-Text', 'GGUF', 'llama'],
|
|
||||||
description:
|
|
||||||
'The Llama 3.2 collection of multilingual large language models (LLMs) is a collection of pretrained and instruction-tuned generative models in 1B and 3B sizes (text in/text out).',
|
|
||||||
size: ['1B', 'Q8_0', 'Q4_0']
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'gpustack/faster-whisper-large-v1',
|
|
||||||
description:
|
|
||||||
'This model can be used in CTranslate2 or projects based on CTranslate2 such as faster-whisper.',
|
|
||||||
tags: ['Speech-to-Text'],
|
|
||||||
size: []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'gpustack/faster-distil-whisper-medium.en',
|
|
||||||
description:
|
|
||||||
'This model can be used in CTranslate2 or projects based on CTranslate2 such as faster-whisper.',
|
|
||||||
tags: ['Speech-to-Text']
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'gpustack/CosyVoice-300M',
|
|
||||||
description:
|
|
||||||
'This model is a lightweight, fast, and efficient text-to-speech model that can be used in various applications.',
|
|
||||||
tags: ['Text-to-Speech']
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'gpustack/FLUX.1-mini-GGUF',
|
|
||||||
tags: ['Text-to-Image', 'GGUF'],
|
|
||||||
description:
|
|
||||||
'A 3.2B MMDiT distilled from Flux-dev for efficient text-to-image generation',
|
|
||||||
size: ['8B', 'Q8_0', 'Q4_0', 'FP16']
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'gpustack/stable-diffusion-v3-5-large-turbo-GGUF',
|
|
||||||
tags: ['Text-to-Image', 'GGUF'],
|
|
||||||
description:
|
|
||||||
'Stable Diffusion 3.5 Large Turbo is a Multimodal Diffusion Transformer (MMDiT) text-to-image model with Adversarial Diffusion Distillation (ADD)',
|
|
||||||
size: ['14B', 'Q8_0', 'Q4_0']
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'gpustack/FLUX.1-lite-GGUF',
|
|
||||||
description:
|
|
||||||
'This version uses 7 GB less RAM and runs 23% faster while maintaining the same precision (bfloat16) as the original model',
|
|
||||||
tags: ['Text-to-Image', 'GGUF'],
|
|
||||||
size: ['14B', 'Q8_0', 'Q4_0']
|
|
||||||
}
|
|
||||||
];
|
|
||||||
@@ -31,6 +31,8 @@ export interface ListItem {
|
|||||||
|
|
||||||
export interface FormData {
|
export interface FormData {
|
||||||
backend?: string;
|
backend?: string;
|
||||||
|
size?: number;
|
||||||
|
quantization?: number;
|
||||||
categories?: string[];
|
categories?: string[];
|
||||||
backend_parameters?: string[];
|
backend_parameters?: string[];
|
||||||
backend_version?: string;
|
backend_version?: string;
|
||||||
@@ -118,3 +120,16 @@ export interface GPUListItem {
|
|||||||
worker_name: string;
|
worker_name: string;
|
||||||
worker_ip: string;
|
worker_ip: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CatalogItem {
|
||||||
|
name: string;
|
||||||
|
id: number;
|
||||||
|
description: string;
|
||||||
|
home: string;
|
||||||
|
icon: string;
|
||||||
|
categories: string[];
|
||||||
|
capabilities: string[];
|
||||||
|
sizes: number[];
|
||||||
|
licenses: string[];
|
||||||
|
release_date: string;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
.catalog-item {
|
.catalog-item {
|
||||||
height: 160px;
|
height: 180px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: 16px 20px;
|
padding: 16px 20px;
|
||||||
@@ -31,7 +31,6 @@
|
|||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
// height: 100%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
@@ -48,8 +47,8 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
gap: 8px;
|
gap: 12px;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 12px;
|
||||||
|
|
||||||
.name {
|
.name {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
@@ -61,23 +60,30 @@
|
|||||||
color: var(--ant-color-text-secondary);
|
color: var(--ant-color-text-secondary);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-footer {
|
.item-footer {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 8px;
|
gap: 12px;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.update-time {
|
||||||
|
color: var(--ant-color-text-tertiary);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
.dot {
|
.dot {
|
||||||
width: 4px;
|
width: 3px;
|
||||||
height: 4px;
|
height: 3px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background-color: var(--ant-color-text-tertiary);
|
background-color: var(--ant-color-text-quaternary);
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user