@servicetitan/titan-chatbot-api 3.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 (160) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dist/api-client/__mocks__/chatbot-api-client.mock.d.ts +11 -0
  3. package/dist/api-client/__mocks__/chatbot-api-client.mock.d.ts.map +1 -0
  4. package/dist/api-client/__mocks__/chatbot-api-client.mock.js +47 -0
  5. package/dist/api-client/__mocks__/chatbot-api-client.mock.js.map +1 -0
  6. package/dist/api-client/base/chatbot-api-client.d.ts +27 -0
  7. package/dist/api-client/base/chatbot-api-client.d.ts.map +1 -0
  8. package/dist/api-client/base/chatbot-api-client.js +10 -0
  9. package/dist/api-client/base/chatbot-api-client.js.map +1 -0
  10. package/dist/api-client/help-center/__tests__/converter-from-models.test.d.ts +2 -0
  11. package/dist/api-client/help-center/__tests__/converter-from-models.test.d.ts.map +1 -0
  12. package/dist/api-client/help-center/__tests__/converter-from-models.test.js +34 -0
  13. package/dist/api-client/help-center/__tests__/converter-from-models.test.js.map +1 -0
  14. package/dist/api-client/help-center/__tests__/converter-to-models.test.d.ts +2 -0
  15. package/dist/api-client/help-center/__tests__/converter-to-models.test.d.ts.map +1 -0
  16. package/dist/api-client/help-center/__tests__/converter-to-models.test.js +82 -0
  17. package/dist/api-client/help-center/__tests__/converter-to-models.test.js.map +1 -0
  18. package/dist/api-client/help-center/chatbot-api-client.d.ts +31 -0
  19. package/dist/api-client/help-center/chatbot-api-client.d.ts.map +1 -0
  20. package/dist/api-client/help-center/chatbot-api-client.js +90 -0
  21. package/dist/api-client/help-center/chatbot-api-client.js.map +1 -0
  22. package/dist/api-client/help-center/converter-from-models.d.ts +13 -0
  23. package/dist/api-client/help-center/converter-from-models.d.ts.map +1 -0
  24. package/dist/api-client/help-center/converter-from-models.js +113 -0
  25. package/dist/api-client/help-center/converter-from-models.js.map +1 -0
  26. package/dist/api-client/help-center/converter-to-models.d.ts +13 -0
  27. package/dist/api-client/help-center/converter-to-models.d.ts.map +1 -0
  28. package/dist/api-client/help-center/converter-to-models.js +95 -0
  29. package/dist/api-client/help-center/converter-to-models.js.map +1 -0
  30. package/dist/api-client/help-center/index.d.ts +2 -0
  31. package/dist/api-client/help-center/index.d.ts.map +1 -0
  32. package/dist/api-client/help-center/index.js +2 -0
  33. package/dist/api-client/help-center/index.js.map +1 -0
  34. package/dist/api-client/help-center/native-client.d.ts +1260 -0
  35. package/dist/api-client/help-center/native-client.d.ts.map +1 -0
  36. package/dist/api-client/help-center/native-client.js +6169 -0
  37. package/dist/api-client/help-center/native-client.js.map +1 -0
  38. package/dist/api-client/index.d.ts +8 -0
  39. package/dist/api-client/index.d.ts.map +1 -0
  40. package/dist/api-client/index.js +8 -0
  41. package/dist/api-client/index.js.map +1 -0
  42. package/dist/api-client/models/__mocks__/models.mock.d.ts +13 -0
  43. package/dist/api-client/models/__mocks__/models.mock.d.ts.map +1 -0
  44. package/dist/api-client/models/__mocks__/models.mock.js +114 -0
  45. package/dist/api-client/models/__mocks__/models.mock.js.map +1 -0
  46. package/dist/api-client/models/index.d.ts +22 -0
  47. package/dist/api-client/models/index.d.ts.map +1 -0
  48. package/dist/api-client/models/index.js +15 -0
  49. package/dist/api-client/models/index.js.map +1 -0
  50. package/dist/api-client/titan-chat/chatbot-api-client.d.ts +34 -0
  51. package/dist/api-client/titan-chat/chatbot-api-client.d.ts.map +1 -0
  52. package/dist/api-client/titan-chat/chatbot-api-client.js +72 -0
  53. package/dist/api-client/titan-chat/chatbot-api-client.js.map +1 -0
  54. package/dist/api-client/titan-chat/index.d.ts +2 -0
  55. package/dist/api-client/titan-chat/index.d.ts.map +1 -0
  56. package/dist/api-client/titan-chat/index.js +2 -0
  57. package/dist/api-client/titan-chat/index.js.map +1 -0
  58. package/dist/api-client/titan-chat/native-client.d.ts +225 -0
  59. package/dist/api-client/titan-chat/native-client.d.ts.map +1 -0
  60. package/dist/api-client/titan-chat/native-client.js +931 -0
  61. package/dist/api-client/titan-chat/native-client.js.map +1 -0
  62. package/dist/api-client/utils/model-utils.d.ts +4 -0
  63. package/dist/api-client/utils/model-utils.d.ts.map +1 -0
  64. package/dist/api-client/utils/model-utils.js +58 -0
  65. package/dist/api-client/utils/model-utils.js.map +1 -0
  66. package/dist/index.d.ts +6 -0
  67. package/dist/index.d.ts.map +1 -0
  68. package/dist/index.js +7 -0
  69. package/dist/index.js.map +1 -0
  70. package/dist/models/chatbot-customizations.d.ts +15 -0
  71. package/dist/models/chatbot-customizations.d.ts.map +1 -0
  72. package/dist/models/chatbot-customizations.js +2 -0
  73. package/dist/models/chatbot-customizations.js.map +1 -0
  74. package/dist/models/index.d.ts +2 -0
  75. package/dist/models/index.d.ts.map +1 -0
  76. package/dist/models/index.js +2 -0
  77. package/dist/models/index.js.map +1 -0
  78. package/dist/stores/__tests__/chatbot-ui-backend.store.test.d.ts +2 -0
  79. package/dist/stores/__tests__/chatbot-ui-backend.store.test.d.ts.map +1 -0
  80. package/dist/stores/__tests__/chatbot-ui-backend.store.test.js +341 -0
  81. package/dist/stores/__tests__/chatbot-ui-backend.store.test.js.map +1 -0
  82. package/dist/stores/__tests__/chatbot-ui.store.test.d.ts +2 -0
  83. package/dist/stores/__tests__/chatbot-ui.store.test.d.ts.map +1 -0
  84. package/dist/stores/__tests__/chatbot-ui.store.test.js +166 -0
  85. package/dist/stores/__tests__/chatbot-ui.store.test.js.map +1 -0
  86. package/dist/stores/__tests__/filter.store.test.d.ts +2 -0
  87. package/dist/stores/__tests__/filter.store.test.d.ts.map +1 -0
  88. package/dist/stores/__tests__/filter.store.test.js +316 -0
  89. package/dist/stores/__tests__/filter.store.test.js.map +1 -0
  90. package/dist/stores/__tests__/initialize.store.test.d.ts +2 -0
  91. package/dist/stores/__tests__/initialize.store.test.d.ts.map +1 -0
  92. package/dist/stores/__tests__/initialize.store.test.js +54 -0
  93. package/dist/stores/__tests__/initialize.store.test.js.map +1 -0
  94. package/dist/stores/chatbot-ui-backend.store.d.ts +61 -0
  95. package/dist/stores/chatbot-ui-backend.store.d.ts.map +1 -0
  96. package/dist/stores/chatbot-ui-backend.store.js +396 -0
  97. package/dist/stores/chatbot-ui-backend.store.js.map +1 -0
  98. package/dist/stores/chatbot-ui.store.d.ts +25 -0
  99. package/dist/stores/chatbot-ui.store.d.ts.map +1 -0
  100. package/dist/stores/chatbot-ui.store.js +87 -0
  101. package/dist/stores/chatbot-ui.store.js.map +1 -0
  102. package/dist/stores/filter.store.d.ts +30 -0
  103. package/dist/stores/filter.store.d.ts.map +1 -0
  104. package/dist/stores/filter.store.js +334 -0
  105. package/dist/stores/filter.store.js.map +1 -0
  106. package/dist/stores/index.d.ts +4 -0
  107. package/dist/stores/index.d.ts.map +1 -0
  108. package/dist/stores/index.js +4 -0
  109. package/dist/stores/index.js.map +1 -0
  110. package/dist/stores/initialize.store.d.ts +17 -0
  111. package/dist/stores/initialize.store.d.ts.map +1 -0
  112. package/dist/stores/initialize.store.js +98 -0
  113. package/dist/stores/initialize.store.js.map +1 -0
  114. package/dist/utils/__tests__/axios-utils.test.d.ts +2 -0
  115. package/dist/utils/__tests__/axios-utils.test.d.ts.map +1 -0
  116. package/dist/utils/__tests__/axios-utils.test.js +33 -0
  117. package/dist/utils/__tests__/axios-utils.test.js.map +1 -0
  118. package/dist/utils/axios-utils.d.ts +5 -0
  119. package/dist/utils/axios-utils.d.ts.map +1 -0
  120. package/dist/utils/axios-utils.js +23 -0
  121. package/dist/utils/axios-utils.js.map +1 -0
  122. package/dist/utils/test-utils.d.ts +5 -0
  123. package/dist/utils/test-utils.d.ts.map +1 -0
  124. package/dist/utils/test-utils.js +17 -0
  125. package/dist/utils/test-utils.js.map +1 -0
  126. package/package.json +45 -0
  127. package/src/api-client/__mocks__/chatbot-api-client.mock.ts +11 -0
  128. package/src/api-client/base/chatbot-api-client.ts +33 -0
  129. package/src/api-client/help-center/__tests__/converter-from-models.test.ts +41 -0
  130. package/src/api-client/help-center/__tests__/converter-to-models.test.ts +89 -0
  131. package/src/api-client/help-center/chatbot-api-client.ts +107 -0
  132. package/src/api-client/help-center/converter-from-models.ts +132 -0
  133. package/src/api-client/help-center/converter-to-models.ts +124 -0
  134. package/src/api-client/help-center/index.ts +1 -0
  135. package/src/api-client/help-center/native-client.ts +5662 -0
  136. package/src/api-client/index.ts +12 -0
  137. package/src/api-client/models/__mocks__/models.mock.ts +141 -0
  138. package/src/api-client/models/index.ts +48 -0
  139. package/src/api-client/titan-chat/chatbot-api-client.ts +77 -0
  140. package/src/api-client/titan-chat/index.ts +1 -0
  141. package/src/api-client/titan-chat/native-client.ts +826 -0
  142. package/src/api-client/utils/model-utils.ts +68 -0
  143. package/src/cypress.d.ts +10 -0
  144. package/src/index.ts +6 -0
  145. package/src/models/chatbot-customizations.ts +16 -0
  146. package/src/models/index.ts +1 -0
  147. package/src/stores/__tests__/chatbot-ui-backend.store.test.ts +426 -0
  148. package/src/stores/__tests__/chatbot-ui.store.test.ts +196 -0
  149. package/src/stores/__tests__/filter.store.test.ts +363 -0
  150. package/src/stores/__tests__/initialize.store.test.ts +73 -0
  151. package/src/stores/chatbot-ui-backend.store.ts +401 -0
  152. package/src/stores/chatbot-ui.store.ts +82 -0
  153. package/src/stores/filter.store.ts +250 -0
  154. package/src/stores/index.ts +12 -0
  155. package/src/stores/initialize.store.ts +62 -0
  156. package/src/utils/__tests__/axios-utils.test.ts +40 -0
  157. package/src/utils/axios-utils.ts +25 -0
  158. package/src/utils/test-utils.ts +22 -0
  159. package/tsconfig.json +19 -0
  160. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,401 @@
