@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
package/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  ## @red-hat-developer-hub/backstage-plugin-lightspeed
2
2
 
3
+ ## 2.6.0
4
+
5
+ ### Patch Changes
6
+
7
+ - 9f5f22a: Fix new chat streams when switching threads and scope Stop to the streaming conversation (RHDHBUGS-3040).
8
+ - @red-hat-developer-hub/backstage-plugin-lightspeed-common@2.6.0
9
+
10
+ ## 2.5.1
11
+
12
+ ### Patch Changes
13
+
14
+ - d621f93: Improved notebook upload modal and MessageBar UX.
15
+ - d621f93: Fixed overwrite flow to add duplicate files to the Add Document modal instead of uploading immediately. Reduced notebook delete toast timeout to 2 seconds.
16
+ - 5d17950: Retain Lightspeed chat and tool-call state when the chat UI remounts (for example when switching display mode between embedded and overlay), so the active thread and tool metadata are not dropped while messages are still streaming or before history refetches.
17
+ - @red-hat-developer-hub/backstage-plugin-lightspeed-common@2.5.1
18
+
3
19
  ## 2.5.0
4
20
 
5
21
  ### Minor Changes
package/README.md CHANGED
@@ -85,7 +85,11 @@ Lightspeed is a front-end plugin that enables you to interact with any LLM serve
85
85
  #### Procedure
86
86
 
87
87
  1. Open your Backstage application and select a Lightspeed nav item from the **Navigation**.
88
- 2. Ask you questions to the Lightspeed chatbot.
88
+ 2. Ask your questions to the Lightspeed chatbot.
89
+
90
+ ### Display modes and chat continuity
91
+
92
+ Lightspeed supports multiple **display modes** from Settings (for example overlay, docked, embedded, and fullscreen). Switching modes can remount the chat surface; your **current conversation** and **tool-call metadata** for that thread stay with the session so the active chat is not reset. Live streaming text may not update continuously across a mode switch until the assistant response finishes loading.
89
93
 
90
94
  ### MCP servers settings
91
95
 
@@ -129,7 +133,7 @@ Notebooks is an experimental feature that enables **document-based conversations
129
133
 
130
134
  #### Prerequisites for Notebooks
131
135
 
