chore: adjust deployment modal form

This commit is contained in:
jialin
2025-09-16 11:26:17 +08:00
parent ed49e03802
commit 29b74f85c5
39 changed files with 1574 additions and 786 deletions
@@ -1,6 +1,7 @@
import IconFont from '@/components/icon-font';
import LabelSelector from '@/components/label-selector';
import ListInput from '@/components/list-input';
import CheckboxField from '@/components/seal-form/checkbox-field';
import SealCascader from '@/components/seal-form/seal-cascader';
import SealInput from '@/components/seal-form/seal-input';
import SealSelect from '@/components/seal-form/seal-select';
@@ -8,17 +9,8 @@ import TooltipList from '@/components/tooltip-list';
import { PageAction } from '@/config';
import { PageActionType } from '@/config/types';
import useAppUtils from '@/hooks/use-app-utils';
import { QuestionCircleOutlined } from '@ant-design/icons';
import { useIntl } from '@umijs/max';
import {
Checkbox,
Collapse,
Form,
FormInstance,
Tooltip,
Typography
} from 'antd';
import { CheckboxChangeEvent } from 'antd/es/checkbox';
import { Collapse, Form, FormInstance, Typography } from 'antd';
import _ from 'lodash';
import React, { useCallback, useMemo } from 'react';
import {
@@ -32,7 +24,6 @@ import {
placementStrategyOptions
} from '../config';
import { useFormContext } from '../config/form-context';
import llamaConfig from '../config/llama-config';
import mindieConfig from '../config/mindie-config';
import { FormData } from '../config/types';
import vllmConfig from '../config/vllm-config';
@@ -62,42 +53,6 @@ const placementStrategyTips = [
}
];
const scheduleTypeTips = [
{
title: {
text: 'models.form.scheduletype.auto',
locale: true
},
tips: 'models.form.scheduletype.auto.tips'
},
{
title: {
text: 'models.form.scheduletype.manual',
locale: true
},
tips: 'models.form.scheduletype.manual.tips'
}
];
const CheckboxField: React.FC<{
title: string;
label: string;
checked?: boolean;
onChange?: (e: CheckboxChangeEvent) => void;
}> = ({ title, label, checked, onChange }) => {
return (
<Checkbox className="p-l-6" checked={checked} onChange={onChange}>
<Tooltip title={title}>
<span style={{ color: 'var(--ant-color-text-tertiary)' }}>{label}</span>
<QuestionCircleOutlined
className="m-l-4"
style={{ color: 'var(--ant-color-text-tertiary)' }}
/>
</Tooltip>
</Checkbox>
);
};
const AdvanceConfig: React.FC<AdvanceConfigProps> = (props) => {
const { form, isGGUF, gpuOptions, source, backendOptions, action } = props;
const { getRuleMessage } = useAppUtils();
@@ -115,9 +70,6 @@ const AdvanceConfig: React.FC<AdvanceConfigProps> = (props) => {
const { onValuesChange } = useFormContext();
const paramsConfig = useMemo(() => {
if (backend === backendOptionsMap.llamaBox) {
return llamaConfig;
}
if (backend === backendOptionsMap.vllm) {
return vllmConfig;
}
@@ -220,14 +172,6 @@ const AdvanceConfig: React.FC<AdvanceConfigProps> = (props) => {
description={<TooltipList list={backendTipsList}></TooltipList>}
options={
backendOptions ?? [
{
label: backendLabelMap[backendOptionsMap.llamaBox],
value: backendOptionsMap.llamaBox,
disabled:
props.source === modelSourceMap.local_path_value
? false
: !isGGUF
},
{
label: backendLabelMap[backendOptionsMap.vllm],
value: backendOptionsMap.vllm,
@@ -261,27 +205,6 @@ const AdvanceConfig: React.FC<AdvanceConfigProps> = (props) => {
}
></SealSelect>
</Form.Item>
{/* <Form.Item name="scheduleType">
<SealSelect
onChange={handleScheduleTypeChange}
label={intl.formatMessage({ id: 'models.form.scheduletype' })}
description={<TooltipList list={scheduleTypeTips}></TooltipList>}
options={[
{
label: intl.formatMessage({
id: 'models.form.scheduletype.auto'
}),
value: 'auto'
},
{
label: intl.formatMessage({
id: 'models.form.scheduletype.manual'
}),
value: 'manual'
}
]}
></SealSelect>
</Form.Item> */}
{scheduleType === 'auto' && (
<>
<Form.Item<FormData> name="placement_strategy">
@@ -482,31 +405,10 @@ const AdvanceConfig: React.FC<AdvanceConfigProps> = (props) => {
></LabelSelector>
</Form.Item>
{backend === backendOptionsMap.llamaBox && (
<div style={{ paddingBottom: 22, paddingLeft: 10 }}>
<Form.Item<FormData>
name="cpu_offloading"
valuePropName="checked"
style={{ padding: '0 10px', marginBottom: 0 }}
noStyle
>
<CheckboxField
title={intl.formatMessage({
id: 'models.form.partialoffload.tips'
})}
label={intl.formatMessage({
id: 'resources.form.enablePartialOffload'
})}
></CheckboxField>
</Form.Item>
</div>
)}
{scheduleType === 'auto' &&
[
backendOptionsMap.llamaBox,
backendOptionsMap.vllm,
backendOptionsMap.ascendMindie
].includes(backend) && (
[backendOptionsMap.vllm, backendOptionsMap.ascendMindie].includes(
backend
) && (
<div style={{ paddingBottom: 22, paddingLeft: 10 }}>
<Form.Item<FormData>
name="distributed_inference_across_workers"
@@ -515,7 +417,7 @@ const AdvanceConfig: React.FC<AdvanceConfigProps> = (props) => {
noStyle
>
<CheckboxField
title={intl.formatMessage({
description={intl.formatMessage({
id: 'models.form.distribution.tips'
})}
label={intl.formatMessage({
@@ -533,7 +435,7 @@ const AdvanceConfig: React.FC<AdvanceConfigProps> = (props) => {
noStyle
>
<CheckboxField
title={intl.formatMessage({
description={intl.formatMessage({
id: 'models.form.restart.onerror.tips'
})}
label={intl.formatMessage({
@@ -1,3 +1,4 @@
import CatalogSkelton from '@/components/templates/card-skelton';
import breakpoints from '@/config/breakpoints';
import { Col, FloatButton, Row, Spin } from 'antd';
import _ from 'lodash';
@@ -6,7 +7,6 @@ import React, { useCallback } from 'react';
import styled from 'styled-components';
import { CatalogItem as CatalogItemType } from '../config/types';
import CatalogItem from './catalog-item';
import CatalogSkelton from './catalog-skelton';
const SpinWrapper = styled.div`
width: 100%;
@@ -1,34 +0,0 @@
import { Col, Row, Skeleton } from 'antd';
import React from 'react';
interface CatalogSkeltonProps {
span: number;
}
const CatalogSkelton: React.FC<CatalogSkeltonProps> = (props) => {
return (
<Row gutter={[16, 16]}>
{Array(6)
.fill(1)
.map((_, index) => {
return (
<Col span={props.span} key={index}>
<Skeleton
avatar={{
size: 32
}}
paragraph={{ rows: 2 }}
style={{
border: '1px solid var(--ant-color-border)',
borderRadius: 'var(--border-radius-base)',
padding: '16px 16px'
}}
></Skeleton>
</Col>
);
})}
</Row>
);
};
export default React.memo(CatalogSkelton);
+1 -31
View File
@@ -258,14 +258,6 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
description={<TooltipList list={backendTipsList}></TooltipList>}
options={
backendOptions ?? [
{
label: backendLabelMap[backendOptionsMap.llamaBox],
value: backendOptionsMap.llamaBox,
disabled:
props.source === modelSourceMap.local_path_value
? false
: !isGGUF
},
{
label: backendLabelMap[backendOptionsMap.vllm],
value: backendOptionsMap.vllm,
@@ -300,28 +292,6 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
></SealSelect>
</Form.Item> */}
<CatalogFrom></CatalogFrom>
{/* <Form.Item<FormData>
name="replicas"
rules={[
{
required: true,
message: getRuleMessage('input', 'models.form.replicas')
}
]}
>
<SealInput.Number
style={{ width: '100%' }}
label={intl.formatMessage({
id: 'models.form.replicas'
})}
required
description={intl.formatMessage(
{ id: 'models.form.replicas.tips' },
{ api: `${window.location.origin}/v1` }
)}
min={0}
></SealInput.Number>
</Form.Item> */}
<Form.Item<FormData> name="description">
<SealInput.TextArea
scaleSize={true}
@@ -343,4 +313,4 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
);
});
export default React.memo(DataForm);
export default DataForm;
@@ -568,6 +568,7 @@ const AddModal: FC<AddModalProps> = (props) => {
isGGUF: isGGUF,
pageAction: action,
modelFileOptions: props.modelFileOptions,
gpuOptions: props.gpuOptions,
onValuesChange: onValuesChange
}}
>
+137 -10
View File
@@ -1,18 +1,145 @@
import IconFont from '@/components/icon-font';
import StatusTag from '@/components/status-tag';
import ThemeTag from '@/components/tags-wrapper/theme-tag';
import Card from '@/components/templates/card';
import { Button } from 'antd';
import dayjs from 'dayjs';
import _ from 'lodash';
import React from 'react';
import styled from 'styled-components';
import {
InstanceStatusMap,
InstanceStatusMapValue,
modelCategories,
modelSourceMap,
status
} from '../config';
const ModelItem: React.FC<{ model: any; onDeploy: (model: any) => void }> = (
props
) => {
const { model, onDeploy } = props;
const ModelItemContent = styled.div`
display: flex;
flex-direction: column;
height: 100%;
width: 100%;
cursor: default;
.title {
margin-bottom: 16px;
display: flex;
align-items: center;
justify-content: space-between;
padding-bottom: 8px;
.anticon {
font-size: 16px;
color: var(--ant-color-text-secondary);
}
.text {
display: flex;
align-items: center;
font-size: var(--font-size-middle);
font-weight: 500;
color: var(--ant-color-text);
}
}
.content {
display: flex;
flex-direction: column;
justify-content: space-between;
flex: 1;
}
.footer {
width: 100%;
display: flex;
justify-content: space-between;
align-items: flex-end;
.btn {
display: none;
}
}
&:hover {
.btn {
display: block;
}
}
.time {
color: var(--ant-color-text-secondary);
font-size: var(--font-size-small);
font-weight: 400;
}
.extra-info {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 16px;
.tag-item {
margin-right: 0;
display: flex;
align-items: center;
justify-content: center;
padding: 2px 6px;
border-radius: 4px;
font-size: 12px;
height: 22px;
opacity: 0.7;
}
}
`;
const sourceIconMap = {
[modelSourceMap.local_path_value]: 'icon-hard-disk',
[modelSourceMap.huggingface_value]: 'icon-huggingface',
[modelSourceMap.modelscope_value]: 'icon-modelscope'
};
const ModelItem: React.FC<{
model: Record<string, any>;
onClick: (model: any) => void;
}> = (props) => {
const { model, onClick } = props;
return (
<Card>
<div className="model-item" onClick={() => onDeploy(model)}>
<h3>{model.name}</h3>
<p>{model.description}</p>
<span>Deploy</span>
</div>
<Card onClick={() => onClick(model)} clickable={false} ghost>
<ModelItemContent>
<div className="title">
<span className="text">
<IconFont
type={sourceIconMap[model.source]}
style={{ marginRight: 8 }}
/>
<span>{model.name}</span>
</span>
<StatusTag
maxTooltipWidth={400}
statusValue={{
status: status[InstanceStatusMap.Running] as any,
text: InstanceStatusMapValue[InstanceStatusMap.Running],
message: model.state_message
}}
></StatusTag>
</div>
<div className="content">
<div className="extra-info">
<ThemeTag className="tag-item" color="blue">
{_.find(modelCategories, { value: model.categories?.[0] })
?.label || model.categories?.[0]}
</ThemeTag>
<ThemeTag className="tag-item" color="purple">
128K context
</ThemeTag>
</div>
<div className="footer">
<span className="time">
{dayjs(model.created_at).format('YYYY-MM-DD HH:mm:ss')}
</span>
<Button
size="middle"
className="btn"
variant="filled"
color="default"
>
Go to Playground
</Button>
</div>
</div>
</ModelItemContent>
</Card>
);
};
@@ -1,103 +0,0 @@
import AlertBlockInfo from '@/components/alert-info/block';
import IconFont from '@/components/icon-font';
import { useIntl } from '@umijs/max';
import { Button } from 'antd';
import { useState } from 'react';
import styled from 'styled-components';
const Wrapper = styled.div`
padding-inline: 24px;
`;
const Tips = styled.div`
margin-top: 8px !important;
ul {
margin: 0 !important;
padding: 0 !important;
margin-top: 8px !important;
li {
position: relative;
list-style: none;
line-height: 24px;
margin: 0 !important;
padding-inline: 16px 0 !important;
&::before {
content: '';
position: absolute;
left: 0px;
top: 0;
width: 6px;
height: 6px;
border-radius: 50%;
background-color: var(--ant-color-text-secondary);
margin-top: 8px;
}
}
}
.bold {
font-weight: 700;
color: var(--ant-color-text);
}
.notice {
margin-top: 8px !important;
}
`;
const TitleWrapper = styled.div`
display: flex;
align-items: center;
font-weight: 700;
color: var(--ant-color-text);
justify-content: space-between;
`;
const RenderMessage = () => {
const intl = useIntl();
return (
<Tips>
<div className="notice">
<div
dangerouslySetInnerHTML={{
__html: intl.formatMessage({
id: 'models.ollama.deprecated.notice'
})
}}
></div>
</div>
</Tips>
);
};
const OllamaTips = () => {
const intl = useIntl();
const [maxHeight, setMaxHeight] = useState(1);
const handleShowMore = () => {
setMaxHeight(maxHeight === 1 ? 600 : 1);
};
return (
<Wrapper>
<AlertBlockInfo
contentStyle={{ paddingInline: 0 }}
maxHeight={maxHeight}
ellipsis={false}
message={<RenderMessage></RenderMessage>}
title={
<TitleWrapper>
<div>
{intl.formatMessage({ id: 'models.ollama.deprecated.title' })}
</div>
<Button
onClick={handleShowMore}
size="small"
type="text"
icon={<IconFont type="icon-down"></IconFont>}
></Button>
</TitleWrapper>
}
type={'warning'}
></AlertBlockInfo>
</Wrapper>
);
};
export default OllamaTips;
+111 -48
View File
@@ -1,12 +1,15 @@
import CheckboxField from '@/components/seal-form/checkbox-field';
import SealCascader from '@/components/seal-form/seal-cascader';
import SealSelect from '@/components/seal-form/seal-select';
import TooltipList from '@/components/tooltip-list';
import useAppUtils from '@/hooks/use-app-utils';
import { QuestionCircleOutlined } from '@ant-design/icons';
import { useIntl } from '@umijs/max';
import { Checkbox, Form, Tooltip } from 'antd';
import { CheckboxChangeEvent } from 'antd/es/checkbox';
import { Form } from 'antd';
import React from 'react';
import { backendOptionsMap } from '../config';
import { useFormContext } from '../config/form-context';
import { FormData } from '../config/types';
import GPUCard from './gpu-card';
const scheduleTypeTips = [
{
@@ -25,29 +28,15 @@ const scheduleTypeTips = [
}
];
const CheckboxField: React.FC<{
title: string;
label: string;
checked?: boolean;
onChange?: (e: CheckboxChangeEvent) => void;
}> = ({ title, label, checked, onChange }) => {
return (
<Checkbox className="p-l-6" checked={checked} onChange={onChange}>
<Tooltip title={title}>
<span style={{ color: 'var(--ant-color-text-tertiary)' }}>{label}</span>
<QuestionCircleOutlined
className="m-l-4"
style={{ color: 'var(--ant-color-text-tertiary)' }}
/>
</Tooltip>
</Checkbox>
);
};
const Performance: React.FC = () => {
const intl = useIntl();
const { onValuesChange, onQuantizationChange, source, quantizationOptions } =
useFormContext();
const {
onValuesChange,
onQuantizationChange,
gpuOptions,
source,
quantizationOptions
} = useFormContext();
const { getRuleMessage } = useAppUtils();
const form = Form.useFormInstance();
@@ -61,6 +50,8 @@ const Performance: React.FC = () => {
onQuantizationChange?.(val);
};
const handleGpuSelectorChange = (value: any) => {};
return (
<>
<Form.Item name="scheduleType">
@@ -80,30 +71,102 @@ const Performance: React.FC = () => {
id: 'models.form.scheduletype.manual'
}),
value: 'manual'
},
{
label: intl.formatMessage({
id: 'models.form.scheduletype.gpuType'
}),
value: 'specific_gpu_type'
}
]}
></SealSelect>
</Form.Item>
<Form.Item<FormData>
name="quantization"
key="quantization"
rules={[
{
required: true,
message: getRuleMessage('select', 'quantization', false)
}
]}
>
<SealSelect
filterOption
defaultActiveFirstOption
disabled={false}
options={quantizationOptions}
onChange={handleOnQuantizationChange}
label="Quantization"
required
></SealSelect>
</Form.Item>
{form.getFieldValue('scheduleType') === 'specific_gpu_type' && (
<>
<Form.Item name={['gpu_selector', 'gpu_type']}>
<SealSelect
label={intl.formatMessage({ id: 'models.form.gpuType' })}
options={[
{
label: 'NVIDIA 4090',
value: 'nvidia-4090'
},
{
label: 'NVIDIA A100',
value: 'nvidia-a100'
},
{
label: 'NVIDIA H100',
value: 'nvidia-h100'
},
{
label: 'Huawei 910B',
value: 'huawei-910b'
},
{
label: 'Huawei 910C',
value: 'huawei-910c'
}
]}
></SealSelect>
</Form.Item>
<Form.Item name={['gpu_selector', 'gpu_count']}>
<SealSelect
label={intl.formatMessage({ id: 'models.form.gpuCount' })}
options={[
{
label: 'Auto',
value: 'auto'
},
{
label: '1',
value: 1
},
{
label: '2',
value: 2
},
{
label: '4',
value: 4
}
]}
></SealSelect>
</Form.Item>
</>
)}
{form.getFieldValue('scheduleType') === 'manual' &&
!form.getFieldValue('fix_gpu_type') && (
<>
<Form.Item
name={['gpu_selector', 'gpu_ids']}
rules={[
{
required: true,
message: getRuleMessage('select', 'models.form.gpuselector')
}
]}
>
<SealCascader
required
showSearch
expandTrigger="hover"
multiple={
form.getFieldValue('backend') !== backendOptionsMap.voxBox
}
popupClassName="cascader-popup-wrapper gpu-selector"
maxTagCount={1}
label={intl.formatMessage({ id: 'models.form.gpuselector' })}
options={gpuOptions}
showCheckedStrategy="SHOW_CHILD"
value={form.getFieldValue(['gpu_selector', 'gpu_ids'])}
optionNode={GPUCard}
getPopupContainer={(triggerNode) => triggerNode.parentNode}
onChange={handleGpuSelectorChange}
></SealCascader>
</Form.Item>
</>
)}
<div style={{ paddingBottom: 22, paddingLeft: 10 }}>
<Form.Item<FormData>
name="optimize_long_prompt"
@@ -112,8 +175,7 @@ const Performance: React.FC = () => {
noStyle
>
<CheckboxField
title="Optimize long prompt tips"
label="Optimize Long Prompt"
label={intl.formatMessage({ id: 'models.form.optimizeLongPrompt' })}
></CheckboxField>
</Form.Item>
</div>
@@ -125,8 +187,9 @@ const Performance: React.FC = () => {
noStyle
>
<CheckboxField
title="Enable speculative decoding tips"
label="Enable Speculative Decoding"
label={intl.formatMessage({
id: 'models.form.enableSpeculativeDecoding'
})}
></CheckboxField>
</Form.Item>
</div>
+47 -9
View File
@@ -111,6 +111,36 @@ interface ModelsProps {
total: number;
}
const clusterList = [
{
label: 'Custom',
value: 'custom'
},
{
label: 'Kubernetes',
value: 'kubernetes'
},
{
label: 'Digital Ocean',
value: 'digital_ocean'
}
];
const statusList = [
{
label: 'Running',
value: 'running'
},
{
label: 'Stopped',
value: 'stopped'
},
{
label: 'Error',
value: 'error'
}
];
const getFormattedData = (record: any, extraData = {}) => ({
id: record.id,
data: {
@@ -605,7 +635,6 @@ const Models: React.FC<ModelsProps> = ({
title: intl.formatMessage({ id: 'common.table.name' }),
dataIndex: 'name',
key: 'name',
width: 400,
span: 5,
render: (text: string, record: ListItem) => (
<span className="flex-center" style={{ maxWidth: '100%' }}>
@@ -620,10 +649,10 @@ const Models: React.FC<ModelsProps> = ({
title: 'Cluster',
dataIndex: 'cluster',
key: 'cluster',
span: 4,
span: 3,
render: (text: string, record: ListItem) => (
<span className="flex flex-column" style={{ width: '100%' }}>
Custom
{['Custom', 'Kubernetes', 'Digital Ocean'][record.id] || 'Custom'}
</span>
)
},
@@ -631,7 +660,7 @@ const Models: React.FC<ModelsProps> = ({
title: intl.formatMessage({ id: 'models.form.source' }),
dataIndex: 'source',
key: 'source',
span: 4,
span: 5,
render: (text: string, record: ListItem) => (
<span className="flex flex-column" style={{ width: '100%' }}>
<AutoTooltip ghost>{generateSource(record)}</AutoTooltip>
@@ -778,7 +807,7 @@ const Models: React.FC<ModelsProps> = ({
<Space>
<Input
placeholder={intl.formatMessage({ id: 'common.filter.name' })}
style={{ width: 200 }}
style={{ width: 160 }}
size="large"
allowClear
onChange={handleNameChange}
@@ -789,7 +818,7 @@ const Models: React.FC<ModelsProps> = ({
placeholder={intl.formatMessage({
id: 'models.filter.category'
})}
style={{ width: 180 }}
style={{ width: 160 }}
size="large"
maxTagCount={1}
onChange={handleCategoryChange}
@@ -798,11 +827,20 @@ const Models: React.FC<ModelsProps> = ({
<Select
allowClear
showSearch={false}
placeholder="Filter by worker"
style={{ width: 180 }}
placeholder="Filter by cluster"
style={{ width: 160 }}
size="large"
maxTagCount={1}
options={[]}
options={clusterList}
></Select>
<Select
allowClear
showSearch={false}
placeholder="Running Replicas"
style={{ width: 140 }}
size="large"
maxTagCount={1}
options={statusList}
></Select>
<Button
type="text"
@@ -23,7 +23,6 @@ import { FormContext, FormInnerContext } from '../config/form-context';
import { FormData, ListItem } from '../config/types';
import HuggingFaceForm from '../forms/hugging-face';
import LocalPathForm from '../forms/local-path';
import OllamaForm from '../forms/ollama_library';
import { useCheckCompatibility } from '../hooks';
import AdvanceConfig from './advance-config';
import ColumnWrapper from './column-wrapper';
@@ -396,7 +395,6 @@ const UpdateModal: React.FC<AddModalProps> = (props) => {
}}
>
<HuggingFaceForm></HuggingFaceForm>
<OllamaForm></OllamaForm>
<LocalPathForm></LocalPathForm>
</FormInnerContext.Provider>
<Form.Item name="backend" rules={[{ required: true }]}>
@@ -447,30 +445,6 @@ const UpdateModal: React.FC<AddModalProps> = (props) => {
}
></SealSelect>
</Form.Item>
<Form.Item<FormData>
name="replicas"
rules={[
{
required: true,
message: getRuleMessage('input', 'models.form.replicas')
}
]}
>
<SealInput.Number
style={{ width: '100%' }}
label={intl.formatMessage({
id: 'models.form.replicas'
})}
required
description={intl.formatMessage(
{
id: 'models.form.replicas.tips'
},
{ api: `${window.location.origin}/v1` }
)}
min={0}
></SealInput.Number>
</Form.Item>
<Form.Item<FormData> name="description">
<SealInput.TextArea
scaleSize={true}
@@ -9,6 +9,7 @@ interface FormContextProps {
sizeOptions?: Global.BaseOption<number>[];
quantizationOptions?: Global.BaseOption<string>[];
modelFileOptions?: any[];
gpuOptions?: any[];
onSizeChange?: (val: number) => void;
onQuantizationChange?: (val: string) => void;
onValuesChange?: (changedValues: any, allValues: any) => void;
+17 -4
View File
@@ -83,10 +83,6 @@ export const ollamaModelOptions = [
];
export const backendTipsList = [
{
title: 'llama-box',
tips: 'models.form.backend.llamabox'
},
{
title: 'vLLM',
tips: 'models.form.backend.vllm'
@@ -537,3 +533,20 @@ export const getBackendParamsTips = (backend: string) => {
version: 'v0.0.13'
};
};
export const scheduleTypeTips = [
{
title: {
text: 'models.form.scheduletype.auto',
locale: true
},
tips: 'models.form.scheduletype.auto.tips'
},
{
title: {
text: 'models.form.scheduletype.manual',
locale: true
},
tips: 'models.form.scheduletype.manual.tips'
}
];
+5 -1
View File
@@ -56,7 +56,9 @@ export interface FormData {
model_scope_model_id?: string;
model_scope_file_path?: string;
gpu_selector?: {
gpu_ids: string[];
gpu_ids?: string[];
gpu_type?: string;
gpu_count?: number;
};
placement_strategy?: string;
cpu_offloading?: boolean;
@@ -65,6 +67,8 @@ export interface FormData {
name: string;
replicas: number;
description: string;
optimize_long_prompt: boolean;
enable_speculative_decoding: boolean;
}
interface ComputedResourceClaim {
+2 -62
View File
@@ -1,21 +1,13 @@
import IconFont from '@/components/icon-font';
import SealAutoComplete from '@/components/seal-form/auto-complete';
import SealInput from '@/components/seal-form/seal-input';
import SealSelect from '@/components/seal-form/seal-select';
import TooltipList from '@/components/tooltip-list';
import useAppUtils from '@/hooks/use-app-utils';
import { ModelFileFormData as FormData } from '@/pages/resources/config/types';
import { useIntl } from '@umijs/max';
import { Form, Typography } from 'antd';
import { Form } from 'antd';
import _ from 'lodash';
import React, { forwardRef, useImperativeHandle, useMemo } from 'react';
import OllamaTips from '../components/ollama-tips';
import {
localPathTipsList,
modelSourceMap,
ollamaModelOptions,
sourceOptions
} from '../config';
import { localPathTipsList, modelSourceMap, sourceOptions } from '../config';
interface TargetFormProps {
ref?: any;
@@ -73,56 +65,7 @@ const TargetForm: React.FC<TargetFormProps> = forwardRef((props, ref) => {
);
};
const renderOllamaModelFields = () => {
return (
<>
<Form.Item<FormData>
name="ollama_library_model_name"
key="ollama_library_model_name"
rules={[
{
required: true,
message: getRuleMessage('input', 'models.table.name')
}
]}
>
<SealAutoComplete
allowClear
filterOption
defaultActiveFirstOption
disabled={false}
options={ollamaModelOptions}
description={
<span>
<span>
{intl.formatMessage({ id: 'models.form.ollamalink' })}
</span>
<Typography.Link
className="flex-center"
href="https://www.ollama.com/library"
target="_blank"
>
<IconFont
type="icon-external-link"
className="font-size-14"
></IconFont>
</Typography.Link>
</span>
}
label={intl.formatMessage({ id: 'model.form.ollama.model' })}
placeholder={intl.formatMessage({ id: 'model.form.ollamaholder' })}
required
></SealAutoComplete>
</Form.Item>
</>
);
};
const renderFieldsBySource = useMemo(() => {
if (props.source === modelSourceMap.ollama_library_value) {
return renderOllamaModelFields();
}
if (props.source === modelSourceMap.local_path_value) {
return renderLocalPathFields();
}
@@ -132,9 +75,6 @@ const TargetForm: React.FC<TargetFormProps> = forwardRef((props, ref) => {
return (
<div>
{source === modelSourceMap.ollama_library_value && (
<OllamaTips></OllamaTips>
)}
<Form
form={form}
onFinish={handleOk}
-19
View File
@@ -51,25 +51,6 @@ const HuggingFaceForm: React.FC = () => {
onBlur={handleOnBlur}
></SealInput.Input>
</Form.Item>
{/* {isGGUF && (
<Form.Item<FormData>
name="file_name"
key="file_name"
rules={[
{
required: true,
message: getRuleMessage('input', 'models.form.filename')
}
]}
>
<SealInput.Input
label={intl.formatMessage({ id: 'models.form.filename' })}
required
disabled={pageAction === PageAction.CREATE}
onBlur={handleOnBlur}
></SealInput.Input>
</Form.Item>
)} */}
</>
);
};
@@ -1,67 +0,0 @@
import SealAutoComplete from '@/components/seal-form/auto-complete';
import useAppUtils from '@/hooks/use-app-utils';
import { useIntl } from '@umijs/max';
import { Form } from 'antd';
import React from 'react';
import { modelSourceMap, ollamaModelOptions } from '../config';
import { useFormContext, useFormInnerContext } from '../config/form-context';
import { FormData } from '../config/types';
const OllamaForm: React.FC = () => {
const formCtx = useFormContext();
const formInnerCtx = useFormInnerContext();
const { byBuiltIn, onValuesChange } = formCtx;
const { getRuleMessage } = useAppUtils();
const intl = useIntl();
const source = Form.useWatch('source');
const formInstance = Form.useFormInstance();
if (![modelSourceMap.ollama_library_value].includes(source) || byBuiltIn) {
return null;
}
const handleModelNameChange = (value: string) => {
if (value) {
onValuesChange?.({}, formInstance.getFieldsValue());
}
};
const handleOnBlur = (e: any) => {
onValuesChange?.({}, formInstance.getFieldsValue());
};
return (
<>
<Form.Item<FormData>
name="ollama_library_model_name"
key="ollama_library_model_name"
rules={[
{
required: true,
message: getRuleMessage('input', 'models.table.name')
}
]}
>
<SealAutoComplete
allowClear
filterOption
defaultActiveFirstOption
disabled={false}
options={ollamaModelOptions}
onSelect={handleModelNameChange}
onBlur={handleOnBlur}
description={
<span
dangerouslySetInnerHTML={{
__html: intl.formatMessage({ id: 'models.form.ollamalink' })
}}
></span>
}
label={intl.formatMessage({ id: 'model.form.ollama.model' })}
placeholder={intl.formatMessage({ id: 'model.form.ollamaholder' })}
required
></SealAutoComplete>
</Form.Item>
</>
);
};
export default OllamaForm;
+2
View File
@@ -104,8 +104,10 @@ export const useGenerateFormEditInitialValues = () => {
queryWorkersList({ page: 1, perPage: 100 })
]);
const gpuList = generateCascaderOptions(gpuData.items, workerData.items);
gpuDeviceList.current = gpuList;
workerList.current = workerData.items;
return gpuList;
};
+65 -24
View File
@@ -1,25 +1,38 @@
import { FilterBar } from '@/components/page-tools';
import MoreButton from '@/components/buttons/more';
import PageTools from '@/components/page-tools';
import CardList from '@/components/templates/card-list';
import CardSkeleton from '@/components/templates/card-skelton';
import useTableFetch from '@/hooks/use-table-fetch';
import { SyncOutlined } from '@ant-design/icons';
import { PageContainer } from '@ant-design/pro-components';
import { useIntl } from '@umijs/max';
import { Button, Input, Select, Space } from 'antd';
import React from 'react';
import { MODELS_API, queryModelsList } from './apis';
import ModelItem from './components/model-item';
const UserModels: React.FC = () => {
const { dataSource, queryParams, fetchData, handleSearch, handleNameChange } =
useTableFetch<any>({
fetchAPI: queryModelsList,
API: MODELS_API,
watch: false
});
const intl = useIntl();
const handleSearch = () => {
// Implement search functionality here
const loadMore = () => {
fetchData({
...queryParams,
page: queryParams.page + 1
});
};
const handleInputChange = () => {};
const handleOnDeploy = (model: any) => {
// Implement deploy functionality here
const handleOnClick = (model: any) => {
console.log('Deploying model:', model);
};
const renderCard = (data: any) => {
return <ModelItem model={data} onDeploy={handleOnDeploy} />;
return <ModelItem model={data} onClick={handleOnClick} />;
};
return (
@@ -34,27 +47,55 @@ const UserModels: React.FC = () => {
}}
extra={[]}
>
<FilterBar
showSelect={true}
showPrimaryButton={false}
showDeleteButton={false}
selectHolder="Filter by cluster"
<PageTools
marginBottom={22}
marginTop={30}
buttonText={intl.formatMessage({ id: 'resources.button.create' })}
handleInputChange={() => {}}
handleSearch={handleSearch}
width={{ input: 200 }}
></FilterBar>
{/* <CardList
left={
<Space>
<Input
placeholder={intl.formatMessage({ id: 'common.filter.name' })}
style={{ width: 230 }}
size="large"
allowClear
onClear={() =>
handleNameChange({
target: {
value: ''
}
})
}
onChange={handleNameChange}
></Input>
<Select
allowClear
showSearch={false}
placeholder={intl.formatMessage({ id: 'models.filter.category' })}
style={{ width: 180 }}
size="large"
maxTagCount={1}
options={[]}
></Select>
<Button
type="text"
style={{ color: 'var(--ant-color-text-tertiary)' }}
icon={<SyncOutlined></SyncOutlined>}
onClick={handleSearch}
></Button>
</Space>
}
></PageTools>
<CardList
dataList={dataSource.dataList}
loading={dataSource.loading}
onDeploy={handleOnDeploy}
activeId={-1}
isFirst={isFirst}
Skeleton={CatalogSkelton}
isFirst={!dataSource.loadend}
Skeleton={CardSkeleton}
renderItem={renderCard}
></CardList> */}
></CardList>
<MoreButton
show={queryParams.page < dataSource.totalPage}
loading={dataSource.loading}
loadMore={loadMore}
></MoreButton>
</PageContainer>
);
};