@gooddata/sdk-ui-gen-ai 11.35.0-alpha.5 → 11.35.0-alpha.7

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 (102) hide show
  1. package/NOTICE +8 -8
  2. package/esm/components/GenAIChatConversations.d.ts +2 -1
  3. package/esm/components/GenAIChatConversations.js +191 -42
  4. package/esm/components/GenAIChatWrapper.js +19 -6
  5. package/esm/components/Input.js +8 -3
  6. package/esm/components/messages/conversationContents/ConversationVisualizationContent.js +32 -46
  7. package/esm/components/utils/conversationGrouper.d.ts +1 -0
  8. package/esm/components/utils/conversationGrouper.js +7 -0
  9. package/esm/index.d.ts +1 -1
  10. package/esm/index.js +1 -1
  11. package/esm/localization/bundles/de-DE.localization-bundle.d.ts +6 -1
  12. package/esm/localization/bundles/de-DE.localization-bundle.js +6 -1
  13. package/esm/localization/bundles/en-AU.localization-bundle.d.ts +6 -1
  14. package/esm/localization/bundles/en-AU.localization-bundle.js +6 -1
  15. package/esm/localization/bundles/en-GB.localization-bundle.d.ts +6 -1
  16. package/esm/localization/bundles/en-GB.localization-bundle.js +6 -1
  17. package/esm/localization/bundles/en-US.localization-bundle.d.ts +21 -1
  18. package/esm/localization/bundles/en-US.localization-bundle.js +23 -3
  19. package/esm/localization/bundles/es-419.localization-bundle.d.ts +6 -1
  20. package/esm/localization/bundles/es-419.localization-bundle.js +6 -1
  21. package/esm/localization/bundles/es-ES.localization-bundle.d.ts +6 -1
  22. package/esm/localization/bundles/es-ES.localization-bundle.js +6 -1
  23. package/esm/localization/bundles/fi-FI.localization-bundle.d.ts +6 -1
  24. package/esm/localization/bundles/fi-FI.localization-bundle.js +6 -1
  25. package/esm/localization/bundles/fr-CA.localization-bundle.d.ts +6 -1
  26. package/esm/localization/bundles/fr-CA.localization-bundle.js +6 -1
  27. package/esm/localization/bundles/fr-FR.localization-bundle.d.ts +6 -1
  28. package/esm/localization/bundles/fr-FR.localization-bundle.js +6 -1
  29. package/esm/localization/bundles/id-ID.localization-bundle.d.ts +6 -1
  30. package/esm/localization/bundles/id-ID.localization-bundle.js +6 -1
  31. package/esm/localization/bundles/it-IT.localization-bundle.d.ts +6 -1
  32. package/esm/localization/bundles/it-IT.localization-bundle.js +6 -1
  33. package/esm/localization/bundles/ja-JP.localization-bundle.d.ts +6 -1
  34. package/esm/localization/bundles/ja-JP.localization-bundle.js +6 -1
  35. package/esm/localization/bundles/ko-KR.localization-bundle.d.ts +6 -1
  36. package/esm/localization/bundles/ko-KR.localization-bundle.js +6 -1
  37. package/esm/localization/bundles/nl-NL.localization-bundle.d.ts +6 -1
  38. package/esm/localization/bundles/nl-NL.localization-bundle.js +6 -1
  39. package/esm/localization/bundles/pl-PL.localization-bundle.d.ts +6 -1
  40. package/esm/localization/bundles/pl-PL.localization-bundle.js +6 -1
  41. package/esm/localization/bundles/pt-BR.localization-bundle.d.ts +6 -1
  42. package/esm/localization/bundles/pt-BR.localization-bundle.js +6 -1
  43. package/esm/localization/bundles/pt-PT.localization-bundle.d.ts +6 -1
  44. package/esm/localization/bundles/pt-PT.localization-bundle.js +6 -1
  45. package/esm/localization/bundles/ru-RU.localization-bundle.d.ts +6 -1
  46. package/esm/localization/bundles/ru-RU.localization-bundle.js +6 -1
  47. package/esm/localization/bundles/sl-SI.localization-bundle.d.ts +6 -1
  48. package/esm/localization/bundles/sl-SI.localization-bundle.js +6 -1
  49. package/esm/localization/bundles/th-TH.localization-bundle.d.ts +6 -1
  50. package/esm/localization/bundles/th-TH.localization-bundle.js +6 -1
  51. package/esm/localization/bundles/tr-TR.localization-bundle.d.ts +6 -1
  52. package/esm/localization/bundles/tr-TR.localization-bundle.js +6 -1
  53. package/esm/localization/bundles/uk-UA.localization-bundle.d.ts +6 -1
  54. package/esm/localization/bundles/uk-UA.localization-bundle.js +6 -1
  55. package/esm/localization/bundles/vi-VN.localization-bundle.d.ts +6 -1
  56. package/esm/localization/bundles/vi-VN.localization-bundle.js +6 -1
  57. package/esm/localization/bundles/zh-HK.localization-bundle.d.ts +6 -1
  58. package/esm/localization/bundles/zh-HK.localization-bundle.js +6 -1
  59. package/esm/localization/bundles/zh-Hans.localization-bundle.d.ts +6 -1
  60. package/esm/localization/bundles/zh-Hans.localization-bundle.js +6 -1
  61. package/esm/localization/bundles/zh-Hant.localization-bundle.d.ts +6 -1
  62. package/esm/localization/bundles/zh-Hant.localization-bundle.js +6 -1
  63. package/esm/model.d.ts +2 -0
  64. package/esm/sdk-ui-gen-ai.d.ts +65 -1
  65. package/esm/store/events.d.ts +57 -1
  66. package/esm/store/events.js +28 -0
  67. package/esm/store/messages/messagesSelectors.d.ts +3 -3
  68. package/esm/store/messages/messagesSelectors.js +26 -4
  69. package/esm/store/messages/messagesSlice.d.ts +36 -20
  70. package/esm/store/messages/messagesSlice.js +212 -94
  71. package/esm/store/sideEffects/index.js +4 -2
  72. package/esm/store/sideEffects/onConversationDelete.d.ts +3 -1
  73. package/esm/store/sideEffects/onConversationDelete.js +1 -1
  74. package/esm/store/sideEffects/onConversationPin.d.ts +19 -0
  75. package/esm/store/sideEffects/onConversationPin.js +30 -0
  76. package/esm/store/sideEffects/onEvent.js +44 -1
  77. package/esm/store/sideEffects/onThreadLoad.js +15 -5
  78. package/esm/store/sideEffects/onUserFeedback.d.ts +3 -0
  79. package/esm/store/sideEffects/onUserFeedback.js +2 -1
  80. package/esm/store/sideEffects/onUserMessage.d.ts +9 -2
  81. package/esm/store/sideEffects/onUserMessage.js +42 -22
  82. package/esm/store/sideEffects/onUserMessageUpdate.d.ts +5 -3
  83. package/esm/store/sideEffects/onUserMessageUpdate.js +5 -2
  84. package/esm/store/sideEffects/onVisualisationRender.d.ts +2 -0
  85. package/esm/store/sideEffects/onVisualizationSave.d.ts +4 -0
  86. package/esm/store/sideEffects/onVisualizationSave.js +2 -0
  87. package/esm/store/sideEffects/onVisualizationSuccessSave.d.ts +2 -0
  88. package/esm/store/sideEffects/utils.js +0 -3
  89. package/esm/store/utils.d.ts +6 -0
  90. package/esm/store/utils.js +30 -0
  91. package/esm/types.d.ts +26 -0
  92. package/esm/utils.d.ts +4 -0
  93. package/esm/utils.js +23 -0
  94. package/package.json +21 -21
  95. package/styles/css/conversations.css +82 -8
  96. package/styles/css/conversations.css.map +1 -1
  97. package/styles/css/main.css +85 -8
  98. package/styles/css/main.css.map +1 -1
  99. package/styles/css/messages.css +3 -0
  100. package/styles/css/messages.css.map +1 -1
  101. package/styles/scss/conversations.scss +98 -11
  102. package/styles/scss/messages.scss +4 -0
