fix(P0): 恢复 handleNewThread 立即创建线程,移除 ensureThread 懒创建
根因:ensureThread 异步 setCurrentThreadId 后 React 状态未同步刷新, useStream 仍绑定 undefined threadId,thread.submit() 提交到错误线程。 修复: - handleNewThread 恢复为 await client.threads.create()(立即创建) - doSubmit 直接使用 currentThreadId(已同步可用) - 移除 ensureThread、skipNextSwitchRef 等懒创建相关逻辑 - 空线程问题后续通过侧边栏过滤解决 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
fd84f07f1d
commit
99b3cda23c
@@ -17,7 +17,7 @@ export function useConversation(
|
||||
const [resetLoading, setResetLoading] = useState(false);
|
||||
const [threadLoadFailed, setThreadLoadFailed] = useState(false);
|
||||
|
||||
const handleNewThread = useCallback(() => {
|
||||
const handleNewThread = useCallback(async () => {
|
||||
if (currentThreadId) {
|
||||
try { localStorage.setItem(`draft_${currentThreadId}`, input); } catch { /* ignore */ }
|
||||
}
|
||||
@@ -25,9 +25,15 @@ export function useConversation(
|
||||
setHistoricalMessages([]);
|
||||
setHistoricalUi([]);
|
||||
setThreadLoadFailed(false);
|
||||
setCurrentThreadId(null);
|
||||
try {
|
||||
const t = await client.threads.create();
|
||||
setThreads((prev) => [t as ThreadItem, ...prev]);
|
||||
setCurrentThreadId(t.thread_id);
|
||||
} catch {
|
||||
setCurrentThreadId(null);
|
||||
}
|
||||
setSidebarOpen(false);
|
||||
}, [currentThreadId, input, setInput]);
|
||||
}, [currentThreadId, input, client, setInput]);
|
||||
|
||||
const handleSelectThread = useCallback(async (threadId: string) => {
|
||||
if (currentThreadId) {
|
||||
|
||||
+8
-23
@@ -43,7 +43,7 @@ function App() {
|
||||
historicalMessages, historicalUi,
|
||||
resetLoading, threadLoadFailed,
|
||||
handleNewThread, handleSelectThread, handleDeleteThread, handleResetThread,
|
||||
applyThreadTitle, ensureThread,
|
||||
applyThreadTitle,
|
||||
} = useConversation(client, input, setInput);
|
||||
|
||||
// Canvas panel state
|
||||
@@ -63,8 +63,6 @@ function App() {
|
||||
type ConcurrentStatus = "idle" | "generating" | "stopping" | "cancelling";
|
||||
const [concurrentStatus, setConcurrentStatus] = useState<ConcurrentStatus>("idle");
|
||||
const wasStoppedRef = useRef(false);
|
||||
// Skip the switchThread useEffect when doSubmit already called it manually
|
||||
const skipNextSwitchRef = useRef(false);
|
||||
|
||||
interface SubmitPayload {
|
||||
text: string;
|
||||
@@ -97,13 +95,8 @@ function App() {
|
||||
return () => window.removeEventListener("open-canvas", handler);
|
||||
}, []);
|
||||
|
||||
// Clear error and stale stream state when thread switches.
|
||||
// Skip when doSubmit set the flag (new thread created during submit — don't clear mid-stream).
|
||||
// Clear error and stale stream state when thread switches
|
||||
useEffect(() => {
|
||||
if (skipNextSwitchRef.current) {
|
||||
skipNextSwitchRef.current = false;
|
||||
return;
|
||||
}
|
||||
thread.switchThread(currentThreadId);
|
||||
}, [currentThreadId]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
@@ -211,21 +204,13 @@ function App() {
|
||||
setInput("");
|
||||
setSourceLabel(null);
|
||||
|
||||
// Lazy thread creation: create a real thread only when the user first sends a message
|
||||
const activeThreadId = await ensureThread();
|
||||
if (!activeThreadId) {
|
||||
if (!currentThreadId) {
|
||||
setConcurrentStatus("idle");
|
||||
return;
|
||||
}
|
||||
// For a newly created thread, currentThreadId just changed via setCurrentThreadId
|
||||
// but React state hasn't flushed yet. Skip the useEffect switchThread call to avoid
|
||||
// clearing thread.messages mid-stream. useStream picks up the new threadId via prop.
|
||||
if (!currentThreadId) {
|
||||
skipNextSwitchRef.current = true;
|
||||
}
|
||||
|
||||
try { localStorage.removeItem(`draft_${activeThreadId}`); } catch { /* ignore */ }
|
||||
updateThreadLastActive(activeThreadId);
|
||||
try { localStorage.removeItem(`draft_${currentThreadId}`); } catch { /* ignore */ }
|
||||
updateThreadLastActive(currentThreadId);
|
||||
|
||||
const enabledTools =
|
||||
tools.size > 0
|
||||
@@ -269,9 +254,9 @@ function App() {
|
||||
// Scroll to bottom after submitting so user message stays visible
|
||||
setTimeout(() => bottomRef.current?.scrollIntoView({ behavior: "smooth" }), 100);
|
||||
|
||||
if (activeThreadId && text) {
|
||||
const existingTitle = threads.find((t) => t.thread_id === activeThreadId)?.metadata?.title as string | undefined;
|
||||
applyThreadTitle(activeThreadId, text, activeMessages.length, existingTitle);
|
||||
if (currentThreadId && text) {
|
||||
const existingTitle = threads.find((t) => t.thread_id === currentThreadId)?.metadata?.title as string | undefined;
|
||||
applyThreadTitle(currentThreadId, text, activeMessages.length, existingTitle);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user