Merge pull request #23021 from BerriAI/litellm_fix_chat_root_path

[Fix] UI Chat - SERVER_ROOT_PATH not respected for chat and back-to-console links
This commit is contained in:
yuneng-jiang
2026-03-06 23:31:27 -08:00
committed by GitHub
2 changed files with 25 additions and 21 deletions
@@ -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<ChatPageProps> = ({ 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<string[]>([]);
@@ -202,7 +200,7 @@ const ChatPage: React.FC<ChatPageProps> = ({ 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<ChatPageProps> = ({ 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<ChatPageProps> = ({ 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<ChatPageProps> = ({ accessToken, userRole, userId, user
{/* Sidebar nav buttons */}
<div style={{ padding: "0 8px 4px", flexShrink: 0 }}>
{sidebarNavItem(<EditOutlined />, "New chat", () => router.push(getChatUrl()))}
{sidebarNavItem(<EditOutlined />, "New chat", () => router.push(getChatUrl(uiRoot)))}
{sidebarNavItem(<SearchOutlined />, "Search chats", () => setSidebarView("chats"))}
</div>
@@ -850,9 +848,9 @@ const ChatPage: React.FC<ChatPageProps> = ({ accessToken, userRole, userId, user
<ConversationList
conversations={conversations}
activeConversationId={activeConversationId}
onSelect={(id) => router.push(getChatUrl(id))}
onSelect={(id) => router.push(getChatUrl(uiRoot, id))}
onDelete={deleteConversation}
onNewChat={() => router.push(getChatUrl())}
onNewChat={() => router.push(getChatUrl(uiRoot))}
onRename={renameConversation}
/>
</div>
@@ -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<NavbarProps> = ({
}) => {
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<NavbarProps> = ({
<div className="flex items-center space-x-5 ml-auto">
{/* Chat CTA — always visible, opens in new tab */}
<a
href={`${serverRootPath && serverRootPath !== "/" ? serverRootPath : ""}/ui/chat`}
href={chatHref}
target="_blank"
rel="noopener noreferrer"
style={{