@gooddata/sdk-ui-gen-ai 11.35.0-alpha.6 → 11.35.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 (79) hide show
  1. package/esm/components/GenAIChatConversations.js +12 -9
  2. package/esm/components/Input.js +8 -3
  3. package/esm/localization/bundles/de-DE.localization-bundle.d.ts +6 -1
  4. package/esm/localization/bundles/de-DE.localization-bundle.js +6 -1
  5. package/esm/localization/bundles/en-AU.localization-bundle.d.ts +6 -1
  6. package/esm/localization/bundles/en-AU.localization-bundle.js +6 -1
  7. package/esm/localization/bundles/en-GB.localization-bundle.d.ts +6 -1
  8. package/esm/localization/bundles/en-GB.localization-bundle.js +6 -1
  9. package/esm/localization/bundles/es-419.localization-bundle.d.ts +6 -1
  10. package/esm/localization/bundles/es-419.localization-bundle.js +6 -1
  11. package/esm/localization/bundles/es-ES.localization-bundle.d.ts +6 -1
  12. package/esm/localization/bundles/es-ES.localization-bundle.js +6 -1
  13. package/esm/localization/bundles/fi-FI.localization-bundle.d.ts +6 -1
  14. package/esm/localization/bundles/fi-FI.localization-bundle.js +6 -1
  15. package/esm/localization/bundles/fr-CA.localization-bundle.d.ts +6 -1
  16. package/esm/localization/bundles/fr-CA.localization-bundle.js +6 -1
  17. package/esm/localization/bundles/fr-FR.localization-bundle.d.ts +6 -1
  18. package/esm/localization/bundles/fr-FR.localization-bundle.js +6 -1
  19. package/esm/localization/bundles/id-ID.localization-bundle.d.ts +6 -1
  20. package/esm/localization/bundles/id-ID.localization-bundle.js +6 -1
  21. package/esm/localization/bundles/it-IT.localization-bundle.d.ts +6 -1
  22. package/esm/localization/bundles/it-IT.localization-bundle.js +6 -1
  23. package/esm/localization/bundles/ja-JP.localization-bundle.d.ts +6 -1
  24. package/esm/localization/bundles/ja-JP.localization-bundle.js +6 -1
  25. package/esm/localization/bundles/ko-KR.localization-bundle.d.ts +6 -1
  26. package/esm/localization/bundles/ko-KR.localization-bundle.js +6 -1
  27. package/esm/localization/bundles/nl-NL.localization-bundle.d.ts +6 -1
  28. package/esm/localization/bundles/nl-NL.localization-bundle.js +6 -1
  29. package/esm/localization/bundles/pl-PL.localization-bundle.d.ts +6 -1
  30. package/esm/localization/bundles/pl-PL.localization-bundle.js +6 -1
  31. package/esm/localization/bundles/pt-BR.localization-bundle.d.ts +6 -1
  32. package/esm/localization/bundles/pt-BR.localization-bundle.js +6 -1
  33. package/esm/localization/bundles/pt-PT.localization-bundle.d.ts +6 -1
  34. package/esm/localization/bundles/pt-PT.localization-bundle.js +6 -1
  35. package/esm/localization/bundles/ru-RU.localization-bundle.d.ts +6 -1
  36. package/esm/localization/bundles/ru-RU.localization-bundle.js +6 -1
  37. package/esm/localization/bundles/sl-SI.localization-bundle.d.ts +6 -1
  38. package/esm/localization/bundles/sl-SI.localization-bundle.js +6 -1
  39. package/esm/localization/bundles/th-TH.localization-bundle.d.ts +6 -1
  40. package/esm/localization/bundles/th-TH.localization-bundle.js +6 -1
  41. package/esm/localization/bundles/tr-TR.localization-bundle.d.ts +6 -1
  42. package/esm/localization/bundles/tr-TR.localization-bundle.js +6 -1
  43. package/esm/localization/bundles/uk-UA.localization-bundle.d.ts +6 -1
  44. package/esm/localization/bundles/uk-UA.localization-bundle.js +6 -1
  45. package/esm/localization/bundles/vi-VN.localization-bundle.d.ts +6 -1
  46. package/esm/localization/bundles/vi-VN.localization-bundle.js +6 -1
  47. package/esm/localization/bundles/zh-HK.localization-bundle.d.ts +6 -1
  48. package/esm/localization/bundles/zh-HK.localization-bundle.js +6 -1
  49. package/esm/localization/bundles/zh-Hans.localization-bundle.d.ts +6 -1
  50. package/esm/localization/bundles/zh-Hans.localization-bundle.js +6 -1
  51. package/esm/localization/bundles/zh-Hant.localization-bundle.d.ts +6 -1
  52. package/esm/localization/bundles/zh-Hant.localization-bundle.js +6 -1
  53. package/esm/model.d.ts +2 -0
  54. package/esm/store/messages/messagesSelectors.d.ts +3 -3
  55. package/esm/store/messages/messagesSelectors.js +26 -4
  56. package/esm/store/messages/messagesSlice.d.ts +24 -18
  57. package/esm/store/messages/messagesSlice.js +186 -99
  58. package/esm/store/sideEffects/index.js +1 -1
  59. package/esm/store/sideEffects/onConversationDelete.d.ts +2 -0
  60. package/esm/store/sideEffects/onThreadLoad.js +15 -5
  61. package/esm/store/sideEffects/onUserFeedback.d.ts +3 -0
  62. package/esm/store/sideEffects/onUserFeedback.js +2 -1
  63. package/esm/store/sideEffects/onUserMessage.d.ts +9 -2
  64. package/esm/store/sideEffects/onUserMessage.js +42 -22
  65. package/esm/store/sideEffects/onUserMessageUpdate.d.ts +5 -3
  66. package/esm/store/sideEffects/onUserMessageUpdate.js +5 -2
  67. package/esm/store/sideEffects/onVisualisationRender.d.ts +2 -0
  68. package/esm/store/sideEffects/onVisualizationSave.d.ts +4 -0
  69. package/esm/store/sideEffects/onVisualizationSave.js +2 -0
  70. package/esm/store/sideEffects/onVisualizationSuccessSave.d.ts +2 -0
  71. package/esm/store/utils.d.ts +6 -0
  72. package/esm/store/utils.js +30 -0
  73. package/esm/types.d.ts +26 -0
  74. package/package.json +20 -20
  75. package/styles/css/conversations.css +16 -9
  76. package/styles/css/conversations.css.map +1 -1
  77. package/styles/css/main.css +16 -9
  78. package/styles/css/main.css.map +1 -1
  79. package/styles/scss/conversations.scss +20 -9
