@servicetitan/titan-chat-ui 1.0.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 (244) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/dist/assets/floating-chat-avatar.svg +16 -0
  3. package/dist/components/chat/__tests-cy__/chat-messages.test.d.ts +2 -0
  4. package/dist/components/chat/__tests-cy__/chat-messages.test.d.ts.map +1 -0
  5. package/dist/components/chat/__tests-cy__/chat-messages.test.js +28 -0
  6. package/dist/components/chat/__tests-cy__/chat-messages.test.js.map +1 -0
  7. package/dist/components/chat/__tests-cy__/chat.test.d.ts +2 -0
  8. package/dist/components/chat/__tests-cy__/chat.test.d.ts.map +1 -0
  9. package/dist/components/chat/__tests-cy__/chat.test.js +122 -0
  10. package/dist/components/chat/__tests-cy__/chat.test.js.map +1 -0
  11. package/dist/components/chat/chat-connecting.d.ts +7 -0
  12. package/dist/components/chat/chat-connecting.d.ts.map +1 -0
  13. package/dist/components/chat/chat-connecting.js +6 -0
  14. package/dist/components/chat/chat-connecting.js.map +1 -0
  15. package/dist/components/chat/chat-error.d.ts +3 -0
  16. package/dist/components/chat/chat-error.d.ts.map +1 -0
  17. package/dist/components/chat/chat-error.js +18 -0
  18. package/dist/components/chat/chat-error.js.map +1 -0
  19. package/dist/components/chat/chat-error.module.less +6 -0
  20. package/dist/components/chat/chat-input-file.d.ts +5 -0
  21. package/dist/components/chat/chat-input-file.d.ts.map +1 -0
  22. package/dist/components/chat/chat-input-file.js +43 -0
  23. package/dist/components/chat/chat-input-file.js.map +1 -0
  24. package/dist/components/chat/chat-input.d.ts +5 -0
  25. package/dist/components/chat/chat-input.d.ts.map +1 -0
  26. package/dist/components/chat/chat-input.js +94 -0
  27. package/dist/components/chat/chat-input.js.map +1 -0
  28. package/dist/components/chat/chat-input.module.less +11 -0
  29. package/dist/components/chat/chat-message-template-agent.d.ts +4 -0
  30. package/dist/components/chat/chat-message-template-agent.d.ts.map +1 -0
  31. package/dist/components/chat/chat-message-template-agent.js +14 -0
  32. package/dist/components/chat/chat-message-template-agent.js.map +1 -0
  33. package/dist/components/chat/chat-message-template-user.d.ts +4 -0
  34. package/dist/components/chat/chat-message-template-user.d.ts.map +1 -0
  35. package/dist/components/chat/chat-message-template-user.js +16 -0
  36. package/dist/components/chat/chat-message-template-user.js.map +1 -0
  37. package/dist/components/chat/chat-message-typing.d.ts +3 -0
  38. package/dist/components/chat/chat-message-typing.d.ts.map +1 -0
  39. package/dist/components/chat/chat-message-typing.js +15 -0
  40. package/dist/components/chat/chat-message-typing.js.map +1 -0
  41. package/dist/components/chat/chat-message.d.ts +12 -0
  42. package/dist/components/chat/chat-message.d.ts.map +1 -0
  43. package/dist/components/chat/chat-message.js +60 -0
  44. package/dist/components/chat/chat-message.js.map +1 -0
  45. package/dist/components/chat/chat-messages.d.ts +7 -0
  46. package/dist/components/chat/chat-messages.d.ts.map +1 -0
  47. package/dist/components/chat/chat-messages.js +12 -0
  48. package/dist/components/chat/chat-messages.js.map +1 -0
  49. package/dist/components/chat/chat-notifications.d.ts +5 -0
  50. package/dist/components/chat/chat-notifications.d.ts.map +1 -0
  51. package/dist/components/chat/chat-notifications.js +12 -0
  52. package/dist/components/chat/chat-notifications.js.map +1 -0
  53. package/dist/components/chat/chat-timer.d.ts +3 -0
  54. package/dist/components/chat/chat-timer.d.ts.map +1 -0
  55. package/dist/components/chat/chat-timer.js +18 -0
  56. package/dist/components/chat/chat-timer.js.map +1 -0
  57. package/dist/components/chat/chat-timer.module.less +5 -0
  58. package/dist/components/chat/chat.d.ts +8 -0
  59. package/dist/components/chat/chat.d.ts.map +1 -0
  60. package/dist/components/chat/chat.js +28 -0
  61. package/dist/components/chat/chat.js.map +1 -0
  62. package/dist/components/common/multiline-text.d.ts +8 -0
  63. package/dist/components/common/multiline-text.d.ts.map +1 -0
  64. package/dist/components/common/multiline-text.js +12 -0
  65. package/dist/components/common/multiline-text.js.map +1 -0
  66. package/dist/components/common/multiline-text.module.less +9 -0
  67. package/dist/components/message-content/message-content-file.d.ts +8 -0
  68. package/dist/components/message-content/message-content-file.d.ts.map +1 -0
  69. package/dist/components/message-content/message-content-file.js +11 -0
  70. package/dist/components/message-content/message-content-file.js.map +1 -0
  71. package/dist/components/message-content/message-content-text.d.ts +8 -0
  72. package/dist/components/message-content/message-content-text.d.ts.map +1 -0
  73. package/dist/components/message-content/message-content-text.js +7 -0
  74. package/dist/components/message-content/message-content-text.js.map +1 -0
  75. package/dist/components/messages/__tests-cy__/message-agent.test.d.ts +2 -0
  76. package/dist/components/messages/__tests-cy__/message-agent.test.d.ts.map +1 -0
  77. package/dist/components/messages/__tests-cy__/message-agent.test.js +89 -0
  78. package/dist/components/messages/__tests-cy__/message-agent.test.js.map +1 -0
  79. package/dist/components/messages/__tests-cy__/message-system.test.d.ts +2 -0
  80. package/dist/components/messages/__tests-cy__/message-system.test.d.ts.map +1 -0
  81. package/dist/components/messages/__tests-cy__/message-system.test.js +20 -0
  82. package/dist/components/messages/__tests-cy__/message-system.test.js.map +1 -0
  83. package/dist/components/messages/__tests-cy__/message-timeout.test.d.ts +2 -0
  84. package/dist/components/messages/__tests-cy__/message-timeout.test.d.ts.map +1 -0
  85. package/dist/components/messages/__tests-cy__/message-timeout.test.js +32 -0
  86. package/dist/components/messages/__tests-cy__/message-timeout.test.js.map +1 -0
  87. package/dist/components/messages/__tests-cy__/message-typing.test.d.ts +2 -0
  88. package/dist/components/messages/__tests-cy__/message-typing.test.d.ts.map +1 -0
  89. package/dist/components/messages/__tests-cy__/message-typing.test.js +49 -0
  90. package/dist/components/messages/__tests-cy__/message-typing.test.js.map +1 -0
  91. package/dist/components/messages/__tests-cy__/message-user.test.d.ts +2 -0
  92. package/dist/components/messages/__tests-cy__/message-user.test.d.ts.map +1 -0
  93. package/dist/components/messages/__tests-cy__/message-user.test.js +33 -0
  94. package/dist/components/messages/__tests-cy__/message-user.test.js.map +1 -0
  95. package/dist/components/messages/message-agent.d.ts +12 -0
  96. package/dist/components/messages/message-agent.d.ts.map +1 -0
  97. package/dist/components/messages/message-agent.js +18 -0
  98. package/dist/components/messages/message-agent.js.map +1 -0
  99. package/dist/components/messages/message-agent.module.less +59 -0
  100. package/dist/components/messages/message-avatar.d.ts +9 -0
  101. package/dist/components/messages/message-avatar.d.ts.map +1 -0
  102. package/dist/components/messages/message-avatar.js +14 -0
  103. package/dist/components/messages/message-avatar.js.map +1 -0
  104. package/dist/components/messages/message-avatar.module.less +26 -0
  105. package/dist/components/messages/message-footer.d.ts +7 -0
  106. package/dist/components/messages/message-footer.d.ts.map +1 -0
  107. package/dist/components/messages/message-footer.js +7 -0
  108. package/dist/components/messages/message-footer.js.map +1 -0
  109. package/dist/components/messages/message-system.d.ts +8 -0
  110. package/dist/components/messages/message-system.d.ts.map +1 -0
  111. package/dist/components/messages/message-system.js +12 -0
  112. package/dist/components/messages/message-system.js.map +1 -0
  113. package/dist/components/messages/message-system.module.less +26 -0
  114. package/dist/components/messages/message-timeout.d.ts +8 -0
  115. package/dist/components/messages/message-timeout.d.ts.map +1 -0
  116. package/dist/components/messages/message-timeout.js +16 -0
  117. package/dist/components/messages/message-timeout.js.map +1 -0
  118. package/dist/components/messages/message-typing.d.ts +8 -0
  119. package/dist/components/messages/message-typing.d.ts.map +1 -0
  120. package/dist/components/messages/message-typing.js +10 -0
  121. package/dist/components/messages/message-typing.js.map +1 -0
  122. package/dist/components/messages/message-typing.module.less +40 -0
  123. package/dist/components/messages/message-user.d.ts +9 -0
  124. package/dist/components/messages/message-user.d.ts.map +1 -0
  125. package/dist/components/messages/message-user.js +13 -0
  126. package/dist/components/messages/message-user.js.map +1 -0
  127. package/dist/components/messages/message-user.module.less +35 -0
  128. package/dist/components/messages/use-avatar-props.d.ts +4 -0
  129. package/dist/components/messages/use-avatar-props.d.ts.map +1 -0
  130. package/dist/components/messages/use-avatar-props.js +12 -0
  131. package/dist/components/messages/use-avatar-props.js.map +1 -0
  132. package/dist/index.d.ts +12 -0
  133. package/dist/index.d.ts.map +1 -0
  134. package/dist/index.js +12 -0
  135. package/dist/index.js.map +1 -0
  136. package/dist/models/chat-customizations.d.ts +29 -0
  137. package/dist/models/chat-customizations.d.ts.map +1 -0
  138. package/dist/models/chat-customizations.js +2 -0
  139. package/dist/models/chat-customizations.js.map +1 -0
  140. package/dist/models/component.d.ts +4 -0
  141. package/dist/models/component.d.ts.map +1 -0
  142. package/dist/models/component.js +2 -0
  143. package/dist/models/component.js.map +1 -0
  144. package/dist/models/index.d.ts +3 -0
  145. package/dist/models/index.d.ts.map +1 -0
  146. package/dist/models/index.js +3 -0
  147. package/dist/models/index.js.map +1 -0
  148. package/dist/models/support-chat.d.ts +66 -0
  149. package/dist/models/support-chat.d.ts.map +1 -0
  150. package/dist/models/support-chat.js +28 -0
  151. package/dist/models/support-chat.js.map +1 -0
  152. package/dist/stores/__mocks-cy__/chat-ui.store.mock.d.ts +65 -0
  153. package/dist/stores/__mocks-cy__/chat-ui.store.mock.d.ts.map +1 -0
  154. package/dist/stores/__mocks-cy__/chat-ui.store.mock.js +268 -0
  155. package/dist/stores/__mocks-cy__/chat-ui.store.mock.js.map +1 -0
  156. package/dist/stores/chat-input.store.d.ts +10 -0
  157. package/dist/stores/chat-input.store.d.ts.map +1 -0
  158. package/dist/stores/chat-input.store.js +46 -0
  159. package/dist/stores/chat-input.store.js.map +1 -0
  160. package/dist/stores/chat-ui-backend-echo.store.d.ts +13 -0
  161. package/dist/stores/chat-ui-backend-echo.store.d.ts.map +1 -0
  162. package/dist/stores/chat-ui-backend-echo.store.js +111 -0
  163. package/dist/stores/chat-ui-backend-echo.store.js.map +1 -0
  164. package/dist/stores/chat-ui-backend.store.d.ts +6 -0
  165. package/dist/stores/chat-ui-backend.store.d.ts.map +1 -0
  166. package/dist/stores/chat-ui-backend.store.js +3 -0
  167. package/dist/stores/chat-ui-backend.store.js.map +1 -0
  168. package/dist/stores/chat-ui.store.d.ts +142 -0
  169. package/dist/stores/chat-ui.store.d.ts.map +1 -0
  170. package/dist/stores/chat-ui.store.js +679 -0
  171. package/dist/stores/chat-ui.store.js.map +1 -0
  172. package/dist/stores/index.d.ts +3 -0
  173. package/dist/stores/index.d.ts.map +1 -0
  174. package/dist/stores/index.js +3 -0
  175. package/dist/stores/index.js.map +1 -0
  176. package/dist/utils/text-utils.d.ts +11 -0
  177. package/dist/utils/text-utils.d.ts.map +1 -0
  178. package/dist/utils/text-utils.js +82 -0
  179. package/dist/utils/text-utils.js.map +1 -0
  180. package/package.json +52 -0
  181. package/src/assets/floating-chat-avatar.svg +16 -0
  182. package/src/components/chat/__tests-cy__/chat-messages.test.tsx +36 -0
  183. package/src/components/chat/__tests-cy__/chat.test.tsx +156 -0
  184. package/src/components/chat/chat-connecting.tsx +23 -0
  185. package/src/components/chat/chat-error.module.less +6 -0
  186. package/src/components/chat/chat-error.module.less.d.ts +3 -0
  187. package/src/components/chat/chat-error.tsx +39 -0
  188. package/src/components/chat/chat-input-file.tsx +68 -0
  189. package/src/components/chat/chat-input.module.less +11 -0
  190. package/src/components/chat/chat-input.module.less.d.ts +3 -0
  191. package/src/components/chat/chat-input.tsx +143 -0
  192. package/src/components/chat/chat-message-template-agent.tsx +26 -0
  193. package/src/components/chat/chat-message-template-user.tsx +46 -0
  194. package/src/components/chat/chat-message-typing.tsx +19 -0
  195. package/src/components/chat/chat-message.tsx +78 -0
  196. package/src/components/chat/chat-messages.tsx +23 -0
  197. package/src/components/chat/chat-notifications.tsx +19 -0
  198. package/src/components/chat/chat-timer.module.less +5 -0
  199. package/src/components/chat/chat-timer.module.less.d.ts +3 -0
  200. package/src/components/chat/chat-timer.tsx +35 -0
  201. package/src/components/chat/chat.tsx +55 -0
  202. package/src/components/common/multiline-text.module.less +9 -0
  203. package/src/components/common/multiline-text.module.less.d.ts +3 -0
  204. package/src/components/common/multiline-text.tsx +30 -0
  205. package/src/components/message-content/message-content-file.tsx +27 -0
  206. package/src/components/message-content/message-content-text.tsx +12 -0
  207. package/src/components/messages/__tests-cy__/message-agent.test.tsx +155 -0
  208. package/src/components/messages/__tests-cy__/message-system.test.tsx +33 -0
  209. package/src/components/messages/__tests-cy__/message-timeout.test.tsx +38 -0
  210. package/src/components/messages/__tests-cy__/message-typing.test.tsx +58 -0
  211. package/src/components/messages/__tests-cy__/message-user.test.tsx +52 -0
  212. package/src/components/messages/message-agent.module.less +59 -0
  213. package/src/components/messages/message-agent.module.less.d.ts +9 -0
  214. package/src/components/messages/message-agent.tsx +62 -0
  215. package/src/components/messages/message-avatar.module.less +26 -0
  216. package/src/components/messages/message-avatar.module.less.d.ts +5 -0
  217. package/src/components/messages/message-avatar.tsx +33 -0
  218. package/src/components/messages/message-footer.tsx +17 -0
  219. package/src/components/messages/message-system.module.less +26 -0
  220. package/src/components/messages/message-system.module.less.d.ts +5 -0
  221. package/src/components/messages/message-system.tsx +35 -0
  222. package/src/components/messages/message-timeout.tsx +42 -0
  223. package/src/components/messages/message-typing.module.less +40 -0
  224. package/src/components/messages/message-typing.module.less.d.ts +5 -0
  225. package/src/components/messages/message-typing.tsx +25 -0
  226. package/src/components/messages/message-user.module.less +35 -0
  227. package/src/components/messages/message-user.module.less.d.ts +7 -0
  228. package/src/components/messages/message-user.tsx +49 -0
  229. package/src/components/messages/use-avatar-props.tsx +17 -0
  230. package/src/cypress.d.ts +10 -0
  231. package/src/index.ts +11 -0
  232. package/src/models/chat-customizations.ts +34 -0
  233. package/src/models/component.ts +3 -0
  234. package/src/models/index.ts +2 -0
  235. package/src/models/support-chat.ts +84 -0
  236. package/src/stores/__mocks-cy__/chat-ui.store.mock.ts +105 -0
  237. package/src/stores/chat-input.store.ts +25 -0
  238. package/src/stores/chat-ui-backend-echo.store.ts +94 -0
  239. package/src/stores/chat-ui-backend.store.ts +10 -0
  240. package/src/stores/chat-ui.store.ts +537 -0
  241. package/src/stores/index.ts +10 -0
  242. package/src/utils/text-utils.ts +93 -0
  243. package/tsconfig.json +15 -0
  244. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,40 @@
