@zeniai/client-epic-state 4.16.81 → 4.16.82

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.
@@ -19,7 +19,6 @@ export interface AIControllerVisualizationPayload {
19
19
  }
20
20
  export interface AIControllerAnswerPayload {
21
21
  text: string;
22
- follow_up_questions?: string[];
23
22
  state: string;
24
23
  type: string;
25
24
  visualization: AIControllerVisualizationPayload | null;
@@ -27,6 +26,7 @@ export interface AIControllerAnswerPayload {
27
26
  assumptions: string | null;
28
27
  insights: string | null;
29
28
  summarized_title: string | null;
29
+ follow_up_questions?: string[];
30
30
  }
31
31
  export interface ChatSessionPayload {
32
32
  chat_session_id: string;
@@ -12,6 +12,7 @@ export declare const setNewSession: import("@reduxjs/toolkit").ActionCreatorWith
12
12
  }, "aiController/setNewSession">, clearSession: import("@reduxjs/toolkit").ActionCreatorWithPayload<string, "aiController/clearSession">, setSessions: import("@reduxjs/toolkit").ActionCreatorWithPayload<ChatSessionPayload[], "aiController/setSessions">, setChatHistory: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
13
13
  chatSessionId: ID;
14
14
  history: MessagePayload[];
15
+ isPaginationComplete?: boolean;
15
16
  }, "aiController/setChatHistory">, upsertAnswerPayload: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
16
17
  chatSessionId: ID;
17
18
  answerPayload: AIControllerAnswerPayload;
@@ -40,6 +41,8 @@ export declare const setNewSession: import("@reduxjs/toolkit").ActionCreatorWith
40
41
  }, "aiController/addQuestionPayload">, clearAIController: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<"aiController/clearAIController">, deleteQuestion: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
41
42
  chatSessionId: ID;
42
43
  questionAnswerId: ID;
43
- }, "aiController/deleteQuestion">;
44
+ }, "aiController/deleteQuestion">, updateNewSession: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
45
+ chatSessionId: ID;
46
+ }, "aiController/updateNewSession">;
44
47
  declare const _default: import("redux").Reducer<AIControllerState>;
45
48
  export default _default;
@@ -1,105 +1,131 @@
1
1
  "use strict";
2
2
  var _a;
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
- exports.deleteQuestion = exports.clearAIController = exports.addQuestionPayload = exports.upsertOrAddQuestionAnswerPayload = exports.upsertAnswerPayload = exports.setChatHistory = exports.setSessions = exports.clearSession = exports.setNewSession = exports.toAIControllerVisualization = exports.initialAIControllerState = void 0;
4
+ exports.updateNewSession = exports.deleteQuestion = exports.clearAIController = exports.addQuestionPayload = exports.upsertOrAddQuestionAnswerPayload = exports.upsertAnswerPayload = exports.setChatHistory = exports.setSessions = exports.clearSession = exports.setNewSession = exports.toAIControllerVisualization = exports.initialAIControllerState = void 0;
5
5
  const toolkit_1 = require("@reduxjs/toolkit");
6
6
  const zeniDayJS_1 = require("../../zeniDayJS");
7
7
  const aiControllerState_1 = require("./aiControllerState");
8
8
  exports.initialAIControllerState = {
9
9
  aiControllerByChatSessionId: {},
10
+ partialQuestionAnswers: {},
10
11
  };
12
+ // Utility: Merge two arrays of Q&A pairs, combining responses for the same question
13
+ function mergeQuestionAnswerPairs(existing, newQAs) {
14
+ const merged = new Map();
15
+ // Add existing Q&A pairs
16
+ for (const qa of existing) {
17
+ merged.set(qa.questionAnswerId, Object.assign({}, qa));
18
+ }
19
+ // Merge new Q&A pairs
20
+ for (const newQA of newQAs) {
21
+ const existingQA = merged.get(newQA.questionAnswerId);
22
+ if (existingQA) {
23
+ // Merge responses
24
+ const mergedResponses = Object.assign(Object.assign({}, existingQA.responses), newQA.responses);
25
+ merged.set(newQA.questionAnswerId, Object.assign(Object.assign({}, newQA), { responses: mergedResponses }));
26
+ }
27
+ else {
28
+ // Add new Q&A pair
29
+ merged.set(newQA.questionAnswerId, newQA);
30
+ }
31
+ }
32
+ // Convert back to array and sort by question creation time
33
+ return Array.from(merged.values()).sort((a, b) => a.question.createdAt.valueOf() - b.question.createdAt.valueOf());
34
+ }
11
35
  // Utility: Convert chat history (array of MessagePayload) to AIControllerQuestionWithAnswer[]
