@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.
Files changed (30) hide show
  1. package/CHANGELOG.md +26 -4
  2. package/README.md +15 -0
  3. package/dist/components/chat/__tests-cy__/chat.test.js +11 -6
  4. package/dist/components/chat/__tests-cy__/chat.test.js.map +1 -1
  5. package/dist/components/chat/chat-error.d.ts.map +1 -1
  6. package/dist/components/chat/chat-error.js +5 -3
  7. package/dist/components/chat/chat-error.js.map +1 -1
  8. package/dist/models/support-chat.d.ts +13 -3
  9. package/dist/models/support-chat.d.ts.map +1 -1
  10. package/dist/models/support-chat.js +29 -0
  11. package/dist/models/support-chat.js.map +1 -1
  12. package/dist/stores/__mocks-cy__/chat-ui.store.mock.d.ts +3 -2
  13. package/dist/stores/__mocks-cy__/chat-ui.store.mock.d.ts.map +1 -1
  14. package/dist/stores/__mocks-cy__/chat-ui.store.mock.js +6 -0
  15. package/dist/stores/__mocks-cy__/chat-ui.store.mock.js.map +1 -1
  16. package/dist/stores/chat-ui-backend-echo.store.d.ts.map +1 -1
  17. package/dist/stores/chat-ui-backend-echo.store.js +6 -2
  18. package/dist/stores/chat-ui-backend-echo.store.js.map +1 -1
  19. package/dist/stores/chat-ui.store.d.ts +7 -5
  20. package/dist/stores/chat-ui.store.d.ts.map +1 -1
  21. package/dist/stores/chat-ui.store.js +17 -11
  22. package/dist/stores/chat-ui.store.js.map +1 -1
  23. package/package.json +2 -2
  24. package/src/components/chat/__tests-cy__/chat.test.tsx +11 -9
  25. package/src/components/chat/chat-error.tsx +8 -6
  26. package/src/models/support-chat.ts +24 -3
  27. package/src/stores/__mocks-cy__/chat-ui.store.mock.ts +4 -2
  28. package/src/stores/chat-ui-backend-echo.store.ts +6 -2
  29. package/src/stores/chat-ui.store.ts +15 -11
  30. 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
- .should('have.length', 1)
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
- 'Hi there! Im Titan, an AI chatbot powered by Titan Intelligence.'
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('Custom error', 'error message', true);
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.only('should handle send message error and retry', () => {
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, BodyText, Button } from '@servicetitan/design-system';
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 (!chatUiStore.error) {
17
+ if (!error) {
16
18
  return null;
17
19
  }
18
20
  return (
19
21
  <Banner
20
22
  status="critical"
21
- title={chatUiStore.error.title}
23
+ title={error.title}
22
24
  icon
23
25
  className={Styles.banner}
24
26
  data-cy="titan-chat-error"
25
27
  >
26
- <BodyText el="div">{chatUiStore.error.message}</BodyText>
27
- {chatUiStore.error.isRecoverable && (
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
- Reconnect
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
- export interface ChatUiStateError {
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
- message: string;
75
- isRecoverable?: boolean;
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
- setError: (errorTitle: string, errorMessage: string, isRecoverableError?: boolean) => void =
87
- cy.stub();
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('Failed to send message', error?.message ?? error, false);
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('Custom error', error?.message ?? error, false);
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?: ChatUiStateError;
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
- setError(errorTitle: string, errorMessage: string, isRecoverableError?: boolean): void;
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?: ChatUiStateError;
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
- setError(errorTitle: string, errorMessage: string, isRecoverableError = false) {
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() {