From aae0c81cd1bf30c3cca655815cb160d929312705 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Fri, 6 Mar 2026 22:07:45 -0800 Subject: [PATCH] [Fix] UI Chat - respect SERVER_ROOT_PATH for chat and back-to-console links The Chat button in the navbar and the "back to Developer Console" link in ChatPage used the `serverRootPath` module variable directly, which is initialized to "/" and only updated after `getUiConfig` resolves. Since React does not re-render on module variable changes, the links computed their hrefs with the stale default, ignoring any configured SERVER_ROOT_PATH. Both components now call `useUIConfig()` (React Query, cached) to reactively read `server_root_path`, matching the pattern used elsewhere in the UI. Co-Authored-By: Claude Sonnet 4.6 --- .../src/components/chat/ChatPage.tsx | 36 +++++++++---------- .../src/components/navbar.tsx | 10 ++++-- 2 files changed, 25 insertions(+), 21 deletions(-) diff --git a/ui/litellm-dashboard/src/components/chat/ChatPage.tsx b/ui/litellm-dashboard/src/components/chat/ChatPage.tsx index 4f40b2bf37..40b41f561c 100644 --- a/ui/litellm-dashboard/src/components/chat/ChatPage.tsx +++ b/ui/litellm-dashboard/src/components/chat/ChatPage.tsx @@ -26,7 +26,8 @@ import MCPConnectPicker from "./MCPConnectPicker"; import MCPAppsPanel from "./MCPAppsPanel"; import { fetchAvailableModels } from "../playground/llm_calls/fetch_models"; import { makeOpenAIChatCompletionRequest } from "../playground/llm_calls/chat_completion"; -import { serverRootPath, getProxyBaseUrl } from "@/components/networking"; +import { getProxyBaseUrl } from "@/components/networking"; +import { useUIConfig } from "@/app/(dashboard)/hooks/uiConfig/useUIConfig"; import { getProviderLogoAndName } from "@/components/provider_info_helpers"; interface ChatPageProps { @@ -47,23 +48,16 @@ function getGreeting(): string { return "Good evening"; } -// Build the chat UI URL respecting server root path (e.g. /litellm/ui/chat) -function getChatUrl(id?: string): string { - const root = serverRootPath && serverRootPath !== "/" ? serverRootPath.replace(/\/+$/, "") : ""; +// Build the chat UI URL respecting server root path (e.g. /api/v1/ui/chat) +function getChatUrl(root: string, id?: string): string { return id ? `${root}/ui/chat?id=${id}` : `${root}/ui/chat`; } -// Build the dashboard root URL -function getDashboardUrl(): string { +// Build the dashboard root URL (e.g. /api/v1/ui/) +function getDashboardUrl(root: string): string { const base = process.env.NEXT_PUBLIC_BASE_URL ?? ""; const trimmed = base.replace(/^\/+|\/+$/g, ""); - const uiPath = trimmed ? `/${trimmed}/` : "/"; - if (serverRootPath && serverRootPath !== "/") { - const cleanRoot = serverRootPath.replace(/\/+$/, ""); - const cleanUi = uiPath.replace(/^\/+/, ""); - return `${cleanRoot}/${cleanUi}`; - } - return uiPath; + return trimmed ? `${root}/${trimmed}/` : `${root}/`; } // Extract provider from model name for logo lookup. @@ -128,6 +122,10 @@ const ChatPage: React.FC = ({ accessToken, userRole, userId, user const router = useRouter(); const searchParams = useSearchParams(); const activeConversationId = searchParams.get("id"); + const { data: uiConfig } = useUIConfig(); + const uiRoot = uiConfig?.server_root_path && uiConfig.server_root_path !== "/" + ? uiConfig.server_root_path.replace(/\/+$/, "") + : ""; const logoSrc = `${getProxyBaseUrl()}/get_image`; const [selectedModels, setSelectedModels] = useState([]); @@ -202,7 +200,7 @@ const ChatPage: React.FC = ({ accessToken, userRole, userId, user }, [accessToken]); useEffect(() => { - if (staleId) router.replace(getChatUrl()); + if (staleId) router.replace(getChatUrl(uiRoot)); }, [staleId, router]); const toggleModel = useCallback((model: string) => { @@ -233,7 +231,7 @@ const ChatPage: React.FC = ({ accessToken, userRole, userId, user let convId = activeConversationId; if (!convId) { convId = createConversation(model); - router.push(getChatUrl(convId)); + router.push(getChatUrl(uiRoot, convId)); } appendMessage(convId, { role: "user", content: trimmed }); @@ -439,7 +437,7 @@ const ChatPage: React.FC = ({ accessToken, userRole, userId, user : comparisonExchanges.length === 0; const displayName = userEmail?.split("@")[0] ?? userId ?? ""; const greeting = displayName ? `${getGreeting()}, ${displayName}` : getGreeting(); - const dashboardUrl = getDashboardUrl(); + const dashboardUrl = getDashboardUrl(uiRoot); // Filtered models: selected ones float to the top, then alphabetical const filteredModels = (modelSearchText @@ -801,7 +799,7 @@ const ChatPage: React.FC = ({ accessToken, userRole, userId, user {/* Sidebar nav buttons */}
- {sidebarNavItem(, "New chat", () => router.push(getChatUrl()))} + {sidebarNavItem(, "New chat", () => router.push(getChatUrl(uiRoot)))} {sidebarNavItem(, "Search chats", () => setSidebarView("chats"))}
@@ -850,9 +848,9 @@ const ChatPage: React.FC = ({ accessToken, userRole, userId, user router.push(getChatUrl(id))} + onSelect={(id) => router.push(getChatUrl(uiRoot, id))} onDelete={deleteConversation} - onNewChat={() => router.push(getChatUrl())} + onNewChat={() => router.push(getChatUrl(uiRoot))} onRename={renameConversation} /> diff --git a/ui/litellm-dashboard/src/components/navbar.tsx b/ui/litellm-dashboard/src/components/navbar.tsx index d0387b1440..c46a3af5a6 100644 --- a/ui/litellm-dashboard/src/components/navbar.tsx +++ b/ui/litellm-dashboard/src/components/navbar.tsx @@ -1,6 +1,7 @@ import { useHealthReadiness } from "@/app/(dashboard)/hooks/healthReadiness/useHealthReadiness"; import { useDisableBouncingIcon } from "@/app/(dashboard)/hooks/useDisableBouncingIcon"; -import { getProxyBaseUrl, serverRootPath } from "@/components/networking"; +import { getProxyBaseUrl } from "@/components/networking"; +import { useUIConfig } from "@/app/(dashboard)/hooks/uiConfig/useUIConfig"; import { useTheme } from "@/contexts/ThemeContext"; import { clearTokenCookies } from "@/utils/cookieUtils"; import { fetchProxySettings } from "@/utils/proxyUtils"; @@ -43,6 +44,11 @@ const Navbar: React.FC = ({ }) => { const baseUrl = getProxyBaseUrl(); const [logoutUrl, setLogoutUrl] = useState(""); + const { data: uiConfig } = useUIConfig(); + const uiRoot = uiConfig?.server_root_path && uiConfig.server_root_path !== "/" + ? uiConfig.server_root_path.replace(/\/+$/, "") + : ""; + const chatHref = `${uiRoot}/ui/chat`; const { logoUrl } = useTheme(); const { data: healthData } = useHealthReadiness(); const version = healthData?.litellm_version; @@ -130,7 +136,7 @@ const Navbar: React.FC = ({
{/* Chat CTA — always visible, opens in new tab */}