diff --git a/src/components/delete-modal/index.tsx b/src/components/delete-modal/index.tsx index f841f3a9..387f348c 100644 --- a/src/components/delete-modal/index.tsx +++ b/src/components/delete-modal/index.tsx @@ -72,6 +72,7 @@ interface Configuration { checked: boolean; } +// default need to pass content and operation const DeleteModal = forwardRef((props, ref) => { const intl = useIntl(); const { styles } = useStyles(); diff --git a/src/components/icon-font/index.tsx b/src/components/icon-font/index.tsx index 98032ffb..3f015b2a 100644 --- a/src/components/icon-font/index.tsx +++ b/src/components/icon-font/index.tsx @@ -2,7 +2,7 @@ import { createFromIconfontCN } from '@ant-design/icons'; // import './iconfont/iconfont.js'; const IconFont = createFromIconfontCN({ - scriptUrl: '//at.alicdn.com/t/c/font_4613488_339f1qeidmn.js' + scriptUrl: '//at.alicdn.com/t/c/font_4613488_mis8x88kojd.js' }); export default IconFont; diff --git a/src/components/page-tools/index.tsx b/src/components/page-tools/index.tsx index 0bfb300c..177986b6 100644 --- a/src/components/page-tools/index.tsx +++ b/src/components/page-tools/index.tsx @@ -156,6 +156,11 @@ export const FilterBar: React.FC = (props) => { {handleClickPrimary ? ( actionType === 'dropdown' ? ( = (props) => { ( - + + + )} > ); diff --git a/src/locales/en-US/backends.ts b/src/locales/en-US/backends.ts index 831141df..e9561221 100644 --- a/src/locales/en-US/backends.ts +++ b/src/locales/en-US/backends.ts @@ -41,5 +41,8 @@ export default { 'If specified, the ENTRYPOINT defined in the image will be ignored, and the command below will be used as the container startup entrypoint.', 'backend.form.defaultEnvironment': 'Default Environment Variables', 'backend.recommendModels': 'Recommended Models', - 'backend.filter.source': 'Filter by source' + 'backend.filter.source': 'Filter by source', + 'backend.add.custom': 'Custom', + 'backend.add.community': 'Community Repository', + 'backend.community.title': 'Community Backends' }; diff --git a/src/locales/ja-JP/backends.ts b/src/locales/ja-JP/backends.ts index 831141df..e9561221 100644 --- a/src/locales/ja-JP/backends.ts +++ b/src/locales/ja-JP/backends.ts @@ -41,5 +41,8 @@ export default { 'If specified, the ENTRYPOINT defined in the image will be ignored, and the command below will be used as the container startup entrypoint.', 'backend.form.defaultEnvironment': 'Default Environment Variables', 'backend.recommendModels': 'Recommended Models', - 'backend.filter.source': 'Filter by source' + 'backend.filter.source': 'Filter by source', + 'backend.add.custom': 'Custom', + 'backend.add.community': 'Community Repository', + 'backend.community.title': 'Community Backends' }; diff --git a/src/locales/ru-RU/backends.ts b/src/locales/ru-RU/backends.ts index 6bc67092..081499f5 100644 --- a/src/locales/ru-RU/backends.ts +++ b/src/locales/ru-RU/backends.ts @@ -41,11 +41,17 @@ export default { 'Если указано, ENTRYPOINT, заданный в образе, будет проигнорирован, а вместо него для запуска контейнера будет использована команда ниже.', 'backend.form.defaultEnvironment': 'Default Environment Variables', 'backend.recommendModels': 'Recommended Models', - 'backend.filter.source': 'Filter by source' + 'backend.filter.source': 'Filter by source', + 'backend.add.custom': 'Custom', + 'backend.add.community': 'Community Repository', + 'backend.community.title': 'Community Backends' }; // ========== To-Do: Translate Keys (Remove After Translation) ========== // 1. 'backend.form.defaultEnvironment': 'Default Environment Variables', // 2. 'backend.recommendModels': 'Recommended Models', -// 3. 'backend.filter.source': 'Filter by source' +// 3. 'backend.filter.source': 'Filter by source', +// 4. 'backend.add.custom': 'Custom', +// 5. 'backend.add.community': 'Community Repository', +// 6. 'backend.community.title': 'Community Backends' // ========== End of To-Do List ========== diff --git a/src/locales/zh-CN/backends.ts b/src/locales/zh-CN/backends.ts index 2cee6e80..46979f17 100644 --- a/src/locales/zh-CN/backends.ts +++ b/src/locales/zh-CN/backends.ts @@ -38,5 +38,8 @@ export default { '如指定,镜像中定义的 ENTRYPOINT 将被忽略,下面的命令将作为容器启动入口命令使用。', 'backend.form.defaultEnvironment': '默认环境变量', 'backend.recommendModels': '推荐模型', - 'backend.filter.source': '按来源过滤' + 'backend.filter.source': '按来源过滤', + 'backend.add.custom': '自定义', + 'backend.add.community': '社区仓库', + 'backend.community.title': '社区后端' }; diff --git a/src/pages/backends/apis/index.ts b/src/pages/backends/apis/index.ts index 56a4d5af..c0f212f7 100644 --- a/src/pages/backends/apis/index.ts +++ b/src/pages/backends/apis/index.ts @@ -5,7 +5,9 @@ export const INFERENCE_BACKEND_API = '/inference-backends'; export const FROM_YAML_API = '/from-yaml'; -export async function queryBackendsList(params: Global.SearchParams) { +export async function queryBackendsList( + params: Global.SearchParams & { community?: number } +) { return request>(`${INFERENCE_BACKEND_API}`, { method: 'GET', params diff --git a/src/pages/backends/community/add-community-modal.tsx b/src/pages/backends/community/add-community-modal.tsx new file mode 100644 index 00000000..3e2d3fba --- /dev/null +++ b/src/pages/backends/community/add-community-modal.tsx @@ -0,0 +1,94 @@ +import GSDrawer from '@/components/scroller-modal/gs-drawer'; +import ColumnWrapper from '@/pages/_components/column-wrapper'; +import Separator from '@/pages/llmodels/components/separator'; +import { useIntl } from '@umijs/max'; +import React from 'react'; +import styled from 'styled-components'; +import { ListItem } from '../config/types'; +import useEnableBackend from '../services/use-enable-backend'; +import BackendDetail from './backend-detail'; +import CommunityBackends from './commnity-backends'; + +const Container = styled.div` + display: grid; + grid-template-columns: 1fr 1fr; + height: 100%; +`; + +const AddCommunityModal: React.FC<{ + open: boolean; + onClose: () => void; + onRefresh?: () => void; +}> = ({ open, onClose, onRefresh }) => { + const intl = useIntl(); + const { handleEnableBackend } = useEnableBackend(); + const [currentData, setCurrentData] = React.useState(null); + + const handleOnEnable = async (checked: boolean, data: ListItem) => { + await handleEnableBackend({ + id: data.id, + data: { + ...data, + enabled: checked + } + }); + onRefresh?.(); + }; + + const handleOnClick = (data: ListItem) => { + setCurrentData(data); + }; + return ( + + +
+ + + + +
+ + + +
+
+ ); +}; + +export default AddCommunityModal; diff --git a/src/pages/backends/community/backend-detail.tsx b/src/pages/backends/community/backend-detail.tsx new file mode 100644 index 00000000..d4d3e9d6 --- /dev/null +++ b/src/pages/backends/community/backend-detail.tsx @@ -0,0 +1,163 @@ +import AutoTooltip from '@/components/auto-tooltip'; +import { BulbOutlined } from '@ant-design/icons'; +import { useIntl } from '@umijs/max'; +import { Button, Tag, Typography } from 'antd'; +import _ from 'lodash'; +import React from 'react'; +import styled from 'styled-components'; +import { generateIcon } from '../components/backend-card'; + +const Title = styled.div` + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; + .text { + display: flex; + align-items: center; + gap: 8px; + font-weight: 700; + } +`; + +const Subtitle = styled.div` + display: flex; + align-items: center; + gap: 8px; + font-size: 13px; + color: var(--ant-color-text); + font-weight: 500; + margin-top: 24px; + margin-bottom: 16px; + border-bottom: 1px solid var(--ant-color-split); + padding-bottom: 8px; + .icon { + color: var(--ant-color-text-tertiary); + } +`; + +const Section = styled.div` + display: flex; + flex-direction: column; +`; + +const Content = styled.div` + padding: 0px 8px; + border-radius: var(--ant-border-radius); +`; + +const UL = styled.ul` + margin: 0; + padding-left: 16px; + list-style-type: disc; + display: flex; + flex-direction: column; + gap: 12px; + .label { + font-weight: 500; + font-size: 14px; + } +`; + +const BackendDetail: React.FC<{ + onEnable: (checked: boolean, data: any) => void; + onDisable?: () => void; + currentData?: any; +}> = ({ onEnable, onDisable, currentData }) => { + const intl = useIntl(); + + const renderSupportedFrameworks = () => { + const frameworks = currentData?.framework_index_map || {}; + if (Object.keys(frameworks).length === 0) { + return null; + } + return ( +
    + {Object.entries(frameworks as Record).map( + ([key, value]) => { + return ( +
  • +
    {_.upperCase(key)}
    + {/* + {value?.map?.((v: string, index: number) => ( + + {v} + + ))} + */} +
  • + ); + } + )} +
+ ); + }; + return ( +
+ + <span className="text"> + {generateIcon(currentData || {}, 32)} + <AutoTooltip ghost>{currentData?.backend_name}</AutoTooltip> + </span> + {currentData && ( + <span> + {currentData?.enabled ? ( + <Tag color="geekblue" variant="filled"> + {intl.formatMessage({ id: 'common.status.enabled' })} + </Tag> + ) : ( + <Button + type="primary" + size="middle" + onClick={() => onEnable(true, currentData)} + > + {intl.formatMessage({ id: 'common.button.enable' })} + </Button> + )} + </span> + )} + + {Object.keys(currentData?.framework_index_map || {}).length > 0 && ( +
+ + + + {intl.formatMessage({ id: 'backend.availableFrameworks' })} + + + {renderSupportedFrameworks()} +
+ )} + {/*
+ + + + {intl.formatMessage({ + id: 'backend.form.defaultExecuteCommand' + })} + + + + + +
*/} + {currentData?.description && ( +
+ + + + {intl.formatMessage({ id: 'common.table.description' })} + + + + {currentData?.description} + +
+ )} +
+ ); +}; + +export default BackendDetail; diff --git a/src/pages/backends/community/commnity-backends.tsx b/src/pages/backends/community/commnity-backends.tsx new file mode 100644 index 00000000..fbc46e5d --- /dev/null +++ b/src/pages/backends/community/commnity-backends.tsx @@ -0,0 +1,153 @@ +import IconFont from '@/components/icon-font'; +import ThemeTag from '@/components/tags-wrapper/theme-tag'; +import useTableFetch from '@/hooks/use-table-fetch'; +import { SearchOutlined } from '@ant-design/icons'; +import { useIntl } from '@umijs/max'; +import { Input, Switch, Tooltip } from 'antd'; +import _ from 'lodash'; +import React, { useEffect } from 'react'; +import styled from 'styled-components'; +import { ScrollerContext } from '../../_components/infinite-scroller/use-scroller-context'; +import NoResult from '../../_components/no-result'; +import { INFERENCE_BACKEND_API, queryBackendsList } from '../apis'; +import BackendCard from '../components/backend-card'; +import BackendCardList from '../components/backend-list'; +import { ListItem } from '../config/types'; + +const FilterBox = styled.div` + position: sticky; + top: 0; + width: 100%; + z-index: 100; + margin-bottom: 16px; + background-color: var(--ant-color-bg-container); +`; + +const CommunityBackends: React.FC<{ + onEnable: (checked: boolean, data: ListItem) => void; + onClick: (data: ListItem) => void; +}> = ({ onEnable, onClick }) => { + const intl = useIntl(); + const [currentData, setCurrentData] = React.useState(null); + const { dataSource, loadMore, queryParams, handleSearch, handleNameChange } = + useTableFetch({ + fetchAPI: queryBackendsList, + API: INFERENCE_BACKEND_API, + watch: false, + isInfiniteScroll: true, + contentForDelete: 'backends.title', + defaultQueryParams: { + perPage: 24, + community: 1 + } + }); + + const handleOnClickItem = (data: ListItem) => { + onClick(data); + setCurrentData(data); + }; + + const handleOnEnable = async (checked: boolean, data: ListItem) => { + await onEnable(checked, data); + await handleSearch(); + if (data.id === currentData?.id) { + handleOnClickItem({ + ...data, + enabled: checked + }); + } + }; + + const actionsRenderer = (data: ListItem) => { + return ( + + <> + {data.enabled ? ( + + {intl.formatMessage({ id: 'common.status.enabled' })} + + ) : ( + e.stopPropagation()}> + handleOnEnable(checked, data)} + size="small" + > + + )} + + + ); + }; + useEffect(() => { + if (dataSource.dataList.length > 0 && dataSource.loadend && !currentData) { + handleOnClickItem(dataSource.dataList[0]); + } + }, [dataSource.dataList, dataSource.loadend]); + + const renderItem = (item: ListItem) => { + return ( + + ); + }; + + return ( +
+ + + } + placeholder={intl.formatMessage({ + id: 'common.filter.name' + })} + style={{ width: '100%' }} + allowClear + onChange={handleNameChange} + > + + + + } + filters={_.omit(queryParams, ['sort_by'])} + noFoundText={intl.formatMessage({ + id: 'noresult.backend.nofound' + })} + > + +
+ ); +}; + +export default CommunityBackends; diff --git a/src/pages/backends/components/backend-card.tsx b/src/pages/backends/components/backend-card.tsx index a67ab03e..9326b39f 100644 --- a/src/pages/backends/components/backend-card.tsx +++ b/src/pages/backends/components/backend-card.tsx @@ -124,7 +124,7 @@ const InfoItem = styled.div` display: grid; width: 100%; grid-template-columns: max-content 1fr minmax(0, max-content); - align-items: center; + align-items: end; gap: 8px; color: var(--ant-color-text-tertiary); .icon { @@ -140,10 +140,37 @@ const InfoItem = styled.div` interface BackendCardProps { onClick?: (data: any) => void; onSelect?: (item: any) => void; + actionsRenderer?: (data: ListItem) => React.ReactNode; data: ListItem; + layout?: 'default' | 'community'; + active?: boolean; } -const BackendCard: React.FC = ({ data, onSelect }) => { +export const generateIcon = (data: ListItem, height = 20) => { + if (builtInBackendLogos[data.backend_name]) { + return ; + } + if (data.icon) { + return ; + } + const color = customColors[data.id % customColors.length]; + const icon = customIcons[data.id % customIcons.length]; + + return ( + + {icon} + + ); +}; + +const BackendCard: React.FC = ({ + data, + onClick, + onSelect, + actionsRenderer, + layout = 'default', + active +}) => { const intl = useIntl(); const handleOnSelect = (item: any) => { @@ -152,23 +179,11 @@ const BackendCard: React.FC = ({ data, onSelect }) => { const handleClick = () => { onSelect?.({ action: 'view_versions', data: data }); + onClick?.(data); }; const renderIcon = () => { - if (builtInBackendLogos[data.backend_name]) { - return ; - } - if (data.icon) { - return ; - } - const color = customColors[data.id % customColors.length]; - const icon = customIcons[data.id % customIcons.length]; - - return ( - - {icon} - - ); + return generateIcon(data); }; const actions = useMemo(() => { @@ -180,7 +195,7 @@ const BackendCard: React.FC = ({ data, onSelect }) => { }); }, [data]); - const onClick = (e: React.MouseEvent) => { + const handleonClickAction = (e: React.MouseEvent) => { e.stopPropagation(); }; @@ -278,6 +293,9 @@ const BackendCard: React.FC = ({ data, onSelect }) => { }; const renderSource = () => { + if (layout === 'community') { + return null; + } const source = data.is_built_in ? BackendSourceLabelMap[BackendSourceValueMap.BUILTIN] || '' : BackendSourceLabelMap[data.backend_source] || ''; @@ -285,7 +303,7 @@ const BackendCard: React.FC = ({ data, onSelect }) => { return null; } return ( - = ({ data, onSelect }) => { ] } className="font-400" - variant="outlined" + variant="filled" style={{ borderRadius: 'var(--ant-border-radius)', margin: 0, @@ -304,7 +322,7 @@ const BackendCard: React.FC = ({ data, onSelect }) => { {intl.formatMessage({ id: source })} - + ); }; @@ -330,49 +348,58 @@ const BackendCard: React.FC = ({ data, onSelect }) => { ); }; + const renderActions = () => { + return actionsRenderer ? ( + actionsRenderer(data) + ) : ( + + + + + + ); + }; + return (
{renderIcon()}
- - - - - + {renderActions()} } > {data.backend_name} + {renderSource()} - + {/* {intl.formatMessage({ id: 'models.form.source' })}: - {renderSource()} {renderEnabledTag()} {renderRecommendModels()} - + */} {renderFrameworks()}
diff --git a/src/pages/backends/components/backend-list.tsx b/src/pages/backends/components/backend-list.tsx index 10ba00bb..d088fa45 100644 --- a/src/pages/backends/components/backend-list.tsx +++ b/src/pages/backends/components/backend-list.tsx @@ -44,6 +44,7 @@ interface BackendListProps { activeId: Global.WithFalse; isFirst: boolean; onSelect?: (item: any) => void; + renderItem?: (item: any) => React.ReactNode; } const ListSkeleton: React.FC<{ @@ -70,7 +71,7 @@ const ListSkeleton: React.FC<{ skeletonStyle={{ display: 'flex', flexDirection: 'column', - gap: 26 + gap: 24 }} > @@ -89,7 +90,8 @@ const CardList: React.FC = (props) => { isFirst, defaultSpan = 8, resizable = true, - onSelect + onSelect, + renderItem } = props; const { total, @@ -111,7 +113,13 @@ const CardList: React.FC = (props) => { defaultSpan={defaultSpan} resizable={resizable} dataList={dataList} - renderItem={(item) => } + renderItem={(item) => + renderItem ? ( + renderItem(item) + ) : ( + + ) + } > diff --git a/src/pages/backends/config/index.ts b/src/pages/backends/config/index.ts index 4568a9b5..cc9fd4c8 100644 --- a/src/pages/backends/config/index.ts +++ b/src/pages/backends/config/index.ts @@ -82,26 +82,26 @@ export const backendActions = [ locale: true, icon: icons.Yaml }, - { - label: 'common.button.enable', - value: 'enable', - key: 'enable', - locale: true, - icon: icons.Charger, - show: (record: any) => - !record.enabled && - record.backend_source === BackendSourceValueMap.COMMUNITY - }, - { - label: 'common.button.disable', - value: 'disable', - key: 'disable', - locale: true, - icon: icons.Disabled, - show: (record: any) => - record.enabled && - record.backend_source === BackendSourceValueMap.COMMUNITY - }, + // { + // label: 'common.button.enable', + // value: 'enable', + // key: 'enable', + // locale: true, + // icon: icons.Charger, + // show: (record: any) => + // !record.enabled && + // record.backend_source === BackendSourceValueMap.COMMUNITY + // }, + // { + // label: 'common.button.disable', + // value: 'disable', + // key: 'disable', + // locale: true, + // icon: icons.Disabled, + // show: (record: any) => + // record.enabled && + // record.backend_source === BackendSourceValueMap.COMMUNITY + // }, { label: 'common.button.delete', value: 'delete', diff --git a/src/pages/backends/hooks/use-community-backend.ts b/src/pages/backends/hooks/use-community-backend.ts new file mode 100644 index 00000000..0ef34102 --- /dev/null +++ b/src/pages/backends/hooks/use-community-backend.ts @@ -0,0 +1,40 @@ +import { PageAction } from '@/config'; +import { PageActionType } from '@/config/types'; +import { useState } from 'react'; + +const useCommunityBackend = () => { + const [openModalStatus, setOpenModalStatus] = useState<{ + open: boolean; + action: PageActionType; + title: string; + }>({ + open: false, + action: PageAction.CREATE, + title: '' + }); + + const openModal = (action: PageActionType, title: string) => { + setOpenModalStatus({ + open: true, + title: title, + action + }); + }; + + const closeModal = () => { + setOpenModalStatus({ + open: false, + action: PageAction.CREATE, + title: '' + }); + }; + + return { + openCommunityModalStatus: openModalStatus, + setOpenCommunityModalStatus: setOpenModalStatus, + openCommunityModal: openModal, + closeCommunityModal: closeModal + }; +}; + +export default useCommunityBackend; diff --git a/src/pages/backends/hooks/use-create-backend.tsx b/src/pages/backends/hooks/use-create-backend.tsx new file mode 100644 index 00000000..2e5ce858 --- /dev/null +++ b/src/pages/backends/hooks/use-create-backend.tsx @@ -0,0 +1,69 @@ +import IconFont from '@/components/icon-font'; +import { PageAction } from '@/config'; +import useBodyScroll from '@/hooks/use-body-scroll'; +import { ListItem } from '../config/types'; +import useCommunityBackend from './use-community-backend'; +import useCustomBackend from './use-custom-backend'; + +const useCreateBackend = () => { + const { saveScrollHeight, restoreScrollHeight } = useBodyScroll(); + const { openCommunityModal, closeCommunityModal, openCommunityModalStatus } = + useCommunityBackend(); + const { openCustomModal, closeCustomModal, openCustomModalStatus } = + useCustomBackend(); + + const addItems = [ + { + label: 'backend.add.community', + locale: true, + key: 'community', + value: 'community', + icon: + }, + { + label: 'backend.add.custom', + locale: true, + key: 'custom', + value: 'custom', + icon: + } + ]; + + const handleEditBackend = ( + action: PageAction, + title: string, + row: ListItem + ) => { + openCustomModal(action, title, row); + saveScrollHeight(); + }; + + const handleCloseModal = (type: 'community' | 'custom') => { + if (type === 'community') { + closeCommunityModal(); + } else { + closeCustomModal(); + } + restoreScrollHeight(); + }; + + const handleAddBackend = (type: 'community' | 'custom') => { + if (type === 'community') { + openCommunityModal(PageAction.CREATE, 'Add Community Backend'); + } else { + openCustomModal(PageAction.CREATE, 'Add Custom Backend'); + } + saveScrollHeight(); + }; + + return { + closeBackendModal: handleCloseModal, + addBackend: handleAddBackend, + editBackend: handleEditBackend, + openBackendModalStatus: openCustomModalStatus, // for create custom backend and edit + openCommunityModalStatus: openCommunityModalStatus, // for create community backend + addActions: addItems + }; +}; + +export default useCreateBackend; diff --git a/src/pages/backends/hooks/use-create-backend.ts b/src/pages/backends/hooks/use-custom-backend.ts similarity index 67% rename from src/pages/backends/hooks/use-create-backend.ts rename to src/pages/backends/hooks/use-custom-backend.ts index e8c01c6c..fbd2bd1e 100644 --- a/src/pages/backends/hooks/use-create-backend.ts +++ b/src/pages/backends/hooks/use-custom-backend.ts @@ -1,11 +1,9 @@ import { PageAction } from '@/config'; import { PageActionType } from '@/config/types'; -import useBodyScroll from '@/hooks/use-body-scroll'; import { useState } from 'react'; import { ListItem } from '../config/types'; -const useCreateBackend = () => { - const { saveScrollHeight, restoreScrollHeight } = useBodyScroll(); +const useCustomBackend = () => { const [openModalStatus, setOpenModalStatus] = useState<{ open: boolean; action: PageActionType; @@ -26,7 +24,6 @@ const useCreateBackend = () => { action, currentData: row }); - saveScrollHeight(); }; const closeModal = () => { @@ -36,15 +33,14 @@ const useCreateBackend = () => { currentData: undefined, title: '' }); - restoreScrollHeight(); }; return { - openBackendModalStatus: openModalStatus, - setOpenBackendModalStatus: setOpenModalStatus, - openBackendModal: openModal, - closeBackendModal: closeModal + openCustomModalStatus: openModalStatus, + setOpenCustomModalStatus: setOpenModalStatus, + openCustomModal: openModal, + closeCustomModal: closeModal }; }; -export default useCreateBackend; +export default useCustomBackend; diff --git a/src/pages/backends/index.tsx b/src/pages/backends/index.tsx index 82ee88d6..9428c8db 100644 --- a/src/pages/backends/index.tsx +++ b/src/pages/backends/index.tsx @@ -19,6 +19,7 @@ import { updateBackend, updateBackendFromYAML } from './apis'; +import AddCommunityModal from './community/add-community-modal'; import AddModal from './components/add-modal'; import BackendCardList from './components/backend-list'; import VersionInfoModal from './components/version-info-modal'; @@ -63,8 +64,14 @@ const BackendList = () => { currentData?: ListItem; }>({ open: false }); const { handleEnableBackend } = useEnableBackend(); - const { openBackendModal, closeBackendModal, openBackendModalStatus } = - useCreateBackend(); + const { + addBackend, + editBackend, + closeBackendModal, + openCommunityModalStatus, + openBackendModalStatus, + addActions + } = useCreateBackend(); // built_in_version_configs is read-only, but needs to be included when updating const handleOnSubmit = async (values: FormData) => { @@ -91,7 +98,7 @@ const BackendList = () => { } }); } - closeBackendModal(); + closeBackendModal('custom'); handleSearch(); } catch (error) {} }; @@ -116,7 +123,7 @@ const BackendList = () => { } }); } - closeBackendModal(); + closeBackendModal('custom'); handleSearch(); } catch (error) {} }; @@ -128,18 +135,54 @@ const BackendList = () => { }); }; - const handleAddBackend = () => { - openBackendModal(PageAction.CREATE, ''); + const handleAddBackend = (item: { key: 'community' | 'custom' }) => { + addBackend(item.key); + }; + + const handleDisableCommunityBackend = async (item: any) => { + // use disable for community backend deletion + handleEnableBackend({ + id: item.data.id, + data: { + ...item.data, + enabled: item.action === 'enable' + }, + showMessage: false + }); + // delay 200ms to refresh list + await new Promise((resolve) => { + setTimeout(resolve, 300); + }); + handleSearch(); }; const handleOnSelect = async (item: any) => { + // ================ Edit ================ if (item.action === 'edit') { - openBackendModal(PageAction.EDIT, '', item.data); - } else if (item.action === 'delete') { - handleDelete(item.data, { - name: item.data.backend_name - }); - } else if (item.action === 'export') { + editBackend(PageAction.EDIT, '', item.data); + return; + } + // ================ Delete ================ + if (item.action === 'delete') { + if (item.data.backend_source === BackendSourceValueMap.COMMUNITY) { + modalRef.current?.show({ + content: 'backends.title', + operation: 'common.delete.single.confirm', + name: item.data.backend_name, + async onOk() { + handleDisableCommunityBackend(item); + } + }); + } else { + handleDelete(item.data, { + name: item.data.backend_name + }); + } + return; + } + + // ================ Export YAML ================ + if (item.action === 'export') { const currentData = structuredClone(item.data); currentData.version_configs = _.mapValues( @@ -150,24 +193,15 @@ const BackendList = () => { ); exportYAML(_.omit(currentData, ['id', 'created_at', 'updated_at'])); - } else if (item.action === 'view_versions') { + return; + } + + // ================ View Versions ================ + if (item.action === 'view_versions') { setOpenVersionInfoModal({ open: true, currentData: item.data }); - } else if (item.action === 'enable' || item.action === 'disable') { - handleEnableBackend({ - id: item.data.id, - data: { - ...item.data, - enabled: item.action === 'enable' - } - }); - // delay 200ms to refresh list - await new Promise((resolve) => { - setTimeout(resolve, 300); - }); - handleSearch(); } }; @@ -176,7 +210,11 @@ const BackendList = () => { open: false, currentData: undefined }); - openBackendModal(PageAction.EDIT, '', openVersionInfoModal.currentData); + editBackend( + PageAction.EDIT, + '', + openVersionInfoModal.currentData as ListItem + ); }; const loadMore = useMemoizedFn((nextPage: number) => { @@ -197,6 +235,8 @@ const BackendList = () => { widths={{ input: 300 }} + actionItems={addActions} + actionType="dropdown" inputHolder={intl.formatMessage({ id: 'common.filter.name' })} selectHolder={intl.formatMessage({ id: 'backend.filter.source' })} buttonText={intl.formatMessage({ id: 'backend.button.add' })} @@ -211,7 +251,6 @@ const BackendList = () => { value: item.value }))} > - { })} title={intl.formatMessage({ id: 'noresult.backend.title' })} subTitle={intl.formatMessage({ id: 'noresult.backend.subTitle' })} - onClick={handleAddBackend} + onClick={() => handleAddBackend({ key: 'community' })} buttonText={intl.formatMessage({ id: 'noresult.button.add' })} > closeBackendModal('custom')} onSubmit={handleOnSubmit} onSubmitYaml={handleOnSubmitYaml} currentData={openBackendModalStatus.currentData as ListItem} @@ -256,6 +295,11 @@ const BackendList = () => { : intl.formatMessage({ id: 'backend.button.edit' }) } > + closeBackendModal('community')} + > { const handleEnableBackend = async (params: { id: number; data: FormData; + showMessage?: boolean; }) => { + const { showMessage = true } = params; await updateBackend(params.id, { data: params.data }); - message.success(intl.formatMessage({ id: 'common.message.success' })); + if (showMessage) { + message.success(intl.formatMessage({ id: 'common.message.success' })); + } }; return { handleEnableBackend }; diff --git a/src/pages/backends/services/use-query-community-backends.ts b/src/pages/backends/services/use-query-community-backends.ts new file mode 100644 index 00000000..b9374063 --- /dev/null +++ b/src/pages/backends/services/use-query-community-backends.ts @@ -0,0 +1,29 @@ +import { useState } from 'react'; +import { queryBackendsList } from '../apis'; + +const useQueryCommunityBackends = () => { + const [loading, setLoading] = useState(false); + const [dataList, setDataList] = useState([]); + + const fetchCommunityBackends = async (params?: any) => { + try { + setLoading(true); + const response = await queryBackendsList({ + page: -1, + community: 1, + ...params + }); + setDataList(response.items || []); + } catch (error) { + setDataList([]); + } finally { + setLoading(false); + } + }; + return { + loading, + dataList, + fetchCommunityBackends + }; +}; +export default useQueryCommunityBackends; diff --git a/src/pages/llmodels/components/separator.tsx b/src/pages/llmodels/components/separator.tsx index 837050db..28b53edc 100644 --- a/src/pages/llmodels/components/separator.tsx +++ b/src/pages/llmodels/components/separator.tsx @@ -2,14 +2,14 @@ import { Divider } from 'antd'; import React from 'react'; import '../style/separator.less'; -const Separator: React.FC = () => { +const Separator: React.FC<{ showArrow?: boolean }> = ({ showArrow = true }) => { return (
- + {showArrow && }
); };