diff --git a/frontend/src/components/layout/sidebar.tsx b/frontend/src/components/layout/sidebar.tsx index 826fa4c6e4..af87aad0ff 100644 --- a/frontend/src/components/layout/sidebar.tsx +++ b/frontend/src/components/layout/sidebar.tsx @@ -25,7 +25,7 @@ import { usePathname } from 'next/navigation'; import { useI18n } from '@/contexts/i18n-context'; import { useGlobal } from '@/contexts/global-context'; -import { cn, decodeJwtScopes } from '@/lib/utils'; +import { cn } from '@/lib/utils'; import { getBrandingFromEnv } from '@/lib/branding'; import { XINFERENCE_DOCS_URL, @@ -38,6 +38,7 @@ import ThemeToggle from '@/components/layout/theme-toggle'; import LanguageSwitcher from '@/components/layout/language-switcher'; import LoginOut from '@/components/layout/login-out'; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; +import { useMenuAuth } from '@/hooks/use-menu-auth'; type IconComponent = ComponentType<{ className?: string }>; @@ -144,17 +145,13 @@ export function Sidebar() { const [collapsed, setCollapsed] = useState(false); const branding = getBrandingFromEnv(); const { clusterVersion, clusterAuth, clusterUIConfig } = useGlobal(); + const { usersManagePage, keysManageCreate } = useMenuAuth(); const token = Cookies.get('token'); const showLoginOut = useMemo( () => clusterAuth?.auth && token && token !== NO_AUTH, [clusterAuth, token] ); - const jwtScopes = useMemo(() => decodeJwtScopes(token), [token]); - const isAdmin = jwtScopes.includes('admin'); - const canManageUsers = isAdmin || jwtScopes.includes('users:manage'); - const canAccessKeysPage = isAdmin || jwtScopes.includes('keys:create'); - const navGroups = useMemo(() => { const groups: NavGroup[] = [ { @@ -212,17 +209,17 @@ export function Sidebar() { name: t('menu.userManagement'), Icon: Users, Extra: ChevronRight, - show: canManageUsers, + show: usersManagePage }, { path: '/api-key-management', name: t('menu.apiKeyManagement'), Icon: KeyRound, Extra: ChevronRight, - show: canAccessKeysPage, + show: keysManageCreate, }, ], - show: (clusterUIConfig?.auth_advanced || false) && (canManageUsers || canAccessKeysPage), + show: (clusterUIConfig?.auth_advanced || false) && (usersManagePage || keysManageCreate), }, { name: t('menu.resourcesAndSupport'), @@ -265,7 +262,7 @@ export function Sidebar() { ...group, items: group.items.filter(({ show = true }) => show), })); - }, [clusterUIConfig, locale, t, canManageUsers, canAccessKeysPage]); + }, [clusterUIConfig, locale, t, usersManagePage, keysManageCreate]); return (
([]); const [loading, setLoading] = useState(true); @@ -300,12 +294,12 @@ export default function ApiKeyManagement() { handleToggleEnabled(key)} /> - {canManageKeys && ( + {keysManagePage && ( + + Try To API +
+ + +
+ {codeExample ? ( + setLanguage(value as typeof language)}> + + {CODE_LANGUAGE_OPTIONS.map((item) => ( + + {item.label} + + ))} + + {CODE_LANGUAGE_OPTIONS.map((item) => ( + +
+ + +
+
+ ))} +
+ ) : ( +
+ No API example available for this model ability. +
+ )} +
+ + + ); +} diff --git a/frontend/src/components/pages/running-model-detail/index.tsx b/frontend/src/components/pages/running-model-detail/index.tsx index 86c8518331..e2fcadbfff 100644 --- a/frontend/src/components/pages/running-model-detail/index.tsx +++ b/frontend/src/components/pages/running-model-detail/index.tsx @@ -1,13 +1,13 @@ 'use client'; import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import { ArrowLeft, Info, WandSparkles } from 'lucide-react'; +import { ArrowLeft, Info, WandSparkles, Code } from 'lucide-react'; import { useRouter } from 'next/navigation'; import { Button } from '@/components/ui/button'; import { CollapsiblePanel } from '@/components/ui/collapsible'; import PageContainer from '@/components/ui/page-container'; -import { ModelAbility, ModelType } from '@/constants'; +import { ModelAbility } from '@/constants'; import request from '@/lib/request'; import type { RunningModelDetail as RunningModelDetailType } from '@/types/services'; @@ -16,16 +16,13 @@ import CapabilityTaskPanel, { CapabilityTaskPanelMethod } from './panels/capabil import { ChatPanel } from './panels/chat-panel'; import { Select } from '@/components/ui/select'; import { useI18n } from '@/contexts/i18n-context'; +import { TryApiDrawer } from './components/try-api-drawer'; +import { transformRunningModelDetail } from './utils'; interface RunningModelDetailProps { modelUid: string; } -const MODEL_TYPE_ABILITY_MAP: Record = { - [ModelType.Rerank]: [ModelAbility.Rerank], - [ModelType.Embedding]: [ModelAbility.Embed], -}; - function DetailItem({ label, value }: { label: string; value?: string | number | null }) { return (
@@ -81,23 +78,21 @@ const RunningModelDetail: FC = ({ modelUid }) => { const [loading, setLoading] = useState(true); const isChat = (model?.model_ability || []).includes(ModelAbility.Chat); const [selectAbility, setSelectAbility] = useState(undefined); + const [tryApiOpen, setTryApiOpen] = useState(false); const capabilityTaskPanelRef = useRef(null); + const tryApiAbility = isChat ? ModelAbility.Chat : selectAbility; const fetchModel = useCallback(() => { setLoading(true); request .get(`/v1/models/${modelUid}`) .then((res) => { - const newModel = { - ...res, - // fix model_ability was not returned when model_type was Rerank or Embedding. - model_ability: Array.isArray(res?.model_ability) - ? res.model_ability - : (res?.model_type && MODEL_TYPE_ABILITY_MAP[res.model_type]) || [], - }; - const firstAbility = newModel.model_ability.filter((item) => !item.includes('_'))?.[0]; + const newModelDetail = transformRunningModelDetail(res) as RunningModelDetailType; + const firstAbility = newModelDetail.model_ability.filter( + (item) => !item.includes('_') + )?.[0]; setSelectAbility(firstAbility); - setModel(newModel); + setModel(newModelDetail); }) .finally(() => setLoading(false)); }, [modelUid]); @@ -174,6 +169,10 @@ const RunningModelDetail: FC = ({ modelUid }) => { onChange={handleAbility} /> )} +
} > @@ -183,6 +182,12 @@ const RunningModelDetail: FC = ({ modelUid }) => { {renderCapability()} )} + ); }; diff --git a/frontend/src/components/pages/running-model-detail/types.ts b/frontend/src/components/pages/running-model-detail/types.ts index 39bce412f7..7d64f9be96 100644 --- a/frontend/src/components/pages/running-model-detail/types.ts +++ b/frontend/src/components/pages/running-model-detail/types.ts @@ -3,6 +3,7 @@ import type { LucideIcon } from 'lucide-react'; import type { FileUploadValue } from '@/types/common'; import type { ModelAbility } from '@/constants'; +import type { CodeExampleConfig } from '@/constants/running'; import type { FormInstance, FormValues } from '@/types/form'; import type { RunningModelDetail } from '@/types/services'; @@ -42,6 +43,7 @@ export interface CapabilityConfig { resultPanel: ComponentType; transformValues: (context: TransformContext) => BodyInit | Record; responseType?: 'blob'; + codeExample?: CodeExampleConfig; } export interface ChatSettings { diff --git a/frontend/src/components/pages/running-model-detail/utils.ts b/frontend/src/components/pages/running-model-detail/utils.ts index 92b5bdefe9..dc61beb024 100644 --- a/frontend/src/components/pages/running-model-detail/utils.ts +++ b/frontend/src/components/pages/running-model-detail/utils.ts @@ -1,3 +1,4 @@ +import { MODEL_TYPE_ABILITY_MAP } from '@/constants/running'; import type { FormValues } from '@/types/form'; import type { ChatChoicesMessage } from '@/types/services'; import type { FileUploadValue } from '@/types/common'; @@ -157,3 +158,16 @@ export function transformFileInfoForResult(message?: ChatChoicesMessage) { return undefined; } + +export function transformRunningModelDetail(detail: T) { + if (!isRecord(detail) || !detail) return {}; + const modelType = typeof detail.model_type === 'string' ? detail.model_type : undefined; + + return { + ...detail, + // fix model_ability was not returned when model_type was Rerank or Embedding. + model_ability: Array.isArray(detail.model_ability) + ? detail.model_ability + : (modelType && MODEL_TYPE_ABILITY_MAP[modelType]) || [], + }; +} diff --git a/frontend/src/components/pages/running-model/index.tsx b/frontend/src/components/pages/running-model/index.tsx index 169b4b9ee9..b16a5e3b23 100644 --- a/frontend/src/components/pages/running-model/index.tsx +++ b/frontend/src/components/pages/running-model/index.tsx @@ -11,6 +11,7 @@ import { MessageCircleMore, Cpu, Server, + Code, } from 'lucide-react'; import { useRouter } from 'next/navigation'; import { toast } from 'sonner'; @@ -18,10 +19,16 @@ import PageContainer from '@/components/ui/page-container'; import { Input } from '@/components/ui/input'; import { Button } from '@/components/ui/button'; import { ConfirmDialog } from '@/components/ui/confirm-dialog'; +import { InfoTooltip } from '@/components/ui/tooltip'; import { useI18n } from '@/contexts/i18n-context'; import type { RunningModelItem, ReplicaItem } from '@/types/services'; import request from '@/lib/request'; import { cn } from '@/lib/utils'; +import { + getTryApiAbility, + TryApiDrawer, +} from '@/components/pages/running-model-detail/components/try-api-drawer'; +import { transformRunningModelDetail } from '@/components/pages/running-model-detail/utils'; interface EmptyStateProps { icon: ReactNode; @@ -77,6 +84,11 @@ const RunningModel = () => { const [deleteConfirmLoading, setDeleteConfirmLoading] = useState(false); const [deleteReplicaId, setDeleteReplicaId] = useState(undefined); const [deleteReplicaLoading, setDeleteReplicaLoading] = useState(false); + const [tryApiOpen, setTryApiOpen] = useState(false); + const tryApiAbility = useMemo( + () => getTryApiAbility(activeModel?.model_ability || []), + [activeModel?.model_ability] + ); const [autostartModelIds, setAutostartModelIds] = useState([]); const [autostartBusyIds, setAutostartBusyIds] = useState([]); const visibleModels = useMemo(() => { @@ -98,7 +110,9 @@ const RunningModel = () => { request .get('/v1/models') .then((res) => { - const list = (res?.data || []) as RunningModelItem[]; + const list = ((res?.data || []) as RunningModelItem[]).map( + (item) => transformRunningModelDetail(item) as RunningModelItem + ); setModels(list); setActiveModel((prev) => { if (!prev) { @@ -257,6 +271,17 @@ const RunningModel = () => {
{activeModel?.model_name}
+ + + {isAutostart && (