@red-hat-developer-hub/backstage-plugin-lightspeed 2.5.0 → 2.6.0

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.
Files changed (39) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +6 -2
  3. package/dist/alpha.d.ts +5 -0
  4. package/dist/components/LightSpeedChat.esm.js +92 -21
  5. package/dist/components/LightSpeedChat.esm.js.map +1 -1
  6. package/dist/components/LightspeedDrawerContext.esm.js.map +1 -1
  7. package/dist/components/notebooks/AddDocumentModal.esm.js +122 -23
  8. package/dist/components/notebooks/AddDocumentModal.esm.js.map +1 -1
  9. package/dist/components/notebooks/DocumentSidebar.esm.js +19 -1
  10. package/dist/components/notebooks/DocumentSidebar.esm.js.map +1 -1
  11. package/dist/components/notebooks/FileListItem.esm.js +94 -0
  12. package/dist/components/notebooks/FileListItem.esm.js.map +1 -0
  13. package/dist/components/notebooks/NotebookView.esm.js +46 -26
  14. package/dist/components/notebooks/NotebookView.esm.js.map +1 -1
  15. package/dist/components/notebooks/OverwriteConfirmModal.esm.js +2 -1
  16. package/dist/components/notebooks/OverwriteConfirmModal.esm.js.map +1 -1
  17. package/dist/components/notebooks/SidebarCollapseIcon.esm.js +5 -2
  18. package/dist/components/notebooks/SidebarCollapseIcon.esm.js.map +1 -1
  19. package/dist/hooks/toolCallsCacheStore.esm.js +61 -0
  20. package/dist/hooks/toolCallsCacheStore.esm.js.map +1 -0
  21. package/dist/hooks/useConversationMessages.esm.js +359 -325
  22. package/dist/hooks/useConversationMessages.esm.js.map +1 -1
  23. package/dist/hooks/useDeleteConversation.esm.js +2 -0
  24. package/dist/hooks/useDeleteConversation.esm.js.map +1 -1
  25. package/dist/hooks/useLightspeedProviderState.esm.js +35 -8
  26. package/dist/hooks/useLightspeedProviderState.esm.js.map +1 -1
  27. package/dist/translations/de.esm.js +5 -0
  28. package/dist/translations/de.esm.js.map +1 -1
  29. package/dist/translations/es.esm.js +5 -0
  30. package/dist/translations/es.esm.js.map +1 -1
  31. package/dist/translations/fr.esm.js +5 -0
  32. package/dist/translations/fr.esm.js.map +1 -1
  33. package/dist/translations/it.esm.js +5 -0
  34. package/dist/translations/it.esm.js.map +1 -1
  35. package/dist/translations/ja.esm.js +5 -0
  36. package/dist/translations/ja.esm.js.map +1 -1
  37. package/dist/translations/ref.esm.js +5 -0
  38. package/dist/translations/ref.esm.js.map +1 -1
  39. package/package.json +2 -2
@@ -6,6 +6,7 @@ import { TEMP_CONVERSATION_ID } from '../const.esm.js';
6
6
  import botAvatar from '../images/bot-avatar.svg';
7
7
  import userAvatar from '../images/user-avatar.svg';
8
8
  import { getConversationsData, createUserMessage, createBotMessage, transformDocumentsToSources, getTimestamp } from '../utils/lightspeed-chatbox-utils.esm.js';
9
+ import { getSharedToolCallsCache, setSharedToolCallsCache, migrateSharedToolCallsCacheSessionPrefixToConversation, clearSharedToolCallsCacheSessionPrefix } from './toolCallsCacheStore.esm.js';
9
10
  import { useCreateConversationMessage } from './useCreateCoversationMessage.esm.js';
10
11
 
