fix(frontend): fix new-chat navigation stale state issue (#1077)

- Use router.replace() instead of history.replaceState() so Next.js
  router's internal state is updated on chat start. This ensures
  subsequent "New Chat" clicks are treated as a real cross-route
  navigation (actual-id → "new") rather than a no-op same-path
  navigation, which was causing stale content to persist.

- In ChatLayout, increment the SubtasksProvider key only when
  navigating TO "new" from a non-"new" route. This forces a full
  remount for a fresh new-chat state without remounting when the URL
  transitions from "new" → actual-id (which would interrupt streaming).

Made-with: Cursor

Co-authored-by: DanielWalnut <45447813+hetaoBackend@users.noreply.github.com>
This commit is contained in:
LofiSu
2026-03-11 13:51:51 +08:00
committed by GitHub
parent 2e7964d0aa
commit 5d4fd9cf72
2 changed files with 27 additions and 2 deletions

View File

@@ -1,5 +1,8 @@
"use client";
import { useParams } from "next/navigation";
import { useEffect, useRef, useState } from "react";
import { PromptInputProvider } from "@/components/ai-elements/prompt-input";
import { ArtifactsProvider } from "@/components/workspace/artifacts";
import { SubtasksProvider } from "@/core/tasks/context";
@@ -9,8 +12,24 @@ export default function ChatLayout({
}: {
children: React.ReactNode;
}) {
const { thread_id } = useParams<{ thread_id: string }>();
const prevThreadId = useRef(thread_id);
// Increment only when navigating TO "new" from a non-"new" route.
// This forces a full remount of the subtree for a fresh new-chat state,
// without remounting when the URL transitions from "new" → actual-id
// (which would interrupt streaming).
const [generation, setGeneration] = useState(0);
useEffect(() => {
if (thread_id === "new" && prevThreadId.current !== "new") {
setGeneration((g) => g + 1);
}
prevThreadId.current = thread_id;
}, [thread_id]);
return (
<SubtasksProvider>
<SubtasksProvider key={generation}>
<ArtifactsProvider>
<PromptInputProvider>{children}</PromptInputProvider>
</ArtifactsProvider>

View File

@@ -1,5 +1,6 @@
"use client";
import { useRouter } from "next/navigation";
import { useCallback } from "react";
import { type PromptInputMessage } from "@/components/ai-elements/prompt-input";
@@ -25,6 +26,7 @@ import { cn } from "@/lib/utils";
export default function ChatPage() {
const { t } = useI18n();
const router = useRouter();
const [settings, setSettings] = useLocalSettings();
const { threadId, isNewThread, setIsNewThread, isMock } = useThreadChat();
@@ -38,7 +40,11 @@ export default function ChatPage() {
isMock,
onStart: () => {
setIsNewThread(false);
history.replaceState(null, "", `/workspace/chats/${threadId}`);
// Use router.replace so Next.js Router's internal state is updated.
// This ensures subsequent "New Chat" clicks are treated as a real
// cross-route navigation (actual-id → "new") rather than a no-op
// same-path navigation, which was causing stale content to persist.
router.replace(`/workspace/chats/${threadId}`);
},
onFinish: (state) => {
if (document.hidden || !document.hasFocus()) {