Skip to content

Commit 6902063

Browse files
Handle optimistic message failures
1 parent 750056d commit 6902063

3 files changed

Lines changed: 87 additions & 14 deletions

File tree

src/frontend/src/modals/IOModal/playground-modal.tsx

Lines changed: 52 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
//import LangflowLogoColor from "@/assets/LangflowLogocolor.svg?react";
22

33
import { useCallback, useEffect, useRef, useState } from "react";
4+
import { v4 as uuid } from "uuid";
45
import { useShallow } from "zustand/react/shallow";
6+
import { markOptimisticMessageFailed } from "@/utils/optimisticMessageUtils";
57
import ThemeButtons from "@/components/core/appHeaderComponent/components/ThemeButtons";
68
import { useGetMessagesQuery } from "@/controllers/API/queries/messages";
79
import { useDeleteSession } from "@/controllers/API/queries/messages/use-delete-sessions";
@@ -43,7 +45,6 @@ export default function IOModal({
4345
const outputs = useFlowStore((state) => state.outputs);
4446
const nodes = useFlowStore((state) => state.nodes);
4547
const buildFlow = useFlowStore((state) => state.buildFlow);
46-
const setIsBuilding = useFlowStore((state) => state.setIsBuilding);
4748
const isBuilding = useFlowStore((state) => state.isBuilding);
4849
const newChatOnPlayground = useFlowStore(
4950
(state) => state.newChatOnPlayground,
@@ -75,6 +76,7 @@ export default function IOModal({
7576
const setErrorData = useAlertStore((state) => state.setErrorData);
7677
const setSuccessData = useAlertStore((state) => state.setSuccessData);
7778
const deleteSession = useMessagesStore((state) => state.deleteSession);
79+
const addMessage = useMessagesStore((state) => state.addMessage);
7880
const currentFlowId = useGetFlowId();
7981
const [sidebarOpen, setSidebarOpen] = useState(true);
8082

@@ -209,22 +211,58 @@ export default function IOModal({
209211
files?: string[];
210212
}): Promise<void> => {
211213
if (isBuilding) return;
214+
const optimisticMessage =
215+
playgroundPage && (chatValue || (files && files.length > 0))
216+
? {
217+
id: `optimistic-${uuid()}`,
218+
text: chatValue,
219+
sender: "User",
220+
sender_name: "User",
221+
session_id: sessionId,
222+
timestamp: new Date().toISOString(),
223+
files: files ?? [],
224+
edit: false,
225+
background_color: "",
226+
text_color: "",
227+
flow_id: currentFlowId,
228+
properties: { optimistic: true },
229+
}
230+
: null;
231+
232+
if (optimisticMessage) {
233+
addMessage(optimisticMessage);
234+
}
212235
setChatValue("");
213-
for (let i = 0; i < repeat; i++) {
214-
await buildFlow({
215-
input_value: chatValue,
216-
startNodeId: chatInput?.id,
217-
files: files,
218-
silent: true,
219-
session: sessionId,
220-
eventDelivery: eventDeliveryConfig,
221-
}).catch((err) => {
222-
console.error(err);
223-
throw err;
224-
});
236+
try {
237+
for (let i = 0; i < repeat; i++) {
238+
await buildFlow({
239+
input_value: chatValue,
240+
startNodeId: chatInput?.id,
241+
files: files,
242+
silent: true,
243+
session: sessionId,
244+
eventDelivery: eventDeliveryConfig,
245+
});
246+
}
247+
} catch (error) {
248+
if (optimisticMessage) {
249+
addMessage(markOptimisticMessageFailed(optimisticMessage));
250+
}
251+
throw error;
225252
}
226253
},
227-
[isBuilding, setIsBuilding, chatValue, chatInput?.id, sessionId, buildFlow],
254+
[
255+
isBuilding,
256+
addMessage,
257+
chatValue,
258+
chatInput?.id,
259+
sessionId,
260+
buildFlow,
261+
currentFlowId,
262+
eventDeliveryConfig,
263+
playgroundPage,
264+
setChatValue,
265+
],
228266
);
229267

230268
useEffect(() => {

src/frontend/src/stores/messagesStore.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,23 @@ export const useMessagesStore = create<MessagesStoreType>((set, get) => ({
3131
}
3232
return;
3333
}
34+
if (message.sender === "User") {
35+
const messages = get().messages;
36+
for (let i = messages.length - 1; i >= 0; i--) {
37+
const candidate = messages[i];
38+
if (
39+
candidate.sender === "User" &&
40+
(candidate.properties?.optimistic || candidate.properties?.failed) &&
41+
candidate.session_id === message.session_id &&
42+
candidate.text === message.text
43+
) {
44+
const updatedMessages = [...messages];
45+
updatedMessages[i] = message;
46+
set(() => ({ messages: updatedMessages }));
47+
return;
48+
}
49+
}
50+
}
3451
if (message.sender === "Machine") {
3552
set(() => ({ displayLoadingMessage: false }));
3653
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { MessageType } from "@/types/messages";
2+
3+
/**
4+
* Marks an optimistic user message as failed without removing it from the UI.
5+
* This preserves user intent while allowing global error handling.
6+
*/
7+
export function markOptimisticMessageFailed(
8+
optimisticMessage: MessageType
9+
): MessageType {
10+
return {
11+
...optimisticMessage,
12+
properties: {
13+
...optimisticMessage.properties,
14+
optimistic: false,
15+
failed: true,
16+
},
17+
};
18+
}

0 commit comments

Comments
 (0)