@servicetitan/titan-chatbot-api 7.1.1 → 8.0.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 (112) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/dist/api-client/__mocks__/chatbot-api-client.mock.js +21 -47
  3. package/dist/api-client/__mocks__/chatbot-api-client.mock.js.map +1 -1
  4. package/dist/api-client/base/chatbot-api-client.js +3 -4
  5. package/dist/api-client/base/chatbot-api-client.js.map +1 -1
  6. package/dist/api-client/help-center/__tests__/converter-from-models.test.js +48 -15
  7. package/dist/api-client/help-center/__tests__/converter-from-models.test.js.map +1 -1
  8. package/dist/api-client/help-center/__tests__/converter-to-models.test.js +23 -22
  9. package/dist/api-client/help-center/__tests__/converter-to-models.test.js.map +1 -1
  10. package/dist/api-client/help-center/chatbot-api-client.js +51 -52
  11. package/dist/api-client/help-center/chatbot-api-client.js.map +1 -1
  12. package/dist/api-client/help-center/converter-from-models.js +15 -12
  13. package/dist/api-client/help-center/converter-from-models.js.map +1 -1
  14. package/dist/api-client/help-center/converter-to-models.js +29 -26
  15. package/dist/api-client/help-center/converter-to-models.js.map +1 -1
  16. package/dist/api-client/help-center/index.d.ts +2 -1
  17. package/dist/api-client/help-center/index.d.ts.map +1 -1
  18. package/dist/api-client/help-center/index.js +1 -0
  19. package/dist/api-client/help-center/index.js.map +1 -1
  20. package/dist/api-client/help-center/native-client.js +1192 -2884
  21. package/dist/api-client/help-center/native-client.js.map +1 -1
  22. package/dist/api-client/index.d.ts +2 -1
  23. package/dist/api-client/index.d.ts.map +1 -1
  24. package/dist/api-client/index.js +14 -7
  25. package/dist/api-client/index.js.map +1 -1
  26. package/dist/api-client/models/__mocks__/models.mock.js +154 -124
  27. package/dist/api-client/models/__mocks__/models.mock.js.map +1 -1
  28. package/dist/api-client/models/index.d.ts +2 -1
  29. package/dist/api-client/models/index.d.ts.map +1 -1
  30. package/dist/api-client/models/index.js +8 -7
  31. package/dist/api-client/models/index.js.map +1 -1
  32. package/dist/api-client/titan-chat/__tests__/native-client.test.js +6 -6
  33. package/dist/api-client/titan-chat/__tests__/native-client.test.js.map +1 -1
  34. package/dist/api-client/titan-chat/chatbot-api-client.js +40 -35
  35. package/dist/api-client/titan-chat/chatbot-api-client.js.map +1 -1
  36. package/dist/api-client/titan-chat/index.d.ts +2 -1
  37. package/dist/api-client/titan-chat/index.d.ts.map +1 -1
  38. package/dist/api-client/titan-chat/index.js +1 -0
  39. package/dist/api-client/titan-chat/index.js.map +1 -1
  40. package/dist/api-client/titan-chat/native-client.d.ts +14 -14
  41. package/dist/api-client/titan-chat/native-client.d.ts.map +1 -1
  42. package/dist/api-client/titan-chat/native-client.js +359 -809
  43. package/dist/api-client/titan-chat/native-client.js.map +1 -1
  44. package/dist/api-client/utils/__tests__/model-utils.test.js +454 -191
  45. package/dist/api-client/utils/__tests__/model-utils.test.js.map +1 -1
  46. package/dist/api-client/utils/model-utils.d.ts.map +1 -1
  47. package/dist/api-client/utils/model-utils.js +28 -25
  48. package/dist/api-client/utils/model-utils.js.map +1 -1
  49. package/dist/hooks/use-customization-chatbot.js +2 -1
  50. package/dist/hooks/use-customization-chatbot.js.map +1 -1
  51. package/dist/index.d.ts +1 -1
  52. package/dist/index.d.ts.map +1 -1
  53. package/dist/index.js +6 -5
  54. package/dist/index.js.map +1 -1
  55. package/dist/models/chatbot-customizations.js +2 -1
  56. package/dist/models/chatbot-customizations.js.map +1 -1
  57. package/dist/models/index.d.ts +1 -1
  58. package/dist/models/index.d.ts.map +1 -1
  59. package/dist/models/index.js +2 -1
  60. package/dist/models/index.js.map +1 -1
  61. package/dist/stores/__tests__/chatbot-ui-backend.store.test.js +267 -172
  62. package/dist/stores/__tests__/chatbot-ui-backend.store.test.js.map +1 -1
  63. package/dist/stores/__tests__/chatbot-ui.store.test.js +61 -64
  64. package/dist/stores/__tests__/chatbot-ui.store.test.js.map +1 -1
  65. package/dist/stores/__tests__/filter.store.test.js +243 -116
  66. package/dist/stores/__tests__/filter.store.test.js.map +1 -1
  67. package/dist/stores/__tests__/initialize.store.test.js +9 -8
  68. package/dist/stores/__tests__/initialize.store.test.js.map +1 -1
  69. package/dist/stores/__tests__/message-feedback-guardrail.store.test.js +8 -7
  70. package/dist/stores/__tests__/message-feedback-guardrail.store.test.js.map +1 -1
  71. package/dist/stores/__tests__/message-feedback.store.test.js +34 -27
  72. package/dist/stores/__tests__/message-feedback.store.test.js.map +1 -1
  73. package/dist/stores/__tests__/session-feedback.store.test.js +9 -8
  74. package/dist/stores/__tests__/session-feedback.store.test.js.map +1 -1
  75. package/dist/stores/chatbot-ui-backend.store.js +171 -240
  76. package/dist/stores/chatbot-ui-backend.store.js.map +1 -1
  77. package/dist/stores/chatbot-ui.store.js +73 -46
  78. package/dist/stores/chatbot-ui.store.js.map +1 -1
  79. package/dist/stores/filter.store.js +298 -378
  80. package/dist/stores/filter.store.js.map +1 -1
  81. package/dist/stores/index.d.ts +5 -3
  82. package/dist/stores/index.d.ts.map +1 -1
  83. package/dist/stores/index.js +3 -2
  84. package/dist/stores/index.js.map +1 -1
  85. package/dist/stores/initialize.store.js +55 -51
  86. package/dist/stores/initialize.store.js.map +1 -1
  87. package/dist/stores/message-feedback-base.store.js +2 -1
  88. package/dist/stores/message-feedback-base.store.js.map +1 -1
  89. package/dist/stores/message-feedback-guardrail.store.js +50 -47
  90. package/dist/stores/message-feedback-guardrail.store.js.map +1 -1
  91. package/dist/stores/message-feedback.store.js +84 -89
  92. package/dist/stores/message-feedback.store.js.map +1 -1
  93. package/dist/stores/session-feedback.store.js +46 -39
  94. package/dist/stores/session-feedback.store.js.map +1 -1
  95. package/dist/utils/__tests__/axios-utils.test.js +8 -7
  96. package/dist/utils/__tests__/axios-utils.test.js.map +1 -1
  97. package/dist/utils/axios-utils.js +9 -7
  98. package/dist/utils/axios-utils.js.map +1 -1
  99. package/dist/utils/test-utils.js +5 -5
  100. package/dist/utils/test-utils.js.map +1 -1
  101. package/package.json +3 -3
  102. package/src/api-client/help-center/index.ts +2 -1
  103. package/src/api-client/help-center/native-client.ts +4 -4
  104. package/src/api-client/index.ts +2 -6
  105. package/src/api-client/models/index.ts +15 -13
  106. package/src/api-client/titan-chat/index.ts +2 -1
  107. package/src/api-client/titan-chat/native-client.ts +17 -14
  108. package/src/api-client/utils/model-utils.ts +4 -8
  109. package/src/index.ts +1 -1
  110. package/src/models/index.ts +1 -1
  111. package/src/stores/index.ts +5 -12
  112. package/tsconfig.tsbuildinfo +1 -1
