@servicetitan/titan-chat-ui 1.0.1 → 1.1.1
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 +26 -4
- package/README.md +15 -0
- package/dist/components/chat/__tests-cy__/chat.test.js +11 -6
- package/dist/components/chat/__tests-cy__/chat.test.js.map +1 -1
- package/dist/components/chat/chat-error.d.ts.map +1 -1
- package/dist/components/chat/chat-error.js +5 -3
- package/dist/components/chat/chat-error.js.map +1 -1
- package/dist/models/support-chat.d.ts +13 -3
- package/dist/models/support-chat.d.ts.map +1 -1
- package/dist/models/support-chat.js +29 -0
- package/dist/models/support-chat.js.map +1 -1
- package/dist/stores/__mocks-cy__/chat-ui.store.mock.d.ts +3 -2
- package/dist/stores/__mocks-cy__/chat-ui.store.mock.d.ts.map +1 -1
- package/dist/stores/__mocks-cy__/chat-ui.store.mock.js +6 -0
- package/dist/stores/__mocks-cy__/chat-ui.store.mock.js.map +1 -1
- package/dist/stores/chat-ui-backend-echo.store.d.ts.map +1 -1
- package/dist/stores/chat-ui-backend-echo.store.js +6 -2
- package/dist/stores/chat-ui-backend-echo.store.js.map +1 -1
- package/dist/stores/chat-ui.store.d.ts +7 -5
- package/dist/stores/chat-ui.store.d.ts.map +1 -1
- package/dist/stores/chat-ui.store.js +17 -11
- package/dist/stores/chat-ui.store.js.map +1 -1
- package/package.json +2 -2
- package/src/components/chat/__tests-cy__/chat.test.tsx +11 -9
- package/src/components/chat/chat-error.tsx +8 -6
- package/src/models/support-chat.ts +24 -3
- package/src/stores/__mocks-cy__/chat-ui.store.mock.ts +4 -2
- package/src/stores/chat-ui-backend-echo.store.ts +6 -2
- package/src/stores/chat-ui.store.ts +15 -11
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -94,12 +94,12 @@ describe('[Chat]', () => {
|
|
|
94
94
|
it('should render default chat', () => {
|
|
95
95
|
render().then(() => {
|
|
96
96
|
cy.getCy('titan-chat-messages').should('be.visible');
|
|
97
|
-
cy.getCy('chat-message-agent')
|
|
98
|
-
|
|
97
|
+
cy.getCy('chat-message-agent').should('have.length', 1).should('be.visible');
|
|
98
|
+
cy.getCy('chat-message-agent-content')
|
|
99
99
|
.should('be.visible')
|
|
100
100
|
.should(
|
|
101
101
|
'contain.text',
|
|
102
|
-
|
|
102
|
+
"Hello! I'm generic echo bot. I can echo your messages. Try it out!"
|
|
103
103
|
);
|
|
104
104
|
cy.getCy('titan-chat-notifications').should('exist');
|
|
105
105
|
cy.getCy('titan-chat-send').should('be.visible').should('be.disabled');
|
|
@@ -125,14 +125,19 @@ describe('[Chat]', () => {
|
|
|
125
125
|
it('should render chat error message', () => {
|
|
126
126
|
render().then(() => {
|
|
127
127
|
chatUiStore.setTimer({ secondsTotal: 100, secondsLeft: 10 });
|
|
128
|
-
chatUiStore.setError('
|
|
128
|
+
chatUiStore.setError('error message', {
|
|
129
|
+
title: 'Custom Error',
|
|
130
|
+
recoverStrategy: {
|
|
131
|
+
recoverButtonTitle: 'Recover button title',
|
|
132
|
+
},
|
|
133
|
+
});
|
|
129
134
|
|
|
130
135
|
cy.getCy('titan-chat-timer').should('not.exist');
|
|
131
136
|
cy.getCy('titan-chat-error').should('be.visible');
|
|
132
137
|
});
|
|
133
138
|
});
|
|
134
139
|
|
|
135
|
-
it
|
|
140
|
+
it('should handle send message error and retry', () => {
|
|
136
141
|
render().then(() => {
|
|
137
142
|
ask('[Error]Custom error message');
|
|
138
143
|
cy.tick(1000);
|
|
@@ -143,10 +148,7 @@ describe('[Chat]', () => {
|
|
|
143
148
|
.should('contain.text', 'Message not delivered. Retry');
|
|
144
149
|
cy.getCy('titan-chat-error')
|
|
145
150
|
.should('be.visible')
|
|
146
|
-
.should(
|
|
147
|
-
'contain.text',
|
|
148
|
-
['Failed to send message', 'Custom error message'].join('')
|
|
149
|
-
);
|
|
151
|
+
.should('contain.text', ['Custom Error Title', 'Custom error message'].join(''));
|
|
150
152
|
|
|
151
153
|
// Retry message
|
|
152
154
|
cy.getCy('titan-chat-message-user-retry').should('be.visible').click();
|
|
@@ -1,37 +1,39 @@
|
|
|
1
|
-
import { Banner,
|
|
1
|
+
import { Banner, Button } from '@servicetitan/design-system';
|
|
2
2
|
import { useDependencies } from '@servicetitan/react-ioc';
|
|
3
3
|
import { observer } from 'mobx-react';
|
|
4
4
|
import { FC, useCallback } from 'react';
|
|
5
5
|
import { CHAT_UI_STORE_TOKEN } from '../../stores';
|
|
6
|
+
import { MultilineText } from '../common/multiline-text';
|
|
6
7
|
import * as Styles from './chat-error.module.less';
|
|
7
8
|
|
|
8
9
|
export const ChatError: FC = observer(() => {
|
|
9
10
|
const [chatUiStore] = useDependencies(CHAT_UI_STORE_TOKEN);
|
|
11
|
+
const { error } = chatUiStore;
|
|
10
12
|
|
|
11
13
|
const handleReconnect = useCallback(async () => {
|
|
12
14
|
await chatUiStore.recover();
|
|
13
15
|
}, [chatUiStore]);
|
|
14
16
|
|
|
15
|
-
if (!
|
|
17
|
+
if (!error) {
|
|
16
18
|
return null;
|
|
17
19
|
}
|
|
18
20
|
return (
|
|
19
21
|
<Banner
|
|
20
22
|
status="critical"
|
|
21
|
-
title={
|
|
23
|
+
title={error.title}
|
|
22
24
|
icon
|
|
23
25
|
className={Styles.banner}
|
|
24
26
|
data-cy="titan-chat-error"
|
|
25
27
|
>
|
|
26
|
-
<
|
|
27
|
-
{
|
|
28
|
+
<MultilineText text={error.message} />
|
|
29
|
+
{error.recoverStrategy && (
|
|
28
30
|
<Button
|
|
29
31
|
className="m-t-2 bg-white-i"
|
|
30
32
|
small
|
|
31
33
|
onClick={handleReconnect}
|
|
32
34
|
data-cy="titan-chat-error-recover"
|
|
33
35
|
>
|
|
34
|
-
|
|
36
|
+
{error.recoverStrategy.recoverButtonTitle}
|
|
35
37
|
</Button>
|
|
36
38
|
)}
|
|
37
39
|
</Banner>
|
|
@@ -69,10 +69,31 @@ export interface ChatMessageModelFile extends ChatMessageModelBase {
|
|
|
69
69
|
fileName: string;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
|
|
72
|
+
type ChatErrorRecoverStrategy =
|
|
73
|
+
| {
|
|
74
|
+
recoverButtonTitle: string;
|
|
75
|
+
}
|
|
76
|
+
| false;
|
|
77
|
+
|
|
78
|
+
export interface ChatErrorOptions<T = any> {
|
|
79
|
+
title?: string;
|
|
80
|
+
recoverStrategy?: ChatErrorRecoverStrategy;
|
|
81
|
+
data?: T;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export class ChatError<T = any> extends Error {
|
|
73
85
|
title: string;
|
|
74
|
-
|
|
75
|
-
|
|
86
|
+
recoverStrategy: ChatErrorRecoverStrategy;
|
|
87
|
+
data?: T;
|
|
88
|
+
|
|
89
|
+
constructor(message: string, options?: ChatErrorOptions<T>) {
|
|
90
|
+
super(message);
|
|
91
|
+
this.name = 'ChatError';
|
|
92
|
+
this.title = options?.title ?? 'Chat Error';
|
|
93
|
+
this.recoverStrategy = options?.recoverStrategy ?? false;
|
|
94
|
+
this.data = options?.data;
|
|
95
|
+
Object.setPrototypeOf(this, ChatError.prototype);
|
|
96
|
+
}
|
|
76
97
|
}
|
|
77
98
|
|
|
78
99
|
export enum ChatRunState {
|
|
@@ -2,6 +2,8 @@ import { FileDescriptor } from '@servicetitan/form';
|
|
|
2
2
|
import {
|
|
3
3
|
ChatConfiguration,
|
|
4
4
|
ChatCustomizations,
|
|
5
|
+
ChatError,
|
|
6
|
+
ChatErrorOptions,
|
|
5
7
|
ChatMessageModelBase,
|
|
6
8
|
ChatMessageModelText,
|
|
7
9
|
ChatMessageModelWelcome,
|
|
@@ -83,8 +85,8 @@ export class ChatUiStoreMock implements IChatUiStore {
|
|
|
83
85
|
on: <T>(event: string, listener: ChatUiEventListener<T>) => void = cy.stub();
|
|
84
86
|
resetError: (runState: ChatRunState) => void = cy.stub();
|
|
85
87
|
setAgentTyping: (typing: boolean) => void = cy.stub();
|
|
86
|
-
|
|
87
|
-
|
|
88
|
+
setChatError: (chatError: ChatError) => void = cy.stub();
|
|
89
|
+
setError: <T = any>(message: string, options?: ChatErrorOptions<T>) => void = cy.stub();
|
|
88
90
|
setFilePickerEnabled: (isEnabled: boolean) => void = cy.stub();
|
|
89
91
|
setMessageState: (message: ChatMessageModelBase, state: ChatMessageState, data?: any) => void =
|
|
90
92
|
cy.stub();
|
|
@@ -57,7 +57,9 @@ export class ChatUiBackendEchoStore implements IChatUiBackendStore {
|
|
|
57
57
|
resolve();
|
|
58
58
|
} catch (error: any) {
|
|
59
59
|
this.chatUiStore.setMessageState(message, ChatMessageState.Failed);
|
|
60
|
-
this.chatUiStore.setError(
|
|
60
|
+
this.chatUiStore.setError(error?.message ?? error, {
|
|
61
|
+
title: 'Custom Error Title',
|
|
62
|
+
});
|
|
61
63
|
} finally {
|
|
62
64
|
this.chatUiStore.setAgentTyping(false);
|
|
63
65
|
}
|
|
@@ -77,7 +79,9 @@ export class ChatUiBackendEchoStore implements IChatUiBackendStore {
|
|
|
77
79
|
resolve();
|
|
78
80
|
} catch (error: any) {
|
|
79
81
|
this.chatUiStore.setMessageState(message, ChatMessageState.Failed);
|
|
80
|
-
this.chatUiStore.setError(
|
|
82
|
+
this.chatUiStore.setError(error?.message ?? error, {
|
|
83
|
+
title: 'Custom Error Title',
|
|
84
|
+
});
|
|
81
85
|
}
|
|
82
86
|
};
|
|
83
87
|
|
|
@@ -7,6 +7,8 @@ import {
|
|
|
7
7
|
ChatConfiguration,
|
|
8
8
|
ChatCustomizations,
|
|
9
9
|
ChatEndReason,
|
|
10
|
+
ChatError,
|
|
11
|
+
ChatErrorOptions,
|
|
10
12
|
ChatMessageModelBase,
|
|
11
13
|
ChatMessageModelFile,
|
|
12
14
|
ChatMessageModelText,
|
|
@@ -18,7 +20,6 @@ import {
|
|
|
18
20
|
ChatParticipantModel,
|
|
19
21
|
ChatRunState,
|
|
20
22
|
ChatTimer,
|
|
21
|
-
ChatUiStateError,
|
|
22
23
|
} from '../models';
|
|
23
24
|
|
|
24
25
|
export const symbolAgent = Symbol('SupportChatAgent');
|
|
@@ -59,7 +60,7 @@ export interface IChatUiStore<T extends ChatCustomizations = ChatCustomizations>
|
|
|
59
60
|
isAgentTyping: boolean;
|
|
60
61
|
isFilePickerEnabled: boolean;
|
|
61
62
|
file?: FileDescriptor;
|
|
62
|
-
error?:
|
|
63
|
+
error?: ChatError;
|
|
63
64
|
|
|
64
65
|
get agent(): ChatParticipantModel;
|
|
65
66
|
get user(): ChatParticipantModel;
|
|
@@ -77,7 +78,8 @@ export interface IChatUiStore<T extends ChatCustomizations = ChatCustomizations>
|
|
|
77
78
|
setMessages(messages: ChatMessageModelBase[]): void;
|
|
78
79
|
setAgentName(name: string): void;
|
|
79
80
|
setAgentIcon(icon: ChatParticipantIcon): void;
|
|
80
|
-
|
|
81
|
+
setChatError(chatError: ChatError): void;
|
|
82
|
+
setError<T = any>(message: string, options?: ChatErrorOptions<T>): void;
|
|
81
83
|
setStatus(status: ChatRunState): void;
|
|
82
84
|
resetError(runState: ChatRunState): void;
|
|
83
85
|
|
|
@@ -105,7 +107,7 @@ export class ChatUiStore<T extends ChatCustomizations> implements IChatUiStore<T
|
|
|
105
107
|
|
|
106
108
|
file?: FileDescriptor;
|
|
107
109
|
|
|
108
|
-
@observable error?:
|
|
110
|
+
@observable.ref error?: ChatError;
|
|
109
111
|
|
|
110
112
|
@observable endReason?: ChatEndReason;
|
|
111
113
|
|
|
@@ -220,16 +222,18 @@ export class ChatUiStore<T extends ChatCustomizations> implements IChatUiStore<T
|
|
|
220
222
|
}
|
|
221
223
|
|
|
222
224
|
@action
|
|
223
|
-
|
|
224
|
-
this.error =
|
|
225
|
-
message: errorMessage,
|
|
226
|
-
isRecoverable: isRecoverableError,
|
|
227
|
-
title: errorTitle,
|
|
228
|
-
};
|
|
225
|
+
setChatError(chatError: ChatError) {
|
|
226
|
+
this.error = chatError;
|
|
229
227
|
this.setStatus(ChatRunState.Error);
|
|
230
228
|
this.triggerScroll();
|
|
231
229
|
}
|
|
232
230
|
|
|
231
|
+
@action
|
|
232
|
+
setError<T = any>(message: string, options?: ChatErrorOptions<T>) {
|
|
233
|
+
this.error = new ChatError(message, options);
|
|
234
|
+
this.setChatError(this.error);
|
|
235
|
+
}
|
|
236
|
+
|
|
233
237
|
@action
|
|
234
238
|
setStatus(status: ChatRunState) {
|
|
235
239
|
this.status = status;
|
|
@@ -294,7 +298,7 @@ export class ChatUiStore<T extends ChatCustomizations> implements IChatUiStore<T
|
|
|
294
298
|
}
|
|
295
299
|
|
|
296
300
|
async recover() {
|
|
297
|
-
await this.emitAsync(ChatUiEvent.eventRecover);
|
|
301
|
+
await this.emitAsync(ChatUiEvent.eventRecover, this.error);
|
|
298
302
|
}
|
|
299
303
|
|
|
300
304
|
async destroy() {
|