From 2b9b681e124c27dd5af1064f8c8100a74ccdc2f5 Mon Sep 17 00:00:00 2001 From: Yuneng Jiang Date: Wed, 15 Jul 2026 13:48:27 -0700 Subject: [PATCH] fix(ui/chat): resolve chat routes at render time so navigation works under server_root_path CHAT_ROUTES was built once at module load via migratedHref, capturing an empty server root path before the UI-config bootstrap sets it. Under SERVER_ROOT_PATH every chat route came out unprefixed, so router.push hard-navigated to a 404; during a send that page unload also aborted the streaming request, so the first message of a new conversation never rendered and only a second send appeared to work. Compute the routes at render time via getChatRoutes(), update the send URL with a shallow history.pushState instead of a router navigation, and source the active conversation id from the hook's local state so it propagates without a router round-trip --- ui/litellm-dashboard/src/app/chat/page.tsx | 9 ++-- .../chat/ChatShell.serverRootPath.test.ts | 43 ++++++++++++++++++ .../src/components/chat/ChatShell.tsx | 45 ++++++++++--------- .../src/components/chat/useChatHistory.ts | 2 + .../src/contexts/ChatShellContext.tsx | 7 +-- 5 files changed, 77 insertions(+), 29 deletions(-) create mode 100644 ui/litellm-dashboard/src/components/chat/ChatShell.serverRootPath.test.ts diff --git a/ui/litellm-dashboard/src/app/chat/page.tsx b/ui/litellm-dashboard/src/app/chat/page.tsx index ae86ca6e926..b6dccef47c6 100644 --- a/ui/litellm-dashboard/src/app/chat/page.tsx +++ b/ui/litellm-dashboard/src/app/chat/page.tsx @@ -10,7 +10,7 @@ import { Button } from "@/components/ui/button"; import MessageManager from "@/components/molecules/message_manager"; import { useRouter } from "next/navigation"; import { useChatShell } from "@/contexts/ChatShellContext"; -import { CHAT_ROUTES } from "@/components/chat/ChatShell"; +import { getChatRoutes } from "@/components/chat/ChatShell"; import ChatMessages from "@/components/chat/ChatMessages"; import MCPConnectPicker from "@/components/chat/MCPConnectPicker"; import { fetchAvailableModels } from "@/components/llm_calls/fetch_models"; @@ -87,7 +87,7 @@ export default function ChatConversationPage() { const streamScrollLock = useRef(null); useEffect(() => { - if (staleId) router.replace(CHAT_ROUTES.chats); + if (staleId) router.replace(getChatRoutes().chats); }, [staleId, router]); // Load models @@ -140,7 +140,7 @@ export default function ChatConversationPage() { if (!convId) { convId = createConversation(model); setResponsesSessionId(null); // new conversation starts a fresh session - router.push(`${CHAT_ROUTES.chats}?id=${convId}`); + window.history.pushState(null, "", `${window.location.pathname}?id=${convId}`); } appendMessage(convId, { role: "user", content: trimmed }); @@ -248,7 +248,6 @@ export default function ChatConversationPage() { createConversation, appendMessage, updateLastAssistantMessage, - router, isStreaming, responsesSessionId, ], @@ -529,7 +528,7 @@ export default function ChatConversationPage() { Chat with 100+ LLMs + MCP tools; authenticate once, use them here.{" "} @@ -85,32 +88,32 @@ const ChatShell: React.FC = ({ children }) => { } label="Chats" - onClick={() => router.push(CHAT_ROUTES.chats)} + onClick={() => router.push(routes.chats)} active={isChatsRoute} /> } label="Integrations" - onClick={() => router.push(CHAT_ROUTES.integrations)} - active={pathname === CHAT_ROUTES.integrations} + onClick={() => router.push(routes.integrations)} + active={pathname === routes.integrations} /> } label="Credentials" - onClick={() => router.push(CHAT_ROUTES.credentials)} - active={pathname === CHAT_ROUTES.credentials} + onClick={() => router.push(routes.credentials)} + active={pathname === routes.credentials} /> } label="API Keys" - onClick={() => router.push(CHAT_ROUTES.apiKeys)} - active={pathname === CHAT_ROUTES.apiKeys} + onClick={() => router.push(routes.apiKeys)} + active={pathname === routes.apiKeys} /> } label="Usage" - onClick={() => router.push(CHAT_ROUTES.usage)} - active={pathname === CHAT_ROUTES.usage} + onClick={() => router.push(routes.usage)} + active={pathname === routes.usage} /> @@ -120,10 +123,10 @@ const ChatShell: React.FC = ({ children }) => { router.push(`${CHAT_ROUTES.chats}?id=${id}`)} + onSelect={(id) => router.push(`${routes.chats}?id=${id}`)} onDelete={(id) => { deleteConversation(id); - if (id === activeConversationId) router.push(CHAT_ROUTES.chats); + if (id === activeConversationId) router.push(routes.chats); }} onRename={renameConversation} /> diff --git a/ui/litellm-dashboard/src/components/chat/useChatHistory.ts b/ui/litellm-dashboard/src/components/chat/useChatHistory.ts index 3b6d350520b..8e6c3ce5877 100644 --- a/ui/litellm-dashboard/src/components/chat/useChatHistory.ts +++ b/ui/litellm-dashboard/src/components/chat/useChatHistory.ts @@ -52,6 +52,7 @@ export function useChatHistory( ): { conversations: Conversation[]; activeConversation: Conversation | null; + currentActiveId: string | null; storageUnavailable: boolean; staleId: boolean; createConversation: (model: string) => string; @@ -208,6 +209,7 @@ export function useChatHistory( return { conversations, activeConversation, + currentActiveId, storageUnavailable, staleId, createConversation, diff --git a/ui/litellm-dashboard/src/contexts/ChatShellContext.tsx b/ui/litellm-dashboard/src/contexts/ChatShellContext.tsx index 96d352ade6f..b0f590d2394 100644 --- a/ui/litellm-dashboard/src/contexts/ChatShellContext.tsx +++ b/ui/litellm-dashboard/src/contexts/ChatShellContext.tsx @@ -57,12 +57,13 @@ export function ChatShellProvider({ children, }: ChatShellProviderProps) { const searchParams = useSearchParams(); - const activeConversationId = searchParams.get("id"); + const urlConversationId = searchParams.get("id"); const [selectedMCPServers, setSelectedMCPServers] = useState([]); const { conversations, activeConversation, + currentActiveId, storageUnavailable, staleId, createConversation, @@ -71,7 +72,7 @@ export function ChatShellProvider({ truncateFromMessage, deleteConversation, renameConversation, - } = useChatHistory(activeConversationId, userId); + } = useChatHistory(urlConversationId, userId); return (