diff --git a/src/components/seal-form/auto-complete.tsx b/src/components/seal-form/auto-complete.tsx
index 46d4a913..679e47a0 100644
--- a/src/components/seal-form/auto-complete.tsx
+++ b/src/components/seal-form/auto-complete.tsx
@@ -1,12 +1,20 @@
-import { AutoComplete, Form, Spin } from 'antd';
+import { LoadingOutlined } from '@ant-design/icons';
+import { AutoComplete, Form, Typography } from 'antd';
import type { AutoCompleteProps } from 'antd/lib';
import React, { useEffect, useRef, useState } from 'react';
+import { LoadingContent } from './components/not-found-content';
import { SealFormItemProps } from './types';
import Wrapper from './wrapper';
import SelectWrapper from './wrapper/select';
+const Link = Typography.Link;
+
const SealAutoComplete: React.FC<
- AutoCompleteProps & SealFormItemProps & { onInput?: (e: Event) => void }
+ AutoCompleteProps &
+ SealFormItemProps & {
+ onInput?: (e: Event) => void;
+ clearSpaceOnBlur?: boolean;
+ }
> = (props) => {
const {
label,
@@ -22,6 +30,8 @@ const SealAutoComplete: React.FC<
style,
addAfter,
loading,
+ allowClear,
+ clearSpaceOnBlur,
...rest
} = props;
const [isFocus, setIsFocus] = useState(false);
@@ -46,6 +56,7 @@ const SealAutoComplete: React.FC<
};
const handleChange = (val: string, option: any) => {
+ console.log('handleChange val:', val);
let value = val;
if (trim) {
value = value?.trim?.();
@@ -62,7 +73,13 @@ const SealAutoComplete: React.FC<
if (!props.value) {
setIsFocus(false);
}
- e.target.value = e.target.value?.trim?.();
+
+ if (clearSpaceOnBlur) {
+ e.target.value = e.target.value?.replace(/\s+/g, '');
+ props.onChange?.(e.target.value);
+ } else {
+ e.target.value = e.target.value?.trim();
+ }
props.onBlur?.(e);
};
@@ -75,9 +92,20 @@ const SealAutoComplete: React.FC<
};
const renderAfter = () => {
if (loading) {
- return ;
+ return (
+
+
+
+ );
}
- return addAfter;
+ return null;
+ };
+
+ const popupRender = (originNode: React.ReactElement): React.ReactElement => {
+ if (loading) {
+ return ;
+ }
+ return originNode || null;
};
return (
@@ -91,7 +119,6 @@ const SealAutoComplete: React.FC<
required={required}
description={description}
disabled={props.disabled}
- addAfter={renderAfter()}
onClick={handleClickWrapper}
>
diff --git a/src/components/seal-form/components/not-found-content.tsx b/src/components/seal-form/components/not-found-content.tsx
new file mode 100644
index 00000000..6d7bc13d
--- /dev/null
+++ b/src/components/seal-form/components/not-found-content.tsx
@@ -0,0 +1,43 @@
+import { LoadingOutlined } from '@ant-design/icons';
+import { useIntl } from '@umijs/max';
+import { Empty, Typography } from 'antd';
+import React from 'react';
+import styled from 'styled-components';
+
+const { Link } = Typography;
+
+const NoContent = styled.div`
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ padding-block: 12px;
+`;
+
+export const LoadingContent: React.FC = () => {
+ return (
+
+
+
+
+
+ );
+};
+
+const NotFoundContent: React.FC<{
+ loading?: boolean;
+ notFoundContent: React.ReactNode;
+}> = ({ loading, notFoundContent }) => {
+ const intl = useIntl();
+ if (loading) {
+ return ;
+ }
+ return (
+
+ {notFoundContent || (
+
+ )}
+
+ );
+};
+
+export default NotFoundContent;
diff --git a/src/components/seal-form/seal-select.tsx b/src/components/seal-form/seal-select.tsx
index 51137b92..a2ef5112 100644
--- a/src/components/seal-form/seal-select.tsx
+++ b/src/components/seal-form/seal-select.tsx
@@ -5,6 +5,7 @@ import { Form } from 'antd';
import { cloneDeep } from 'lodash';
import React, { useEffect, useMemo, useRef, useState } from 'react';
import BaseSelect from './base/select';
+import NotFoundContent from './components/not-found-content';
import { SealFormItemProps } from './types';
import Wrapper from './wrapper';
import SelectWrapper from './wrapper/select';
@@ -20,6 +21,7 @@ const SealSelect: React.FC = (props) => {
allowNull,
isInFormItems = true,
notFoundContent = null,
+ loading,
...rest
} = props;
const intl = useIntl();
@@ -107,7 +109,12 @@ const SealSelect: React.FC = (props) => {
onFocus={handleOnFocus}
onBlur={handleOnBlur}
onChange={handleChange}
- notFoundContent={notFoundContent}
+ notFoundContent={
+
+ }
>
{children}
diff --git a/src/config/global.d.ts b/src/config/global.d.ts
index 693ecb76..2a059853 100644
--- a/src/config/global.d.ts
+++ b/src/config/global.d.ts
@@ -39,6 +39,11 @@ declare namespace Global {
meta?: Record;
} & Partial;
+ type BaseOptionGroup = {
+ label: string;
+ options?: BaseOption[];
+ };
+
interface HintOptions {
label: string;
value: string;
diff --git a/src/pages/cluster-management/components/cloud-provider-form.tsx b/src/pages/cluster-management/components/cloud-provider-form.tsx
index fa7ca274..f328f335 100644
--- a/src/pages/cluster-management/components/cloud-provider-form.tsx
+++ b/src/pages/cluster-management/components/cloud-provider-form.tsx
@@ -3,7 +3,6 @@ import SealSelect from '@/components/seal-form/seal-select';
import { PageAction } from '@/config';
import { PageActionType } from '@/config/types';
import useAppUtils from '@/hooks/use-app-utils';
-import { LoadingOutlined } from '@ant-design/icons';
import { Link, useIntl } from '@umijs/max';
import { Form } from 'antd';
import { useAtom } from 'jotai';
@@ -13,13 +12,6 @@ import styled from 'styled-components';
import { ClusterFormData as FormData } from '../config/types';
import { useProviderRegions } from '../hooks/use-provider-regions';
-type OptionData = {
- label: string;
- datacenter: string;
- value: string | number;
- icon: string;
-};
-
const OptionItem = styled.div`
display: flex;
align-items: center;
@@ -42,13 +34,6 @@ const OptionItem = styled.div`
}
`;
-const NoContent = styled.div`
- display: flex;
- justify-content: center;
- align-items: center;
- padding-block: 12px;
-`;
-
interface CloudProviderProps {
provider: string; // 'kubernetes' | 'digitalocean';
credentialList: Global.BaseOption[];
@@ -56,22 +41,6 @@ interface CloudProviderProps {
credentialID?: number;
}
-const NotFoundContent: React.FC<{ loading: boolean }> = ({ loading }) => {
- const intl = useIntl();
- if (loading) {
- return (
-
-
-
- );
- }
- return (
-
- {intl.formatMessage({ id: 'clusters.create.noRegions' })}
-
- );
-};
-
const NotFoundCredentialContent: React.FC = () => {
const [, setFromClusterCreation] = useAtom(fromClusterCreationAtom);
const intl = useIntl();
@@ -81,11 +50,9 @@ const NotFoundCredentialContent: React.FC = () => {
};
return (
-
-
- {intl.formatMessage({ id: 'clusters.button.addCredential' })}
-
-
+
+ {intl.formatMessage({ id: 'clusters.button.addCredential' })}
+
);
};
@@ -204,7 +171,9 @@ const CloudProvider: React.FC = (props) => {
labelRender={labelRender}
optionRender={optionRender}
onChange={handleRegionChange}
- notFoundContent={}
+ notFoundContent={intl.formatMessage({
+ id: 'clusters.create.noRegions'
+ })}
>
>
diff --git a/src/pages/llmodels/forms/speculative-decode.tsx b/src/pages/llmodels/forms/speculative-decode.tsx
index 9d92be9e..277e926a 100644
--- a/src/pages/llmodels/forms/speculative-decode.tsx
+++ b/src/pages/llmodels/forms/speculative-decode.tsx
@@ -4,19 +4,12 @@ import SealInputNumber from '@/components/seal-form/input-number';
import SealInput from '@/components/seal-form/seal-input';
import SealSelect from '@/components/seal-form/seal-select';
import useAppUtils from '@/hooks/use-app-utils';
-import useDeferredRequest from '@/hooks/use-deferred-request';
import { useIntl } from '@umijs/max';
import { Form } from 'antd';
-import _ from 'lodash';
-import { useEffect, useRef, useState } from 'react';
-import {
- queryDraftModelList,
- queryHuggingfaceModels,
- queryModelScopeModels
-} from '../apis';
-import { modelSourceMap } from '../config';
+import { useRef } from 'react';
import { useFormContext } from '../config/form-context';
import { FormData } from '../config/types';
+import useQueryDraftModels from '../hooks/use-query-draftModels';
const AlgorithmMap = {
Eagle3: 'eagle3',
@@ -34,144 +27,12 @@ const SpeculativeDecode = () => {
form
);
const algorithm = Form.useWatch(['speculative_config', 'algorithm'], form);
- const [draftModelList, setDraftModelList] = useState<
- Global.BaseOption[] }>[]
- >([]);
- const presetDraftModelListRef = useRef[]>([]);
const speculativeConfigRef = useRef({});
- const axiosTokenRef = useRef(null);
- const [loading, setLoading] = useState(false);
- const fetchDraftModels = async () => {
- const response = await queryDraftModelList({
- page: 1,
- perPage: 100
+ const { draftModelList, loading, resetDraftModels, onSearch } =
+ useQueryDraftModels({
+ source
});
- const options = response.items.map((item) => ({
- label: item.name,
- value: item.name
- }));
- presetDraftModelListRef.current = options;
- setDraftModelList(options);
- };
-
- const getHuggingfaceModels = async (query: string) => {
- if (axiosTokenRef.current) {
- axiosTokenRef.current.abort();
- }
- axiosTokenRef.current = new AbortController();
- try {
- const params = {
- limit: 10,
- search: {
- query: query
- }
- };
- setLoading(true);
- const data = await queryHuggingfaceModels(params, {
- signal: axiosTokenRef.current.signal
- });
- const list = _.map(data || [], (item: any) => {
- return {
- value: item.name,
- label: item.name
- };
- });
-
- const catalogModelList =
- presetDraftModelListRef.current.length > 0
- ? [
- {
- label: `${intl.formatMessage({ id: 'models.form.source' })}: ${intl.formatMessage({ id: 'menu.models.modelCatalog' })}`,
- title: `${intl.formatMessage({ id: 'models.form.source' })}: ${intl.formatMessage({ id: 'menu.models.modelCatalog' })}`,
- options: presetDraftModelListRef.current || []
- }
- ]
- : [];
-
- setDraftModelList([
- ...catalogModelList,
- {
- label: `${intl.formatMessage({ id: 'models.form.source' })}: Hugging Face`,
- title: `${intl.formatMessage({ id: 'models.form.source' })}: Hugging Face`,
- options: list
- }
- ]);
- } catch (error) {
- setDraftModelList(presetDraftModelListRef.current);
- } finally {
- setLoading(false);
- }
- };
-
- const getModelScopeModels = async (query: string) => {
- if (axiosTokenRef.current) {
- axiosTokenRef.current.abort();
- }
- axiosTokenRef.current = new AbortController();
- try {
- const params = {
- Name: query,
- PageSize: 10,
- PageNumber: 1,
- tasks: []
- };
- setLoading(true);
- const data = await queryModelScopeModels(params, {
- signal: axiosTokenRef.current.signal
- });
- const list = _.map(
- _.get(data, 'Data.Model.Models') || [],
- (item: any) => {
- return {
- label: `${item.Path}/${item.Name}`,
- value: `${item.Path}/${item.Name}`
- };
- }
- );
-
- const catalogModelList =
- presetDraftModelListRef.current.length > 0
- ? [
- {
- label: `${intl.formatMessage({ id: 'models.form.source' })}: ${intl.formatMessage({ id: 'menu.models.modelCatalog' })}`,
- title: `${intl.formatMessage({ id: 'models.form.source' })}: ${intl.formatMessage({ id: 'menu.models.modelCatalog' })}`,
- options: presetDraftModelListRef.current || []
- }
- ]
- : [];
-
- setDraftModelList([
- ...catalogModelList,
- {
- label: `${intl.formatMessage({ id: 'models.form.source' })}: ModelScope`,
- title: `${intl.formatMessage({ id: 'models.form.source' })}: ModelScope`,
- options: list
- }
- ]);
- } catch (error) {
- setDraftModelList(presetDraftModelListRef.current);
- } finally {
- setLoading(false);
- }
- };
-
- const handleOnSearch = async (value: string) => {
- if (!value) {
- setDraftModelList(presetDraftModelListRef.current);
- return;
- }
- if (source === modelSourceMap.huggingface_value) {
- await getHuggingfaceModels(value);
- } else if (source === modelSourceMap.modelscope_value) {
- await getModelScopeModels(value);
- }
- };
-
- const { run: onSearch } = useDeferredRequest(
- (value: string) => handleOnSearch(value),
- 150
- );
const handleSpeculativeEnabledChange = (e: any) => {
if (e.target.checked) {
@@ -191,11 +52,11 @@ const SpeculativeDecode = () => {
}
};
- useEffect(() => {
- if (algorithm === AlgorithmMap.Eagle3) {
- fetchDraftModels();
+ const handleAlgorithemChange = (value: string) => {
+ if (value === AlgorithmMap.Eagle3) {
+ resetDraftModels();
}
- }, [algorithm]);
+ };
return (
<>
@@ -228,11 +89,14 @@ const SpeculativeDecode = () => {
>
@@ -252,6 +116,9 @@ const SpeculativeDecode = () => {
[]>([]);
+ const axiosTokenRef = useRef(null);
+ const [loading, setLoading] = useState(false);
+ const [draftModelList, setDraftModelList] = useState<
+ Global.BaseOptionGroup[]
+ >([]);
+
+ const fetchDraftModels = async () => {
+ const response = await queryDraftModelList({
+ page: 1,
+ perPage: 100
+ });
+ const options = response.items.map((item) => ({
+ label: item.name,
+ value: item.name
+ }));
+ presetDraftModelListRef.current = options;
+ setDraftModelList(options);
+ };
+
+ const generateSourceLabel = (source: string) => {
+ let label = '';
+ const sourceLabel = intl.formatMessage({ id: 'models.form.source' });
+ if (source === modelSourceMap.huggingface_value) {
+ label = `${sourceLabel}: Hugging Face`;
+ }
+
+ if (source === modelSourceMap.modelscope_value) {
+ label = `${sourceLabel}: ModelScope`;
+ }
+
+ if (source === 'catalog') {
+ label = `${sourceLabel}: ${intl.formatMessage({ id: 'menu.models.modelCatalog' })}`;
+ }
+
+ return {
+ label: label
+ };
+ };
+
+ const getHuggingfaceModels = async (query: string) => {
+ if (axiosTokenRef.current) {
+ axiosTokenRef.current.abort();
+ }
+ axiosTokenRef.current = new AbortController();
+ try {
+ const params = {
+ limit: 10,
+ search: {
+ query: query
+ }
+ };
+ setLoading(true);
+ const data = await queryHuggingfaceModels(params, {
+ signal: axiosTokenRef.current.signal
+ });
+ const list = _.map(data || [], (item: any) => {
+ return {
+ value: item.name,
+ label: item.name
+ };
+ });
+
+ const catalogModelList =
+ presetDraftModelListRef.current.length > 0
+ ? [
+ {
+ ...generateSourceLabel('catalog'),
+ options: presetDraftModelListRef.current || []
+ }
+ ]
+ : [];
+
+ setDraftModelList([
+ ...catalogModelList,
+ {
+ ...generateSourceLabel(modelSourceMap.huggingface_value),
+ options: list
+ }
+ ]);
+ } catch (error) {
+ setDraftModelList(presetDraftModelListRef.current);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const getModelScopeModels = async (query: string) => {
+ if (axiosTokenRef.current) {
+ axiosTokenRef.current.abort();
+ }
+ axiosTokenRef.current = new AbortController();
+ try {
+ const params = {
+ Name: query,
+ PageSize: 10,
+ PageNumber: 1,
+ tasks: []
+ };
+ setLoading(true);
+ const data = await queryModelScopeModels(params, {
+ signal: axiosTokenRef.current.signal
+ });
+
+ const list = _.map(
+ _.get(data, 'Data.Model.Models') || [],
+ (item: any) => {
+ return {
+ label: `${item.Path}/${item.Name}`,
+ value: `${item.Path}/${item.Name}`
+ };
+ }
+ );
+
+ const catalogModelList =
+ presetDraftModelListRef.current.length > 0
+ ? [
+ {
+ ...generateSourceLabel('catalog'),
+ options: presetDraftModelListRef.current || []
+ }
+ ]
+ : [];
+
+ setDraftModelList([
+ ...catalogModelList,
+ {
+ ...generateSourceLabel(modelSourceMap.modelscope_value),
+ options: list
+ }
+ ]);
+ } catch (error) {
+ setDraftModelList(presetDraftModelListRef.current);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const handleOnSearch = async (value: string) => {
+ if (!value) {
+ setDraftModelList(presetDraftModelListRef.current);
+ return;
+ }
+ if (source === modelSourceMap.huggingface_value) {
+ await getHuggingfaceModels(value);
+ } else if (source === modelSourceMap.modelscope_value) {
+ await getModelScopeModels(value);
+ }
+ };
+
+ const { run: onSearch, cancel: cancelSearch } = useRequest(
+ (value: string) => handleOnSearch(value),
+ {
+ manual: true,
+ debounceWait: 300
+ }
+ );
+
+ const resetDraftModels = () => {
+ setDraftModelList(presetDraftModelListRef.current);
+ };
+
+ useEffect(() => {
+ fetchDraftModels();
+ return () => {
+ if (axiosTokenRef.current) {
+ axiosTokenRef.current.abort();
+ }
+ cancelSearch();
+ };
+ }, []);
+
+ return {
+ draftModelList,
+ loading,
+ fetchDraftModels,
+ onSearch,
+ resetDraftModels
+ };
+}