feat: catalog page
This commit is contained in:
@@ -67,6 +67,15 @@ export default [
|
||||
access: 'canSeeAdmin',
|
||||
component: './llmodels'
|
||||
},
|
||||
{
|
||||
name: 'modelsCatalog',
|
||||
path: '/models/catalog',
|
||||
key: 'modelsCatalog',
|
||||
icon: 'Block',
|
||||
access: 'canSeeAdmin',
|
||||
hideInMenu: true,
|
||||
component: './llmodels/catalog'
|
||||
},
|
||||
{
|
||||
name: 'resources',
|
||||
path: '/resources',
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
.tags-wrapper {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
|
||||
.more {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
height: 22px;
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
import { MoreOutlined } from '@ant-design/icons';
|
||||
import { Dropdown, Tag } from 'antd';
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||
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 uid = useRef(0);
|
||||
|
||||
const updateUid = () => {
|
||||
uid.current += 1;
|
||||
return uid.current;
|
||||
};
|
||||
|
||||
const dropItems = useMemo(() => {
|
||||
return React.Children.toArray(children)
|
||||
.slice(hiddenIndex)
|
||||
.map((child, index) => ({
|
||||
label: child,
|
||||
key: updateUid()
|
||||
}));
|
||||
}, [children, hiddenIndex]);
|
||||
|
||||
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
|
||||
}
|
||||
);
|
||||
|
||||
const childrenList = wrapperRef.current.children;
|
||||
Array.from(childrenList).forEach((child) => {
|
||||
observer.current?.observe(child);
|
||||
});
|
||||
|
||||
return () => {
|
||||
observer.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 && (
|
||||
<Dropdown
|
||||
menu={{
|
||||
items: dropItems
|
||||
}}
|
||||
>
|
||||
<Tag className="more">
|
||||
<MoreOutlined rotate={90} />
|
||||
</Tag>
|
||||
</Dropdown>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(TagsWrapper);
|
||||
@@ -8,6 +8,8 @@ export default {
|
||||
'menu.playground.text2images': 'Image',
|
||||
'menu.compare': 'Compare',
|
||||
'menu.models': 'Models',
|
||||
'menu.models.catalog': 'Model Catalog',
|
||||
'menu.modelsCatalog': 'Model Catalog',
|
||||
'menu.resources': 'Resources',
|
||||
'menu.apikeys': 'API Keys',
|
||||
'menu.users': 'Users',
|
||||
|
||||
@@ -8,6 +8,8 @@ export default {
|
||||
'menu.playground.text2images': '文生图',
|
||||
'menu.compare': '多模型对比',
|
||||
'menu.models': '模型',
|
||||
'menu.models.catalog': '模型库',
|
||||
'menu.modelsCatalog': '模型库',
|
||||
'menu.resources': '资源',
|
||||
'menu.apikeys': 'API 密钥',
|
||||
'menu.users': '用户',
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
import PageTools from '@/components/page-tools';
|
||||
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 _ from 'lodash';
|
||||
import ResizeObserver from 'rc-resize-observer';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { createModel } from './apis';
|
||||
import CatalogItem from './components/catalog-item';
|
||||
import DelopyBuiltInModal from './components/deploy-builtin-modal';
|
||||
import { getSourceRepoConfigValue, modelSourceMap } from './config';
|
||||
import { FormData } from './config/types';
|
||||
|
||||
const Catalog: React.FC = () => {
|
||||
const intl = useIntl();
|
||||
const [span, setSpan] = React.useState(6);
|
||||
const [activeId, setActiveId] = React.useState(-1);
|
||||
const [openDeployModal, setOpenDeployModal] = useState<any>({
|
||||
show: false,
|
||||
width: 600,
|
||||
source: modelSourceMap.huggingface_value
|
||||
});
|
||||
|
||||
const handleResize = useCallback(
|
||||
_.throttle((size: { width: number; height: number }) => {
|
||||
const { width } = size;
|
||||
if (width < breakpoints.xs) {
|
||||
setSpan(24);
|
||||
} else if (width < breakpoints.sm) {
|
||||
setSpan(24);
|
||||
} else if (width < breakpoints.md) {
|
||||
setSpan(12);
|
||||
} else if (width < breakpoints.lg) {
|
||||
setSpan(8);
|
||||
} else {
|
||||
setSpan(6);
|
||||
}
|
||||
console.log('size:', size);
|
||||
}, 100),
|
||||
[]
|
||||
);
|
||||
|
||||
const handleDeployModalCancel = () => {
|
||||
setOpenDeployModal({
|
||||
...openDeployModal,
|
||||
show: false
|
||||
});
|
||||
};
|
||||
|
||||
const handleOnDeploy = useCallback(
|
||||
(index: number) => {
|
||||
setActiveId(index);
|
||||
setOpenDeployModal({
|
||||
show: true,
|
||||
source: modelSourceMap.huggingface_value,
|
||||
width: 600
|
||||
});
|
||||
},
|
||||
[openDeployModal]
|
||||
);
|
||||
|
||||
const handleCreateModel = useCallback(
|
||||
async (data: FormData) => {
|
||||
try {
|
||||
console.log('data:', data, openDeployModal);
|
||||
|
||||
const result = getSourceRepoConfigValue(openDeployModal.source, data);
|
||||
|
||||
const modelData = await createModel({
|
||||
data: {
|
||||
...result.values,
|
||||
..._.omit(data, result.omits)
|
||||
}
|
||||
});
|
||||
setOpenDeployModal({
|
||||
...openDeployModal,
|
||||
show: false
|
||||
});
|
||||
message.success(intl.formatMessage({ id: 'common.message.success' }));
|
||||
} catch (error) {}
|
||||
},
|
||||
[openDeployModal]
|
||||
);
|
||||
|
||||
return (
|
||||
<PageContainer
|
||||
ghost
|
||||
header={{
|
||||
title: intl.formatMessage({ id: 'menu.models.catalog' }),
|
||||
breadcrumb: {}
|
||||
}}
|
||||
extra={[]}
|
||||
>
|
||||
<PageTools
|
||||
marginBottom={22}
|
||||
left={
|
||||
<Space>
|
||||
<Input
|
||||
placeholder={intl.formatMessage({ id: 'common.filter.name' })}
|
||||
style={{ width: 300 }}
|
||||
size="large"
|
||||
allowClear
|
||||
></Input>
|
||||
<Button
|
||||
type="text"
|
||||
style={{ color: 'var(--ant-color-text-tertiary)' }}
|
||||
icon={<SyncOutlined></SyncOutlined>}
|
||||
></Button>
|
||||
</Space>
|
||||
}
|
||||
></PageTools>
|
||||
<ResizeObserver onResize={handleResize}>
|
||||
<Row gutter={[16, 16]}>
|
||||
{Array.from({ length: 12 }).map((_, index) => {
|
||||
return (
|
||||
<Col span={span} key={index}>
|
||||
<CatalogItem
|
||||
onDeploy={() => handleOnDeploy(index)}
|
||||
activeId={activeId}
|
||||
itemId={index}
|
||||
></CatalogItem>
|
||||
</Col>
|
||||
);
|
||||
})}
|
||||
</Row>
|
||||
</ResizeObserver>
|
||||
<div style={{ marginBlock: '32px 16px' }}>
|
||||
<Pagination align="end" defaultCurrent={1} total={50} showSizeChanger />
|
||||
</div>
|
||||
<DelopyBuiltInModal
|
||||
open={openDeployModal.show}
|
||||
action={PageAction.CREATE}
|
||||
title={intl.formatMessage({ id: 'models.button.deploy' })}
|
||||
source={openDeployModal.source}
|
||||
width={openDeployModal.width}
|
||||
onCancel={handleDeployModalCancel}
|
||||
onOk={handleCreateModel}
|
||||
></DelopyBuiltInModal>
|
||||
</PageContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default Catalog;
|
||||
@@ -0,0 +1,62 @@
|
||||
import IMG from '@/assets/images/small-logo-200x200.png';
|
||||
import AutoTooltip from '@/components/auto-tooltip';
|
||||
import { Button, Tag, Typography } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import React from 'react';
|
||||
import '../style/catalog-item.less';
|
||||
|
||||
interface CatalogItemProps {
|
||||
activeId: number;
|
||||
itemId: number;
|
||||
onDeploy: () => void;
|
||||
}
|
||||
const CatalogItem: React.FC<CatalogItemProps> = (props) => {
|
||||
const { onDeploy, itemId, activeId } = props;
|
||||
|
||||
const handleOnDeploy = () => {
|
||||
onDeploy();
|
||||
};
|
||||
return (
|
||||
<div
|
||||
className={classNames('catalog-item', { active: activeId === itemId })}
|
||||
>
|
||||
<div className="content">
|
||||
<div className="title">
|
||||
<div className="img">
|
||||
<img src={IMG} alt="" />
|
||||
</div>
|
||||
<AutoTooltip ghost style={{ flex: 1 }}>
|
||||
gpustack/stable-diffusion-v3-5-medium-GGUF
|
||||
</AutoTooltip>
|
||||
</div>
|
||||
<Typography.Paragraph className="desc" ellipsis={{ rows: 2 }}>
|
||||
this is description this is description this is description this is
|
||||
this is description this isthis is description this isthis is
|
||||
description this is this is description this isthis is description
|
||||
this isthis is description this isthis is description this isthis is
|
||||
description this isthis is description this isthis is description this
|
||||
is this is description this isthis is description this isthis is
|
||||
description this isthis is description this is
|
||||
</Typography.Paragraph>
|
||||
</div>
|
||||
<div className="item-footer">
|
||||
<div className="tags">
|
||||
<Tag color="blue" className="tag-item">
|
||||
Audio
|
||||
</Tag>
|
||||
<Tag color="purple" className="tag-item">
|
||||
GGUF
|
||||
</Tag>
|
||||
<Tag color="orange" className="tag-item">
|
||||
Qwen
|
||||
</Tag>
|
||||
</div>
|
||||
<Button size="small" type="primary" onClick={handleOnDeploy}>
|
||||
Deploy
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(CatalogItem);
|
||||
@@ -0,0 +1,232 @@
|
||||
import ModalFooter from '@/components/modal-footer';
|
||||
import { PageActionType } from '@/config/types';
|
||||
import { CloseOutlined } from '@ant-design/icons';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { Button, Drawer } from 'antd';
|
||||
import { debounce } from 'lodash';
|
||||
import { memo, useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { backendOptionsMap, modelSourceMap } from '../config';
|
||||
import { FormData, ListItem } from '../config/types';
|
||||
import ColumnWrapper from './column-wrapper';
|
||||
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 = {
|
||||
title: string;
|
||||
action: PageActionType;
|
||||
open: boolean;
|
||||
data?: ListItem;
|
||||
source: string;
|
||||
width?: string | number;
|
||||
onOk: (values: FormData) => 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 {
|
||||
title,
|
||||
open,
|
||||
onOk,
|
||||
onCancel,
|
||||
source,
|
||||
action,
|
||||
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 handleSumit = () => {
|
||||
form.current?.submit?.();
|
||||
};
|
||||
|
||||
const debounceFetchModelFiles = debounce(() => {
|
||||
modelFileRef.current?.fetchModelFiles?.();
|
||||
}, 300);
|
||||
|
||||
const handleSetIsGGUF = (flag: boolean) => {
|
||||
setIsGGUF(flag);
|
||||
if (flag) {
|
||||
debounceFetchModelFiles();
|
||||
}
|
||||
};
|
||||
|
||||
const handleBackendChange = (backend: string) => {
|
||||
if (backend === backendOptionsMap.vllm) {
|
||||
setIsGGUF(false);
|
||||
}
|
||||
|
||||
if (backend === backendOptionsMap.llamaBox) {
|
||||
setIsGGUF(true);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCancel = useCallback(() => {
|
||||
onCancel?.();
|
||||
}, [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);
|
||||
}
|
||||
|
||||
return () => {
|
||||
setSelectedModel({});
|
||||
};
|
||||
}, [open, source]);
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
title={
|
||||
<div className="flex-between flex-center">
|
||||
<span
|
||||
style={{
|
||||
color: 'var(--ant-color-text)',
|
||||
fontWeight: 'var(--font-weight-medium)',
|
||||
fontSize: 'var(--font-size-middle)'
|
||||
}}
|
||||
>
|
||||
{title}
|
||||
</span>
|
||||
<Button type="text" size="small" onClick={handleCancel}>
|
||||
<CloseOutlined></CloseOutlined>
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
open={open}
|
||||
onClose={handleCancel}
|
||||
destroyOnClose={true}
|
||||
closeIcon={false}
|
||||
maskClosable={false}
|
||||
keyboard={false}
|
||||
styles={{
|
||||
body: {
|
||||
height: 'calc(100vh - 57px)',
|
||||
padding: '16px 0',
|
||||
overflowX: 'hidden'
|
||||
},
|
||||
content: {
|
||||
borderRadius: '6px 0 0 6px'
|
||||
}
|
||||
}}
|
||||
width={width}
|
||||
footer={false}
|
||||
>
|
||||
<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
|
||||
footer={
|
||||
<ModalFooter
|
||||
onCancel={handleCancel}
|
||||
onOk={handleSumit}
|
||||
style={{
|
||||
padding: '16px 24px',
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-end'
|
||||
}}
|
||||
></ModalFooter>
|
||||
}
|
||||
>
|
||||
<>
|
||||
{SEARCH_SOURCE.includes(source) && (
|
||||
<TitleWrapper>
|
||||
{intl.formatMessage({ id: 'models.form.configurations' })}
|
||||
<span style={{ display: 'flex', height: 24 }}></span>
|
||||
</TitleWrapper>
|
||||
)}
|
||||
<DataForm
|
||||
source={source}
|
||||
action={action}
|
||||
selectedModel={selectedModel}
|
||||
onOk={onOk}
|
||||
ref={form}
|
||||
isGGUF={isGGUF}
|
||||
onBackendChange={handleBackendChange}
|
||||
></DataForm>
|
||||
</>
|
||||
</ColumnWrapper>
|
||||
</div>
|
||||
</Drawer>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(AddModal);
|
||||
@@ -6,7 +6,7 @@ import { Button, Drawer } from 'antd';
|
||||
import { debounce } from 'lodash';
|
||||
import { memo, useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { backendOptionsMap, modelSourceMap } from '../config';
|
||||
import { FormData, ListItem } from '../config/types';
|
||||
import { FormData } from '../config/types';
|
||||
import ColumnWrapper from './column-wrapper';
|
||||
import DataForm from './data-form';
|
||||
import HFModelFile from './hf-model-file';
|
||||
@@ -19,7 +19,6 @@ type AddModalProps = {
|
||||
title: string;
|
||||
action: PageActionType;
|
||||
open: boolean;
|
||||
data?: ListItem;
|
||||
source: string;
|
||||
width?: string | number;
|
||||
onOk: (values: FormData) => void;
|
||||
@@ -57,10 +56,7 @@ const AddModal: React.FC<AddModalProps> = (props) => {
|
||||
modelSourceMap.huggingface_value,
|
||||
modelSourceMap.modelscope_value
|
||||
];
|
||||
// const { start } = useDriver({
|
||||
// steps,
|
||||
// id: 'deploy-model'
|
||||
// });
|
||||
|
||||
const form = useRef<any>({});
|
||||
const intl = useIntl();
|
||||
const [selectedModel, setSelectedModel] = useState<any>({});
|
||||
@@ -126,14 +122,6 @@ const AddModal: React.FC<AddModalProps> = (props) => {
|
||||
};
|
||||
}, [open, source]);
|
||||
|
||||
// useEffect(() => {
|
||||
// if (open && loadfinish) {
|
||||
// setTimeout(() => {
|
||||
// start();
|
||||
// }, 1000);
|
||||
// }
|
||||
// }, [loadfinish, open]);
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
title={
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
DownOutlined,
|
||||
EditOutlined,
|
||||
ExperimentOutlined,
|
||||
GoldOutlined,
|
||||
PictureOutlined,
|
||||
SyncOutlined,
|
||||
WechatWorkOutlined
|
||||
@@ -221,61 +222,35 @@ const Models: React.FC<ModelsProps> = ({
|
||||
}, [deleteIds]);
|
||||
|
||||
const sourceOptions = [
|
||||
{
|
||||
label: 'Model Catalog',
|
||||
value: 'model_catalog',
|
||||
key: 'model_catalog',
|
||||
icon: <GoldOutlined />
|
||||
},
|
||||
{
|
||||
label: 'Hugging Face',
|
||||
value: modelSourceMap.huggingface_value,
|
||||
key: 'huggingface',
|
||||
icon: <IconFont type="icon-huggingface"></IconFont>,
|
||||
onClick: (e: any) => {
|
||||
setOpenDeployModal({
|
||||
show: true,
|
||||
width: 'calc(100vw - 220px)',
|
||||
source: modelSourceMap.huggingface_value
|
||||
});
|
||||
}
|
||||
icon: <IconFont type="icon-huggingface"></IconFont>
|
||||
},
|
||||
{
|
||||
label: 'Ollama Library',
|
||||
value: modelSourceMap.ollama_library_value,
|
||||
key: 'ollama_library',
|
||||
icon: <IconFont type="icon-ollama"></IconFont>,
|
||||
onClick: (e: any) => {
|
||||
setOpenDeployModal(() => {
|
||||
return {
|
||||
show: true,
|
||||
width: 600,
|
||||
source: modelSourceMap.ollama_library_value
|
||||
};
|
||||
});
|
||||
}
|
||||
icon: <IconFont type="icon-ollama"></IconFont>
|
||||
},
|
||||
{
|
||||
label: 'ModelScope',
|
||||
value: modelSourceMap.modelscope_value,
|
||||
key: 'modelscope',
|
||||
icon: <IconFont type="icon-tu2"></IconFont>,
|
||||
onClick: (e: any) => {
|
||||
setOpenDeployModal({
|
||||
show: true,
|
||||
width: 'calc(100vw - 220px)',
|
||||
source: modelSourceMap.modelscope_value
|
||||
});
|
||||
}
|
||||
icon: <IconFont type="icon-tu2"></IconFont>
|
||||
},
|
||||
{
|
||||
label: intl.formatMessage({ id: 'models.form.localPath' }),
|
||||
value: modelSourceMap.local_path_value,
|
||||
key: 'local_path',
|
||||
icon: <IconFont type="icon-hard-disk"></IconFont>,
|
||||
onClick: (e: any) => {
|
||||
setOpenDeployModal(() => {
|
||||
return {
|
||||
show: true,
|
||||
width: 600,
|
||||
source: modelSourceMap.local_path_value
|
||||
};
|
||||
});
|
||||
}
|
||||
icon: <IconFont type="icon-hard-disk"></IconFont>
|
||||
}
|
||||
];
|
||||
|
||||
@@ -673,6 +648,7 @@ const Models: React.FC<ModelsProps> = ({
|
||||
},
|
||||
[intl]
|
||||
);
|
||||
|
||||
const renderChildren = useCallback(
|
||||
(list: any, parent?: any) => {
|
||||
return (
|
||||
@@ -704,6 +680,43 @@ const Models: React.FC<ModelsProps> = ({
|
||||
return '';
|
||||
}, []);
|
||||
|
||||
const handleClickDropdown = (item: any) => {
|
||||
if (item.key === 'huggingface') {
|
||||
setOpenDeployModal({
|
||||
show: true,
|
||||
width: 'calc(100vw - 220px)',
|
||||
source: modelSourceMap.huggingface_value
|
||||
});
|
||||
}
|
||||
if (item.key === 'model_catalog') {
|
||||
navigate('/models/catalog');
|
||||
}
|
||||
|
||||
if (item.key === 'ollama_library') {
|
||||
setOpenDeployModal({
|
||||
show: true,
|
||||
width: 600,
|
||||
source: modelSourceMap.ollama_library_value
|
||||
});
|
||||
}
|
||||
|
||||
if (item.key === 'modelscope') {
|
||||
setOpenDeployModal({
|
||||
show: true,
|
||||
width: 'calc(100vw - 220px)',
|
||||
source: modelSourceMap.modelscope_value
|
||||
});
|
||||
}
|
||||
|
||||
if (item.key === 'local_path') {
|
||||
setOpenDeployModal({
|
||||
show: true,
|
||||
width: 600,
|
||||
source: modelSourceMap.local_path_value
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageContainer
|
||||
@@ -734,7 +747,13 @@ const Models: React.FC<ModelsProps> = ({
|
||||
}
|
||||
right={
|
||||
<Space size={20}>
|
||||
<Dropdown menu={{ items: sourceOptions }} placement="bottomRight">
|
||||
<Dropdown
|
||||
menu={{
|
||||
items: sourceOptions,
|
||||
onClick: handleClickDropdown
|
||||
}}
|
||||
placement="bottomRight"
|
||||
>
|
||||
<Button
|
||||
icon={<DownOutlined></DownOutlined>}
|
||||
type="primary"
|
||||
@@ -883,7 +902,6 @@ const Models: React.FC<ModelsProps> = ({
|
||||
open={openDeployModal.show}
|
||||
action={PageAction.CREATE}
|
||||
title={intl.formatMessage({ id: 'models.button.deploy' })}
|
||||
data={currentData}
|
||||
source={openDeployModal.source}
|
||||
width={openDeployModal.width}
|
||||
onCancel={handleDeployModalCancel}
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
.catalog-item {
|
||||
height: 146px;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
padding: 12px 14px;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
border: 1px solid var(--ant-color-border);
|
||||
border-radius: var(--border-radius-base);
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
|
||||
.img {
|
||||
width: 32px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
|
||||
img {
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
// height: 100%;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-fill-sider);
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: var(--color-fill-sider);
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: var(--font-size-middle);
|
||||
font-weight: var(--font-weight-bold);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
gap: 8px;
|
||||
margin-bottom: 10px;
|
||||
|
||||
.name {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.desc {
|
||||
font-size: var(--font-size-base);
|
||||
color: var(--ant-color-text-secondary);
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.item-footer {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.tags {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tag-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
height: 22px;
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user