1
+ import { ILog, Log } from '@servicetitan/log-service';
2
+ import { inject, injectable, symbolToken } from '@servicetitan/react-ioc';
3
+ import {
4
+ ChatError,
5
+ ChatMessageModelBase,
6
+ ChatMessageModelText,
7
+ ChatMessageState,
8
+ ChatRunState,
9
+ ChatUiEvent,
10
+ ChatUiEventListener,
11
+ IChatUiBackendStore,
12
+ } from '@servicetitan/titan-chat-ui-common';
13
+ import axios from 'axios';
14
+ import { makeObservable, runInAction } from 'mobx';
15
+ import { CHATBOT_API_CLIENT, IChatbotApiClient, Models, ModelsUtils } from '../api-client';
16
+ import { withTimeout } from '../utils/axios-utils';
17
+ import { CHATBOT_UI_STORE_TOKEN, ChatbotUiEvent, IChatbotUiStore } from './chatbot-ui.store';
18
+ import { InitializeStore } from './initialize.store';
19
+
20
+ const CHATBOT_ASK_BOT_TIMEOUT = 31000; // 31 seconds
21
+
22
+ export const CHATBOT_UI_BACKEND_STORE_TOKEN = symbolToken<IChatbotUiBackendStore>(
23
+ 'CHATBOT_UI_BACKEND_STORE_TOKEN'
24
+ );
25
+
26
+ export interface IChatbotUiBackendStore extends IChatUiBackendStore {
27
+ session?: Models.ISession;
28
+ initialize: () => void;
29
+ saveCurrentState: () => void;
30
+ deleteFromSessionStorage: () => void;
31
+ restoreChatSession: () => void;
32
+ restartTimers: () => void;
33
+ }
34
+
35
+ @injectable()
36
+ export class ChatbotUiBackendStore extends InitializeStore implements IChatbotUiBackendStore {
37
+ session?: Models.ISession;
38
+ abortController!: AbortController;
39
+ @inject(CHATBOT_API_CLIENT) protected readonly chatbotApiClient!: IChatbotApiClient;
40
+ @inject(CHATBOT_UI_STORE_TOKEN) protected readonly chatUiStore!: IChatbotUiStore;
41
+
42
+ @inject(Log) private log!: ILog;
43
+ private intervalId?: ReturnType<typeof setInterval>;
44
+ private sessionPromise?: Promise<Models.ISession>;
45
+
46
+ protected get logService(): ILog {
47
+ return this.log;
48
+ }
49
+
50
+ constructor() {
51
+ super();
52
+ makeObservable(this);
53
+ this.initializeAbortController();
54
+ this.startRefreshingAuth();
55
+ }
56
+
57
+ subscribe(): void {
58
+ this.unsubscribe();
59
+ this.chatUiStore.on(ChatUiEvent.eventRun, this.handleRun);
60
+ this.chatUiStore.on(ChatUiEvent.eventDestroy, this.handleDestroy);
61
+ this.chatUiStore.on(ChatUiEvent.eventRestart, this.handleRestart);
62
+ this.chatUiStore.on(ChatUiEvent.eventMessageSend, this.handleMessageSend);
63
+ this.chatUiStore.on(ChatUiEvent.eventMessageSendRetry, this.handleMessageSendRetry);
64
+ this.chatUiStore.on(ChatbotUiEvent.eventChatbotSessionFeedback, this.handleSessionFeedback);
65
+ this.chatUiStore.on(ChatbotUiEvent.eventChatbotMessageFeedback, this.handleMessageFeedback);
66
+ }
67
+ unsubscribe(): void {
68
+ this.chatUiStore.off(ChatUiEvent.eventRun, this.handleRun);
69
+ this.chatUiStore.off(ChatUiEvent.eventDestroy, this.handleDestroy);
70
+ this.chatUiStore.off(ChatUiEvent.eventRestart, this.handleRestart);
71
+ this.chatUiStore.off(ChatUiEvent.eventMessageSend, this.handleMessageSend);
72
+ this.chatUiStore.off(ChatUiEvent.eventMessageSendRetry, this.handleMessageSendRetry);
73
+ this.chatUiStore.off(
74
+ ChatbotUiEvent.eventChatbotSessionFeedback,
75
+ this.handleSessionFeedback
76
+ );
77
+ this.chatUiStore.off(
78
+ ChatbotUiEvent.eventChatbotMessageFeedback,
79
+ this.handleMessageFeedback
80
+ );
81
+ }
82
+
83
+ handleRun: ChatUiEventListener = async resolve => {
84
+ await this.run();
85
+ resolve();
86
+ };
87
+
88
+ handleDestroy: ChatUiEventListener = async resolve => {
89
+ await this.destroy();
90
+ resolve();
91
+ };
92
+
93
+ handleRestart: ChatUiEventListener = async resolve => {
94
+ await this.destroy();
95
+ this.deleteFromSessionStorage();
96
+ await this.run();
97
+ resolve();
98
+ };
99
+
100
+ handleMessageSend: ChatUiEventListener = async (
101
+ resolve,
102
+ _,
103
+ messageModel: ChatMessageModelText,
104
+ selections: Models.Selections | undefined
105
+ ) => {
106
+ await this.sendMessage(messageModel, selections);
107
+ resolve();
108
+ };
109
+
110
+ handleMessageSendRetry: ChatUiEventListener = async (
111
+ resolve,
112
+ _,
113
+ messageModel: ChatMessageModelBase
114
+ ) => {
115
+ if (messageModel.type !== 'message') {
116
+ resolve();
117
+ return;
118
+ }
119
+ const selections = messageModel.data?.selections as Models.Selections | undefined;
120
+ await this.sendMessage(messageModel as ChatMessageModelText, selections);
121
+ resolve();
122
+ };
123
+
124
+ handleSessionFeedback: ChatUiEventListener<{
125
+ state: Models.ChatbotFeedbackState;
126
+ feedback?: Models.Feedback;
127
+ }> = async (resolve, _, feedback?: Models.Feedback) => {
128
+ let feedbackState: Models.ChatbotFeedbackState;
129
+ let feedbackResult = feedback;
130
+ try {
131
+ if (!feedback) {
132
+ throw new ChatError('Session feedback is missing.', {
133
+ recoverStrategy: false,
134
+ });
135
+ }
136
+ if (typeof feedback.messageId !== 'undefined') {
137
+ throw new ChatError('Session feedback should not contain messageId.', {
138
+ recoverStrategy: false,
139
+ });
140
+ }
141
+ await this.ensureSession();
142
+ runInAction(() => {
143
+ feedback.sessionId = this.session!.id;
144
+ });
145
+ feedbackResult = await this.api('postFeedback', feedback, this.abortController.signal);
146
+ feedbackState = Models.ChatbotFeedbackState.Success;
147
+ } catch (error: unknown) {
148
+ this.setError(
149
+ 'FailedToSendFeedback',
150
+ 'Send session feedback',
151
+ 'Failed to send feedback',
152
+ error
153
+ );
154
+ feedbackState = Models.ChatbotFeedbackState.Failure;
155
+ }
156
+ resolve({
157
+ state: feedbackState,
158
+ feedback: feedbackResult,
159
+ });
160
+ };
161
+
162
+ handleMessageFeedback: ChatUiEventListener<{
163
+ state: Models.ChatbotFeedbackState;
164
+ feedback?: Models.Feedback;
165
+ }> = async (resolve, _, feedback?: Models.Feedback) => {
166
+ let feedbackState: Models.ChatbotFeedbackState;
167
+ let feedbackResult = feedback;
168
+ try {
169
+ if (!feedback) {
170
+ throw new ChatError('Message feedback is missing.', {
171
+ recoverStrategy: false,
172
+ });
173
+ }
174
+ if (!feedback.messageId) {
175
+ throw new ChatError('Message feedback is missing messageId.', {
176
+ recoverStrategy: false,
177
+ });
178
+ }
179
+ await this.ensureSession();
180
+ runInAction(() => {
181
+ feedback.sessionId = this.session!.id;
182
+ });
183
+ feedbackResult = await this.api('postFeedback', feedback, this.abortController.signal);
184
+ feedbackState = Models.ChatbotFeedbackState.Success;
185
+ } catch (error: unknown) {
186
+ this.setError(
187
+ 'FailedToSendMessageFeedback',
188
+ 'Send message feedback',
189
+ 'Failed to send message feedback',
190
+ error
191
+ );
192
+ feedbackState = Models.ChatbotFeedbackState.Failure;
193
+ }
194
+ resolve({
195
+ state: feedbackState,
196
+ feedback: feedbackResult,
197
+ });
198
+ };
199
+
200
+ restoreChatSession() {}
201
+
202
+ saveCurrentState() {}
203
+
204
+ deleteFromSessionStorage() {}
205
+
206
+ restartTimers() {
207
+ this.startRefreshingAuth();
208
+ }
209
+
210
+ protected ensureWelcomeMessage() {
211
+ if (this.chatUiStore.messages.length) {
212
+ return;
213
+ }
214
+ this.withSaveState(() => {
215
+ this.chatUiStore.addMessageWelcome(
216
+ 'Hi there! I’m Titan, an AI chatbot powered by Titan Intelligence. ' +
217
+ 'I’m here to answer your questions about using ServiceTitan. ' +
218
+ 'You can ask me things like "How to merge duplicate customers?". ' +
219
+ 'Do you have a question for me?'
220
+ );
221
+ });
222
+ }
223
+
224
+ protected logError(code: string, message: string, error: any) {
225
+ if (axios.isCancel(error)) {
226
+ return;
227
+ }
228
+ this.logService.error({
229
+ error,
230
+ message,
231
+ category: 'TitanChatbot',
232
+ code: `TitanChatbot_${code}`,
233
+ });
234
+ }
235
+
236
+ protected override async initializeInternal() {
237
+ await super.initializeInternal();
238
+ const options = await this.chatbotApiClient.getOptions();
239
+ runInAction(() => {
240
+ this.chatUiStore.filterStore.initFilters(options);
241
+ });
242
+ }
243
+
244
+ protected async run() {
245
+ this.chatUiStore.setStatus(ChatRunState.Initializing);
246
+ await this.initialize();
247
+ this.restoreChatSession();
248
+ this.ensureWelcomeMessage();
249
+ this.restartTimers();
250
+ this.chatUiStore.setStatus(ChatRunState.Started);
251
+ }
252
+
253
+ protected reset() {
254
+ this.setSession(undefined);
255
+ }
256
+
257
+ protected async destroy() {
258
+ if (this.session) {
259
+ const session = await this.api('deleteSession', this.session);
260
+ this.setSession(session);
261
+ }
262
+ this.reset();
263
+ this.chatUiStore.reset();
264
+ }
265
+
266
+ protected processBotAnswer(answer: Models.IBotMessage) {
267
+ this.chatUiStore.addMessage(true, answer.answer, answer);
268
+ }
269
+
270
+ private async sendMessage(messageModel: ChatMessageModelText, selections?: Models.Selections) {
271
+ try {
272
+ await this.ensureSession();
273
+ this.chatUiStore.setAgentTyping(true);
274
+ const answer = await withTimeout(
275
+ this.api(
276
+ 'postMessage',
277
+ new Models.UserMessage({
278
+ sessionId: this.session!.id!,
279
+ question: messageModel.message,
280
+ experience: Models.Experience.MultiTurn,
281
+ selections,
282
+ }),
283
+ this.abortController.signal
284
+ ),
285
+ CHATBOT_ASK_BOT_TIMEOUT,
286
+ this.abortController
287
+ );
288
+ this.withSaveState(() => {
289
+ this.chatUiStore.setMessageState(messageModel, ChatMessageState.Delivered, {
290
+ id: messageModel.id,
291
+ selections,
292
+ });
293
+ this.processBotAnswer(answer);
294
+ this.chatUiStore.resetError(ChatRunState.Started);
295
+ });
296
+ } catch (error: unknown) {
297
+ this.withSaveState(() => {
298
+ this.chatUiStore.setMessageState(messageModel, ChatMessageState.Failed, {
299
+ selections,
300
+ });
301
+ });
302
+ if (error instanceof ChatError) {
303
+ this.setChatError('FailedToSendMessage', error);
304
+ } else {
305
+ this.setError(
306
+ 'FailedToSendMessage',
307
+ 'Send message',
308
+ 'Failed to send message',
309
+ error
310
+ );
311
+ }
312
+ } finally {
313
+ this.chatUiStore.setAgentTyping(false);
314
+ }
315
+ }
316
+
317
+ private setSession(session?: Models.ISession) {
318
+ this.session = session;
319
+ }
320
+
321
+ private async ensureSession() {
322
+ if (this.session) {
323
+ return;
324
+ }
325
+ if (!this.sessionPromise) {
326
+ this.sessionPromise = this.api('postSession', ModelsUtils.createNewSessionModel());
327
+ const session = await this.sessionPromise;
328
+ this.setSession(session);
329
+ this.withSaveState(() => {
330
+ this.chatUiStore.resetError(ChatRunState.Started);
331
+ });
332
+ }
333
+ try {
334
+ await this.sessionPromise;
335
+ } finally {
336
+ this.sessionPromise = undefined;
337
+ }
338
+ }
339
+
340
+ private async api<
341
+ K extends keyof IChatbotApiClient,
342
+ Args extends Parameters<IChatbotApiClient[K]>,
343
+ Result extends ReturnType<IChatbotApiClient[K]>,
344
+ >(methodName: K, ...args: Args): Promise<Awaited<Result>> {
345
+ const method = this.chatbotApiClient[methodName];
346
+ if (method) {
347
+ try {
348
+ const result = (method as (...args: Args) => Result).call(
349
+ this.chatbotApiClient,
350
+ ...args
351
+ ) as Promise<Awaited<Result>>;
352
+ return result;
353
+ } finally {
354
+ this.restartTimers();
355
+ }
356
+ }
357
+ throw new Error(`Method '${methodName}' not found in the API.`);
358
+ }
359
+
360
+ private withSaveState<T>(action: () => T): T {
361
+ const result = action();
362
+ this.saveCurrentState();
363
+ return result;
364
+ }
365
+
366
+ private setChatError(code: string, error: ChatError) {
367
+ this.logError(code, error.message, error);
368
+ this.withSaveState(() => {
369
+ this.chatUiStore.setChatError(error);
370
+ });
371
+ }
372
+
373
+ private setError(code: string, title: string, message: string, error: any) {
374
+ if (axios.isCancel(error)) {
375
+ return;
376
+ }
377
+ this.logError(code, message, error);
378
+ this.withSaveState(() => {
379
+ this.chatUiStore.setError(message, error?.message ?? error);
380
+ });
381
+ }
382
+
383
+ private startRefreshingAuth() {
384
+ clearInterval(this.intervalId);
385
+ this.intervalId = setInterval(
386
+ () => {
387
+ this.chatbotApiClient
388
+ .postRefreshAuthCookies()
389
+ .then(res => res || clearInterval(this.intervalId));
390
+ },
391
+ 60 * 60 * 1000
392
+ );
393
+ }
394
+
395
+ private initializeAbortController(): void {
396
+ this.abortController = new AbortController();
397
+ this.abortController.signal.addEventListener('abort', () => {
398
+ this.initializeAbortController();
399
+ });
400
+ }
401
+ }
@@ -0,0 +1,82 @@
1
+ import { injectable, symbolToken } from '@servicetitan/react-ioc';
2
+ import { ChatUiEvent, ChatUiStore, IChatUiStore } from '@servicetitan/titan-chat-ui-common';
3
+ import { action, makeObservable } from 'mobx';
4
+ import { Models } from '../api-client';
5
+ import { ChatbotCustomizations } from '../models';
6
+ import { FilterStore } from './filter.store';
7
+
8
+ export const CHATBOT_UI_STORE_TOKEN = symbolToken<IChatbotUiStore>('CHATBOT_UI_STORE_TOKEN');
9
+
10
+ export enum ChatbotUiEvent {
11
+ eventChatbotSessionFeedback = 'eventChatbotSessionFeedback',
12
+ eventChatbotMessageFeedback = 'eventChatbotMessageFeedback',
13
+ }
14
+
15
+ export interface IChatbotUiStore extends IChatUiStore<ChatbotCustomizations> {
16
+ filterStore: FilterStore;
17
+ settings?: Models.IFrontendModel;
18
+ sendSessionFeedback: (
19
+ feedback: Models.IFeedback
20
+ ) => Promise<void | Models.ChatbotFeedbackState>;
21
+ sendMessageFeedback: (
22
+ message: Models.IBotMessageWithFeedback,
23
+ messageFeedback: Models.IFeedback
24
+ ) => Promise<void | Models.ChatbotFeedbackState>;
25
+ }
26
+
27
+ @injectable()
28
+ export class ChatbotUiStore extends ChatUiStore<ChatbotCustomizations> implements IChatbotUiStore {
29
+ filterStore = new FilterStore();
30
+
31
+ constructor() {
32
+ super();
33
+ makeObservable(this);
34
+ }
35
+
36
+ override async recover() {
37
+ const errorCustomization = this.customizations.error;
38
+ if (errorCustomization?.recover) {
39
+ await errorCustomization.recover(this.error);
40
+ } else {
41
+ await super.recover();
42
+ }
43
+ this.triggerScroll();
44
+ }
45
+
46
+ override async sendMessageText(messageText: string) {
47
+ await this.restartTimers();
48
+ if (messageText.trim() === '') {
49
+ return;
50
+ }
51
+ const messageModel = this.addMessage(false, messageText);
52
+ await this.emitAsync(ChatUiEvent.eventMessageSend, messageModel, this.filterStore.export());
53
+ }
54
+
55
+ @action
56
+ setFilters(filterOptions: Models.IFrontendModel) {
57
+ this.filterStore.initFilters(filterOptions);
58
+ }
59
+
60
+ @action
61
+ async sendSessionFeedback(feedback: Models.Feedback) {
62
+ feedback.messageId = undefined; // Ensure messageId is not set for session feedback
63
+ const result = await this.emitAsync<Models.ChatbotFeedbackState>(
64
+ ChatbotUiEvent.eventChatbotSessionFeedback,
65
+ feedback
66
+ );
67
+ return result;
68
+ }
69
+
70
+ @action
71
+ async sendMessageFeedback(
72
+ message: Models.IBotMessageWithFeedback,
73
+ messageFeedback: Models.Feedback
74
+ ) {
75
+ messageFeedback.messageId = message.id;
76
+ const result = await this.emitAsync<Models.ChatbotFeedbackState>(
77
+ ChatbotUiEvent.eventChatbotMessageFeedback,
78
+ messageFeedback
79
+ );
80
+ return result;
81
+ }
82
+ }