fix: auto expand the fields group

This commit is contained in:
jialin
2025-11-07 11:56:12 +08:00
parent 39efca0d4c
commit 64e2cfa35a
18 changed files with 168 additions and 101 deletions
+4 -4
View File
@@ -229,6 +229,10 @@
margin-bottom: 0; margin-bottom: 0;
} }
.m-t-0 {
margin-top: 0;
}
.m-b-16 { .m-b-16 {
margin-bottom: 16px; margin-bottom: 16px;
} }
@@ -278,10 +282,6 @@
gap: 16px; gap: 16px;
} }
.gap-4 {
gap: 4px;
}
.color-white-tertiary { .color-white-tertiary {
color: var(--color-white-tertiary); color: var(--color-white-tertiary);
} }
+9 -1
View File
@@ -2,9 +2,11 @@ import IconFont from '@/components/icon-font';
import type { SelectProps } from 'antd'; import type { SelectProps } from 'antd';
import { Select } from 'antd'; import { Select } from 'antd';
import React, { forwardRef, useImperativeHandle } from 'react'; import React, { forwardRef, useImperativeHandle } from 'react';
import NotFoundContent from '../components/not-found-content';
const BaseSelect: React.FC<SelectProps & { ref?: any }> = forwardRef( const BaseSelect: React.FC<SelectProps & { ref?: any }> = forwardRef(
(props, ref) => { (props, ref) => {
const { notFoundContent, loading, ...restProps } = props;
const [isFocus, setIsFocus] = React.useState(false); const [isFocus, setIsFocus] = React.useState(false);
const inputRef = React.useRef<any>(null); const inputRef = React.useRef<any>(null);
@@ -32,7 +34,13 @@ const BaseSelect: React.FC<SelectProps & { ref?: any }> = forwardRef(
return ( return (
<Select <Select
{...props} {...restProps}
notFoundContent={
<NotFoundContent
loading={loading}
notFoundContent={notFoundContent}
/>
}
ref={inputRef} ref={inputRef}
onFocus={handleFocus} onFocus={handleFocus}
onBlur={handleBlur} onBlur={handleBlur}
@@ -34,7 +34,10 @@ const NotFoundContent: React.FC<{
return ( return (
<NoContent> <NoContent>
{notFoundContent || ( {notFoundContent || (
<Empty description={intl.formatMessage({ id: 'common.data.empty' })} /> <Empty
description={intl.formatMessage({ id: 'common.data.empty' })}
image={Empty.PRESENTED_IMAGE_SIMPLE}
/>
)} )}
</NoContent> </NoContent>
); );
+9
View File
@@ -62,3 +62,12 @@ export const specialCharacterReg = /(?=.*[\W_])/;
export const noSpaceReg = /(?=\S+$)/; export const noSpaceReg = /(?=\S+$)/;
export const lengthReg = /^.{6,12}$/; export const lengthReg = /^.{6,12}$/;
/**
* Model name rules:
* 1. no more than 63 characters
* 2. contain only alphanumeric characters, '-', '_', and '.'
* 3. start and end with an alphanumeric character
*/
export const modelNameReg =
/^[A-Za-z0-9](?:[A-Za-z0-9._-]{0,61}[A-Za-z0-9])?$/g;
+7
View File
@@ -7,6 +7,13 @@ const useAppUtils = () => {
const intl = useIntl(); const intl = useIntl();
const [messageApi, contextHolder] = message.useMessage(); const [messageApi, contextHolder] = message.useMessage();
/**
*
* @param type Array<'input' | 'select'>
* @param name
* @param locale boolean
* @returns
*/
const getRuleMessage = ( const getRuleMessage = (
type: MessageType | MessageType[], type: MessageType | MessageType[],
name: string, name: string,
+3 -1
View File
@@ -238,5 +238,7 @@ export default {
'models.form.draftModel.tips': 'models.form.draftModel.tips':
'You can enter a local path (e.g., /path/to/model) or select a model from Hugging Face or ModelScope (e.g., Tengyunw/qwen3_8b_eagle3). The system will automatically match based on the primary model source.', 'You can enter a local path (e.g., /path/to/model) or select a model from Hugging Face or ModelScope (e.g., Tengyunw/qwen3_8b_eagle3). The system will automatically match based on the primary model source.',
'models.form.quantization': 'Quantization', 'models.form.quantization': 'Quantization',
'models.form.backend.custom': 'User-defined' 'models.form.backend.custom': 'User-defined',
'models.form.rules.name':
'Up to 63 characters; letters, numbers, dots (.), underscores (_), and hyphens (-) only; must start and end with an alphanumeric character.'
}; };
+5 -2
View File
@@ -238,7 +238,9 @@ export default {
'models.form.draftModel.tips': 'models.form.draftModel.tips':
'You can enter a local path (e.g., /path/to/model) or select a model from Hugging Face or ModelScope (e.g., Tengyunw/qwen3_8b_eagle3). The system will automatically match based on the primary model source.', 'You can enter a local path (e.g., /path/to/model) or select a model from Hugging Face or ModelScope (e.g., Tengyunw/qwen3_8b_eagle3). The system will automatically match based on the primary model source.',
'models.form.quantization': 'Quantization', 'models.form.quantization': 'Quantization',
'models.form.backend.custom': 'User Defined' 'models.form.backend.custom': 'User Defined',
'models.form.rules.name':
'Up to 63 characters; letters, numbers, dots (.), underscores (_), and hyphens (-) only; must start and end with an alphanumeric character.'
}; };
// ========== To-Do: Translate Keys (Remove After Translation) ========== // ========== To-Do: Translate Keys (Remove After Translation) ==========
@@ -306,5 +308,6 @@ export default {
// 45. 'models.form.draftModel.placeholder': 'Please select or enter a draft model', // 45. 'models.form.draftModel.placeholder': 'Please select or enter a draft model',
// 46. 'models.form.draftModel.tips': 'You can enter a local path (e.g., /path/to/model) or select a model from Hugging Face or ModelScope (e.g., Tengyunw/qwen3_8b_eagle3). The system will automatically match based on the primary model source.' // 46. 'models.form.draftModel.tips': 'You can enter a local path (e.g., /path/to/model) or select a model from Hugging Face or ModelScope (e.g., Tengyunw/qwen3_8b_eagle3). The system will automatically match based on the primary model source.'
// 47. 'models.form.quantization': 'Quantization', // 47. 'models.form.quantization': 'Quantization',
// 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.'
// ========== End of To-Do List ========== // ========== End of To-Do List ==========
+20 -10
View File
@@ -208,13 +208,17 @@ export default {
'models.mymodels.status.inactive': 'Остановлен', 'models.mymodels.status.inactive': 'Остановлен',
'models.mymodels.status.degrade': 'Аномальный', 'models.mymodels.status.degrade': 'Аномальный',
'models.mymodels.status.active': 'Активен', 'models.mymodels.status.active': 'Активен',
'models.form.kvCache.tips': 'Расширенный KV-кэш и спекулятивное декодирование доступны только со встроенными бэкендами (vLLM / SGLang). Переключите бэкенд в <span class="bold-text">Расширенных</span> настройках, чтобы включить их.', 'models.form.kvCache.tips':
'models.form.kvCache.tips2': 'Поддерживается только при использовании встроенных бэкендов вывода (vLLM или SGLang).', 'Расширенный KV-кэш и спекулятивное декодирование доступны только со встроенными бэкендами (vLLM / SGLang). Переключите бэкенд в <span class="bold-text">Расширенных</span> настройках, чтобы включить их.',
'models.form.kvCache.tips2':
'Поддерживается только при использовании встроенных бэкендов вывода (vLLM или SGLang).',
'models.form.scheduling': 'Планирование', 'models.form.scheduling': 'Планирование',
'models.form.ramRatio': 'Соотношение ОЗУ к VRAM', 'models.form.ramRatio': 'Соотношение ОЗУ к VRAM',
'models.form.ramSize': 'Максимальный размер ОЗУ (ГиБ)', 'models.form.ramSize': 'Максимальный размер ОЗУ (ГиБ)',
'models.form.ramRatio.tips': 'Соотношение системной оперативной памяти к видеопамяти GPU, используемое для KV-кэша. Например, 2.0 означает, что кэш в ОЗУ может быть в два раза больше, чем в видеопамяти GPU.', 'models.form.ramRatio.tips':
'models.form.ramSize.tips': 'Максимальный размер KV-кэша, хранящегося в системной памяти (ГиБ). Если задано, это значение переопределяет "{content}".', 'Соотношение системной оперативной памяти к видеопамяти GPU, используемое для KV-кэша. Например, 2.0 означает, что кэш в ОЗУ может быть в два раза больше, чем в видеопамяти GPU.',
'models.form.ramSize.tips':
'Максимальный размер KV-кэша, хранящегося в системной памяти (ГиБ). Если задано, это значение переопределяет "{content}".',
'models.form.chunkSize.tips': 'Количество токенов на чанк KV-кэша.', 'models.form.chunkSize.tips': 'Количество токенов на чанк KV-кэша.',
'models.form.mode': 'Режим', 'models.form.mode': 'Режим',
'models.form.algorithm': 'Алгоритм', 'models.form.algorithm': 'Алгоритм',
@@ -225,15 +229,21 @@ export default {
'models.form.mode.throughput': 'Пропускная способность', 'models.form.mode.throughput': 'Пропускная способность',
'models.form.mode.latency': 'Задержка', 'models.form.mode.latency': 'Задержка',
'models.form.mode.baseline': 'Стандартный', 'models.form.mode.baseline': 'Стандартный',
'models.form.mode.throughput.tips': 'оптимизировано для высокой пропускной способности при высокой конкурентности запросов.', 'models.form.mode.throughput.tips':
'models.form.mode.latency.tips': 'оптимизировано для низкой задержки при низкой конкурентности запросов.', 'оптимизировано для высокой пропускной способности при высокой конкурентности запросов.',
'models.form.mode.baseline.tips': 'наиболее совместимый вариант с полной точностью.', 'models.form.mode.latency.tips':
'оптимизировано для низкой задержки при низкой конкурентности запросов.',
'models.form.mode.baseline.tips':
'наиболее совместимый вариант с полной точностью.',
'models.form.draftModel.placeholder': 'Выберите или введите черновую модель', 'models.form.draftModel.placeholder': 'Выберите или введите черновую модель',
'models.form.draftModel.tips': 'Вы можете ввести локальный путь (например, /path/to/model) или выбрать модель из Hugging Face или ModelScope (например, Tengyunw/qwen3_8b_eagle3). Система автоматически сопоставит модель на основе источника основной модели.', 'models.form.draftModel.tips':
'Вы можете ввести локальный путь (например, /path/to/model) или выбрать модель из Hugging Face или ModelScope (например, Tengyunw/qwen3_8b_eagle3). Система автоматически сопоставит модель на основе источника основной модели.',
'models.form.quantization': 'Квантование', 'models.form.quantization': 'Квантование',
'models.form.backend.custom': 'Пользовательский' 'models.form.backend.custom': 'Пользовательский',
'models.form.rules.name':
'Up to 63 characters; letters, numbers, dots (.), underscores (_), and hyphens (-) only; must start and end with an alphanumeric character.'
}; };
// ========== 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.'
// ========== End of To-Do List ========== // ========== End of To-Do List ==========
+3 -1
View File
@@ -225,5 +225,7 @@ export default {
'models.form.draftModel.tips': 'models.form.draftModel.tips':
'可填写本地路径(如 /path/to/model),或从 Hugging Face、ModelScope 选择模型(如 Tengyunw/qwen3_8b_eagle3)。系统将根据主模型来源自动匹配。', '可填写本地路径(如 /path/to/model),或从 Hugging Face、ModelScope 选择模型(如 Tengyunw/qwen3_8b_eagle3)。系统将根据主模型来源自动匹配。',
'models.form.quantization': '量化', 'models.form.quantization': '量化',
'models.form.backend.custom': '用户定义' 'models.form.backend.custom': '用户定义',
'models.form.rules.name':
'长度不超过 63 个字符,只能包含字母、数字、点(.)、下划线(_)和连字符(-),且必须以字母或数字开头和结尾。'
}; };
+7 -3
View File
@@ -9,7 +9,11 @@ import _ from 'lodash';
import React, { useEffect, useId, useRef, useState } from 'react'; import React, { useEffect, useId, useRef, useState } from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import ColumnWrapper from '../../_components/column-wrapper'; import ColumnWrapper from '../../_components/column-wrapper';
import { backendFields, json2Yaml } from '../config'; import {
builtInBackendFields,
customBackendFields,
json2Yaml
} from '../config';
import { FormData, ListItem } from '../config/types'; import { FormData, ListItem } from '../config/types';
import BackendForm from '../forms'; import BackendForm from '../forms';
import ImportYAML from './import-yaml'; import ImportYAML from './import-yaml';
@@ -151,10 +155,10 @@ const AddModal: React.FC<AddModalProps> = (props) => {
); );
if (currentData?.is_built_in) { if (currentData?.is_built_in) {
return json2Yaml(_.pick(copyValues, backendFields)); return json2Yaml(_.pick(copyValues, builtInBackendFields));
} }
return json2Yaml( return json2Yaml(
_.pick(copyValues, [...backendFields, 'default_version']) _.pick(copyValues, [...customBackendFields, 'default_version'])
); );
}; };
+14 -1
View File
@@ -109,13 +109,26 @@ export const customIcons = [
'≥' '≥'
]; ];
export const backendFields = [ /**
* custom backend fields for config yaml
*/
export const customBackendFields = [
'description', 'description',
'health_check_path', 'health_check_path',
'default_run_command', 'default_run_command',
'version_configs', 'version_configs',
'default_backend_param' 'default_backend_param'
]; ];
/**
* built-in backend fields for config yaml
*/
export const builtInBackendFields = [
'description',
'version_configs',
'default_backend_param'
];
export const frameworks = [ export const frameworks = [
{ {
label: 'CUDA', label: 'CUDA',
+34 -32
View File
@@ -37,38 +37,40 @@ const BasicForm: React.FC<AddModalProps> = ({ action, currentData }) => {
required required
></SealInput.Input> ></SealInput.Input>
</Form.Item> </Form.Item>
<Form.Item<FormData> {!currentData?.is_built_in && (
name="health_check_path" <>
rules={[{ required: false }]} <Form.Item<FormData>
> name="health_check_path"
<SealInput.Input rules={[{ required: false }]}
trim >
placeholder={intl.formatMessage( <SealInput.Input
{ id: 'common.help.default' }, trim
{ content: '/v1/models' } placeholder={intl.formatMessage(
)} { id: 'common.help.default' },
label={intl.formatMessage({ id: 'backend.form.healthCheckPath' })} { content: '/v1/models' }
></SealInput.Input> )}
</Form.Item> label={intl.formatMessage({ id: 'backend.form.healthCheckPath' })}
></SealInput.Input>
<Form.Item name="default_run_command"> </Form.Item>
<SealTextArea <Form.Item name="default_run_command">
scaleSize={true} <SealTextArea
alwaysFocus={true} scaleSize={true}
description={intl.formatMessage({ alwaysFocus={true}
id: 'backend.form.defaultExecuteCommand.tips' description={intl.formatMessage({
})} id: 'backend.form.defaultExecuteCommand.tips'
placeholder={intl.formatMessage( })}
{ id: 'common.help.eg' }, placeholder={intl.formatMessage(
{ content: 'vllm serve {{model_path}} --port {{port}}' } { id: 'common.help.eg' },
)} { content: 'vllm serve {{model_path}} --port {{port}}' }
autoSize={{ minRows: 2, maxRows: 4 }} )}
label={intl.formatMessage({ autoSize={{ minRows: 2, maxRows: 4 }}
id: 'backend.form.defaultExecuteCommand' label={intl.formatMessage({
})} id: 'backend.form.defaultExecuteCommand'
></SealTextArea> })}
</Form.Item> ></SealTextArea>
</Form.Item>
</>
)}
<Form.Item<FormData> <Form.Item<FormData>
name="default_backend_param" name="default_backend_param"
rules={[{ required: false }]} rules={[{ required: false }]}
-1
View File
@@ -51,7 +51,6 @@ const BackendForm: React.FC<AddModalProps> = forwardRef(
} }
return item; return item;
}); });
onFinish(data); onFinish(data);
}; };
+6
View File
@@ -1,5 +1,6 @@
import SealInput from '@/components/seal-form/seal-input'; import SealInput from '@/components/seal-form/seal-input';
import SealSelect from '@/components/seal-form/seal-select'; import SealSelect from '@/components/seal-form/seal-select';
import { modelNameReg } from '@/config';
import useAppUtils from '@/hooks/use-app-utils'; import useAppUtils from '@/hooks/use-app-utils';
import { import {
ClusterStatusLabelMap, ClusterStatusLabelMap,
@@ -65,10 +66,15 @@ const BasicForm: React.FC<BasicFormProps> = (props) => {
{ {
required: true, required: true,
message: getRuleMessage('input', 'common.table.name') message: getRuleMessage('input', 'common.table.name')
},
{
pattern: modelNameReg,
message: intl.formatMessage({ id: 'models.form.rules.name' })
} }
]} ]}
> >
<SealInput.Input <SealInput.Input
description={intl.formatMessage({ id: 'models.form.rules.name' })}
label={intl.formatMessage({ label={intl.formatMessage({
id: 'common.table.name' id: 'common.table.name'
})} })}
+12 -6
View File
@@ -30,12 +30,10 @@ import BasicForm from './basic';
import Performance from './performance'; import Performance from './performance';
import ScheduleTypeForm from './schedule-type'; import ScheduleTypeForm from './schedule-type';
const advancedRequiredFields = [ const advancedRequiredFields = ['backend', 'image_name', 'run_command'];
'gpu_selector',
'backend', const scheduleRequiredFields = ['gpu_selector'];
'image_name',
'run_command'
];
const performanceRequiredFields = ['speculative_config']; const performanceRequiredFields = ['speculative_config'];
const SegmentedInner = styled(Segmented)` const SegmentedInner = styled(Segmented)`
@@ -281,6 +279,14 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
performanceRequiredFields.includes(name) performanceRequiredFields.includes(name)
); );
const isScheduleRequired = names.some((name: string) =>
scheduleRequiredFields.includes(name)
);
if (isScheduleRequired) {
collapseKeys.push('scheduling');
}
if (isPerformanceRequired) { if (isPerformanceRequired) {
collapseKeys.push('performance'); collapseKeys.push('performance');
} }
@@ -40,7 +40,7 @@ const SpeculativeDecode = () => {
const onValuesChangeDebounced = _.debounce(() => { const onValuesChangeDebounced = _.debounce(() => {
const allValues = form.getFieldsValue(); const allValues = form.getFieldsValue();
onValuesChange?.({}, allValues); onValuesChange?.({}, allValues);
}, 200); }, 150);
const handleSpeculativeEnabledChange = (e: any) => { const handleSpeculativeEnabledChange = (e: any) => {
if (e.target.checked) { if (e.target.checked) {
@@ -57,8 +57,8 @@ const SpeculativeDecode = () => {
}); });
} else { } else {
speculativeConfigRef.current = form.getFieldValue('speculative_config'); speculativeConfigRef.current = form.getFieldValue('speculative_config');
onValuesChangeDebounced();
} }
onValuesChangeDebounced();
}; };
const handleAlgorithemChange = (value: string) => { const handleAlgorithemChange = (value: string) => {
+24 -29
View File
@@ -3,9 +3,10 @@ import useSetChunkRequest from '@/hooks/use-chunk-request';
import useUpdateChunkedList from '@/hooks/use-update-chunk-list'; import useUpdateChunkedList from '@/hooks/use-update-chunk-list';
import { queryWorkersList } from '@/pages/resources/apis'; import { queryWorkersList } from '@/pages/resources/apis';
import { ListItem as WokerListItem } from '@/pages/resources/config/types'; import { ListItem as WokerListItem } from '@/pages/resources/config/types';
import { useMemoizedFn } from 'ahooks';
import _ from 'lodash'; import _ from 'lodash';
import qs from 'query-string'; import qs from 'query-string';
import React, { useCallback, useEffect, useRef, useState } from 'react'; import React, { useEffect, useRef, useState } from 'react';
import { import {
MODELS_API, MODELS_API,
MODEL_INSTANCE_API, MODEL_INSTANCE_API,
@@ -73,7 +74,7 @@ const Models: React.FC = () => {
setDataList: setModelInstances setDataList: setModelInstances
}); });
const getAllModelInstances = useCallback(async () => { const getAllModelInstances = useMemoizedFn(async () => {
try { try {
instancesToken.current?.cancel?.(); instancesToken.current?.cancel?.();
instancesToken.current = createAxiosToken(); instancesToken.current = createAxiosToken();
@@ -89,9 +90,9 @@ const Models: React.FC = () => {
} catch (error) { } catch (error) {
// ignore // ignore
} }
}, []); });
const fetchData = useCallback( const fetchData = useMemoizedFn(
async (params?: { async (params?: {
loadingVal?: boolean; loadingVal?: boolean;
query?: { query?: {
@@ -156,8 +157,7 @@ const Models: React.FC = () => {
}); });
} }
} }
}, }
[queryParams]
); );
const handleQueryChange = (params: any) => { const handleQueryChange = (params: any) => {
@@ -168,14 +168,13 @@ const Models: React.FC = () => {
fetchData({ query: { ...queryParams, ...params } }); fetchData({ query: { ...queryParams, ...params } });
}; };
const handlePageChange = useCallback( const handlePageChange = useMemoizedFn(
(page: number, pageSize: number | undefined) => { (page: number, pageSize: number | undefined) => {
handleQueryChange({ handleQueryChange({
page: page, page: page,
perPage: pageSize || 10 perPage: pageSize || 10
}); });
}, }
[queryParams]
); );
const updateHandler = (list: any) => { const updateHandler = (list: any) => {
@@ -191,7 +190,7 @@ const Models: React.FC = () => {
}); });
}; };
const createModelsChunkRequest = useCallback( const createModelsChunkRequest = useMemoizedFn(
async (params?: { async (params?: {
search: string; search: string;
categories: any[]; categories: any[];
@@ -212,11 +211,10 @@ const Models: React.FC = () => {
} catch (error) { } catch (error) {
// ignore // ignore
} }
}, }
[queryParams.categories, queryParams.search]
); );
const createModelsInstanceChunkRequest = useCallback(async () => { const createModelsInstanceChunkRequest = useMemoizedFn(async () => {
chunkInstanceRequedtRef.current?.current?.cancel?.(); chunkInstanceRequedtRef.current?.current?.cancel?.();
try { try {
chunkInstanceRequedtRef.current = setModelInstanceChunkRequest({ chunkInstanceRequedtRef.current = setModelInstanceChunkRequest({
@@ -227,18 +225,18 @@ const Models: React.FC = () => {
} catch (error) { } catch (error) {
// ignore // ignore
} }
}, [updateInstanceHandler]); });
const handleOnViewLogs = useCallback(() => { const handleOnViewLogs = useMemoizedFn(() => {
isPageHidden.current = true; isPageHidden.current = true;
chunkRequedtRef.current?.current?.cancel?.(); chunkRequedtRef.current?.current?.cancel?.();
cacheDataListRef.current = []; cacheDataListRef.current = [];
cacheInsDataListRef.current = []; cacheInsDataListRef.current = [];
chunkInstanceRequedtRef.current?.current?.cancel?.(); chunkInstanceRequedtRef.current?.current?.cancel?.();
instancesToken.current?.cancel?.(); instancesToken.current?.cancel?.();
}, []); });
const handleOnCancelViewLogs = useCallback(async () => { const handleOnCancelViewLogs = useMemoizedFn(async () => {
isPageHidden.current = false; isPageHidden.current = false;
await getAllModelInstances(); await getAllModelInstances();
await createModelsInstanceChunkRequest(); await createModelsInstanceChunkRequest();
@@ -246,23 +244,20 @@ const Models: React.FC = () => {
fetchData({ fetchData({
loadingVal: false loadingVal: false
}); });
}, [fetchData, createModelsChunkRequest, createModelsInstanceChunkRequest]); });
const handleSearchBySilent = useCallback(async () => { const handleSearchBySilent = useMemoizedFn(async () => {
await new Promise((resolve) => { await new Promise((resolve) => {
setTimeout(resolve, 300); setTimeout(resolve, 300);
}); });
fetchData({ fetchData({
loadingVal: false loadingVal: false
}); });
}, [fetchData]); });
const handleSearch = useCallback( const handleSearch = useMemoizedFn(async (params?: any) => {
async (params?: any) => { await fetchData(params);
await fetchData(params); });
},
[fetchData]
);
const debounceUpdateFilter = _.debounce((e: any) => { const debounceUpdateFilter = _.debounce((e: any) => {
handleQueryChange({ handleQueryChange({
@@ -276,7 +271,7 @@ const Models: React.FC = () => {
}); });
}, 350); }, 350);
const handleNameChange = useCallback(debounceUpdateFilter, [queryParams]); const handleNameChange = useMemoizedFn(debounceUpdateFilter);
const handleCategoryChange = async (value: any) => { const handleCategoryChange = async (value: any) => {
handleQueryChange({ handleQueryChange({
@@ -369,9 +364,9 @@ const Models: React.FC = () => {
}; };
}, []); }, []);
const setDisableExpand = useCallback((record: any) => { const setDisableExpand = useMemoizedFn((record: any) => {
return !record?.replicas; return !record?.replicas;
}, []); });
useEffect(() => { useEffect(() => {
const handleVisibilityChange = async () => { const handleVisibilityChange = async () => {
@@ -259,11 +259,6 @@ const GroundReranker: React.FC<MessageProps> = forwardRef((props, ref) => {
); );
}; };
const handleStopConversation = () => {
requestToken.current?.cancel?.();
setLoading(false);
};
const submitMessage = async (query: string) => { const submitMessage = async (query: string) => {
try { try {
setIsEmptyQuery(!queryValue); setIsEmptyQuery(!queryValue);
@@ -507,9 +502,12 @@ const GroundReranker: React.FC<MessageProps> = forwardRef((props, ref) => {
)} )}
</SearchInputWrapper> </SearchInputWrapper>
</div> </div>
<div className="center" ref={scroller}> <div className="center" ref={scroller} style={{ marginTop: 16 }}>
<div className="documents"> <div className="documents">
<div className="flex-between m-b-8 doc-header"> <div
className="flex-between m-b-8 doc-header"
style={{ marginTop: 0 }}
>
<h3 className="m-l-10 flex-between flex-center font-size-14 line-24 m-b-0"> <h3 className="m-l-10 flex-between flex-center font-size-14 line-24 m-b-0">
<span> <span>
{intl.formatMessage({ id: 'playground.embedding.documents' })} {intl.formatMessage({ id: 'playground.embedding.documents' })}