Skip to content

Commit 08eb4bb

Browse files
sasamukuclaude
andcommitted
fix: Fix optimistic update not working on reload for first HumanMessage
Move optimistic update logic from useStream to SessionDetailPageClient to ensure consistent behavior. Previously, the isFirstMessage flag was reset on reload, preventing optimistic updates for the first message after page reload. Changes: - Remove isFirstMessage ref from useStream.ts - Remove optimistic update logic from start function - Expose setMessages from useStream hook - Remove senderName param from useStream (moved to parent component) - Implement handleSendMessage in SessionDetailPageClient with optimistic update - Rollback optimistic message on error 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent 200ae3d commit 08eb4bb

File tree

2 files changed

+35
-40
lines changed

2 files changed

+35
-40
lines changed

frontend/apps/app/components/SessionDetailPage/SessionDetailPageClient.tsx

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use client'
22

33
import {
4+
HumanMessage,
45
mapStoredMessagesToChatMessages,
56
type StoredMessage,
67
} from '@langchain/core/messages'
@@ -128,11 +129,11 @@ export const SessionDetailPageClient: FC<Props> = ({
128129
(artifact !== null || selectedVersion !== null) && activeTab
129130

130131
const chatMessages = mapStoredMessagesToChatMessages(initialMessages)
131-
const { isStreaming, messages, start, replay, error } = useStream({
132-
initialMessages: chatMessages,
133-
designSessionId,
134-
senderName,
135-
})
132+
const { isStreaming, messages, setMessages, start, replay, error } =
133+
useStream({
134+
initialMessages: chatMessages,
135+
designSessionId,
136+
})
136137

137138
const handleLayoutChange = useCallback((sizes: number[]) => {
138139
setCookieJson(PANEL_LAYOUT_COOKIE_NAME, sizes, {
@@ -146,6 +147,31 @@ export const SessionDetailPageClient: FC<Props> = ({
146147
// Track if initial workflow has been triggered to prevent multiple executions
147148
const hasTriggeredInitialWorkflow = useRef(false)
148149

150+
const handleSendMessage = useCallback(
151+
async (content: string) => {
152+
const tempId = `optimistic-${crypto.randomUUID()}`
153+
const optimisticMessage = new HumanMessage({
154+
content,
155+
id: tempId,
156+
additional_kwargs: {
157+
userName: senderName,
158+
},
159+
})
160+
setMessages((prev) => [...prev, optimisticMessage])
161+
162+
const result = await start({
163+
userInput: content,
164+
designSessionId,
165+
isDeepModelingEnabled,
166+
})
167+
168+
if (result.isErr()) {
169+
setMessages((prev) => prev.filter((msg) => msg.id !== tempId))
170+
}
171+
},
172+
[setMessages, start, senderName, designSessionId, isDeepModelingEnabled],
173+
)
174+
149175
// Auto-trigger workflow on page load if there's an unanswered user message
150176
useEffect(() => {
151177
const triggerInitialWorkflow = async () => {
@@ -201,13 +227,7 @@ export const SessionDetailPageClient: FC<Props> = ({
201227
schemaData={displayedSchema}
202228
messages={messages}
203229
isWorkflowRunning={isStreaming}
204-
onSendMessage={(content: string) =>
205-
start({
206-
userInput: content,
207-
designSessionId,
208-
isDeepModelingEnabled,
209-
})
210-
}
230+
onSendMessage={handleSendMessage}
211231
onNavigate={setActiveTab}
212232
error={combinedError}
213233
/>

frontend/apps/app/components/SessionDetailPage/hooks/useStream/useStream.ts

Lines changed: 3 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import {
44
type BaseMessage,
55
coerceMessageLikeToMessage,
6-
HumanMessage,
76
} from '@langchain/core/messages'
87
import { MessageTupleManager, SSE_EVENTS } from '@liam-hq/agent/client'
98
import { err, ok, type Result } from 'neverthrow'
@@ -63,18 +62,11 @@ const extractStreamErrorMessage = (rawData: unknown): string => {
6362
type Props = {
6463
designSessionId: string
6564
initialMessages: BaseMessage[]
66-
senderName: string
6765
}
68-
export const useStream = ({
69-
designSessionId,
70-
initialMessages,
71-
senderName,
72-
}: Props) => {
66+
export const useStream = ({ designSessionId, initialMessages }: Props) => {
7367
const messageManagerRef = useRef(new MessageTupleManager())
7468
const storedMessage = useSessionStorageOnce(designSessionId)
7569

76-
const isFirstMessage = useRef(true)
77-
7870
const processedInitialMessages = useMemo(() => {
7971
if (storedMessage) {
8072
return [storedMessage, ...initialMessages]
@@ -268,30 +260,12 @@ export const useStream = ({
268260
abortRef.current?.abort()
269261
retryCountRef.current = 0
270262

271-
let tempId: string | undefined
272-
if (!isFirstMessage.current) {
273-
tempId = `optimistic-${crypto.randomUUID()}`
274-
const optimisticMessage = new HumanMessage({
275-
content: params.userInput,
276-
id: tempId,
277-
additional_kwargs: {
278-
userName: senderName,
279-
},
280-
})
281-
setMessages((prev) => [...prev, optimisticMessage])
282-
} else {
283-
isFirstMessage.current = false
284-
}
285-
286263
// Set workflow in progress flag
287264
setWorkflowInProgress(params.designSessionId)
288265

289266
const result = await runStreamAttempt('/api/chat/stream', params)
290267

291268
if (result.isErr()) {
292-
if (tempId) {
293-
setMessages((prev) => prev.filter((msg) => msg.id !== tempId))
294-
}
295269
return err(result.error)
296270
}
297271

@@ -304,11 +278,12 @@ export const useStream = ({
304278
isDeepModelingEnabled: params.isDeepModelingEnabled,
305279
})
306280
},
307-
[replay, runStreamAttempt, senderName],
281+
[replay, runStreamAttempt],
308282
)
309283

310284
return {
311285
messages,
286+
setMessages,
312287
isStreaming,
313288
error,
314289
start,

0 commit comments

Comments
 (0)