fix: check form data after blur
This commit is contained in:
@@ -5,6 +5,7 @@ import ClipboardJS from 'clipboard';
|
|||||||
import React, { useEffect, useRef, useState } from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
|
|
||||||
type CopyButtonProps = {
|
type CopyButtonProps = {
|
||||||
|
children?: React.ReactNode;
|
||||||
text: string;
|
text: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
fontSize?: string;
|
fontSize?: string;
|
||||||
@@ -26,6 +27,7 @@ type CopyButtonProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const CopyButton: React.FC<CopyButtonProps> = ({
|
const CopyButton: React.FC<CopyButtonProps> = ({
|
||||||
|
children,
|
||||||
tips,
|
tips,
|
||||||
text,
|
text,
|
||||||
disabled,
|
disabled,
|
||||||
@@ -91,7 +93,9 @@ const CopyButton: React.FC<CopyButtonProps> = ({
|
|||||||
<CopyOutlined style={{ fontSize: fontSize, ...style }} />
|
<CopyOutlined style={{ fontSize: fontSize, ...style }} />
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
></Button>
|
>
|
||||||
|
{children}
|
||||||
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,11 +9,13 @@ interface LabelSelectorProps {
|
|||||||
btnText?: string;
|
btnText?: string;
|
||||||
description?: React.ReactNode;
|
description?: React.ReactNode;
|
||||||
onChange?: (labels: Record<string, any>) => void;
|
onChange?: (labels: Record<string, any>) => void;
|
||||||
|
onBlur?: (e: any, type: string, index: number) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const LabelSelector: React.FC<LabelSelectorProps> = ({
|
const LabelSelector: React.FC<LabelSelectorProps> = ({
|
||||||
labels,
|
labels,
|
||||||
onChange,
|
onChange,
|
||||||
|
onBlur,
|
||||||
label,
|
label,
|
||||||
btnText,
|
btnText,
|
||||||
description
|
description
|
||||||
@@ -87,6 +89,7 @@ const LabelSelector: React.FC<LabelSelectorProps> = ({
|
|||||||
onChange={handleLabelsChange}
|
onChange={handleLabelsChange}
|
||||||
onLabelListChange={handleLabelListChange}
|
onLabelListChange={handleLabelListChange}
|
||||||
onPaste={handleOnPaste}
|
onPaste={handleOnPaste}
|
||||||
|
onBlur={onBlur}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ interface LabelSelectorProps {
|
|||||||
onLabelListChange: (list: { key: string; value: string }[]) => void;
|
onLabelListChange: (list: { key: string; value: string }[]) => void;
|
||||||
onChange?: (labels: Record<string, any>) => void;
|
onChange?: (labels: Record<string, any>) => void;
|
||||||
onPaste?: (e: any, index: number) => void;
|
onPaste?: (e: any, index: number) => void;
|
||||||
|
onBlur?: (e: any, type: string, index: number) => void;
|
||||||
description?: React.ReactNode;
|
description?: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22,6 +23,7 @@ const Inner: React.FC<LabelSelectorProps> = ({
|
|||||||
onChange,
|
onChange,
|
||||||
onLabelListChange,
|
onLabelListChange,
|
||||||
onPaste,
|
onPaste,
|
||||||
|
onBlur,
|
||||||
label,
|
label,
|
||||||
btnText,
|
btnText,
|
||||||
description
|
description
|
||||||
@@ -82,6 +84,7 @@ const Inner: React.FC<LabelSelectorProps> = ({
|
|||||||
onDelete={() => handleOnDelete(index)}
|
onDelete={() => handleOnDelete(index)}
|
||||||
onChange={(obj) => handleOnChange(index, obj)}
|
onChange={(obj) => handleOnChange(index, obj)}
|
||||||
onPaste={(e) => onPaste?.(e, index)}
|
onPaste={(e) => onPaste?.(e, index)}
|
||||||
|
onBlur={(e: any, type: string) => onBlur?.(e, type, index)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ interface LabelItemProps {
|
|||||||
labelList: { key: string; value: string }[];
|
labelList: { key: string; value: string }[];
|
||||||
onChange?: (params: { key: string; value: string }) => void;
|
onChange?: (params: { key: string; value: string }) => void;
|
||||||
onPaste?: (e: any) => void;
|
onPaste?: (e: any) => void;
|
||||||
|
onBlur?: (e: any, type: string) => void;
|
||||||
}
|
}
|
||||||
const LabelItem: React.FC<LabelItemProps> = ({
|
const LabelItem: React.FC<LabelItemProps> = ({
|
||||||
label,
|
label,
|
||||||
@@ -28,7 +29,8 @@ const LabelItem: React.FC<LabelItemProps> = ({
|
|||||||
valueAddon,
|
valueAddon,
|
||||||
onChange,
|
onChange,
|
||||||
onDelete,
|
onDelete,
|
||||||
onPaste
|
onPaste,
|
||||||
|
onBlur
|
||||||
}) => {
|
}) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
@@ -49,7 +51,7 @@ const LabelItem: React.FC<LabelItemProps> = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleKeyOnBlur = (e: any) => {
|
const handleKeyOnBlur = (e: any, type: string) => {
|
||||||
const val = e.target.value;
|
const val = e.target.value;
|
||||||
// has duplicate key
|
// has duplicate key
|
||||||
const duplicates = _.filter(
|
const duplicates = _.filter(
|
||||||
@@ -68,6 +70,7 @@ const LabelItem: React.FC<LabelItemProps> = ({
|
|||||||
} else {
|
} else {
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
}
|
}
|
||||||
|
onBlur?.(e, type);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -83,7 +86,7 @@ const LabelItem: React.FC<LabelItemProps> = ({
|
|||||||
label={intl.formatMessage({ id: 'common.input.key' })}
|
label={intl.formatMessage({ id: 'common.input.key' })}
|
||||||
value={label.key}
|
value={label.key}
|
||||||
onChange={handleOnKeyChange}
|
onChange={handleOnKeyChange}
|
||||||
onBlur={handleKeyOnBlur}
|
onBlur={(e: any) => handleKeyOnBlur(e, 'key')}
|
||||||
onPaste={onPaste}
|
onPaste={onPaste}
|
||||||
></SealInput.Input>
|
></SealInput.Input>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -97,6 +100,7 @@ const LabelItem: React.FC<LabelItemProps> = ({
|
|||||||
label={intl.formatMessage({ id: 'common.input.value' })}
|
label={intl.formatMessage({ id: 'common.input.value' })}
|
||||||
value={label.value}
|
value={label.value}
|
||||||
onChange={handleOnValueChange}
|
onChange={handleOnValueChange}
|
||||||
|
onBlur={(e: any) => onBlur?.(e, 'value')}
|
||||||
></SealInput.Input>
|
></SealInput.Input>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ interface HintInputProps {
|
|||||||
value: string;
|
value: string;
|
||||||
label?: string;
|
label?: string;
|
||||||
onChange: (value: string) => void;
|
onChange: (value: string) => void;
|
||||||
|
onBlur?: (e: any) => void;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
sourceOptions?: Global.HintOptions[];
|
sourceOptions?: Global.HintOptions[];
|
||||||
}
|
}
|
||||||
@@ -13,7 +14,7 @@ interface HintInputProps {
|
|||||||
const matchReg = /[^=]+=[^=]*$/;
|
const matchReg = /[^=]+=[^=]*$/;
|
||||||
|
|
||||||
const HintInput: React.FC<HintInputProps> = (props) => {
|
const HintInput: React.FC<HintInputProps> = (props) => {
|
||||||
const { value, label, onChange, sourceOptions } = props;
|
const { value, label, onChange, onBlur, sourceOptions } = props;
|
||||||
const cursorPosRef = React.useRef(0);
|
const cursorPosRef = React.useRef(0);
|
||||||
const contextBeforeCursorRef = React.useRef('');
|
const contextBeforeCursorRef = React.useRef('');
|
||||||
const [options, setOptions] = React.useState<
|
const [options, setOptions] = React.useState<
|
||||||
@@ -89,6 +90,7 @@ const HintInput: React.FC<HintInputProps> = (props) => {
|
|||||||
onInput={handleInput}
|
onInput={handleInput}
|
||||||
onSelect={handleOnSelect}
|
onSelect={handleOnSelect}
|
||||||
onFocus={getContextBeforeCursor}
|
onFocus={getContextBeforeCursor}
|
||||||
|
onBlur={onBlur}
|
||||||
label={label}
|
label={label}
|
||||||
options={options}
|
options={options}
|
||||||
style={{ width: '100%' }}
|
style={{ width: '100%' }}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ interface ListInputProps {
|
|||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
labelExtra?: React.ReactNode;
|
labelExtra?: React.ReactNode;
|
||||||
onChange: (data: string[]) => void;
|
onChange: (data: string[]) => void;
|
||||||
|
onBlur?: (e: any, index: number) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ListInput: React.FC<ListInputProps> = (props) => {
|
const ListInput: React.FC<ListInputProps> = (props) => {
|
||||||
@@ -24,6 +25,7 @@ const ListInput: React.FC<ListInputProps> = (props) => {
|
|||||||
label,
|
label,
|
||||||
description,
|
description,
|
||||||
onChange,
|
onChange,
|
||||||
|
onBlur,
|
||||||
btnText,
|
btnText,
|
||||||
options,
|
options,
|
||||||
labelExtra
|
labelExtra
|
||||||
@@ -89,6 +91,7 @@ const ListInput: React.FC<ListInputProps> = (props) => {
|
|||||||
options={options}
|
options={options}
|
||||||
key={item.uid}
|
key={item.uid}
|
||||||
value={item.value}
|
value={item.value}
|
||||||
|
onBlur={(e) => onBlur?.(e, index)}
|
||||||
onRemove={() => handleOnRemove(index)}
|
onRemove={() => handleOnRemove(index)}
|
||||||
onChange={(val) => handleOnChange(val, index)}
|
onChange={(val) => handleOnChange(val, index)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import './styles/list-item.less';
|
|||||||
interface LabelItemProps {
|
interface LabelItemProps {
|
||||||
onRemove: () => void;
|
onRemove: () => void;
|
||||||
onChange: (value: string) => void;
|
onChange: (value: string) => void;
|
||||||
|
onBlur?: (e: any) => void;
|
||||||
value: string;
|
value: string;
|
||||||
label?: string;
|
label?: string;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
@@ -15,7 +16,7 @@ interface LabelItemProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ListItem: React.FC<LabelItemProps> = (props) => {
|
const ListItem: React.FC<LabelItemProps> = (props) => {
|
||||||
const { onRemove, onChange, label, value, options } = props;
|
const { onRemove, onChange, onBlur, label, value, options } = props;
|
||||||
|
|
||||||
const handleOnChange = (value: any) => {
|
const handleOnChange = (value: any) => {
|
||||||
onChange(value);
|
onChange(value);
|
||||||
@@ -26,6 +27,7 @@ const ListItem: React.FC<LabelItemProps> = (props) => {
|
|||||||
<HintInput
|
<HintInput
|
||||||
value={value}
|
value={value}
|
||||||
onChange={handleOnChange}
|
onChange={handleOnChange}
|
||||||
|
onBlur={onBlur}
|
||||||
label={label}
|
label={label}
|
||||||
sourceOptions={options}
|
sourceOptions={options}
|
||||||
placeholder={props.placeholder}
|
placeholder={props.placeholder}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { AutoComplete, Form, Spin } from 'antd';
|
import { AutoComplete, Form, Spin } from 'antd';
|
||||||
import type { AutoCompleteProps } from 'antd/lib';
|
import type { AutoCompleteProps } from 'antd/lib';
|
||||||
import { useEffect, useRef, useState } from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
import Wrapper from './components/wrapper';
|
import Wrapper from './components/wrapper';
|
||||||
import { SealFormItemProps } from './types';
|
import { SealFormItemProps } from './types';
|
||||||
|
|
||||||
@@ -15,6 +15,7 @@ const SealAutoComplete: React.FC<
|
|||||||
isInFormItems = true,
|
isInFormItems = true,
|
||||||
trim = true,
|
trim = true,
|
||||||
onSelect,
|
onSelect,
|
||||||
|
onBlur,
|
||||||
extra,
|
extra,
|
||||||
style,
|
style,
|
||||||
addAfter,
|
addAfter,
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ export default {
|
|||||||
'models.catalog.release.date': 'Release Date',
|
'models.catalog.release.date': 'Release Date',
|
||||||
'models.localpath.gguf.tips.title': 'GGUF format model',
|
'models.localpath.gguf.tips.title': 'GGUF format model',
|
||||||
'models.localpat.safe.tips.title': 'Safetensors format model',
|
'models.localpat.safe.tips.title': 'Safetensors format model',
|
||||||
'models.localpath.shared.tips.title': 'Sharded GGUF Format Model',
|
'models.localpath.shared.tips.title': 'Sharded GGUF format model',
|
||||||
'models.localpath.gguf.tips':
|
'models.localpath.gguf.tips':
|
||||||
' Specify the model file, e.g., /data/models/model.gguf.',
|
' Specify the model file, e.g., /data/models/model.gguf.',
|
||||||
'models.localpath.safe.tips':
|
'models.localpath.safe.tips':
|
||||||
|
|||||||
@@ -13,10 +13,8 @@ export default {
|
|||||||
'models.form.env': '環境変数',
|
'models.form.env': '環境変数',
|
||||||
'models.form.configurations': '設定',
|
'models.form.configurations': '設定',
|
||||||
'models.form.s3address': 'S3アドレス',
|
'models.form.s3address': 'S3アドレス',
|
||||||
'models.form.partialoffload.tips':
|
'models.form.partialoffload.tips': `When CPU offloading is enabled, if GPU resources are insufficient, part of the model's layers will be offloaded to the CPU. If no GPU is available, full CPU inference will be used.`,
|
||||||
'CPUオフロードを有効にすると、GPUStackは可能な限り多くのレイヤーをGPUにロードしてパフォーマンスを最大化します。GPUリソースが制限されている場合、一部のレイヤーがCPUにオフロードされ、GPUが利用できない場合は完全にCPU推論が使用されます。',
|
'models.form.distribution.tips': `Allows for offloading part of the model's layers to single or multiple remote workers when the resources of a worker are insufficient.`,
|
||||||
'models.form.distribution.tips':
|
|
||||||
'単一のGPUまたはワーカーのリソースが不足している場合、計算の一部を単一または複数のリモートワーカーにオフロードすることを許可します。',
|
|
||||||
'models.openinplayground': 'プレイグラウンドで開く',
|
'models.openinplayground': 'プレイグラウンドで開く',
|
||||||
'models.instances': 'インスタンス',
|
'models.instances': 'インスタンス',
|
||||||
'models.table.replicas.edit': 'レプリカを編集',
|
'models.table.replicas.edit': 'レプリカを編集',
|
||||||
@@ -137,4 +135,6 @@ export default {
|
|||||||
// 6. 'models.form.restart.onerror',
|
// 6. 'models.form.restart.onerror',
|
||||||
// 7. 'models.form.restart.onerror.tips',
|
// 7. 'models.form.restart.onerror.tips',
|
||||||
// 8. 'models.form.check.params',
|
// 8. 'models.form.check.params',
|
||||||
|
// 9. 'models.form.partialoffload.tips',
|
||||||
|
// 10. 'models.form.distribution.tips
|
||||||
// ========== End of To-Do List ==========
|
// ========== End of To-Do List ==========
|
||||||
|
|||||||
+50
-26
@@ -13,8 +13,8 @@ export default {
|
|||||||
'models.form.env': 'Переменные окружения',
|
'models.form.env': 'Переменные окружения',
|
||||||
'models.form.configurations': 'Конфигурации',
|
'models.form.configurations': 'Конфигурации',
|
||||||
'models.form.s3address': 'S3-адрес',
|
'models.form.s3address': 'S3-адрес',
|
||||||
'models.form.partialoffload.tips': 'При включении CPU оффлоудинга GPUStack загружает максимум слоев на GPU для производительности. При нехватке ресурсов GPU часть слоев переносится на CPU. Полная CPU-инференция используется только при отсутствии GPU.',
|
'models.form.partialoffload.tips': `When CPU offloading is enabled, if GPU resources are insufficient, part of the model's layers will be offloaded to the CPU. If no GPU is available, full CPU inference will be used.`,
|
||||||
'models.form.distribution.tips': 'Позволяет распределить вычисления между одним или несколькими удаленными воркерами при нехватке ресурсов одного GPU/воркера.',
|
'models.form.distribution.tips': `Allows for offloading part of the model's layers to single or multiple remote workers when the resources of a worker are insufficient.`,
|
||||||
'models.openinplayground': 'Открыть в Песочнице',
|
'models.openinplayground': 'Открыть в Песочнице',
|
||||||
'models.instances': 'инстансы',
|
'models.instances': 'инстансы',
|
||||||
'models.table.replicas.edit': 'Редактировать реплики',
|
'models.table.replicas.edit': 'Редактировать реплики',
|
||||||
@@ -22,7 +22,8 @@ export default {
|
|||||||
'model.form.ollamaholder': 'Выберите или введите название модели',
|
'model.form.ollamaholder': 'Выберите или введите название модели',
|
||||||
'model.deploy.sort': 'Сортировка',
|
'model.deploy.sort': 'Сортировка',
|
||||||
'model.deploy.search.placeholder': 'Поиск моделей в {source}',
|
'model.deploy.search.placeholder': 'Поиск моделей в {source}',
|
||||||
'model.form.ollamatips': 'Подсказка: ниже представлены предустановленные модели Ollama в GPUStack. Выберите нужную или введите модель для развертывания в поле 【{name}】 справа.',
|
'model.form.ollamatips':
|
||||||
|
'Подсказка: ниже представлены предустановленные модели Ollama в GPUStack. Выберите нужную или введите модель для развертывания в поле 【{name}】 справа.',
|
||||||
'models.sort.name': 'По имени',
|
'models.sort.name': 'По имени',
|
||||||
'models.sort.size': 'По размеру',
|
'models.sort.size': 'По размеру',
|
||||||
'models.sort.likes': 'По лайкам',
|
'models.sort.likes': 'По лайкам',
|
||||||
@@ -39,13 +40,16 @@ export default {
|
|||||||
'models.search.nofiles': 'Нет доступных файлов',
|
'models.search.nofiles': 'Нет доступных файлов',
|
||||||
'models.search.networkerror': 'Ошибка сетевого подключения!',
|
'models.search.networkerror': 'Ошибка сетевого подключения!',
|
||||||
'models.search.hfvisit': 'Убедитесь, что доступен',
|
'models.search.hfvisit': 'Убедитесь, что доступен',
|
||||||
'models.search.unsupport': 'Модель не поддерживается и может быть нефункциональна после развертывания.',
|
'models.search.unsupport':
|
||||||
|
'Модель не поддерживается и может быть нефункциональна после развертывания.',
|
||||||
'models.form.scheduletype': 'Тип планирования',
|
'models.form.scheduletype': 'Тип планирования',
|
||||||
'models.form.categories': 'Категория модели',
|
'models.form.categories': 'Категория модели',
|
||||||
'models.form.scheduletype.auto': 'Авто',
|
'models.form.scheduletype.auto': 'Авто',
|
||||||
'models.form.scheduletype.manual': 'Вручную',
|
'models.form.scheduletype.manual': 'Вручную',
|
||||||
'models.form.scheduletype.auto.tips': 'Автоматическое развертывание инстансов модели на подходящие GPU/воркеры в зависимости от текущих ресурсов.',
|
'models.form.scheduletype.auto.tips':
|
||||||
'models.form.scheduletype.manual.tips': 'Позволяет вручную указать GPU/воркеры для развертывания инстансов модели.',
|
'Автоматическое развертывание инстансов модели на подходящие GPU/воркеры в зависимости от текущих ресурсов.',
|
||||||
|
'models.form.scheduletype.manual.tips':
|
||||||
|
'Позволяет вручную указать GPU/воркеры для развертывания инстансов модели.',
|
||||||
'models.form.manual.schedule': 'Ручное распределение',
|
'models.form.manual.schedule': 'Ручное распределение',
|
||||||
'models.table.gpuindex': 'Индекс GPU',
|
'models.table.gpuindex': 'Индекс GPU',
|
||||||
'models.table.backend': 'Бэкенды',
|
'models.table.backend': 'Бэкенды',
|
||||||
@@ -54,13 +58,19 @@ export default {
|
|||||||
'models.table.layers': 'Слои',
|
'models.table.layers': 'Слои',
|
||||||
'models.form.backend': 'Бэкенд',
|
'models.form.backend': 'Бэкенд',
|
||||||
'models.form.backend_parameters': 'Параметры бэкенда',
|
'models.form.backend_parameters': 'Параметры бэкенда',
|
||||||
'models.search.gguf.tips': 'GGUF-модели используют llama-box (поддерживает Linux, macOS и Windows).',
|
'models.search.gguf.tips':
|
||||||
'models.search.vllm.tips': 'Не-GGUF модели используют vox-box для аудио и vLLM (только x86 Linux) для остальных.',
|
'GGUF-модели используют llama-box (поддерживает Linux, macOS и Windows).',
|
||||||
'models.search.voxbox.tips': 'Для развертывания аудиомодели снимите отметку GGUF.',
|
'models.search.vllm.tips':
|
||||||
|
'Не-GGUF модели используют vox-box для аудио и vLLM (только x86 Linux) для остальных.',
|
||||||
|
'models.search.voxbox.tips':
|
||||||
|
'Для развертывания аудиомодели снимите отметку GGUF.',
|
||||||
'models.form.ollamalink': 'Больше моделей в библиотеке Ollama',
|
'models.form.ollamalink': 'Больше моделей в библиотеке Ollama',
|
||||||
'models.form.backend_parameters.llamabox.placeholder': 'например: --ctx-size=8192',
|
'models.form.backend_parameters.llamabox.placeholder':
|
||||||
'models.form.backend_parameters.vllm.placeholder': 'например: --max-model-len=8192',
|
'например: --ctx-size=8192',
|
||||||
'models.form.backend_parameters.vllm.tips': 'Подробнее о параметрах {backend}',
|
'models.form.backend_parameters.vllm.placeholder':
|
||||||
|
'например: --max-model-len=8192',
|
||||||
|
'models.form.backend_parameters.vllm.tips':
|
||||||
|
'Подробнее о параметрах {backend}',
|
||||||
'models.logs.pagination.prev': 'Предыдущие {lines} строк',
|
'models.logs.pagination.prev': 'Предыдущие {lines} строк',
|
||||||
'models.logs.pagination.next': 'Следующие {lines} строк',
|
'models.logs.pagination.next': 'Следующие {lines} строк',
|
||||||
'models.logs.pagination.last': 'Последняя страница',
|
'models.logs.pagination.last': 'Последняя страница',
|
||||||
@@ -68,12 +78,15 @@ export default {
|
|||||||
'models.form.localPath': 'Локальный путь',
|
'models.form.localPath': 'Локальный путь',
|
||||||
'models.form.filePath': 'Путь к модели',
|
'models.form.filePath': 'Путь к модели',
|
||||||
'models.form.backendVersion': 'Версия бэкенда',
|
'models.form.backendVersion': 'Версия бэкенда',
|
||||||
'models.form.backendVersion.tips': 'Чтобы использовать желаемую версию {backend}, система автоматически создаст виртуальную среду в онлайн-окружении для установки соответствующей версии. После обновления GPUStack версия бэкенда останется зафиксированной. {link}',
|
'models.form.backendVersion.tips':
|
||||||
|
'Чтобы использовать желаемую версию {backend}, система автоматически создаст виртуальную среду в онлайн-окружении для установки соответствующей версии. После обновления GPUStack версия бэкенда останется зафиксированной. {link}',
|
||||||
'models.form.gpuselector': 'Селектор GPU',
|
'models.form.gpuselector': 'Селектор GPU',
|
||||||
'models.form.backend.llamabox': 'Для моделей формата GGUF. Поддержка Linux, macOS и Windows.',
|
'models.form.backend.llamabox':
|
||||||
|
'Для моделей формата GGUF. Поддержка Linux, macOS и Windows.',
|
||||||
'models.form.backend.vllm': 'Для моделей не-GGUF формата. Только x86 Linux.',
|
'models.form.backend.vllm': 'Для моделей не-GGUF формата. Только x86 Linux.',
|
||||||
'models.form.backend.voxbox': 'Для аудиомоделей не-GGUF формата.',
|
'models.form.backend.voxbox': 'Для аудиомоделей не-GGUF формата.',
|
||||||
'models.form.search.gguftips': 'Для воркеров на macOS/Windows отметьте GGUF (для аудиомоделей снимите).',
|
'models.form.search.gguftips':
|
||||||
|
'Для воркеров на macOS/Windows отметьте GGUF (для аудиомоделей снимите).',
|
||||||
'models.form.button.addlabel': 'Добавить метку',
|
'models.form.button.addlabel': 'Добавить метку',
|
||||||
'models.filter.category': 'Фильтр по категориям',
|
'models.filter.category': 'Фильтр по категориям',
|
||||||
'models.list.more.logs': 'Показать больше',
|
'models.list.more.logs': 'Показать больше',
|
||||||
@@ -81,31 +94,42 @@ export default {
|
|||||||
'models.localpath.gguf.tips.title': 'Модель формата GGUF',
|
'models.localpath.gguf.tips.title': 'Модель формата GGUF',
|
||||||
'models.localpat.safe.tips.title': 'Модель формата Safetensors',
|
'models.localpat.safe.tips.title': 'Модель формата Safetensors',
|
||||||
'models.localpath.shared.tips.title': 'Шардированная GGUF-модель',
|
'models.localpath.shared.tips.title': 'Шардированная GGUF-модель',
|
||||||
'models.localpath.gguf.tips': 'Укажите файл модели, например: /data/models/model.gguf.',
|
'models.localpath.gguf.tips':
|
||||||
'models.localpath.safe.tips': 'Укажите директорию модели с файлами .safetensors и config.json.',
|
'Укажите файл модели, например: /data/models/model.gguf.',
|
||||||
'models.localpath.chunks.tips': 'Укажите первый шард модели, например: /data/models/model-00001-of-00004.gguf.',
|
'models.localpath.safe.tips':
|
||||||
'models.form.replicas.tips': 'Несколько реплик обеспечивают балансировку нагрузки для { api } запросов.',
|
'Укажите директорию модели с файлами .safetensors и config.json.',
|
||||||
|
'models.localpath.chunks.tips':
|
||||||
|
'Укажите первый шард модели, например: /data/models/model-00001-of-00004.gguf.',
|
||||||
|
'models.form.replicas.tips':
|
||||||
|
'Несколько реплик обеспечивают балансировку нагрузки для { api } запросов.',
|
||||||
'models.table.list.empty': 'Модели отсутствуют!',
|
'models.table.list.empty': 'Модели отсутствуют!',
|
||||||
'models.table.list.getStart': '<span style="margin-right: 5px;font-size: 13px;">Начните работу с</span> <span style="font-size: 14px;font-weight: 700">DeepSeek-R1-Distill-Qwen-1.5B</span>',
|
'models.table.list.getStart':
|
||||||
|
'<span style="margin-right: 5px;font-size: 13px;">Начните работу с</span> <span style="font-size: 14px;font-weight: 700">DeepSeek-R1-Distill-Qwen-1.5B</span>',
|
||||||
'models.table.llamaAcrossworker': 'Llama-box между воркерами',
|
'models.table.llamaAcrossworker': 'Llama-box между воркерами',
|
||||||
'models.table.vllmAcrossworker': 'vLLM между воркерами',
|
'models.table.vllmAcrossworker': 'vLLM между воркерами',
|
||||||
'models.form.releases': 'Релизы',
|
'models.form.releases': 'Релизы',
|
||||||
'models.form.moreparameters': 'Описание параметров',
|
'models.form.moreparameters': 'Описание параметров',
|
||||||
'models.table.vram.allocated': 'Выделенная VRAM',
|
'models.table.vram.allocated': 'Выделенная VRAM',
|
||||||
'models.form.backend.warning': 'Бэкенд для моделей формата GGUF использует llama-box.',
|
'models.form.backend.warning':
|
||||||
'models.form.ollama.warning': 'Чтобы развернуть бэкенд для моделей Ollama с использованием llama-box , выполните следующие шаги.',
|
'Бэкенд для моделей формата GGUF использует llama-box.',
|
||||||
'models.form.backend.warning.llamabox': 'Чтобы использовать бэкенд llama-box , укажите полный путь к файлу модели (например,<span style="font-weight: 700">/data/models/model.gguf</span>). Для шардированных моделей укажите путь к первому шарду (например,<span style="font-weight: 700">/data/models/model-00001-of-00004.gguf</span>).',
|
'models.form.ollama.warning':
|
||||||
'models.form.keyvalue.paste': 'Вставьте несколько строк текста, где каждая строка содержит пару ключ-значение. Ключ и значение разделяются знаком равенства (=), а разные пары — символами новой строки.',
|
'Чтобы развернуть бэкенд для моделей Ollama с использованием llama-box , выполните следующие шаги.',
|
||||||
|
'models.form.backend.warning.llamabox':
|
||||||
|
'Чтобы использовать бэкенд llama-box , укажите полный путь к файлу модели (например,<span style="font-weight: 700">/data/models/model.gguf</span>). Для шардированных моделей укажите путь к первому шарду (например,<span style="font-weight: 700">/data/models/model-00001-of-00004.gguf</span>).',
|
||||||
|
'models.form.keyvalue.paste':
|
||||||
|
'Вставьте несколько строк текста, где каждая строка содержит пару ключ-значение. Ключ и значение разделяются знаком равенства (=), а разные пары — символами новой строки.',
|
||||||
'models.form.files': 'файлы',
|
'models.form.files': 'файлы',
|
||||||
'models.table.status': 'Статус',
|
'models.table.status': 'Статус',
|
||||||
'models.form.submit.anyway': 'Отправить в любом случае',
|
'models.form.submit.anyway': 'Отправить в любом случае',
|
||||||
'models.form.evaluating': 'Анализ совместимости модели',
|
'models.form.evaluating': 'Анализ совместимости модели',
|
||||||
'models.form.incompatible': 'Обнаружена несовместимость',
|
'models.form.incompatible': 'Обнаружена несовместимость',
|
||||||
'models.form.restart.onerror': 'Автоперезапуск при ошибке',
|
'models.form.restart.onerror': 'Автоперезапуск при ошибке',
|
||||||
'models.form.restart.onerror.tips': 'При возникновении ошибки система автоматически попытается перезапуститься.',
|
'models.form.restart.onerror.tips':
|
||||||
|
'При возникновении ошибки система автоматически попытается перезапуститься.',
|
||||||
'models.form.check.params': 'Проверка конфигурации...'
|
'models.form.check.params': 'Проверка конфигурации...'
|
||||||
};
|
};
|
||||||
|
|
||||||
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
||||||
|
// 1. 'models.form.partialoffload.tips',
|
||||||
|
// 2. 'models.form.distribution.tips
|
||||||
// ========== End of To-Do List ==========
|
// ========== End of To-Do List ==========
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import {
|
|||||||
modelCategories,
|
modelCategories,
|
||||||
placementStrategyOptions
|
placementStrategyOptions
|
||||||
} from '../config';
|
} from '../config';
|
||||||
|
import { useFormContext } from '../config/form-context';
|
||||||
import llamaConfig from '../config/llama-config';
|
import llamaConfig from '../config/llama-config';
|
||||||
import { FormData } from '../config/types';
|
import { FormData } from '../config/types';
|
||||||
import vllmConfig from '../config/vllm-config';
|
import vllmConfig from '../config/vllm-config';
|
||||||
@@ -73,6 +74,7 @@ const AdvanceConfig: React.FC<AdvanceConfigProps> = (props) => {
|
|||||||
const placement_strategy = Form.useWatch('placement_strategy', form);
|
const placement_strategy = Form.useWatch('placement_strategy', form);
|
||||||
const gpuSelectorIds = Form.useWatch(['gpu_selector', 'gpu_ids'], form);
|
const gpuSelectorIds = Form.useWatch(['gpu_selector', 'gpu_ids'], form);
|
||||||
const worker_selector = Form.useWatch('worker_selector', form);
|
const worker_selector = Form.useWatch('worker_selector', form);
|
||||||
|
const { onValuesChange } = useFormContext();
|
||||||
|
|
||||||
const placementStrategyTips = [
|
const placementStrategyTips = [
|
||||||
{
|
{
|
||||||
@@ -153,6 +155,37 @@ const AdvanceConfig: React.FC<AdvanceConfigProps> = (props) => {
|
|||||||
form.setFieldValue('backend_parameters', list);
|
form.setFieldValue('backend_parameters', list);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const handleBackendParametersOnBlur = () => {
|
||||||
|
const backendParams = form.getFieldValue('backend_parameters');
|
||||||
|
console.log('backendParams==', backendParams);
|
||||||
|
onValuesChange?.({
|
||||||
|
source: source,
|
||||||
|
allValues: form.getFieldsValue(),
|
||||||
|
changedValues: {}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSelectorOnBlur = () => {
|
||||||
|
const workerSelector = form.getFieldValue('worker_selector');
|
||||||
|
console.log('workerSelector==', workerSelector);
|
||||||
|
onValuesChange?.({
|
||||||
|
source: source,
|
||||||
|
allValues: form.getFieldsValue(),
|
||||||
|
changedValues: {}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleBackendVersionOnBlur = () => {
|
||||||
|
const backendVersion = form.getFieldValue('backend_version');
|
||||||
|
if (backendVersion) {
|
||||||
|
onValuesChange?.({
|
||||||
|
source: source,
|
||||||
|
allValues: form.getFieldsValue(),
|
||||||
|
changedValues: {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const collapseItems = useMemo(() => {
|
const collapseItems = useMemo(() => {
|
||||||
const children = (
|
const children = (
|
||||||
<>
|
<>
|
||||||
@@ -233,6 +266,7 @@ const AdvanceConfig: React.FC<AdvanceConfigProps> = (props) => {
|
|||||||
})}
|
})}
|
||||||
labels={wokerSelector}
|
labels={wokerSelector}
|
||||||
onChange={handleWorkerLabelsChange}
|
onChange={handleWorkerLabelsChange}
|
||||||
|
onBlur={handleSelectorOnBlur}
|
||||||
description={
|
description={
|
||||||
<span>
|
<span>
|
||||||
{intl.formatMessage({
|
{intl.formatMessage({
|
||||||
@@ -275,6 +309,7 @@ const AdvanceConfig: React.FC<AdvanceConfigProps> = (props) => {
|
|||||||
|
|
||||||
<Form.Item name="backend_version">
|
<Form.Item name="backend_version">
|
||||||
<SealInput.Input
|
<SealInput.Input
|
||||||
|
onBlur={handleBackendVersionOnBlur}
|
||||||
label={intl.formatMessage({ id: 'models.form.backendVersion' })}
|
label={intl.formatMessage({ id: 'models.form.backendVersion' })}
|
||||||
description={intl.formatMessage(
|
description={intl.formatMessage(
|
||||||
{
|
{
|
||||||
@@ -324,6 +359,7 @@ const AdvanceConfig: React.FC<AdvanceConfigProps> = (props) => {
|
|||||||
})}
|
})}
|
||||||
dataList={form.getFieldValue('backend_parameters') || []}
|
dataList={form.getFieldValue('backend_parameters') || []}
|
||||||
onChange={handleBackendParametersChange}
|
onChange={handleBackendParametersChange}
|
||||||
|
onBlur={handleBackendParametersOnBlur}
|
||||||
options={paramsConfig}
|
options={paramsConfig}
|
||||||
description={
|
description={
|
||||||
backendParamsTips && (
|
backendParamsTips && (
|
||||||
|
|||||||
@@ -132,16 +132,11 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// voxbox is not support multi gpu
|
||||||
const handleSetGPUIds = (backend: string) => {
|
const handleSetGPUIds = (backend: string) => {
|
||||||
if (backend === backendOptionsMap.llamaBox) {
|
const gpuids = form.getFieldValue(['gpu_selector', 'gpu_ids']) || [];
|
||||||
return;
|
|
||||||
}
|
|
||||||
const gpuids = form.getFieldValue(['gpu_selector', 'gpu_ids']);
|
|
||||||
|
|
||||||
if (!gpuids?.length) {
|
if (backend === backendOptionsMap.voxBox && gpuids.length > 0) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (gpuids.length > 1 && Array.isArray(gpuids[0])) {
|
|
||||||
form.setFieldValue(['gpu_selector', 'gpu_ids'], [gpuids[0]]);
|
form.setFieldValue(['gpu_selector', 'gpu_ids'], [gpuids[0]]);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -77,9 +77,7 @@ const AddModal: FC<AddModalProps> = (props) => {
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
handleShowCompatibleAlert,
|
handleShowCompatibleAlert,
|
||||||
handleUpdateWarning,
|
|
||||||
setWarningStatus,
|
setWarningStatus,
|
||||||
handleEvaluate,
|
|
||||||
handleOnValuesChange,
|
handleOnValuesChange,
|
||||||
checkTokenRef,
|
checkTokenRef,
|
||||||
warningStatus,
|
warningStatus,
|
||||||
@@ -140,40 +138,25 @@ const AddModal: FC<AddModalProps> = (props) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// trigger from local_path change or backend change
|
|
||||||
const handleBackendChangeBefore = async () => {
|
|
||||||
const localPath = form.current.form.getFieldValue?.('local_path');
|
|
||||||
const backend = form.current.form.getFieldValue?.('backend');
|
|
||||||
|
|
||||||
const res = handleUpdateWarning?.({
|
|
||||||
backend,
|
|
||||||
localPath: localPath,
|
|
||||||
source: props.source
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!res.show) {
|
|
||||||
const values = form.current.form.getFieldsValue?.();
|
|
||||||
const data = getSourceRepoConfigValue(props.source, values);
|
|
||||||
const evalutionData = await handleEvaluate(
|
|
||||||
_.omit(data.values, [
|
|
||||||
'cpu_offloading',
|
|
||||||
'distributed_inference_across_workers'
|
|
||||||
])
|
|
||||||
);
|
|
||||||
handleShowCompatibleAlert?.(evalutionData);
|
|
||||||
} else {
|
|
||||||
setWarningStatus?.(res);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleBackendChange = async (backend: string) => {
|
const handleBackendChange = async (backend: string) => {
|
||||||
handleBackendChangeBefore();
|
|
||||||
if (backend === backendOptionsMap.vllm) {
|
|
||||||
setIsGGUF(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (backend === backendOptionsMap.llamaBox) {
|
if (backend === backendOptionsMap.llamaBox) {
|
||||||
setIsGGUF(true);
|
setIsGGUF(true);
|
||||||
|
} else {
|
||||||
|
setIsGGUF(false);
|
||||||
|
}
|
||||||
|
const data = form.current.form.getFieldsValue?.();
|
||||||
|
if (data.local_path || props.source !== modelSourceMap.local_path_value) {
|
||||||
|
handleOnValuesChange?.({
|
||||||
|
changedValues: {},
|
||||||
|
allValues:
|
||||||
|
backend === backendOptionsMap.llamaBox
|
||||||
|
? data
|
||||||
|
: _.omit(data, [
|
||||||
|
'cpu_offloading',
|
||||||
|
'distributed_inference_across_workers'
|
||||||
|
]),
|
||||||
|
source: props.source
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -291,7 +274,8 @@ const AddModal: FC<AddModalProps> = (props) => {
|
|||||||
<FormContext.Provider
|
<FormContext.Provider
|
||||||
value={{
|
value={{
|
||||||
isGGUF: isGGUF,
|
isGGUF: isGGUF,
|
||||||
modelFileOptions: props.modelFileOptions
|
modelFileOptions: props.modelFileOptions,
|
||||||
|
onValuesChange: handleOnValuesChange
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<FormWrapper>
|
<FormWrapper>
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import {
|
|||||||
ollamaModelOptions,
|
ollamaModelOptions,
|
||||||
sourceOptions
|
sourceOptions
|
||||||
} from '../config';
|
} from '../config';
|
||||||
|
import { FormContext } from '../config/form-context';
|
||||||
import { FormData, ListItem } from '../config/types';
|
import { FormData, ListItem } from '../config/types';
|
||||||
import { useCheckCompatibility } from '../hooks';
|
import { useCheckCompatibility } from '../hooks';
|
||||||
import AdvanceConfig from './advance-config';
|
import AdvanceConfig from './advance-config';
|
||||||
@@ -55,10 +56,7 @@ const UpdateModal: React.FC<AddModalProps> = (props) => {
|
|||||||
updateFormInitials: { gpuOptions, isGGUF, data: formData }
|
updateFormInitials: { gpuOptions, isGGUF, data: formData }
|
||||||
} = props || {};
|
} = props || {};
|
||||||
const {
|
const {
|
||||||
handleShowCompatibleAlert,
|
|
||||||
handleUpdateWarning,
|
|
||||||
setWarningStatus,
|
setWarningStatus,
|
||||||
handleEvaluate,
|
|
||||||
generateGPUIds,
|
generateGPUIds,
|
||||||
handleOnValuesChange,
|
handleOnValuesChange,
|
||||||
checkTokenRef,
|
checkTokenRef,
|
||||||
@@ -70,56 +68,42 @@ const UpdateModal: React.FC<AddModalProps> = (props) => {
|
|||||||
const localPathCache = useRef<string>('');
|
const localPathCache = useRef<string>('');
|
||||||
const submitAnyway = useRef<boolean>(false);
|
const submitAnyway = useRef<boolean>(false);
|
||||||
|
|
||||||
|
// voxbox is not support multi gpu
|
||||||
const handleSetGPUIds = (backend: string) => {
|
const handleSetGPUIds = (backend: string) => {
|
||||||
if (backend === backendOptionsMap.llamaBox) {
|
const gpuids = form.getFieldValue(['gpu_selector', 'gpu_ids']) || [];
|
||||||
return;
|
|
||||||
}
|
|
||||||
const gpuids = form.getFieldValue(['gpu_selector', 'gpu_ids']);
|
|
||||||
|
|
||||||
if (!gpuids?.length) {
|
if (backend === backendOptionsMap.voxBox && gpuids.length > 0) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (gpuids.length > 1 && Array.isArray(gpuids[0])) {
|
|
||||||
form.setFieldValue(['gpu_selector', 'gpu_ids'], [gpuids[0]]);
|
form.setFieldValue(['gpu_selector', 'gpu_ids'], [gpuids[0]]);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// trigger from local_path change or backend change
|
const handleBackendChange = (backend: string) => {
|
||||||
const handleBackendChangeBefore = async () => {
|
const updates = {
|
||||||
const localPath = form.getFieldValue?.('local_path');
|
backend_version: ''
|
||||||
const backend = form.getFieldValue?.('backend');
|
};
|
||||||
|
if (backend === backendOptionsMap.llamaBox) {
|
||||||
const res = handleUpdateWarning?.({
|
Object.assign(updates, {
|
||||||
backend,
|
|
||||||
localPath: localPath,
|
|
||||||
source: formData?.source as string
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!res.show) {
|
|
||||||
const values = form.getFieldsValue?.();
|
|
||||||
const data = getSourceRepoConfigValue(formData?.source as string, values);
|
|
||||||
const evalutionData = await handleEvaluate(
|
|
||||||
_.omit(data.values, [
|
|
||||||
'cpu_offloading',
|
|
||||||
'distributed_inference_across_workers'
|
|
||||||
])
|
|
||||||
);
|
|
||||||
handleShowCompatibleAlert?.(evalutionData);
|
|
||||||
} else {
|
|
||||||
setWarningStatus?.(res);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleBackendChange = (val: string) => {
|
|
||||||
if (val === backendOptionsMap.llamaBox) {
|
|
||||||
form.setFieldsValue({
|
|
||||||
distributed_inference_across_workers: true,
|
distributed_inference_across_workers: true,
|
||||||
cpu_offloading: true
|
cpu_offloading: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
form.setFieldValue('backend_version', '');
|
form.setFieldsValue(updates);
|
||||||
handleSetGPUIds(val);
|
handleSetGPUIds(backend);
|
||||||
handleBackendChangeBefore();
|
|
||||||
|
const data = form.getFieldsValue?.();
|
||||||
|
if (data.local_path || data.source !== modelSourceMap.local_path_value) {
|
||||||
|
handleOnValuesChange?.({
|
||||||
|
changedValues: {},
|
||||||
|
allValues:
|
||||||
|
backend === backendOptionsMap.llamaBox
|
||||||
|
? data
|
||||||
|
: _.omit(data, [
|
||||||
|
'cpu_offloading',
|
||||||
|
'distributed_inference_across_workers'
|
||||||
|
]),
|
||||||
|
source: data.source
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleOnFocus = () => {
|
const handleOnFocus = () => {
|
||||||
@@ -137,8 +121,21 @@ const UpdateModal: React.FC<AddModalProps> = (props) => {
|
|||||||
if (!isEndwithGGUF || !isBlobFile) {
|
if (!isEndwithGGUF || !isBlobFile) {
|
||||||
backend = backendOptionsMap.vllm;
|
backend = backendOptionsMap.vllm;
|
||||||
}
|
}
|
||||||
handleBackendChange?.(backend);
|
|
||||||
form.setFieldValue('backend', backend);
|
form.setFieldValue('backend', backend);
|
||||||
|
handleBackendChange?.(backend);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOnBlur = (e: any) => {
|
||||||
|
const value = e.target.value;
|
||||||
|
if (value) {
|
||||||
|
handleOnValuesChange?.({
|
||||||
|
changedValues: {},
|
||||||
|
allValues: {
|
||||||
|
...form.getFieldsValue?.()
|
||||||
|
},
|
||||||
|
source: formData?.source
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderHuggingfaceFields = () => {
|
const renderHuggingfaceFields = () => {
|
||||||
@@ -159,6 +156,7 @@ const UpdateModal: React.FC<AddModalProps> = (props) => {
|
|||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<SealInput.Input
|
<SealInput.Input
|
||||||
|
onBlur={handleOnBlur}
|
||||||
label={intl.formatMessage({ id: 'models.form.repoid' })}
|
label={intl.formatMessage({ id: 'models.form.repoid' })}
|
||||||
required
|
required
|
||||||
disabled={false}
|
disabled={false}
|
||||||
@@ -180,6 +178,7 @@ const UpdateModal: React.FC<AddModalProps> = (props) => {
|
|||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<SealInput.Input
|
<SealInput.Input
|
||||||
|
onBlur={handleOnBlur}
|
||||||
label={intl.formatMessage({ id: 'models.form.filename' })}
|
label={intl.formatMessage({ id: 'models.form.filename' })}
|
||||||
required
|
required
|
||||||
disabled={false}
|
disabled={false}
|
||||||
@@ -212,6 +211,7 @@ const UpdateModal: React.FC<AddModalProps> = (props) => {
|
|||||||
defaultActiveFirstOption
|
defaultActiveFirstOption
|
||||||
disabled={false}
|
disabled={false}
|
||||||
options={ollamaModelOptions}
|
options={ollamaModelOptions}
|
||||||
|
onBlur={handleOnBlur}
|
||||||
placeholder={intl.formatMessage({ id: 'model.form.ollamaholder' })}
|
placeholder={intl.formatMessage({ id: 'model.form.ollamaholder' })}
|
||||||
description={
|
description={
|
||||||
<span>
|
<span>
|
||||||
@@ -423,137 +423,143 @@ const UpdateModal: React.FC<AddModalProps> = (props) => {
|
|||||||
></CompatibilityAlert>
|
></CompatibilityAlert>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Form
|
<FormContext.Provider
|
||||||
name="addModalForm"
|
value={{
|
||||||
form={form}
|
onValuesChange: handleOnValuesChange
|
||||||
onFinish={handleOk}
|
|
||||||
onValuesChange={onValuesChange}
|
|
||||||
scrollToFirstError={true}
|
|
||||||
preserve={false}
|
|
||||||
clearOnDestroy={true}
|
|
||||||
initialValues={{
|
|
||||||
...formData
|
|
||||||
}}
|
|
||||||
style={{
|
|
||||||
padding: 'var(--ant-modal-content-padding)',
|
|
||||||
paddingBlock: 0
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Form.Item<FormData>
|
<Form
|
||||||
name="name"
|
name="addModalForm"
|
||||||
rules={[
|
form={form}
|
||||||
{
|
onFinish={handleOk}
|
||||||
required: true,
|
onValuesChange={onValuesChange}
|
||||||
message: getRuleMessage('input', 'common.table.name')
|
scrollToFirstError={true}
|
||||||
}
|
preserve={false}
|
||||||
]}
|
clearOnDestroy={true}
|
||||||
|
initialValues={{
|
||||||
|
...formData
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
padding: 'var(--ant-modal-content-padding)',
|
||||||
|
paddingBlock: 0
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<SealInput.Input
|
<Form.Item<FormData>
|
||||||
label={intl.formatMessage({
|
name="name"
|
||||||
id: 'common.table.name'
|
rules={[
|
||||||
})}
|
|
||||||
required
|
|
||||||
></SealInput.Input>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item<FormData>
|
|
||||||
name="source"
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
message: getRuleMessage('select', 'models.form.source')
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
{action === PageAction.EDIT && (
|
|
||||||
<SealSelect
|
|
||||||
disabled={true}
|
|
||||||
label={intl.formatMessage({
|
|
||||||
id: 'models.form.source'
|
|
||||||
})}
|
|
||||||
options={sourceOptions}
|
|
||||||
required
|
|
||||||
></SealSelect>
|
|
||||||
)}
|
|
||||||
</Form.Item>
|
|
||||||
{renderFieldsBySource}
|
|
||||||
<Form.Item name="backend" rules={[{ required: true }]}>
|
|
||||||
<SealSelect
|
|
||||||
required
|
|
||||||
onChange={handleBackendChange}
|
|
||||||
label={intl.formatMessage({ id: 'models.form.backend' })}
|
|
||||||
description={<TooltipList list={backendTipsList}></TooltipList>}
|
|
||||||
options={[
|
|
||||||
{
|
{
|
||||||
label: `llama-box`,
|
required: true,
|
||||||
value: backendOptionsMap.llamaBox,
|
message: getRuleMessage('input', 'common.table.name')
|
||||||
disabled:
|
|
||||||
formData?.source === modelSourceMap.local_path_value
|
|
||||||
? false
|
|
||||||
: !isGGUF
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'vLLM',
|
|
||||||
value: backendOptionsMap.vllm,
|
|
||||||
disabled:
|
|
||||||
formData?.source === modelSourceMap.local_path_value
|
|
||||||
? false
|
|
||||||
: isGGUF
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'vox-box',
|
|
||||||
value: backendOptionsMap.voxBox,
|
|
||||||
disabled:
|
|
||||||
formData?.source === modelSourceMap.local_path_value
|
|
||||||
? false
|
|
||||||
: isGGUF
|
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
disabled={
|
>
|
||||||
action === PageAction.EDIT &&
|
<SealInput.Input
|
||||||
formData?.source !== modelSourceMap.local_path_value
|
label={intl.formatMessage({
|
||||||
}
|
id: 'common.table.name'
|
||||||
></SealSelect>
|
})}
|
||||||
</Form.Item>
|
required
|
||||||
<Form.Item<FormData>
|
></SealInput.Input>
|
||||||
name="replicas"
|
</Form.Item>
|
||||||
rules={[
|
<Form.Item<FormData>
|
||||||
{
|
name="source"
|
||||||
required: true,
|
rules={[
|
||||||
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'
|
required: true,
|
||||||
},
|
message: getRuleMessage('select', 'models.form.source')
|
||||||
{ api: `${window.location.origin}/v1` }
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{action === PageAction.EDIT && (
|
||||||
|
<SealSelect
|
||||||
|
disabled={true}
|
||||||
|
label={intl.formatMessage({
|
||||||
|
id: 'models.form.source'
|
||||||
|
})}
|
||||||
|
options={sourceOptions}
|
||||||
|
required
|
||||||
|
></SealSelect>
|
||||||
)}
|
)}
|
||||||
min={0}
|
</Form.Item>
|
||||||
></SealInput.Number>
|
{renderFieldsBySource}
|
||||||
</Form.Item>
|
<Form.Item name="backend" rules={[{ required: true }]}>
|
||||||
<Form.Item<FormData> name="description">
|
<SealSelect
|
||||||
<SealInput.TextArea
|
required
|
||||||
label={intl.formatMessage({
|
onChange={handleBackendChange}
|
||||||
id: 'common.table.description'
|
label={intl.formatMessage({ id: 'models.form.backend' })}
|
||||||
})}
|
description={<TooltipList list={backendTipsList}></TooltipList>}
|
||||||
></SealInput.TextArea>
|
options={[
|
||||||
</Form.Item>
|
{
|
||||||
|
label: `llama-box`,
|
||||||
|
value: backendOptionsMap.llamaBox,
|
||||||
|
disabled:
|
||||||
|
formData?.source === modelSourceMap.local_path_value
|
||||||
|
? false
|
||||||
|
: !isGGUF
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'vLLM',
|
||||||
|
value: backendOptionsMap.vllm,
|
||||||
|
disabled:
|
||||||
|
formData?.source === modelSourceMap.local_path_value
|
||||||
|
? false
|
||||||
|
: isGGUF
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'vox-box',
|
||||||
|
value: backendOptionsMap.voxBox,
|
||||||
|
disabled:
|
||||||
|
formData?.source === modelSourceMap.local_path_value
|
||||||
|
? false
|
||||||
|
: isGGUF
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
disabled={
|
||||||
|
action === PageAction.EDIT &&
|
||||||
|
formData?.source !== modelSourceMap.local_path_value
|
||||||
|
}
|
||||||
|
></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
|
||||||
|
label={intl.formatMessage({
|
||||||
|
id: 'common.table.description'
|
||||||
|
})}
|
||||||
|
></SealInput.TextArea>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
<AdvanceConfig
|
<AdvanceConfig
|
||||||
form={form}
|
form={form}
|
||||||
gpuOptions={gpuOptions}
|
gpuOptions={gpuOptions}
|
||||||
action={PageAction.EDIT}
|
action={PageAction.EDIT}
|
||||||
source={formData?.source || ''}
|
source={formData?.source || ''}
|
||||||
isGGUF={formData?.backend === backendOptionsMap.llamaBox}
|
isGGUF={formData?.backend === backendOptionsMap.llamaBox}
|
||||||
></AdvanceConfig>
|
></AdvanceConfig>
|
||||||
</Form>
|
</Form>
|
||||||
|
</FormContext.Provider>
|
||||||
</ColumnWrapper>
|
</ColumnWrapper>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
interface FormContextProps {
|
interface FormContextProps {
|
||||||
isGGUF: boolean;
|
isGGUF?: boolean;
|
||||||
byBuiltIn?: boolean;
|
byBuiltIn?: boolean;
|
||||||
sizeOptions?: Global.BaseOption<number>[];
|
sizeOptions?: Global.BaseOption<number>[];
|
||||||
quantizationOptions?: Global.BaseOption<string>[];
|
quantizationOptions?: Global.BaseOption<string>[];
|
||||||
modelFileOptions?: any[];
|
modelFileOptions?: any[];
|
||||||
onSizeChange?: (val: number) => void;
|
onSizeChange?: (val: number) => void;
|
||||||
onQuantizationChange?: (val: string) => void;
|
onQuantizationChange?: (val: string) => void;
|
||||||
|
onValuesChange?: (val: any) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface FormInnerContextProps {
|
interface FormInnerContextProps {
|
||||||
|
|||||||
@@ -450,6 +450,8 @@ export const modelLabels = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export const excludeFields = [
|
export const excludeFields = [
|
||||||
|
'repo_id',
|
||||||
|
'file_name',
|
||||||
'replicas',
|
'replicas',
|
||||||
'categories',
|
'categories',
|
||||||
'name',
|
'name',
|
||||||
@@ -460,5 +462,8 @@ export const excludeFields = [
|
|||||||
'size',
|
'size',
|
||||||
'restart_on_error',
|
'restart_on_error',
|
||||||
'worker_selector',
|
'worker_selector',
|
||||||
'backend_parameters'
|
'backend_parameters',
|
||||||
|
'local_path',
|
||||||
|
'backend_version',
|
||||||
|
'ollama_library_model_name'
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -215,9 +215,11 @@ export const useGenerateModelFileOptions = () => {
|
|||||||
export const useCheckCompatibility = () => {
|
export const useCheckCompatibility = () => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
|
const cacheFormValuesRef = useRef<any>({});
|
||||||
const checkTokenRef = useRef<any>(null);
|
const checkTokenRef = useRef<any>(null);
|
||||||
const submitAnyway = useRef<boolean>(false);
|
const submitAnyway = useRef<boolean>(false);
|
||||||
const requestIdRef = useRef(0);
|
const requestIdRef = useRef(0);
|
||||||
|
const updateStatusTimer = useRef<any>(null);
|
||||||
const [warningStatus, setWarningStatus] = useState<{
|
const [warningStatus, setWarningStatus] = useState<{
|
||||||
show: boolean;
|
show: boolean;
|
||||||
title?: string;
|
title?: string;
|
||||||
@@ -295,7 +297,12 @@ export const useCheckCompatibility = () => {
|
|||||||
|
|
||||||
const handleShowCompatibleAlert = (evaluateResult: EvaluateResult | null) => {
|
const handleShowCompatibleAlert = (evaluateResult: EvaluateResult | null) => {
|
||||||
const result = handleCheckCompatibility(evaluateResult);
|
const result = handleCheckCompatibility(evaluateResult);
|
||||||
setWarningStatus(result);
|
if (updateStatusTimer.current) {
|
||||||
|
clearTimeout(updateStatusTimer.current);
|
||||||
|
}
|
||||||
|
updateStatusTimer.current = setTimeout(() => {
|
||||||
|
setWarningStatus(result);
|
||||||
|
}, 300);
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateShowWarning = (params: {
|
const updateShowWarning = (params: {
|
||||||
@@ -386,6 +393,15 @@ export const useCheckCompatibility = () => {
|
|||||||
source: string;
|
source: string;
|
||||||
}) => {
|
}) => {
|
||||||
const { changedValues, allValues, source } = params;
|
const { changedValues, allValues, source } = params;
|
||||||
|
|
||||||
|
if (
|
||||||
|
_.isEqual(cacheFormValuesRef.current, allValues) ||
|
||||||
|
(allValues.source === modelSourceMap.local_path_value &&
|
||||||
|
!allValues.local_path)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cacheFormValuesRef.current = allValues;
|
||||||
const data = getSourceRepoConfigValue(source, allValues);
|
const data = getSourceRepoConfigValue(source, allValues);
|
||||||
const gpuSelector = generateGPUIds(data.values);
|
const gpuSelector = generateGPUIds(data.values);
|
||||||
|
|
||||||
|
|||||||
@@ -467,7 +467,7 @@ const ModelFiles = () => {
|
|||||||
return (
|
return (
|
||||||
record.resolved_paths?.length > 0 && (
|
record.resolved_paths?.length > 0 && (
|
||||||
<PathWrapper>
|
<PathWrapper>
|
||||||
<AutoTooltip ghost>
|
<AutoTooltip ghost title={record.resolved_paths?.[0]} showTitle>
|
||||||
<span>{getResolvedPath(record.resolved_paths)}</span>
|
<span>{getResolvedPath(record.resolved_paths)}</span>
|
||||||
</AutoTooltip>
|
</AutoTooltip>
|
||||||
<span className="btn-wrapper">
|
<span className="btn-wrapper">
|
||||||
|
|||||||
Reference in New Issue
Block a user