package/NOTICE CHANGED
@@ -7,9 +7,9 @@
7
7
 
8
8
  The following 3rd-party software packages may be used by or distributed with gooddata-ui-sdk. Any information relevant to third-party vendors listed below are collected using common, reasonable means.
9
9
 
10
- Date generated: 2026-5-4
10
+ Date generated: 2026-5-12
11
11
 
12
- Revision ID: 0c39c460e837a54efedde9d37131feb930e4b8a1
12
+ Revision ID: 52d6a5f81cbc391c8c918fe465fad2be3e3de86f
13
13
 
14
14
  ================================================================================
15
15
  ================================================================================
@@ -376,7 +376,7 @@ Revision ID: 0c39c460e837a54efedde9d37131feb930e4b8a1
376
376
  - bail (2.0.2) [MIT]
377
377
  - balanced-match (2.0.0) [MIT]
378
378
  - base64-js (1.5.1) [MIT]
379
- - baseline-browser-mapping (2.10.27) [Apache-2.0]
379
+ - baseline-browser-mapping (2.10.29) [Apache-2.0]
380
380
  - batch (0.6.1) [MIT]
381
381
  - binary-extensions (2.3.0) [MIT]
382
382
  - bl (4.1.0) [MIT]
@@ -710,7 +710,7 @@ Revision ID: 0c39c460e837a54efedde9d37131feb930e4b8a1
710
710
  - magic-string (0.30.21) [MIT]
711
711
  - make-dir (2.1.0) [MIT]
712
712
  - mapbox-gl (2.15.0) [BSD-3-Clause, MIT]
713
- - maplibre-gl (4.7.1) [MIT, BSD-3-Clause, ISC, 0BSD]
713
+ - maplibre-gl (4.7.1) [MIT, BSD-3-Clause, 0BSD, ISC]
714
714
  - markdown-table (3.0.4) [MIT]
715
715
  - matchmediaquery (0.4.2) [MIT]
716
716
  - math-intrinsics (1.1.0) [MIT]
@@ -5367,9 +5367,9 @@ MIT
5367
5367
 
5368
5368
 
5369
5369
  --------------------------------------------------------------------------------
5370
- Package Title: baseline-browser-mapping (2.10.27)
5370
+ Package Title: baseline-browser-mapping (2.10.29)
5371
5371
 
5372
- Package Locator: npm+baseline-browser-mapping$2.10.27
5372
+ Package Locator: npm+baseline-browser-mapping$2.10.29
5373
5373
 
5374
5374
  Package Depth: Transitive
5375
5375
  --------------------------------------------------------------------------------
@@ -9407,7 +9407,7 @@ MIT, BSD-3-Clause
9407
9407
 
9408
9408
 
9409
9409
  * Other Licenses *
9410
- ISC, 0BSD
9410
+ 0BSD, ISC
9411
9411
 
9412
9412
 
9413
9413
  --------------------------------------------------------------------------------
@@ -38277,4 +38277,4 @@ POSSIBILITY OF SUCH DAMAGE.
38277
38277
  --------------------------------------------------------------------------------
38278
38278
  --------------------------------------------------------------------------------
38279
38279
 
38280
- Report Generated by FOSSA on 2026-5-4
38280
+ Report Generated by FOSSA on 2026-5-12
@@ -1,7 +1,7 @@
1
1
  import { type FC } from "react";
2
2
  import { setHistoryAction } from "../store/chatWindow/chatWindowSlice.js";
3
3
  import { conversationSelector, conversationsSelector } from "../store/messages/messagesSelectors.js";