@@ -14,9 +14,12 @@ export declare function onUserFeedback({ payload }: PayloadAction<{
14
14
  }>): Generator<import("redux-saga/effects").GetContextEffect | Promise<void> | import("redux-saga/effects").PutEffect<{
15
15
  payload: {
16
16
  assistantMessageId: string;
17
+ conversationId?: string | undefined;
17
18
  error: string;
18
19
  };
19
20
  type: "messages/setUserFeedbackError";
20
21
  }> | import("redux-saga/effects").SelectEffect, void, IAnalyticalBackend & string & IUserWorkspaceSettings & import("@gooddata/sdk-backend-spi").IChatConversation & {
22
+ localId: string;
21
23
  generatingTitle?: boolean | undefined;
24
+ inProgress?: boolean | undefined;
22
25
  } & IChatConversationLocalItem[] & Message[]>;
@@ -15,8 +15,8 @@ export function* onUserFeedback({ payload, }) {
15
15
  const workspace = yield getContext("workspace");
16
16
  const settings = yield select(settingsSelector);
17
17
  if (settings?.enableAiAgenticConversations) {
18
+ const conversation = yield select(conversationSelector);
18
19
  try {
19
- const conversation = yield select(conversationSelector);
20
20
  const messages = yield select(conversationMessagesSelector);
21
21
  const message = messages.find((message) => message.localId === payload.assistantMessageId);
22
22
  if (!message?.id) {
@@ -35,6 +35,7 @@ export function* onUserFeedback({ payload, }) {
35
35
  yield put(setUserFeedbackError({
36
36
  assistantMessageId: payload.assistantMessageId,
37
37
  error: extractError(e),
38
+ conversationId: conversation.localId,
38
39
  }));
39
40
  }
40
41
  }
@@ -1,3 +1,4 @@
1
+ import { type IChatConversation } from "@gooddata/sdk-backend-spi";
1
2
  import { type AssistantMessage, type IChatConversationLocal, type IChatConversationLocalItem } from "../../model.js";
2
3
  import { type newMessageAction } from "../messages/messagesSlice.js";
3
4
  /**
@@ -9,35 +10,41 @@ export declare function onUserMessage({ payload }: ReturnType<typeof newMessageA
9
10
  }> | import("redux-saga/effects").CancelledEffect | import("redux-saga/effects").GetContextEffect | import("redux-saga/effects").PutEffect<{
10
11
  payload: {
11
12
  message: AssistantMessage | IChatConversationLocalItem;
13
+ conversationId?: string | undefined;
12
14
  };
13
15
  type: "messages/evaluateMessageAction";
14
16
  }> | import("redux-saga/effects").PutEffect<{
15
17
  payload: {
16
18
  error: string;
17
19
  assistantMessageId: string;
20
+ conversationId?: string | undefined;
18
21
  };
19
22
  type: "messages/evaluateMessageErrorAction";
20
23
  }> | import("redux-saga/effects").PutEffect<{
21
24
  payload: {
22
25
  assistantMessageId: string;
26
+ conversationId?: string | undefined;
23
27
  };
24
28
  type: "messages/evaluateMessageCompleteAction";
25
- }> | import("redux-saga/effects").SelectEffect, void, never> | Generator<import("redux-saga/effects").CallEffect<import("@gooddata/sdk-backend-spi").IChatConversation> | import("redux-saga/effects").CallEffect<{
29
+ }> | import("redux-saga/effects").SelectEffect, void, never> | Generator<import("redux-saga/effects").CallEffect<IChatConversation> | import("redux-saga/effects").CallEffect<{
26
30
  lastAssistantMessage: IChatConversationLocalItem;
27
31
  }> | import("redux-saga/effects").CancelledEffect | import("redux-saga/effects").GetContextEffect | import("redux-saga/effects").PutEffect<{
28
32
  payload: {
29
33
  message: AssistantMessage | IChatConversationLocalItem;
34
+ conversationId?: string | undefined;
30
35
  };
31
36
  type: "messages/evaluateMessageAction";
32
37
  }> | import("redux-saga/effects").PutEffect<{
33
38
  payload: {
34
39
  error: string;
35
40
  assistantMessageId: string;
41
+ conversationId?: string | undefined;
36
42
  };
37
43
  type: "messages/evaluateMessageErrorAction";
38
44
  }> | import("redux-saga/effects").PutEffect<{
39
45
  payload: {
40
46
  assistantMessageId: string;
47
+ conversationId?: string | undefined;
41
48
  };
42
49
  type: "messages/evaluateMessageCompleteAction";
43
50
  }> | import("redux-saga/effects").PutEffect<{
@@ -45,4 +52,4 @@ export declare function onUserMessage({ payload }: ReturnType<typeof newMessageA
45
52
  conversation: IChatConversationLocal;
46
53
  };
47
54
  type: "messages/setCurrentConversationAction";
48
- }> | import("redux-saga/effects").SelectEffect, void, never> | import("redux-saga/effects").SelectEffect, void, "new" | IChatConversationLocal | undefined>;
55
+ }> | import("redux-saga/effects").SelectEffect, void, never> | import("redux-saga/effects").SelectEffect, void, IChatConversationLocal | undefined>;
@@ -5,7 +5,7 @@ import { isChatConversationLocalItem, isTextContents, isUserMessage, makeAssista
5
5
  import { generateTitleFromQuestion } from "../../utils.js";
6
6
  import { allowedRelationshipTypesSelector, objectTypesSelector, settingsSelector, userContextSelector, } from "../chatWindow/chatWindowSelectors.js";
7
7
  import { clearUserContextAction } from "../chatWindow/chatWindowSlice.js";
8
- import { conversationMessagesSelector, conversationSelector, messagesSelector, } from "../messages/messagesSelectors.js";
8
+ import { conversationMessagesByIdSelector, conversationSelector, messagesSelector, } from "../messages/messagesSelectors.js";
9
9
  import { evaluateMessageAction, evaluateMessageCompleteAction, evaluateMessageErrorAction, evaluateMessageStreamingAction, evaluateMessageSuggestionsAction, evaluateMessageUpdateAction, setCurrentConversationAction, } from "../messages/messagesSlice.js";
10
10
  import { processContents } from "./converters/interactionsToMessages.js";
11
11
  import { convertToLocalContent } from "./converters/toLocalContent.js";
@@ -174,6 +174,9 @@ function* evaluateUserMessage(message, preparedChatThread) {
174
174
  function* conversationUserMessage(message) {
175
175
  let initialAssistantMessage = undefined;
176
176
  let lastAssistantMessage = undefined;
177
+ // Check current conversation
178
+ const conversationState = yield select(conversationSelector);
179
+ let conversation = undefined;
177
180
  try {
178
181
  // Make sure the message is a user message and it got text contents
179
182
  if (message.role !== "user") {
@@ -186,32 +189,35 @@ function* conversationUserMessage(message) {
186
189
  const backend = yield getContext("backend");
187
190
  const workspace = yield getContext("workspace");
188
191
  const isPreview = yield getContext("isPreview");
189
- // Check current conversation
190
- const conversationState = yield select(conversationSelector);
191
192
  // Check state
192
- if (conversationState !== "new" && !conversationState?.id) {
193
+ if (!conversationState?.localId) {
193
194
  throw new Error("Conversation ID is not available.");
194
195
  }
195
196
  // Create assistant message
196
197
  initialAssistantMessage = makeAssistantItem();
197
- // Set evaluation state in store
198
- yield put(evaluateMessageAction({ message: initialAssistantMessage }));
199
- let conversation;
200
198
  // If we are in the transient new-conversation state, create the conversation first
201
- if (conversationState === "new") {
199
+ if (conversationState.id) {
200
+ conversation = conversationState;
201
+ }
202
+ else {
202
203
  const api = backend.workspace(workspace).genAI().getChatConversations({ isPreview });
203
204
  const created = yield call(api.create.bind(api));
204
205
  const updated = yield call(api.update.bind(api), created.id, {
205
206
  title: generateTitleFromQuestion(message.content.text),
206
207
  });
207
- // Store it as current conversation and clear the transient flag
208
- yield put(setCurrentConversationAction({ conversation: updated }));
209
208
  //save
210
- conversation = updated;
211
- }
212
- else {
213
- conversation = conversationState;
209
+ conversation = {
210
+ ...updated,
211
+ localId: conversationState.localId,
212
+ };
213
+ // Store it as current conversation and clear the transient flag
214
+ yield put(setCurrentConversationAction({ conversation }));
214
215
  }
216
+ // Set evaluation state in store
217
+ yield put(evaluateMessageAction({
218
+ message: initialAssistantMessage,
219
+ conversationId: conversation.localId,
220
+ }));
215
221
  // Make the request to start the evaluation
216
222
  const chatThreadQuery = backend
217
223
  .workspace(workspace)
@@ -221,7 +227,7 @@ function* conversationUserMessage(message) {
221
227
  .query(message.content.text);
222
228
  // evaluateUserMessage may create additional assistant messages if the stream contains
223
229
  // multiple interaction IDs. It returns the last message that needs to be completed.
224
- const result = yield call(evaluateUserConversationMessage, conversation, message, initialAssistantMessage, chatThreadQuery, conversationState === "new");
230
+ const result = yield call(evaluateUserConversationMessage, conversation, message, initialAssistantMessage, chatThreadQuery, !conversationState.id);
225
231
  lastAssistantMessage = result.lastAssistantMessage;
226
232
  }
227
233
  catch (e) {
@@ -232,6 +238,7 @@ function* conversationUserMessage(message) {
232
238
  yield put(evaluateMessageErrorAction({
233
239
  assistantMessageId: messageToError.localId,
234
240
  error: extractError(e),
241
+ conversationId: conversation?.localId,
235
242
  }));
236
243
  }
237
244
  }
@@ -242,11 +249,12 @@ function* conversationUserMessage(message) {
242
249
  if (messageToComplete && !wasCanceled) {
243
250
  // Check if the message still exists before marking it complete
244
251
  // (it may have been removed if the chat was cleared)
245
- const currentMessages = yield select(conversationMessagesSelector);
252
+ const currentMessages = yield select(conversationMessagesByIdSelector, conversation?.localId);
246
253
  const messageExists = currentMessages.some((m) => m.localId === messageToComplete.localId);
247
254
  if (messageExists) {
248
255
  yield put(evaluateMessageCompleteAction({
249
256
  assistantMessageId: messageToComplete.localId,
257
+ conversationId: conversation?.localId,
250
258
  }));
251
259
  }
252
260
  }
@@ -287,6 +295,7 @@ function* evaluateUserConversationMessage(conversation, userMessage, assistantMe
287
295
  if (value.role === "user" && currentUserMessage) {
288
296
  yield put(evaluateMessageUpdateAction({
289
297
  conversation,
298
+ conversationId: conversation.localId,
290
299
  userMessageId: currentUserMessage.localId,
291
300
  interactionId: value.id,
292
301
  message: value,
@@ -306,17 +315,21 @@ function* evaluateUserConversationMessage(conversation, userMessage, assistantMe
306
315
  chunkInteractionId !== currentInteractionId) {
307
316
  // Check if the current message still exists before marking it complete
308
317
  // (it may have been removed if the chat was cleared)
309
- const currentMessages = yield select(conversationMessagesSelector);
318
+ const currentMessages = yield select(conversationMessagesByIdSelector, conversation.localId);
310
319
  const messageExists = currentMessages.some((m) => m.localId === currentAssistantMessage.localId);
311
320
  if (messageExists) {
312
321
  // Mark current message as complete
313
322
  yield put(evaluateMessageCompleteAction({
314
323
  assistantMessageId: currentAssistantMessage.localId,
324
+ conversationId: conversation.localId,
315
325
  }));
316
326
  }
317
327
  // Create new assistant message for the new interaction
318
328
  currentAssistantMessage = makeAssistantItem();
319
- yield put(evaluateMessageAction({ message: currentAssistantMessage }));
329
+ yield put(evaluateMessageAction({
330
+ message: currentAssistantMessage,
331
+ conversationId: conversation.localId,
332
+ }));
320
333
  }
321
334
  // Track the current interaction ID
322
335
  if (chunkInteractionId) {
@@ -324,16 +337,18 @@ function* evaluateUserConversationMessage(conversation, userMessage, assistantMe
324
337
  }
325
338
  // Dispatch streaming content to current message
326
339
  yield put(evaluateMessageStreamingAction({
340
+ content: convertToLocalContent(value.content),
327
341
  assistantMessageId: currentAssistantMessage.localId,
342
+ conversationId: conversation.localId,
328
343
  interactionId: chunkInteractionId,
329
344
  item: value,
330
- content: convertToLocalContent(value.content),
331
345
  }));
332
346
  }
333
347
  if (isChatSuggestionsItem(value)) {
334
348
  // Dispatch suggestions content to current message
335
349
  yield put(evaluateMessageSuggestionsAction({
336
350
  assistantMessageId: currentAssistantMessage.localId,
351
+ conversationId: conversation.localId,
337
352
  item: value,
338
353
  }));
339
354
  }
@@ -345,17 +360,21 @@ function* evaluateUserConversationMessage(conversation, userMessage, assistantMe
345
360
  chunkInteractionId !== currentInteractionId) {
346
361
  // Check if the current message still exists before marking it complete
347
362
  // (it may have been removed if the chat was cleared)
348
- const currentMessages = yield select(conversationMessagesSelector);
363
+ const currentMessages = yield select(conversationMessagesByIdSelector, conversation.localId);
349
364
  const messageExists = currentMessages.some((m) => m.localId === currentAssistantMessage.localId);
350
365
  if (messageExists) {
351
366
  // Mark current message as complete
352
367
  yield put(evaluateMessageCompleteAction({
353
368
  assistantMessageId: currentAssistantMessage.localId,
369
+ conversationId: conversation.localId,
354
370
  }));
355
371
  }
356
372
  // Create new assistant message for the new interaction
357
373
  currentAssistantMessage = makeAssistantItem();
358
- yield put(evaluateMessageAction({ message: currentAssistantMessage }));
374
+ yield put(evaluateMessageAction({
375
+ message: currentAssistantMessage,
376
+ conversationId: conversation.localId,
377
+ }));
359
378
  }
360
379
  // Track the current interaction ID
361
380
  if (chunkInteractionId) {
@@ -366,6 +385,7 @@ function* evaluateUserConversationMessage(conversation, userMessage, assistantMe
366
385
  assistantMessageId: currentAssistantMessage.localId,
367
386
  interactionId: chunkInteractionId,
368
387
  content: value,
388
+ conversationId: conversation.localId,
369
389
  }));
370
390
  }
371
391
  }
@@ -381,7 +401,7 @@ function* evaluateUserConversationMessage(conversation, userMessage, assistantMe
381
401
  yield call([reader, reader.releaseLock]);
382
402
  }
383
403
  //Cancel saga
384
- const messages = yield select(conversationMessagesSelector);
404
+ const messages = yield select(conversationMessagesByIdSelector, conversation.localId);
385
405
  const found = messages.find((m) => m.localId === currentAssistantMessage.localId);
386
406
  if (!found) {
387
407
  yield cancel();
@@ -1,17 +1,19 @@
1
- import { type IAnalyticalBackend } from "@gooddata/sdk-backend-spi";
1
+ import { type IAnalyticalBackend, type IChatConversation } from "@gooddata/sdk-backend-spi";
2
2
  import { type IChatConversationLocal } from "../../model.js";
3
3
  import { type evaluateMessageUpdateAction } from "../messages/messagesSlice.js";
4
4
  /**
5
5
  * Load thread history and put it to the store.
6
6
  * @internal
7
7
  */
8
- export declare function onUserMessageUpdate({ payload }: ReturnType<typeof evaluateMessageUpdateAction>): Generator<import("redux-saga/effects").CallEffect<import("@gooddata/sdk-backend-spi").IChatConversation> | import("redux-saga/effects").GetContextEffect | import("redux-saga/effects").PutEffect<{
8
+ export declare function onUserMessageUpdate({ payload }: ReturnType<typeof evaluateMessageUpdateAction>): Generator<import("redux-saga/effects").CallEffect<IChatConversation> | import("redux-saga/effects").GetContextEffect | import("redux-saga/effects").PutEffect<{
9
9
  payload: {
10
10
  conversation: IChatConversationLocal;
11
11
  generatingTitle: boolean;
12
12
  title?: string | undefined;
13
13
  };
14
14
  type: "messages/evaluateConversationTitleAction";
15
- }> | import("redux-saga/effects").SelectEffect, void, IAnalyticalBackend & string & import("@gooddata/sdk-backend-spi").IChatConversation & {
15
+ }> | import("redux-saga/effects").SelectEffect, void, IAnalyticalBackend & string & IChatConversation & {
16
+ localId: string;
16
17
  generatingTitle?: boolean | undefined;
18
+ inProgress?: boolean | undefined;
17
19
  }>;
@@ -13,7 +13,7 @@ export function* onUserMessageUpdate({ payload }) {
13
13
  if (!payload.isStartMessage) {
14
14
  return;
15
15
  }
16
- const conversation = yield select(conversationByIdSelector, payload.conversation.id);
16
+ const conversation = yield select(conversationByIdSelector, payload.conversation.localId);
17
17
  if (!conversation) {
18
18
  return;
19
19
  }
@@ -28,7 +28,10 @@ export function* onUserMessageUpdate({ payload }) {
28
28
  const conversationsCall = backend.workspace(workspace).genAI().getChatConversations();
29
29
  const updated = yield call(conversationsCall.generateTitle.bind(conversationsCall), payload.conversation.id);
30
30
  yield put(evaluateConversationTitleAction({
31
- conversation: updated,
31
+ conversation: {
32
+ ...updated,
33
+ localId: conversation.localId,
34
+ },
32
35
  generatingTitle: false,
33
36
  title: updated.title,
34
37
  }));
@@ -18,5 +18,7 @@ export declare function onVisualisationRender({ payload }: PayloadAction<{
18
18
  };
19
19
  type: "messages/saveVisualisationRenderStatusSuccessAction";
20
20
  }> | import("redux-saga/effects").SelectEffect, void, IAnalyticalBackend & string & import("@gooddata/sdk-backend-spi").IChatConversation & {
21
+ localId: string;
21
22
  generatingTitle?: boolean | undefined;
23
+ inProgress?: boolean | undefined;
22
24
  } & Message[]>;
@@ -15,6 +15,7 @@ export declare function onVisualizationSave({ payload }: PayloadAction<{
15
15
  };
16
16
  visualizationId: string;
17
17
  assistantMessageId: string;
18
+ conversationId?: string | undefined;
18
19
  };
19
20
  type: "messages/saveVisualizationErrorAction";
20
21
  }> | import("redux-saga/effects").PutEffect<{
@@ -22,11 +23,14 @@ export declare function onVisualizationSave({ payload }: PayloadAction<{
22
23
  visualizationId: string;
23
24
  assistantMessageId: string;
24
25
  savedVisualizationId: string;
26
+ conversationId?: string | undefined;
25
27
  explore: boolean;
26
28
  };
27
29
  type: "messages/saveVisualizationSuccessAction";
28
30
  }> | import("redux-saga/effects").SelectEffect, void, IAnalyticalBackend & string & import("@gooddata/sdk-backend-spi").IChatConversation & {
31
+ localId: string;
29
32
  generatingTitle?: boolean | undefined;
33
+ inProgress?: boolean | undefined;
30
34
  } & IChatConversationLocalItem[] & IInsightDefinition & {
31
35
  insight: import("@gooddata/sdk-model").IAuditableDates & import("@gooddata/sdk-model").IAuditableUsers & {
32
36
  identifier: string;
@@ -46,6 +46,7 @@ export function* onVisualizationSave({ payload, }) {
46
46
  assistantMessageId: payload.assistantMessageId,
47
47
  savedVisualizationId: savedVisualization.insight.identifier,
48
48
  explore: payload.explore,
49
+ conversationId: conversation.localId,
49
50
  }));
50
51
  }
51
52
  else {
@@ -82,6 +83,7 @@ export function* onVisualizationSave({ payload, }) {
82
83
  },
83
84
  visualizationId: payload.visualizationId,
84
85
  assistantMessageId: payload.assistantMessageId,
86
+ conversationId: conversation?.localId,
85
87
  }));
86
88
  }
87
89
  }
@@ -7,5 +7,7 @@ export declare function onVisualizationSuccessSave({ payload }: PayloadAction<{
7
7
  savedVisualizationId: string;
8
8
  explore: boolean;
9
9
  }>): Generator<import("redux-saga/effects").CallEffect<void> | import("redux-saga/effects").GetContextEffect | import("redux-saga/effects").SelectEffect, void, IAnalyticalBackend & string & import("@gooddata/sdk-backend-spi").IChatConversation & {
10
+ localId: string;
10
11
  generatingTitle?: boolean | undefined;
12
+ inProgress?: boolean | undefined;
11
13
  } & Message[]>;
@@ -0,0 +1,6 @@
1
+ import type { IChatConversationLocal } from "../model.js";
2
+ import { type StoredConversation } from "../types.js";
3
+ export declare function isConversationWithLocalId(conv: IChatConversationLocal | undefined, localId?: string): conv is IChatConversationLocal;
4
+ export declare function getConversationLocalId(conv: IChatConversationLocal | undefined): string | undefined;
5
+ export declare function createEmptyConversation(): IChatConversationLocal;
6
+ export declare function getConversationData(data: Record<string, StoredConversation>, localId?: string): StoredConversation | undefined;
@@ -0,0 +1,30 @@
1
+ // (C) 2026 GoodData Corporation
2
+ import { v4 as uuid } from "uuid";
3
+ export function isConversationWithLocalId(conv, localId) {
4
+ return Boolean(conv && conv.localId === localId);
5
+ }
6
+ export function getConversationLocalId(conv) {
7
+ return conv?.localId;
8
+ }
9
+ export function createEmptyConversation() {
10
+ return {
11
+ id: "",
12
+ createdAt: new Date().toISOString(),
13
+ updatedAt: new Date().toISOString(),
14
+ title: "",
15
+ localId: uuid(),
16
+ };
17
+ }
18
+ export function getConversationData(data, localId) {
19
+ if (!localId) {
20
+ return undefined;
21
+ }
22
+ if (data[localId]) {
23
+ return data[localId];
24
+ }
25
+ data[localId] = {
26
+ order: [],
27
+ items: {},
28
+ };
29
+ return data[localId];
30
+ }
package/esm/types.d.ts CHANGED
@@ -1,3 +1,29 @@
1
1
  import { type IChatConversationVisualisationContent } from "@gooddata/sdk-backend-spi";
2
2
  import { type IGenAIVisualization } from "@gooddata/sdk-model";
3
+ import { type IChatConversationLocalItem } from "./model.js";
3
4
  export type Config = IGenAIVisualization["config"] | IChatConversationVisualisationContent["visualization"]["insight"]["properties"]["controls"];
5
+ export type StoredConversation = {
6
+ /**
7
+ * A record containing chat conversation items indexed by string keys.
8
+ *
9
+ * Each key in the record corresponds to a unique identifier for a chat conversation item.
10
+ * The value associated with each key is an object implementing the IChatConversationLocalItem interface.
11
+ *
12
+ * This structure is used to store and manage local chat conversation data in a key-value format.
13
+ */
14
+ items: Record<string, IChatConversationLocalItem>;
15
+ /**
16
+ * Represents an array of strings that specifies the order of elements or items.
17
+ * Typically used to define a sequence or arrangement.
18
+ */
19
+ order: string[];
20
+ /**
21
+ * If the interface is busy, this specifies the details of the async operation.
22
+ * Where:
23
+ * - loading: the thread history is being loaded from the backend (no messages to show yet)
24
+ * - restoring: cached messages have been restored while the backend fetch is still in-flight
25
+ * - clearing: the thread is being cleared
26
+ * - evaluating: the new user message is being evaluated by assistant
27
+ */
28
+ asyncProcess?: "loading" | "restoring" | "clearing" | "evaluating";
29
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gooddata/sdk-ui-gen-ai",
3
- "version": "11.35.0-alpha.6",
3
+ "version": "11.35.0",
4
4
  "description": "GoodData GenAI SDK",
5
5
  "license": "MIT",
6
6
  "author": "GoodData Corporation",
@@ -56,18 +56,18 @@
56
56
  "reselect": "5.1.1",
57
57
  "tslib": "2.8.1",
58
58
  "uuid": "11.1.0",
59
- "@gooddata/api-client-tiger": "11.35.0-alpha.6",
60
- "@gooddata/sdk-backend-spi": "11.35.0-alpha.6",
61
- "@gooddata/sdk-model": "11.35.0-alpha.6",
62
- "@gooddata/sdk-ui-charts": "11.35.0-alpha.6",
63
- "@gooddata/sdk-ui": "11.35.0-alpha.6",
64
- "@gooddata/sdk-ui-dashboard": "11.35.0-alpha.6",
65
- "@gooddata/sdk-ui-filters": "11.35.0-alpha.6",
66
- "@gooddata/sdk-ui-kit": "11.35.0-alpha.6",
67
- "@gooddata/sdk-ui-pivot": "11.35.0-alpha.6",
68
- "@gooddata/sdk-ui-semantic-search": "11.35.0-alpha.6",
69
- "@gooddata/sdk-ui-theme-provider": "11.35.0-alpha.6",
70
- "@gooddata/util": "11.35.0-alpha.6"
59
+ "@gooddata/api-client-tiger": "11.35.0",
60
+ "@gooddata/sdk-backend-spi": "11.35.0",
61
+ "@gooddata/sdk-model": "11.35.0",
62
+ "@gooddata/sdk-ui": "11.35.0",
63
+ "@gooddata/sdk-ui-charts": "11.35.0",
64
+ "@gooddata/sdk-ui-dashboard": "11.35.0",
65
+ "@gooddata/sdk-ui-filters": "11.35.0",
66
+ "@gooddata/sdk-ui-kit": "11.35.0",
67
+ "@gooddata/sdk-ui-pivot": "11.35.0",
68
+ "@gooddata/sdk-ui-theme-provider": "11.35.0",
69
+ "@gooddata/sdk-ui-semantic-search": "11.35.0",
70
+ "@gooddata/util": "11.35.0"
71
71
  },
72
72
  "devDependencies": {
73
73
  "@microsoft/api-documenter": "^7.17.0",
@@ -110,13 +110,13 @@
110
110
  "typescript": "5.9.3",
111
111
  "vitest": "4.1.0",
112
112
  "vitest-dom": "0.1.1",
113
- "@gooddata/eslint-config": "11.35.0-alpha.6",
114
- "@gooddata/oxlint-config": "11.35.0-alpha.6",
115
- "@gooddata/i18n-toolkit": "11.35.0-alpha.6",
116
- "@gooddata/reference-workspace": "11.35.0-alpha.6",
117
- "@gooddata/sdk-backend-mockingbird": "11.35.0-alpha.6",
118
- "@gooddata/sdk-ui-theme-provider": "11.35.0-alpha.6",
119
- "@gooddata/stylelint-config": "11.35.0-alpha.6"
113
+ "@gooddata/eslint-config": "11.35.0",
114
+ "@gooddata/i18n-toolkit": "11.35.0",
115
+ "@gooddata/oxlint-config": "11.35.0",
116
+ "@gooddata/reference-workspace": "11.35.0",
117
+ "@gooddata/sdk-ui-theme-provider": "11.35.0",
118
+ "@gooddata/sdk-backend-mockingbird": "11.35.0",
119
+ "@gooddata/stylelint-config": "11.35.0"
120
120
  },
121
121
  "peerDependencies": {
122
122
  "react": "^18.0.0 || ^19.0.0",
@@ -66,22 +66,30 @@
66
66
  .gd-gen-ai-chat__window__conversations__drop-placeholder {
67
67
  z-index: 1;
68
68
  position: absolute;
69
- display: none;
69
+ visibility: hidden;
70
70
  text-transform: uppercase;
71
71
  inset: 0;
72
- margin: 0 10px;
73
- padding: 4px 4px;
74
- border: 2px dashed var(--gd-palette-primary-base, #14b2e2);
75
- background-color: var(--gd-palette-primary-dimmed, #e8f7fc);
76
- border-radius: 5px;
77
72
  color: var(--gd-palette-complementary-8, #464e56);
78
73
  font-size: 12px;
79
74
  font-weight: 700;
80
75
  line-height: 14px;
81
76
  text-align: center;
77
+ margin: 30px 10px 0 10px;
78
+ padding: 4px 4px;
79
+ border: 2px dashed var(--gd-palette-primary-base, #14b2e2);
80
+ background-color: var(--gd-palette-primary-dimmed, #e8f7fc);
81
+ border-radius: 5px;
82
+ min-height: 30px;
83
+ }
84
+ .gd-gen-ai-chat__window__conversations__drop-placeholder.isEmpty {
85
+ margin: 5px 10px 0 10px;
86
+ overflow: hidden;
82
87
  }
83
88
  .gd-gen-ai-chat__window__conversations__drop-placeholder.isDragging, .gd-gen-ai-chat__window__conversations__drop-placeholder.isDraggingOver {
84
- display: block;
89
+ visibility: visible;
90
+ }
91
+ .gd-gen-ai-chat__window__conversations__drop-placeholder.isEmpty.isDragging, .gd-gen-ai-chat__window__conversations__drop-placeholder.isEmpty.isDraggingOver {
92
+ visibility: visible;
85
93
  }
86
94
  .gd-gen-ai-chat__window__conversations__drop-placeholder .gd-gen-ai-chat__window__conversations__drop-placeholder__content {
87
95
  width: 100%;
@@ -106,7 +114,6 @@
106
114
  margin-left: -10px;
107
115
  margin-right: -10px;
108
116
  align-items: stretch;
109
- gap: 10px;
110
117
  align-self: stretch;
111
118
  height: 100%;
112
119
  }
@@ -151,7 +158,7 @@
151
158
  background-color: var(--gd-palette-primary-dimmed, #e8f7fc);
152
159
  color: var(--gd-palette-primary-base, #14b2e2);
153
160
  }
154
- .gd-gen-ai-chat__window__conversations__list__item.generatingTitle .gd-ui-kit-menu__item-title {
161
+ .gd-gen-ai-chat__window__conversations__list__item.generatingTitle .gd-ui-kit-menu__item-title, .gd-gen-ai-chat__window__conversations__list__item.inProgress .gd-ui-kit-menu__item-title {
155
162
  background: linear-gradient(90deg, var(--gd-palette-complementary-7, #6d7680) 0%, var(--gd-palette-complementary-7, #6d7680) 40%, var(--gd-palette-complementary-0, #fff) 50%, var(--gd-palette-complementary-7, #6d7680) 60%, var(--gd-palette-complementary-7, #6d7680) 100%);
156
163
  background-size: 200% 100%;
157
164
  background-clip: text;
@@ -1 +1 @@
1
- {"version":3,"sourceRoot":"","sources":["../scss/variables.scss","../scss/conversations.scss","../../node_modules/@gooddata/sdk-ui-kit/styles/scss/variables.scss"],"names":[],"mappings":"AAuCA;EACI;IACI;;EAGJ;IACI;;;ACxCR;EACI;EACA;EACA;EACA;EAEA;EACA,OCwBY;EDvBZ;EACA;EACA;EACA;;;AAGJ;EAGI;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;;AAGJ;EAEI;EACA;;AAGJ;EACI;EACA;EACA,kBCGU;;ADAd;EACI;EACA;EACA;EACA,kBCJU;;ADOd;EACI;EACA;EACA;EACA;EACA;EACA;EACA;;AAGJ;EACI;EACA,OCnCe;EDoCf;EACA;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,kBC/BoB;EDgCpB;EACA,OC/CQ;EDgDR;EACA;EACA;EACA;;AAEA;EAEI;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA;;AAGJ;EACI,kBCzDc;ED0Dd,OClFK;;ADsFb;EACI;EACA;EACA;;AAGJ;EAGI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI;;AACA;EACI;;AAGJ;EACI;;AAGJ;EACI;EACA;;AAEJ;EACI;;AAEJ;EACI;EACA;EACA;;AAGJ;AAAA;EAEI;EACA;;AAGJ;EACI;;AAKJ;EACI;EACA;;AAEJ;EACI,kBCjDQ;EDkDR,OC9HA;;ADgIJ;EACI,kBCzHY;ED0HZ,OC7HU;;ADiIlB;ED9HJ;EAQA;EACA;EAEA;EACA;EACA;;ACqHI;EACI;;AAIR;EACI;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,OC5KY;ED6KZ;EACA;EACA;;AAEA;EAEI,OC9IY;ED+IZ,kBC7Ic","file":"conversations.css"}
1
+ {"version":3,"sourceRoot":"","sources":["../scss/variables.scss","../scss/conversations.scss","../../node_modules/@gooddata/sdk-ui-kit/styles/scss/variables.scss"],"names":[],"mappings":"AAuCA;EACI;IACI;;EAGJ;IACI;;;ACxCR;EACI;EACA;EACA;EACA;EAEA;EACA,OCwBY;EDvBZ;EACA;EACA;EACA;;;AAGJ;EAGI;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;;AAGJ;EAEI;EACA;;AAGJ;EACI;EACA;EACA,kBCGU;;ADAd;EACI;EACA;EACA;EACA,kBCJU;;ADOd;EACI;EACA;EACA;EACA;EACA;EACA;EACA;;AAGJ;EACI;EACA,OCnCe;EDoCf;EACA;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA,OC1CQ;ED2CR;EACA;EACA;EACA;EACA;EACA;EACA;EACA,kBCpCoB;EDqCpB;EACA;;AAEA;EACI;EACA;;AAGJ;EAEI;;AAGJ;EAEI;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA;;AAGJ;EACI,kBCpEc;EDqEd,OC7FK;;ADiGb;EACI;EACA;EACA;;AAGJ;EAGI;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI;;AACA;EACI;;AAGJ;EACI;;AAGJ;EACI;EACA;;AAEJ;EACI;;AAEJ;EACI;EACA;EACA;;AAGJ;AAAA;EAEI;EACA;;AAGJ;EACI;;AAKJ;EACI;EACA;;AAEJ;EACI,kBC3DQ;ED4DR,OCxIA;;AD0IJ;EACI,kBCnIY;EDoIZ,OCvIU;;AD2IlB;EDxIJ;EAQA;EACA;EAEA;EACA;EACA;;ACgII;EACI;;AAIR;EACI;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,OCvLY;EDwLZ;EACA;EACA;;AAEA;EAEI,OCzJY;ED0JZ,kBCxJc","file":"conversations.css"}
@@ -14269,22 +14269,30 @@ body.gd-kda-dialog-opened .highcharts-tooltip-container {
14269
14269
  .gd-gen-ai-chat__window__conversations__drop-placeholder {
14270
14270
  z-index: 1;
14271
14271
  position: absolute;
14272
- display: none;
14272
+ visibility: hidden;
14273
14273
  text-transform: uppercase;
14274
14274
  inset: 0;
14275
- margin: 0 10px;
14276
- padding: 4px 4px;
14277
- border: 2px dashed var(--gd-palette-primary-base, #14b2e2);
14278
- background-color: var(--gd-palette-primary-dimmed, #e8f7fc);
14279
- border-radius: 5px;
14280
14275
  color: var(--gd-palette-complementary-8, #464e56);
14281
14276
  font-size: 12px;
14282
14277
  font-weight: 700;
14283
14278
  line-height: 14px;
14284
14279
  text-align: center;
14280
+ margin: 30px 10px 0 10px;
14281
+ padding: 4px 4px;
14282
+ border: 2px dashed var(--gd-palette-primary-base, #14b2e2);
14283
+ background-color: var(--gd-palette-primary-dimmed, #e8f7fc);
14284
+ border-radius: 5px;
14285
+ min-height: 30px;
14286
+ }
14287
+ .gd-gen-ai-chat__window__conversations__drop-placeholder.isEmpty {
14288
+ margin: 5px 10px 0 10px;
14289
+ overflow: hidden;
14285
14290
  }
14286
14291
  .gd-gen-ai-chat__window__conversations__drop-placeholder.isDragging, .gd-gen-ai-chat__window__conversations__drop-placeholder.isDraggingOver {
14287
- display: block;
14292
+ visibility: visible;
14293
+ }
14294
+ .gd-gen-ai-chat__window__conversations__drop-placeholder.isEmpty.isDragging, .gd-gen-ai-chat__window__conversations__drop-placeholder.isEmpty.isDraggingOver {
14295
+ visibility: visible;
14288
14296
  }
14289
14297
  .gd-gen-ai-chat__window__conversations__drop-placeholder .gd-gen-ai-chat__window__conversations__drop-placeholder__content {
14290
14298
  width: 100%;
@@ -14309,7 +14317,6 @@ body.gd-kda-dialog-opened .highcharts-tooltip-container {
14309
14317
  margin-left: -10px;
14310
14318
  margin-right: -10px;
14311
14319
  align-items: stretch;
14312
- gap: 10px;
14313
14320
  align-self: stretch;
14314
14321
  height: 100%;
14315
14322
  }
@@ -14354,7 +14361,7 @@ body.gd-kda-dialog-opened .highcharts-tooltip-container {
14354
14361
  background-color: var(--gd-palette-primary-dimmed, #e8f7fc);
14355
14362
  color: var(--gd-palette-primary-base, #14b2e2);
14356
14363
  }
14357
- .gd-gen-ai-chat__window__conversations__list__item.generatingTitle .gd-ui-kit-menu__item-title {
14364
+ .gd-gen-ai-chat__window__conversations__list__item.generatingTitle .gd-ui-kit-menu__item-title, .gd-gen-ai-chat__window__conversations__list__item.inProgress .gd-ui-kit-menu__item-title {
14358
14365
  background: linear-gradient(90deg, var(--gd-palette-complementary-7, #6d7680) 0%, var(--gd-palette-complementary-7, #6d7680) 40%, var(--gd-palette-complementary-0, #fff) 50%, var(--gd-palette-complementary-7, #6d7680) 60%, var(--gd-palette-complementary-7, #6d7680) 100%);
14359
14366
  background-size: 200% 100%;
14360
14367
  background-clip: text;