132
- - Notebooks requires a **Llama Stack service** to be running
136
+ - Notebooks requires a **Lightspeed Stack service** to be running
133
137
  - The backend administrator must enable the feature (see [Backend Configuration](../lightspeed-backend/README.md#notebooks-developer-preview))
134
138
  - Users need the appropriate RBAC permissions (if enabled)
135
139
 
package/dist/alpha.d.ts CHANGED
@@ -66,10 +66,12 @@ declare const lightspeedTranslationRef: _backstage_frontend_plugin_api.Translati
66
66
  readonly "notebook.view.upload.heading": string;
67
67
  readonly "notebook.view.upload.action": string;
68
68
  readonly "notebook.view.input.placeholder": string;
69
+ readonly "notebook.view.input.disabledTooltip": string;
69
70
  readonly "notebook.view.sidebar.collapse": string;
70
71
  readonly "notebook.view.sidebar.expand": string;
71
72
  readonly "notebook.view.sidebar.resize": string;
72
73
  readonly "notebook.view.documents.uploading": string;
74
+ readonly "notebook.view.documents.maxReached": string;
73
75
  readonly "notebook.upload.success": string;
74
76
  readonly "notebook.upload.failed": string;
75
77
  readonly "notebook.upload.modal.title": string;
@@ -77,6 +79,9 @@ declare const lightspeedTranslationRef: _backstage_frontend_plugin_api.Translati
77
79
  readonly "notebook.upload.modal.browseButton": string;
78
80
  readonly "notebook.upload.modal.separator": string;
79
81
  readonly "notebook.upload.modal.infoText": string;
82
+ readonly "notebook.upload.modal.selectedFiles": string;
83
+ readonly "notebook.upload.modal.addButton": string;
84
+ readonly "notebook.upload.modal.removeFile": string;
80
85
  readonly "notebook.upload.error.unsupportedType": string;
81
86
  readonly "notebook.upload.error.fileTooLarge": string;
82
87
  readonly "notebook.upload.error.tooManyFiles": string;
@@ -1,5 +1,5 @@
1
1
  import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
2
- import { useState, useEffect, useRef, useCallback, useMemo, useLayoutEffect } from 'react';
2
+ import { useState, useEffect, useRef, useCallback, useLayoutEffect, useMemo } from 'react';
3
3
  import { useNavigate, useMatch } from 'react-router-dom';
4
4
  import { makeStyles, Button } from '@material-ui/core';
5
5
  import { ChatbotDisplayMode, ChatbotContent, ChatbotFooter, MessageBar, ChatbotFootnote, Settings, Chatbot, ChatbotHeader, ChatbotHeaderMain, ChatbotHeaderMenu, ChatbotHeaderTitle, FileDropZone, ChatbotAlert } from '@patternfly/chatbot';
@@ -452,7 +452,8 @@ const LightspeedChat = ({
452
452
  currentConversationId: routeConversationId,
453
453
  setCurrentConversationId,
454
454
  draftMessage,
455
- setDraftMessage
455
+ setDraftMessage,
456
+ consumePendingOverlayThreadHandoff
456
457
  } = useLightspeedDrawerContext();
457
458
  const isFullscreenMode = displayMode === ChatbotDisplayMode.embedded;
458
459
  const showChatPanel = !isFullscreenMode || activeTab === 0;
@@ -529,11 +530,44 @@ const LightspeedChat = ({
529
530
  handleFileUpload,
530
531
  setUploadError
531
532
  } = useFileAttachmentContext();
533
+ useLayoutEffect(() => {
534
+ if (!isReady) return;
535
+ if (isFullscreenMode) {
536
+ return;
537
+ }
538
+ const handoff = consumePendingOverlayThreadHandoff?.() ?? false;
539
+ if (handoff) {
540
+ if (routeConversationId) {
541
+ setConversationId(routeConversationId);
542
+ setNewChatCreated(false);
543
+ } else {
544
+ setConversationId(TEMP_CONVERSATION_ID);
545
+ setNewChatCreated(true);
546
+ }
547
+ return;
548
+ }
549
+ if (routeConversationId && routeConversationId !== conversationId) {
550
+ setConversationId(routeConversationId);
551
+ setNewChatCreated(false);
552
+ } else if (!routeConversationId && conversationId === "" && lastOpenedId !== null) {
553
+ setConversationId(lastOpenedId);
554
+ }
555
+ }, [
556
+ isReady,
557
+ isFullscreenMode,
558
+ routeConversationId,
559
+ conversationId,
560
+ lastOpenedId,
561
+ consumePendingOverlayThreadHandoff
562
+ ]);
532
563
  useEffect(() => {
533
- if (isReady && lastOpenedId !== null) {
564
+ if (!isReady || !isFullscreenMode) {
565
+ return;
566
+ }
567
+ if (lastOpenedId !== null) {
534
568
  setConversationId(lastOpenedId);
535
569
  }
536
- }, [lastOpenedId, isReady]);
570
+ }, [lastOpenedId, isReady, isFullscreenMode]);
537
571
  const queryClient = useQueryClient();
538
572
  const {
539
573
  data: conversations = [],
@@ -545,27 +579,53 @@ const LightspeedChat = ({
545
579
  const samplePrompts = useWelcomePrompts();
546
580
  useEffect(() => {
547
581
  if (!user || !isReady) return;
582
+ const onOverlayLikeSurface = isFullscreenMode || !routeConversationId;
583
+ const stillOnProvisionalThread = !conversationId || conversationId === TEMP_CONVERSATION_ID;
548
584
  if (lastOpenedId === null) {
549
- setConversationId(TEMP_CONVERSATION_ID);
585
+ if (onOverlayLikeSurface && stillOnProvisionalThread) {
586
+ setConversationId(TEMP_CONVERSATION_ID);
587
+ }
550
588
  }
551
589
  if (lastOpenedId === TEMP_CONVERSATION_ID || lastOpenedId === null) {
552
- setNewChatCreated(true);
590
+ if (onOverlayLikeSurface && stillOnProvisionalThread) {
591
+ setNewChatCreated(true);
592
+ }
553
593
  }
554
- }, [user, isReady, lastOpenedId, setConversationId]);
594
+ }, [
595
+ user,
596
+ isReady,
597
+ lastOpenedId,
598
+ setConversationId,
599
+ isFullscreenMode,
600
+ routeConversationId,
601
+ conversationId
602
+ ]);
555
603
  useEffect(() => {
556
604
  if (!isLoading && !isRefetching && conversations.length === 0 && lastOpenedId) {
557
605
  clearLastOpenedId();
606
+ setConversationId(TEMP_CONVERSATION_ID);
607
+ setNewChatCreated(true);
608
+ setCurrentConversationId(undefined);
558
609
  }
559
- }, [isLoading, isRefetching, conversations, lastOpenedId, clearLastOpenedId]);
610
+ }, [
611
+ isLoading,
612
+ isRefetching,
613
+ conversations,
614
+ lastOpenedId,
615
+ clearLastOpenedId,
616
+ setCurrentConversationId
617
+ ]);
560
618
  useEffect(() => {
561
619
  if (!isLoading && !isRefetching && routeConversationId && isFullscreenMode) {
562
620
  const conversationExists = conversations.some(
563
621
  (c) => c.conversation_id === routeConversationId
564
622
  );
565
623
  if (!conversationExists) {
566
- setConversationId(TEMP_CONVERSATION_ID);
567
- setCurrentConversationId(undefined);
568
- setNewChatCreated(true);
624
+ if (routeConversationId !== conversationId) {
625
+ setConversationId(TEMP_CONVERSATION_ID);
626
+ setCurrentConversationId(undefined);
627
+ setNewChatCreated(true);
628
+ }
569
629
  } else if (conversationId !== routeConversationId) {
570
630
  setConversationId(routeConversationId);
571
631
  }
@@ -585,6 +645,10 @@ const LightspeedChat = ({
585
645
  setLastOpenedId(conversationId);
586
646
  }
587
647
  }, [conversationId, setLastOpenedId]);
648
+ const viewConversationId = useMemo(
649
+ () => !isFullscreenMode && routeConversationId ? routeConversationId : conversationId,
650
+ [isFullscreenMode, routeConversationId, conversationId]
651
+ );
588
652
  const onStart = (conv_id) => {
589
653
  setConversationId(conv_id);
590
654
  setCurrentConversationId(conv_id);
@@ -602,12 +666,17 @@ const LightspeedChat = ({
602
666
  queryKey: ["conversations"]
603
667
  });
604
668
  queryClient.invalidateQueries({
605
- queryKey: ["conversationMessages", conversationId]
669
+ queryKey: ["conversationMessages", viewConversationId]
606
670
  });
607
671
  setNewChatCreated(false);
608
672
  };
609
- const { conversationMessages, handleInputPrompt, scrollToBottomRef } = useConversationMessages(
610
- conversationId,
673
+ const {
674
+ conversationMessages,
675
+ handleInputPrompt,
676
+ scrollToBottomRef,
677
+ streamingConversationId
678
+ } = useConversationMessages(
679
+ viewConversationId,
611
680
  userName,
612
681
  selectedModel,
613
682
  selectedProvider,
@@ -617,11 +686,12 @@ const LightspeedChat = ({
617
686
  undefined,
618
687
  onRequestIdReady
619
688
  );
689
+ const streamingUiMatchesView = isSendButtonDisabled && streamingConversationId !== null && viewConversationId === streamingConversationId;
620
690
  const [messages, setMessages] = useState(conversationMessages);
621
691
  const sendMessage = (message) => {
622
692
  if (!message.toString().trim()) return;
623
693
  wasStoppedByUserRef.current = false;
624
- if (conversationId !== TEMP_CONVERSATION_ID) {
694
+ if (viewConversationId !== TEMP_CONVERSATION_ID) {
625
695
  setNewChatCreated(false);
626
696
  }
627
697
  setAnnouncement(
@@ -839,7 +909,7 @@ const LightspeedChat = ({
839
909
  ]
840
910
  );
841
911
  const conversationFound = !!conversations.find(
842
- (c) => c.conversation_id === conversationId
912
+ (c) => c.conversation_id === viewConversationId
843
913
  );
844
914
  const getMaxPrompts = () => {
845
915
  if (isFullscreenMode) {
@@ -1112,7 +1182,7 @@ const LightspeedChat = ({
1112
1182
  ref: scrollToBottomRef,
1113
1183
  welcomePrompts,
1114
1184
  conversationId,
1115
- isStreaming: isSendButtonDisabled,
1185
+ isStreaming: streamingUiMatchesView,
1116
1186
  topicRestrictionEnabled,
1117
1187
  displayMode
1118
1188
  }
@@ -1140,8 +1210,8 @@ const LightspeedChat = ({
1140
1210
  hasMicrophoneButton: true,
1141
1211
  value: draftMessage,
1142
1212
  onChange: handleDraftMessage,
1143
- hasStopButton: isSendButtonDisabled,
1144
- handleStopButton: isSendButtonDisabled ? handleStopButton : undefined,
1213
+ hasStopButton: streamingUiMatchesView,
1214
+ handleStopButton: streamingUiMatchesView ? handleStopButton : undefined,
1145
1215
  buttonProps: {
1146
1216
  attach: {
1147
1217
  inputTestId: "attachment-input",
@@ -1217,7 +1287,7 @@ const LightspeedChat = ({
1217
1287
  variant: AlertVariant[variant ?? "success"],
1218
1288
  title,
1219
1289
  className: classes.toastAlert,
1220
- timeout: 8e3,
1290
+ timeout: 2e3,
1221
1291
  onTimeout: () => handleRemoveNotebookAlert(key),
1222
1292
  actionClose: /* @__PURE__ */ jsx(
1223
1293
  AlertActionCloseButton,
@@ -1342,7 +1412,7 @@ const LightspeedChat = ({
1342
1412
  "aria-label": t("aria.closeDrawerPanel")
1343
1413
  },
1344
1414
  setIsDrawerOpen: setIsChatHistoryDrawerOpen,
1345
- activeItemId: conversationId,
1415
+ activeItemId: viewConversationId,
1346
1416
  onSelectActiveItem,
1347
1417
  conversations: filterConversations(filterValue),
1348
1418
  onNewChat: newChatCreated ? undefined : onNewChat,
@@ -1406,6 +1476,7 @@ const LightspeedChat = ({
1406
1476
  avatar,
1407
1477
  profileLoading,
1408
1478
  topicRestrictionEnabled,
1479
+ selectedModel,
1409
1480
  onClose: handleCloseNotebook
1410
1481
  }
1411
1482
  ),