4
- import { deleteConversationAction, setCurrentConversationAction } from "../store/messages/messagesSlice.js";
4
+ import { deleteConversationAction, pinConversationAction, setCurrentConversationAction } from "../store/messages/messagesSlice.js";
5
5
  type GenAIChatConversationsStateProps = {
6
6
  conversation: ReturnType<typeof conversationSelector>;
7
7
  conversations: ReturnType<typeof conversationsSelector>;
@@ -10,6 +10,7 @@ type GenAIChatConversationsDispatchProps = {
10
10
  setHistory: typeof setHistoryAction;
11
11
  loadConversation: typeof setCurrentConversationAction;
12
12
  deleteConversation: typeof deleteConversationAction;
13
+ pinConversation: typeof pinConversationAction;
13
14
  };
14
15
  export type GenAIChatConversationsProps = GenAIChatConversationsStateProps & GenAIChatConversationsDispatchProps;
15
16
  export declare const GenAIChatConversations: FC;
@@ -1,26 +1,30 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  // (C) 2026 GoodData Corporation
3
- import { useCallback, useMemo, useRef, useState } from "react";
3
+ import { useCallback, useMemo, useRef, useState, } from "react";
4
4
  import cx from "classnames";
5
5
  import { FormattedMessage, useIntl } from "react-intl";
6
6
  import { connect, useSelector } from "react-redux";
7
- import { DefaultUiMenuInteractiveItemWrapper, UiDrawer, UiIcon, UiIconButton, UiMenu, } from "@gooddata/sdk-ui-kit";
7
+ import { DefaultUiMenuInteractiveItemWrapper, Dropdown, UiDrawer, UiIcon, UiIconButton, UiMenu, } from "@gooddata/sdk-ui-kit";
8
8
  import { catalogItemsSelector } from "../store/chatWindow/chatWindowSelectors.js";
9
9
  import { setHistoryAction } from "../store/chatWindow/chatWindowSlice.js";
10
10
  import { conversationSelector, conversationsSelector } from "../store/messages/messagesSelectors.js";
11
- import { deleteConversationAction, setCurrentConversationAction } from "../store/messages/messagesSlice.js";
11
+ import { deleteConversationAction, pinConversationAction, setCurrentConversationAction, } from "../store/messages/messagesSlice.js";
12
+ import { isConversationWithLocalId } from "../store/utils.js";
12
13
  import { generateTemporaryTitle } from "../utils.js";
13
14
  import { collectReferences, replaceReferences } from "./completion/references.js";
14
15
  import { ConversationDeleteDialog } from "./ConversationDeleteDialog.js";
15
16
  import { useFullscreenCheck } from "./hooks/useFullscreenCheck.js";
16
17
  import { useHistoryCheck } from "./hooks/useHistoryCheck.js";
17
18
  import { ConversationDateGroup, groupConversationsByDate } from "./utils/conversationGrouper.js";
18
- function GenAIChatConversationsComponent({ setHistory, deleteConversation, loadConversation, conversations, conversation: currentConversation, }) {
19
+ function GenAIChatConversationsComponent({ setHistory, deleteConversation, pinConversation, loadConversation, conversations, conversation: currentConversation, }) {
19
20
  const ref = useRef(undefined);
20
21
  const intl = useIntl();
21
22
  const { isFullscreen, isSmallScreen } = useFullscreenCheck();
22
23
  const { isHistory } = useHistoryCheck();
23
24
  const [conversationToDelete, setConversationToDelete] = useState();
25
+ const [openedId, setOpenedId] = useState();
26
+ const [draggedConversationId, setDraggedConversationId] = useState();
27
+ const [activeDropZone, setActiveDropZone] = useState();
24
28
  const catalogItems = useSelector(catalogItemsSelector);
25
29
  const groupedConversations = useMemo(() => groupConversationsByDate(conversations), [conversations]);
26
30
  const menuItems = useMemo(() => groupedConversations.map((group) => ({
@@ -30,19 +34,74 @@ function GenAIChatConversationsComponent({ setHistory, deleteConversation, loadC
30
34
  data: group.group,
31
35
  subItems: group.conversations.map((conversation) => ({
32
36
  type: "interactive",
33
- id: conversation.id,
37
+ id: conversation.localId,
34
38
  stringTitle: replaceReferences(conversation.title ?? "", collectReferences(conversation.title ?? "", catalogItems)),
35
39
  data: conversation,
36
- iconRight: (_jsx("div", { className: "gd-gen-ai-chat__window__conversations__list__delete-button", children: _jsx(UiIconButton, { isDesctructive: true, size: "xsmall", variant: "tertiary", label: intl.formatMessage({
37
- id: "gd.gen-ai.conversations.delete-button.aria-label",
38
- }), onClick: (event) => {
39
- event.preventDefault();
40
- event.stopPropagation();
41
- setConversationToDelete(conversation);
42
- }, tabIndex: -1, icon: "trash" }) })),
43
- isSelected: currentConversation === "new" ? false : conversation.id === currentConversation?.id,
40
+ iconRight: (_jsx("div", { className: "gd-gen-ai-chat__window__conversations__list__delete-button", children: _jsx(Dropdown, { onToggle: () => {
41
+ setOpenedId(openedId ? undefined : conversation.localId);
42
+ }, isOpen: openedId === conversation.localId, alignPoints: [{ align: "bl tl" }], renderButton: ({ toggleDropdown, buttonRef, ariaAttributes, accessibilityConfig, }) => (_jsx(UiIconButton, { ref: (element) => {
43
+ buttonRef.current = element;
44
+ }, size: "medium", variant: "tertiary", label: intl.formatMessage({
45
+ id: "gd.gen-ai.conversations.menu.aria-label",
46
+ }), onClick: (event) => {
47
+ event.preventDefault();
48
+ event.stopPropagation();
49
+ toggleDropdown();
50
+ }, accessibilityConfig: {
51
+ ...accessibilityConfig,
52
+ ariaExpanded: ariaAttributes["aria-expanded"],
53
+ ariaHaspopup: ariaAttributes["aria-haspopup"],
54
+ ariaControls: ariaAttributes["aria-controls"],
55
+ }, tabIndex: -1, icon: "ellipsis" })), renderBody: ({ closeDropdown, ariaAttributes }) => (_jsx(UiMenu, { shouldCloseOnSelect: true, items: [
56
+ {
57
+ type: "interactive",
58
+ id: conversation.pinned ? "unpin" : "pin",
59
+ stringTitle: conversation.pinned
60
+ ? intl.formatMessage({
61
+ id: "gd.gen-ai.conversations.menu.unpin",
62
+ })
63
+ : intl.formatMessage({
64
+ id: "gd.gen-ai.conversations.menu.pin",
65
+ }),
66
+ iconLeft: conversation.pinned ? (_jsx(UiIcon, { type: "unpin", size: 14 })) : (_jsx(UiIcon, { type: "pin", size: 14 })),
67
+ data: conversation,
68
+ },
69
+ {
70
+ type: "static",
71
+ id: "delete-separator",
72
+ data: (_jsx("div", { className: "gd-gen-ai-chat__window__conversations__divider_menu" })),
73
+ },
74
+ {
75
+ type: "interactive",
76
+ id: "delete",
77
+ stringTitle: intl.formatMessage({
78
+ id: "gd.gen-ai.conversations.delete-dialog.submit",
79
+ }),
80
+ isDestructive: true,
81
+ iconLeft: _jsx(UiIcon, { type: "trash", size: 14 }),
82
+ data: {
83
+ ...conversation,
84
+ action: "delete",
85
+ },
86
+ },
87
+ ], onSelect: (item, event) => {
88
+ const selectedConversation = item.data;
89
+ if (selectedConversation.action === "delete") {
90
+ setConversationToDelete(conversation);
91
+ }
92
+ else {
93
+ pinConversation({
94
+ conversationId: selectedConversation.localId,
95
+ pinned: !selectedConversation.pinned,
96
+ });
97
+ }
98
+ event.preventDefault();
99
+ event.stopPropagation();
100
+ }, onClose: closeDropdown, ariaAttributes: ariaAttributes })), closeOnEscape: true, autofocusOnOpen: true }) })),
101
+ isSelected: isConversationWithLocalId(currentConversation, conversation.localId),
44
102
  })),
45
- })), [groupedConversations, intl, catalogItems, currentConversation]);
103
+ })), [groupedConversations, intl, catalogItems, openedId, currentConversation, pinConversation]);
104
+ const draggedConversation = useMemo(() => conversations?.find((conversation) => conversation.localId === draggedConversationId), [conversations, draggedConversationId]);
46
105
  const handleDeleteCancel = useCallback(() => {
47
106
  setConversationToDelete(undefined);
48
107
  }, []);
@@ -52,43 +111,130 @@ function GenAIChatConversationsComponent({ setHistory, deleteConversation, loadC
52
111
  }, [loadConversation, setHistory]);
53
112
  const handleDeleteSubmit = useCallback(() => {
54
113
  if (conversationToDelete) {
55
- deleteConversation({ conversationId: conversationToDelete.id });
114
+ deleteConversation({ conversationId: conversationToDelete.localId });
56
115
  }
57
116
  setConversationToDelete(undefined);
58
117
  }, [conversationToDelete, deleteConversation]);
118
+ const handleDragStart = useCallback((conversationId, event) => {
119
+ event.dataTransfer.effectAllowed = "move";
120
+ event.dataTransfer.setData("text/plain", conversationId);
121
+ setDraggedConversationId(conversationId);
122
+ setActiveDropZone("body");
123
+ }, []);
124
+ const handleDragEnd = useCallback(() => {
125
+ setDraggedConversationId(undefined);
126
+ setActiveDropZone(undefined);
127
+ }, []);
128
+ const handleDropZoneDragOver = useCallback((dropZone, event) => {
129
+ if (!draggedConversation) {
130
+ return;
131
+ }
132
+ const canDrop = dropZone === "pin" ? !draggedConversation.pinned : draggedConversation.pinned;
133
+ if (!canDrop) {
134
+ return;
135
+ }
136
+ event.preventDefault();
137
+ event.dataTransfer.dropEffect = "move";
138
+ setActiveDropZone(dropZone);
139
+ }, [draggedConversation]);
140
+ const handleDropZoneDragLeave = useCallback((event) => {
141
+ const nextTarget = event.relatedTarget;
142
+ if (nextTarget && event.currentTarget.contains(nextTarget)) {
143
+ return;
144
+ }
145
+ setActiveDropZone("body");
146
+ }, []);
147
+ const handleDropZoneDrop = useCallback((dropZone, event) => {
148
+ event.preventDefault();
149
+ if (!draggedConversation) {
150
+ return;
151
+ }
152
+ const shouldBePinned = dropZone === "pin";
153
+ if (draggedConversation.pinned !== shouldBePinned) {
154
+ pinConversation({
155
+ conversationId: draggedConversation.localId,
156
+ pinned: shouldBePinned,
157
+ });
158
+ }
159
+ setDraggedConversationId(undefined);
160
+ setActiveDropZone(undefined);
161
+ }, [draggedConversation, pinConversation]);
162
+ const handleMenuUnhandledKeyDown = useCallback((event, { focusedItem }) => {
163
+ if (event.key === "Delete" && focusedItem) {
164
+ event.preventDefault();
165
+ event.stopPropagation();
166
+ setConversationToDelete(focusedItem.data);
167
+ }
168
+ }, []);
59
169
  return (_jsxs(_Fragment, { children: [
60
170
  _jsx("div", { className: "gd-gen-ai-chat__window__drawer", ref: ref }), _jsx(UiDrawer, { anchor: "left", showCloseButton: true, open: isHistory, node: ref.current, showBackdrop: false, header: _jsxs("div", { style: { width: "100%" }, children: [
61
171
  _jsx("div", { className: "gd-gen-ai-chat__window__conversations__header", children: intl.formatMessage({ id: "gd.gen-ai.conversations.title" }) }), _jsx("div", { className: "gd-gen-ai-chat__window__conversations__divider" })
62
172
  ] }), closeLabel: intl.formatMessage({ id: "gd.gen-ai.conversations.close-label" }), onClickClose: () => setHistory({ isHistory: false }), onClickOutside: () => setHistory({ isHistory: false }), children: _jsx("div", { className: cx("gd-gen-ai-chat__window__conversations", {
63
173
  "gd-gen-ai-chat__window__conversations--isFullscreen": isFullscreen,
64
174
  "gd-gen-ai-chat__window__conversations--isSmallScreen": isSmallScreen,
65
- }), children: (conversations ?? []).length === 0 ? (_jsxs("div", { className: "gd-gen-ai-chat__window__conversations__empty", children: [
66
- _jsx(UiIcon, { type: "history2", size: 20, color: "complementary-6" }), _jsx("div", { className: "gd-gen-ai-chat__window__conversations__empty__text", children: _jsx(FormattedMessage, { id: "gd.gen-ai.conversations.empty", values: {
67
- br: _jsx("br", {}),
68
- } }) })
69
- ] })) : (_jsx("div", { className: "gd-gen-ai-chat__window__conversations__list", children: _jsx(UiMenu, { onUnhandledKeyDown: (event, { focusedItem }) => {
70
- if (event.key === "Delete" && focusedItem) {
71
- event.preventDefault();
72
- event.stopPropagation();
73
- setConversationToDelete(focusedItem.data);
74
- }
75
- }, InteractiveItemWrapper: (props) => {
76
- const data = props.item.data;
77
- return (_jsx("div", { className: cx("gd-gen-ai-chat__window__conversations__list__item", {
78
- generatingTitle: data.generatingTitle,
79
- }), children: _jsx(DefaultUiMenuInteractiveItemWrapper, { ...props, item: {
80
- ...props.item,
81
- stringTitle: props.item.stringTitle ||
82
- generateTemporaryTitle(intl, data),
83
- } }) }));
84
- }, items: menuItems, onSelect: (item, event) => {
85
- handleSelect(item.data);
86
- event.stopPropagation();
87
- event.preventDefault();
88
- }, shouldCloseOnSelect: false, size: "small", ariaAttributes: {
89
- id: "gd-gen-ai-conversations-menu",
90
- "aria-label": intl.formatMessage({ id: "gd.gen-ai.conversations.title" }),
91
- } }) })) }) }), conversationToDelete ? (_jsx(ConversationDeleteDialog, { conversation: conversationToDelete, onClose: handleDeleteCancel, onDelete: handleDeleteSubmit })) : null] }));
175
+ }), children: _jsx(DrawerContent, { openedId: openedId, menuItems: menuItems, conversations: conversations ?? [], handleSelect: handleSelect, draggedConversation: draggedConversation, activeDropZone: activeDropZone, onDragStart: handleDragStart, onDragEnd: handleDragEnd, onDropZoneDragOver: handleDropZoneDragOver, onDropZoneDragLeave: handleDropZoneDragLeave, onDropZoneDrop: handleDropZoneDrop, onMenuUnhandledKeyDown: handleMenuUnhandledKeyDown }) }) }), conversationToDelete ? (_jsx(ConversationDeleteDialog, { conversation: conversationToDelete, onClose: handleDeleteCancel, onDelete: handleDeleteSubmit })) : null] }));
176
+ }
177
+ function DrawerContent({ openedId, conversations, menuItems, handleSelect, draggedConversation, activeDropZone, onDragStart, onDragEnd, onDropZoneDragOver, onDropZoneDragLeave, onDropZoneDrop, onMenuUnhandledKeyDown, }) {
178
+ const intl = useIntl();
179
+ const pinnedItems = useMemo(() => {
180
+ return menuItems.filter((item) => item.id === ConversationDateGroup.PINNED);
181
+ }, [menuItems]);
182
+ const restItems = useMemo(() => {
183
+ return menuItems.filter((item) => item.id !== ConversationDateGroup.PINNED);
184
+ }, [menuItems]);
185
+ return (_jsx(_Fragment, { children: (conversations ?? []).length === 0 ? (_jsxs("div", { className: "gd-gen-ai-chat__window__conversations__empty", children: [
186
+ _jsx(UiIcon, { type: "history2", size: 20, color: "complementary-6" }), _jsx("div", { className: "gd-gen-ai-chat__window__conversations__empty__text", children: _jsx(FormattedMessage, { id: "gd.gen-ai.conversations.empty", values: {
187
+ br: _jsx("br", {}),
188
+ } }) })
189
+ ] })) : (_jsxs("div", { className: "gd-gen-ai-chat__window__conversations__list", children: [
190
+ _jsxs("div", { className: "gd-gen-ai-chat__window__conversations__drop-group", onDragLeave: onDropZoneDragLeave, onDragOver: (event) => onDropZoneDragOver("pin", event), onDrop: (event) => onDropZoneDrop("pin", event), children: [
191
+ _jsx("div", { className: cx("gd-gen-ai-chat__window__conversations__drop-placeholder", {
192
+ isDragging: activeDropZone === "body" && !draggedConversation?.pinned,
193
+ isDraggingOver: activeDropZone === "pin",
194
+ isEmpty: pinnedItems.length === 0,
195
+ }), children: _jsx("div", { className: "gd-gen-ai-chat__window__conversations__drop-placeholder__content", children: intl.formatMessage({
196
+ id: "gd.gen-ai.conversations.dnd.pin-placeholder",
197
+ }) }) }), _jsx(ConversationsList, { id: "gd-gen-ai-conversations-pinned", openedId: openedId, listItems: pinnedItems, onDragStart: onDragStart, onDragEnd: onDragEnd, onMenuUnhandledKeyDown: onMenuUnhandledKeyDown, handleSelect: handleSelect })
198
+ ] }), _jsxs("div", { className: "gd-gen-ai-chat__window__conversations__drop-group", onDragOver: (event) => onDropZoneDragOver("unpin", event), onDragLeave: onDropZoneDragLeave, onDrop: (event) => onDropZoneDrop("unpin", event), children: [
199
+ _jsx("div", { className: cx("gd-gen-ai-chat__window__conversations__drop-placeholder", {
200
+ isDragging: activeDropZone === "body" && draggedConversation?.pinned,
201
+ isDraggingOver: activeDropZone === "unpin",
202
+ }), children: _jsx("div", { className: "gd-gen-ai-chat__window__conversations__drop-placeholder__content", children: intl.formatMessage({
203
+ id: "gd.gen-ai.conversations.dnd.unpin-placeholder",
204
+ }) }) }), _jsx(ConversationsList, { id: "gd-gen-ai-conversations-rest", openedId: openedId, listItems: restItems, onDragStart: onDragStart, onDragEnd: onDragEnd, onMenuUnhandledKeyDown: onMenuUnhandledKeyDown, handleSelect: handleSelect })
205
+ ] }), draggedConversation ? (_jsx("div", { className: cx("gd-gen-ai-chat__window__conversations__drop-overlay", {
206
+ isDragging: !!activeDropZone,
207
+ }) })) : null] })) }));
208
+ }
209
+ function ConversationsList({ id, openedId, listItems, handleSelect, onDragStart, onDragEnd, onMenuUnhandledKeyDown, }) {
210
+ const intl = useIntl();
211
+ return useMemo(() => {
212
+ return (_jsx(UiMenu, { onUnhandledKeyDown: onMenuUnhandledKeyDown, InteractiveItemWrapper: (props) => (_jsx(DraggableConversationItem, { ...props, intl: intl, openedId: openedId, onDragStart: onDragStart, onDragEnd: onDragEnd })), items: listItems, onSelect: (item, event) => {
213
+ handleSelect(item.data);
214
+ event.stopPropagation();
215
+ event.preventDefault();
216
+ }, shouldCloseOnSelect: false, size: "small", ariaAttributes: {
217
+ id,
218
+ "aria-label": intl.formatMessage({ id: "gd.gen-ai.conversations.title" }),
219
+ } }));
220
+ }, [id, handleSelect, intl, listItems, onDragEnd, onDragStart, onMenuUnhandledKeyDown, openedId]);
221
+ }
222
+ function DraggableConversationItem(props) {
223
+ const data = props.item.data;
224
+ return (_jsx("div", { draggable: true, className: cx("gd-gen-ai-chat__window__conversations__list__item", {
225
+ generatingTitle: data.generatingTitle,
226
+ inProgress: data.inProgress,
227
+ openedMenu: props.openedId === props.item.id,
228
+ }), onDragStart: (event) => {
229
+ event.currentTarget.classList.add("dragging");
230
+ props.onDragStart(data.localId, event);
231
+ }, onDragEnd: (event) => {
232
+ event.currentTarget.classList.remove("dragging");
233
+ props.onDragEnd();
234
+ }, children: _jsx(DefaultUiMenuInteractiveItemWrapper, { ...props, item: {
235
+ ...props.item,
236
+ stringTitle: props.item.stringTitle || generateTemporaryTitle(props.intl, data),
237
+ } }) }));
92
238
  }