11
12
  const toolCallIdKey = (id) => {
@@ -18,6 +19,11 @@ const isMcpStyleToolCallPayload = (data) => {
18
19
  const isLegacyToolResultToken = (token) => {
19
20
  return !!token && typeof token === "object" && !Array.isArray(token) && typeof token.tool_name === "string" && token.tool_name.length > 0;
20
21
  };
22
+ let tempToolCallsCachePrefixFallbackSeq = 0;
23
+ function createTempToolCallsCacheSessionPrefix() {
24
+ const suffix = globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${++tempToolCallsCachePrefixFallbackSeq}`;
25
+ return `lightspeed-temp:${suffix}`;
26
+ }
21
27
  const legacyToolResultToString = (response) => {
22
28
  if (!response) return "";
23
29
  if (typeof response === "string") return response;
@@ -49,13 +55,17 @@ const useConversationMessages = (conversationId, userName, selectedModel, select
49
55
  const streamingConversations = useRef({
50
56
  [currentConversation]: []
51
57
  });
58
+ const isTempStreamInProgressRef = useRef(false);
59
+ const [streamingConversationId, setStreamingConversationId] = useState(null);
52
60
  const pendingToolCalls = useRef({});
53
- const toolCallsCache = useRef({});
54
61
  useEffect(() => {
55
62
  if (currentConversation !== conversationId) {
56
63
  setCurrentConversation(conversationId);
57
64
  setConversations((prev) => {
58
65
  if (conversationId === TEMP_CONVERSATION_ID) {
66
+ if (isTempStreamInProgressRef.current && (prev[TEMP_CONVERSATION_ID]?.length ?? 0) > 0) {
67
+ return prev;
68
+ }
59
69
  return { ...prev, [TEMP_CONVERSATION_ID]: [] };
60
70
  }
61
71
  if (prev[conversationId]) return prev;
@@ -95,7 +105,7 @@ const useConversationMessages = (conversationId, userName, selectedModel, select
95
105
  )
96
106
  });
97
107
  const cacheKey = `${currentConversation}-${i}`;
98
- const cachedToolCalls = toolCallsCache.current[cacheKey];
108
+ const cachedToolCalls = getSharedToolCallsCache(cacheKey);
99
109
  if (cachedToolCalls && cachedToolCalls.length > 0) {
100
110
  botMsg.toolCalls = cachedToolCalls;
101
111
  }
@@ -106,7 +116,10 @@ const useConversationMessages = (conversationId, userName, selectedModel, select
106
116
  ...streamingConversations.current[currentConversation]
107
117
  );
108
118
  }
109
- setConversations(_conversations);
119
+ setConversations((prev) => ({
120
+ ...prev,
121
+ ..._conversations
122
+ }));
110
123
  }
111
124
  }, [
112
125
  conversationsData,
@@ -118,116 +131,254 @@ const useConversationMessages = (conversationId, userName, selectedModel, select
118
131
  ]);
119
132
  const handleInputPrompt = useCallback(
120
133
  async (prompt, attachments = []) => {
121
- let newConversationId = "";
122
- let requestId = "";
123
- const conversationTuple = [
124
- createUserMessage({
125
- avatar,
126
- name: userName,
127
- content: prompt,
128
- timestamp: getTimestamp(Date.now()) ?? ""
129
- }),
130
- createBotMessage({
131
- avatar: botAvatar,
132
- isLoading: true,
133
- name: selectedModel,
134
- content: "",
135
- timestamp: ""
136
- })
137
- ];
138
- streamingConversations.current = {
139
- ...streamingConversations.current,
140
- [currentConversation]: conversationTuple
141
- };
142
- setConversations((prevConv) => {
143
- return {
144
- ...prevConv,
145
- [currentConversation]: [
146
- ...prevConv?.[currentConversation] ?? [],
147
- ...conversationTuple
148
- ]
149
- };
150
- });
151
- setTimeout(() => {
152
- scrollToBottomRef.current?.scrollToBottom();
153
- }, 0);
154
- const finalMessages = [];
155
- let buffer = "";
134
+ const streamStartedOnTemp = currentConversation === TEMP_CONVERSATION_ID;
135
+ if (streamStartedOnTemp) {
136
+ isTempStreamInProgressRef.current = true;
137
+ }
156
138
  try {
157
- const reader = await createMessage({
158
- prompt,
159
- selectedModel,
160
- selectedProvider,
161
- currentConversation,
162
- attachments
139
+ let newConversationId = "";
140
+ let requestId = "";
141
+ setStreamingConversationId(currentConversation);
142
+ const toolCallsCacheKeyPrefix = currentConversation === TEMP_CONVERSATION_ID ? createTempToolCallsCacheSessionPrefix() : currentConversation;
143
+ const conversationTuple = [
144
+ createUserMessage({
145
+ avatar,
146
+ name: userName,
147
+ content: prompt,
148
+ timestamp: getTimestamp(Date.now()) ?? ""
149
+ }),
150
+ createBotMessage({
151
+ avatar: botAvatar,
152
+ isLoading: true,
153
+ name: selectedModel,
154
+ content: "",
155
+ timestamp: ""
156
+ })
157
+ ];
158
+ streamingConversations.current = {
159
+ ...streamingConversations.current,
160
+ [currentConversation]: conversationTuple
161
+ };
162
+ setConversations((prevConv) => {
163
+ return {
164
+ ...prevConv,
165
+ [currentConversation]: [
166
+ ...prevConv?.[currentConversation] ?? [],
167
+ ...conversationTuple
168
+ ]
169
+ };
163
170
  });
164
- const decoder = new TextDecoder("utf-8");
165
- let streamEnded = false;
166
- while (!streamEnded) {
167
- const { value, done } = await reader.read();
168
- if (done) {
169
- streamEnded = true;
170
- break;
171
- }
172
- buffer += decoder.decode(value, { stream: true });
173
- const parts = buffer.split("\n\n");
174
- buffer = parts.pop();
175
- for (const part of parts) {
176
- const lines = part.split("\n").filter((line) => line.startsWith("data:"));
177
- const jsonString = lines.map((line) => line.trim().slice(5).trim()).join("");
178
- try {
179
- const { event, data } = JSON.parse(jsonString);
180
- if (event === "start") {
181
- requestId = data?.request_id;
182
- onRequestIdReady?.(requestId);
183
- if (currentConversation === TEMP_CONVERSATION_ID) {
184
- newConversationId = data?.conversation_id;
171
+ setTimeout(() => {
172
+ scrollToBottomRef.current?.scrollToBottom();
173
+ }, 0);
174
+ const finalMessages = [];
175
+ let buffer = "";
176
+ try {
177
+ const reader = await createMessage({
178
+ prompt,
179
+ selectedModel,
180
+ selectedProvider,
181
+ currentConversation,
182
+ attachments
183
+ });
184
+ const decoder = new TextDecoder("utf-8");
185
+ let streamEnded = false;
186
+ while (!streamEnded) {
187
+ const { value, done } = await reader.read();
188
+ if (done) {
189
+ streamEnded = true;
190
+ break;
191
+ }
192
+ buffer += decoder.decode(value, { stream: true });
193
+ const parts = buffer.split("\n\n");
194
+ buffer = parts.pop();
195
+ for (const part of parts) {
196
+ const lines = part.split("\n").filter((line) => line.startsWith("data:"));
197
+ const jsonString = lines.map((line) => line.trim().slice(5).trim()).join("");
198
+ try {
199
+ const { event, data } = JSON.parse(jsonString);
200
+ if (event === "start") {
201
+ requestId = data?.request_id;
202
+ onRequestIdReady?.(requestId);
203
+ if (currentConversation === TEMP_CONVERSATION_ID) {
204
+ newConversationId = data?.conversation_id;
205
+ }
185
206
  }
186
- }
187
- if (event === "tool_call") {
188
- const toolCallData = data?.token;
189
- const legacyObjectCall = typeof toolCallData === "object" && toolCallData !== null && !Array.isArray(toolCallData) && toolCallData.tool_name;
190
- const mcpStyle = isMcpStyleToolCallPayload(data);
191
- const rawArgs = data?.args ?? data?.arguments;
192
- const mcpArgs = rawArgs && typeof rawArgs === "object" && !Array.isArray(rawArgs) ? rawArgs : {};
193
- let toolCall;
194
- if (legacyObjectCall && data.id !== null) {
195
- toolCall = {
196
- id: data.id,
197
- toolName: toolCallData.tool_name,
198
- arguments: toolCallData.arguments || {},
199
- startTime: Date.now(),
200
- isLoading: true
201
- };
202
- } else if (mcpStyle) {
203
- toolCall = {
204
- id: data.id,
205
- toolName: data.name.trim(),
206
- description: typeof data.type === "string" && data.type !== data.name ? data.type : void 0,
207
- arguments: mcpArgs,
208
- startTime: Date.now(),
209
- isLoading: true
210
- };
207
+ if (event === "tool_call") {
208
+ const toolCallData = data?.token;
209
+ const legacyObjectCall = typeof toolCallData === "object" && toolCallData !== null && !Array.isArray(toolCallData) && toolCallData.tool_name;
210
+ const mcpStyle = isMcpStyleToolCallPayload(data);
211
+ const rawArgs = data?.args ?? data?.arguments;
212
+ const mcpArgs = rawArgs && typeof rawArgs === "object" && !Array.isArray(rawArgs) ? rawArgs : {};
213
+ let toolCall;
214
+ if (legacyObjectCall && data.id !== null) {
215
+ toolCall = {
216
+ id: data.id,
217
+ toolName: toolCallData.tool_name,
218
+ arguments: toolCallData.arguments || {},
219
+ startTime: Date.now(),
220
+ isLoading: true
221
+ };
222
+ } else if (mcpStyle) {
223
+ toolCall = {
224
+ id: data.id,
225
+ toolName: data.name.trim(),
226
+ description: typeof data.type === "string" && data.type !== data.name ? data.type : void 0,
227
+ arguments: mcpArgs,
228
+ startTime: Date.now(),
229
+ isLoading: true
230
+ };
231
+ }
232
+ if (toolCall && data.id !== null) {
233
+ const newToolCall = toolCall;
234
+ pendingToolCalls.current[toolCallIdKey(data.id)] = newToolCall;
235
+ setConversations((prevConversations) => {
236
+ const conversation = prevConversations[currentConversation] ?? [];
237
+ const lastMessageIndex = conversation.length - 1;
238
+ if (lastMessageIndex < 0) return prevConversations;
239
+ const lastMessage = { ...conversation[lastMessageIndex] };
240
+ const existingToolCalls = normalizeToolCalls(
241
+ lastMessage.toolCalls
242
+ );
243
+ const nextToolCalls = [
244
+ ...existingToolCalls,
245
+ newToolCall
246
+ ];
247
+ lastMessage.toolCalls = nextToolCalls;
248
+ const messageIndex = Math.floor(lastMessageIndex / 2);
249
+ const cacheKey = `${toolCallsCacheKeyPrefix}-${messageIndex}`;
250
+ setSharedToolCallsCache(cacheKey, nextToolCalls);
251
+ const updatedConversation = [
252
+ ...conversation.slice(0, lastMessageIndex),
253
+ lastMessage
254
+ ];
255
+ return {
256
+ ...prevConversations,
257
+ [currentConversation]: updatedConversation
258
+ };
259
+ });
260
+ const [humanMessage, aiMessage] = streamingConversations.current[currentConversation] || [];
261
+ if (aiMessage) {
262
+ const existingStreamingToolCalls = normalizeToolCalls(
263
+ aiMessage.toolCalls
264
+ );
265
+ streamingConversations.current[currentConversation] = [
266
+ humanMessage,
267
+ {
268
+ ...aiMessage,
269
+ toolCalls: [
270
+ ...existingStreamingToolCalls,
271
+ newToolCall
272
+ ]
273
+ }
274
+ ];
275
+ }
276
+ }
277
+ }
278
+ if (event === "tool_result") {
279
+ const tokenResult = data?.token;
280
+ const legacyResult = isLegacyToolResultToken(tokenResult);
281
+ const mcpHasContent = data?.id !== null && data.content !== void 0 && !legacyResult;
282
+ let responsePayload;
283
+ let matchToolName;
284
+ let toolIdKey;
285
+ if (legacyResult) {
286
+ responsePayload = legacyToolResultToString(
287
+ tokenResult.response
288
+ );
289
+ matchToolName = tokenResult.tool_name;
290
+ toolIdKey = data?.id !== null ? toolCallIdKey(data.id) : void 0;
291
+ } else if (mcpHasContent) {
292
+ toolIdKey = toolCallIdKey(data.id);
293
+ responsePayload = typeof data.content === "string" ? data.content : JSON.stringify(data.content);
294
+ if (typeof data.status === "string" && data.status !== "success") {
295
+ responsePayload = `[${data.status}] ${responsePayload}`;
296
+ }
297
+ }
298
+ if (responsePayload !== void 0 && toolIdKey !== void 0) {
299
+ const pendingCall = pendingToolCalls.current[toolIdKey];
300
+ const endTime = Date.now();
301
+ const executionTime = pendingCall ? (endTime - pendingCall.startTime) / 1e3 : 0;
302
+ setConversations((prevConversations) => {
303
+ const conversation = prevConversations[currentConversation] ?? [];
304
+ const lastMessageIndex = conversation.length - 1;
305
+ if (lastMessageIndex < 0) return prevConversations;
306
+ const lastMessage = { ...conversation[lastMessageIndex] };
307
+ const toolCalls = lastMessage.toolCalls || [];
308
+ const updatedToolCalls = toolCalls.map((tc) => {
309
+ const idMatches = toolCallIdKey(tc.id) === toolIdKey || matchToolName !== void 0 && tc.toolName === matchToolName;
310
+ if (idMatches) {
311
+ return {
312
+ ...tc,
313
+ response: responsePayload,
314
+ endTime,
315
+ executionTime,
316
+ isLoading: false
317
+ };
318
+ }
319
+ return tc;
320
+ });
321
+ lastMessage.toolCalls = updatedToolCalls;
322
+ const messageIndex = Math.floor(lastMessageIndex / 2);
323
+ const cacheKey = `${toolCallsCacheKeyPrefix}-${messageIndex}`;
324
+ setSharedToolCallsCache(cacheKey, updatedToolCalls);
325
+ const updatedConversation = [
326
+ ...conversation.slice(0, lastMessageIndex),
327
+ lastMessage
328
+ ];
329
+ return {
330
+ ...prevConversations,
331
+ [currentConversation]: updatedConversation
332
+ };
333
+ });
334
+ const [humanMessage, aiMessage] = streamingConversations.current[currentConversation] || [];
335
+ if (aiMessage) {
336
+ const toolCalls = aiMessage.toolCalls || [];
337
+ const updatedToolCalls = toolCalls.map((tc) => {
338
+ const idMatches = toolCallIdKey(tc.id) === toolIdKey || matchToolName !== void 0 && tc.toolName === matchToolName;
339
+ if (idMatches) {
340
+ return {
341
+ ...tc,
342
+ response: responsePayload,
343
+ endTime,
344
+ executionTime,
345
+ isLoading: false
346
+ };
347
+ }
348
+ return tc;
349
+ });
350
+ streamingConversations.current[currentConversation] = [
351
+ humanMessage,
352
+ { ...aiMessage, toolCalls: updatedToolCalls }
353
+ ];
354
+ }
355
+ delete pendingToolCalls.current[toolIdKey];
356
+ }
211
357
  }
212
- if (toolCall && data.id !== null) {
213
- const newToolCall = toolCall;
214
- pendingToolCalls.current[toolCallIdKey(data.id)] = newToolCall;
358
+ if (event === "token") {
359
+ const content = data?.token || "";
360
+ finalMessages.push(content);
361
+ const [humanMessage, aiMessage] = streamingConversations.current[currentConversation];
362
+ streamingConversations.current[currentConversation] = [
363
+ humanMessage,
364
+ { ...aiMessage, content: aiMessage.content + content }
365
+ ];
215
366
  setConversations((prevConversations) => {
216
367
  const conversation = prevConversations[currentConversation] ?? [];
217
368
  const lastMessageIndex = conversation.length - 1;
218
- if (lastMessageIndex < 0) return prevConversations;
219
- const lastMessage = { ...conversation[lastMessageIndex] };
220
- const existingToolCalls = normalizeToolCalls(
221
- lastMessage.toolCalls
369
+ const lastMessage = conversation.length === 0 ? createBotMessage({
370
+ content: "",
371
+ timestamp: getTimestamp(Date.now())
372
+ }) : { ...conversation[lastMessageIndex] };
373
+ if ((lastMessage?.content ?? "").trim().length > 0) {
374
+ lastMessage.isLoading = false;
375
+ }
376
+ lastMessage.content += content;
377
+ lastMessage.name = data?.response_metadata?.model || selectedModel;
378
+ lastMessage.timestamp = getTimestamp(
379
+ // TODO: To be fixed in the query response
380
+ data?.response_metadata?.created_at || Date.now()
222
381
  );
223
- const nextToolCalls = [
224
- ...existingToolCalls,
225
- newToolCall
226
- ];
227
- lastMessage.toolCalls = nextToolCalls;
228
- const messageIndex = Math.floor(lastMessageIndex / 2);
229
- const cacheKey = `${currentConversation}-${messageIndex}`;
230
- toolCallsCache.current[cacheKey] = nextToolCalls;
231
382
  const updatedConversation = [
232
383
  ...conversation.slice(0, lastMessageIndex),
233
384
  lastMessage
@@ -237,68 +388,22 @@ const useConversationMessages = (conversationId, userName, selectedModel, select
237
388
  [currentConversation]: updatedConversation
238
389
  };
239
390
  });
240
- const [humanMessage, aiMessage] = streamingConversations.current[currentConversation] || [];
241
- if (aiMessage) {
242
- const existingStreamingToolCalls = normalizeToolCalls(
243
- aiMessage.toolCalls
244
- );
245
- streamingConversations.current[currentConversation] = [
246
- humanMessage,
247
- {
248
- ...aiMessage,
249
- toolCalls: [...existingStreamingToolCalls, newToolCall]
250
- }
251
- ];
252
- }
253
391
  }
254
- }
255
- if (event === "tool_result") {
256
- const tokenResult = data?.token;
257
- const legacyResult = isLegacyToolResultToken(tokenResult);
258
- const mcpHasContent = data?.id !== null && data.content !== void 0 && !legacyResult;
259
- let responsePayload;
260
- let matchToolName;
261
- let toolIdKey;
262
- if (legacyResult) {
263
- responsePayload = legacyToolResultToString(
264
- tokenResult.response
265
- );
266
- matchToolName = tokenResult.tool_name;
267
- toolIdKey = data?.id !== null ? toolCallIdKey(data.id) : void 0;
268
- } else if (mcpHasContent) {
269
- toolIdKey = toolCallIdKey(data.id);
270
- responsePayload = typeof data.content === "string" ? data.content : JSON.stringify(data.content);
271
- if (typeof data.status === "string" && data.status !== "success") {
272
- responsePayload = `[${data.status}] ${responsePayload}`;
392
+ if (event === "interrupted") {
393
+ if (currentConversation === TEMP_CONVERSATION_ID && data?.conversation_id) {
394
+ newConversationId = data.conversation_id;
273
395
  }
274
- }
275
- if (responsePayload !== void 0 && toolIdKey !== void 0) {
276
- const pendingCall = pendingToolCalls.current[toolIdKey];
277
- const endTime = Date.now();
278
- const executionTime = pendingCall ? (endTime - pendingCall.startTime) / 1e3 : 0;
279
396
  setConversations((prevConversations) => {
280
397
  const conversation = prevConversations[currentConversation] ?? [];
281
398
  const lastMessageIndex = conversation.length - 1;
282
- if (lastMessageIndex < 0) return prevConversations;
283
- const lastMessage = { ...conversation[lastMessageIndex] };
284
- const toolCalls = lastMessage.toolCalls || [];
285
- const updatedToolCalls = toolCalls.map((tc) => {
286
- const idMatches = toolCallIdKey(tc.id) === toolIdKey || matchToolName !== void 0 && tc.toolName === matchToolName;
287
- if (idMatches) {
288
- return {
289
- ...tc,
290
- response: responsePayload,
291
- endTime,
292
- executionTime,
293
- isLoading: false
294
- };
295
- }
296
- return tc;
297
- });
298
- lastMessage.toolCalls = updatedToolCalls;
299
- const messageIndex = Math.floor(lastMessageIndex / 2);
300
- const cacheKey = `${currentConversation}-${messageIndex}`;
301
- toolCallsCache.current[cacheKey] = updatedToolCalls;
399
+ const lastMessage = conversation.length === 0 ? createBotMessage({
400
+ content: "",
401
+ isLoading: false,
402
+ timestamp: getTimestamp(Date.now())
403
+ }) : {
404
+ ...conversation[lastMessageIndex],
405
+ isLoading: false
406
+ };
302
407
  const updatedConversation = [
303
408
  ...conversation.slice(0, lastMessageIndex),
304
409
  lastMessage
@@ -308,177 +413,105 @@ const useConversationMessages = (conversationId, userName, selectedModel, select
308
413
  [currentConversation]: updatedConversation
309
414
  };
310
415
  });
311
- const [humanMessage, aiMessage] = streamingConversations.current[currentConversation] || [];
312
- if (aiMessage) {
313
- const toolCalls = aiMessage.toolCalls || [];
314
- const updatedToolCalls = toolCalls.map((tc) => {
315
- const idMatches = toolCallIdKey(tc.id) === toolIdKey || matchToolName !== void 0 && tc.toolName === matchToolName;
316
- if (idMatches) {
317
- return {
318
- ...tc,
319
- response: responsePayload,
320
- endTime,
321
- executionTime,
322
- isLoading: false
323
- };
324
- }
325
- return tc;
326
- });
327
- streamingConversations.current[currentConversation] = [
328
- humanMessage,
329
- { ...aiMessage, toolCalls: updatedToolCalls }
416
+ streamEnded = true;
417
+ break;
418
+ }
419
+ if (event === "end") {
420
+ const documents = data?.referenced_documents || [];
421
+ setConversations((prevConversations) => {
422
+ const conversation = prevConversations[currentConversation] ?? [];
423
+ const lastMessageIndex = conversation.length - 1;
424
+ const lastMessage = conversation.length === 0 ? createBotMessage({
425
+ content: "",
426
+ isLoading: false,
427
+ timestamp: getTimestamp(Date.now())
428
+ }) : {
429
+ ...conversation[lastMessageIndex],
430
+ isLoading: false
431
+ };
432
+ if (documents.length) {
433
+ lastMessage.sources = {
434
+ sources: documents.map((doc) => ({
435
+ title: doc.doc_title,
436
+ link: doc.doc_url,
437
+ body: doc.doc_description
438
+ }))
439
+ };
440
+ }
441
+ const updatedConversation = [
442
+ ...conversation.slice(0, lastMessageIndex),
443
+ lastMessage
330
444
  ];
331
- }
332
- delete pendingToolCalls.current[toolIdKey];
445
+ return {
446
+ ...prevConversations,
447
+ [currentConversation]: updatedConversation
448
+ };
449
+ });
333
450
  }
334
- }
335
- if (event === "token") {
336
- const content = data?.token || "";
337
- finalMessages.push(content);
338
- const [humanMessage, aiMessage] = streamingConversations.current[currentConversation];
339
- streamingConversations.current[currentConversation] = [
340
- humanMessage,
341
- { ...aiMessage, content: aiMessage.content + content }
342
- ];
343
- setConversations((prevConversations) => {
344
- const conversation = prevConversations[currentConversation] ?? [];
345
- const lastMessageIndex = conversation.length - 1;
346
- const lastMessage = conversation.length === 0 ? createBotMessage({
347
- content: "",
348
- timestamp: getTimestamp(Date.now())
349
- }) : { ...conversation[lastMessageIndex] };
350
- if ((lastMessage?.content ?? "").trim().length > 0) {
351
- lastMessage.isLoading = false;
352
- }
353
- lastMessage.content += content;
354
- lastMessage.name = data?.response_metadata?.model || selectedModel;
355
- lastMessage.timestamp = getTimestamp(
356
- // TODO: To be fixed in the query response
357
- data?.response_metadata?.created_at || Date.now()
358
- );
359
- const updatedConversation = [
360
- ...conversation.slice(0, lastMessageIndex),
361
- lastMessage
362
- ];
363
- return {
364
- ...prevConversations,
365
- [currentConversation]: updatedConversation
366
- };
367
- });
368
- }
369
- if (event === "interrupted") {
370
- if (currentConversation === TEMP_CONVERSATION_ID && data?.conversation_id) {
371
- newConversationId = data.conversation_id;
451
+ } catch (error) {
452
+ console.warn("Error parsing JSON:", error);
453
+ if (typeof onComplete === "function") {
454
+ onComplete("Invalid JSON received");
372
455
  }
373
- setConversations((prevConversations) => {
374
- const conversation = prevConversations[currentConversation] ?? [];
375
- const lastMessageIndex = conversation.length - 1;
376
- const lastMessage = conversation.length === 0 ? createBotMessage({
377
- content: "",
378
- isLoading: false,
379
- timestamp: getTimestamp(Date.now())
380
- }) : { ...conversation[lastMessageIndex], isLoading: false };
381
- const updatedConversation = [
382
- ...conversation.slice(0, lastMessageIndex),
383
- lastMessage
384
- ];
385
- return {
386
- ...prevConversations,
387
- [currentConversation]: updatedConversation
388
- };
389
- });
390
- streamEnded = true;
391
- break;
392
- }
393
- if (event === "end") {
394
- const documents = data?.referenced_documents || [];
395
- setConversations((prevConversations) => {
396
- const conversation = prevConversations[currentConversation] ?? [];
397
- const lastMessageIndex = conversation.length - 1;
398
- const lastMessage = conversation.length === 0 ? createBotMessage({
399
- content: "",
400
- isLoading: false,
401
- timestamp: getTimestamp(Date.now())
402
- }) : { ...conversation[lastMessageIndex], isLoading: false };
403
- if (documents.length) {
404
- lastMessage.sources = {
405
- sources: documents.map((doc) => ({
406
- title: doc.doc_title,
407
- link: doc.doc_url,
408
- body: doc.doc_description
409
- }))
410
- };
411
- }
412
- const updatedConversation = [
413
- ...conversation.slice(0, lastMessageIndex),
414
- lastMessage
415
- ];
416
- return {
417
- ...prevConversations,
418
- [currentConversation]: updatedConversation
419
- };
420
- });
421
- }
422
- } catch (error) {
423
- console.warn("Error parsing JSON:", error);
424
- if (typeof onComplete === "function") {
425
- onComplete("Invalid JSON received");
426
456
  }
427
457
  }
458
+ if (streamEnded) break;
428
459
  }
429
- if (streamEnded) break;
460
+ } catch (e) {
461
+ setConversations((prevConversations) => {
462
+ const conversation = prevConversations[currentConversation] ?? [];
463
+ const lastMessageIndex = conversation.length - 1;
464
+ const lastMessage = conversation.length === 0 ? createBotMessage({
465
+ content: "",
466
+ timestamp: getTimestamp(Date.now())
467
+ }) : { ...conversation[lastMessageIndex] };
468
+ lastMessage.isLoading = false;
469
+ lastMessage.content += e;
470
+ lastMessage.error = {
471
+ title: e.message
472
+ };
473
+ lastMessage.timestamp = getTimestamp(Date.now());
474
+ const updatedConversation = [
475
+ ...conversation.slice(0, lastMessageIndex),
476
+ lastMessage
477
+ ];
478
+ finalMessages.push(`${e}`);
479
+ return {
480
+ ...prevConversations,
481
+ [newConversationId.length > 0 ? newConversationId : currentConversation]: updatedConversation
482
+ };
483
+ });
430
484
  }
431
- } catch (e) {
432
- setConversations((prevConversations) => {
433
- const conversation = prevConversations[currentConversation] ?? [];
434
- const lastMessageIndex = conversation.length - 1;
435
- const lastMessage = conversation.length === 0 ? createBotMessage({
436
- content: "",
437
- timestamp: getTimestamp(Date.now())
438
- }) : { ...conversation[lastMessageIndex] };
439
- lastMessage.isLoading = false;
440
- lastMessage.content += e;
441
- lastMessage.error = {
442
- title: e.message
443
- };
444
- lastMessage.timestamp = getTimestamp(Date.now());
445
- const updatedConversation = [
446
- ...conversation.slice(0, lastMessageIndex),
447
- lastMessage
448
- ];
449
- finalMessages.push(`${e}`);
450
- return {
451
- ...prevConversations,
452
- [newConversationId.length > 0 ? newConversationId : currentConversation]: updatedConversation
453
- };
454
- });
455
- }
456
- streamingConversations.current[currentConversation] = [];
457
- if (typeof onComplete === "function") {
458
- onComplete(finalMessages.join(""));
459
- }
460
- if (currentConversation === TEMP_CONVERSATION_ID && newConversationId) {
461
- Object.keys(toolCallsCache.current).forEach((key) => {
462
- if (key.startsWith(`${TEMP_CONVERSATION_ID}-`)) {
463
- const messageIndex = key.replace(`${TEMP_CONVERSATION_ID}-`, "");
464
- const newKey = `${newConversationId}-${messageIndex}`;
465
- toolCallsCache.current[newKey] = toolCallsCache.current[key];
466
- delete toolCallsCache.current[key];
467
- }
468
- });
469
- setConversations((prevConversations) => {
470
- return {
471
- ...prevConversations,
472
- [newConversationId]: prevConversations[TEMP_CONVERSATION_ID]
473
- };
474
- });
475
- onStart?.(newConversationId);
476
- setTimeout(() => {
477
- setConversations((prev) => {
478
- const { [TEMP_CONVERSATION_ID]: _, ...rest } = prev;
479
- return rest;
485
+ streamingConversations.current[currentConversation] = [];
486
+ if (typeof onComplete === "function") {
487
+ onComplete(finalMessages.join(""));
488
+ }
489
+ if (currentConversation === TEMP_CONVERSATION_ID && newConversationId) {
490
+ migrateSharedToolCallsCacheSessionPrefixToConversation(
491
+ toolCallsCacheKeyPrefix,
492
+ newConversationId
493
+ );
494
+ setConversations((prevConversations) => {
495
+ return {
496
+ ...prevConversations,
497
+ [newConversationId]: prevConversations[TEMP_CONVERSATION_ID]
498
+ };
480
499
  });
481
- }, 0);
500
+ onStart?.(newConversationId);
501
+ setTimeout(() => {
502
+ setConversations((prev) => {
503
+ const { [TEMP_CONVERSATION_ID]: _, ...rest } = prev;
504
+ return rest;
505
+ });
506
+ }, 0);
507
+ } else if (currentConversation === TEMP_CONVERSATION_ID) {
508
+ clearSharedToolCallsCacheSessionPrefix(toolCallsCacheKeyPrefix);
509
+ }
510
+ } finally {
511
+ if (streamStartedOnTemp) {
512
+ isTempStreamInProgressRef.current = false;
513
+ }
514
+ setStreamingConversationId(null);
482
515
  }
483
516
  },
484
517
  [
@@ -498,6 +531,7 @@ const useConversationMessages = (conversationId, userName, selectedModel, select
498
531
  handleInputPrompt,
499
532
  conversations,
500
533
  scrollToBottomRef,
534
+ streamingConversationId,
501
535
  ...queryProps
502
536
  };
503
537
  };