mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-07-22 14:08:28 +00:00
fix(frontend): reset new chat on client-side navigation (#3673)
* fix(frontend): reset new chat on client-side navigation Drive the chat reset effect with Next.js's reactive pathname instead of the render-time window.location-derived flag. During App Router transitions, window.location may still point to the previous thread until commit, leaving chat state stale until another UI interaction triggers a render. Preserve the stale 'new' param guard so created thread UUIDs are not overwritten. * test(frontend): add e2e to cover history-only new chat reset * docs(frontend): clarify new chat pathname synchronization
This commit is contained in:
parent
4572217038
commit
5ddf698895
@ -27,6 +27,9 @@ export function resetThreadChatAfterDelete(detail: ThreadChatResetDetail) {
|
||||
export function useThreadChat() {
|
||||
const { thread_id: threadIdFromPath } = useParams<{ thread_id: string }>();
|
||||
const pathname = usePathname();
|
||||
// Render-time values use the committed browser URL. The sync effect below
|
||||
// intentionally watches the reactive pathname so client navigation still
|
||||
// schedules a reset when window.location is stale during render.
|
||||
const actualPathname =
|
||||
typeof window === "undefined" ? pathname : window.location.pathname;
|
||||
const isNewPath = actualPathname.endsWith("/new");
|
||||
@ -57,7 +60,7 @@ export function useThreadChat() {
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (isNewPath) {
|
||||
if (pathname.endsWith("/new")) {
|
||||
const nextThreadId = newThreadIdRef.current ?? uuid();
|
||||
newThreadIdRef.current = nextThreadId;
|
||||
setIsNewThreadState(true);
|
||||
@ -65,17 +68,15 @@ export function useThreadChat() {
|
||||
return;
|
||||
}
|
||||
newThreadIdRef.current = null;
|
||||
// Guard: after history.replaceState updates the URL from /chats/new to
|
||||
// /chats/{UUID}, Next.js useParams may still return the stale "new" value
|
||||
// because replaceState does not trigger router updates. Avoid propagating
|
||||
// this invalid thread ID to downstream hooks (e.g. useStream), which would
|
||||
// cause a 422 from LangGraph Server.
|
||||
// Native history updates the canonical pathname but preserves the route
|
||||
// tree, so useParams may still return the stale "new" value. Avoid passing
|
||||
// it to downstream hooks (e.g. useStream), which would cause a 422.
|
||||
if (threadIdFromPath === "new") {
|
||||
return;
|
||||
}
|
||||
setIsNewThreadState(false);
|
||||
setThreadIdState(threadIdFromPath);
|
||||
}, [isNewPath, threadIdFromPath]);
|
||||
}, [pathname, threadIdFromPath]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleReset = (event: Event) => {
|
||||
|
||||
@ -202,6 +202,43 @@ test.describe("Thread history", () => {
|
||||
await expect(page.getByPlaceholder(/how can i assist you/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test("new chat resets immediately after a history-only thread URL update", async ({
|
||||
page,
|
||||
}) => {
|
||||
mockLangGraphAPI(page);
|
||||
|
||||
await page.goto("/workspace/chats/new");
|
||||
const textarea = page.getByPlaceholder(/how can i assist you/i);
|
||||
await expect(textarea).toBeVisible({ timeout: 15_000 });
|
||||
await textarea.fill("Message that must disappear in the next new chat");
|
||||
await textarea.press("Enter");
|
||||
await expect(page.getByText("Hello from DeerFlow!")).toBeVisible({
|
||||
timeout: 15_000,
|
||||
});
|
||||
|
||||
// A newly created chat changes the URL with history.replaceState so the
|
||||
// active stream is not remounted. Reproduce that history-only transition:
|
||||
// the canonical pathname becomes the UUID while useParams can stay "new".
|
||||
await page.evaluate((threadId) => {
|
||||
history.replaceState(null, "", `/workspace/chats/${threadId}`);
|
||||
}, MOCK_THREAD_ID);
|
||||
|
||||
const newChatLink = page.locator(
|
||||
"[data-sidebar='sidebar'] a[href='/workspace/chats/new']",
|
||||
);
|
||||
await expect(page).toHaveURL(
|
||||
new RegExp(`/workspace/chats/${MOCK_THREAD_ID}$`),
|
||||
);
|
||||
await expect(newChatLink).toHaveAttribute("data-active", "false");
|
||||
|
||||
// One click must reset the chat without a second click or unrelated UI
|
||||
// interaction forcing another render.
|
||||
await newChatLink.click();
|
||||
await expect(page).toHaveURL(/\/workspace\/chats\/new$/);
|
||||
await expect(page.getByText("Hello from DeerFlow!")).toHaveCount(0);
|
||||
await expect(textarea).toBeVisible();
|
||||
});
|
||||
|
||||
test("deleting the active newly created chat returns to the new chat screen", async ({
|
||||
page,
|
||||
}) => {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user