diff --git a/config/routes.ts b/config/routes.ts index 5840ae33..08599f9f 100644 --- a/config/routes.ts +++ b/config/routes.ts @@ -119,9 +119,9 @@ export default [ name: 'accesses', path: '/models/accesses', key: 'accesses', - icon: 'icon-extension-outline', - selectedIcon: 'icon-extension-filled', - defaultIcon: 'icon-extension-outline', + icon: 'icon-captive_portal', + selectedIcon: 'icon-captive_portal', + defaultIcon: 'icon-captive_portal', access: 'canSeeAdmin', component: './model-access/index' }, diff --git a/src/components/label-selector/autocomplete-item.tsx b/src/components/label-selector/autocomplete-item.tsx index 4d51194c..bce8bf7b 100644 --- a/src/components/label-selector/autocomplete-item.tsx +++ b/src/components/label-selector/autocomplete-item.tsx @@ -38,7 +38,7 @@ const LabelItem: React.FC = ({ }) => { const intl = useIntl(); const [open, setOpen] = useState(false); - const { options } = useLabelSelectorContext(); + const { options, placeholder = [] } = useLabelSelectorContext(); const keyOptions = useMemo(() => { return options?.filter( @@ -98,7 +98,10 @@ const LabelItem: React.FC = ({ options={keyOptions} disabled={disabled} checkStatus="success" - label={intl.formatMessage({ id: 'common.input.key' })} + label={ + placeholder?.[0] || + intl.formatMessage({ id: 'common.input.key' }) + } value={label.key} onChange={handleOnKeyChange} onBlur={(e: any) => handleKeyOnBlur(e, 'key')} @@ -113,7 +116,10 @@ const LabelItem: React.FC = ({ options={valueOptions} disabled={disabled} checkStatus={label.value ? 'success' : ''} - label={intl.formatMessage({ id: 'common.input.value' })} + label={ + placeholder?.[1] || + intl.formatMessage({ id: 'common.input.value' }) + } value={label.value} onChange={handleOnValueChange} onBlur={(e: any) => onBlur?.(e, 'value')} diff --git a/src/components/label-selector/context.ts b/src/components/label-selector/context.ts index 93304790..d13a49d1 100644 --- a/src/components/label-selector/context.ts +++ b/src/components/label-selector/context.ts @@ -6,6 +6,7 @@ interface LabelSelectorContextProps { value: string | number; children?: { label: string; value: string | number }[]; }>; + placeholder?: string[]; currentData?: Record; } diff --git a/src/components/label-selector/index.tsx b/src/components/label-selector/index.tsx index 17304cab..38eb85ee 100644 --- a/src/components/label-selector/index.tsx +++ b/src/components/label-selector/index.tsx @@ -10,6 +10,7 @@ interface LabelSelectorProps { description?: React.ReactNode; disabled?: boolean; isAutoComplete?: boolean; + enablePaste?: boolean; onChange?: (labels: Record) => void; onBlur?: (e: any, type: string, index: number) => void; onDelete?: (index: number) => void; @@ -24,7 +25,8 @@ const LabelSelector: React.FC = ({ label, btnText, description, - isAutoComplete + isAutoComplete, + enablePaste = true }) => { const intl = useIntl(); const [labelsData, setLabelsData] = useState({}); @@ -73,6 +75,7 @@ const LabelSelector: React.FC = ({ e: React.ClipboardEvent, index: number ) => { + if (!enablePaste) return; const clipboardText = e.clipboardData.getData('text'); if (!clipboardText || clipboardText.indexOf('=') === -1) return; e.preventDefault(); diff --git a/src/components/label-selector/inner.tsx b/src/components/label-selector/inner.tsx index 0e017976..3ea172e9 100644 --- a/src/components/label-selector/inner.tsx +++ b/src/components/label-selector/inner.tsx @@ -1,6 +1,5 @@ -import { useIntl } from '@umijs/max'; import _ from 'lodash'; -import React, { useEffect } from 'react'; +import React from 'react'; import AutoCompleteItem from './autocomplete-item'; import LabelItem from './label-item'; import Wrapper from './wrapper'; @@ -33,12 +32,6 @@ const Inner: React.FC = ({ description, isAutoComplete }) => { - const intl = useIntl(); - - useEffect(() => { - console.log('labels changed in Inner', labels); - }, [labels]); - const updateLabels = (list: { key: string; value: string }[]) => { const newLabels = _.reduce( list, diff --git a/src/components/label-selector/label-item.tsx b/src/components/label-selector/label-item.tsx index 905685c2..c0443a4b 100644 --- a/src/components/label-selector/label-item.tsx +++ b/src/components/label-selector/label-item.tsx @@ -1,5 +1,4 @@ import SealInput from '@/components/seal-form/seal-input'; -import { Global } from '@/config/global'; import { MinusOutlined } from '@ant-design/icons'; import { useIntl } from '@umijs/max'; import { Button, Tooltip } from 'antd'; diff --git a/src/pages/_components/transfer.tsx b/src/pages/_components/transfer.tsx index 2c531dfd..9a663c2c 100644 --- a/src/pages/_components/transfer.tsx +++ b/src/pages/_components/transfer.tsx @@ -6,10 +6,10 @@ import styled from 'styled-components'; type TransferKey = string | number | bigint; const TransferWrap = styled.div` - .ant-transfer-list { + .ant-transfer-section { width: 100%; height: 300px; - .ant-transfer-list-header-dropdown { + .anticon-more { display: none; } .ant-input-outlined { @@ -18,7 +18,7 @@ const TransferWrap = styled.div` border-radius: 4px; } } - .ant-transfer-operation { + .ant-transfer-actions { margin: 0 16px; gap: 12px; .ant-btn-icon-only { diff --git a/src/pages/maas-provider/forms/advance-config.tsx b/src/pages/maas-provider/forms/advance-config.tsx index 6e849345..1db44b5a 100644 --- a/src/pages/maas-provider/forms/advance-config.tsx +++ b/src/pages/maas-provider/forms/advance-config.tsx @@ -5,7 +5,7 @@ import { useIntl } from '@umijs/max'; import { Form } from 'antd'; import React, { forwardRef, useImperativeHandle } from 'react'; import { maasProviderType } from '../config'; -import ProxyConfig from './proxy_config'; +import ProxyConfig from './proxy-config'; const AdvanceConfig: React.FC<{ action: PageActionType; diff --git a/src/pages/maas-provider/forms/index.tsx b/src/pages/maas-provider/forms/index.tsx index 8dac365b..463fd2a0 100644 --- a/src/pages/maas-provider/forms/index.tsx +++ b/src/pages/maas-provider/forms/index.tsx @@ -15,7 +15,7 @@ import { } from 'react'; import { maasProviderType } from '../config'; import FormContext from '../config/form-context'; -import { MaasProviderItem as ListItem } from '../config/types'; +import { FormData, MaasProviderItem as ListItem } from '../config/types'; import AccessToken from './access-token'; import AdvanceConfig from './advance-config'; import Basic from './basic'; @@ -26,7 +26,7 @@ interface ProviderFormProps { action: PageActionType; provider?: maasProviderType; currentData?: ListItem; // Used when action is EDIT - onFinish: (values: FormData) => void; + onFinish: (values: FormData) => Promise; } const TABKeysMap = { diff --git a/src/pages/maas-provider/forms/proxy_config.tsx b/src/pages/maas-provider/forms/proxy-config.tsx similarity index 100% rename from src/pages/maas-provider/forms/proxy_config.tsx rename to src/pages/maas-provider/forms/proxy-config.tsx diff --git a/src/pages/model-access/components/access-points.tsx b/src/pages/model-access/components/access-points.tsx index 8a428c1f..f106ee6d 100644 --- a/src/pages/model-access/components/access-points.tsx +++ b/src/pages/model-access/components/access-points.tsx @@ -19,6 +19,7 @@ interface ProviderModelProps { dataList: any[]; provider: string; providerId: number; + onSelect: (val: any, record: any) => void; } interface AccessItemProps { @@ -32,6 +33,12 @@ export const childActionList = [ label: 'common.button.viewlog', icon: }, + { + key: 'fallback', + label: 'Fallback settings', + localel: false, + icon: + }, { key: 'delete', label: 'common.button.delete', @@ -95,20 +102,14 @@ const AccessItem: React.FC = ({ onSelect, data }) => { ); }; -const AccessPoints: React.FC = ({ dataList }) => { +const AccessPoints: React.FC = ({ dataList, onSelect }) => { console.log('AccessPoints dataList:', dataList); return (
{Array(2) .fill(1) .map((item, index) => ( - { - console.log('Selected action:', val, 'on record:', record); - }} - > + ))}
); diff --git a/src/pages/model-access/components/add-access-modal.tsx b/src/pages/model-access/components/add-access-modal.tsx index 5ba75afe..868632af 100644 --- a/src/pages/model-access/components/add-access-modal.tsx +++ b/src/pages/model-access/components/add-access-modal.tsx @@ -3,15 +3,13 @@ import FormDrawer from '@/pages/_components/form-drawer'; import React, { useRef } from 'react'; import { FormData, AccessItem as ListItem } from '../config/types'; -import { maasProviderType } from '../config'; -import ProviderForm from '../forms'; +import ModelAccessForm from '../forms'; type AddModalProps = { title: string; action: PageActionType; open: boolean; currentData?: ListItem; // Used when action is EDIT - provider: maasProviderType | null; onOk: (values: FormData) => void; onCancel: () => void; }; @@ -19,7 +17,6 @@ const AddProvider: React.FC = ({ title, action, open, - provider, currentData, onOk, onCancel @@ -30,7 +27,7 @@ const AddProvider: React.FC = ({ form.current?.submit(); }; - const handleOk = async (data: FormData) => { + const onFinish = async (data: FormData) => { onOk({ ...data }); @@ -49,11 +46,11 @@ const AddProvider: React.FC = ({ onSubmit={handleSubmit} width={600} > - ); diff --git a/src/pages/model-access/components/fallback-modal.tsx b/src/pages/model-access/components/fallback-modal.tsx new file mode 100644 index 00000000..885f7d78 --- /dev/null +++ b/src/pages/model-access/components/fallback-modal.tsx @@ -0,0 +1,50 @@ +import FormDrawer from '@/pages/_components/form-drawer'; +import React, { useRef } from 'react'; +import { FormData, AccessItem as ListItem } from '../config/types'; +import FallbackSettings from '../forms/fallback-settings'; + +type AddModalProps = { + title: string; + open: boolean; + currentData?: ListItem; // Used when action is EDIT + onOk: (values: FormData) => void; + onCancel: () => void; +}; +const AddProvider: React.FC = ({ + title, + open, + currentData, + onOk, + onCancel +}) => { + const form = useRef(null); + + const handleSubmit = () => { + form.current?.submit(); + }; + + const onFinish = async (data: FormData) => { + onOk({ + ...data + }); + }; + + const handleCancel = () => { + form.current?.resetFields(); + onCancel(); + }; + + return ( + + + + ); +}; + +export default AddProvider; diff --git a/src/pages/model-access/config/types.ts b/src/pages/model-access/config/types.ts index b462d350..e752d5b7 100644 --- a/src/pages/model-access/config/types.ts +++ b/src/pages/model-access/config/types.ts @@ -7,6 +7,13 @@ export interface AccessItem { export interface FormData { name: string; - category: string; description: string; + categories: any[]; + meta: Record; + endpoints: { + provider_model_name: string; + weight: number; + model_id: number; + provider_id: number; + }[]; } diff --git a/src/pages/model-access/forms/basic.tsx b/src/pages/model-access/forms/basic.tsx index a3a696a5..1f9b17b1 100644 --- a/src/pages/model-access/forms/basic.tsx +++ b/src/pages/model-access/forms/basic.tsx @@ -1,6 +1,6 @@ import SealInput from '@/components/seal-form/seal-input'; import SealSelect from '@/components/seal-form/seal-select'; -import { categoryOptions } from '@/pages/llmodels/config'; +import { modelCategories } from '@/pages/llmodels/config'; import { useIntl } from '@umijs/max'; import { Form } from 'antd'; import { FormData } from '../config/types'; @@ -14,7 +14,11 @@ const Basic = () => { - + { const intl = useIntl(); const form = Form.useFormInstance(); + const endpoints = Form.useWatch('endpoints', form); + + const handleEndpointsChange = (labels: Record) => { + form.setFieldValue('endpoints', labels); + }; + return ( <> - - - - - - - + + ({ + validator(rule, value) { + if (_.keys(value).length > 0) { + if (_.some(_.keys(value), (k: string) => !value[k])) { + return Promise.reject( + intl.formatMessage( + { + id: 'common.validate.value' + }, + { + name: intl.formatMessage({ + id: 'models.form.selector' + }) + } + ) + ); + } + } + return Promise.resolve(); + } + }) + ]} + > + + + ); }; diff --git a/src/pages/model-access/forms/fallback-settings.tsx b/src/pages/model-access/forms/fallback-settings.tsx new file mode 100644 index 00000000..d2ecdce1 --- /dev/null +++ b/src/pages/model-access/forms/fallback-settings.tsx @@ -0,0 +1,49 @@ +import SealInput from '@/components/seal-form/seal-input'; +import { Checkbox, Form, Typography } from 'antd'; + +const FallbackSettings: React.FC<{ + currentData?: any; + onFinish: (data: any) => void; +}> = ({ currentData, onFinish }) => { + const [form] = Form.useForm(); + + return ( +
+ + + + + + + + + + + + Fallback Status + + + +
+ ); +}; +export default FallbackSettings; diff --git a/src/pages/model-access/forms/index.tsx b/src/pages/model-access/forms/index.tsx index f2448b65..a22b00a5 100644 --- a/src/pages/model-access/forms/index.tsx +++ b/src/pages/model-access/forms/index.tsx @@ -6,8 +6,7 @@ import ScrollSpyTabs from '@/pages/_components/scroll-spy-tabs'; import { useIntl } from '@umijs/max'; import { Form } from 'antd'; import { forwardRef, useImperativeHandle, useRef, useState } from 'react'; -import { maasProviderType } from '../config'; -import { AccessItem as ListItem } from '../config/types'; +import { FormData, AccessItem as ListItem } from '../config/types'; import Basic from './basic'; import Endpoints from './endpoints'; import MetaData from './meta-data'; @@ -15,9 +14,8 @@ import MetaData from './meta-data'; interface ProviderFormProps { ref?: any; action: PageActionType; - provider?: maasProviderType; currentData?: ListItem; // Used when action is EDIT - onFinish: (values: FormData) => void; + onFinish: (values: FormData) => Promise; } const TABKeysMap = { @@ -27,8 +25,8 @@ const TABKeysMap = { ADVANCED: 'advanced' }; -const ProviderForm: React.FC = forwardRef((props, ref) => { - const { action, provider, currentData, onFinish } = props; +const AccessForm: React.FC = forwardRef((props, ref) => { + const { action, currentData, onFinish } = props; const intl = useIntl(); const { getScrollElementScrollableHeight } = useWrapperContext(); const [activeKey, setActiveKey] = useState([TABKeysMap.BASIC]); @@ -86,7 +84,7 @@ const ProviderForm: React.FC = forwardRef((props, ref) => { }} getScrollElementScrollableHeight={getScrollElementScrollableHeight} > -
+ = forwardRef((props, ref) => { key: TABKeysMap.ENDPOINTS, label: 'Endpoints', forceRender: true, - children: ( - - ) + children: } ]} > @@ -114,4 +110,4 @@ const ProviderForm: React.FC = forwardRef((props, ref) => { ); }); -export default ProviderForm; +export default AccessForm; diff --git a/src/pages/model-access/forms/meta-data.tsx b/src/pages/model-access/forms/meta-data.tsx index ad450e7d..0c711743 100644 --- a/src/pages/model-access/forms/meta-data.tsx +++ b/src/pages/model-access/forms/meta-data.tsx @@ -1,36 +1,71 @@ -import SealInput from '@/components/seal-form/seal-input'; +import LabelSelector from '@/components/label-selector'; +import { LabelSelectorContext } from '@/components/label-selector/context'; import { useIntl } from '@umijs/max'; import { Form } from 'antd'; +import _ from 'lodash'; import { FormData } from '../config/types'; const MetaData = () => { const intl = useIntl(); const form = Form.useFormInstance(); + const metadata = Form.useWatch('meta', form); + + const handleMetadataChange = (labels: Record) => { + form.setFieldValue('meta', labels); + }; + return ( <> - - - - - - - + + ({ + validator(rule, value) { + if (_.keys(value).length > 0) { + if (_.some(_.keys(value), (k: string) => !value[k])) { + return Promise.reject( + intl.formatMessage( + { + id: 'common.validate.value' + }, + { + name: intl.formatMessage({ + id: 'models.form.selector' + }) + } + ) + ); + } + } + return Promise.resolve(); + } + }) + ]} + > + + + ); }; diff --git a/src/pages/model-access/hooks/use-create-access.ts b/src/pages/model-access/hooks/use-create-access.ts index fe6c121b..1311dc9f 100644 --- a/src/pages/model-access/hooks/use-create-access.ts +++ b/src/pages/model-access/hooks/use-create-access.ts @@ -1,37 +1,41 @@ import { PageAction } from '@/config'; import { PageActionType } from '@/config/types'; import { useState } from 'react'; -import { maasProviderType } from '../config'; import { AccessItem as ListItem } from '../config/types'; -const useCreateAccess = (options: { refresh: () => void }) => { +const useCreateAccess = (options?: { refresh: () => void }) => { const [openModalStatus, setOpenModalStatus] = useState<{ open: boolean; action: PageActionType; currentData?: ListItem; title: string; - provider: maasProviderType | null; }>({ open: false, action: PageAction.CREATE, currentData: undefined, - title: '', - provider: null + title: '' }); - const openModal = (action: PageActionType, currentData?: ListItem) => { + const openModal = ( + action: PageActionType, + title: string, + currentData?: ListItem + ) => { setOpenModalStatus({ - ...openModalStatus, open: true, action, currentData, - title: action === PageAction.CREATE ? 'Create Access' : 'Edit Access' + title: title }); }; const closeModal = () => { - setOpenModalStatus({ ...openModalStatus, open: false }); - options.refresh(); + setOpenModalStatus({ + open: false, + action: PageAction.CREATE, + currentData: undefined, + title: '' + }); }; return { diff --git a/src/pages/model-access/hooks/use-fallback-settings.ts b/src/pages/model-access/hooks/use-fallback-settings.ts new file mode 100644 index 00000000..f4b45292 --- /dev/null +++ b/src/pages/model-access/hooks/use-fallback-settings.ts @@ -0,0 +1,49 @@ +import { PageAction } from '@/config'; +import { PageActionType } from '@/config/types'; +import { useState } from 'react'; +import { AccessItem as ListItem } from '../config/types'; + +const useFallbackSettings = (options?: { refresh: () => void }) => { + const [openModalStatus, setOpenModalStatus] = useState<{ + open: boolean; + action: PageActionType; + currentData?: ListItem; + title: string; + }>({ + open: false, + action: PageAction.CREATE, + currentData: undefined, + title: '' + }); + + const openModal = ( + action: PageActionType, + title: string, + currentData?: ListItem + ) => { + setOpenModalStatus({ + open: true, + action, + currentData, + title: title + }); + }; + + const closeModal = () => { + setOpenModalStatus({ + open: false, + action: PageAction.CREATE, + currentData: undefined, + title: '' + }); + }; + + return { + openFallbackSettingsModalStatus: openModalStatus, + setOpenFallbackSettingsModalStatus: setOpenModalStatus, + openFallbackSettingsModal: openModal, + closeFallbackSettingsModal: closeModal + }; +}; + +export default useFallbackSettings; diff --git a/src/pages/model-access/index.tsx b/src/pages/model-access/index.tsx index 67d41e47..c1c39538 100644 --- a/src/pages/model-access/index.tsx +++ b/src/pages/model-access/index.tsx @@ -26,11 +26,13 @@ import { } from './apis'; import AccessPoints from './components/access-points'; import AddAccessModal from './components/add-access-modal'; +import FallbackModal from './components/fallback-modal'; import { maasProviderOptions } from './config'; import { mockDataList } from './config/mock'; import { FormData, AccessItem as ListItem } from './config/types'; import useAccessColumns from './hooks/use-access-columns'; import useCreateAccess from './hooks/use-create-access'; +import useFallbackSettings from './hooks/use-fallback-settings'; const Accesses: React.FC = () => { const { @@ -57,17 +59,20 @@ const Accesses: React.FC = () => { const { handleExpandChange, handleExpandAll, expandedRowKeys } = useExpandedRowKeys(expandAtom); const intl = useIntl(); + const { openAccessModalStatus, openAccessModal, closeAccessModal } = + useCreateAccess(); + const { - openAccessModalStatus, - setOpenAccessModalStatus, - openAccessModal, - closeAccessModal - } = useCreateAccess({ - refresh: handleSearch - }); + openFallbackSettingsModalStatus, + openFallbackSettingsModal, + closeFallbackSettingsModal + } = useFallbackSettings(); const handleClickDropdown = () => { - openAccessModal('create'); + openAccessModal( + PageAction.CREATE, + intl.formatMessage({ id: 'accesses.button.add' }) + ); }; const handleModalOk = async (data: FormData) => { @@ -82,39 +87,25 @@ const Accesses: React.FC = () => { }); } fetchData(); - setOpenAccessModalStatus({ - open: false, - action: PageAction.CREATE, - currentData: undefined, - title: '', - provider: null - }); + closeAccessModal(); message.success(intl.formatMessage({ id: 'common.message.success' })); } catch (error) {} }; const handleModalCancel = () => { console.log('handleModalCancel'); - setOpenAccessModalStatus({ - open: false, - action: PageAction.CREATE, - currentData: undefined, - title: '', - provider: null - }); + closeAccessModal(); }; const handleEditProvider = (row: ListItem) => { - setOpenAccessModalStatus({ - open: true, - action: PageAction.EDIT, - currentData: row, - title: intl.formatMessage( + openAccessModal( + PageAction.EDIT, + intl.formatMessage( { id: 'clusters.edit.cluster' }, { cluster: row.name } ), - provider: row.provider - }); + row + ); }; const handleSelect = useMemoizedFn((val: any, row: ListItem) => { @@ -154,6 +145,12 @@ const Accesses: React.FC = () => { handleTableChange({}, {}, order, { action: 'sort' }); }; + const onChildSelect = useMemoizedFn((val: any, record: any) => { + if (val === 'fallback') { + openFallbackSettingsModal(PageAction.EDIT, 'Fallback Settings', record); + } + }); + const renderChildren = ( list: any, options: { parent?: any; [key: string]: any } @@ -163,6 +160,7 @@ const Accesses: React.FC = () => { dataList={list} provider={options.parent?.provider} providerId={options.parent?.id} + onSelect={onChildSelect} /> ); }; @@ -173,7 +171,6 @@ const Accesses: React.FC = () => { <> { { onCancel={handleModalCancel} onOk={handleModalOk} > + );