@tangle-network/sandbox-ui 0.91.1 → 0.91.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -79,7 +79,12 @@ function useSessionStream({
79
79
  const reconnectTimerRef = useRef(null);
80
80
  const streamingMsgIdRef = useRef(null);
81
81
  const insertionCounterRef = useRef(0);
82
+ const echoCounterRef = useRef(0);
83
+ const pendingEchoesRef = useRef(/* @__PURE__ */ new Map());
82
84
  const handleSSEEventRef = useRef(null);
85
+ useEffect(() => {
86
+ pendingEchoesRef.current.clear();
87
+ }, [sessionId]);
83
88
  const refetch = useCallback(async () => {
84
89
  if (!token || !sessionId || !apiUrl) return;
85
90
  try {
@@ -93,8 +98,12 @@ function useSessionStream({
93
98
  newMessages.push(message);
94
99
  newPartMap[message.id] = parts;
95
100
  }
96
- setMessages(newMessages);
97
- setPartMap(newPartMap);
101
+ const echoes = [...pendingEchoesRef.current.values()];
102
+ setMessages([...newMessages, ...echoes.map((echo) => echo.message)]);
103
+ setPartMap({
104
+ ...newPartMap,
105
+ ...Object.fromEntries(echoes.map((echo) => [echo.message.id, echo.parts]))
106
+ });
98
107
  streamingMsgIdRef.current = null;
99
108
  } catch (err) {
100
109
  const msg = err instanceof Error ? err.message : "Failed to fetch messages";
@@ -240,18 +249,34 @@ function useSessionStream({
240
249
  } else if (type === "session.idle") {
241
250
  setIsStreaming(false);
242
251
  streamingMsgIdRef.current = null;
252
+ pendingEchoesRef.current.clear();
243
253
  refetch();
244
254
  } else if (type === "session.error") {
245
255
  setIsStreaming(false);
246
256
  streamingMsgIdRef.current = null;
247
257
  const errorMsg = props.error ?? props.message ?? "Agent error";
248
258
  setError(errorMsg);
259
+ pendingEchoesRef.current.clear();
249
260
  refetch();
250
261
  }
251
262
  }, [refetch]);
252
263
  handleSSEEventRef.current = handleSSEEvent;
253
264
  const send = useCallback(async (text, options) => {
254
265
  if (!token || !sessionId || !apiUrl) return;
266
+ const echoId = `local-echo-${echoCounterRef.current++}`;
267
+ const echo = {
268
+ message: {
269
+ id: echoId,
270
+ role: "user",
271
+ time: { created: Date.now() },
272
+ _insertionIndex: insertionCounterRef.current++
273
+ },
274
+ parts: [{ type: "text", text }]
275
+ };
276
+ pendingEchoesRef.current.set(echoId, echo);
277
+ setMessages((prev) => [...prev, echo.message]);
278
+ setPartMap((prev) => ({ ...prev, [echoId]: echo.parts }));
279
+ setIsStreaming(true);
255
280
  try {
256
281
  const url = `${apiUrl}/session/sessions/${encodeURIComponent(sessionId)}/messages`;
257
282
  await fetchJson(url, token, {
@@ -266,8 +291,18 @@ function useSessionStream({
266
291
  ...options?.reasoningEffort ? { reasoningEffort: options.reasoningEffort } : {}
267
292
  })
268
293
  });
269
- setIsStreaming(true);
270
294
  } catch (err) {
295
+ pendingEchoesRef.current.delete(echoId);
296
+ setMessages((prev) => prev.filter((message) => message.id !== echoId));
297
+ setPartMap((prev) => {
298
+ if (!(echoId in prev)) return prev;
299
+ const next = { ...prev };
300
+ delete next[echoId];
301
+ return next;
302
+ });
303
+ if (pendingEchoesRef.current.size === 0 && !streamingMsgIdRef.current) {
304
+ setIsStreaming(false);
305
+ }
271
306
  const msg = err instanceof Error ? err.message : "Failed to send message";
272
307
  setError(msg);
273
308
  }