@red-hat-developer-hub/backstage-plugin-lightspeed 2.5.1 → 2.6.1

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