12
36
  function chatHistoryToQA(history) {
37
+ var _a, _b, _c;
38
+ const questionGroups = new Map();
39
+ // First pass: collect all messages and group by message_id
40
+ for (const message of history) {
41
+ const messageId = message.message_id;
42
+ if (!questionGroups.has(messageId)) {
43
+ questionGroups.set(messageId, {
44
+ question: null,
45
+ responses: [],
46
+ });
47
+ }
48
+ const group = questionGroups.get(messageId);
49
+ if (!group) {
50
+ continue;
51
+ }
52
+ if (message.sender === 'user') {
53
+ // User message is the question
54
+ group.question = message;
55
+ }
56
+ else if (message.sender === 'agent') {
57
+ // Agent message is a response block
58
+ group.responses.push(message);
59
+ }
60
+ }
61
+ // Convert groups to AIControllerQuestionWithAnswer
13
62
  const questionAnswerPairs = [];
14
- let userMessageIndex = 0;
15
- while (userMessageIndex < history.length) {
16
- // Skip all but the last consecutive user message
17
- let lastUserMessageIndex = userMessageIndex;
18
- while (lastUserMessageIndex + 1 < history.length &&
19
- history[lastUserMessageIndex + 1].sender === 'user') {
20
- lastUserMessageIndex++;
63
+ for (const [messageId, group] of questionGroups.entries()) {
64
+ // Skip groups that don't have a question
65
+ if (!group.question) {
66
+ continue;
21
67
  }
22
- const userMessage = history[lastUserMessageIndex];
23
- if (userMessage.sender === 'user') {
24
- // Find the next agent message
25
- let agentMessageIndex = lastUserMessageIndex + 1;
26
- while (agentMessageIndex < history.length &&
27
- history[agentMessageIndex].sender !== 'agent') {
28
- agentMessageIndex++;
29
- }
30
- const agentMessage = agentMessageIndex < history.length &&
31
- history[agentMessageIndex].sender === 'agent'
32
- ? history[agentMessageIndex]
33
- : null;
34
- const questionAnswerId = `qa_${userMessage.message_id}`;
35
- const sender = (0, aiControllerState_1.toMessageSender)(userMessage.sender);
36
- const question = {
37
- messageId: userMessage.message_id,
38
- chatSessionId: userMessage.chat_session_id,
39
- userId: userMessage.user_id,
40
- sender,
41
- state: 'complete',
42
- questionText: userMessage.content,
43
- createdAt: (0, zeniDayJS_1.date)(userMessage.created_at),
44
- };
45
- const responses = {};
46
- if (agentMessage != null) {
47
- try {
48
- const parsedAgentMessage = (0, aiControllerState_1.toMessageType)(agentMessage.message_type) === 'json'
49
- ? JSON.parse(agentMessage.content)
50
- : {
51
- text: agentMessage.content,
52
- state: 'complete',
53
- type: 'answer',
54
- visualization: undefined,
55
- follow_up_questions: [],
56
- };
57
- const responseType = (0, aiControllerState_1.toAIControllerAnswerResponseType)(parsedAgentMessage.type);
58
- const responseState = (0, aiControllerState_1.toAIControllerAnswerStateType)(parsedAgentMessage.state);
59
- if (typeof parsedAgentMessage.text === 'string' &&
60
- aiControllerState_1.ALL_AI_CONTROLLER_ANSWER_RESPONSE_TYPES.includes(responseType)) {
61
- responses[responseType] = {
62
- messageId: agentMessage.message_id,
63
- chatSessionId: agentMessage.chat_session_id,
64
- userId: agentMessage.user_id,
65
- sender: (0, aiControllerState_1.toMessageSender)(agentMessage.sender),
66
- type: responseType,
67
- state: responseState,
68
- content: {
69
- text: parsedAgentMessage.text,
70
- visualization: parsedAgentMessage.visualization
71
- ? (0, exports.toAIControllerVisualization)(parsedAgentMessage.visualization)
72
- : undefined,
73
- followUpQuestions: Array.isArray(parsedAgentMessage.follow_up_questions)
74
- ? parsedAgentMessage.follow_up_questions
75
- : [],
76
- assumptions: '',
77
- insights: '',
78
- summarizedTitle: undefined,
79
- },
80
- createdAt: (0, zeniDayJS_1.date)(agentMessage.created_at),
81
- };
82
- }
83
- }
84
- catch (error) {
85
- console.error('Failed to parse agent message: ', error);
68
+ const question = {
69
+ messageId: group.question.message_id,
70
+ chatSessionId: group.question.chat_session_id,
71
+ userId: group.question.user_id,
72
+ sender: (0, aiControllerState_1.toMessageSender)(group.question.sender),
73
+ state: 'complete',
74
+ questionText: group.question.content,
75
+ createdAt: (0, zeniDayJS_1.date)(group.question.created_at),
76
+ };
77
+ const responses = {};
78
+ // Process each response for this question
79
+ for (const agentMessage of group.responses) {
80
+ try {
81
+ const parsedAgentMessage = (0, aiControllerState_1.toMessageType)(agentMessage.message_type) === 'json'
82
+ ? JSON.parse(agentMessage.content)
83
+ : {
84
+ text: agentMessage.content,
85
+ state: 'complete',
86
+ type: 'answer',
87
+ visualization: null,
88
+ follow_up_questions: [],
89
+ };
90
+ const responseType = (0, aiControllerState_1.toAIControllerAnswerResponseType)(parsedAgentMessage.type);
91
+ const responseState = (0, aiControllerState_1.toAIControllerAnswerStateType)(parsedAgentMessage.state);
92
+ if (typeof parsedAgentMessage.text === 'string' &&
93
+ aiControllerState_1.ALL_AI_CONTROLLER_ANSWER_RESPONSE_TYPES.includes(responseType)) {
94
+ responses[responseType] = {
95
+ messageId: agentMessage.message_id,
96
+ chatSessionId: agentMessage.chat_session_id,
97
+ userId: agentMessage.user_id,
98
+ sender: (0, aiControllerState_1.toMessageSender)(agentMessage.sender),
99
+ type: responseType,
100
+ state: responseState,
101
+ content: {
102
+ text: parsedAgentMessage.text,
103
+ visualization: parsedAgentMessage.visualization
104
+ ? (0, exports.toAIControllerVisualization)(parsedAgentMessage.visualization)
105
+ : undefined,
106
+ followUpQuestions: Array.isArray(parsedAgentMessage.follow_up_questions)
107
+ ? parsedAgentMessage.follow_up_questions
108
+ : [],
109
+ assumptions: (_a = parsedAgentMessage.assumptions) !== null && _a !== void 0 ? _a : '',
110
+ insights: (_b = parsedAgentMessage.insights) !== null && _b !== void 0 ? _b : '',
111
+ summarizedTitle: (_c = parsedAgentMessage.summarized_title) !== null && _c !== void 0 ? _c : undefined,
112
+ },
113
+ createdAt: (0, zeniDayJS_1.date)(agentMessage.created_at),
114
+ };
86
115
  }
87
116
  }
88
- questionAnswerPairs.push({
89
- questionAnswerId,
90
- question,
91
- responses,
92
- });
93
- userMessageIndex =
94
- agentMessage !== null
95
- ? agentMessageIndex + 1
96
- : lastUserMessageIndex + 1;
97
- }
98
- else {
99
- userMessageIndex++;
117
+ catch (error) {
118
+ console.error('Failed to parse agent message: ', error);
119
+ }
100
120
  }
121
+ questionAnswerPairs.push({
122
+ questionAnswerId: `qa_${messageId}`,
123
+ question,
124
+ responses,
125
+ });
101
126
  }
102
- return questionAnswerPairs;
127
+ // Sort by question creation time (oldest first)
128
+ return questionAnswerPairs.sort((a, b) => a.question.createdAt.valueOf() - b.question.createdAt.valueOf());
103
129
  }
104
130
  const toAIControllerVisualization = (visualization) => {
105
131
  var _a;
@@ -178,10 +204,23 @@ const aiController = (0, toolkit_1.createSlice)({
178
204
  chatSessionId,
179
205
  userId,
180
206
  chatSessionSummary: '',
207
+ newSession: true,
181
208
  createdAt,
182
209
  },
183
210
  questionAnswers: [],
184
211
  };
212
+ // Clear any existing partial Q&A when creating a new session
213
+ draft.partialQuestionAnswers[chatSessionId] = undefined;
214
+ },
215
+ updateNewSession(draft, action) {
216
+ const { chatSessionId } = action.payload;
217
+ if (draft.aiControllerByChatSessionId[chatSessionId] == null) {
218
+ console.warn(`session with id ${chatSessionId} not found`);
219
+ return;
220
+ }
221
+ if (draft.aiControllerByChatSessionId[chatSessionId].chatSession.newSession) {
222
+ draft.aiControllerByChatSessionId[chatSessionId].chatSession.newSession = false;
223
+ }
185
224
  },
186
225
  setSessions(draft, action) {
187
226
  action.payload.forEach((chatSessionPayload) => {
@@ -190,16 +229,145 @@ const aiController = (0, toolkit_1.createSlice)({
190
229
  chatSession: toChatSession(chatSessionPayload),
191
230
  questionAnswers: [],
192
231
  };
232
+ // Clear any existing partial Q&A for this session
233
+ draft.partialQuestionAnswers[chatSessionPayload.chat_session_id] =
234
+ undefined;
193
235
  });
194
236
  },
195
237
  setChatHistory(draft, action) {
196
- const { chatSessionId, history } = action.payload;
238
+ const { chatSessionId, history, isPaginationComplete = false, } = action.payload;
197
239
  if (draft.aiControllerByChatSessionId[chatSessionId] == null) {
198
240
  console.warn(`session with id ${chatSessionId} not found in setChatHistory`);
199
241
  return;
200
242
  }
201
- draft.aiControllerByChatSessionId[chatSessionId].questionAnswers =
202
- chatHistoryToQA(history);
243
+ const session = draft.aiControllerByChatSessionId[chatSessionId];
244
+ // Check if we have an existing partial Q&A pair for this session
245
+ const existingPartialQA = draft.partialQuestionAnswers[chatSessionId];
246
+ // Process new messages into Q&A pairs
247
+ const newQAs = chatHistoryToQA(history);
248
+ // If we have an existing partial Q&A pair, merge with new Q&A pairs
249
+ let mergedQAs;
250
+ if (existingPartialQA && newQAs.length > 0) {
251
+ // Check if any new QAs have the same message_id as the existing partial QA
252
+ const hasMatchingMessageId = newQAs.some((newQA) => newQA.question.messageId === existingPartialQA.question.messageId);
253
+ if (hasMatchingMessageId) {
254
+ // Merge the existing partial QA with new QAs that have matching message_id
255
+ mergedQAs = mergeQuestionAnswerPairs([existingPartialQA], newQAs);
256
+ }
257
+ else {
258
+ // No matching message_id - existing partial QA should be moved to complete
259
+ // and new QAs should be processed separately
260
+ mergedQAs = [existingPartialQA, ...newQAs];
261
+ }
262
+ }
263
+ else if (existingPartialQA && newQAs.length === 0) {
264
+ // Intermediate page scenario: existing partial QA but no new blocks to merge
265
+ // This means the partial QA is complete (no more blocks coming to complete it)
266
+ mergedQAs = [existingPartialQA];
267
+ }
268
+ else {
269
+ mergedQAs = newQAs;
270
+ }
271
+ // Separate complete and incomplete pairs
272
+ const completeQAs = [];
273
+ let incompleteQA;
274
+ for (const qa of mergedQAs) {
275
+ const questionText = qa.question.questionText;
276
+ const hasQuestion = questionText != null && questionText.trim().length > 0;
277
+ if (!hasQuestion) {
278
+ // Skip if no question text
279
+ continue;
280
+ }
281
+ // Check if this Q&A pair is complete
282
+ // A Q&A pair is considered complete if it has:
283
+ // 1. A question (already checked above)
284
+ // 2. At least one agent response (any type: thought, reasoning, analysis, answer, etc.)
285
+ // 3. AND EITHER:
286
+ // - At least one response with state: 'complete' (not 'partial')
287
+ // - OR multiple response blocks (thought, reasoning, analysis) even if they're all partial
288
+ const hasAnyResponse = Object.keys(qa.responses).length > 0;
289
+ const hasCompleteResponse = Object.values(qa.responses).some((response) => response.state === 'complete');
290
+ const hasMultipleResponseBlocks = Object.keys(qa.responses).length > 1;
291
+ // Check if this is an existing partial QA that should be moved to complete
292
+ // because no more blocks are coming to complete it
293
+ const isExistingPartialQA = existingPartialQA &&
294
+ qa.question.messageId === existingPartialQA.question.messageId &&
295
+ !hasCompleteResponse;
296
+ if (hasQuestion &&
297
+ hasAnyResponse &&
298
+ (hasCompleteResponse || hasMultipleResponseBlocks)) {
299
+ // This Q&A pair is complete - has question and either:
300
+ // - at least one complete response, OR
301
+ // - multiple response blocks (thought, reasoning, analysis) even if all partial
302
+ completeQAs.push(qa);
303
+ }
304
+ else if (hasQuestion &&
305
+ hasAnyResponse &&
306
+ isExistingPartialQA === true) {
307
+ // This is an existing partial QA that should be moved to complete
308
+ // because no more blocks are coming to complete it (intermediate page scenario)
309
+ completeQAs.push(qa);
310
+ }
311
+ else if (hasQuestion && hasAnyResponse) {
312
+ // This Q&A pair has a question and responses but they're all partial
313
+ // Keep as incomplete for potential merging with future pages
314
+ if (!incompleteQA) {
315
+ incompleteQA = qa;
316
+ }
317
+ }
318
+ else if (hasQuestion) {
319
+ // This Q&A pair has only a question, no responses yet
320
+ // Keep as incomplete for potential merging with future pages
321
+ if (!incompleteQA) {
322
+ incompleteQA = qa;
323
+ }
324
+ }
325
+ }
326
+ // Update partialQuestionAnswers based on completion status
327
+ if (isPaginationComplete) {
328
+ // If pagination is complete, we should have processed all messages
329
+ // Any remaining incomplete Q&A pairs should be moved to complete
330
+ if (incompleteQA) {
331
+ // When pagination is complete, move all incomplete Q&A pairs to complete
332
+ // This handles cases where:
333
+ // 1. Question with no responses (error in generation)
334
+ // 2. Question with only partial responses (error in generation)
335
+ // 3. Question with only answer blocks (no thought/reasoning blocks)
336
+ completeQAs.push(incompleteQA);
337
+ draft.partialQuestionAnswers[chatSessionId] = undefined;
338
+ }
339
+ else {
340
+ // No incomplete Q&A pairs, clear partialQuestionAnswers
341
+ draft.partialQuestionAnswers[chatSessionId] = undefined;
342
+ }
343
+ }
344
+ else if (incompleteQA) {
345
+ // Check if this is an intermediate page scenario
346
+ // If we had an existing partial QA but no new blocks were merged,
347
+ // then the partial QA should be considered complete
348
+ if (existingPartialQA && newQAs.length === 0) {
349
+ // Intermediate page: existing partial QA but no new blocks to merge
350
+ // Move the partial QA to complete since no more blocks are coming
351
+ completeQAs.push(incompleteQA);
352
+ draft.partialQuestionAnswers[chatSessionId] = undefined;
353
+ }
354
+ else {
355
+ // Pagination is not complete, keep incomplete Q&A pairs for future merging
356
+ draft.partialQuestionAnswers[chatSessionId] = incompleteQA;
357
+ }
358
+ }
359
+ else if (existingPartialQA && newQAs.length === 0) {
360
+ // Special case: existing partial QA but no new blocks and no incomplete QA in mergedQAs
361
+ // This means the existing partial QA should be moved to complete
362
+ completeQAs.push(existingPartialQA);
363
+ draft.partialQuestionAnswers[chatSessionId] = undefined;
364
+ }
365
+ else {
366
+ // No incomplete Q&A pairs, clear partialQuestionAnswers
367
+ draft.partialQuestionAnswers[chatSessionId] = undefined;
368
+ }
369
+ // Update state - append new complete Q&As to existing ones
370
+ session.questionAnswers = mergeQuestionAnswerPairs(session.questionAnswers, completeQAs);
203
371
  },
204
372
  /**
205
373
  * Upsert a Q/A pair: if the questionAnswerId exists, upsert the response block; if not, add a new Q/A entry.
@@ -281,10 +449,17 @@ const aiController = (0, toolkit_1.createSlice)({
281
449
  }
282
450
  },
283
451
  clearSession(draft, action) {
284
- delete draft.aiControllerByChatSessionId[action.payload];
452
+ var _a;
453
+ const sessionId = action.payload;
454
+ delete draft.aiControllerByChatSessionId[sessionId];
455
+ // Clear partial Q&A if it belongs to the cleared session
456
+ if (((_a = draft.partialQuestionAnswers) === null || _a === void 0 ? void 0 : _a[sessionId]) != null) {
457
+ draft.partialQuestionAnswers[sessionId] = undefined;
458
+ }
285
459
  },
286
460
  clearAIController(draft) {
287
461
  draft.aiControllerByChatSessionId = {};
462
+ draft.partialQuestionAnswers = {};
288
463
  },
289
464
  deleteQuestion(draft, action) {
290
465
  const { chatSessionId, questionAnswerId } = action.payload;
@@ -305,9 +480,10 @@ const toChatSession = (chatSessionPayload) => {
305
480
  return {
306
481
  chatSessionId: chat_session_id,
307
482
  userId: user_id,
483
+ newSession: false,
308
484
  chatSessionSummary: chat_session_summary !== null && chat_session_summary !== void 0 ? chat_session_summary : undefined,
309
485
  createdAt: (0, zeniDayJS_1.date)(created_at),
310
486
  };
311
487
  };
312
- _a = aiController.actions, exports.setNewSession = _a.setNewSession, exports.clearSession = _a.clearSession, exports.setSessions = _a.setSessions, exports.setChatHistory = _a.setChatHistory, exports.upsertAnswerPayload = _a.upsertAnswerPayload, exports.upsertOrAddQuestionAnswerPayload = _a.upsertOrAddQuestionAnswerPayload, exports.addQuestionPayload = _a.addQuestionPayload, exports.clearAIController = _a.clearAIController, exports.deleteQuestion = _a.deleteQuestion;
488
+ _a = aiController.actions, exports.setNewSession = _a.setNewSession, exports.clearSession = _a.clearSession, exports.setSessions = _a.setSessions, exports.setChatHistory = _a.setChatHistory, exports.upsertAnswerPayload = _a.upsertAnswerPayload, exports.upsertOrAddQuestionAnswerPayload = _a.upsertOrAddQuestionAnswerPayload, exports.addQuestionPayload = _a.addQuestionPayload, exports.clearAIController = _a.clearAIController, exports.deleteQuestion = _a.deleteQuestion, exports.updateNewSession = _a.updateNewSession;
313
489
  exports.default = aiController.reducer;
@@ -54,11 +54,11 @@ export interface ResponseBlockBase {
54
54
  }
55
55
  export interface AnswerContentBlockBase {
56
56
  text: string;
57
- visualization?: AIControllerVisualization;
58
57
  followUpQuestions: string[];
59
58
  assumptions: string;
60
59
  insights: string;
61
60
  summarizedTitle?: string;
61
+ visualization?: AIControllerVisualization;
62
62
  }
63
63
  export interface QuestionContentBlockBase extends ResponseBlockBase {
64
64
  questionText: string;
@@ -76,6 +76,7 @@ export interface ChatSession {
76
76
  chatSessionId: ID;
77
77
  userId: ID;
78
78
  createdAt: ZeniDate;
79
+ newSession: boolean;
79
80
  chatSessionSummary?: string;
80
81
  }
81
82
  export interface ChatSessionWithMessages {
@@ -84,4 +85,5 @@ export interface ChatSessionWithMessages {
84
85
  }
85
86
  export interface AIControllerState {
86
87
  aiControllerByChatSessionId: Record<ID, ChatSessionWithMessages>;
88
+ partialQuestionAnswers: Record<ID, AIControllerQuestionWithAnswer | undefined>;
87
89
  }
@@ -1,5 +1,6 @@
1
1
  import { FetchStateAndError, ID } from '../../commonStateTypes/common';
2
2
  import { PageToken } from '../../commonStateTypes/viewAndReport/viewAndReport';
3
+ import { ChatSessionPayload } from '../../entity/aiController/aiControllerPayload';
3
4
  import { AIControllerAnswerResponseType } from '../../entity/aiController/aiControllerState';
4
5
  import { ZeniAPIStatus } from '../../responsePayload';
5
6
  import { AIControllerViewState } from './aiControllerViewState';
@@ -25,8 +26,10 @@ export declare const submitQuestion: import("@reduxjs/toolkit").ActionCreatorWit
25
26
  sessionId: ID;
26
27
  }, "aiControllerView/createSessionSuccess">, createSessionFailure: import("@reduxjs/toolkit").ActionCreatorWithPayload<ZeniAPIStatus<Record<string, unknown>>, "aiControllerView/createSessionFailure">, fetchChatSessionsForUser: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<"aiControllerView/fetchChatSessionsForUser">, fetchChatSessionsForUserSuccess: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
27
28
  nextPageToken: PageToken;
28
- }, "aiControllerView/fetchChatSessionsForUserSuccess">, fetchChatSessionsForUserFailure: import("@reduxjs/toolkit").ActionCreatorWithPayload<ZeniAPIStatus<Record<string, unknown>>, "aiControllerView/fetchChatSessionsForUserFailure">, fetchChatHistory: import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<[chatSessionId: string], {
29
+ sessions: ChatSessionPayload[];
30
+ }, "aiControllerView/fetchChatSessionsForUserSuccess">, fetchChatSessionsForUserFailure: import("@reduxjs/toolkit").ActionCreatorWithPayload<ZeniAPIStatus<Record<string, unknown>>, "aiControllerView/fetchChatSessionsForUserFailure">, fetchChatHistory: import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<[chatSessionId: string, userId: string], {
29
31
  chatSessionId: string;
32
+ userId: string;
30
33
  }, "aiControllerView/fetchChatHistory", never, never>, fetchChatHistorySuccess: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
31
34
  chatSessionId: ID;
32
35
  nextPageToken: PageToken;
@@ -11,8 +11,9 @@ exports.initialAIControllerViewState = {
11
11
  scrollPositionByChatSessionId: {},
12
12
  currentChatSessionId: undefined,
13
13
  agentId: undefined,
14
- chatHistoryNextPageToken: { nextPageToken: null, hasMore: false },
14
+ chatHistoryNextPageToken: {},
15
15
  chatSessionsNextPageToken: { nextPageToken: null, hasMore: false },
16
+ chatSessionsFirstPageFetched: false,
16
17
  },
17
18
  allChatSessionIds: [],
18
19
  createSessionState: {
@@ -99,6 +100,13 @@ const aiControllerView = (0, toolkit_1.createSlice)({
99
100
  action.payload.nextPageToken;
100
101
  draft.uiState.chatSessionsNextPageToken.hasMore =
101
102
  action.payload.nextPageToken != null;
103
+ if (draft.uiState.chatSessionsFirstPageFetched == false) {
104
+ draft.uiState.chatSessionsFirstPageFetched = true;
105
+ }
106
+ draft.allChatSessionIds = Array.from(new Set([
107
+ ...draft.allChatSessionIds,
108
+ ...action.payload.sessions.map((session) => session.chat_session_id),
109
+ ]));
102
110
  },
103
111
  fetchChatSessionsForUserFailure(draft, action) {
104
112
  draft.fetchState = 'Error';
@@ -112,8 +120,8 @@ const aiControllerView = (0, toolkit_1.createSlice)({
112
120
  error: undefined,
113
121
  };
114
122
  },
115
- prepare(chatSessionId) {
116
- return { payload: { chatSessionId } };
123
+ prepare(chatSessionId, userId) {
124
+ return { payload: { chatSessionId, userId } };
117
125
  },
118
126
  },
119
127
  fetchChatHistorySuccess(draft, action) {
@@ -122,8 +130,18 @@ const aiControllerView = (0, toolkit_1.createSlice)({
122
130
  fetchState: 'Completed',
123
131
  error: undefined,
124
132
  };
125
- draft.uiState.chatHistoryNextPageToken.nextPageToken = nextPageToken;
126
- draft.uiState.chatHistoryNextPageToken.hasMore = nextPageToken != null;
133
+ draft.uiState.chatHistoryNextPageToken[chatSessionId] = {
134
+ nextPageToken,
135
+ hasMore: nextPageToken != null,
136
+ };
137
+ if (draft.responseStateByChatSessionId[chatSessionId] == null) {
138
+ draft.responseStateByChatSessionId[chatSessionId] = {
139
+ thought: { fetchState: 'Completed', error: undefined },
140
+ analysis: { fetchState: 'Completed', error: undefined },
141
+ reasoning: { fetchState: 'Completed', error: undefined },
142
+ answer: { fetchState: 'Completed', error: undefined },
143
+ };
144
+ }
127
145
  },
128
146
  fetchChatHistoryFailure(draft, action) {
129
147
  const { chatSessionId, error } = action.payload;
@@ -152,6 +170,10 @@ const aiControllerView = (0, toolkit_1.createSlice)({
152
170
  draft.createSessionState.fetchState = 'Completed';
153
171
  draft.createSessionState.error = undefined;
154
172
  draft.uiState.currentChatSessionId = sessionId;
173
+ draft.uiState.chatHistoryNextPageToken[sessionId] = {
174
+ nextPageToken: null,
175
+ hasMore: false,
176
+ };
155
177
  },
156
178
  createSessionFailure(draft, action) {
157
179
  draft.createSessionState.fetchState = 'Error';
@@ -7,6 +7,7 @@ import { AIControllerViewUIState } from './aiControllerViewState';
7
7
  export interface AIControllerViewSelector extends SelectorView, AIControllerSelectorView {
8
8
  createSessionState: FetchStateAndError;
9
9
  stopSubmitStateByChatSessionId: Record<ID, FetchStateAndError>;
10
+ fetchChatSessionHistoryByChatSessionId: Record<ID, FetchStateAndError>;
10
11
  responseStateByChatSessionId: Record<ID, {
11
12
  thought: FetchStateAndError;
12
13
  analysis: FetchStateAndError;
@@ -3,12 +3,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getAIControllerView = void 0;
4
4
  const aiControllerSelector_1 = require("../../entity/aiController/aiControllerSelector");
5
5
  const getAIControllerView = (state) => {
6
- const { fetchState, error, uiState, createSessionState, stopSubmitStateByChatSessionId, responseStateByChatSessionId, } = state.aiControllerViewState;
6
+ const { fetchState, error, uiState, createSessionState, stopSubmitStateByChatSessionId, responseStateByChatSessionId, fetchChatSessionHistoryByChatSessionId, } = state.aiControllerViewState;
7
7
  return Object.assign(Object.assign({ fetchState,
8
8
  error,
9
9
  createSessionState,
10
10
  stopSubmitStateByChatSessionId,
11
11
  responseStateByChatSessionId,
12
- uiState }, (0, aiControllerSelector_1.getAIControllerSelectorView)(state.aiControllerState)), { version: 1 });
12
+ uiState,
13
+ fetchChatSessionHistoryByChatSessionId }, (0, aiControllerSelector_1.getAIControllerSelectorView)(state.aiControllerState)), { version: 1 });
13
14
  };
14
15
  exports.getAIControllerView = getAIControllerView;
@@ -11,10 +11,13 @@ export interface AIControllerViewUIState {
11
11
  scrollTop: number;
12
12
  };
13
13
  };
14
+ chatHistoryNextPageToken: {
15
+ [chatSessionId: ID]: PageTokenState;
16
+ };
17
+ chatSessionsNextPageToken: PageTokenState;
18
+ chatSessionsFirstPageFetched: boolean;
14
19
  currentChatSessionId?: ID;
15
20
  agentId?: ID;
16
- chatHistoryNextPageToken: PageTokenState;
17
- chatSessionsNextPageToken: PageTokenState;
18
21
  }
19
22
  export interface AIControllerViewState extends FetchStateAndError {
20
23
  uiState: AIControllerViewUIState;
@@ -5,4 +5,4 @@ import { RootState } from '../../../reducer';
5
5
  import { ZeniAPI } from '../../../zeniAPI';
6
6
  import { fetchChatHistory, fetchChatHistoryFailure, fetchChatHistorySuccess } from '../aiControllerViewReducer';
7
7
  export type ActionType = ReturnType<typeof fetchChatHistory> | ReturnType<typeof setChatHistory> | ReturnType<typeof fetchChatHistorySuccess> | ReturnType<typeof fetchChatHistoryFailure>;
8
- export declare const fetchChatHistoryEpic: (actions$: ActionsObservable<ActionType>, _: StateObservable<RootState>, zeniAPI: ZeniAPI) => Observable<ActionType>;
8
+ export declare const fetchChatHistoryEpic: (actions$: ActionsObservable<ActionType>, state$: StateObservable<RootState>, zeniAPI: ZeniAPI) => Observable<ActionType>;
@@ -6,15 +6,27 @@ const operators_1 = require("rxjs/operators");
6
6
  const aiControllerReducer_1 = require("../../../entity/aiController/aiControllerReducer");
7
7
  const responsePayload_1 = require("../../../responsePayload");
8
8
  const aiControllerViewReducer_1 = require("../aiControllerViewReducer");
9
- const fetchChatHistoryEpic = (actions$, _, zeniAPI) => actions$.pipe((0, operators_1.filter)(aiControllerViewReducer_1.fetchChatHistory.match), (0, operators_1.switchMap)((action) => {
10
- const { chatSessionId } = action.payload;
9
+ const fetchChatHistoryEpic = (actions$, state$, zeniAPI) => actions$.pipe((0, operators_1.filter)(aiControllerViewReducer_1.fetchChatHistory.match), (0, operators_1.mergeMap)((action) => {
10
+ var _a, _b;
11
+ const { chatSessionId, userId } = action.payload;
12
+ const state = state$.value;
13
+ const pageToken = (_b = (_a = state.aiControllerViewState.uiState.chatHistoryNextPageToken[chatSessionId]) === null || _a === void 0 ? void 0 : _a.nextPageToken) !== null && _b !== void 0 ? _b : null;
14
+ const queryParams = new URLSearchParams();
15
+ queryParams.append('page_size', '20');
16
+ queryParams.append('chat_session_id', chatSessionId);
17
+ queryParams.append('user_id', userId);
18
+ if (pageToken != null) {
19
+ queryParams.append('page_token', pageToken);
20
+ }
11
21
  return zeniAPI
12
- .getJSON(`${zeniAPI.apiEndPoints.aiControllerMicroServiceBaseUrl}/1.0/sessions/${chatSessionId}/messages`)
22
+ .getJSON(`${zeniAPI.apiEndPoints.aiControllerMicroServiceBaseUrl}/1.0/history?${queryParams.toString()}`)
13
23
  .pipe((0, operators_1.mergeMap)((response) => {
14
24
  if ((0, responsePayload_1.isSuccessResponse)(response) && response.data != null) {
25
+ const isPaginationComplete = response.data.next_page_token == null;
15
26
  return (0, rxjs_1.of)((0, aiControllerReducer_1.setChatHistory)({
16
27
  chatSessionId,
17
28
  history: response.data.messages,
29
+ isPaginationComplete,
18
30
  }), (0, aiControllerViewReducer_1.fetchChatHistorySuccess)({
19
31
  chatSessionId,
20
32
  nextPageToken: response.data.next_page_token,
@@ -5,4 +5,4 @@ import { RootState } from '../../../reducer';
5
5
  import { ZeniAPI } from '../../../zeniAPI';
6
6
  import { fetchChatSessionsForUser, fetchChatSessionsForUserFailure, fetchChatSessionsForUserSuccess } from '../aiControllerViewReducer';
7
7
  export type ActionType = ReturnType<typeof fetchChatSessionsForUser> | ReturnType<typeof setSessions> | ReturnType<typeof fetchChatSessionsForUserSuccess> | ReturnType<typeof fetchChatSessionsForUserFailure>;
8
- export declare const fetchChatSessionsForUserEpic: (actions$: ActionsObservable<ActionType>, _: StateObservable<RootState>, zeniAPI: ZeniAPI) => Observable<ActionType>;
8
+ export declare const fetchChatSessionsForUserEpic: (actions$: ActionsObservable<ActionType>, state$: StateObservable<RootState>, zeniAPI: ZeniAPI) => Observable<ActionType>;
@@ -6,12 +6,24 @@ const operators_1 = require("rxjs/operators");
6
6
  const aiControllerReducer_1 = require("../../../entity/aiController/aiControllerReducer");
7
7
  const responsePayload_1 = require("../../../responsePayload");
8
8
  const aiControllerViewReducer_1 = require("../aiControllerViewReducer");
9
- const fetchChatSessionsForUserEpic = (actions$, _, zeniAPI) => actions$.pipe((0, operators_1.filter)(aiControllerViewReducer_1.fetchChatSessionsForUser.match), (0, operators_1.switchMap)(() => {
9
+ const fetchChatSessionsForUserEpic = (actions$, state$, zeniAPI) => actions$.pipe((0, operators_1.filter)(aiControllerViewReducer_1.fetchChatSessionsForUser.match), (0, operators_1.switchMap)(() => {
10
+ var _a;
11
+ const state = state$.value;
12
+ const pageToken = (_a = state.aiControllerViewState.uiState.chatSessionsNextPageToken
13
+ .nextPageToken) !== null && _a !== void 0 ? _a : null;
14
+ const queryParams = new URLSearchParams();
15
+ queryParams.append('page_size', '50');
16
+ if (pageToken != null) {
17
+ queryParams.append('page_token', pageToken);
18
+ }
10
19
  return zeniAPI
11
- .getJSON(`${zeniAPI.apiEndPoints.aiControllerMicroServiceBaseUrl}/1.0/sessions`)
20
+ .getJSON(`${zeniAPI.apiEndPoints.aiControllerMicroServiceBaseUrl}/1.0/sessions?${queryParams.toString()}`)
12
21
  .pipe((0, operators_1.mergeMap)((response) => {
13
22
  if ((0, responsePayload_1.isSuccessResponse)(response) && response.data != null) {
14
- return (0, rxjs_1.of)((0, aiControllerReducer_1.setSessions)(response.data.sessions), (0, aiControllerViewReducer_1.fetchChatSessionsForUserSuccess)({
23
+ // Filter out empty sessions that don't have a summary
24
+ const sessions = response.data.sessions.filter((session) => Boolean(session.chat_session_summary) === true);
25
+ return (0, rxjs_1.of)((0, aiControllerReducer_1.setSessions)(sessions), (0, aiControllerViewReducer_1.fetchChatSessionsForUserSuccess)({
26
+ sessions,
15
27
  nextPageToken: response.data.next_page_token,
16
28
  }));
17
29
  }
@@ -1,8 +1,8 @@
1
1
  import { ActionsObservable, StateObservable } from 'redux-observable';
2
2
  import { Observable } from 'rxjs';
3
- import { upsertOrAddQuestionAnswerPayload } from '../../../entity/aiController/aiControllerReducer';
3
+ import { updateNewSession, upsertOrAddQuestionAnswerPayload } from '../../../entity/aiController/aiControllerReducer';
4
4
  import { RootState } from '../../../reducer';
5
5
  import { ZeniAPI } from '../../../zeniAPI';
6
6
  import { submitQuestion, submitQuestionFailure, submitQuestionSuccess } from '../aiControllerViewReducer';
7
- export type ActionType = ReturnType<typeof submitQuestion> | ReturnType<typeof submitQuestionSuccess> | ReturnType<typeof submitQuestionFailure> | ReturnType<typeof upsertOrAddQuestionAnswerPayload>;
7
+ export type ActionType = ReturnType<typeof submitQuestion> | ReturnType<typeof updateNewSession> | ReturnType<typeof submitQuestionSuccess> | ReturnType<typeof submitQuestionFailure> | ReturnType<typeof upsertOrAddQuestionAnswerPayload>;
8
8
  export declare const submitQuestionEpic: (actions$: ActionsObservable<ActionType>, state$: StateObservable<RootState>, zeniAPI: ZeniAPI) => Observable<ActionType>;
@@ -6,7 +6,7 @@ const operators_1 = require("rxjs/operators");
6
6
  const aiControllerReducer_1 = require("../../../entity/aiController/aiControllerReducer");
7
7
  const responsePayload_1 = require("../../../responsePayload");
8
8
  const aiControllerViewReducer_1 = require("../aiControllerViewReducer");
9
- const submitQuestionEpic = (actions$, state$, zeniAPI) => actions$.pipe((0, operators_1.filter)(aiControllerViewReducer_1.submitQuestion.match), (0, operators_1.switchMap)((action) => {
9
+ const submitQuestionEpic = (actions$, state$, zeniAPI) => actions$.pipe((0, operators_1.filter)(aiControllerViewReducer_1.submitQuestion.match), (0, operators_1.mergeMap)((action) => {
10
10
  const { questionAnswerId, userId, questionId, createdAt, question } = action.payload;
11
11
  const { aiControllerViewState } = state$.value;
12
12
  const { uiState } = aiControllerViewState;
@@ -16,7 +16,9 @@ const submitQuestionEpic = (actions$, state$, zeniAPI) => actions$.pipe((0, oper
16
16
  if (agentId == null || sessionId == null || Boolean(question) == false) {
17
17
  throw new Error('Invalid State');
18
18
  }
19
- return zeniAPI
19
+ // First, update the session to mark it as no longer new
20
+ const updateSessionAction = (0, aiControllerReducer_1.updateNewSession)({ chatSessionId: sessionId });
21
+ return (0, rxjs_1.concat)((0, rxjs_1.of)(updateSessionAction), zeniAPI
20
22
  .postAndGetJSON(`${zeniAPI.apiEndPoints.aiControllerMicroServiceBaseUrl}/1.0/agents/${agentId}`, payload)
21
23
  .pipe((0, operators_1.mergeMap)((response) => {
22
24
  if ((0, responsePayload_1.isSuccessResponse)(response) && response.data != null) {
@@ -32,14 +34,13 @@ const submitQuestionEpic = (actions$, state$, zeniAPI) => actions$.pipe((0, oper
32
34
  answerPayload: response.data,
33
35
  userId,
34
36
  insertQuestionAnswer: false, // Don't insert the Q/A here after stopSubmitSuccess
35
- }));
36
- actions.push((0, aiControllerViewReducer_1.submitQuestionSuccess)());
37
+ }), (0, aiControllerViewReducer_1.submitQuestionSuccess)());
37
38
  return (0, rxjs_1.of)(...actions);
38
39
  }
39
40
  else {
40
41
  return (0, rxjs_1.of)((0, aiControllerViewReducer_1.submitQuestionFailure)(response.status));
41
42
  }
42
43
  }), (0, operators_1.catchError)((error) => (0, rxjs_1.of)((0, aiControllerViewReducer_1.submitQuestionFailure)((0, responsePayload_1.createZeniAPIStatus)('Unexpected Error', 'Submit question REST API call errored out' +
43
- JSON.stringify(error))))));
44
+ JSON.stringify(error)))))));
44
45
  }));
45
46
  exports.submitQuestionEpic = submitQuestionEpic;
@@ -4,9 +4,9 @@ import { SubscriptionViewSortKey } from '../../subscriptionView/types/subscripti
4
4
  import { FilterCategoryValueType } from '../helpers/cockpitHelpers';
5
5
  import { CockpitDownloadTabsID, CockpitTabsFileType } from './cockpitTabsID';
6
6
  export declare const toPortfolioSortKeyType: (v: string) => "expenses" | "runway" | "companyName" | "cashBalance" | "revenues" | "netIncome";
7
- export declare const toManagementSortKeyType: (v: string) => "status" | "bookCloseDate" | "taxStatus" | "controller" | "preparer" | "reviewer" | "annualRecurringRevenue" | "monthlyRecurringRevenue" | "companyName" | "group" | "task" | "notes" | "plans" | "customerSuccessManager" | "apAnalyst" | "reportSent" | "taxLead" | "satisfaction";
7
+ export declare const toManagementSortKeyType: (v: string) => "status" | "bookCloseDate" | "group" | "taxStatus" | "controller" | "preparer" | "reviewer" | "annualRecurringRevenue" | "monthlyRecurringRevenue" | "companyName" | "task" | "notes" | "plans" | "customerSuccessManager" | "apAnalyst" | "reportSent" | "taxLead" | "satisfaction";
8
8
  export declare const toOnboardingSortKeyType: (v: string) => "companyName" | "identityVerified" | "businessVerified" | "bankAccountLinked" | "qboLinked" | "groupCreated" | "customerCreated" | "notes" | "enroller" | "invite" | "latestCreated";
9
- export declare const toHealthSortKeyType: (v: string) => "runway" | "controller" | "monthlyRecurringRevenue" | "companyName" | "group" | "companyEngagementMetricsUpdatedAt" | "netPromoterScore" | "customerSatisfactionScore" | "externalProductServiceExposure" | "customerSuccessManager" | "lastReportSent" | "issues" | "productVerticals" | "churnProbability" | "subscriptionDate" | "renewalDate" | "dashboardLastSeen" | "lastEngagement";
9
+ export declare const toHealthSortKeyType: (v: string) => "group" | "runway" | "controller" | "monthlyRecurringRevenue" | "companyName" | "companyEngagementMetricsUpdatedAt" | "netPromoterScore" | "customerSatisfactionScore" | "externalProductServiceExposure" | "customerSuccessManager" | "lastReportSent" | "issues" | "productVerticals" | "churnProbability" | "subscriptionDate" | "renewalDate" | "dashboardLastSeen" | "lastEngagement";
10
10
  export declare const toTaskManagerSortKeyType: (v: string) => "status" | "name" | "dueDate" | "tags" | "createdBy" | "priority" | "taskName" | "assignee";
11
11
  export type PortfolioViewSortKey = ReturnType<typeof toPortfolioSortKeyType>;
12
12
  export type ManagementViewSortKey = ReturnType<typeof toManagementSortKeyType>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zeniai/client-epic-state",
3
- "version": "4.16.81",
3
+ "version": "4.16.82",
4
4
  "description": "Shared module between Web & Mobile containing required abstractions for state management, async network communication. ",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",