mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-13 22:15:47 +00:00
fix(ui): resolve nested button error and stale agentId in chat
- Provider list: change outer <button> to <div role="button"> to fix invalid nested <button> HTML (delete button inside row button) - Chat page: derive agentId via useMemo from URL session key instead of useState initializer that only runs once (stale on session switch) - Agent change no longer creates ghost sessions — navigates to /chat and lets handleSend create session on first message - Clear messages when navigating away from session (empty key only); session-to-session switch still replaces atomically via loadHistory - Split ROUTES.CHAT into CHAT (nav) + CHAT_PATTERN (route definition) Closes #349
This commit is contained in:
1 parent
2ac5eb6c4b
commit
0973b842a2
5 files changed
+28
-16
No files matched your search
@@ -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",
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 (
|
||||
<button
|
||||
type="button"
|
||||
<div
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={onClick}
|
||||
onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); onClick(); } }}
|
||||
className="flex w-full cursor-pointer items-center gap-3 rounded-lg border bg-card px-4 py-3 text-left transition-all hover:border-primary/30 hover:shadow-sm"
|
||||
>
|
||||
{/* Icon */}
|
||||
@@ -64,6 +66,6 @@ export function ProviderListRow({ provider, onClick, onDelete }: ProviderListRow
|
||||
<Trash2 className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -146,7 +146,7 @@ export function AppRoutes() {
|
||||
>
|
||||
<Route index element={<Navigate to={ROUTES.OVERVIEW} replace />} />
|
||||
<Route path={ROUTES.OVERVIEW} element={<OverviewPage />} />
|
||||
<Route path={ROUTES.CHAT} element={<ChatPage />} />
|
||||
<Route path={ROUTES.CHAT_PATTERN} element={<ChatPage />} />
|
||||
<Route path={ROUTES.AGENTS} element={<AgentsPage key="list" />} />
|
||||
<Route path={ROUTES.AGENT_DETAIL} element={<AgentsPage key="detail" />} />
|
||||
<Route path={ROUTES.TEAMS} element={<TeamsPage key="list" />} />
|
||||
|
||||
Reference in new issue
Block a user