mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-04-25 11:18:22 +00:00
fix(frontend):keep DeerFlow chat thread ids in sync (#1931)
* fix: replay thread sync changes on top of main * fix: avoid stale thread ids during stream startup
This commit is contained in:
parent
3b3e8e1b0b
commit
ab41de2961
@ -41,20 +41,22 @@ export default function AgentChatPage() {
|
|||||||
|
|
||||||
const { agent } = useAgent(agent_name);
|
const { agent } = useAgent(agent_name);
|
||||||
|
|
||||||
const { threadId, isNewThread, setIsNewThread } = useThreadChat();
|
const { threadId, setThreadId, isNewThread, setIsNewThread } =
|
||||||
|
useThreadChat();
|
||||||
const [settings, setSettings] = useThreadSettings(threadId);
|
const [settings, setSettings] = useThreadSettings(threadId);
|
||||||
|
|
||||||
const { showNotification } = useNotification();
|
const { showNotification } = useNotification();
|
||||||
const [thread, sendMessage] = useThreadStream({
|
const [thread, sendMessage] = useThreadStream({
|
||||||
threadId: isNewThread ? undefined : threadId,
|
threadId: isNewThread ? undefined : threadId,
|
||||||
context: { ...settings.context, agent_name: agent_name },
|
context: { ...settings.context, agent_name: agent_name },
|
||||||
onStart: () => {
|
onStart: (createdThreadId) => {
|
||||||
|
setThreadId(createdThreadId);
|
||||||
setIsNewThread(false);
|
setIsNewThread(false);
|
||||||
// ! Important: Never use next.js router for navigation in this case, otherwise it will cause the thread to re-mount and lose all states. Use native history API instead.
|
// ! Important: Never use next.js router for navigation in this case, otherwise it will cause the thread to re-mount and lose all states. Use native history API instead.
|
||||||
history.replaceState(
|
history.replaceState(
|
||||||
null,
|
null,
|
||||||
"",
|
"",
|
||||||
`/workspace/agents/${agent_name}/chats/${threadId}`,
|
`/workspace/agents/${agent_name}/chats/${createdThreadId}`,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
onFinish: (state) => {
|
onFinish: (state) => {
|
||||||
|
|||||||
@ -32,7 +32,8 @@ import { cn } from "@/lib/utils";
|
|||||||
export default function ChatPage() {
|
export default function ChatPage() {
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const [showFollowups, setShowFollowups] = useState(false);
|
const [showFollowups, setShowFollowups] = useState(false);
|
||||||
const { threadId, isNewThread, setIsNewThread, isMock } = useThreadChat();
|
const { threadId, setThreadId, isNewThread, setIsNewThread, isMock } =
|
||||||
|
useThreadChat();
|
||||||
const [settings, setSettings] = useThreadSettings(threadId);
|
const [settings, setSettings] = useThreadSettings(threadId);
|
||||||
const [mounted, setMounted] = useState(false);
|
const [mounted, setMounted] = useState(false);
|
||||||
useSpecificChatMode();
|
useSpecificChatMode();
|
||||||
@ -47,10 +48,11 @@ export default function ChatPage() {
|
|||||||
threadId: isNewThread ? undefined : threadId,
|
threadId: isNewThread ? undefined : threadId,
|
||||||
context: settings.context,
|
context: settings.context,
|
||||||
isMock,
|
isMock,
|
||||||
onStart: () => {
|
onStart: (createdThreadId) => {
|
||||||
|
setThreadId(createdThreadId);
|
||||||
setIsNewThread(false);
|
setIsNewThread(false);
|
||||||
// ! Important: Never use next.js router for navigation in this case, otherwise it will cause the thread to re-mount and lose all states. Use native history API instead.
|
// ! Important: Never use next.js router for navigation in this case, otherwise it will cause the thread to re-mount and lose all states. Use native history API instead.
|
||||||
history.replaceState(null, "", `/workspace/chats/${threadId}`);
|
history.replaceState(null, "", `/workspace/chats/${createdThreadId}`);
|
||||||
},
|
},
|
||||||
onFinish: (state) => {
|
onFinish: (state) => {
|
||||||
if (document.hidden || !document.hasFocus()) {
|
if (document.hidden || !document.hasFocus()) {
|
||||||
|
|||||||
@ -22,8 +22,11 @@ export function useThreadChat() {
|
|||||||
if (pathname.endsWith("/new")) {
|
if (pathname.endsWith("/new")) {
|
||||||
setIsNewThread(true);
|
setIsNewThread(true);
|
||||||
setThreadId(uuid());
|
setThreadId(uuid());
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}, [pathname]);
|
setIsNewThread(false);
|
||||||
|
setThreadId(threadIdFromPath);
|
||||||
|
}, [pathname, threadIdFromPath]);
|
||||||
const isMock = searchParams.get("mock") === "true";
|
const isMock = searchParams.get("mock") === "true";
|
||||||
return { threadId, isNewThread, setIsNewThread, isMock };
|
return { threadId, setThreadId, isNewThread, setIsNewThread, isMock };
|
||||||
}
|
}
|
||||||
|
|||||||
@ -164,9 +164,11 @@ export function useThreadStream({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const normalizedThreadId = threadId ?? null;
|
const normalizedThreadId = threadId ?? null;
|
||||||
if (!normalizedThreadId) {
|
if (!normalizedThreadId) {
|
||||||
// Just reset for new thread creation when threadId becomes null/undefined
|
// Reset when the UI moves back to a brand new unsaved thread.
|
||||||
startedRef.current = false;
|
startedRef.current = false;
|
||||||
setOnStreamThreadId(normalizedThreadId);
|
setOnStreamThreadId(normalizedThreadId);
|
||||||
|
} else {
|
||||||
|
setOnStreamThreadId(normalizedThreadId);
|
||||||
}
|
}
|
||||||
threadIdRef.current = normalizedThreadId;
|
threadIdRef.current = normalizedThreadId;
|
||||||
}, [threadId]);
|
}, [threadId]);
|
||||||
@ -294,6 +296,16 @@ export function useThreadStream({
|
|||||||
// Track message count before sending so we know when server has responded
|
// Track message count before sending so we know when server has responded
|
||||||
const prevMsgCountRef = useRef(thread.messages.length);
|
const prevMsgCountRef = useRef(thread.messages.length);
|
||||||
|
|
||||||
|
// Reset thread-local pending UI state when switching between threads so
|
||||||
|
// optimistic messages and in-flight guards do not leak across chat views.
|
||||||
|
useEffect(() => {
|
||||||
|
startedRef.current = false;
|
||||||
|
sendInFlightRef.current = false;
|
||||||
|
prevMsgCountRef.current = 0;
|
||||||
|
setOptimisticMessages([]);
|
||||||
|
setIsUploading(false);
|
||||||
|
}, [threadId]);
|
||||||
|
|
||||||
// Clear optimistic when server messages arrive (count increases)
|
// Clear optimistic when server messages arrive (count increases)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
@ -357,7 +369,12 @@ export function useThreadStream({
|
|||||||
}
|
}
|
||||||
setOptimisticMessages(newOptimistic);
|
setOptimisticMessages(newOptimistic);
|
||||||
|
|
||||||
_handleOnStart(threadId);
|
// Only fire onStart immediately for an existing persisted thread.
|
||||||
|
// Brand-new chats should wait for onCreated(meta.thread_id) so URL sync
|
||||||
|
// uses the real server-generated thread id.
|
||||||
|
if (threadIdRef.current) {
|
||||||
|
_handleOnStart(threadId);
|
||||||
|
}
|
||||||
|
|
||||||
let uploadedFileInfo: UploadedFileInfo[] = [];
|
let uploadedFileInfo: UploadedFileInfo[] = [];
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user