diff --git a/src/pages/chat/ChatPage.tsx b/src/pages/chat/ChatPage.tsx index a74ce78..30cd59b 100644 --- a/src/pages/chat/ChatPage.tsx +++ b/src/pages/chat/ChatPage.tsx @@ -15,6 +15,7 @@ import { useChatSender } from './hooks/useChatSender'; import { markdownToPlainText } from './utils/copy'; const lastRoomKey = (agentId: string) => `chat:lastRoom:${agentId}`; +const isValidRoomId = (v: string | null): v is string => !!v && !String(v).startsWith('legacy_'); export default function ChatPage() { const { id } = useParams(); @@ -36,11 +37,14 @@ export default function ChatPage() { useEffect(() => { if (!id) return; + abortRef.current?.abort(); + setRoomId(null); + setHighlightId(null); const s = searchParams.get('session'); const m = searchParams.get('msg'); - if (s) { + if (isValidRoomId(s)) { setRoomId(s); try { localStorage.setItem(lastRoomKey(id), s); @@ -55,6 +59,12 @@ export default function ChatPage() { } return; } + if (s && !isValidRoomId(s)) { + const next = new URLSearchParams(searchParams); + next.delete('session'); + next.delete('msg'); + setSearchParams(next, { replace: true }); + } const saved = (() => { try { @@ -63,10 +73,17 @@ export default function ChatPage() { return null; } })(); - if (saved) { + if (isValidRoomId(saved)) { setRoomId(saved); return; } + if (saved && !isValidRoomId(saved)) { + try { + localStorage.removeItem(lastRoomKey(id)); + } catch { + // ignore + } + } (async () => { try { diff --git a/src/pages/chat/hooks/useChatData.ts b/src/pages/chat/hooks/useChatData.ts index 0f60eb5..d39131e 100644 --- a/src/pages/chat/hooks/useChatData.ts +++ b/src/pages/chat/hooks/useChatData.ts @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; import type { Agent, BranchInfo, ChatMessage, ModelOverrides } from '../../../api'; import { AgentAPI, ChatAPI } from '../../../api'; import { parseAgentModels } from '../utils/agentModels'; @@ -18,6 +18,7 @@ export function useChatData(args: { const [agentList, setAgentList] = useState([]); const [messages, setMessages] = useState([]); const [branches, setBranches] = useState>({}); + const loadSeqRef = useRef(0); const loadAgent = async () => { if (!agentId) { @@ -49,18 +50,14 @@ export function useChatData(args: { const loadMessages = async () => { if (!roomId) return; - const his = await ChatAPI.history(roomId); + const seq = ++loadSeqRef.current; + const rid = roomId; + const his = await ChatAPI.history(rid); + if (seq !== loadSeqRef.current) return; + if (rid !== roomId) return; setMessages(Array.isArray(his.messages) ? his.messages : []); setBranches(his.branches || {}); requestAnimationFrame(() => { - if (highlightId) { - const el = document.getElementById('msg-' + highlightId); - if (el) { - el.scrollIntoView({ behavior: 'smooth', block: 'center' }); - setTimeout(() => setHighlightId(null), 3000); - return; - } - } if (!initialScrollDoneRef.current) { scrollBottom(true); initialScrollDoneRef.current = true; @@ -78,9 +75,12 @@ export function useChatData(args: { if (!agentId) { setAgent(null); setMessages([]); + setBranches({}); setOverrides(() => ({})); return abort; } + setMessages([]); + setBranches({}); loadAgent(); setOverrides(() => ({})); return abort; @@ -89,13 +89,22 @@ export function useChatData(args: { useEffect(() => { initialScrollDoneRef.current = false; - }, [agentId]); + }, [agentId, roomId]); useEffect(() => { if (!roomId) return; loadMessages(); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [roomId, highlightId, agentId]); + }, [roomId]); + + useEffect(() => { + if (!highlightId) return; + const el = document.getElementById('msg-' + highlightId); + if (!el) return; + el.scrollIntoView({ behavior: 'smooth', block: 'center' }); + const t = setTimeout(() => setHighlightId(null), 3000); + return () => clearTimeout(t); + }, [highlightId, setHighlightId]); return { agent,