fix: calc scrollheight dynamically for tabs change
This commit is contained in:
@@ -217,12 +217,6 @@ export default function useOverlayScroller(data?: {
|
|||||||
|
|
||||||
// const currentScroll = instanceRef.current?.scroll().position.y;
|
// const currentScroll = instanceRef.current?.scroll().position.y;
|
||||||
const targetPos = targetRect.top - containerRect.top + currentScroll;
|
const targetPos = targetRect.top - containerRect.top + currentScroll;
|
||||||
console.log(
|
|
||||||
'target=======',
|
|
||||||
currentScroll,
|
|
||||||
targetPos,
|
|
||||||
instanceRef.current?.options()
|
|
||||||
);
|
|
||||||
|
|
||||||
scrollEventElement.current.scroll({
|
scrollEventElement.current.scroll({
|
||||||
y: targetPos - offset,
|
y: targetPos - offset,
|
||||||
@@ -231,6 +225,35 @@ export default function useOverlayScroller(data?: {
|
|||||||
instanceRef.current?.update?.();
|
instanceRef.current?.update?.();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getScrollElementScrollableHeight = () => {
|
||||||
|
if (!instanceRef.current || !scrollEventElement.current) {
|
||||||
|
instanceRef.current = instance?.();
|
||||||
|
scrollEventElement.current =
|
||||||
|
instanceRef.current?.elements()?.scrollEventElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
const scrollerState = instanceRef.current?.state();
|
||||||
|
const overflowAmount = scrollerState?.overflowAmount;
|
||||||
|
const scrollOffsetElement = instanceRef.current?.elements().viewport;
|
||||||
|
console.log('osInstance==========', overflowAmount, scrollOffsetElement, {
|
||||||
|
top:
|
||||||
|
Math.round(
|
||||||
|
(overflowAmount?.y - scrollOffsetElement?.scrollTop || 0) /
|
||||||
|
overflowAmount?.y
|
||||||
|
) * overflowAmount?.y,
|
||||||
|
scrollTop: scrollOffsetElement?.scrollTop,
|
||||||
|
scrollLeft: scrollOffsetElement?.scrollLeft,
|
||||||
|
scrollHeight:
|
||||||
|
scrollOffsetElement?.scrollHeight - scrollOffsetElement?.clientHeight
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
scrollTop: scrollOffsetElement?.scrollTop,
|
||||||
|
scrollHeight:
|
||||||
|
scrollOffsetElement?.scrollHeight - scrollOffsetElement?.clientHeight
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
instanceRef.current?.destroy?.();
|
instanceRef.current?.destroy?.();
|
||||||
@@ -240,9 +263,10 @@ export default function useOverlayScroller(data?: {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
initialize: createInstance,
|
initialize: createInstance,
|
||||||
instance: instanceRef.current,
|
instance: instanceRef,
|
||||||
scrollEventElement: scrollEventElement.current,
|
scrollEventElement: scrollEventElement,
|
||||||
initialized: initialized.current,
|
initialized: initialized.current,
|
||||||
|
getScrollElementScrollableHeight,
|
||||||
generateInstance,
|
generateInstance,
|
||||||
destroyInstance: destroyInstance,
|
destroyInstance: destroyInstance,
|
||||||
updateScrollerPosition: throttledUpdateScrollerPosition,
|
updateScrollerPosition: throttledUpdateScrollerPosition,
|
||||||
|
|||||||
@@ -45,7 +45,8 @@ export default {
|
|||||||
'models.form.scheduletype': 'Schedule Type',
|
'models.form.scheduletype': 'Schedule Type',
|
||||||
'models.form.categories': 'Model Category',
|
'models.form.categories': 'Model Category',
|
||||||
'models.form.scheduletype.auto': 'Auto',
|
'models.form.scheduletype.auto': 'Auto',
|
||||||
'models.form.scheduletype.manual': 'Specify GPU',
|
'models.form.scheduletype.manual': 'Manual',
|
||||||
|
'models.form.scheduletype.gpu': 'Specify GPU',
|
||||||
'models.form.scheduletype.gpuType': 'Specify GPU Type',
|
'models.form.scheduletype.gpuType': 'Specify GPU Type',
|
||||||
'models.form.scheduletype.auto.tips':
|
'models.form.scheduletype.auto.tips':
|
||||||
'Automatically deploys model instances to appropriate GPUs based on current resource conditions.',
|
'Automatically deploys model instances to appropriate GPUs based on current resource conditions.',
|
||||||
@@ -242,5 +243,6 @@ export default {
|
|||||||
'models.form.rules.name':
|
'models.form.rules.name':
|
||||||
'Up to 63 characters; letters, numbers, dots (.), underscores (_), and hyphens (-) only; must start and end with an alphanumeric character.',
|
'Up to 63 characters; letters, numbers, dots (.), underscores (_), and hyphens (-) only; must start and end with an alphanumeric character.',
|
||||||
'models.catalog.button.explore': 'Explore Other Models',
|
'models.catalog.button.explore': 'Explore Other Models',
|
||||||
'models.catalog.precision': 'Precision'
|
'models.catalog.precision': 'Precision',
|
||||||
|
'models.form.gpuPerReplica.tips': 'Enter a custom number'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -47,7 +47,8 @@ export default {
|
|||||||
'models.form.scheduletype': 'スケジュールタイプ',
|
'models.form.scheduletype': 'スケジュールタイプ',
|
||||||
'models.form.categories': 'モデルカテゴリ',
|
'models.form.categories': 'モデルカテゴリ',
|
||||||
'models.form.scheduletype.auto': '自動',
|
'models.form.scheduletype.auto': '自動',
|
||||||
'models.form.scheduletype.manual': 'GPUを指定',
|
'models.form.scheduletype.manual': '手動',
|
||||||
|
'models.form.scheduletype.gpu': 'GPUを指定',
|
||||||
'models.form.scheduletype.gpuType': 'GPUタイプを指定',
|
'models.form.scheduletype.gpuType': 'GPUタイプを指定',
|
||||||
'models.form.scheduletype.auto.tips':
|
'models.form.scheduletype.auto.tips':
|
||||||
'現在のリソース状況に基づいて、モデルインスタンスを適切なGPUに自動的にデプロイします。',
|
'現在のリソース状況に基づいて、モデルインスタンスを適切なGPUに自動的にデプロイします。',
|
||||||
@@ -242,7 +243,8 @@ export default {
|
|||||||
'models.form.rules.name':
|
'models.form.rules.name':
|
||||||
'Up to 63 characters; letters, numbers, dots (.), underscores (_), and hyphens (-) only; must start and end with an alphanumeric character.',
|
'Up to 63 characters; letters, numbers, dots (.), underscores (_), and hyphens (-) only; must start and end with an alphanumeric character.',
|
||||||
'models.catalog.button.explore': 'Explore Other Models',
|
'models.catalog.button.explore': 'Explore Other Models',
|
||||||
'models.catalog.precision': 'Precision'
|
'models.catalog.precision': 'Precision',
|
||||||
|
'models.form.gpuPerReplica.tips': 'Enter a custom number'
|
||||||
};
|
};
|
||||||
|
|
||||||
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
||||||
@@ -313,5 +315,7 @@ export default {
|
|||||||
// 48. 'models.form.backend.custom': 'User Defined',
|
// 48. 'models.form.backend.custom': 'User Defined',
|
||||||
// 49. 'models.form.rules.name': 'Up to 63 characters; letters, numbers, dots (.), underscores (_), and hyphens (-) only; must start and end with an alphanumeric character.',
|
// 49. 'models.form.rules.name': 'Up to 63 characters; letters, numbers, dots (.), underscores (_), and hyphens (-) only; must start and end with an alphanumeric character.',
|
||||||
// 50. 'models.catalog.button.explore': 'Explore Other Models',
|
// 50. 'models.catalog.button.explore': 'Explore Other Models',
|
||||||
// 51. 'models.catalog.precision': 'Precision'
|
// 51. 'models.catalog.precision': 'Precision',
|
||||||
|
// 52. 'models.form.gpuPerReplica.tips': 'Enter a custom number'
|
||||||
|
|
||||||
// ========== End of To-Do List ==========
|
// ========== End of To-Do List ==========
|
||||||
|
|||||||
@@ -45,7 +45,8 @@ export default {
|
|||||||
'models.form.scheduletype': 'Тип планирования',
|
'models.form.scheduletype': 'Тип планирования',
|
||||||
'models.form.categories': 'Категория модели',
|
'models.form.categories': 'Категория модели',
|
||||||
'models.form.scheduletype.auto': 'Авто',
|
'models.form.scheduletype.auto': 'Авто',
|
||||||
'models.form.scheduletype.manual': 'Указать GPU',
|
'models.form.scheduletype.manual': 'Ручной',
|
||||||
|
'models.form.scheduletype.gpu': 'Указать GPU',
|
||||||
'models.form.scheduletype.gpuType': 'Указать тип GPU',
|
'models.form.scheduletype.gpuType': 'Указать тип GPU',
|
||||||
'models.form.scheduletype.auto.tips':
|
'models.form.scheduletype.auto.tips':
|
||||||
'Автоматическое развертывание инстансов модели на подходящие GPU в зависимости от текущих ресурсов.',
|
'Автоматическое развертывание инстансов модели на подходящие GPU в зависимости от текущих ресурсов.',
|
||||||
@@ -243,9 +244,11 @@ export default {
|
|||||||
'models.form.rules.name':
|
'models.form.rules.name':
|
||||||
'Up to 63 characters; letters, numbers, dots (.), underscores (_), and hyphens (-) only; must start and end with an alphanumeric character.',
|
'Up to 63 characters; letters, numbers, dots (.), underscores (_), and hyphens (-) only; must start and end with an alphanumeric character.',
|
||||||
'models.catalog.button.explore': 'Изучить другие модели',
|
'models.catalog.button.explore': 'Изучить другие модели',
|
||||||
'models.catalog.precision': 'Точность'
|
'models.catalog.precision': 'Точность',
|
||||||
|
'models.form.gpuPerReplica.tips': 'Enter a custom number'
|
||||||
};
|
};
|
||||||
|
|
||||||
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
||||||
// 1. 'models.form.rules.name': 'Up to 63 characters; letters, numbers, dots (.), underscores (_), and hyphens (-) only; must start and end with an alphanumeric character.'
|
// 1. 'models.form.rules.name': 'Up to 63 characters; letters, numbers, dots (.), underscores (_), and hyphens (-) only; must start and end with an alphanumeric character.',
|
||||||
|
// 2. 'models.form.gpuPerReplica.tips': 'Enter a custom number'
|
||||||
// ========== End of To-Do List ==========
|
// ========== End of To-Do List ==========
|
||||||
|
|||||||
@@ -46,7 +46,8 @@ export default {
|
|||||||
'models.form.categories': '模型类别',
|
'models.form.categories': '模型类别',
|
||||||
'models.form.scheduletype': '调度方式',
|
'models.form.scheduletype': '调度方式',
|
||||||
'models.form.scheduletype.auto': '自动',
|
'models.form.scheduletype.auto': '自动',
|
||||||
'models.form.scheduletype.manual': '指定 GPU',
|
'models.form.scheduletype.manual': '手动',
|
||||||
|
'models.form.scheduletype.gpu': '指定 GPU',
|
||||||
'models.form.scheduletype.gpuType': '指定 GPU 类型',
|
'models.form.scheduletype.gpuType': '指定 GPU 类型',
|
||||||
'models.form.scheduletype.auto.tips':
|
'models.form.scheduletype.auto.tips':
|
||||||
'自动根据当前资源情况部署模型实例到合适的 GPU 上。',
|
'自动根据当前资源情况部署模型实例到合适的 GPU 上。',
|
||||||
@@ -229,5 +230,6 @@ export default {
|
|||||||
'models.form.rules.name':
|
'models.form.rules.name':
|
||||||
'长度不超过 63 个字符,只能包含字母、数字、点(.)、下划线(_)和连字符(-),且必须以字母或数字开头和结尾。',
|
'长度不超过 63 个字符,只能包含字母、数字、点(.)、下划线(_)和连字符(-),且必须以字母或数字开头和结尾。',
|
||||||
'models.catalog.button.explore': '浏览其他模型',
|
'models.catalog.button.explore': '浏览其他模型',
|
||||||
'models.catalog.precision': '精度'
|
'models.catalog.precision': '精度',
|
||||||
|
'models.form.gpuPerReplica.tips': '输入自定义数值'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -22,8 +22,14 @@ const ColumnWrapper: React.FC<ColumnWrapperProps> = ({
|
|||||||
styles = {}
|
styles = {}
|
||||||
}) => {
|
}) => {
|
||||||
const scroller = React.useRef<any>(null);
|
const scroller = React.useRef<any>(null);
|
||||||
const { initialize, instance, scrollToBottom, scrollToTarget } =
|
const {
|
||||||
useOverlayScroller({
|
initialize,
|
||||||
|
instance,
|
||||||
|
scrollEventElement,
|
||||||
|
scrollToBottom,
|
||||||
|
scrollToTarget,
|
||||||
|
getScrollElementScrollableHeight
|
||||||
|
} = useOverlayScroller({
|
||||||
options: {
|
options: {
|
||||||
scrollbars: {
|
scrollbars: {
|
||||||
autoHide: 'move'
|
autoHide: 'move'
|
||||||
@@ -39,7 +45,14 @@ const ColumnWrapper: React.FC<ColumnWrapperProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<WrapperContext.Provider
|
<WrapperContext.Provider
|
||||||
value={{ osInstance: instance, scrollToBottom, scrollToTarget }}
|
value={{
|
||||||
|
scroller: scroller,
|
||||||
|
osInstance: instance,
|
||||||
|
scrollEventElement,
|
||||||
|
getScrollElementScrollableHeight,
|
||||||
|
scrollToBottom,
|
||||||
|
scrollToTarget
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="column-wrapper-footer"
|
className="column-wrapper-footer"
|
||||||
|
|||||||
@@ -2,8 +2,14 @@ import { createContext, useContext } from 'react';
|
|||||||
|
|
||||||
interface WrapperContextProps {
|
interface WrapperContextProps {
|
||||||
osInstance?: any;
|
osInstance?: any;
|
||||||
|
scroller?: any;
|
||||||
|
scrollEventElement?: any;
|
||||||
scrollToBottom?: () => void;
|
scrollToBottom?: () => void;
|
||||||
scrollToTop?: () => void;
|
scrollToTop?: () => void;
|
||||||
|
getScrollElementScrollableHeight?: () => {
|
||||||
|
scrollHeight: number;
|
||||||
|
scrollTop: number;
|
||||||
|
};
|
||||||
scrollToTarget?: (target: any, offset?: number) => void;
|
scrollToTarget?: (target: any, offset?: number) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ const BackendFields: React.FC = () => {
|
|||||||
const { onValuesChange, backendOptions, onBackendChange } = useFormContext();
|
const { onValuesChange, backendOptions, onBackendChange } = useFormContext();
|
||||||
const backend = Form.useWatch('backend', form);
|
const backend = Form.useWatch('backend', form);
|
||||||
|
|
||||||
const handleBackendVersionOnBlur = () => {
|
const handleBackendVersionOnChange = () => {
|
||||||
onValuesChange?.({}, form.getFieldsValue());
|
onValuesChange?.({}, form.getFieldsValue());
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -137,13 +137,14 @@ const BackendFields: React.FC = () => {
|
|||||||
{backendOptionsMap.custom !== backend && (
|
{backendOptionsMap.custom !== backend && (
|
||||||
<Form.Item name="backend_version">
|
<Form.Item name="backend_version">
|
||||||
<SealSelect
|
<SealSelect
|
||||||
|
allowClear
|
||||||
options={backendVersions}
|
options={backendVersions}
|
||||||
optionRender={optionRender}
|
optionRender={optionRender}
|
||||||
labelRender={labelRender}
|
labelRender={labelRender}
|
||||||
placeholder={intl.formatMessage({
|
placeholder={intl.formatMessage({
|
||||||
id: 'models.form.backendVersion.holder'
|
id: 'models.form.backendVersion.holder'
|
||||||
})}
|
})}
|
||||||
onBlur={handleBackendVersionOnBlur}
|
onChange={handleBackendVersionOnChange}
|
||||||
label={intl.formatMessage({ id: 'models.form.backendVersion' })}
|
label={intl.formatMessage({ id: 'models.form.backendVersion' })}
|
||||||
description={intl.formatMessage(
|
description={intl.formatMessage(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,33 +4,22 @@ import { useIntl } from '@umijs/max';
|
|||||||
import { Form } from 'antd';
|
import { Form } from 'antd';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { DeployFormKeyMap } from '../config';
|
import { DeployFormKeyMap } from '../config';
|
||||||
import { useCatalogFormContext, useFormContext } from '../config/form-context';
|
import { useFormContext } from '../config/form-context';
|
||||||
import { FormData } from '../config/types';
|
import { FormData } from '../config/types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* It can access some props from CatalogFormContext
|
||||||
|
*/
|
||||||
const CatalogForm: React.FC = () => {
|
const CatalogForm: React.FC = () => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const formCtx = useFormContext();
|
const formCtx = useFormContext();
|
||||||
const catalogFormCtx = useCatalogFormContext();
|
|
||||||
const { getRuleMessage } = useAppUtils();
|
const { getRuleMessage } = useAppUtils();
|
||||||
const { formKey } = formCtx;
|
const { formKey } = formCtx;
|
||||||
const {
|
|
||||||
sizeOptions,
|
|
||||||
quantizationOptions,
|
|
||||||
onSizeChange,
|
|
||||||
onQuantizationChange
|
|
||||||
} = catalogFormCtx;
|
|
||||||
|
|
||||||
if (formKey !== DeployFormKeyMap.CATALOG) {
|
if (formKey !== DeployFormKeyMap.CATALOG) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSizeChange = (val: any) => {
|
|
||||||
onSizeChange?.(val);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleOnQuantizationChange = (val: any) => {
|
|
||||||
onQuantizationChange?.(val);
|
|
||||||
};
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Form.Item<FormData>
|
<Form.Item<FormData>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { PageAction } from '@/config';
|
import { PageAction } from '@/config';
|
||||||
import { PageActionType } from '@/config/types';
|
import { PageActionType } from '@/config/types';
|
||||||
import CollapsePanel from '@/pages/_components/collapse-panel';
|
import CollapsePanel from '@/pages/_components/collapse-panel';
|
||||||
|
import { useWrapperContext } from '@/pages/_components/column-wrapper/use-wrapper-context';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { Form, Segmented } from 'antd';
|
import { Form, Segmented } from 'antd';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
@@ -72,6 +73,13 @@ interface DataFormProps {
|
|||||||
onBackendChange?: (value: string) => void;
|
onBackendChange?: (value: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const TABKeysMap = {
|
||||||
|
BASIC: 'basic',
|
||||||
|
SCHEDULING: 'scheduling',
|
||||||
|
PERFORMANCE: 'performance',
|
||||||
|
ADVANCED: 'advanced'
|
||||||
|
};
|
||||||
|
|
||||||
const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
|
const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
|
||||||
const {
|
const {
|
||||||
action,
|
action,
|
||||||
@@ -89,6 +97,7 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
|
|||||||
onValuesChange,
|
onValuesChange,
|
||||||
onOk
|
onOk
|
||||||
} = props;
|
} = props;
|
||||||
|
const { getScrollElementScrollableHeight } = useWrapperContext();
|
||||||
const { backendOptions, getBackendOptions } = useQueryBackends();
|
const { backendOptions, getBackendOptions } = useQueryBackends();
|
||||||
const { getGPUOptionList, gpuOptions, workerLabelOptions } =
|
const { getGPUOptionList, gpuOptions, workerLabelOptions } =
|
||||||
useGenerateGPUOptions();
|
useGenerateGPUOptions();
|
||||||
@@ -96,14 +105,7 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
|
|||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const [activeKey, setActiveKey] = React.useState<string[]>([]);
|
const [activeKey, setActiveKey] = React.useState<string[]>([]);
|
||||||
const scheduleType = Form.useWatch('scheduleType', form);
|
const scheduleType = Form.useWatch('scheduleType', form);
|
||||||
const [target, setTarget] = React.useState<string>('basic');
|
const [target, setTarget] = React.useState<string>(TABKeysMap.BASIC);
|
||||||
|
|
||||||
const TABKeysMap = {
|
|
||||||
BASIC: 'basic',
|
|
||||||
SCHEDULING: 'scheduling',
|
|
||||||
PERFORMANCE: 'performance',
|
|
||||||
ADVANCED: 'advanced'
|
|
||||||
};
|
|
||||||
|
|
||||||
const segmentOptions = [
|
const segmentOptions = [
|
||||||
{
|
{
|
||||||
@@ -128,14 +130,12 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const { scrollToSegment } = useFieldScroll({
|
console.log(
|
||||||
form,
|
'getScrollElementScrollableHeight',
|
||||||
activeKey,
|
getScrollElementScrollableHeight?.()
|
||||||
setActiveKey,
|
);
|
||||||
segmentOptions
|
|
||||||
});
|
|
||||||
|
|
||||||
const SegmentedTop = useMemo(() => {
|
const segmentedTop = useMemo(() => {
|
||||||
if (
|
if (
|
||||||
modelSourceMap.local_path_value === source ||
|
modelSourceMap.local_path_value === source ||
|
||||||
action === PageAction.EDIT ||
|
action === PageAction.EDIT ||
|
||||||
@@ -153,6 +153,15 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
|
|||||||
};
|
};
|
||||||
}, [source, formKey, action]);
|
}, [source, formKey, action]);
|
||||||
|
|
||||||
|
const { scrollToSegment, holderHeight } = useFieldScroll({
|
||||||
|
form,
|
||||||
|
activeKey,
|
||||||
|
setActiveKey,
|
||||||
|
segmentOptions,
|
||||||
|
segmentedTop: segmentedTop,
|
||||||
|
getScrollElementScrollableHeight: getScrollElementScrollableHeight
|
||||||
|
});
|
||||||
|
|
||||||
const handleSumit = () => {
|
const handleSumit = () => {
|
||||||
form.submit();
|
form.submit();
|
||||||
};
|
};
|
||||||
@@ -288,7 +297,7 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
|
|||||||
const handleTargetChange = async (val: any) => {
|
const handleTargetChange = async (val: any) => {
|
||||||
setTarget(val);
|
setTarget(val);
|
||||||
|
|
||||||
await scrollToSegment(val, { offsetTop: SegmentedTop.offsetTop });
|
await scrollToSegment(val, { offsetTop: segmentedTop.offsetTop });
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleOnFinishFailed = (errorInfo: any) => {
|
const handleOnFinishFailed = (errorInfo: any) => {
|
||||||
@@ -389,7 +398,7 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
|
|||||||
onBackendChange: handleBackendChange
|
onBackendChange: handleBackendChange
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SegmentedHeader $top={SegmentedTop.top}>
|
<SegmentedHeader $top={segmentedTop.top}>
|
||||||
<SegmentedInner
|
<SegmentedInner
|
||||||
defaultValue="basic"
|
defaultValue="basic"
|
||||||
value={target}
|
value={target}
|
||||||
@@ -465,6 +474,7 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
|
|||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
></CollapsePanel>
|
></CollapsePanel>
|
||||||
|
<div className="holder" style={{ height: holderHeight }}></div>
|
||||||
</Form>
|
</Form>
|
||||||
</FormContext.Provider>
|
</FormContext.Provider>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -5,9 +5,10 @@ import SealSelect from '@/components/seal-form/seal-select';
|
|||||||
import TooltipList from '@/components/tooltip-list';
|
import TooltipList from '@/components/tooltip-list';
|
||||||
import useAppUtils from '@/hooks/use-app-utils';
|
import useAppUtils from '@/hooks/use-app-utils';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { Form } from 'antd';
|
import { Form, InputNumber } from 'antd';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
import GPUCard from '../components/gpu-card';
|
import GPUCard from '../components/gpu-card';
|
||||||
import {
|
import {
|
||||||
placementStrategyOptions,
|
placementStrategyOptions,
|
||||||
@@ -18,6 +19,10 @@ import { backendOptionsMap } from '../config/backend-parameters';
|
|||||||
import { useFormContext } from '../config/form-context';
|
import { useFormContext } from '../config/form-context';
|
||||||
import { FormData } from '../config/types';
|
import { FormData } from '../config/types';
|
||||||
|
|
||||||
|
const InputWrapper = styled.div`
|
||||||
|
padding: 8px 4px;
|
||||||
|
`;
|
||||||
|
|
||||||
const placementStrategyTips = [
|
const placementStrategyTips = [
|
||||||
{
|
{
|
||||||
title: 'Spread',
|
title: 'Spread',
|
||||||
@@ -70,6 +75,10 @@ const ScheduleTypeForm: React.FC = () => {
|
|||||||
const form = Form.useFormInstance();
|
const form = Form.useFormInstance();
|
||||||
const scheduleType = Form.useWatch('scheduleType', form);
|
const scheduleType = Form.useWatch('scheduleType', form);
|
||||||
const workerSelector = Form.useWatch('worker_selector', form);
|
const workerSelector = Form.useWatch('worker_selector', form);
|
||||||
|
const GPUsPerReplicas = Form.useWatch(
|
||||||
|
['gpu_selector', 'gpus_per_replica'],
|
||||||
|
form
|
||||||
|
);
|
||||||
|
|
||||||
const handleScheduleTypeChange = async (value: string) => {
|
const handleScheduleTypeChange = async (value: string) => {
|
||||||
if (value === ScheduleValueMap.Auto) {
|
if (value === ScheduleValueMap.Auto) {
|
||||||
@@ -81,6 +90,15 @@ const ScheduleTypeForm: React.FC = () => {
|
|||||||
form.setFieldValue(['gpu_selector', 'gpus_per_replica'], null);
|
form.setFieldValue(['gpu_selector', 'gpus_per_replica'], null);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
const handleGpusPerReplicasChange = (val: string | number | null) => {
|
||||||
|
if (val === null) {
|
||||||
|
form.setFieldValue(['gpu_selector', 'gpus_per_replica'], null);
|
||||||
|
} else {
|
||||||
|
form.setFieldValue(['gpu_selector', 'gpus_per_replica'], val);
|
||||||
|
}
|
||||||
|
|
||||||
|
onValuesChange?.({}, form.getFieldsValue());
|
||||||
|
};
|
||||||
|
|
||||||
const handleGpuSelectorChange = (value: any[]) => {
|
const handleGpuSelectorChange = (value: any[]) => {
|
||||||
if (value.length > 0) {
|
if (value.length > 0) {
|
||||||
@@ -191,15 +209,32 @@ const ScheduleTypeForm: React.FC = () => {
|
|||||||
{ label: '2', value: 2 },
|
{ label: '2', value: 2 },
|
||||||
{ label: '4', value: 4 },
|
{ label: '4', value: 4 },
|
||||||
{ label: '8', value: 8 },
|
{ label: '8', value: 8 },
|
||||||
{ label: '16', value: 16 },
|
{ label: '16', value: 16 }
|
||||||
{ label: '32', value: 32 },
|
|
||||||
{ label: '64', value: 64 },
|
|
||||||
{ label: '128', value: 128 },
|
|
||||||
{ label: '256', value: 256 }
|
|
||||||
]}
|
]}
|
||||||
description={
|
description={
|
||||||
<TooltipList list={GPUsPerReplicaTips}></TooltipList>
|
<TooltipList list={GPUsPerReplicaTips}></TooltipList>
|
||||||
}
|
}
|
||||||
|
popupRender={(originNode) => (
|
||||||
|
<div>
|
||||||
|
{originNode}
|
||||||
|
<InputWrapper>
|
||||||
|
<InputNumber
|
||||||
|
min={1}
|
||||||
|
max={16}
|
||||||
|
step={1}
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
defaultValue={
|
||||||
|
GPUsPerReplicas === -1 ? null : GPUsPerReplicas
|
||||||
|
}
|
||||||
|
placeholder={intl.formatMessage({
|
||||||
|
id: 'models.form.gpuPerReplica.tips'
|
||||||
|
})}
|
||||||
|
value={GPUsPerReplicas === -1 ? null : GPUsPerReplicas}
|
||||||
|
onChange={handleGpusPerReplicasChange}
|
||||||
|
/>
|
||||||
|
</InputWrapper>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useCallback } from 'react';
|
import { useMemoizedFn } from 'ahooks';
|
||||||
|
import { useCallback, useRef, useState } from 'react';
|
||||||
|
|
||||||
interface ScrollOptions {
|
interface ScrollOptions {
|
||||||
wait?: number;
|
wait?: number;
|
||||||
@@ -12,14 +13,27 @@ export default function useScrollAfterExpand({
|
|||||||
activeKey,
|
activeKey,
|
||||||
setActiveKey,
|
setActiveKey,
|
||||||
segmentOptions,
|
segmentOptions,
|
||||||
defaultWait = 300
|
defaultWait = 300,
|
||||||
|
segmentedTop = { top: 0, offsetTop: 96 },
|
||||||
|
getScrollElementScrollableHeight
|
||||||
}: {
|
}: {
|
||||||
form: any;
|
form: any;
|
||||||
activeKey: string[];
|
activeKey: string[];
|
||||||
setActiveKey: React.Dispatch<React.SetStateAction<string[]>>;
|
setActiveKey: React.Dispatch<React.SetStateAction<string[]>>;
|
||||||
segmentOptions: { value: string; field: string }[];
|
segmentOptions: { value: string; field: string }[];
|
||||||
|
getScrollElementScrollableHeight?: () => {
|
||||||
|
scrollHeight: number;
|
||||||
|
scrollTop: number;
|
||||||
|
};
|
||||||
defaultWait?: number;
|
defaultWait?: number;
|
||||||
|
segmentedTop: {
|
||||||
|
top: number;
|
||||||
|
offsetTop: number;
|
||||||
|
};
|
||||||
}) {
|
}) {
|
||||||
|
const [holderHeight, setHolderHeight] = useState<number>(0);
|
||||||
|
const boxHeightRef = useRef<number>(0);
|
||||||
|
|
||||||
const scrollToElement = useCallback(
|
const scrollToElement = useCallback(
|
||||||
(
|
(
|
||||||
el: HTMLElement,
|
el: HTMLElement,
|
||||||
@@ -46,7 +60,11 @@ export default function useScrollAfterExpand({
|
|||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
const scrollToSegment = useCallback(
|
/**
|
||||||
|
* due to the scrollheight changes after expanding the segment and including the holder height.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
const scrollToSegment = useMemoizedFn(
|
||||||
async (val: string, options?: ScrollOptions) => {
|
async (val: string, options?: ScrollOptions) => {
|
||||||
if (!activeKey.includes(val)) {
|
if (!activeKey.includes(val)) {
|
||||||
setActiveKey((prev) => [...prev, val]);
|
setActiveKey((prev) => [...prev, val]);
|
||||||
@@ -55,19 +73,48 @@ export default function useScrollAfterExpand({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await new Promise(requestAnimationFrame);
|
|
||||||
|
|
||||||
const current = segmentOptions.find((item) => item.value === val);
|
const current = segmentOptions.find((item) => item.value === val);
|
||||||
if (!current?.field) return;
|
if (!current?.field) return;
|
||||||
|
|
||||||
const el = document.querySelector(
|
await new Promise(requestAnimationFrame);
|
||||||
|
|
||||||
|
const el: HTMLElement | null = document.querySelector(
|
||||||
`[data-field="${current.field}"]`
|
`[data-field="${current.field}"]`
|
||||||
) as HTMLElement | null;
|
) as HTMLElement | null;
|
||||||
|
|
||||||
|
const targetRectTop = el?.getBoundingClientRect().top || 0;
|
||||||
|
|
||||||
|
const scroller = getScrollElementScrollableHeight?.() || {
|
||||||
|
scrollHeight: 0,
|
||||||
|
scrollTop: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
// remaining scroll height
|
||||||
|
const remainingScrollHeight = scroller.scrollHeight - scroller.scrollTop;
|
||||||
|
|
||||||
|
// total distance from the top of the scroller to the target element
|
||||||
|
const offsetDistance =
|
||||||
|
targetRectTop - segmentedTop.offsetTop - segmentedTop.top;
|
||||||
|
|
||||||
|
let boxHeight = 0;
|
||||||
|
|
||||||
|
// verify boxHeight is correct, if setting the boxHeight causes the element to be hidden, use the previous boxHeight
|
||||||
|
if (offsetDistance <= 0) {
|
||||||
|
boxHeight = boxHeightRef.current;
|
||||||
|
} else {
|
||||||
|
boxHeight =
|
||||||
|
offsetDistance - remainingScrollHeight + boxHeightRef.current;
|
||||||
|
}
|
||||||
|
|
||||||
|
// update boxHeightRef
|
||||||
|
boxHeightRef.current = boxHeight;
|
||||||
|
|
||||||
|
setHolderHeight(boxHeight);
|
||||||
|
await new Promise(requestAnimationFrame);
|
||||||
|
|
||||||
if (el) scrollToElement(el, options);
|
if (el) scrollToElement(el, options);
|
||||||
},
|
}
|
||||||
[activeKey, setActiveKey, segmentOptions, defaultWait, scrollToElement]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return { scrollToSegment };
|
return { scrollToSegment, holderHeight };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,11 +17,11 @@ export default function useQueryBackends() {
|
|||||||
value: item.backend_name,
|
value: item.backend_name,
|
||||||
label:
|
label:
|
||||||
item.backend_name === backendOptionsMap.custom
|
item.backend_name === backendOptionsMap.custom
|
||||||
? intl.formatMessage({ id: 'backend.quickConfig' })
|
? intl.formatMessage({ id: 'backend.custom' })
|
||||||
: item.backend_name,
|
: item.backend_name,
|
||||||
title:
|
title:
|
||||||
item.backend_name === backendOptionsMap.custom
|
item.backend_name === backendOptionsMap.custom
|
||||||
? intl.formatMessage({ id: 'backend.quickConfig' })
|
? intl.formatMessage({ id: 'backend.custom' })
|
||||||
: item.backend_name.replace(/-custom$/, ''),
|
: item.backend_name.replace(/-custom$/, ''),
|
||||||
default_backend_param: item.default_backend_param || [],
|
default_backend_param: item.default_backend_param || [],
|
||||||
default_version: item.default_version,
|
default_version: item.default_version,
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
import HighlightCode from '@/components/highlight-code';
|
||||||
|
import { useIntl } from '@umijs/max';
|
||||||
|
import React, { useMemo } from 'react';
|
||||||
|
import { addWorkerGuide } from '../config';
|
||||||
|
|
||||||
|
type ViewModalProps = { token: string };
|
||||||
|
|
||||||
|
const AddWorker: React.FC<ViewModalProps> = (props) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
|
const origin = window.location.origin;
|
||||||
|
|
||||||
|
const labels = useMemo(
|
||||||
|
() => ({
|
||||||
|
step1: intl.formatMessage({ id: 'resources.worker.add.step1' }),
|
||||||
|
step2: intl.formatMessage({ id: 'resources.worker.add.step2' }),
|
||||||
|
step2Tips: intl.formatMessage({ id: 'resources.worker.add.step2.tips' }),
|
||||||
|
step3: intl.formatMessage({ id: 'resources.worker.add.step3' }),
|
||||||
|
linuxOrMac: intl.formatMessage({ id: 'resources.worker.linuxormaxos' })
|
||||||
|
}),
|
||||||
|
[intl]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="script-install">
|
||||||
|
<h3 className="font-size-14 font-600">
|
||||||
|
1. <span dangerouslySetInnerHTML={{ __html: labels.step1 }}></span>
|
||||||
|
</h3>
|
||||||
|
<h4 className="font-size-13">{labels.linuxOrMac}</h4>
|
||||||
|
<HighlightCode
|
||||||
|
code={addWorkerGuide.mac.getToken}
|
||||||
|
theme="dark"
|
||||||
|
></HighlightCode>
|
||||||
|
<h4 className="m-t-6 font-size-13">Windows </h4>
|
||||||
|
<HighlightCode
|
||||||
|
code={addWorkerGuide.win.getToken}
|
||||||
|
theme="dark"
|
||||||
|
></HighlightCode>
|
||||||
|
<h3 className="m-t-10 font-size-14 font-600">
|
||||||
|
2. {labels.step2}{' '}
|
||||||
|
<span
|
||||||
|
className="font-size-12"
|
||||||
|
style={{ color: 'var(--ant-color-text-tertiary)' }}
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: `${labels.step2Tips}`
|
||||||
|
}}
|
||||||
|
></span>
|
||||||
|
</h3>
|
||||||
|
<h4 className="font-size-13">{labels.linuxOrMac}</h4>
|
||||||
|
<HighlightCode
|
||||||
|
code={addWorkerGuide.mac.registerWorker({
|
||||||
|
server: origin,
|
||||||
|
token: '${token}'
|
||||||
|
})}
|
||||||
|
theme="dark"
|
||||||
|
></HighlightCode>
|
||||||
|
<h4 className="m-t-6 font-size-13">Windows</h4>
|
||||||
|
<HighlightCode
|
||||||
|
theme="dark"
|
||||||
|
code={addWorkerGuide.win.registerWorker({
|
||||||
|
server: origin,
|
||||||
|
token: '${token}'
|
||||||
|
})}
|
||||||
|
></HighlightCode>
|
||||||
|
<h3 className="m-b-0 m-t-10 font-size-14 font-600">3. {labels.step3}</h3>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AddWorker;
|
||||||
Reference in New Issue
Block a user