diff --git a/config/routes.ts b/config/routes.ts index 6b1fbb2c..ee7a95b4 100644 --- a/config/routes.ts +++ b/config/routes.ts @@ -86,14 +86,14 @@ export default [ redirect: '/models/deployments' }, { - name: 'routes', - path: '/models/routes', - key: 'routes', - icon: 'icon-captive_portal', - selectedIcon: 'icon-captive_portal', - defaultIcon: 'icon-captive_portal', + name: 'modelCatalog', + path: '/models/catalog', + key: 'modelsCatalog', + icon: 'icon-layers', + selectedIcon: 'icon-layers-filled', + defaultIcon: 'icon-layers', access: 'canSeeAdmin', - component: './model-routes/index' + component: './llmodels/catalog' }, { name: 'deployment', @@ -106,14 +106,14 @@ export default [ component: './llmodels/index' }, { - name: 'modelCatalog', - path: '/models/catalog', - key: 'modelsCatalog', - icon: 'icon-layers', - selectedIcon: 'icon-layers-filled', - defaultIcon: 'icon-layers', + name: 'routes', + path: '/models/routes', + key: 'routes', + icon: 'icon-captive_portal', + selectedIcon: 'icon-captive_portal', + defaultIcon: 'icon-captive_portal', access: 'canSeeAdmin', - component: './llmodels/catalog' + component: './model-routes/index' }, { name: 'providers', diff --git a/src/components/footer/index.tsx b/src/components/footer/index.tsx index e69d6210..48acfc50 100644 --- a/src/components/footer/index.tsx +++ b/src/components/footer/index.tsx @@ -3,7 +3,7 @@ import { getAtomStorage } from '@/atoms/utils'; import VersionInfo, { modalConfig } from '@/components/version-info'; import externalLinks from '@/constants/external-links'; import { useIntl } from '@umijs/max'; -import { Button, Divider, Modal } from 'antd'; +import { Button, Divider, Modal, Typography } from 'antd'; import { createStyles } from 'antd-style'; import styled from 'styled-components'; @@ -54,7 +54,13 @@ const Footer: React.FC = () => { © {new Date().getFullYear()} - {intl.formatMessage({ id: 'settings.company' })} + + {intl.formatMessage({ id: 'settings.company' })} + - ) - } > } diff --git a/src/pages/llmodels/config/button-actions.ts b/src/pages/llmodels/config/button-actions.ts index a4567541..c3685c45 100644 --- a/src/pages/llmodels/config/button-actions.ts +++ b/src/pages/llmodels/config/button-actions.ts @@ -39,16 +39,21 @@ export const ActionList: ActionItem[] = [ key: 'edit', icon: icons.EditOutlined }, + { + label: 'models.openinplayground', + key: 'chat', + icon: icons.ExperimentOutlined + }, + { + key: 'copy', + label: 'common.button.duplicate', + icon: icons.CopyOutlined + }, { label: 'common.button.start', key: 'start', icon: icons.Play }, - { - label: 'models.table.button.apiAccessInfo', - key: 'api', - icon: icons.ApiOutlined - }, { label: 'common.button.stop', key: 'stop', diff --git a/src/pages/llmodels/hooks/use-edit-deployment.ts b/src/pages/llmodels/hooks/use-edit-deployment.ts new file mode 100644 index 00000000..8a368cbb --- /dev/null +++ b/src/pages/llmodels/hooks/use-edit-deployment.ts @@ -0,0 +1,82 @@ +import { PageAction } from '@/config'; +import { PageActionType } from '@/config/types'; +import { useIntl } from '@umijs/max'; +import { useState } from 'react'; +import { ListItem } from '../config/types'; + +const useEditDeployment = () => { + const intl = useIntl(); + const [openModalStatus, setOpenModalStatus] = useState<{ + open: boolean; + action: PageActionType; + currentData: { + isGGUF: boolean; + data: any; // for form data + row: ListItem; + realAction?: PageActionType; // added action here for copy action + }; + title: string; + }>({ + open: false, + action: PageAction.EDIT, + currentData: { + isGGUF: false, + data: {}, + row: {} as ListItem + }, + title: '' + }); + + const openEditModal = (formData: any, row: ListItem) => { + setOpenModalStatus({ + open: true, + action: PageAction.EDIT, + currentData: { + isGGUF: false, + data: formData, + row: row + }, + title: intl.formatMessage({ id: 'models.title.edit' }) + }); + }; + + const openDuplicateModal = (formData: any, row: ListItem) => { + setOpenModalStatus({ + open: true, + action: PageAction.EDIT, + currentData: { + isGGUF: false, + data: { + ...formData, + name: `${formData.name}-copy` + }, + row: row, + realAction: PageAction.COPY + }, + title: intl.formatMessage({ id: 'models.title.duplicate' }) + }); + }; + + const closeModal = () => { + setOpenModalStatus({ + open: false, + action: PageAction.EDIT, + currentData: { + isGGUF: false, + data: {}, + row: {} as ListItem + }, + title: '' + }); + }; + + return { + openEditModalStatus: openModalStatus, + setOpenEditModalStatus: setOpenModalStatus, + openEditModal: openEditModal, + openDuplicateModal: openDuplicateModal, + closeEditModal: closeModal + }; +}; + +export default useEditDeployment; diff --git a/src/pages/llmodels/hooks/use-models-columns.tsx b/src/pages/llmodels/hooks/use-models-columns.tsx index df167913..05912309 100644 --- a/src/pages/llmodels/hooks/use-models-columns.tsx +++ b/src/pages/llmodels/hooks/use-models-columns.tsx @@ -3,8 +3,10 @@ import AutoTooltip from '@/components/auto-tooltip'; import DropdownButtons from '@/components/drop-down-buttons'; import { SealColumnProps } from '@/components/seal-table/types'; import { OPENAI_COMPATIBLE, tableSorter } from '@/config/settings'; +import { TargetStatusValueMap } from '@/pages/model-routes/config'; import { QuestionCircleOutlined } from '@ant-design/icons'; import { useIntl } from '@umijs/max'; +import { useMemoizedFn } from 'ahooks'; import { Tooltip } from 'antd'; import dayjs from 'dayjs'; import _ from 'lodash'; @@ -13,24 +15,6 @@ import ModelTag from '../../_components/model-tag'; import { ActionList, generateSource } from '../config/button-actions'; import { ListItem } from '../config/types'; -const setModelActionList = (record: any) => { - return _.filter(ActionList, (action: any) => { - if (action.key === 'chat' || action.key === 'api') { - return record.ready_replicas > 0; - } - - if (action.key === 'start') { - return record.replicas === 0; - } - - if (action.key === 'stop') { - return record.replicas > 0; - } - - return true; - }); -}; - interface ModelsColumnsHookProps { handleSelect: (val: string, record: ListItem) => void; sortOrder: string[]; @@ -43,10 +27,36 @@ interface ModelsColumnsHookProps { const useModelsColumns = ({ handleSelect, clusterList, - sortOrder -}: ModelsColumnsHookProps): SealColumnProps[] => { + sortOrder, + targetList +}: ModelsColumnsHookProps & { targetList: any[] }): SealColumnProps[] => { const intl = useIntl(); + const setModelActionList = useMemoizedFn((record: any) => { + return _.filter(ActionList, (action: any) => { + if (action.key === 'chat') { + return ( + record.ready_replicas > 0 && + targetList?.find( + (target) => + target.model_id === record.id && + target.state === TargetStatusValueMap.Active + ) + ); + } + + if (action.key === 'start') { + return record.replicas === 0; + } + + if (action.key === 'stop') { + return record.replicas > 0; + } + + return true; + }); + }); + return useMemo(() => { return [ { @@ -137,7 +147,7 @@ const useModelsColumns = ({ key: 'operation', dataIndex: 'operation', span: 3, - render: (text, record) => ( + render: (text: any, record: ListItem) => ( handleSelect(val, record)} diff --git a/src/pages/model-routes/apis/index.ts b/src/pages/model-routes/apis/index.ts index aeba1000..ff759006 100644 --- a/src/pages/model-routes/apis/index.ts +++ b/src/pages/model-routes/apis/index.ts @@ -36,17 +36,18 @@ export async function deleteModelRoute(id: number) { }); } -export async function queryRouteTargets(params: { id: number }, options?: any) { - return request>( - `${MODEL_ROUTE_TARGETS}?route_id=${params.id}`, - { - method: 'GET', - params: { - page: -1 - }, - cancelToken: options?.token - } - ); +export async function queryRouteTargets( + params: { id?: number }, + options?: any +) { + return request>(`${MODEL_ROUTE_TARGETS}`, { + method: 'GET', + params: { + route_id: params.id, + page: -1 + }, + cancelToken: options?.token + }); } export async function deleteModelRouteTarget(id: number) { diff --git a/src/pages/model-routes/config/index.ts b/src/pages/model-routes/config/index.ts index db6a4a52..e66067be 100644 --- a/src/pages/model-routes/config/index.ts +++ b/src/pages/model-routes/config/index.ts @@ -29,6 +29,11 @@ export const rowActionList = [ key: 'chat', icon: icons.ExperimentOutlined }, + { + label: 'models.table.button.apiAccessInfo', + key: 'api', + icon: icons.ApiOutlined + }, { label: 'models.button.accessSettings', key: 'accessControl', diff --git a/src/pages/model-routes/config/types.ts b/src/pages/model-routes/config/types.ts index c114268a..ad028542 100644 --- a/src/pages/model-routes/config/types.ts +++ b/src/pages/model-routes/config/types.ts @@ -38,13 +38,10 @@ export interface RouteTarget { id: number; created_at: string; updated_at: string; - deleted_at: string; - provider_model_name: string; - weight: number | null; + weight: number; model_id: number; - provider_id: number; name: string; + route_name: string; route_id: number; state: string; - fallback_status_codes: string[]; } diff --git a/src/pages/model-routes/forms/basic.tsx b/src/pages/model-routes/forms/basic.tsx index ee1520b5..646b3eea 100644 --- a/src/pages/model-routes/forms/basic.tsx +++ b/src/pages/model-routes/forms/basic.tsx @@ -13,10 +13,19 @@ const Basic = () => { const { getRuleMessage } = useAppUtils(); return ( <> - + { return rowActionList.filter((action) => { - if (action.key === 'chat') { + if (action.key === 'chat' || action.key === 'api') { return record.ready_targets > 0; } return true; diff --git a/src/pages/model-routes/hooks/use-view-api-info.ts b/src/pages/model-routes/hooks/use-view-api-info.ts new file mode 100644 index 00000000..8bee346a --- /dev/null +++ b/src/pages/model-routes/hooks/use-view-api-info.ts @@ -0,0 +1,30 @@ +import { useState } from 'react'; + +const useViewApIInfo = () => { + const [apiAccessInfo, setAPIAccessInfo] = useState({ + show: false, + data: {} + }); + + const openViewAPIInfo = (row: any) => { + setAPIAccessInfo({ + show: true, + data: row + }); + }; + + const closeViewAPIInfo = () => { + setAPIAccessInfo({ + show: false, + data: {} + }); + }; + + return { + apiAccessInfo, + openViewAPIInfo, + closeViewAPIInfo + }; +}; + +export default useViewApIInfo; diff --git a/src/pages/model-routes/index.tsx b/src/pages/model-routes/index.tsx index c7f954e0..19d312b0 100644 --- a/src/pages/model-routes/index.tsx +++ b/src/pages/model-routes/index.tsx @@ -10,6 +10,7 @@ import { TABLE_SORT_DIRECTIONS } from '@/config/settings'; import useExpandedRowKeys from '@/hooks/use-expanded-row-keys'; import useTableFetch from '@/hooks/use-table-fetch'; import useWatchList from '@/hooks/use-watch-list'; +import APIAccessInfoModal from '@/pages/llmodels/components/api-access-info'; import { useIntl } from '@umijs/max'; import { useMemoizedFn } from 'ahooks'; import { message } from 'antd'; @@ -39,6 +40,7 @@ import useCreateRoute from './hooks/use-create-route'; import useOpenPlayground from './hooks/use-open-playground'; import useRoutesColumns from './hooks/use-routes-columns'; import useTargetSourceModels from './hooks/use-target-source-models'; +import useViewApIInfo from './hooks/use-view-api-info'; const ModelRoutes: React.FC = () => { const { @@ -75,6 +77,7 @@ const ModelRoutes: React.FC = () => { } = useAccessControl(); const { sourceModels, fetchSourceModels } = useTargetSourceModels(); const { handleOpenPlayGround } = useOpenPlayground(); + const { apiAccessInfo, openViewAPIInfo, closeViewAPIInfo } = useViewApIInfo(); const [modelList, setModelsList] = useState[]>([]); useEffect(() => { @@ -149,6 +152,8 @@ const ModelRoutes: React.FC = () => { ); } else if (val === 'chat') { handleOpenPlayGround(row); + } else if (val === 'api') { + openViewAPIInfo(row); } }); @@ -316,6 +321,11 @@ const ModelRoutes: React.FC = () => { currentData={openAccessControlModalStatus.currentData} action={openAccessControlModalStatus.action} > + ); diff --git a/src/pages/model-routes/services/use-query-targets.ts b/src/pages/model-routes/services/use-query-targets.ts new file mode 100644 index 00000000..97ac8958 --- /dev/null +++ b/src/pages/model-routes/services/use-query-targets.ts @@ -0,0 +1,23 @@ +import { useQueryDataList } from '@/hooks/use-query-data-list'; +import { queryRouteTargets } from '../apis'; +import { RouteTarget as ListItem } from '../config/types'; + +export const useQueryRouteTargets = (optons?: { + getLabel?: (item: ListItem) => string; + getValue?: (item: ListItem) => any; +}) => { + const { dataList, loading, fetchData, cancelRequest } = + useQueryDataList({ + key: 'routeTargets', + fetchList: queryRouteTargets + }); + + return { + dataList, + loading, + fetchData, + cancelRequest + }; +}; + +export default useQueryRouteTargets; diff --git a/src/pages/resources/config/gpu-driver.ts b/src/pages/resources/config/gpu-driver.ts index f2442fb7..8b8c17e5 100644 --- a/src/pages/resources/config/gpu-driver.ts +++ b/src/pages/resources/config/gpu-driver.ts @@ -179,14 +179,13 @@ const generateEnvArgs = (params: any) => { // concat the args, the args is a key-value const generateExtraArgs = (params: any) => { - const args = params.registrationInfo?.args || {}; - const argsList = Object.entries(args); - if (argsList.length === 0) { + const args = params.registrationInfo?.args || []; + if (args.length === 0) { return ''; } let argsStr = ''; - argsList.forEach(([key, value]) => { - argsStr += `${key} ${_.isBoolean(value) ? value : value || ''} \\\n `; + args.forEach((item: string[]) => { + argsStr += `${item[0]} ${_.isBoolean(item[1]) ? item[1] : item[1] || ''} \\\n `; }); return argsStr; };