fix(workspace): 恢复先删后建并修复新会话初始化时序

- 新会话初始化改为 delete -> create

- 通过初始化就绪门控,确保 history 在创建完成后再加载

- 发送消息前等待初始化完成,避免与初始化并发
This commit is contained in:
肖应宇 2026-04-28 18:34:18 +08:00
parent d1cdb7eef7
commit 08e8de5e3e
1 changed files with 42 additions and 18 deletions

View File

@ -81,16 +81,23 @@ export default function ChatPage() {
() => isNewThread && !safeThreadId, () => isNewThread && !safeThreadId,
[isNewThread, safeThreadId], [isNewThread, safeThreadId],
); );
const [isThreadInitReady, setIsThreadInitReady] = useState(false);
const streamThreadId = useMemo(() => { const streamThreadId = useMemo(() => {
if (isNewThread && createNewSession) { if (!safeThreadId) {
return undefined;
}
// In /new flow, defer history loading until thread init is finished:
// delete -> create -> history.
if (isNewThread && !isThreadInitReady) {
return undefined; return undefined;
} }
return safeThreadId; return safeThreadId;
}, [createNewSession, isNewThread, safeThreadId]); }, [isNewThread, isThreadInitReady, safeThreadId]);
const apiClient = useMemo(() => getAPIClient(isMock), [isMock]); const apiClient = useMemo(() => getAPIClient(isMock), [isMock]);
const warnedMissingThreadIdRef = useRef(false); const warnedMissingThreadIdRef = useRef(false);
const initializedThreadRef = useRef<string | null>(null); const initializedThreadRef = useRef<string | null>(null);
const threadInitPromiseRef = useRef<Promise<void> | null>(null);
const { showNotification } = useNotification(); const { showNotification } = useNotification();
const currentSlogan = motivationSlogans[ const currentSlogan = motivationSlogans[
@ -130,6 +137,7 @@ export default function ChatPage() {
useEffect(() => { useEffect(() => {
if (!isNewThread) { if (!isNewThread) {
warnedMissingThreadIdRef.current = false; warnedMissingThreadIdRef.current = false;
setIsThreadInitReady(true);
return; return;
} }
if (!safeThreadId) { if (!safeThreadId) {
@ -137,29 +145,38 @@ export default function ChatPage() {
warnedMissingThreadIdRef.current = true; warnedMissingThreadIdRef.current = true;
toast.error(t.chatPage.missingThreadIdForCreate); toast.error(t.chatPage.missingThreadIdForCreate);
} }
setIsThreadInitReady(false);
return; return;
} }
warnedMissingThreadIdRef.current = false; warnedMissingThreadIdRef.current = false;
if (initializedThreadRef.current === safeThreadId) return; if (initializedThreadRef.current === safeThreadId) return;
initializedThreadRef.current = safeThreadId; initializedThreadRef.current = safeThreadId;
void apiClient.threads setIsThreadInitReady(false);
// TODO: 先注释先删除再创建的逻辑
// .delete(safeThreadId) const initPromise = apiClient.threads
// .catch(() => undefined) .delete(safeThreadId)
// .then(() => .catch(() => undefined)
// apiClient.threads.create({ .then(() =>
// threadId: safeThreadId, apiClient.threads.create({
// ifExists: "raise",
// }),
// )
.create({
threadId: safeThreadId, threadId: safeThreadId,
ifExists: "do_nothing", ifExists: "do_nothing",
}),
)
.then(() => {
setIsThreadInitReady(true);
}) })
.catch(() => { .catch(() => {
initializedThreadRef.current = null; initializedThreadRef.current = null;
setIsThreadInitReady(false);
toast.error(t.chatPage.createSessionFailed); toast.error(t.chatPage.createSessionFailed);
}); });
threadInitPromiseRef.current = initPromise;
void initPromise.finally(() => {
if (threadInitPromiseRef.current === initPromise) {
threadInitPromiseRef.current = null;
}
});
}, [ }, [
apiClient, apiClient,
isNewThread, isNewThread,
@ -291,7 +308,7 @@ export default function ChatPage() {
const [showExitDialog, setShowExitDialog] = useState(false); const [showExitDialog, setShowExitDialog] = useState(false);
const isStreaming = isUploading || thread.isLoading; const isStreaming = isUploading || thread.isLoading;
const handleSubmit = useCallback( const handleSubmit = useCallback(
(message: Parameters<typeof sendMessage>[1]) => { async (message: Parameters<typeof sendMessage>[1]) => {
if (isSelectedSkillBootstrapping) { if (isSelectedSkillBootstrapping) {
return; return;
} }
@ -299,6 +316,12 @@ export default function ChatPage() {
toast.error(t.chatPage.missingThreadIdForSend); toast.error(t.chatPage.missingThreadIdForSend);
return; return;
} }
if (isNewThread && safeThreadId) {
await threadInitPromiseRef.current;
}
if (isNewThread && safeThreadId && !isThreadInitReady) {
return;
}
setHasSubmitted(true); setHasSubmitted(true);
if (safeThreadId && (isNewThread || showWelcomeStyle)) { if (safeThreadId && (isNewThread || showWelcomeStyle)) {
router.replace(`/workspace/chats/${safeThreadId}?is_chatting=true`); router.replace(`/workspace/chats/${safeThreadId}?is_chatting=true`);
@ -307,6 +330,7 @@ export default function ChatPage() {
}, },
[ [
isNewThread, isNewThread,
isThreadInitReady,
isSelectedSkillBootstrapping, isSelectedSkillBootstrapping,
router, router,
safeThreadId, safeThreadId,
@ -633,14 +657,14 @@ export default function ChatPage() {
type: POST_MESSAGE_TYPES.IS_CHATTING, type: POST_MESSAGE_TYPES.IS_CHATTING,
isChatting: false, isChatting: false,
}); });
resetNewSessionState();
// 始终复用 query 中的 thread_id。 // 始终复用 query 中的 thread_id。
const nextQuery = new URLSearchParams(); const nextQuery = new URLSearchParams();
if (threadId && threadId !== "new") { if (threadId && threadId !== "new") {
nextQuery.set("thread_id", threadId); nextQuery.set("thread_id", threadId);
} }
// /workspace/chats/${threadId}?is_chatting=false
router.replace( router.replace(
`/workspace/chats/${threadId}?is_chatting=false`, `/workspace/chats/new?thread_id=${threadId}`,
); );
}} }}
> >