@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.
- package/CHANGELOG.md +9 -0
- package/dist/api-client/__mocks__/chatbot-api-client.mock.d.ts +11 -0
- package/dist/api-client/__mocks__/chatbot-api-client.mock.d.ts.map +1 -0
- package/dist/api-client/__mocks__/chatbot-api-client.mock.js +47 -0
- package/dist/api-client/__mocks__/chatbot-api-client.mock.js.map +1 -0
- package/dist/api-client/base/chatbot-api-client.d.ts +27 -0
- package/dist/api-client/base/chatbot-api-client.d.ts.map +1 -0
- package/dist/api-client/base/chatbot-api-client.js +10 -0
- package/dist/api-client/base/chatbot-api-client.js.map +1 -0
- package/dist/api-client/help-center/__tests__/converter-from-models.test.d.ts +2 -0
- package/dist/api-client/help-center/__tests__/converter-from-models.test.d.ts.map +1 -0
- package/dist/api-client/help-center/__tests__/converter-from-models.test.js +34 -0
- package/dist/api-client/help-center/__tests__/converter-from-models.test.js.map +1 -0
- package/dist/api-client/help-center/__tests__/converter-to-models.test.d.ts +2 -0
- package/dist/api-client/help-center/__tests__/converter-to-models.test.d.ts.map +1 -0
- package/dist/api-client/help-center/__tests__/converter-to-models.test.js +82 -0
- package/dist/api-client/help-center/__tests__/converter-to-models.test.js.map +1 -0
- package/dist/api-client/help-center/chatbot-api-client.d.ts +31 -0
- package/dist/api-client/help-center/chatbot-api-client.d.ts.map +1 -0
- package/dist/api-client/help-center/chatbot-api-client.js +90 -0
- package/dist/api-client/help-center/chatbot-api-client.js.map +1 -0
- package/dist/api-client/help-center/converter-from-models.d.ts +13 -0
- package/dist/api-client/help-center/converter-from-models.d.ts.map +1 -0
- package/dist/api-client/help-center/converter-from-models.js +113 -0
- package/dist/api-client/help-center/converter-from-models.js.map +1 -0
- package/dist/api-client/help-center/converter-to-models.d.ts +13 -0
- package/dist/api-client/help-center/converter-to-models.d.ts.map +1 -0
- package/dist/api-client/help-center/converter-to-models.js +95 -0
- package/dist/api-client/help-center/converter-to-models.js.map +1 -0
- package/dist/api-client/help-center/index.d.ts +2 -0
- package/dist/api-client/help-center/index.d.ts.map +1 -0
- package/dist/api-client/help-center/index.js +2 -0
- package/dist/api-client/help-center/index.js.map +1 -0
- package/dist/api-client/help-center/native-client.d.ts +1260 -0
- package/dist/api-client/help-center/native-client.d.ts.map +1 -0
- package/dist/api-client/help-center/native-client.js +6169 -0
- package/dist/api-client/help-center/native-client.js.map +1 -0
- package/dist/api-client/index.d.ts +8 -0
- package/dist/api-client/index.d.ts.map +1 -0
- package/dist/api-client/index.js +8 -0
- package/dist/api-client/index.js.map +1 -0
- package/dist/api-client/models/__mocks__/models.mock.d.ts +13 -0
- package/dist/api-client/models/__mocks__/models.mock.d.ts.map +1 -0
- package/dist/api-client/models/__mocks__/models.mock.js +114 -0
- package/dist/api-client/models/__mocks__/models.mock.js.map +1 -0
- package/dist/api-client/models/index.d.ts +22 -0
- package/dist/api-client/models/index.d.ts.map +1 -0
- package/dist/api-client/models/index.js +15 -0
- package/dist/api-client/models/index.js.map +1 -0
- package/dist/api-client/titan-chat/chatbot-api-client.d.ts +34 -0
- package/dist/api-client/titan-chat/chatbot-api-client.d.ts.map +1 -0
- package/dist/api-client/titan-chat/chatbot-api-client.js +72 -0
- package/dist/api-client/titan-chat/chatbot-api-client.js.map +1 -0
- package/dist/api-client/titan-chat/index.d.ts +2 -0
- package/dist/api-client/titan-chat/index.d.ts.map +1 -0
- package/dist/api-client/titan-chat/index.js +2 -0
- package/dist/api-client/titan-chat/index.js.map +1 -0
- package/dist/api-client/titan-chat/native-client.d.ts +225 -0
- package/dist/api-client/titan-chat/native-client.d.ts.map +1 -0
- package/dist/api-client/titan-chat/native-client.js +931 -0
- package/dist/api-client/titan-chat/native-client.js.map +1 -0
- package/dist/api-client/utils/model-utils.d.ts +4 -0
- package/dist/api-client/utils/model-utils.d.ts.map +1 -0
- package/dist/api-client/utils/model-utils.js +58 -0
- package/dist/api-client/utils/model-utils.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/models/chatbot-customizations.d.ts +15 -0
- package/dist/models/chatbot-customizations.d.ts.map +1 -0
- package/dist/models/chatbot-customizations.js +2 -0
- package/dist/models/chatbot-customizations.js.map +1 -0
- package/dist/models/index.d.ts +2 -0
- package/dist/models/index.d.ts.map +1 -0
- package/dist/models/index.js +2 -0
- package/dist/models/index.js.map +1 -0
- package/dist/stores/__tests__/chatbot-ui-backend.store.test.d.ts +2 -0
- package/dist/stores/__tests__/chatbot-ui-backend.store.test.d.ts.map +1 -0
- package/dist/stores/__tests__/chatbot-ui-backend.store.test.js +341 -0
- package/dist/stores/__tests__/chatbot-ui-backend.store.test.js.map +1 -0
- package/dist/stores/__tests__/chatbot-ui.store.test.d.ts +2 -0
- package/dist/stores/__tests__/chatbot-ui.store.test.d.ts.map +1 -0
- package/dist/stores/__tests__/chatbot-ui.store.test.js +166 -0
- package/dist/stores/__tests__/chatbot-ui.store.test.js.map +1 -0
- package/dist/stores/__tests__/filter.store.test.d.ts +2 -0
- package/dist/stores/__tests__/filter.store.test.d.ts.map +1 -0
- package/dist/stores/__tests__/filter.store.test.js +316 -0
- package/dist/stores/__tests__/filter.store.test.js.map +1 -0
- package/dist/stores/__tests__/initialize.store.test.d.ts +2 -0
- package/dist/stores/__tests__/initialize.store.test.d.ts.map +1 -0
- package/dist/stores/__tests__/initialize.store.test.js +54 -0
- package/dist/stores/__tests__/initialize.store.test.js.map +1 -0
- package/dist/stores/chatbot-ui-backend.store.d.ts +61 -0
- package/dist/stores/chatbot-ui-backend.store.d.ts.map +1 -0
- package/dist/stores/chatbot-ui-backend.store.js +396 -0
- package/dist/stores/chatbot-ui-backend.store.js.map +1 -0
- package/dist/stores/chatbot-ui.store.d.ts +25 -0
- package/dist/stores/chatbot-ui.store.d.ts.map +1 -0
- package/dist/stores/chatbot-ui.store.js +87 -0
- package/dist/stores/chatbot-ui.store.js.map +1 -0
- package/dist/stores/filter.store.d.ts +30 -0
- package/dist/stores/filter.store.d.ts.map +1 -0
- package/dist/stores/filter.store.js +334 -0
- package/dist/stores/filter.store.js.map +1 -0
- package/dist/stores/index.d.ts +4 -0
- package/dist/stores/index.d.ts.map +1 -0
- package/dist/stores/index.js +4 -0
- package/dist/stores/index.js.map +1 -0
- package/dist/stores/initialize.store.d.ts +17 -0
- package/dist/stores/initialize.store.d.ts.map +1 -0
- package/dist/stores/initialize.store.js +98 -0
- package/dist/stores/initialize.store.js.map +1 -0
- package/dist/utils/__tests__/axios-utils.test.d.ts +2 -0
- package/dist/utils/__tests__/axios-utils.test.d.ts.map +1 -0
- package/dist/utils/__tests__/axios-utils.test.js +33 -0
- package/dist/utils/__tests__/axios-utils.test.js.map +1 -0
- package/dist/utils/axios-utils.d.ts +5 -0
- package/dist/utils/axios-utils.d.ts.map +1 -0
- package/dist/utils/axios-utils.js +23 -0
- package/dist/utils/axios-utils.js.map +1 -0
- package/dist/utils/test-utils.d.ts +5 -0
- package/dist/utils/test-utils.d.ts.map +1 -0
- package/dist/utils/test-utils.js +17 -0
- package/dist/utils/test-utils.js.map +1 -0
- package/package.json +45 -0
- package/src/api-client/__mocks__/chatbot-api-client.mock.ts +11 -0
- package/src/api-client/base/chatbot-api-client.ts +33 -0
- package/src/api-client/help-center/__tests__/converter-from-models.test.ts +41 -0
- package/src/api-client/help-center/__tests__/converter-to-models.test.ts +89 -0
- package/src/api-client/help-center/chatbot-api-client.ts +107 -0
- package/src/api-client/help-center/converter-from-models.ts +132 -0
- package/src/api-client/help-center/converter-to-models.ts +124 -0
- package/src/api-client/help-center/index.ts +1 -0
- package/src/api-client/help-center/native-client.ts +5662 -0
- package/src/api-client/index.ts +12 -0
- package/src/api-client/models/__mocks__/models.mock.ts +141 -0
- package/src/api-client/models/index.ts +48 -0
- package/src/api-client/titan-chat/chatbot-api-client.ts +77 -0
- package/src/api-client/titan-chat/index.ts +1 -0
- package/src/api-client/titan-chat/native-client.ts +826 -0
- package/src/api-client/utils/model-utils.ts +68 -0
- package/src/cypress.d.ts +10 -0
- package/src/index.ts +6 -0
- package/src/models/chatbot-customizations.ts +16 -0
- package/src/models/index.ts +1 -0
- package/src/stores/__tests__/chatbot-ui-backend.store.test.ts +426 -0
- package/src/stores/__tests__/chatbot-ui.store.test.ts +196 -0
- package/src/stores/__tests__/filter.store.test.ts +363 -0
- package/src/stores/__tests__/initialize.store.test.ts +73 -0
- package/src/stores/chatbot-ui-backend.store.ts +401 -0
- package/src/stores/chatbot-ui.store.ts +82 -0
- package/src/stores/filter.store.ts +250 -0
- package/src/stores/index.ts +12 -0
- package/src/stores/initialize.store.ts +62 -0
- package/src/utils/__tests__/axios-utils.test.ts +40 -0
- package/src/utils/axios-utils.ts +25 -0
- package/src/utils/test-utils.ts +22 -0
- package/tsconfig.json +19 -0
- 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
|
+
}
|