@red-hat-developer-hub/backstage-plugin-lightspeed 2.5.0 → 2.5.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 (39) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README.md +5 -1
  3. package/dist/alpha.d.ts +5 -0
  4. package/dist/components/LightSpeedChat.esm.js +82 -17
  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 +18 -14
  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,14 @@
1
1
  ## @red-hat-developer-hub/backstage-plugin-lightspeed
2
2
 
3
+ ## 2.5.1
4
+
5
+ ### Patch Changes
6
+
7
+ - d621f93: Improved notebook upload modal and MessageBar UX.
8
+ - 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.
9
+ - 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.
10
+ - @red-hat-developer-hub/backstage-plugin-lightspeed-common@2.5.1
11
+
3
12
  ## 2.5.0
4
13
 
5
14
  ### 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
 
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,12 @@ 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
673
  const { conversationMessages, handleInputPrompt, scrollToBottomRef } = useConversationMessages(
610
- conversationId,
674
+ viewConversationId,
611
675
  userName,
612
676
  selectedModel,
613
677
  selectedProvider,
@@ -621,7 +685,7 @@ const LightspeedChat = ({
621
685
  const sendMessage = (message) => {
622
686
  if (!message.toString().trim()) return;
623
687
  wasStoppedByUserRef.current = false;
624
- if (conversationId !== TEMP_CONVERSATION_ID) {
688
+ if (viewConversationId !== TEMP_CONVERSATION_ID) {
625
689
  setNewChatCreated(false);
626
690
  }
627
691
  setAnnouncement(
@@ -839,7 +903,7 @@ const LightspeedChat = ({
839
903
  ]
840
904
  );
841
905
  const conversationFound = !!conversations.find(
842
- (c) => c.conversation_id === conversationId
906
+ (c) => c.conversation_id === viewConversationId
843
907
  );
844
908
  const getMaxPrompts = () => {
845
909
  if (isFullscreenMode) {
@@ -1217,7 +1281,7 @@ const LightspeedChat = ({
1217
1281
  variant: AlertVariant[variant ?? "success"],
1218
1282
  title,
1219
1283
  className: classes.toastAlert,
1220
- timeout: 8e3,
1284
+ timeout: 2e3,
1221
1285
  onTimeout: () => handleRemoveNotebookAlert(key),
1222
1286
  actionClose: /* @__PURE__ */ jsx(
1223
1287
  AlertActionCloseButton,
@@ -1342,7 +1406,7 @@ const LightspeedChat = ({
1342
1406
  "aria-label": t("aria.closeDrawerPanel")
1343
1407
  },
1344
1408
  setIsDrawerOpen: setIsChatHistoryDrawerOpen,
1345
- activeItemId: conversationId,
1409
+ activeItemId: viewConversationId,
1346
1410
  onSelectActiveItem,
1347
1411
  conversations: filterConversations(filterValue),
1348
1412
  onNewChat: newChatCreated ? undefined : onNewChat,
@@ -1406,6 +1470,7 @@ const LightspeedChat = ({
1406
1470
  avatar,
1407
1471
  profileLoading,
1408
1472
  topicRestrictionEnabled,
1473
+ selectedModel,
1409
1474
  onClose: handleCloseNotebook
1410
1475
  }
1411
1476
  ),