1
+ @import '@servicetitan/tokens/dist/tokens.less';
2
+
3
+ .dotsContainer {
4
+ @keyframes dot-jumping {
5
+ 0% {
6
+ transform: translateY(0);
7
+ }
8
+ 16.6666% {
9
+ transform: translateY(-6px);
10
+ }
11
+ 33.3333% {
12
+ transform: translateY(0);
13
+ }
14
+ 100% {
15
+ transform: translateY(0);
16
+ }
17
+ }
18
+
19
+ & .dot {
20
+ width: 6px;
21
+ height: 6px;
22
+ margin: @spacing-0 2px;
23
+ background-color: @color-neutral-80;
24
+ border-radius: @border-radius-circular;
25
+ display: inline-block;
26
+ animation: dot-jumping 1.5s infinite ease-in-out;
27
+ }
28
+
29
+ & .dot:nth-child(1) {
30
+ animation-delay: 0s;
31
+ }
32
+
33
+ & .dot:nth-child(2) {
34
+ animation-delay: 0.5s;
35
+ }
36
+
37
+ & .dot:nth-child(3) {
38
+ animation-delay: 1s;
39
+ }
40
+ }
@@ -0,0 +1,5 @@
1
+ export const __esModule: true;
2
+ export const dot: string;
3
+ export const dotJumping: string;
4
+ export const dotsContainer: string;
5
+
@@ -0,0 +1,25 @@
1
+ import { Stack } from '@servicetitan/design-system';
2
+ import { FC } from 'react';
3
+ import { IDataCyProps } from '../../models/component';
4
+ import { MessageAgent } from './message-agent';
5
+ import { IMessageAvatarProps } from './message-avatar';
6
+ import * as Styles from './message-typing.module.less';
7
+
8
+ export interface IMessageTypingProps extends IDataCyProps {
9
+ avatar: IMessageAvatarProps;
10
+ }
11
+
12
+ export const MessageTyping: FC<IMessageTypingProps> = ({ avatar, ...rest }) => {
13
+ const dataCy = rest['data-cy'] ?? 'chat-message-typing';
14
+ return (
15
+ <MessageAgent avatar={avatar} data-cy={dataCy} subtle>
16
+ <Stack className="h-100" alignItems="center">
17
+ <div className={Styles.dotsContainer} data-cy={`${dataCy}-dots`}>
18
+ <span className={Styles.dot} />
19
+ <span className={Styles.dot} />
20
+ <span className={Styles.dot} />
21
+ </div>
22
+ </Stack>
23
+ </MessageAgent>
24
+ );
25
+ };
@@ -0,0 +1,35 @@
1
+ @import '@servicetitan/tokens/dist/tokens.less';
2
+
3
+ /* stylelint-disable declaration-property-value-no-unknown */
4
+ .messageRoot {
5
+ display: grid;
6
+ grid-template-areas:
7
+ '. content'
8
+ '. footer';
9
+ grid-template-rows: auto auto;
10
+ grid-template-columns: minmax(@spacing-5, auto) 1fr;
11
+ column-gap: @spacing-2;
12
+ row-gap: @spacing-1;
13
+ }
14
+
15
+ .messageFooter {
16
+ grid-area: footer;
17
+ justify-self: end;
18
+ }
19
+
20
+ .messageContent {
21
+ grid-area: content;
22
+ overflow-wrap: anywhere;
23
+ justify-self: end;
24
+ }
25
+
26
+ .messageBubble {
27
+ color: @color-neutral-200;
28
+ padding: @spacing-2 @spacing-3;
29
+ background-color: @color-neutral-50;
30
+ border-radius: @spacing-3 @spacing-3 @spacing-0 @spacing-3;
31
+
32
+ &.error {
33
+ background-color: @color-red-100;
34
+ }
35
+ }
@@ -0,0 +1,7 @@
1
+ export const __esModule: true;
2
+ export const error: string;
3
+ export const messageBubble: string;
4
+ export const messageContent: string;
5
+ export const messageFooter: string;
6
+ export const messageRoot: string;
7
+
@@ -0,0 +1,49 @@
1
+ import { Stack } from '@servicetitan/design-system';
2
+ import classNames from 'classnames';
3
+ import { FC, PropsWithChildren, ReactNode } from 'react';
4
+ import { IDataCyProps } from '../../models/component';
5
+ import * as Styles from './message-user.module.less';
6
+
7
+ export interface IMessageUserProps extends IDataCyProps {
8
+ messageFooter?: ReactNode;
9
+ isError?: boolean;
10
+ className?: string;
11
+ }
12
+
13
+ export const MessageUser: FC<PropsWithChildren<IMessageUserProps>> = ({
14
+ children,
15
+ className,
16
+ isError,
17
+ messageFooter,
18
+ ...rest
19
+ }) => {
20
+ const dataCy = rest['data-cy'] ?? 'chat-message-user';
21
+ const dataCy2 = isError ? 'chat-message-error' : 'chat-message-normal';
22
+ return (
23
+ <div
24
+ className={classNames(Styles.messageRoot, 'max-w-100')}
25
+ data-cy={dataCy}
26
+ data-cy2={dataCy2}
27
+ >
28
+ <Stack
29
+ direction="column"
30
+ spacing="1"
31
+ className={classNames('max-w-100', Styles.messageContent)}
32
+ >
33
+ <div
34
+ className={classNames(Styles.messageBubble, {
35
+ [Styles.error]: isError,
36
+ })}
37
+ data-cy={`${dataCy}-content`}
38
+ >
39
+ {children}
40
+ </div>
41
+ </Stack>
42
+ {Boolean(messageFooter) && (
43
+ <div className={Styles.messageFooter} data-cy={`${dataCy}-footer`}>
44
+ {messageFooter}
45
+ </div>
46
+ )}
47
+ </div>
48
+ );
49
+ };
@@ -0,0 +1,17 @@
1
+ import { useMemo } from 'react';
2
+ import { ChatParticipantIcon } from '../../models/support-chat';
3
+ import { getNameInitialsFirst } from '../../utils/text-utils';
4
+ import { IMessageAvatarProps } from './message-avatar';
5
+
6
+ export const useAvatarProps = (name: string, icon: ChatParticipantIcon): IMessageAvatarProps => {
7
+ return useMemo<IMessageAvatarProps>(
8
+ () => ({
9
+ name:
10
+ name.length && icon === ChatParticipantIcon.Initials
11
+ ? getNameInitialsFirst(name)
12
+ : name,
13
+ icon,
14
+ }),
15
+ [icon, name]
16
+ );
17
+ };
@@ -0,0 +1,10 @@
1
+ // eslint-disable-next-line spaced-comment
2
+ /// <reference types="cypress" />
3
+ declare namespace Cypress {
4
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
5
+ interface Chainable<Subject = any> {
6
+ getCy<E extends Node = HTMLElement>(value: string): Chainable<JQuery<E>>;
7
+ getCy2<E extends Node = HTMLElement>(value: string): Chainable<JQuery<E>>;
8
+ getAnvil<E extends Node = HTMLElement>(value: string, extra?: string): Chainable<JQuery<E>>;
9
+ }
10
+ }
package/src/index.ts ADDED
@@ -0,0 +1,11 @@
1
+ export { MessageAgent, IMessageAgentProps } from './components/messages/message-agent';
2
+ export { MessageUser, IMessageUserProps } from './components/messages/message-user';
3
+ export { MessageSystem, IMessageSystemProps } from './components/messages/message-system';
4
+ export { MessageTyping, IMessageTypingProps } from './components/messages/message-typing';
5
+ export { MessageTimeout, IMessageTimeoutProps } from './components/messages/message-timeout';
6
+ export { MessageAvatar, IMessageAvatarProps } from './components/messages/message-avatar';
7
+ export { MessageFooter, IMessageFooterProps } from './components/messages/message-footer';
8
+ export { useAvatarProps } from './components/messages/use-avatar-props';
9
+ export { MultilineText } from './components/common/multiline-text';
10
+ export * from './stores';
11
+ export * from './models';
@@ -0,0 +1,34 @@
1
+ import { FC, PropsWithChildren, ReactNode } from 'react';
2
+ import { IMessageTypingProps } from '../components/messages/message-typing';
3
+ import { ChatMessageModelBase } from './support-chat';
4
+
5
+ export interface IChatMessageProps<T extends ChatMessageModelBase = ChatMessageModelBase> {
6
+ message: T;
7
+ }
8
+
9
+ export interface ChatMessageTemplateCustomization {
10
+ component: FC<PropsWithChildren<IChatMessageProps>>;
11
+ predicate: (message: ChatMessageModelBase) => boolean;
12
+ }
13
+
14
+ export type ChatMessageTemplateCustomizations = Partial<{
15
+ agent: ChatMessageTemplateCustomization;
16
+ user: ChatMessageTemplateCustomization;
17
+ }>;
18
+
19
+ export interface ChatMessageCustomization {
20
+ component: FC<IChatMessageProps>;
21
+ predicate: (message: ChatMessageModelBase) => boolean;
22
+ isSystem?: boolean; // System messages are messages that are not sent by the user or the agent and rendered without chat bubbles
23
+ }
24
+
25
+ export interface ChatMessageTypingCustomization {
26
+ component: FC<IMessageTypingProps>;
27
+ }
28
+
29
+ export type ChatCustomizations = Partial<{
30
+ messageTemplates: ChatMessageTemplateCustomizations;
31
+ messages: ChatMessageCustomization[];
32
+ messageTyping: ChatMessageTypingCustomization;
33
+ footerComponent: ReactNode;
34
+ }>;
@@ -0,0 +1,3 @@
1
+ export interface IDataCyProps {
2
+ ['data-cy']?: string;
3
+ }
@@ -0,0 +1,2 @@
1
+ export * from './support-chat';
2
+ export * from './chat-customizations';
@@ -0,0 +1,84 @@
1
+ export interface ChatTimer {
2
+ secondsLeft: number;
3
+ secondsTotal: number;
4
+ }
5
+
6
+ export enum ChatMessageState {
7
+ Delivering = 'Delivering',
8
+ Delivered = 'Delivered',
9
+ Failed = 'Failed',
10
+ }
11
+
12
+ export enum ChatEndReason {
13
+ ByUser = 'ByUser',
14
+ ByAgent = 'ByAgent',
15
+ ByTimeout = 'ByTimeout',
16
+ ByFailedToStart = 'ByFailedToStart',
17
+ }
18
+
19
+ export enum ChatParticipantIcon {
20
+ Empty = 'empty',
21
+ Initials = 'initials',
22
+ Bot = 'bot',
23
+ }
24
+
25
+ export interface ChatParticipantModel {
26
+ isAgent: boolean;
27
+ name: string;
28
+ icon: ChatParticipantIcon;
29
+ }
30
+
31
+ export type ChatMessageModelType =
32
+ | 'welcome'
33
+ | 'message'
34
+ | 'timeout'
35
+ | 'file'
36
+ | 'askSupport'
37
+ | 'askSupportButtons';
38
+
39
+ export interface ChatConfiguration {
40
+ agentName?: string;
41
+ agentIcon?: ChatParticipantIcon;
42
+ }
43
+
44
+ export interface ChatMessageModelBase {
45
+ id: string;
46
+ timestamp: Date;
47
+ type: ChatMessageModelType;
48
+ participant: ChatParticipantModel;
49
+ state: ChatMessageState;
50
+ data?: any;
51
+ }
52
+
53
+ export interface ChatMessageModelTimeout extends ChatMessageModelBase {
54
+ type: 'timeout';
55
+ }
56
+
57
+ export interface ChatMessageModelText extends ChatMessageModelBase {
58
+ type: 'message';
59
+ message: string;
60
+ }
61
+
62
+ export interface ChatMessageModelWelcome extends ChatMessageModelBase {
63
+ type: 'welcome';
64
+ message: string;
65
+ }
66
+
67
+ export interface ChatMessageModelFile extends ChatMessageModelBase {
68
+ type: 'file';
69
+ fileName: string;
70
+ }
71
+
72
+ export interface ChatUiStateError {
73
+ title: string;
74
+ message: string;
75
+ isRecoverable?: boolean;
76
+ }
77
+
78
+ export enum ChatRunState {
79
+ Offline = 0,
80
+ Initializing = 1,
81
+ Started = 2,
82
+ Ended = 3,
83
+ Error = 4,
84
+ }
@@ -0,0 +1,105 @@
1
+ import { FileDescriptor } from '@servicetitan/form';
2
+ import {
3
+ ChatConfiguration,
4
+ ChatCustomizations,
5
+ ChatMessageModelBase,
6
+ ChatMessageModelText,
7
+ ChatMessageModelWelcome,
8
+ ChatMessageState,
9
+ ChatParticipantIcon,
10
+ ChatRunState,
11
+ ChatTimer,
12
+ } from '../../models';
13
+ import { ChatUiEventListener, IChatUiStore, symbolAgent, symbolUser } from '../chat-ui.store';
14
+
15
+ export class ChatUiStoreMock implements IChatUiStore {
16
+ scrollCounter = 0;
17
+ isFilePickerEnabled = true;
18
+ isStarting = false;
19
+ isStarted = true;
20
+ isAgentTyping = false;
21
+ status = ChatRunState.Started;
22
+ file = undefined;
23
+ participants = {
24
+ [symbolAgent]: {
25
+ isAgent: true,
26
+ name: 'Agent',
27
+ icon: ChatParticipantIcon.Bot,
28
+ },
29
+ [symbolUser]: {
30
+ isAgent: false,
31
+ name: 'User',
32
+ icon: ChatParticipantIcon.Initials,
33
+ },
34
+ };
35
+ get user() {
36
+ return this.participants[symbolUser];
37
+ }
38
+ get agent() {
39
+ return this.participants[symbolAgent];
40
+ }
41
+ get isEnded(): boolean {
42
+ return false;
43
+ }
44
+ get isError(): boolean {
45
+ return false;
46
+ }
47
+ get customizations(): ChatCustomizations {
48
+ return {};
49
+ }
50
+ messages: ChatMessageModelBase[] = [
51
+ {
52
+ type: 'welcome',
53
+ id: 'messageid',
54
+ state: ChatMessageState.Delivered,
55
+ timestamp: new Date(),
56
+ message:
57
+ 'Hi there! I’m ServiceTitan Bot, a chatbot powered by Titan Intelligence designed to answer your questions about using ServiceTitan. I’m here to help you, but I’m still learning, so I might not have all the answers yet. Do you have a question?',
58
+ participant: {
59
+ isAgent: true,
60
+ name: 'Agent',
61
+ icon: ChatParticipantIcon.Bot,
62
+ },
63
+ } as ChatMessageModelWelcome,
64
+ {
65
+ type: 'message',
66
+ id: 'messageid2',
67
+ state: ChatMessageState.Delivered,
68
+ timestamp: new Date(),
69
+ message: 'How to create a form?',
70
+ participant: {
71
+ isAgent: false,
72
+ name: 'User',
73
+ icon: ChatParticipantIcon.Initials,
74
+ },
75
+ } as ChatMessageModelText,
76
+ ];
77
+ sendMessageRetry: (message: ChatMessageModelBase) => Promise<void> = cy.stub();
78
+ sendMessageText: (message: string) => Promise<void> = cy.stub();
79
+ setFile: (file: FileDescriptor | undefined) => void = cy.stub();
80
+ triggerScroll: () => void = cy.stub();
81
+ addMessage: (isAgent: boolean, message: string, data?: any) => void = cy.stub();
82
+ off: <T>(event: string, listener: ChatUiEventListener<T>) => void = cy.stub();
83
+ on: <T>(event: string, listener: ChatUiEventListener<T>) => void = cy.stub();
84
+ resetError: (runState: ChatRunState) => void = cy.stub();
85
+ setAgentTyping: (typing: boolean) => void = cy.stub();
86
+ setError: (errorTitle: string, errorMessage: string, isRecoverableError?: boolean) => void =
87
+ cy.stub();
88
+ setFilePickerEnabled: (isEnabled: boolean) => void = cy.stub();
89
+ setMessageState: (message: ChatMessageModelBase, state: ChatMessageState, data?: any) => void =
90
+ cy.stub();
91
+ addMessageWelcome: (message: string) => void = cy.stub();
92
+ setCustomizationContext: (customizationContext?: ChatCustomizations) => void = cy.stub();
93
+ chasitorTyping: (isTyping: boolean) => Promise<void> = cy.stub();
94
+ configure: (configuration?: ChatConfiguration) => void = cy.stub();
95
+ run: (configuration?: ChatConfiguration, data?: unknown) => Promise<void> = cy.stub();
96
+ recover: () => Promise<void> = cy.stub();
97
+ destroy: () => Promise<void> = cy.stub();
98
+ setAgentIcon: (icon: ChatParticipantIcon) => void = cy.stub();
99
+ setAgentName: (name: string) => void = cy.stub();
100
+ setIncomingSound: (soundUrl: string) => void = cy.stub();
101
+ setMessages: (messages: ChatMessageModelBase[]) => void = cy.stub();
102
+ setStatus: (status: ChatRunState) => void = cy.stub();
103
+ setTimer: (timer?: ChatTimer) => void = cy.stub();
104
+ restartTimers: () => Promise<void> = cy.stub();
105
+ }
@@ -0,0 +1,25 @@
1
+ import { TextAreaFieldState } from '@servicetitan/form';
2
+ import { injectable } from '@servicetitan/react-ioc';
3
+ import { FormState } from 'formstate';
4
+ import { computed, makeObservable, observable } from 'mobx';
5
+
6
+ @injectable()
7
+ export class ChatInputStore {
8
+ @observable
9
+ formState = new FormState({
10
+ message: new TextAreaFieldState('')
11
+ .validators((text: string) =>
12
+ text.length > 6000 ? 'Message character max is 6000.' : false
13
+ )
14
+ .disableAutoValidation(),
15
+ });
16
+
17
+ @computed
18
+ get isEmpty() {
19
+ return !this.formState.$.message.value;
20
+ }
21
+
22
+ constructor() {
23
+ makeObservable(this);
24
+ }
25
+ }
@@ -0,0 +1,94 @@
1
+ import { inject, injectable } from '@servicetitan/react-ioc';
2
+ import {
3
+ ChatMessageModelBase,
4
+ ChatMessageModelText,
5
+ ChatMessageState,
6
+ ChatRunState,
7
+ } from '../models';
8
+ import { IChatUiBackendStore } from './chat-ui-backend.store';
9
+ import {
10
+ CHAT_UI_STORE_TOKEN,
11
+ ChatUiEvent,
12
+ ChatUiEventListener,
13
+ IChatUiStore,
14
+ } from './chat-ui.store';
15
+
16
+ @injectable()
17
+ export class ChatUiBackendEchoStore implements IChatUiBackendStore {
18
+ constructor(@inject(CHAT_UI_STORE_TOKEN) readonly chatUiStore: IChatUiStore) {}
19
+
20
+ subscribe() {
21
+ this.chatUiStore.on(ChatUiEvent.eventRun, this.handleRun);
22
+ this.chatUiStore.on(ChatUiEvent.eventMessageSend, this.handleTextMessageSend);
23
+ this.chatUiStore.on(ChatUiEvent.eventMessageSendRetry, this.handleMessageSendRetry);
24
+ }
25
+
26
+ unsubscribe() {
27
+ this.chatUiStore.off(ChatUiEvent.eventRun, this.handleRun);
28
+ this.chatUiStore.off(ChatUiEvent.eventMessageSend, this.handleTextMessageSend);
29
+ this.chatUiStore.off(ChatUiEvent.eventMessageSendRetry, this.handleMessageSendRetry);
30
+ }
31
+
32
+ private handleRun: ChatUiEventListener = async (resolve, _) => {
33
+ this.chatUiStore.setStatus(ChatRunState.Initializing);
34
+ await new Promise(resolve => setTimeout(resolve, 1000));
35
+ this.chatUiStore.addMessageWelcome(
36
+ "Hello! I'm generic echo bot. I can echo your messages. Try it out!"
37
+ );
38
+ this.chatUiStore.setStatus(ChatRunState.Started);
39
+ resolve();
40
+ };
41
+
42
+ private handleTextMessageSend: ChatUiEventListener = async (
43
+ resolve,
44
+ _,
45
+ message: ChatMessageModelText
46
+ ) => {
47
+ const isErrorSimulated = message.message.startsWith('[Error]');
48
+ try {
49
+ this.chatUiStore.setAgentTyping(true);
50
+ await new Promise(resolve => setTimeout(resolve, 1000));
51
+ if (isErrorSimulated) {
52
+ throw new Error(message.message.split('[Error]')[1]);
53
+ }
54
+ this.chatUiStore.setMessageState(message, ChatMessageState.Delivered);
55
+ this.chatUiStore.addMessage(true, this.getEchoAnswer(message));
56
+ this.chatUiStore.resetError(ChatRunState.Started);
57
+ resolve();
58
+ } catch (error: any) {
59
+ this.chatUiStore.setMessageState(message, ChatMessageState.Failed);
60
+ this.chatUiStore.setError('Failed to send message', error?.message ?? error, false);
61
+ } finally {
62
+ this.chatUiStore.setAgentTyping(false);
63
+ }
64
+ };
65
+
66
+ private handleMessageSendRetry: ChatUiEventListener = async (
67
+ resolve,
68
+ _,
69
+ message: ChatMessageModelBase
70
+ ) => {
71
+ try {
72
+ this.chatUiStore.setMessageState(message, ChatMessageState.Delivering);
73
+ await new Promise(resolve => setTimeout(resolve, 1000));
74
+ this.chatUiStore.setMessageState(message, ChatMessageState.Delivered);
75
+ this.chatUiStore.addMessage(true, this.getEchoAnswer(message));
76
+ this.chatUiStore.resetError(ChatRunState.Started);
77
+ resolve();
78
+ } catch (error: any) {
79
+ this.chatUiStore.setMessageState(message, ChatMessageState.Failed);
80
+ this.chatUiStore.setError('Custom error', error?.message ?? error, false);
81
+ }
82
+ };
83
+
84
+ private getEchoAnswer(message: ChatMessageModelBase) {
85
+ if (message.type === 'message') {
86
+ let messageText = (message as ChatMessageModelText).message;
87
+ if (messageText.startsWith('[Error]')) {
88
+ messageText = messageText.split('[Error]')[1];
89
+ }
90
+ return `Echo: ${messageText}`;
91
+ }
92
+ return `Echo bot cannot answer to message type ${message.type} yet`;
93
+ }
94
+ }
@@ -0,0 +1,10 @@
1
+ import { symbolToken } from '@servicetitan/react-ioc';
2
+
3
+ export const CHAT_UI_BACKEND_STORE_TOKEN = symbolToken<IChatUiBackendStore>(
4
+ 'CHAT_UI_BACKEND_STORE_TOKEN'
5
+ );
6
+
7
+ export interface IChatUiBackendStore {
8
+ subscribe(): void;
9
+ unsubscribe(): void;
10
+ }