diff --git a/ui/web/src/lib/constants.ts b/ui/web/src/lib/constants.ts
index 625e3d1f..e8c02198 100644
--- a/ui/web/src/lib/constants.ts
+++ b/ui/web/src/lib/constants.ts
@@ -1,7 +1,8 @@
export const ROUTES = {
LOGIN: "/login",
OVERVIEW: "/overview",
- CHAT: "/chat/:sessionKey?",
+ CHAT: "/chat",
+ CHAT_PATTERN: "/chat/:sessionKey?",
AGENTS: "/agents",
AGENT_DETAIL: "/agents/:id",
SESSIONS: "/sessions",
diff --git a/ui/web/src/pages/chat/chat-page.tsx b/ui/web/src/pages/chat/chat-page.tsx
index 82582ce6..59d7f166 100644
--- a/ui/web/src/pages/chat/chat-page.tsx
+++ b/ui/web/src/pages/chat/chat-page.tsx
@@ -1,4 +1,4 @@
-import { useState, useCallback, useEffect, useRef } from "react";
+import { useState, useCallback, useEffect, useRef, useMemo } from "react";
import { useTranslation } from "react-i18next";
import { useParams, useNavigate } from "react-router";
import { Eye, PanelLeftOpen } from "lucide-react";
@@ -29,13 +29,17 @@ export function ChatPage() {
// sessionKey derived from URL — single source of truth, no separate state
const sessionKey = urlSessionKey ?? "";
- const [agentId, setAgentId] = useState(() => {
+ // Fallback agent ID used only when URL has no session key
+ const [agentIdFallback, setAgentIdFallback] = useState("default");
+
+ // Derive agentId from URL (source of truth), fallback to state when no session
+ const agentId = useMemo(() => {
if (urlSessionKey) {
const { agentId: parsed } = parseSessionKey(urlSessionKey);
if (parsed) return parsed;
}
- return "default";
- });
+ return agentIdFallback;
+ }, [urlSessionKey, agentIdFallback]);
const {
sessions,
@@ -91,12 +95,10 @@ export function ChatPage() {
const handleSessionSelect = useCallback(
(key: string) => {
const { agentId: parsed } = parseSessionKey(key);
- if (parsed && parsed !== agentId) {
- setAgentId(parsed);
- }
+ if (parsed) setAgentIdFallback(parsed);
navigate(`/chat/${encodeURIComponent(key)}`);
},
- [navigate, agentId],
+ [navigate],
);
const handleDeleteSession = useCallback(async (key: string) => {
@@ -113,10 +115,12 @@ export function ChatPage() {
const handleAgentChange = useCallback(
(newAgentId: string) => {
- setAgentId(newAgentId);
- navigate(`/chat/${encodeURIComponent(`agent:${newAgentId}:ws:direct:${crypto.randomUUID()}`)}`);
+ setAgentIdFallback(newAgentId);
+ if (sessionKey) {
+ navigate("/chat");
+ }
},
- [navigate],
+ [navigate, sessionKey],
);
const handleSend = useCallback(
diff --git a/ui/web/src/pages/chat/hooks/use-chat-messages.ts b/ui/web/src/pages/chat/hooks/use-chat-messages.ts
index 58453784..bb3f36a1 100644
--- a/ui/web/src/pages/chat/hooks/use-chat-messages.ts
+++ b/ui/web/src/pages/chat/hooks/use-chat-messages.ts
@@ -61,6 +61,11 @@ export function useChatMessages(sessionKey: string, agentId: string) {
blockRepliesRef.current = [];
cancelAnimationFrame(rafHandleRef.current);
rafPendingRef.current = false;
+ // Clear messages when navigating away from a session (empty key).
+ // When switching to another session, loadHistory() will replace them.
+ if (!sessionKey) {
+ setMessages([]);
+ }
}, [sessionKey]);
// Load history (no loading spinner — the empty state placeholder is shown instead)
diff --git a/ui/web/src/pages/providers/provider-list-row.tsx b/ui/web/src/pages/providers/provider-list-row.tsx
index 52c9144d..683fa5e2 100644
--- a/ui/web/src/pages/providers/provider-list-row.tsx
+++ b/ui/web/src/pages/providers/provider-list-row.tsx
@@ -17,9 +17,11 @@ export function ProviderListRow({ provider, onClick, onDelete }: ProviderListRow
const tb = PROVIDER_TYPE_BADGE[provider.provider_type] ?? { label: provider.provider_type, variant: "outline" as const };
return (
-
)}
-
+
);
}
diff --git a/ui/web/src/routes.tsx b/ui/web/src/routes.tsx
index bdde235c..af7afa1e 100644
--- a/ui/web/src/routes.tsx
+++ b/ui/web/src/routes.tsx
@@ -146,7 +146,7 @@ export function AppRoutes() {
>
} />
} />
- } />
+ } />
} />
} />
} />