@@ -1,50 +1,43 @@
1
+ function _define_property(obj, key, value) {
2
+ if (key in obj) {
3
+ Object.defineProperty(obj, key, {
4
+ value: value,
5
+ enumerable: true,
6
+ configurable: true,
7
+ writable: true
8
+ });
9
+ } else {
10
+ obj[key] = value;
11
+ }
12
+ return obj;
13
+ }
1
14
  import { expect } from '@jest/globals';
2
15
  import { Log } from '@servicetitan/log-service';
3
- import { ChatError, ChatRunState, } from '@servicetitan/titan-chat-ui-common';
16
+ import { ChatError, ChatRunState } from '@servicetitan/titan-chat-ui-common';
4
17
  import { CHATBOT_API_CLIENT, Models, ModelsMocks } from '../../api-client';
5
18
  import { ChatbotApiClientMock } from '../../api-client/__mocks__/chatbot-api-client.mock';
6
19
  import { initTestContainer } from '../../utils/test-utils';
7
20
  import { ChatbotUiBackendStore } from '../chatbot-ui-backend.store';
8
21
  import { CHATBOT_UI_STORE_TOKEN, ChatbotUiStore } from '../chatbot-ui.store';
9
22
  const WELCOME_MESSAGE = 'Hi there! I’m Agent, an AI chatbot powered by Titan Intelligence. I’m here to answer your questions about using ServiceTitan. You can ask me things like "How to merge duplicate customers?". Do you have a question for me?';