93
239
  const mapStateToProps = (state) => ({
94
240
  conversation: conversationSelector(state),
@@ -97,11 +243,14 @@ const mapStateToProps = (state) => ({
97
243
  const mapDispatchToProps = {
98
244
  setHistory: setHistoryAction,
99
245
  deleteConversation: deleteConversationAction,
246
+ pinConversation: pinConversationAction,
100
247
  loadConversation: setCurrentConversationAction,
101
248
  };
102
249
  export const GenAIChatConversations = connect(mapStateToProps, mapDispatchToProps)(GenAIChatConversationsComponent);
103
250
  function getConversationGroupLabel(group, intl) {
104
251
  switch (group) {
252
+ case ConversationDateGroup.PINNED:
253
+ return intl.formatMessage({ id: "gd.gen-ai.conversations.group.pinned" });
105
254
  case ConversationDateGroup.TODAY:
106
255
  return intl.formatMessage({ id: "gd.gen-ai.conversations.group.today" });
107
256
  case ConversationDateGroup.LAST_7_DAYS:
@@ -11,7 +11,7 @@ import { settingsSelector } from "../store/chatWindow/chatWindowSelectors.js";
11
11
  import { setAllowedRelationshipTypesAction } from "../store/chatWindow/chatWindowSlice.js";
12
12
  import { asyncProcessSelector } from "../store/messages/messagesSelectors.js";
13
13
  import { cancelAsyncAction, clearThreadAction, loadThreadAction } from "../store/messages/messagesSlice.js";
14
- import { getAbsoluteSettingHref, getAbsoluteWorkspaceSettingHref, getSettingHref, getWorkspaceSettingHref, } from "../utils.js";
14
+ import { getAbsoluteSettingHref, getAbsoluteShellAppOrgSettingHref, getAbsoluteShellAppWorkspaceSettingHref, getAbsoluteWorkspaceSettingHref, getSettingHref, getShellAppOrgSettingHref, getShellAppWorkspaceSettingHref, getWorkspaceSettingHref, } from "../utils.js";
15
15
  import { useConfig } from "./ConfigContext.js";
16
16
  import { useCustomization } from "./CustomizationProvider.js";
17
17
  import { ErrorBoundary } from "./ErrorBoundary.js";
@@ -49,11 +49,18 @@ export function GenAIChatWrapperComponent({ loadThread, clearThread, cancelLoadi
49
49
  navigationId: GEN_AI_INPUT_ANCHOR_ID,
50
50
  tabIndex: -1,
51
51
  });
52
+ // When the shell host is enabled, the standalone home-ui at `/settings` or
53
+ // `/workspaces/{id}/settings` is bounced to the host base path, dropping the
54
+ // workspaceId and hash. Switch to host-shaped URLs so the deep-link
55
+ // (e.g. `#/ai/change-llm-model`) reaches the settings page intact.
56
+ const useShellAppUrls = Boolean(settings?.enableShellApplication);
52
57
  const onSettingClick = useCallback((type) => (e) => {
53
58
  switch (type) {
54
59
  case "change-model":
55
60
  if (allowNativeLinks) {
56
- window.location.href = getAbsoluteWorkspaceSettingHref(workspaceId, GEN_AI_SECTION, CHANGE_LLM_MODEL_ACTION);
61
+ window.location.href = useShellAppUrls
62
+ ? getAbsoluteShellAppWorkspaceSettingHref(workspaceId, GEN_AI_SECTION, CHANGE_LLM_MODEL_ACTION)
63
+ : getAbsoluteWorkspaceSettingHref(workspaceId, GEN_AI_SECTION, CHANGE_LLM_MODEL_ACTION);
57
64
  }
58
65
  else {
59
66
  linkHandler?.({
@@ -63,14 +70,18 @@ export function GenAIChatWrapperComponent({ loadThread, clearThread, cancelLoadi
63
70
  newTab: e.metaKey,
64
71
  section: GEN_AI_SECTION,
65
72
  preventDefault: e.preventDefault.bind(e),
66
- itemUrl: getWorkspaceSettingHref(workspaceId, GEN_AI_SECTION, CHANGE_LLM_MODEL_ACTION),
73
+ itemUrl: useShellAppUrls
74
+ ? getShellAppWorkspaceSettingHref(workspaceId, GEN_AI_SECTION, CHANGE_LLM_MODEL_ACTION)
75
+ : getWorkspaceSettingHref(workspaceId, GEN_AI_SECTION, CHANGE_LLM_MODEL_ACTION),
67
76
  });
68
77
  e.stopPropagation();
69
78
  }
70
79
  break;
71
80
  case "create":
72
81
  if (allowNativeLinks) {
73
- window.location.href = getAbsoluteSettingHref(GEN_AI_SECTION, CREATE_LLM_PROVIDER_ACTION);
82
+ window.location.href = useShellAppUrls
83
+ ? getAbsoluteShellAppOrgSettingHref(GEN_AI_SECTION, CREATE_LLM_PROVIDER_ACTION)
84
+ : getAbsoluteSettingHref(GEN_AI_SECTION, CREATE_LLM_PROVIDER_ACTION);
74
85
  }
75
86
  else {
76
87
  linkHandler?.({
@@ -80,13 +91,15 @@ export function GenAIChatWrapperComponent({ loadThread, clearThread, cancelLoadi
80
91
  newTab: e.metaKey,
81
92
  section: GEN_AI_SECTION,
82
93
  preventDefault: e.preventDefault.bind(e),
83
- itemUrl: getSettingHref(GEN_AI_SECTION, CREATE_LLM_PROVIDER_ACTION),
94
+ itemUrl: useShellAppUrls
95
+ ? getShellAppOrgSettingHref(GEN_AI_SECTION, CREATE_LLM_PROVIDER_ACTION)
96
+ : getSettingHref(GEN_AI_SECTION, CREATE_LLM_PROVIDER_ACTION),
84
97
  });
85
98
  e.stopPropagation();
86
99
  }
87
100
  break;
88
101
  }
89
- }, [allowNativeLinks, linkHandler, workspaceId]);
102
+ }, [allowNativeLinks, linkHandler, workspaceId, useShellAppUrls]);
90
103
  if (evaluated && hasUnsupportedOpenAiModel) {
91
104
  return (_jsx(GlobalError, { errorMessage: intl.formatMessage({ id: "gd.gen-ai.global-unsupported-model" }), errorDescription: intl.formatMessage({
92
105
  id: "gd.gen-ai.global-unsupported-model.description",
@@ -90,14 +90,19 @@ function InputComponent({ isBusy, isEvaluating, newMessage, autofocus = false, c
90
90
  setValue("");
91
91
  };
92
92
  const handleKeyDown = (e) => {
93
- if (e.key === "Enter" && !e.shiftKey && !isBusy && value) {
93
+ if (e.key === "Enter" && !e.shiftKey) {
94
+ const trimmed = value.trim();
95
+ if (isBusy || !trimmed) {
96
+ return true;
97
+ }
94
98
  e.preventDefault();
99
+ e.stopPropagation();
95
100
  handleSubmit();
96
101
  return true;
97
102
  }
98
103
  return false;
99
104
  };
100
- const buttonDisabled = !value || isBusy || isEvaluating;
105
+ const buttonDisabled = !value.trim() || isBusy || isEvaluating;
101
106
  const buttonClasses = cx("gd-gen-ai-chat__input__send_button", {
102
107
  "gd-gen-ai-chat__input__send_button--disabled": buttonDisabled,
103
108
  });
@@ -111,7 +116,7 @@ function InputComponent({ isBusy, isEvaluating, newMessage, autofocus = false, c
111
116
  }), children: _jsxs("div", { className: "gd-gen-ai-chat__input__content", children: [
112
117
  _jsx("div", { ref: targetRef, onFocus: () => {
113
118
  editorApi?.focus();
114
- } }), _jsx(HintText, { focused: focused, where: "top" }), _jsx(SyntaxHighlightingInput, { className: "gd-gen-ai-chat__input__mc", placeholder: intl.formatMessage(msgs.placeholder), label: isMac ? intl.formatMessage(msgs.labelMac) : intl.formatMessage(msgs.labelWin), value: value, disabled: isBusy, autocompletion: {
119
+ } }), _jsx(HintText, { focused: focused, where: "top" }), _jsx(SyntaxHighlightingInput, { className: "gd-gen-ai-chat__input__mc", placeholder: intl.formatMessage(msgs.placeholder), label: isMac ? intl.formatMessage(msgs.labelMac) : intl.formatMessage(msgs.labelWin), value: value, autocompletion: {
115
120
  aboveCursor: true,
116
121
  whenTyping: true,
117
122
  whenTypingDelay: 300,
@@ -27,11 +27,7 @@ function ConversationVisualizationContentCore({ message, part, scenario, visuali
27
27
  const [visError, setVisError] = useState(null);
28
28
  const [isTable, setIsTable] = useState(false);
29
29
  const moreButtonDescId = useId();
30
- const { setDrillState, DrillOverlay, DrillChooser } = useDrillState({
31
- containerRef,
32
- filters: [],
33
- setKeyDriverAnalysis,
34
- });
30
+ const [drillState, setDrillState] = useState(null);
35
31
  const { setSaveDialogOpen, SaveDialog } = useSaveDialog({
36
32
  message,
37
33
  part,
@@ -55,8 +51,8 @@ function ConversationVisualizationContentCore({ message, part, scenario, visuali
55
51
  }, [visError]);
56
52
  const classNames = cx("gd-gen-ai-chat__conversation__item__content", "gd-gen-ai-chat__conversation__item__content--visualization", className);
57
53
  return (_jsxs("div", { className: classNames, children: [
58
- _jsx(DrillChooser, { children: _jsxs(Wrapper, { containerRef: containerRef, visualization: visualization, children: [
59
- _jsx(VisualisationMenu, { visualization: visualization, scenario: scenario, isVisualisationSaved: visualisationSaved, isVisualisationCheckLoading: visualisationCheckLoading, isTable: isTable, onTable: setIsTable, hasError: hasVisFatal, moreButtonId: moreButtonDescId, onSave: onSave, onOpen: onOpen, onCopy: onCopy, isLoading: part.reporting ?? false }), _jsx(Title, { id: moreButtonDescId, visualization: visualization, scenario: scenario, useMarkdown: useMarkdown }), _jsx(VisualisationWrapper, { message: message, colorPalette: colorPalette, isTable: isTable, visualization: visualization, agGridToken: agGridToken, execConfig: scenario?.execConfig, enableChangeAnalysis: enableChangeAnalysis, enableNewPivotTable: enableNewPivotTable, enableAccessibleChartTooltip: enableAccessibleChartTooltip, enableDrilling: !scenario || scenario.isBaseline, onDrillFired: setDrillState, onVisualisationError: onVisualizationError }), _jsx(SaveDialog, {}), _jsx(DrillOverlay, {})
54
+ _jsx(DrillState, { drillState: drillState, setDrillState: setDrillState, containerRef: containerRef, setKeyDriverAnalysis: setKeyDriverAnalysis, children: _jsxs(Wrapper, { containerRef: containerRef, visualization: visualization, children: [
55
+ _jsx(VisualisationMenu, { visualization: visualization, scenario: scenario, isVisualisationSaved: visualisationSaved, isVisualisationCheckLoading: visualisationCheckLoading, isTable: isTable, onTable: setIsTable, hasError: hasVisFatal, moreButtonId: moreButtonDescId, onSave: onSave, onOpen: onOpen, onCopy: onCopy, isLoading: part.reporting ?? false }), _jsx(Title, { id: moreButtonDescId, visualization: visualization, scenario: scenario, useMarkdown: useMarkdown }), _jsx(VisualisationWrapper, { message: message, colorPalette: colorPalette, isTable: isTable, visualization: visualization, agGridToken: agGridToken, execConfig: scenario?.execConfig, enableChangeAnalysis: enableChangeAnalysis, enableNewPivotTable: enableNewPivotTable, enableAccessibleChartTooltip: enableAccessibleChartTooltip, enableDrilling: !scenario || scenario.isBaseline, onDrillFired: setDrillState, onVisualisationError: onVisualizationError }), _jsx(SaveDialog, {})
60
56
  ] }) }), _jsx(VisualizationErrorReport, { error: visError })
61
57
  ] }));
62
58
  }
@@ -214,48 +210,38 @@ function VisualizationErrorReport({ error }) {
214
210
  error: error.message,
215
211
  } }) })) : null] }));
216
212
  }
217
- function useDrillState({ containerRef, filters, setKeyDriverAnalysis }) {
213
+ function DrillState({ containerRef, filters, setKeyDriverAnalysis, children, drillState, setDrillState, }) {
218
214
  const intl = useIntl();
219
- const [drillState, setDrillState] = useState(null);
220
215
  const catalogItems = useSelector(catalogItemsSelector);
221
- const DrillChooser = useCallback(({ children }) => {
222
- return (_jsx(Dropdown, { enableAutoToggle: false, isOpen: Boolean(drillState), onToggle: (state) => {
223
- if (!state) {
216
+ return (_jsx(Dropdown, { enableAutoToggle: false, isOpen: Boolean(drillState), onToggle: (state) => {
217
+ if (!state) {
218
+ setDrillState(null);
219
+ }
220
+ }, closeOnEscape: true, closeOnParentScroll: true, alignPoints: [
221
+ {
222
+ align: "tl tl",
223
+ offset: calculateOffset(containerRef.current, drillState?.event),
224
+ },
225
+ {
226
+ align: "tl tr",
227
+ offset: calculateOffset(containerRef.current, drillState?.event),
228
+ },
229
+ ], renderBody: () => {
230
+ return (_jsx(DrillSelectDropdownMenu, { drillState: drillState, onSelect: (item) => {
231
+ const data = item.data.context;
232
+ const event = drillState?.event;
233
+ if (!event) {
234
+ return;
235
+ }
236
+ const allFilters = mergeFilters(convertIntersectionToAttributeFilters(event.drillContext.intersection ?? []), (filters?.map(getDashboardAttributeFilter).filter(Boolean) ??
237
+ []));
238
+ const definition = createKdaDefinitionFromDrill(catalogItems, intl.locale, data, event, allFilters);
239
+ setKeyDriverAnalysis?.({ keyDriverAnalysis: definition });
240
+ }, onClose: () => {
224
241
  setDrillState(null);
225
- }
226
- }, closeOnEscape: true, closeOnParentScroll: true, alignPoints: [
227
- {
228
- align: "tl tl",
229
- offset: calculateOffset(containerRef.current, drillState?.event),
230
- },
231
- {
232
- align: "tl tr",
233
- offset: calculateOffset(containerRef.current, drillState?.event),
234
- },
235
- ], renderBody: () => {
236
- return (_jsx(DrillSelectDropdownMenu, { drillState: drillState, onSelect: (item) => {
237
- const data = item.data.context;
238
- const event = drillState?.event;
239
- if (!event) {
240
- return;
241
- }
242
- const allFilters = mergeFilters(convertIntersectionToAttributeFilters(event.drillContext.intersection ?? []), filters
243
- .map(getDashboardAttributeFilter)
244
- .filter(Boolean));
245
- const definition = createKdaDefinitionFromDrill(catalogItems, intl.locale, data, event, allFilters);
246
- setKeyDriverAnalysis?.({ keyDriverAnalysis: definition });
247
- }, onClose: () => {
248
- setDrillState(null);
249
- } }));
250
- }, renderButton: () => _jsx(_Fragment, { children: children }) }));
251
- }, [catalogItems, containerRef, drillState, filters, intl.locale, setKeyDriverAnalysis]);
252
- const DrillOverlay = useCallback(() => (_jsx(_Fragment, { children: drillState ? (_jsx("div", { className: "gd-gen-ai-chat__conversation__visualization__drill_overlay" })) : null })), [drillState]);
253
- return {
254
- DrillOverlay,
255
- DrillChooser,
256
- setDrillState,
257
- drillState,
258
- };
242
+ } }));
243
+ }, renderButton: () => (_jsxs(_Fragment, { children: [children, _jsx(_Fragment, { children: drillState ? (_jsx("div", { className: "gd-gen-ai-chat__conversation__visualization__drill_overlay" })) : null })
244
+ ] })) }));
259
245
  }
260
246
  function calculateOffset(container, drillEvent) {
261
247
  const offest = getRelativeOffset(drillEvent?.target, container);
@@ -1,5 +1,6 @@
1
1
  import { type IChatConversationLocal } from "../../model.js";
2
2
  export declare enum ConversationDateGroup {
3
+ PINNED = "PINNED",
3
4
  TODAY = "TODAY",
4
5
  LAST_7_DAYS = "LAST_7_DAYS",
5
6
  OLDER = "OLDER"
@@ -2,11 +2,13 @@
2
2
  export { ConversationDateGroup };
3
3
  var ConversationDateGroup;
4
4
  (function (ConversationDateGroup) {
5
+ ConversationDateGroup["PINNED"] = "PINNED";
5
6
  ConversationDateGroup["TODAY"] = "TODAY";
6
7
  ConversationDateGroup["LAST_7_DAYS"] = "LAST_7_DAYS";
7
8
  ConversationDateGroup["OLDER"] = "OLDER";
8
9
  })(ConversationDateGroup || (ConversationDateGroup = {}));
9
10
  export const DEFAULT_CONVERSATION_DATE_GROUPS = [
11
+ { group: ConversationDateGroup.PINNED, maxAgeDays: Number.POSITIVE_INFINITY },
10
12
  { group: ConversationDateGroup.TODAY, maxAgeDays: 0 },
11
13
  { group: ConversationDateGroup.LAST_7_DAYS, maxAgeDays: 6 },
12
14
  { group: ConversationDateGroup.OLDER, maxAgeDays: Number.POSITIVE_INFINITY },
@@ -20,9 +22,14 @@ export function groupConversationsByDate(conversations = [], groups = DEFAULT_CO
20
22
  .slice()
21
23
  .sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime());
22
24
  sorted.forEach((conversation) => {
25
+ if (conversation.pinned) {
26
+ conversationGroups.get(ConversationDateGroup.PINNED)?.push(conversation);
27
+ return;
28
+ }
23
29
  const updatedAt = new Date(conversation.updatedAt);
24
30
  const ageInDays = getAgeInDays(updatedAt, now);
25
31
  const targetGroup = groups
32
+ .filter((group) => group.group !== ConversationDateGroup.PINNED)
26
33
  .filter((group) => ageInDays <= group.maxAgeDays)
27
34
  .sort((a, b) => a.maxAgeDays - b.maxAgeDays)[0]?.group ?? groups[groups.length - 1].group;
28
35
  conversationGroups.get(targetGroup)?.push(conversation);
package/esm/index.d.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  */
6
6
  export { GenAIChat, GenAIAssistant, type GenAIChatProps, type GenAIAssistantProps, } from "./components/GenAIChat.js";
7
7
  export { makeUserMessage, makeTextContents, makeUserItem, makeAssistantItem, type Message, type BaseMessage, type UserMessage, type AssistantMessage, type Contents, type TextContents, type SearchContents, type SemanticSearchContents, type RoutingContents, type ReasoningContents, type ReasoningStep, type ReasoningThought, type VisualizationContents, type ChangeAnalysisContents, type ErrorContents, type TextContentObject, type IChatConversationLocalContent, type IChatConversationLocalItem, type IChatConversationErrorContent, type IChatConversationMultipartLocalPart, } from "./model.js";
8
- export { type ChatEventHandler, type BaseEvent, type ChatAssistantMessageEvent, type ChatUserMessageEvent, type ChatClosedEvent, type ChatOpenedEvent, type ChatResetEvent, type ChatFeedbackEvent, type ChatFeedbackErrorEvent, type ChatEvent, type ChatVisualizationErrorEvent, type ChatSaveVisualizationErrorEvent, type ChatSaveVisualizationSuccessEvent, type ChatCopyToClipboardEvent, isChatAssistantMessageEvent, isChatUserMessageEvent, isChatClosedEvent, isChatOpenedEvent, isChatResetEvent, isChatFeedbackEvent, isChatFeedbackErrorEvent, isChatVisualizationErrorEvent, isChatSaveVisualizationErrorEvent, isChatSaveVisualizationSuccessEvent, isChatCopyToClipboardEvent, } from "./store/events.js";
8
+ export { type ChatEventHandler, type BaseEvent, type ChatAssistantMessageEvent, type ChatUserMessageEvent, type ChatClosedEvent, type ChatOpenedEvent, type ChatResetEvent, type ChatFeedbackEvent, type ChatFeedbackErrorEvent, type ChatEvent, type ChatVisualizationErrorEvent, type ChatSaveVisualizationErrorEvent, type ChatSaveVisualizationSuccessEvent, type ChatCopyToClipboardEvent, type ChatConversationPinnedEvent, type ChatConversationDeletedEvent, type ChatConversationPinErrorEvent, type ChatConversationDeletedErrorEvent, isChatAssistantMessageEvent, isChatUserMessageEvent, isChatClosedEvent, isChatOpenedEvent, isChatResetEvent, isChatFeedbackEvent, isChatFeedbackErrorEvent, isChatVisualizationErrorEvent, isChatSaveVisualizationErrorEvent, isChatSaveVisualizationSuccessEvent, isChatCopyToClipboardEvent, isChatConversationDeletedEvent, isChatConversationPinnedEvent, isChatConversationPinErrorEvent, isChatConversationDeletedErrorEvent, } from "./store/events.js";
9
9
  export { clearThreadAction, newMessageAction } from "./store/messages/messagesSlice.js";
10
10
  export { type LinkHandlerEvent } from "./components/ConfigContext.js";
11
11
  export { useGenAiChatAvailability } from "./hooks/useGenAiChatAvailability.js";