fix(chat): prevent first user message from being swallowed in new conversations
The optimistic message clearing effect cleared too eagerly — any stream message (including AI messages from messages-tuple events) triggered the clear before the server's human message had arrived via values events. For new threads this caused the user's first prompt to disappear permanently. Only clear optimistic messages once the server's human message has been confirmed to arrive in thread.messages, not just when any message arrives. Fixes #2730
This commit is contained in:
@@ -286,6 +286,13 @@ export function useThreadStream({
|
|||||||
const summarizedRef = useRef<Set<string>>(null);
|
const summarizedRef = useRef<Set<string>>(null);
|
||||||
// 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);
|
||||||
|
// Track human message count before sending to prevent clearing optimistic
|
||||||
|
// messages before the server's human message arrives (e.g. when AI messages
|
||||||
|
// from "messages-tuple" events arrive before the input human message from
|
||||||
|
// "values" events).
|
||||||
|
const prevHumanMsgCountRef = useRef(
|
||||||
|
thread.messages.filter((m) => m.type === "human").length,
|
||||||
|
);
|
||||||
|
|
||||||
summarizedRef.current ??= new Set<string>();
|
summarizedRef.current ??= new Set<string>();
|
||||||
|
|
||||||
@@ -296,12 +303,22 @@ export function useThreadStream({
|
|||||||
sendInFlightRef.current = false;
|
sendInFlightRef.current = false;
|
||||||
}, [threadId]);
|
}, [threadId]);
|
||||||
|
|
||||||
// Clear optimistic when server messages arrive (count increases)
|
// Clear optimistic when server messages arrive.
|
||||||
|
// For messages with a human optimistic message, wait until the server's
|
||||||
|
// human message has arrived to avoid clearing before the input message
|
||||||
|
// appears in the stream (the input message may arrive via "values" events
|
||||||
|
// after individual "messages-tuple" events for AI messages).
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (optimisticMessages.length === 0) return;
|
||||||
optimisticMessages.length > 0 &&
|
|
||||||
thread.messages.length > prevMsgCountRef.current
|
const hasHumanOptimistic = optimisticMessages.some(
|
||||||
) {
|
(m) => m.type === "human",
|
||||||
|
);
|
||||||
|
const newHumanMsgArrived =
|
||||||
|
thread.messages.filter((m) => m.type === "human").length >
|
||||||
|
prevHumanMsgCountRef.current;
|
||||||
|
|
||||||
|
if (!hasHumanOptimistic || newHumanMsgArrived) {
|
||||||
setOptimisticMessages([]);
|
setOptimisticMessages([]);
|
||||||
}
|
}
|
||||||
}, [thread.messages.length, optimisticMessages.length]);
|
}, [thread.messages.length, optimisticMessages.length]);
|
||||||
@@ -322,6 +339,9 @@ export function useThreadStream({
|
|||||||
|
|
||||||
// Capture current count before showing optimistic messages
|
// Capture current count before showing optimistic messages
|
||||||
prevMsgCountRef.current = thread.messages.length;
|
prevMsgCountRef.current = thread.messages.length;
|
||||||
|
prevHumanMsgCountRef.current = thread.messages.filter(
|
||||||
|
(m) => m.type === "human",
|
||||||
|
).length;
|
||||||
|
|
||||||
// Build optimistic files list with uploading status
|
// Build optimistic files list with uploading status
|
||||||
const optimisticFiles: FileInMessage[] = (message.files ?? []).map(
|
const optimisticFiles: FileInMessage[] = (message.files ?? []).map(
|
||||||
|
|||||||
Reference in New Issue
Block a user