10
- const initContainer = initTestContainer(ChatbotUiBackendStore, container => {
11
- container
12
- .bind(Log)
13
- .to(class {
14
- constructor() {
15
- Object.defineProperty(this, "error", {
16
- enumerable: true,
17
- configurable: true,
18
- writable: true,
19
- value: jest.fn()
20
- });
21
- Object.defineProperty(this, "info", {
22
- enumerable: true,
23
- configurable: true,
24
- writable: true,
25
- value: jest.fn()
26
- });
27
- Object.defineProperty(this, "warning", {
28
- enumerable: true,
29
- configurable: true,
30
- writable: true,
31
- value: jest.fn()
32
- });
23
+ const initContainer = initTestContainer(ChatbotUiBackendStore, (container)=>{
24
+ container.bind(Log).to(class {
25
+ constructor(){
26
+ _define_property(this, "error", jest.fn());
27
+ _define_property(this, "info", jest.fn());
28
+ _define_property(this, "warning", jest.fn());
33
29
  }
34
- })
35
- .inSingletonScope();
36
- container
37
- .bind(CHATBOT_API_CLIENT)
38
- .toConstantValue(new ChatbotApiClientMock());
30
+ }).inSingletonScope();
31
+ container.bind(CHATBOT_API_CLIENT).toConstantValue(new ChatbotApiClientMock());
39
32
  container.bind(CHATBOT_UI_STORE_TOKEN).to(ChatbotUiStore).inSingletonScope();
40
33
  });
41
- describe('[ChatbotUiBackendStore]', () => {
34
+ describe('[ChatbotUiBackendStore]', ()=>{
42
35
  let container;
43
36
  let store;
44
37
  let chatbotApi;
45
38
  let chatUiStore;
46
39
  let log;
47
- beforeEach(() => {
40
+ beforeEach(()=>{
48
41
  container = initContainer();
49
42
  store = container.get(ChatbotUiBackendStore);
50
43
  chatbotApi = container.get(CHATBOT_API_CLIENT);
@@ -53,63 +46,69 @@ describe('[ChatbotUiBackendStore]', () => {
53
46
  jest.useFakeTimers();
54
47
  jest.setSystemTime(new Date('2000-01-01T00:00:00.000Z'));
55
48
  });
56
- afterEach(() => {
49
+ afterEach(()=>{
57
50
  jest.useRealTimers();
58
51
  jest.clearAllMocks();
59
52
  });
60
- describe('with subscription', () => {
53
+ describe('with subscription', ()=>{
61
54
  let spyOn;
62
55
  let spyOff;
63
- beforeEach(() => {
56
+ beforeEach(()=>{
64
57
  spyOn = jest.spyOn(chatUiStore, 'on');
65
58
  spyOff = jest.spyOn(chatUiStore, 'off');
66
59
  });
67
- test('should subscribe on external events', () => {
60
+ test('should subscribe on external events', ()=>{
68
61
  store.subscribe();
69
62
  expect(spyOn).toHaveBeenCalledTimes(8);
70
63
  expect(spyOff).toHaveBeenCalledTimes(8);
71
64
  });
72
- test('should unsubscribe from external events', () => {
65
+ test('should unsubscribe from external events', ()=>{
73
66
  store.unsubscribe();
74
67
  expect(chatUiStore.on).toHaveBeenCalledTimes(0);
75
68
  expect(chatUiStore.off).toHaveBeenCalledTimes(8);
76
69
  });
77
70
  });
78
- describe('with run', () => {
79
- const runChatUiEventListener = async (listener, args) => {
80
- return new Promise((resolve, reject) => {
81
- listener.apply(store, [resolve, reject, ...args]);
71
+ describe('with run', ()=>{
72
+ const runChatUiEventListener = async (listener, args)=>{
73
+ return new Promise((resolve, reject)=>{
74
+ listener.apply(store, [
75
+ resolve,
76
+ reject,
77
+ ...args
78
+ ]);
82
79
  });
83
80
  };
84
- const runStore = async () => {
81
+ const runStore = async ()=>{
85
82
  await runChatUiEventListener(store.handleRun, []);
86
83
  await jest.advanceTimersToNextTimerAsync(); // Wait for the timer to be set
87
84
  };
88
- const mockChatbotSession = () => {
85
+ const mockChatbotSession = ()=>{
89
86
  chatbotApi.getOptions.mockResolvedValue(ModelsMocks.mockFrontendModel());
90
87
  chatbotApi.postSession.mockResolvedValue(ModelsMocks.mockSession());
91
88
  chatbotApi.deleteSession.mockResolvedValue(ModelsMocks.mockSession());
92
89
  };
93
- const mockChatbotAnswer = (answer) => {
90
+ const mockChatbotAnswer = (answer)=>{
94
91
  const answerMock = ModelsMocks.mockBotMessage({
95
92
  id: 123,
96
- answer,
93
+ answer
97
94
  });
98
95
  chatbotApi.postMessage.mockResolvedValue(answerMock);
99
96
  return answerMock;
100
97
  };
101
- const mockChatbotAnswers = (answers) => {
102
- const answerMocks = answers.map(({ answer, answerId }, index) => ModelsMocks.mockBotMessage({
103
- id: answerId !== null && answerId !== void 0 ? answerId : 123 + index,
104
- answer,
105
- }));
98
+ const mockChatbotAnswers = (answers)=>{
99
+ const answerMocks = answers.map(({ answer, answerId }, index)=>ModelsMocks.mockBotMessage({
100
+ id: answerId !== null && answerId !== void 0 ? answerId : 123 + index,
101
+ answer
102
+ }));
106
103
  // Mock in reverse order to simulate responses coming back in different order
107
- [...answerMocks].reverse().forEach(mock => {
104
+ [
105
+ ...answerMocks
106
+ ].reverse().forEach((mock)=>{
108
107
  chatbotApi.postMessage.mockResolvedValueOnce(mock);
109
108
  });
110
109
  return answerMocks;
111
110
  };
112
- const ensureChatStarted = () => {
111
+ const ensureChatStarted = ()=>{
113
112
  expect(chatUiStore.isAgentTyping).toBe(false);
114
113
  expect(chatUiStore.isFilePickerEnabled).toBe(false);
115
114
  expect(chatUiStore.file).toBe(undefined);
@@ -120,7 +119,7 @@ describe('[ChatbotUiBackendStore]', () => {
120
119
  expect(chatUiStore.scrollCounter > 1).toBe(true);
121
120
  expect(chatUiStore.status).toBe(ChatRunState.Started);
122
121
  };
123
- test('should run chatbot without stored session', async () => {
122
+ test('should run chatbot without stored session', async ()=>{
124
123
  store.subscribe();
125
124
  mockChatbotSession();
126
125
  await runStore();
@@ -131,8 +130,7 @@ describe('[ChatbotUiBackendStore]', () => {
131
130
  /*
132
131
  * We don't have session yet because we've just initialized the chat with welcome message
133
132
  * and we don't do any chatbotApi.postSession call
134
- */
135
- expect(store.session).toBeUndefined();
133
+ */ expect(store.session).toBeUndefined();
136
134
  expect(chatbotApi.postSession).toHaveBeenCalledTimes(0);
137
135
  const botAnswer = mockChatbotAnswer('bot answer');
138
136
  await chatUiStore.sendMessageText('user question');
@@ -147,7 +145,7 @@ describe('[ChatbotUiBackendStore]', () => {
147
145
  // Ensure that question data contains answerId from bot answer
148
146
  expect(question.data).toEqual({
149
147
  answerId: botAnswer.id,
150
- selections: undefined,
148
+ selections: undefined
151
149
  });
152
150
  expect(answer.type).toBe('message');
153
151
  expect(answer.participant.isAgent).toBe(true);
@@ -155,32 +153,38 @@ describe('[ChatbotUiBackendStore]', () => {
155
153
  expect(answer.data).toEqual(botAnswer);
156
154
  expect(log.error).toHaveBeenCalledTimes(0);
157
155
  });
158
- test('should have correct answer ids in user messages for simultaneous messages', async () => {
156
+ test('should have correct answer ids in user messages for simultaneous messages', async ()=>{
159
157
  store.subscribe();
160
158
  mockChatbotSession();
161
159
  await runStore();
162
160
  ensureChatStarted();
163
161
  expect(chatUiStore.messages.length).toBe(1);
164
162
  const [botAnswer1, botAnswer2] = mockChatbotAnswers([
165
- { answer: 'bot answer 1', answerId: 111 },
166
- { answer: 'bot answer 2', answerId: 222 },
163
+ {
164
+ answer: 'bot answer 1',
165
+ answerId: 111
166
+ },
167
+ {
168
+ answer: 'bot answer 2',
169
+ answerId: 222
170
+ }
167
171
  ]);
168
172
  await Promise.all([
169
173
  chatUiStore.sendMessageText('user question 1'),
170
- chatUiStore.sendMessageText('user question 2'),
174
+ chatUiStore.sendMessageText('user question 2')
171
175
  ]);
172
176
  expect(chatUiStore.messages.length).toBe(5);
173
177
  const textMessages = chatUiStore.messages;
174
- const question1 = textMessages.find(x => x.message === 'user question 1');
175
- const answer1 = textMessages.find(x => x.message === 'bot answer 1');
176
- const question2 = textMessages.find(x => x.message === 'user question 2');
177
- const answer2 = textMessages.find(x => x.message === 'bot answer 2');
178
+ const question1 = textMessages.find((x)=>x.message === 'user question 1');
179
+ const answer1 = textMessages.find((x)=>x.message === 'bot answer 1');
180
+ const question2 = textMessages.find((x)=>x.message === 'user question 2');
181
+ const answer2 = textMessages.find((x)=>x.message === 'bot answer 2');
178
182
  expect(question1.type).toBe('message');
179
183
  expect(question1.participant.isAgent).toBe(false);
180
184
  expect(question1.message).toBe('user question 1');
181
185
  expect(question1.data).toEqual({
182
186
  answerId: botAnswer1.id,
183
- selections: undefined,
187
+ selections: undefined
184
188
  });
185
189
  expect(answer1.type).toBe('message');
186
190
  expect(answer1.participant.isAgent).toBe(true);
@@ -191,7 +195,7 @@ describe('[ChatbotUiBackendStore]', () => {
191
195
  expect(question2.message).toBe('user question 2');
192
196
  expect(question2.data).toEqual({
193
197
  answerId: botAnswer2.id,
194
- selections: undefined,
198
+ selections: undefined
195
199
  });
196
200
  expect(answer2.type).toBe('message');
197
201
  expect(answer2.participant.isAgent).toBe(true);
@@ -199,7 +203,7 @@ describe('[ChatbotUiBackendStore]', () => {
199
203
  expect(answer2.data).toEqual(botAnswer2);
200
204
  expect(log.error).toHaveBeenCalledTimes(0);
201
205
  });
202
- test('should run chatbot with stored session with expiration', async () => {
206
+ test('should run chatbot with stored session with expiration', async ()=>{
203
207
  mockChatbotSession();
204
208
  await jest.advanceTimersByTimeAsync(1000 * 60 * 60 * 16);
205
209
  await runStore();
@@ -209,7 +213,7 @@ describe('[ChatbotUiBackendStore]', () => {
209
213
  expect(chatbotApi.postSession).toHaveBeenCalledTimes(0);
210
214
  expect(log.error).toHaveBeenCalledTimes(0);
211
215
  });
212
- test('should destroy', async () => {
216
+ test('should destroy', async ()=>{
213
217
  mockChatbotSession();
214
218
  await runStore();
215
219
  store.session = ModelsMocks.mockSession();
@@ -220,7 +224,7 @@ describe('[ChatbotUiBackendStore]', () => {
220
224
  expect(store.session).toBeUndefined();
221
225
  expect(log.error).toHaveBeenCalledTimes(0);
222
226
  });
223
- test('should restart', async () => {
227
+ test('should restart', async ()=>{
224
228
  mockChatbotSession();
225
229
  await runStore();
226
230
  store.session = ModelsMocks.mockSession();
@@ -230,26 +234,30 @@ describe('[ChatbotUiBackendStore]', () => {
230
234
  expect(chatUiStore.status).toBe(ChatRunState.Started);
231
235
  expect(log.error).toHaveBeenCalledTimes(0);
232
236
  });
233
- describe('with messages', () => {
234
- test('should send message', async () => {
237
+ describe('with messages', ()=>{
238
+ test('should send message', async ()=>{
235
239
  mockChatbotSession();
236
240
  mockChatbotAnswer('bot answer');
237
241
  await runStore();
238
242
  await chatUiStore.sendMessageText('user question');
239
243
  const message = chatUiStore.messages.at(-1);
240
- await runChatUiEventListener(store.handleMessageSend, [message]);
244
+ await runChatUiEventListener(store.handleMessageSend, [
245
+ message
246
+ ]);
241
247
  expect(chatUiStore.messages.length).toBe(3);
242
248
  expect(chatUiStore.messages[0].message).toBe(WELCOME_MESSAGE);
243
249
  expect(chatUiStore.messages[1].message).toBe('user question');
244
250
  expect(chatUiStore.messages[2].message).toBe('bot answer');
245
251
  expect(log.error).toHaveBeenCalledTimes(0);
246
252
  });
247
- test('should send message with error', async () => {
253
+ test('should send message with error', async ()=>{
248
254
  mockChatbotSession();
249
255
  chatbotApi.postMessage.mockRejectedValue(new Error('error'));
250
256
  await chatUiStore.sendMessageText('user question');
251
257
  const message = chatUiStore.messages.at(-1);
252
- await runChatUiEventListener(store.handleMessageSend, [message]);
258
+ await runChatUiEventListener(store.handleMessageSend, [
259
+ message
260
+ ]);
253
261
  expect(chatbotApi.postSession).toHaveBeenCalled();
254
262
  expect(chatbotApi.postMessage).toHaveBeenCalled();
255
263
  expect(chatUiStore.isError).toBe(true);
@@ -257,61 +265,85 @@ describe('[ChatbotUiBackendStore]', () => {
257
265
  category: 'TitanChatbot',
258
266
  code: 'TitanChatbot_FailedToSendMessage',
259
267
  error: new Error('error'),
260
- message: 'Failed to send message',
268
+ message: 'Failed to send message'
261
269
  });
262
270
  });
263
- test('should use per-request timeoutMs when provided', async () => {
271
+ test('should use per-request timeoutMs when provided', async ()=>{
264
272
  mockChatbotSession();
265
- chatbotApi.postMessage.mockImplementation(() => new Promise(resolve => setTimeout(() => resolve(ModelsMocks.mockBotMessage({ answer: 'late answer' })), 200)));
273
+ chatbotApi.postMessage.mockImplementation(()=>new Promise((resolve)=>setTimeout(()=>resolve(ModelsMocks.mockBotMessage({
274
+ answer: 'late answer'
275
+ })), 200)));
266
276
  await runStore();
267
277
  await chatUiStore.sendMessageText('user question');
268
278
  const message = chatUiStore.messages.at(-1);
269
- const p = runChatUiEventListener(store.handleMessageSend, [message, undefined, 50]);
279
+ const p = runChatUiEventListener(store.handleMessageSend, [
280
+ message,
281
+ undefined,
282
+ 50
283
+ ]);
270
284
  await jest.advanceTimersByTimeAsync(50);
271
285
  await p;
272
286
  expect(chatUiStore.isError).toBe(true);
273
287
  expect(log.error).toHaveBeenCalledWith(expect.objectContaining({
274
288
  code: 'TitanChatbot_FailedToSendMessage',
275
- message: 'Failed to send message',
289
+ message: 'Failed to send message'
276
290
  }));
277
291
  });
278
- test('should fall back to global timeout when no per-request timeoutMs', async () => {
292
+ test('should fall back to global timeout when no per-request timeoutMs', async ()=>{
279
293
  mockChatbotSession();
280
- chatbotApi.postMessage.mockImplementation(() => new Promise(resolve => setTimeout(() => resolve(ModelsMocks.mockBotMessage({ answer: 'late answer' })), 200)));
281
- chatUiStore.setCustomizationContext({ timeouts: { chatbotRequestTimeoutMs: 100 } });
294
+ chatbotApi.postMessage.mockImplementation(()=>new Promise((resolve)=>setTimeout(()=>resolve(ModelsMocks.mockBotMessage({
295
+ answer: 'late answer'
296
+ })), 200)));
297
+ chatUiStore.setCustomizationContext({
298
+ timeouts: {
299
+ chatbotRequestTimeoutMs: 100
300
+ }
301
+ });
282
302
  await runStore();
283
303
  await chatUiStore.sendMessageText('user question');
284
304
  const message = chatUiStore.messages.at(-1);
285
305
  // No per-request timeoutMs — should use global (100ms)
286
- const p = runChatUiEventListener(store.handleMessageSend, [message, undefined]);
306
+ const p = runChatUiEventListener(store.handleMessageSend, [
307
+ message,
308
+ undefined
309
+ ]);
287
310
  await jest.advanceTimersByTimeAsync(100);
288
311
  await p;
289
312
  expect(chatUiStore.isError).toBe(true);
290
- expect(log.error).toHaveBeenCalledWith(expect.objectContaining({ code: 'TitanChatbot_FailedToSendMessage' }));
313
+ expect(log.error).toHaveBeenCalledWith(expect.objectContaining({
314
+ code: 'TitanChatbot_FailedToSendMessage'
315
+ }));
291
316
  });
292
- test('should fall back to DEFAULT timeout when neither per-request nor global timeout is set', async () => {
317
+ test('should fall back to DEFAULT timeout when neither per-request nor global timeout is set', async ()=>{
293
318
  mockChatbotSession();
294
- chatbotApi.postMessage.mockImplementation(() => new Promise(resolve => setTimeout(() => resolve(ModelsMocks.mockBotMessage({ answer: 'late answer' })), 40000)));
319
+ chatbotApi.postMessage.mockImplementation(()=>new Promise((resolve)=>setTimeout(()=>resolve(ModelsMocks.mockBotMessage({
320
+ answer: 'late answer'
321
+ })), 40000)));
295
322
  await runStore();
296
323
  await chatUiStore.sendMessageText('user question');
297
324
  const message = chatUiStore.messages.at(-1);
298
325
  // No per-request timeoutMs, no global timeout — should use DEFAULT (31000ms)
299
- const p = runChatUiEventListener(store.handleMessageSend, [message, undefined]);
326
+ const p = runChatUiEventListener(store.handleMessageSend, [
327
+ message,
328
+ undefined
329
+ ]);
300
330
  await jest.runOnlyPendingTimersAsync();
301
331
  await p;
302
332
  expect(chatUiStore.isError).toBe(true);
303
- expect(log.error).toHaveBeenCalledWith(expect.objectContaining({ code: 'TitanChatbot_FailedToSendMessage' }));
333
+ expect(log.error).toHaveBeenCalledWith(expect.objectContaining({
334
+ code: 'TitanChatbot_FailedToSendMessage'
335
+ }));
304
336
  });
305
- test('should not abort store-level controller when per-request timeout fires', async () => {
337
+ test('should not abort store-level controller when per-request timeout fires', async ()=>{
306
338
  mockChatbotSession();
307
339
  /*
308
340
  * First message: slow — will time out at 50ms
309
341
  * Second message: fast — should succeed despite the first timing out
310
- */
311
- const fastAnswer = ModelsMocks.mockBotMessage({ answer: 'fast answer' });
312
- chatbotApi.postMessage
313
- .mockImplementationOnce(() => new Promise(() => { })) // never resolves — times out
314
- .mockResolvedValueOnce(fastAnswer);
342
+ */ const fastAnswer = ModelsMocks.mockBotMessage({
343
+ answer: 'fast answer'
344
+ });
345
+ chatbotApi.postMessage.mockImplementationOnce(()=>new Promise(()=>{})) // never resolves — times out
346
+ .mockResolvedValueOnce(fastAnswer);
315
347
  await runStore();
316
348
  await chatUiStore.sendMessageText('slow question');
317
349
  const slowMessage = chatUiStore.messages.at(-1);
@@ -319,7 +351,7 @@ describe('[ChatbotUiBackendStore]', () => {
319
351
  const p1 = runChatUiEventListener(store.handleMessageSend, [
320
352
  slowMessage,
321
353
  undefined,
322
- 50,
354
+ 50
323
355
  ]);
324
356
  await jest.advanceTimersByTimeAsync(50);
325
357
  await p1;
@@ -329,15 +361,19 @@ describe('[ChatbotUiBackendStore]', () => {
329
361
  chatUiStore.resetError(ChatRunState.Started);
330
362
  await chatUiStore.sendMessageText('fast question');
331
363
  const fastMessage = chatUiStore.messages.at(-1);
332
- await runChatUiEventListener(store.handleMessageSend, [fastMessage]);
364
+ await runChatUiEventListener(store.handleMessageSend, [
365
+ fastMessage
366
+ ]);
333
367
  expect(chatUiStore.isError).toBe(false);
334
368
  expect(chatUiStore.messages.at(-1)).toMatchObject({
335
- message: 'fast answer',
369
+ message: 'fast answer'
336
370
  });
337
371
  });
338
- test('should preserve per-request timeoutMs on retry', async () => {
372
+ test('should preserve per-request timeoutMs on retry', async ()=>{
339
373
  mockChatbotSession();
340
- chatbotApi.postMessage.mockImplementation(() => new Promise(resolve => setTimeout(() => resolve(ModelsMocks.mockBotMessage({ answer: 'late answer' })), 200)));
374
+ chatbotApi.postMessage.mockImplementation(()=>new Promise((resolve)=>setTimeout(()=>resolve(ModelsMocks.mockBotMessage({
375
+ answer: 'late answer'
376
+ })), 200)));
341
377
  await runStore();
342
378
  await chatUiStore.sendMessageText('user question');
343
379
  const message = chatUiStore.messages.at(-1);
@@ -345,110 +381,155 @@ describe('[ChatbotUiBackendStore]', () => {
345
381
  const p1 = runChatUiEventListener(store.handleMessageSend, [
346
382
  message,
347
383
  undefined,
348
- 50,
384
+ 50
349
385
  ]);
350
386
  await jest.advanceTimersByTimeAsync(50);
351
387
  await p1;
352
388
  expect(chatUiStore.isError).toBe(true);
353
389
  // Retry should reuse the persisted timeoutMs (50ms), not fall back to default (31000ms)
354
- chatbotApi.postMessage.mockImplementation(() => new Promise(resolve => setTimeout(() => resolve(ModelsMocks.mockBotMessage({ answer: 'late answer' })), 200)));
355
- const p2 = runChatUiEventListener(store.handleMessageSendRetry, [message]);
390
+ chatbotApi.postMessage.mockImplementation(()=>new Promise((resolve)=>setTimeout(()=>resolve(ModelsMocks.mockBotMessage({
391
+ answer: 'late answer'
392
+ })), 200)));
393
+ const p2 = runChatUiEventListener(store.handleMessageSendRetry, [
394
+ message
395
+ ]);
356
396
  await jest.advanceTimersByTimeAsync(50);
357
397
  await p2;
358
398
  // If retry had fallen back to default 31000ms, it would not have timed out in 50ms
359
- expect(log.error).toHaveBeenCalledWith(expect.objectContaining({ code: 'TitanChatbot_FailedToSendMessage' }));
399
+ expect(log.error).toHaveBeenCalledWith(expect.objectContaining({
400
+ code: 'TitanChatbot_FailedToSendMessage'
401
+ }));
360
402
  });
361
- test('should send message retry', async () => {
403
+ test('should send message retry', async ()=>{
362
404
  mockChatbotSession();
363
405
  const spyMessage = jest.spyOn(chatbotApi, 'postMessage');
364
406
  // Retry with non-text message should not call "message"
365
407
  const messageNonText = ModelsMocks.mockChatMessageModel({
366
- type: 'welcome',
408
+ type: 'welcome'
367
409
  });
368
- await runChatUiEventListener(store.handleMessageSendRetry, [messageNonText]);
410
+ await runChatUiEventListener(store.handleMessageSendRetry, [
411
+ messageNonText
412
+ ]);
369
413
  expect(spyMessage).not.toHaveBeenCalled();
370
414
  // Retry with text message should call "message"
371
415
  const message = ModelsMocks.mockChatMessageModel({
372
- message: 'message',
416
+ message: 'message'
373
417
  });
374
- await runChatUiEventListener(store.handleMessageSendRetry, [message]);
418
+ await runChatUiEventListener(store.handleMessageSendRetry, [
419
+ message
420
+ ]);
375
421
  expect(spyMessage).toHaveBeenCalledWith(ModelsMocks.mockUserMessage({
376
422
  context: undefined,
377
423
  selections: undefined,
378
- question: 'message',
424
+ question: 'message'
379
425
  }), expect.any(AbortSignal));
380
426
  });
381
427
  });
382
- describe('with session start', () => {
383
- const createSession = (id) => ModelsMocks.mockSession({
384
- id,
385
- data: { custom: 'data' },
386
- });
387
- test('should start session', async () => {
428
+ describe('with session start', ()=>{
429
+ const createSession = (id)=>ModelsMocks.mockSession({
430
+ id,
431
+ data: {
432
+ custom: 'data'
433
+ }
434
+ });
435
+ test('should start session', async ()=>{
388
436
  const spyGetData = jest.spyOn(store, 'getSessionData').mockResolvedValue({
389
- defaultData: 'defaultData',
437
+ defaultData: 'defaultData'
390
438
  });
391
439
  chatbotApi.postSession.mockResolvedValue(createSession(1));
392
440
  const { session } = await runChatUiEventListener(store.handleSessionStart, [
393
- { custom: 'data' },
441
+ {
442
+ custom: 'data'
443
+ }
394
444
  ]);
395
445
  expect(spyGetData).toHaveBeenCalled();
396
446
  expect(session).toEqual(createSession(1));
397
447
  expect(chatbotApi.postSession).toHaveBeenCalledWith({
398
- data: { custom: 'data', defaultData: 'defaultData' },
448
+ data: {
449
+ custom: 'data',
450
+ defaultData: 'defaultData'
451
+ }
399
452
  }, expect.any(AbortSignal));
400
453
  });
401
- test('should handle multiple calls', async () => {
454
+ test('should handle multiple calls', async ()=>{
402
455
  chatbotApi.postSession.mockResolvedValue(createSession(1));
403
- const p1 = runChatUiEventListener(store.handleSessionStart, [{ custom: 'data' }]);
404
- const p2 = runChatUiEventListener(store.handleSessionStart, [{ custom: 'data' }]);
405
- const [r1, r2] = await Promise.all([p1, p2]);
456
+ const p1 = runChatUiEventListener(store.handleSessionStart, [
457
+ {
458
+ custom: 'data'
459
+ }
460
+ ]);
461
+ const p2 = runChatUiEventListener(store.handleSessionStart, [
462
+ {
463
+ custom: 'data'
464
+ }
465
+ ]);
466
+ const [r1, r2] = await Promise.all([
467
+ p1,
468
+ p2
469
+ ]);
406
470
  expect(r1.session).toEqual(createSession(1));
407
471
  expect(r2.session).toEqual(createSession(1));
408
472
  expect(chatbotApi.postSession).toHaveBeenCalledTimes(1);
409
473
  expect(chatbotApi.postSession).toHaveBeenCalledWith({
410
- data: { custom: 'data' },
474
+ data: {
475
+ custom: 'data'
476
+ }
411
477
  }, expect.any(AbortSignal));
412
478
  });
413
- test('should handle forced calls', async () => {
479
+ test('should handle forced calls', async ()=>{
414
480
  chatbotApi.postSession.mockResolvedValue(createSession(1));
415
481
  const { session } = await runChatUiEventListener(store.handleSessionStart, [
416
- { custom: 'data' },
482
+ {
483
+ custom: 'data'
484
+ }
417
485
  ]);
418
486
  chatbotApi.postSession.mockResolvedValue(createSession(2));
419
- const { session: session2 } = await runChatUiEventListener(store.handleSessionStart, [{ custom: 'data' }]);
487
+ const { session: session2 } = await runChatUiEventListener(store.handleSessionStart, [
488
+ {
489
+ custom: 'data'
490
+ }
491
+ ]);
420
492
  expect(chatbotApi.postSession).toHaveBeenCalledTimes(1);
421
493
  expect(session === null || session === void 0 ? void 0 : session.id).toEqual(1);
422
494
  expect(session2 === null || session2 === void 0 ? void 0 : session2.id).toEqual(1);
423
495
  expect(session === session2).toBe(true);
424
496
  // Make api call return another session object and ensure that after
425
497
  chatbotApi.postSession.mockResolvedValue(createSession(3));
426
- const { session: session3 } = await runChatUiEventListener(store.handleSessionStart, [{ custom: 'data' }, true]);
498
+ const { session: session3 } = await runChatUiEventListener(store.handleSessionStart, [
499
+ {
500
+ custom: 'data'
501
+ },
502
+ true
503
+ ]);
427
504
  expect(chatbotApi.postSession).toHaveBeenCalledTimes(2);
428
505
  expect(session3 === null || session3 === void 0 ? void 0 : session3.id).toEqual(3);
429
506
  expect(session === session3).toBe(false);
430
507
  });
431
508
  });
432
- describe('with session feedback', () => {
433
- const createSessionFeedback = () => ModelsMocks.mockFeedback({
434
- sessionId: 1,
435
- messageId: undefined,
436
- rating: Models.FeedbackRatings.ThumbsUp,
437
- description: 'description',
438
- });
439
- test('should send feedback', async () => {
509
+ describe('with session feedback', ()=>{
510
+ const createSessionFeedback = ()=>ModelsMocks.mockFeedback({
511
+ sessionId: 1,
512
+ messageId: undefined,
513
+ rating: Models.FeedbackRatings.ThumbsUp,
514
+ description: 'description'
515
+ });
516
+ test('should send feedback', async ()=>{
440
517
  mockChatbotSession();
441
518
  chatbotApi.postFeedback.mockResolvedValueOnce(createSessionFeedback());
442
- const { feedback, state } = await runChatUiEventListener(store.handleSessionFeedback, [createSessionFeedback()]);
519
+ const { feedback, state } = await runChatUiEventListener(store.handleSessionFeedback, [
520
+ createSessionFeedback()
521
+ ]);
443
522
  expect(state).toBe(Models.ChatbotFeedbackState.Success);
444
523
  expect(feedback).toEqual(createSessionFeedback());
445
524
  expect(chatbotApi.postSession).toHaveBeenCalled();
446
525
  expect(chatbotApi.postFeedback).toHaveBeenCalledWith(createSessionFeedback(), expect.any(AbortSignal));
447
526
  });
448
- test('should send feedback with session error', async () => {
527
+ test('should send feedback with session error', async ()=>{
449
528
  chatbotApi.postSession.mockRejectedValueOnce('session error');
450
529
  chatbotApi.postFeedback.mockResolvedValueOnce(createSessionFeedback());
451
- const { feedback, state } = await runChatUiEventListener(store.handleSessionFeedback, [createSessionFeedback()]);
530
+ const { feedback, state } = await runChatUiEventListener(store.handleSessionFeedback, [
531
+ createSessionFeedback()
532
+ ]);
452
533
  expect(state).toBe(Models.ChatbotFeedbackState.Failure);
453
534
  expect(feedback).toEqual(createSessionFeedback());
454
535
  expect(chatbotApi.postSession).toHaveBeenCalled();
@@ -458,14 +539,16 @@ describe('[ChatbotUiBackendStore]', () => {
458
539
  category: 'TitanChatbot',
459
540
  code: 'TitanChatbot_FailedToSendFeedback',
460
541
  error: 'session error',
461
- message: 'Failed to send feedback',
542
+ message: 'Failed to send feedback'
462
543
  });
463
544
  });
464
- test('should send feedback with error', async () => {
545
+ test('should send feedback with error', async ()=>{
465
546
  mockChatbotSession();
466
547
  chatbotApi.postSession.mockReset().mockRejectedValue(new Error('session error'));
467
548
  chatbotApi.postFeedback.mockResolvedValueOnce(createSessionFeedback());
468
- const { feedback, state } = await runChatUiEventListener(store.handleSessionFeedback, [createSessionFeedback()]);
549
+ const { feedback, state } = await runChatUiEventListener(store.handleSessionFeedback, [
550
+ createSessionFeedback()
551
+ ]);
469
552
  expect(state).toBe(Models.ChatbotFeedbackState.Failure);
470
553
  expect(feedback).toEqual(createSessionFeedback());
471
554
  expect(chatbotApi.postSession).toHaveBeenCalled();
@@ -475,82 +558,94 @@ describe('[ChatbotUiBackendStore]', () => {
475
558
  category: 'TitanChatbot',
476
559
  code: 'TitanChatbot_FailedToSendFeedback',
477
560
  error: new Error('session error'),
478
- message: 'Failed to send feedback',
561
+ message: 'Failed to send feedback'
479
562
  });
480
563
  });
481
564
  });
482
- describe('with message feedback', () => {
483
- const createMessageFeedback = () => ModelsMocks.mockFeedback({
484
- sessionId: 1,
485
- messageId: 1,
486
- description: 'description',
487
- linkUrl: 'linkUrl',
488
- options: [Models.FeedbackOptions.Unclear, Models.FeedbackOptions.Incorrect],
489
- rating: Models.FeedbackRatings.ThumbsUp,
490
- });
491
- const createLogError = (errorMessage) => ({
492
- category: 'TitanChatbot',
493
- code: 'TitanChatbot_FailedToSendMessageFeedback',
494
- error: errorMessage,
495
- message: 'Failed to send message feedback',
496
- });
497
- test('should assert message feedback 1', async () => {
498
- var _a;
565
+ describe('with message feedback', ()=>{
566
+ const createMessageFeedback = ()=>ModelsMocks.mockFeedback({
567
+ sessionId: 1,
568
+ messageId: 1,
569
+ description: 'description',
570
+ linkUrl: 'linkUrl',
571
+ options: [
572
+ Models.FeedbackOptions.Unclear,
573
+ Models.FeedbackOptions.Incorrect
574
+ ],
575
+ rating: Models.FeedbackRatings.ThumbsUp
576
+ });
577
+ const createLogError = (errorMessage)=>({
578
+ category: 'TitanChatbot',
579
+ code: 'TitanChatbot_FailedToSendMessageFeedback',
580
+ error: errorMessage,
581
+ message: 'Failed to send message feedback'
582
+ });
583
+ test('should assert message feedback 1', async ()=>{
584
+ var _chatUiStore_error;
499
585
  const { feedback, state } = await runChatUiEventListener(store.handleMessageFeedback, []);
500
586
  expect(feedback).toBeUndefined();
501
587
  expect(state).toBe(Models.ChatbotFeedbackState.Failure);
502
588
  expect(chatUiStore.isError).toBe(true);
503
- expect((_a = chatUiStore.error) === null || _a === void 0 ? void 0 : _a.message).toBe('Failed to send message feedback');
589
+ expect((_chatUiStore_error = chatUiStore.error) === null || _chatUiStore_error === void 0 ? void 0 : _chatUiStore_error.message).toBe('Failed to send message feedback');
504
590
  expect(log.error).toHaveBeenCalledWith({
505
591
  category: 'TitanChatbot',
506
592
  code: 'TitanChatbot_FailedToSendMessageFeedback',
507
593
  error: new ChatError('Message feedback is missing.'),
508
- message: 'Failed to send message feedback',
594
+ message: 'Failed to send message feedback'
509
595
  });
510
596
  expect(chatbotApi.postFeedback).not.toHaveBeenCalled();
511
597
  });
512
- test('should assert message feedback 3', async () => {
513
- var _a;
598
+ test('should assert message feedback 3', async ()=>{
599
+ var _chatUiStore_error;
514
600
  chatbotApi.postSession.mockRejectedValueOnce('session error');
515
- const { feedback, state } = await runChatUiEventListener(store.handleMessageFeedback, [createMessageFeedback()]);
601
+ const { feedback, state } = await runChatUiEventListener(store.handleMessageFeedback, [
602
+ createMessageFeedback()
603
+ ]);
516
604
  expect(state).toBe(Models.ChatbotFeedbackState.Failure);
517
605
  expect(feedback).toEqual(createMessageFeedback());
518
606
  expect(chatUiStore.isError).toBe(true);
519
- expect((_a = chatUiStore.error) === null || _a === void 0 ? void 0 : _a.message).toBe('Failed to send message feedback');
607
+ expect((_chatUiStore_error = chatUiStore.error) === null || _chatUiStore_error === void 0 ? void 0 : _chatUiStore_error.message).toBe('Failed to send message feedback');
520
608
  expect(log.error).toHaveBeenCalledWith({
521
609
  category: 'TitanChatbot',
522
610
  code: 'TitanChatbot_FailedToSendMessageFeedback',
523
611
  error: 'session error',
524
- message: 'Failed to send message feedback',
612
+ message: 'Failed to send message feedback'
525
613
  });
526
614
  expect(chatbotApi.postFeedback).not.toHaveBeenCalled();
527
615
  });
528
- test('should send message feedback', async () => {
616
+ test('should send message feedback', async ()=>{
529
617
  mockChatbotSession();
530
618
  chatbotApi.postFeedback.mockResolvedValueOnce(createMessageFeedback());
531
619
  const feedback = createMessageFeedback();
532
- await runChatUiEventListener(store.handleMessageFeedback, [feedback]);
620
+ await runChatUiEventListener(store.handleMessageFeedback, [
621
+ feedback
622
+ ]);
533
623
  expect(chatbotApi.postFeedback).toHaveBeenCalledWith({
534
- ...feedback,
624
+ ...feedback
535
625
  }, expect.any(AbortSignal));
536
626
  });
537
- test('should send message feedback with session error', async () => {
627
+ test('should send message feedback with session error', async ()=>{
538
628
  chatbotApi.postSession.mockRejectedValueOnce('session error');
539
629
  chatbotApi.postFeedback.mockResolvedValueOnce(createMessageFeedback());
540
630
  const feedback = createMessageFeedback();
541
- await runChatUiEventListener(store.handleMessageFeedback, [feedback]);
631
+ await runChatUiEventListener(store.handleMessageFeedback, [
632
+ feedback
633
+ ]);
542
634
  expect(chatbotApi.postFeedback).not.toHaveBeenCalled();
543
635
  expect(log.error).toHaveBeenCalledWith(createLogError('session error'));
544
636
  });
545
- test('should send message feedback with feedback error', async () => {
637
+ test('should send message feedback with feedback error', async ()=>{
546
638
  mockChatbotSession();
547
639
  chatbotApi.postFeedback.mockRejectedValueOnce('feedback error');
548
640
  const feedback = createMessageFeedback();
549
- await runChatUiEventListener(store.handleMessageFeedback, [feedback]);
641
+ await runChatUiEventListener(store.handleMessageFeedback, [
642
+ feedback
643
+ ]);
550
644
  expect(chatbotApi.postFeedback).toHaveBeenCalledTimes(1);
551
645
  expect(log.error).toHaveBeenCalledWith(createLogError('feedback error'));
552
646
  });
553
647
  });
554
648
  });
555
649
  });
650
+
556
651
  //# sourceMappingURL=chatbot-ui-backend.store.test.js.map