@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.
- package/esm/components/GenAIChatConversations.js +12 -9
- package/esm/components/Input.js +8 -3
- package/esm/localization/bundles/de-DE.localization-bundle.d.ts +6 -1
- package/esm/localization/bundles/de-DE.localization-bundle.js +6 -1
- package/esm/localization/bundles/en-AU.localization-bundle.d.ts +6 -1
- package/esm/localization/bundles/en-AU.localization-bundle.js +6 -1
- package/esm/localization/bundles/en-GB.localization-bundle.d.ts +6 -1
- package/esm/localization/bundles/en-GB.localization-bundle.js +6 -1
- package/esm/localization/bundles/es-419.localization-bundle.d.ts +6 -1
- package/esm/localization/bundles/es-419.localization-bundle.js +6 -1
- package/esm/localization/bundles/es-ES.localization-bundle.d.ts +6 -1
- package/esm/localization/bundles/es-ES.localization-bundle.js +6 -1
- package/esm/localization/bundles/fi-FI.localization-bundle.d.ts +6 -1
- package/esm/localization/bundles/fi-FI.localization-bundle.js +6 -1
- package/esm/localization/bundles/fr-CA.localization-bundle.d.ts +6 -1
- package/esm/localization/bundles/fr-CA.localization-bundle.js +6 -1
- package/esm/localization/bundles/fr-FR.localization-bundle.d.ts +6 -1
- package/esm/localization/bundles/fr-FR.localization-bundle.js +6 -1
- package/esm/localization/bundles/id-ID.localization-bundle.d.ts +6 -1
- package/esm/localization/bundles/id-ID.localization-bundle.js +6 -1
- package/esm/localization/bundles/it-IT.localization-bundle.d.ts +6 -1
- package/esm/localization/bundles/it-IT.localization-bundle.js +6 -1
- package/esm/localization/bundles/ja-JP.localization-bundle.d.ts +6 -1
- package/esm/localization/bundles/ja-JP.localization-bundle.js +6 -1
- package/esm/localization/bundles/ko-KR.localization-bundle.d.ts +6 -1
- package/esm/localization/bundles/ko-KR.localization-bundle.js +6 -1
- package/esm/localization/bundles/nl-NL.localization-bundle.d.ts +6 -1
- package/esm/localization/bundles/nl-NL.localization-bundle.js +6 -1
- package/esm/localization/bundles/pl-PL.localization-bundle.d.ts +6 -1
- package/esm/localization/bundles/pl-PL.localization-bundle.js +6 -1
- package/esm/localization/bundles/pt-BR.localization-bundle.d.ts +6 -1
- package/esm/localization/bundles/pt-BR.localization-bundle.js +6 -1
- package/esm/localization/bundles/pt-PT.localization-bundle.d.ts +6 -1
- package/esm/localization/bundles/pt-PT.localization-bundle.js +6 -1
- package/esm/localization/bundles/ru-RU.localization-bundle.d.ts +6 -1
- package/esm/localization/bundles/ru-RU.localization-bundle.js +6 -1
- package/esm/localization/bundles/sl-SI.localization-bundle.d.ts +6 -1
- package/esm/localization/bundles/sl-SI.localization-bundle.js +6 -1
- package/esm/localization/bundles/th-TH.localization-bundle.d.ts +6 -1
- package/esm/localization/bundles/th-TH.localization-bundle.js +6 -1
- package/esm/localization/bundles/tr-TR.localization-bundle.d.ts +6 -1
- package/esm/localization/bundles/tr-TR.localization-bundle.js +6 -1
- package/esm/localization/bundles/uk-UA.localization-bundle.d.ts +6 -1
- package/esm/localization/bundles/uk-UA.localization-bundle.js +6 -1
- package/esm/localization/bundles/vi-VN.localization-bundle.d.ts +6 -1
- package/esm/localization/bundles/vi-VN.localization-bundle.js +6 -1
- package/esm/localization/bundles/zh-HK.localization-bundle.d.ts +6 -1
- package/esm/localization/bundles/zh-HK.localization-bundle.js +6 -1
- package/esm/localization/bundles/zh-Hans.localization-bundle.d.ts +6 -1
- package/esm/localization/bundles/zh-Hans.localization-bundle.js +6 -1
- package/esm/localization/bundles/zh-Hant.localization-bundle.d.ts +6 -1
- package/esm/localization/bundles/zh-Hant.localization-bundle.js +6 -1
- package/esm/model.d.ts +2 -0
- package/esm/store/messages/messagesSelectors.d.ts +3 -3
- package/esm/store/messages/messagesSelectors.js +26 -4
- package/esm/store/messages/messagesSlice.d.ts +24 -18
- package/esm/store/messages/messagesSlice.js +186 -99
- package/esm/store/sideEffects/index.js +1 -1
- package/esm/store/sideEffects/onConversationDelete.d.ts +2 -0
- package/esm/store/sideEffects/onThreadLoad.js +15 -5
- package/esm/store/sideEffects/onUserFeedback.d.ts +3 -0
- package/esm/store/sideEffects/onUserFeedback.js +2 -1
- package/esm/store/sideEffects/onUserMessage.d.ts +9 -2
- package/esm/store/sideEffects/onUserMessage.js +42 -22
- package/esm/store/sideEffects/onUserMessageUpdate.d.ts +5 -3
- package/esm/store/sideEffects/onUserMessageUpdate.js +5 -2
- package/esm/store/sideEffects/onVisualisationRender.d.ts +2 -0
- package/esm/store/sideEffects/onVisualizationSave.d.ts +4 -0
- package/esm/store/sideEffects/onVisualizationSave.js +2 -0
- package/esm/store/sideEffects/onVisualizationSuccessSave.d.ts +2 -0
- package/esm/store/utils.d.ts +6 -0
- package/esm/store/utils.js +30 -0
- package/esm/types.d.ts +26 -0
- package/package.json +20 -20
- package/styles/css/conversations.css +16 -9
- package/styles/css/conversations.css.map +1 -1
- package/styles/css/main.css +16 -9
- package/styles/css/main.css.map +1 -1
- package/styles/scss/conversations.scss +20 -9
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { createSlice } from "@reduxjs/toolkit";
|
|
3
3
|
import { isAssistantMessage, isChatConversationLocalItem, isUserMessage, isVisualizationContents, makeErrorContent, makeErrorContents, } from "../../model.js";
|
|
4
4
|
import { convertMessageToChatConversation } from "../sideEffects/utils.js";
|
|
5
|
+
import { createEmptyConversation, getConversationData, getConversationLocalId, isConversationWithLocalId, } from "../utils.js";
|
|
5
6
|
export const LS_VERBOSE_KEY = "gd-gen-ai-verbose";
|
|
6
7
|
export const messagesSliceName = "messages";
|
|
7
8
|
/**
|
|
@@ -16,17 +17,16 @@ const getInitialVerboseState = () => {
|
|
|
16
17
|
};
|
|
17
18
|
const initialState = {
|
|
18
19
|
// Start with loading state to avoid re-render from empty state on startup
|
|
19
|
-
asyncProcess: "loading",
|
|
20
20
|
loaded: false,
|
|
21
21
|
verbose: getInitialVerboseState(),
|
|
22
22
|
//old messages
|
|
23
|
+
messageAsyncProcess: "loading",
|
|
23
24
|
messageOrder: [],
|
|
24
25
|
messages: {},
|
|
25
26
|
//conversations
|
|
26
27
|
conversations: undefined,
|
|
27
28
|
currentConversation: undefined,
|
|
28
|
-
|
|
29
|
-
conversationItemsOrder: [],
|
|
29
|
+
conversationsData: {},
|
|
30
30
|
};
|
|
31
31
|
const setNormalizedMessages = (state, messages) => {
|
|
32
32
|
state.messages = messages.reduce((acc, message) => {
|
|
@@ -41,16 +41,37 @@ const setNormalizedConversations = (state, conversations) => {
|
|
|
41
41
|
};
|
|
42
42
|
const setNormalizedConversation = (state, conversation, items = []) => {
|
|
43
43
|
state.currentConversation = conversation;
|
|
44
|
-
state.
|
|
44
|
+
state.loaded = true;
|
|
45
|
+
const data = getConversationData(state.conversationsData, conversation.localId);
|
|
46
|
+
const normalizedItems = items.reduce((acc, message) => {
|
|
45
47
|
acc[message.localId] = message;
|
|
46
48
|
return acc;
|
|
47
49
|
}, {});
|
|
48
|
-
|
|
49
|
-
|
|
50
|
+
const normalizedOrder = items.map((message) => message.localId);
|
|
51
|
+
if (data) {
|
|
52
|
+
data.order = normalizedOrder;
|
|
53
|
+
data.items = normalizedItems;
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
const setConversationInProgress = (state, inProgress, localConversationId) => {
|
|
57
|
+
if (!localConversationId) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
state.conversations = state.conversations?.map((conversation) => conversation.localId === localConversationId ? { ...conversation, inProgress } : conversation);
|
|
61
|
+
if (isConversationWithLocalId(state.currentConversation, localConversationId)) {
|
|
62
|
+
state.currentConversation = {
|
|
63
|
+
...state.currentConversation,
|
|
64
|
+
inProgress,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
50
67
|
};
|
|
51
|
-
const getAssistantMessageStrict = (state, assistantMessageId) => {
|
|
68
|
+
const getAssistantMessageStrict = (state, assistantMessageId, conversationId) => {
|
|
52
69
|
if (state.currentConversation) {
|
|
53
|
-
const
|
|
70
|
+
const data = getConversationData(state.conversationsData, conversationId);
|
|
71
|
+
if (!data) {
|
|
72
|
+
throw new Error(`Unexpected error during message evaluation.`);
|
|
73
|
+
}
|
|
74
|
+
const message = data.items[assistantMessageId];
|
|
54
75
|
if (message.role !== "assistant") {
|
|
55
76
|
throw new Error(`Unexpected error during message evaluation.`);
|
|
56
77
|
}
|
|
@@ -64,9 +85,13 @@ const getAssistantMessageStrict = (state, assistantMessageId) => {
|
|
|
64
85
|
return message;
|
|
65
86
|
}
|
|
66
87
|
};
|
|
67
|
-
const getUserMessageStrict = (state, assistantMessageId) => {
|
|
88
|
+
const getUserMessageStrict = (state, assistantMessageId, conversationId) => {
|
|
68
89
|
if (state.currentConversation) {
|
|
69
|
-
const
|
|
90
|
+
const data = getConversationData(state.conversationsData, conversationId);
|
|
91
|
+
if (!data) {
|
|
92
|
+
throw new Error(`Unexpected error during message evaluation.`);
|
|
93
|
+
}
|
|
94
|
+
const message = data.items[assistantMessageId];
|
|
70
95
|
if (message.role !== "user") {
|
|
71
96
|
throw new Error(`Unexpected error during message evaluation.`);
|
|
72
97
|
}
|
|
@@ -80,9 +105,13 @@ const getUserMessageStrict = (state, assistantMessageId) => {
|
|
|
80
105
|
return message;
|
|
81
106
|
}
|
|
82
107
|
};
|
|
83
|
-
const getMessageExists = (state, assistantMessageId) => {
|
|
108
|
+
const getMessageExists = (state, assistantMessageId, conversationId) => {
|
|
84
109
|
if (state.currentConversation) {
|
|
85
|
-
|
|
110
|
+
if (!conversationId) {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
const data = getConversationData(state.conversationsData, conversationId);
|
|
114
|
+
return !!data?.items[assistantMessageId];
|
|
86
115
|
}
|
|
87
116
|
return !!state.messages[assistantMessageId];
|
|
88
117
|
};
|
|
@@ -90,13 +119,17 @@ const getMessageExists = (state, assistantMessageId) => {
|
|
|
90
119
|
* Get the user message before the assistant message, or undefined if there isn't one.
|
|
91
120
|
* This is useful for dynamically created assistant messages that don't have a preceding user message.
|
|
92
121
|
*/
|
|
93
|
-
const getUserMessageBeforeSafe = (state, assistantMessageId) => {
|
|
122
|
+
const getUserMessageBeforeSafe = (state, assistantMessageId, conversationId) => {
|
|
94
123
|
if (state.currentConversation) {
|
|
95
|
-
const
|
|
124
|
+
const data = getConversationData(state.conversationsData, conversationId);
|
|
125
|
+
if (!data) {
|
|
126
|
+
return undefined;
|
|
127
|
+
}
|
|
128
|
+
const messageIndex = data.order.indexOf(assistantMessageId);
|
|
96
129
|
if (messageIndex <= 0) {
|
|
97
130
|
return undefined;
|
|
98
131
|
}
|
|
99
|
-
const message =
|
|
132
|
+
const message = data.items[data.order[messageIndex - 1]];
|
|
100
133
|
return message.role === "user" ? message : undefined;
|
|
101
134
|
}
|
|
102
135
|
const messageIndex = state.messageOrder.indexOf(assistantMessageId);
|
|
@@ -111,20 +144,36 @@ const messagesSlice = createSlice({
|
|
|
111
144
|
initialState,
|
|
112
145
|
reducers: {
|
|
113
146
|
loadThreadAction: (state) => {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
147
|
+
if (state.currentConversation) {
|
|
148
|
+
const data = getConversationData(state.conversationsData, state.currentConversation.localId);
|
|
149
|
+
if (data) {
|
|
150
|
+
data.asyncProcess = "loading";
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
state.messageAsyncProcess = "loading";
|
|
117
155
|
}
|
|
118
|
-
state.asyncProcess = "loading";
|
|
119
156
|
},
|
|
120
157
|
loadThreadErrorAction: (state, { payload: { error } }) => {
|
|
121
158
|
state.globalError = errorToObject(error);
|
|
122
|
-
|
|
159
|
+
if (state.currentConversation) {
|
|
160
|
+
const data = getConversationData(state.conversationsData, state.currentConversation.localId);
|
|
161
|
+
delete data?.asyncProcess;
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
delete state.messageAsyncProcess;
|
|
165
|
+
}
|
|
123
166
|
},
|
|
124
167
|
loadThreadSuccessAction: (state, { payload: { messages, threadId } }) => {
|
|
125
168
|
setNormalizedMessages(state, messages);
|
|
126
169
|
state.threadId = threadId;
|
|
127
|
-
|
|
170
|
+
if (state.currentConversation) {
|
|
171
|
+
const data = getConversationData(state.conversationsData, state.currentConversation.localId);
|
|
172
|
+
delete data?.asyncProcess;
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
delete state.messageAsyncProcess;
|
|
176
|
+
}
|
|
128
177
|
},
|
|
129
178
|
/**
|
|
130
179
|
* Restore previously cached messages immediately while the backend is still loading.
|
|
@@ -135,14 +184,12 @@ const messagesSlice = createSlice({
|
|
|
135
184
|
* - The input remains disabled (Input checks !!asyncProcess, and "restoring" is truthy).
|
|
136
185
|
*/
|
|
137
186
|
restoreCachedMessagesAction: (state, { payload: { messages } }) => {
|
|
138
|
-
|
|
139
|
-
const normalized = messages.reduce((acc, message) => {
|
|
187
|
+
state.messages = messages.reduce((acc, message) => {
|
|
140
188
|
acc[message.localId] = message;
|
|
141
189
|
return acc;
|
|
142
190
|
}, {});
|
|
143
|
-
state.messages = normalized;
|
|
144
191
|
state.messageOrder = messages.map((message) => message.localId);
|
|
145
|
-
state.
|
|
192
|
+
state.messageAsyncProcess = "restoring";
|
|
146
193
|
},
|
|
147
194
|
loadConversationsSuccessAction: (state, { payload: { conversations }, }) => {
|
|
148
195
|
setNormalizedConversations(state, conversations);
|
|
@@ -150,31 +197,56 @@ const messagesSlice = createSlice({
|
|
|
150
197
|
loadConversationSuccessAction: (state, { payload: { currentConversation, conversationItems, threadId }, }) => {
|
|
151
198
|
setNormalizedConversation(state, currentConversation, conversationItems);
|
|
152
199
|
state.threadId = threadId;
|
|
153
|
-
|
|
200
|
+
if (state.currentConversation) {
|
|
201
|
+
const data = getConversationData(state.conversationsData, state.currentConversation.localId);
|
|
202
|
+
delete data?.asyncProcess;
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
delete state.messageAsyncProcess;
|
|
206
|
+
}
|
|
154
207
|
},
|
|
155
208
|
clearThreadAction: (state) => {
|
|
156
|
-
state.
|
|
209
|
+
if (state.currentConversation) {
|
|
210
|
+
const data = getConversationData(state.conversationsData, state.currentConversation.localId);
|
|
211
|
+
if (data) {
|
|
212
|
+
data.asyncProcess = "clearing";
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
state.messageAsyncProcess = "clearing";
|
|
217
|
+
}
|
|
157
218
|
},
|
|
158
219
|
clearThreadErrorAction: (state, { payload: { error } }) => {
|
|
159
220
|
state.globalError = errorToObject(error);
|
|
160
|
-
|
|
221
|
+
if (state.currentConversation) {
|
|
222
|
+
const data = getConversationData(state.conversationsData, state.currentConversation.localId);
|
|
223
|
+
delete data?.asyncProcess;
|
|
224
|
+
}
|
|
225
|
+
else {
|
|
226
|
+
delete state.messageAsyncProcess;
|
|
227
|
+
}
|
|
161
228
|
},
|
|
162
229
|
clearThreadSuccessAction: (state) => {
|
|
163
230
|
state.messages = {};
|
|
164
231
|
state.messageOrder = [];
|
|
165
232
|
state.loaded = false;
|
|
166
|
-
delete state.asyncProcess;
|
|
167
233
|
delete state.globalError;
|
|
234
|
+
if (state.currentConversation) {
|
|
235
|
+
const data = getConversationData(state.conversationsData, state.currentConversation.localId);
|
|
236
|
+
delete data?.asyncProcess;
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
delete state.messageAsyncProcess;
|
|
240
|
+
}
|
|
168
241
|
},
|
|
169
242
|
clearConversationSuccessAction: (state, { payload: { conversation, threadId }, }) => {
|
|
170
243
|
state.conversations = [conversation, ...(state.conversations ?? [])];
|
|
171
244
|
state.currentConversation = conversation;
|
|
172
|
-
state.conversationItems = {};
|
|
173
|
-
state.conversationItemsOrder = [];
|
|
174
245
|
state.threadId = threadId;
|
|
175
246
|
state.loaded = false;
|
|
176
|
-
delete state.asyncProcess;
|
|
177
247
|
delete state.globalError;
|
|
248
|
+
const data = getConversationData(state.conversationsData, state.currentConversation.localId);
|
|
249
|
+
delete data?.asyncProcess;
|
|
178
250
|
},
|
|
179
251
|
/**
|
|
180
252
|
* Add message to the stack
|
|
@@ -189,20 +261,21 @@ const messagesSlice = createSlice({
|
|
|
189
261
|
throw new Error("Working with conversation message but thread mode is active.");
|
|
190
262
|
}
|
|
191
263
|
const currentConversation = state.currentConversation;
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
264
|
+
currentConversation.updatedAt = new Date().toISOString();
|
|
265
|
+
state.conversations = state.conversations?.map((conversation) => {
|
|
266
|
+
if (conversation.localId === currentConversation.localId) {
|
|
267
|
+
return {
|
|
268
|
+
...conversation,
|
|
269
|
+
updatedAt: currentConversation.updatedAt,
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
return conversation;
|
|
273
|
+
});
|
|
274
|
+
const data = getConversationData(state.conversationsData, currentConversation.localId);
|
|
275
|
+
if (data) {
|
|
276
|
+
data.items[message.localId] = message;
|
|
277
|
+
data.order.push(message.localId);
|
|
203
278
|
}
|
|
204
|
-
state.conversationItems[message.localId] = message;
|
|
205
|
-
state.conversationItemsOrder.push(message.localId);
|
|
206
279
|
}
|
|
207
280
|
else {
|
|
208
281
|
if (state.currentConversation) {
|
|
@@ -216,13 +289,17 @@ const messagesSlice = createSlice({
|
|
|
216
289
|
/**
|
|
217
290
|
* Start the message evaluation, adding new assistant message as an incomplete placeholder
|
|
218
291
|
*/
|
|
219
|
-
evaluateMessageAction: (state, { payload: { message }, }) => {
|
|
292
|
+
evaluateMessageAction: (state, { payload: { message, conversationId }, }) => {
|
|
220
293
|
if (isChatConversationLocalItem(message)) {
|
|
221
|
-
if (!
|
|
294
|
+
if (!conversationId) {
|
|
222
295
|
throw new Error("Working with conversation message but thread mode is active.");
|
|
223
296
|
}
|
|
224
|
-
state.
|
|
225
|
-
|
|
297
|
+
const data = getConversationData(state.conversationsData, conversationId);
|
|
298
|
+
if (data) {
|
|
299
|
+
data.items[message.localId] = message;
|
|
300
|
+
data.order.push(message.localId);
|
|
301
|
+
}
|
|
302
|
+
setConversationInProgress(state, true, conversationId);
|
|
226
303
|
}
|
|
227
304
|
else {
|
|
228
305
|
if (state.currentConversation) {
|
|
@@ -231,23 +308,33 @@ const messagesSlice = createSlice({
|
|
|
231
308
|
state.messages[message.localId] = message;
|
|
232
309
|
state.messageOrder.push(message.localId);
|
|
233
310
|
}
|
|
234
|
-
state.
|
|
311
|
+
if (state.currentConversation) {
|
|
312
|
+
const data = getConversationData(state.conversationsData, conversationId);
|
|
313
|
+
if (data) {
|
|
314
|
+
data.asyncProcess = "evaluating";
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
else {
|
|
318
|
+
state.messageAsyncProcess = "evaluating";
|
|
319
|
+
}
|
|
235
320
|
},
|
|
236
321
|
/**
|
|
237
322
|
* The evaluation failed, need to update the assistant message.
|
|
238
323
|
*/
|
|
239
324
|
evaluateMessageErrorAction: (state, { payload, }) => {
|
|
240
|
-
const assistantMessage = getAssistantMessageStrict(state, payload.assistantMessageId);
|
|
325
|
+
const assistantMessage = getAssistantMessageStrict(state, payload.assistantMessageId, payload.conversationId);
|
|
241
326
|
if (isChatConversationLocalItem(assistantMessage)) {
|
|
242
327
|
assistantMessage.complete = true;
|
|
243
328
|
assistantMessage.streaming = false;
|
|
244
329
|
assistantMessage.content = makeErrorContent(payload.error);
|
|
330
|
+
const data = getConversationData(state.conversationsData, payload.conversationId);
|
|
331
|
+
delete data?.asyncProcess;
|
|
245
332
|
}
|
|
246
333
|
else {
|
|
247
334
|
assistantMessage.complete = true;
|
|
248
335
|
assistantMessage.content.push(makeErrorContents(payload.error));
|
|
336
|
+
delete state.messageAsyncProcess;
|
|
249
337
|
}
|
|
250
|
-
delete state.asyncProcess;
|
|
251
338
|
},
|
|
252
339
|
/**
|
|
253
340
|
* Received new chunk from server over SSE.
|
|
@@ -255,12 +342,12 @@ const messagesSlice = createSlice({
|
|
|
255
342
|
evaluateMessageStreamingAction: (state, { payload, }) => {
|
|
256
343
|
//NOTE: During streaming a message, user can choose to close or reset a chat
|
|
257
344
|
// and without this check, we would get an unwanted error
|
|
258
|
-
const exists = getMessageExists(state, payload.assistantMessageId);
|
|
345
|
+
const exists = getMessageExists(state, payload.assistantMessageId, payload.conversationId);
|
|
259
346
|
if (!exists) {
|
|
260
347
|
return;
|
|
261
348
|
}
|
|
262
349
|
// Update assistant message
|
|
263
|
-
const assistantMessage = getAssistantMessageStrict(state, payload.assistantMessageId);
|
|
350
|
+
const assistantMessage = getAssistantMessageStrict(state, payload.assistantMessageId, payload.conversationId);
|
|
264
351
|
assistantMessage.id = payload.interactionId ?? assistantMessage.id;
|
|
265
352
|
if (isChatConversationLocalItem(assistantMessage)) {
|
|
266
353
|
assistantMessage.content = payload.content ?? {
|
|
@@ -279,21 +366,20 @@ const messagesSlice = createSlice({
|
|
|
279
366
|
// Also update the interaction id in the relevant user message (if one exists)
|
|
280
367
|
// Note: dynamically created assistant messages (for multi-interaction streams)
|
|
281
368
|
// may not have a preceding user message
|
|
282
|
-
const userMessage = getUserMessageBeforeSafe(state, payload.assistantMessageId);
|
|
369
|
+
const userMessage = getUserMessageBeforeSafe(state, payload.assistantMessageId, payload.conversationId);
|
|
283
370
|
if (userMessage) {
|
|
284
371
|
userMessage.id = payload.interactionId ?? userMessage.id;
|
|
285
372
|
}
|
|
286
373
|
},
|
|
287
374
|
evaluateConversationTitleAction: (state, { payload, }) => {
|
|
288
|
-
const conversation = state.conversations?.find((c) => c.
|
|
375
|
+
const conversation = state.conversations?.find((c) => c.localId === payload.conversation.localId);
|
|
289
376
|
if (!conversation) {
|
|
290
377
|
return;
|
|
291
378
|
}
|
|
292
379
|
conversation.generatingTitle = payload.generatingTitle;
|
|
293
380
|
if (payload.title) {
|
|
294
381
|
conversation.title = payload.title;
|
|
295
|
-
if (state.currentConversation
|
|
296
|
-
state.currentConversation?.id === conversation.id) {
|
|
382
|
+
if (isConversationWithLocalId(state.currentConversation, conversation.localId)) {
|
|
297
383
|
state.currentConversation.title = payload.title;
|
|
298
384
|
}
|
|
299
385
|
}
|
|
@@ -301,12 +387,12 @@ const messagesSlice = createSlice({
|
|
|
301
387
|
evaluateMessageUpdateAction: (state, { payload, }) => {
|
|
302
388
|
//NOTE: During streaming a message, user can choose to close or reset a chat
|
|
303
389
|
// and without this check, we would get an unwanted error
|
|
304
|
-
const exists = getMessageExists(state, payload.userMessageId);
|
|
390
|
+
const exists = getMessageExists(state, payload.userMessageId, payload.conversationId);
|
|
305
391
|
if (!exists) {
|
|
306
392
|
return;
|
|
307
393
|
}
|
|
308
394
|
// Update assistant message
|
|
309
|
-
const userMessage = getUserMessageStrict(state, payload.userMessageId);
|
|
395
|
+
const userMessage = getUserMessageStrict(state, payload.userMessageId, payload.conversationId);
|
|
310
396
|
userMessage.id = payload.interactionId ?? userMessage.id;
|
|
311
397
|
if (isChatConversationLocalItem(userMessage)) {
|
|
312
398
|
const message = payload.message;
|
|
@@ -320,12 +406,15 @@ const messagesSlice = createSlice({
|
|
|
320
406
|
}
|
|
321
407
|
},
|
|
322
408
|
evaluateMessageCompleteAction: (state, { payload, }) => {
|
|
323
|
-
const assistantMessage = getAssistantMessageStrict(state, payload.assistantMessageId);
|
|
409
|
+
const assistantMessage = getAssistantMessageStrict(state, payload.assistantMessageId, payload.conversationId);
|
|
324
410
|
assistantMessage.complete = true;
|
|
325
|
-
|
|
411
|
+
setConversationInProgress(state, false, payload.conversationId);
|
|
412
|
+
const data = getConversationData(state.conversationsData, payload.conversationId);
|
|
413
|
+
delete data?.asyncProcess;
|
|
414
|
+
delete state.messageAsyncProcess;
|
|
326
415
|
},
|
|
327
416
|
evaluateMessageSuggestionsAction: (state, { payload, }) => {
|
|
328
|
-
const assistantMessage = getAssistantMessageStrict(state, payload.assistantMessageId);
|
|
417
|
+
const assistantMessage = getAssistantMessageStrict(state, payload.assistantMessageId, payload.conversationId);
|
|
329
418
|
if (isChatConversationLocalItem(assistantMessage)) {
|
|
330
419
|
assistantMessage.suggestions = payload.item;
|
|
331
420
|
}
|
|
@@ -344,31 +433,23 @@ const messagesSlice = createSlice({
|
|
|
344
433
|
},
|
|
345
434
|
startNewConversationAction: (state) => {
|
|
346
435
|
// keep list of conversations, but clear current conversation data in the view
|
|
347
|
-
state.currentConversation =
|
|
348
|
-
state.conversationItems = {};
|
|
349
|
-
state.conversationItemsOrder = [];
|
|
436
|
+
state.currentConversation = createEmptyConversation();
|
|
350
437
|
// mark as loaded to prevent auto-loading last conversation while on landing
|
|
351
438
|
state.loaded = true;
|
|
352
|
-
delete state.asyncProcess;
|
|
353
439
|
},
|
|
354
440
|
setCurrentConversationAction: (state, { payload }) => {
|
|
355
|
-
const existing = state.conversations?.find((c) => c.
|
|
441
|
+
const existing = state.conversations?.find((c) => c.localId === payload.conversation.localId);
|
|
356
442
|
if (existing) {
|
|
357
|
-
|
|
358
|
-
if (state.currentConversation !== "new" && state.currentConversation?.id === existing.id) {
|
|
359
|
-
return;
|
|
360
|
-
}
|
|
443
|
+
const data = getConversationData(state.conversationsData, existing.localId);
|
|
361
444
|
state.currentConversation = existing;
|
|
362
|
-
state.
|
|
363
|
-
state.
|
|
364
|
-
|
|
365
|
-
state.loaded = false;
|
|
366
|
-
//TODO: Existing conversation
|
|
445
|
+
state.loaded = !!data?.order.length;
|
|
446
|
+
state.threadId = payload.conversation.id;
|
|
447
|
+
existing.id = payload.conversation.id;
|
|
367
448
|
}
|
|
368
449
|
else {
|
|
369
450
|
state.conversations = [payload.conversation, ...(state.conversations ?? [])];
|
|
370
451
|
state.currentConversation = payload.conversation;
|
|
371
|
-
state.threadId = payload.conversation.
|
|
452
|
+
state.threadId = payload.conversation.localId;
|
|
372
453
|
state.loaded = true;
|
|
373
454
|
}
|
|
374
455
|
},
|
|
@@ -376,10 +457,17 @@ const messagesSlice = createSlice({
|
|
|
376
457
|
state.globalError = errorToObject(error);
|
|
377
458
|
},
|
|
378
459
|
cancelAsyncAction: (state) => {
|
|
379
|
-
|
|
460
|
+
if (state.currentConversation) {
|
|
461
|
+
const data = getConversationData(state.conversationsData, state.currentConversation.localId);
|
|
462
|
+
delete data?.asyncProcess;
|
|
463
|
+
}
|
|
464
|
+
else {
|
|
465
|
+
delete state.messageAsyncProcess;
|
|
466
|
+
}
|
|
380
467
|
},
|
|
381
468
|
setUserFeedback: (state, { payload, }) => {
|
|
382
|
-
const
|
|
469
|
+
const conversationId = getConversationLocalId(state.currentConversation);
|
|
470
|
+
const assistantMessage = getAssistantMessageStrict(state, payload.assistantMessageId, conversationId);
|
|
383
471
|
if (isChatConversationLocalItem(assistantMessage)) {
|
|
384
472
|
const original = assistantMessage.feedback ?? {
|
|
385
473
|
type: "feedback",
|
|
@@ -397,7 +485,7 @@ const messagesSlice = createSlice({
|
|
|
397
485
|
},
|
|
398
486
|
setUserFeedbackError: (state, { payload, }) => {
|
|
399
487
|
// Reset feedback to NONE and set error when submission fails
|
|
400
|
-
const assistantMessage = getAssistantMessageStrict(state, payload.assistantMessageId);
|
|
488
|
+
const assistantMessage = getAssistantMessageStrict(state, payload.assistantMessageId, payload.conversationId);
|
|
401
489
|
if (isChatConversationLocalItem(assistantMessage)) {
|
|
402
490
|
const original = assistantMessage.feedback ?? {
|
|
403
491
|
type: "feedback",
|
|
@@ -417,7 +505,7 @@ const messagesSlice = createSlice({
|
|
|
417
505
|
},
|
|
418
506
|
clearUserFeedbackError: (state, { payload, }) => {
|
|
419
507
|
// Clear feedback error after showing toast
|
|
420
|
-
const assistantMessage = getAssistantMessageStrict(state, payload.assistantMessageId);
|
|
508
|
+
const assistantMessage = getAssistantMessageStrict(state, payload.assistantMessageId, payload.conversationId);
|
|
421
509
|
if (isChatConversationLocalItem(assistantMessage)) {
|
|
422
510
|
delete assistantMessage.feedback?.error;
|
|
423
511
|
}
|
|
@@ -426,7 +514,8 @@ const messagesSlice = createSlice({
|
|
|
426
514
|
}
|
|
427
515
|
},
|
|
428
516
|
saveVisualizationAction: (state, { payload, }) => {
|
|
429
|
-
const
|
|
517
|
+
const conversationId = getConversationLocalId(state.currentConversation);
|
|
518
|
+
const assistantMessage = getAssistantMessageStrict(state, payload.assistantMessageId, conversationId);
|
|
430
519
|
if (isChatConversationLocalItem(assistantMessage)) {
|
|
431
520
|
if (assistantMessage.content.type !== "multipart") {
|
|
432
521
|
throw new Error("Unexpected message type");
|
|
@@ -453,7 +542,8 @@ const messagesSlice = createSlice({
|
|
|
453
542
|
}
|
|
454
543
|
},
|
|
455
544
|
savedVisualizationAction: (state, { payload, }) => {
|
|
456
|
-
const
|
|
545
|
+
const conversationId = getConversationLocalId(state.currentConversation);
|
|
546
|
+
const assistantMessage = getAssistantMessageStrict(state, payload.assistantMessageId, conversationId);
|
|
457
547
|
if (isChatConversationLocalItem(assistantMessage)) {
|
|
458
548
|
if (assistantMessage.content.type !== "multipart") {
|
|
459
549
|
throw new Error("Unexpected message type");
|
|
@@ -467,7 +557,7 @@ const messagesSlice = createSlice({
|
|
|
467
557
|
}
|
|
468
558
|
},
|
|
469
559
|
saveVisualizationErrorAction: (state, { payload, }) => {
|
|
470
|
-
const assistantMessage = getAssistantMessageStrict(state, payload.assistantMessageId);
|
|
560
|
+
const assistantMessage = getAssistantMessageStrict(state, payload.assistantMessageId, payload.conversationId);
|
|
471
561
|
if (isChatConversationLocalItem(assistantMessage)) {
|
|
472
562
|
if (assistantMessage.content.type !== "multipart") {
|
|
473
563
|
throw new Error("Unexpected message type");
|
|
@@ -494,7 +584,7 @@ const messagesSlice = createSlice({
|
|
|
494
584
|
}
|
|
495
585
|
},
|
|
496
586
|
saveVisualizationSuccessAction: (state, { payload, }) => {
|
|
497
|
-
const assistantMessage = getAssistantMessageStrict(state, payload.assistantMessageId);
|
|
587
|
+
const assistantMessage = getAssistantMessageStrict(state, payload.assistantMessageId, payload.conversationId);
|
|
498
588
|
if (isChatConversationLocalItem(assistantMessage)) {
|
|
499
589
|
if (assistantMessage.content.type !== "multipart") {
|
|
500
590
|
throw new Error("Unexpected message type");
|
|
@@ -523,7 +613,8 @@ const messagesSlice = createSlice({
|
|
|
523
613
|
},
|
|
524
614
|
saveVisualisationRenderStatusAction: (state, _action) => state,
|
|
525
615
|
saveVisualisationRenderStatusSuccessAction: (state, { payload, }) => {
|
|
526
|
-
const
|
|
616
|
+
const conversationId = getConversationLocalId(state.currentConversation);
|
|
617
|
+
const assistantMessage = getAssistantMessageStrict(state, payload.assistantMessageId, conversationId);
|
|
527
618
|
if (isChatConversationLocalItem(assistantMessage)) {
|
|
528
619
|
if (assistantMessage.content.type !== "multipart") {
|
|
529
620
|
throw new Error("Unexpected message type");
|
|
@@ -548,14 +639,13 @@ const messagesSlice = createSlice({
|
|
|
548
639
|
visualizationErrorAction: (state, _action) => state,
|
|
549
640
|
pinConversationAction: (state, _action) => state,
|
|
550
641
|
pinConversationSuccessAction: (state, { payload }) => {
|
|
551
|
-
state.conversations = state.conversations?.map((conversation) => conversation.
|
|
642
|
+
state.conversations = state.conversations?.map((conversation) => conversation.localId === payload.conversationId
|
|
552
643
|
? {
|
|
553
644
|
...conversation,
|
|
554
645
|
pinned: payload.pinned,
|
|
555
646
|
}
|
|
556
647
|
: conversation);
|
|
557
|
-
if (state.currentConversation
|
|
558
|
-
state.currentConversation?.id === payload.conversationId) {
|
|
648
|
+
if (isConversationWithLocalId(state.currentConversation, payload.conversationId)) {
|
|
559
649
|
state.currentConversation = {
|
|
560
650
|
...state.currentConversation,
|
|
561
651
|
pinned: payload.pinned,
|
|
@@ -563,14 +653,13 @@ const messagesSlice = createSlice({
|
|
|
563
653
|
}
|
|
564
654
|
},
|
|
565
655
|
pinConversationFailureAction: (state, { payload }) => {
|
|
566
|
-
state.conversations = state.conversations?.map((conversation) => conversation.
|
|
656
|
+
state.conversations = state.conversations?.map((conversation) => conversation.localId === payload.conversationId
|
|
567
657
|
? {
|
|
568
658
|
...conversation,
|
|
569
659
|
pinned: payload.pinned,
|
|
570
660
|
}
|
|
571
661
|
: conversation);
|
|
572
|
-
if (state.currentConversation
|
|
573
|
-
state.currentConversation?.id === payload.conversationId) {
|
|
662
|
+
if (isConversationWithLocalId(state.currentConversation, payload.conversationId)) {
|
|
574
663
|
state.currentConversation = {
|
|
575
664
|
...state.currentConversation,
|
|
576
665
|
pinned: payload.pinned,
|
|
@@ -579,14 +668,12 @@ const messagesSlice = createSlice({
|
|
|
579
668
|
},
|
|
580
669
|
deleteConversationAction: (state, _action) => state,
|
|
581
670
|
deleteConversationStartAction: (state, { payload }) => {
|
|
582
|
-
if (state.currentConversation
|
|
583
|
-
payload.conversationId === state.currentConversation?.id) {
|
|
671
|
+
if (isConversationWithLocalId(state.currentConversation, payload.conversationId)) {
|
|
584
672
|
// keep list of conversations, but clear current conversation data in the view
|
|
585
|
-
state.currentConversation =
|
|
586
|
-
state.conversationItems = {};
|
|
587
|
-
state.conversationItemsOrder = [];
|
|
673
|
+
state.currentConversation = createEmptyConversation();
|
|
588
674
|
}
|
|
589
|
-
|
|
675
|
+
delete state.conversationsData[payload.conversationId];
|
|
676
|
+
state.conversations = state.conversations?.filter((conversation) => conversation.localId !== payload.conversationId);
|
|
590
677
|
},
|
|
591
678
|
deleteConversationSuccessAction: (state, _action) => state,
|
|
592
679
|
deleteConversationFailureAction: (state, { payload }) => {
|
|
@@ -23,7 +23,7 @@ import { onVisualizationSuccessSave } from "./onVisualizationSuccessSave.js";
|
|
|
23
23
|
export function* rootSaga() {
|
|
24
24
|
yield takeLatest(loadThreadAction.type, onThreadLoad);
|
|
25
25
|
yield takeLatest(clearThreadAction.type, onThreadClear);
|
|
26
|
-
yield
|
|
26
|
+
yield takeEvery(newMessageAction.type, onUserMessage);
|
|
27
27
|
//messages API
|
|
28
28
|
yield takeEvery(setUserFeedback.type, onUserFeedback);
|
|
29
29
|
yield takeEvery(saveVisualizationAction.type, onVisualizationSave);
|
|
@@ -20,5 +20,7 @@ export declare function onConversationDelete({ payload }: PayloadAction<{
|
|
|
20
20
|
};
|
|
21
21
|
type: "messages/deleteConversationFailureAction";
|
|
22
22
|
}> | import("redux-saga/effects").SelectEffect, void, IAnalyticalBackend & string & import("@gooddata/sdk-backend-spi").IChatConversation & {
|
|
23
|
+
localId: string;
|
|
23
24
|
generatingTitle?: boolean | undefined;
|
|
25
|
+
inProgress?: boolean | undefined;
|
|
24
26
|
}>;
|
|
@@ -5,6 +5,7 @@ import { settingsSelector } from "../chatWindow/chatWindowSelectors.js";
|
|
|
5
5
|
import { loadMessages } from "../localStorage.js";
|
|
6
6
|
import { conversationSelector, conversationsSelector } from "../messages/messagesSelectors.js";
|
|
7
7
|
import { cancelAsyncAction, loadConversationSuccessAction, loadConversationsSuccessAction, loadThreadErrorAction, loadThreadSuccessAction, restoreCachedMessagesAction, } from "../messages/messagesSlice.js";
|
|
8
|
+
import { createEmptyConversation } from "../utils.js";
|
|
8
9
|
import { interactionsToMessages } from "./converters/interactionsToMessages.js";
|
|
9
10
|
import { convertToLocalContent } from "./converters/toLocalContent.js";
|
|
10
11
|
/**
|
|
@@ -124,7 +125,12 @@ function* fetchAllConversations() {
|
|
|
124
125
|
if (cancelledConversations) {
|
|
125
126
|
return;
|
|
126
127
|
}
|
|
127
|
-
const conversationItems = resultsConversations.items
|
|
128
|
+
const conversationItems = resultsConversations.items.map((item) => {
|
|
129
|
+
return {
|
|
130
|
+
...item,
|
|
131
|
+
localId: item.id,
|
|
132
|
+
};
|
|
133
|
+
});
|
|
128
134
|
yield put(loadConversationsSuccessAction({
|
|
129
135
|
conversations: conversationItems,
|
|
130
136
|
}));
|
|
@@ -136,16 +142,20 @@ function* fetchCurrentConversation() {
|
|
|
136
142
|
const conversations = yield select(conversationsSelector);
|
|
137
143
|
const conversation = yield select(conversationSelector);
|
|
138
144
|
// New conversation selected
|
|
139
|
-
if (conversation
|
|
145
|
+
if (conversation?.localId && !conversation.id) {
|
|
140
146
|
yield put(loadConversationSuccessAction({
|
|
141
|
-
currentConversation:
|
|
147
|
+
currentConversation: conversation,
|
|
142
148
|
conversationItems: [],
|
|
143
149
|
}));
|
|
144
150
|
return;
|
|
145
151
|
}
|
|
146
152
|
const api = backend.workspace(workspace).genAI().getChatConversations({ isPreview });
|
|
153
|
+
//Sort by createion date, because normally there are pinned on top
|
|
154
|
+
const sortedConv = conversations
|
|
155
|
+
?.slice()
|
|
156
|
+
.sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime());
|
|
147
157
|
//Load the first conversation if there are any or create a new one
|
|
148
|
-
const selectedConversation = conversation ??
|
|
158
|
+
const selectedConversation = conversation ?? sortedConv?.[0] ?? undefined;
|
|
149
159
|
if (selectedConversation) {
|
|
150
160
|
const preparedThread = api.getConversationThread(selectedConversation.id);
|
|
151
161
|
const [resultsItems, cancelledItems] = yield race([call(fetchConversationHistory, preparedThread), take(cancelAsyncAction.type)]);
|
|
@@ -166,7 +176,7 @@ function* fetchCurrentConversation() {
|
|
|
166
176
|
}
|
|
167
177
|
else {
|
|
168
178
|
yield put(loadConversationSuccessAction({
|
|
169
|
-
currentConversation:
|
|
179
|
+
currentConversation: createEmptyConversation(),
|
|
170
180
|
conversationItems: [],
|
|
171
181
|
}));
|
|
172
182
|
}
|