fix: lazy thread creation — only create thread on first message send
Previously handleNewThread called client.threads.create() immediately, leaving empty "新对话" threads in the sidebar when users clicked new thread but never sent a message (ChatGPT-style behavior). Now handleNewThread just resets UI state (setCurrentThreadId(null)). A new ensureThread() helper in useConversation lazily creates the backend thread only when the user actually submits a message. 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
361b576c6c
commit
902c8331be
@@ -15,23 +15,19 @@ export function useConversation(
|
||||
const [historicalMessages, setHistoricalMessages] = useState<Message[]>([]);
|
||||
const [historicalUi, setHistoricalUi] = useState<UIMsgLocal[]>([]);
|
||||
const [resetLoading, setResetLoading] = useState(false);
|
||||
const [threadLoadFailed, setThreadLoadFailed] = useState(false);
|
||||
|
||||
const handleNewThread = useCallback(async () => {
|
||||
const handleNewThread = useCallback(() => {
|
||||
if (currentThreadId) {
|
||||
try { localStorage.setItem(`draft_${currentThreadId}`, input); } catch { /* ignore */ }
|
||||
}
|
||||
setInput("");
|
||||
setHistoricalMessages([]);
|
||||
setHistoricalUi([]);
|
||||
try {
|
||||
const t = await client.threads.create();
|
||||
setThreads((prev) => [t as ThreadItem, ...prev]);
|
||||
setCurrentThreadId(t.thread_id);
|
||||
} catch {
|
||||
setCurrentThreadId(null);
|
||||
}
|
||||
setThreadLoadFailed(false);
|
||||
setCurrentThreadId(null);
|
||||
setSidebarOpen(false);
|
||||
}, [currentThreadId, input, client, setInput]);
|
||||
}, [currentThreadId, input, setInput]);
|
||||
|
||||
const handleSelectThread = useCallback(async (threadId: string) => {
|
||||
if (currentThreadId) {
|
||||
@@ -48,6 +44,8 @@ export function useConversation(
|
||||
const state = await loadThreadState(client, threadId);
|
||||
setHistoricalMessages(state.messages.length ? state.messages : []);
|
||||
setHistoricalUi(state.ui.length ? (state.ui as UIMsgLocal[]) : []);
|
||||
// Track whether this is a selected thread with no loadable history
|
||||
setThreadLoadFailed(state.messages.length === 0);
|
||||
setCurrentThreadId(threadId);
|
||||
setSidebarOpen(false);
|
||||
}, [currentThreadId, input, client, setInput]);
|
||||
@@ -105,6 +103,22 @@ export function useConversation(
|
||||
}
|
||||
}, [client]);
|
||||
|
||||
/**
|
||||
* Ensures a thread exists, creating one lazily if currentThreadId is null.
|
||||
* Returns the thread_id to use for submission.
|
||||
*/
|
||||
const ensureThread = useCallback(async (): Promise<string | null> => {
|
||||
if (currentThreadId) return currentThreadId;
|
||||
try {
|
||||
const t = await client.threads.create();
|
||||
setThreads((prev) => [t as ThreadItem, ...prev]);
|
||||
setCurrentThreadId(t.thread_id);
|
||||
return t.thread_id;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}, [currentThreadId, client]);
|
||||
|
||||
return {
|
||||
threads,
|
||||
setThreads,
|
||||
@@ -117,11 +131,13 @@ export function useConversation(
|
||||
historicalUi,
|
||||
setHistoricalUi,
|
||||
resetLoading,
|
||||
threadLoadFailed,
|
||||
handleNewThread,
|
||||
handleSelectThread,
|
||||
handleDeleteThread,
|
||||
handleResetThread,
|
||||
applyThreadTitle,
|
||||
updateThreadLastActive,
|
||||
ensureThread,
|
||||
};
|
||||
}
|
||||
|
||||
+14
-7
@@ -43,7 +43,7 @@ function App() {
|
||||
historicalMessages, historicalUi,
|
||||
resetLoading, threadLoadFailed,
|
||||
handleNewThread, handleSelectThread, handleDeleteThread, handleResetThread,
|
||||
applyThreadTitle,
|
||||
applyThreadTitle, ensureThread,
|
||||
} = useConversation(client, input, setInput);
|
||||
|
||||
// Canvas panel state
|
||||
@@ -203,10 +203,17 @@ function App() {
|
||||
const { text, attachedFile: file, activeTools: tools, modelMode: mode } = payload;
|
||||
setInput("");
|
||||
setSourceLabel(null);
|
||||
if (currentThreadId) {
|
||||
try { localStorage.removeItem(`draft_${currentThreadId}`); } catch { /* ignore */ }
|
||||
updateThreadLastActive(currentThreadId);
|
||||
|
||||
// Lazy thread creation: create a real thread only when the user first sends a message
|
||||
const activeThreadId = await ensureThread();
|
||||
if (!activeThreadId) {
|
||||
setConcurrentStatus("idle");
|
||||
return;
|
||||
}
|
||||
thread.switchThread(activeThreadId);
|
||||
|
||||
try { localStorage.removeItem(`draft_${activeThreadId}`); } catch { /* ignore */ }
|
||||
updateThreadLastActive(activeThreadId);
|
||||
|
||||
const enabledTools =
|
||||
tools.size > 0
|
||||
@@ -250,9 +257,9 @@ function App() {
|
||||
// Scroll to bottom after submitting so user message stays visible
|
||||
setTimeout(() => bottomRef.current?.scrollIntoView({ behavior: "smooth" }), 100);
|
||||
|
||||
if (currentThreadId && text) {
|
||||
const existingTitle = threads.find((t) => t.thread_id === currentThreadId)?.metadata?.title as string | undefined;
|
||||
applyThreadTitle(currentThreadId, text, activeMessages.length, existingTitle);
|
||||
if (activeThreadId && text) {
|
||||
const existingTitle = threads.find((t) => t.thread_id === activeThreadId)?.metadata?.title as string | undefined;
|
||||
applyThreadTitle(activeThreadId, text, activeMessages.length, existingTitle);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user