fix: 稳定 /new 线程复用逻辑并调整会话跳转

This commit is contained in:
肖应宇 2026-04-01 15:10:03 +08:00
parent 081adb34b3
commit 875cfa7e7b
2 changed files with 29 additions and 12 deletions

View File

@ -66,7 +66,10 @@ export default function ChatPage() {
!isNewThread || !isNewThread ||
searchParams.get("xclaw_used")?.trim().toLowerCase() === "true"; searchParams.get("xclaw_used")?.trim().toLowerCase() === "true";
// Submission strategy: // Submission strategy: always reuse the thread id from query; never create new.
const createNewSession = false;
/*
// Original strategy:
// - isnew=false + thread_id: reuse existing thread (explicit request from URL) // - isnew=false + thread_id: reuse existing thread (explicit request from URL)
// - xclaw_used=true: follow `isnew` (isnew=false => reuse existing thread) // - xclaw_used=true: follow `isnew` (isnew=false => reuse existing thread)
// - otherwise: create/start a new session (no history) // - otherwise: create/start a new session (no history)
@ -86,6 +89,7 @@ export default function ChatPage() {
} }
return searchParams.get("isnew")?.trim().toLowerCase() !== "false"; return searchParams.get("isnew")?.trim().toLowerCase() !== "false";
}, [isNewThread, searchParams]); }, [isNewThread, searchParams]);
*/
const streamThreadId = useMemo(() => { const streamThreadId = useMemo(() => {
return isNewThread && createNewSession ? undefined : threadId; return isNewThread && createNewSession ? undefined : threadId;
}, [createNewSession, isNewThread, threadId]); }, [createNewSession, isNewThread, threadId]);
@ -105,7 +109,8 @@ export default function ChatPage() {
onStart: (currentThreadId) => { onStart: (currentThreadId) => {
setIsNewThread(false); setIsNewThread(false);
// Keep /new in history so router.back() can return to it. // Keep /new in history so router.back() can return to it.
history.pushState(null, "", pathOfThread(currentThreadId)); router.replace(`/workspace/chats/${currentThreadId}`);
// history.pushState(null, "", pathOfThread(currentThreadId));
}, },
onFinish: (state) => { onFinish: (state) => {
if (document.hidden || !document.hasFocus()) { if (document.hidden || !document.hasFocus()) {
@ -226,6 +231,9 @@ export default function ChatPage() {
setArtifactsOpen, setArtifactsOpen,
setIsNewThread, setIsNewThread,
]); ]);
// shouldRenderHistory || historyCutoff === null
// console.log('shouldRenderHistory', shouldRenderHistory, 'historyCutoff', historyCutoff);
return ( return (
<ThreadContext.Provider value={{ thread }}> <ThreadContext.Provider value={{ thread }}>
@ -497,7 +505,7 @@ export default function ChatPage() {
XClawUsed: false, XClawUsed: false,
}); });
resetNewSessionState(); resetNewSessionState();
// 因为threadId可能为undefined所以这里不直接导航到 /workspace/chats/new而是通过 replace 的方式更新 URL 参数,保持在当前页面,触发 useThreadChat 重新计算状态 // 始终复用 query 中的 thread_id
const nextQuery = new URLSearchParams(); const nextQuery = new URLSearchParams();
nextQuery.set("isnew", "false"); nextQuery.set("isnew", "false");
nextQuery.set("xclaw_used", "false"); nextQuery.set("xclaw_used", "false");

View File

@ -7,6 +7,7 @@ export function useThreadChat() {
const pathname = usePathname(); const pathname = usePathname();
const router = useRouter(); const router = useRouter();
const params = useParams<{ thread_id?: string }>(); const params = useParams<{ thread_id?: string }>();
// 兜底:当 params 还未就绪时,从 pathname 解析 thread_id。
const threadIdFromPathname = (() => { const threadIdFromPathname = (() => {
const parts = pathname.split("?")[0]?.split("/") ?? []; const parts = pathname.split("?")[0]?.split("/") ?? [];
const idx = parts.lastIndexOf("chats"); const idx = parts.lastIndexOf("chats");
@ -15,11 +16,14 @@ export function useThreadChat() {
} }
return undefined; return undefined;
})(); })();
const threadIdFromPath = params?.thread_id !== 'new' ? params?.thread_id : threadIdFromPathname; // 优先使用 params如果是 "new",则回退到 pathname 解析出的 id。
console.log("[useThreadChat] pathname", pathname); const threadIdFromPath =
console.log("[useThreadChat] params.thread_id", params?.thread_id); params?.thread_id !== "new" ? params?.thread_id : threadIdFromPathname;
console.log("[useThreadChat] threadIdFromPathname", threadIdFromPathname); // console.log("[useThreadChat] pathname", pathname);
console.log("[useThreadChat] threadIdFromPath", threadIdFromPath); // console.log("[useThreadChat] params.thread_id", params?.thread_id);
// console.log("[useThreadChat] threadIdFromPathname", threadIdFromPathname);
// console.log("[useThreadChat] threadIdFromPath", threadIdFromPath);
// 持久化兜底:用于处理首屏水合或 params 时序问题。
const readStoredThreadId = () => { const readStoredThreadId = () => {
if (typeof window === "undefined") { if (typeof window === "undefined") {
return undefined; return undefined;
@ -29,6 +33,7 @@ export function useThreadChat() {
}; };
const searchParams = useSearchParams(); const searchParams = useSearchParams();
// 读取 query 的 thread_id先用 hook必要时用 window 兜底)。
const readQueryThreadId = () => { const readQueryThreadId = () => {
const fromHook = searchParams.get("thread_id")?.trim(); const fromHook = searchParams.get("thread_id")?.trim();
if (fromHook && fromHook !== "new") { if (fromHook && fromHook !== "new") {
@ -45,8 +50,10 @@ export function useThreadChat() {
} }
return undefined; return undefined;
}; };
const queryThreadIdFromParams = readQueryThreadId(); const queryThreadIdFromParams = readQueryThreadId();
console.log("[useThreadChat] query.thread_id", queryThreadIdFromParams); // console.log("[useThreadChat] query.thread_id", queryThreadIdFromParams);
// 归一化:当值为 "new" 时,替换为 query 中的 thread_id如果存在
const normalizeThreadId = (value?: string | null) => { const normalizeThreadId = (value?: string | null) => {
if (!value) { if (!value) {
return undefined; return undefined;
@ -58,17 +65,19 @@ export function useThreadChat() {
searchParams.get("isnew")?.trim().toLowerCase() === "false"; searchParams.get("isnew")?.trim().toLowerCase() === "false";
const effectiveThreadIdFromPath = const effectiveThreadIdFromPath =
normalizeThreadId(threadIdFromPath) ?? readStoredThreadId(); normalizeThreadId(threadIdFromPath) ?? readStoredThreadId();
console.log("[useThreadChat] effectiveThreadIdFromPath", effectiveThreadIdFromPath); // console.log("[useThreadChat] effectiveThreadIdFromPath", effectiveThreadIdFromPath);
const [threadId, setThreadId] = useState(() => { const [threadId, setThreadId] = useState(() => {
return effectiveThreadIdFromPath ?? undefined; return effectiveThreadIdFromPath ?? undefined;
}); });
// /new 或缺少 query 的 thread_id 时,视为新会话状态。 但是这个并不是新会话的意思,而是说当前处在对话状态。
const [isNewThread, setIsNewThread] = useState( const [isNewThread, setIsNewThread] = useState(
() => threadIdFromPath === "new", () => threadIdFromPath === "new" || !queryThreadIdFromParams,
); );
useEffect(() => { useEffect(() => {
// 记住最近一次有效的 thread_id供下次加载兜底使用。
if (threadId && threadId !== "new" && typeof window !== "undefined") { if (threadId && threadId !== "new" && typeof window !== "undefined") {
window.sessionStorage.setItem("workspace.thread_id", threadId); window.sessionStorage.setItem("workspace.thread_id", threadId);
} }
@ -82,7 +91,7 @@ export function useThreadChat() {
return; return;
} }
setIsNewThread(false); setIsNewThread(false);
console.log("threadIdFromPath", threadIdFromPath, "normalized", normalizeThreadId(threadIdFromPath)); // console.log("threadIdFromPath", threadIdFromPath, "normalized", normalizeThreadId(threadIdFromPath));
setThreadId(normalizeThreadId(threadIdFromPath)); setThreadId(normalizeThreadId(threadIdFromPath));
}, [pathname, router, searchParams, threadIdFromPath]); }, [pathname, router, searchParams, threadIdFromPath]);