@servicetitan/titan-chatbot-ui 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 (226) hide show
  1. package/CHANGELOG.md +142 -0
  2. package/README.md +15 -0
  3. package/dist/components/chatbot/__tests-cy__/chatbot-help-center.test.d.ts +2 -0
  4. package/dist/components/chatbot/__tests-cy__/chatbot-help-center.test.d.ts.map +1 -0
  5. package/dist/components/chatbot/__tests-cy__/chatbot-help-center.test.js +160 -0
  6. package/dist/components/chatbot/__tests-cy__/chatbot-help-center.test.js.map +1 -0
  7. package/dist/components/chatbot/__tests-cy__/chatbot-live.test.d.ts +2 -0
  8. package/dist/components/chatbot/__tests-cy__/chatbot-live.test.d.ts.map +1 -0
  9. package/dist/components/chatbot/__tests-cy__/chatbot-live.test.js +90 -0
  10. package/dist/components/chatbot/__tests-cy__/chatbot-live.test.js.map +1 -0
  11. package/dist/components/chatbot/__tests-cy__/chatbot-titan-chatbot.test.d.ts +2 -0
  12. package/dist/components/chatbot/__tests-cy__/chatbot-titan-chatbot.test.d.ts.map +1 -0
  13. package/dist/components/chatbot/__tests-cy__/chatbot-titan-chatbot.test.js +155 -0
  14. package/dist/components/chatbot/__tests-cy__/chatbot-titan-chatbot.test.js.map +1 -0
  15. package/dist/components/chatbot/__tests-cy__/chatbot.test.d.ts +2 -0
  16. package/dist/components/chatbot/__tests-cy__/chatbot.test.d.ts.map +1 -0
  17. package/dist/components/chatbot/__tests-cy__/chatbot.test.js +139 -0
  18. package/dist/components/chatbot/__tests-cy__/chatbot.test.js.map +1 -0
  19. package/dist/components/chatbot/chatbot-to-chat-provider-adapter.d.ts +6 -0
  20. package/dist/components/chatbot/chatbot-to-chat-provider-adapter.d.ts.map +1 -0
  21. package/dist/components/chatbot/chatbot-to-chat-provider-adapter.js +21 -0
  22. package/dist/components/chatbot/chatbot-to-chat-provider-adapter.js.map +1 -0
  23. package/dist/components/chatbot/chatbot.d.ts +9 -0
  24. package/dist/components/chatbot/chatbot.d.ts.map +1 -0
  25. package/dist/components/chatbot/chatbot.js +68 -0
  26. package/dist/components/chatbot/chatbot.js.map +1 -0
  27. package/dist/components/chatbot/dialog/chatbot-restart-dialog.d.ts +6 -0
  28. package/dist/components/chatbot/dialog/chatbot-restart-dialog.d.ts.map +1 -0
  29. package/dist/components/chatbot/dialog/chatbot-restart-dialog.js +20 -0
  30. package/dist/components/chatbot/dialog/chatbot-restart-dialog.js.map +1 -0
  31. package/dist/components/chatbot/dialog/chatbot-restart-link.d.ts +4 -0
  32. package/dist/components/chatbot/dialog/chatbot-restart-link.d.ts.map +1 -0
  33. package/dist/components/chatbot/dialog/chatbot-restart-link.js +13 -0
  34. package/dist/components/chatbot/dialog/chatbot-restart-link.js.map +1 -0
  35. package/dist/components/chatbot/feedback/__tests-cy__/chatbot-message-feedback-form-guardrail.test.d.ts +2 -0
  36. package/dist/components/chatbot/feedback/__tests-cy__/chatbot-message-feedback-form-guardrail.test.d.ts.map +1 -0
  37. package/dist/components/chatbot/feedback/__tests-cy__/chatbot-message-feedback-form-guardrail.test.js +65 -0
  38. package/dist/components/chatbot/feedback/__tests-cy__/chatbot-message-feedback-form-guardrail.test.js.map +1 -0
  39. package/dist/components/chatbot/feedback/__tests-cy__/chatbot-message-feedback-form.test.d.ts +2 -0
  40. package/dist/components/chatbot/feedback/__tests-cy__/chatbot-message-feedback-form.test.d.ts.map +1 -0
  41. package/dist/components/chatbot/feedback/__tests-cy__/chatbot-message-feedback-form.test.js +100 -0
  42. package/dist/components/chatbot/feedback/__tests-cy__/chatbot-message-feedback-form.test.js.map +1 -0
  43. package/dist/components/chatbot/feedback/chatbot-message-feedback-form-guardrail.d.ts +3 -0
  44. package/dist/components/chatbot/feedback/chatbot-message-feedback-form-guardrail.d.ts.map +1 -0
  45. package/dist/components/chatbot/feedback/chatbot-message-feedback-form-guardrail.js +13 -0
  46. package/dist/components/chatbot/feedback/chatbot-message-feedback-form-guardrail.js.map +1 -0
  47. package/dist/components/chatbot/feedback/chatbot-message-feedback-form.d.ts +3 -0
  48. package/dist/components/chatbot/feedback/chatbot-message-feedback-form.d.ts.map +1 -0
  49. package/dist/components/chatbot/feedback/chatbot-message-feedback-form.js +12 -0
  50. package/dist/components/chatbot/feedback/chatbot-message-feedback-form.js.map +1 -0
  51. package/dist/components/chatbot/feedback/chatbot-message-feedback-form.module.less +15 -0
  52. package/dist/components/chatbot/feedback/chatbot-message-feedback-popover.d.ts +9 -0
  53. package/dist/components/chatbot/feedback/chatbot-message-feedback-popover.d.ts.map +1 -0
  54. package/dist/components/chatbot/feedback/chatbot-message-feedback-popover.js +60 -0
  55. package/dist/components/chatbot/feedback/chatbot-message-feedback-popover.js.map +1 -0
  56. package/dist/components/chatbot/feedback/chatbot-message-feedback-popover.module.less +10 -0
  57. package/dist/components/chatbot/feedback/chatbot-message-feedback.d.ts +9 -0
  58. package/dist/components/chatbot/feedback/chatbot-message-feedback.d.ts.map +1 -0
  59. package/dist/components/chatbot/feedback/chatbot-message-feedback.js +17 -0
  60. package/dist/components/chatbot/feedback/chatbot-message-feedback.js.map +1 -0
  61. package/dist/components/chatbot/feedback/chatbot-session-feedback-link.d.ts +4 -0
  62. package/dist/components/chatbot/feedback/chatbot-session-feedback-link.d.ts.map +1 -0
  63. package/dist/components/chatbot/feedback/chatbot-session-feedback-link.js +16 -0
  64. package/dist/components/chatbot/feedback/chatbot-session-feedback-link.js.map +1 -0
  65. package/dist/components/chatbot/feedback/chatbot-session-feedback-modal.d.ts +5 -0
  66. package/dist/components/chatbot/feedback/chatbot-session-feedback-modal.d.ts.map +1 -0
  67. package/dist/components/chatbot/feedback/chatbot-session-feedback-modal.js +24 -0
  68. package/dist/components/chatbot/feedback/chatbot-session-feedback-modal.js.map +1 -0
  69. package/dist/components/chatbot/filters/chatbot-filter.d.ts +8 -0
  70. package/dist/components/chatbot/filters/chatbot-filter.d.ts.map +1 -0
  71. package/dist/components/chatbot/filters/chatbot-filter.js +61 -0
  72. package/dist/components/chatbot/filters/chatbot-filter.js.map +1 -0
  73. package/dist/components/chatbot/filters/chatbot-filter.module.css +19 -0
  74. package/dist/components/chatbot/filters/chatbot-filters.d.ts +5 -0
  75. package/dist/components/chatbot/filters/chatbot-filters.d.ts.map +1 -0
  76. package/dist/components/chatbot/filters/chatbot-filters.js +11 -0
  77. package/dist/components/chatbot/filters/chatbot-filters.js.map +1 -0
  78. package/dist/components/chatbot/messages/__tests-cy__/chatbot-message-answer.test.d.ts +2 -0
  79. package/dist/components/chatbot/messages/__tests-cy__/chatbot-message-answer.test.d.ts.map +1 -0
  80. package/dist/components/chatbot/messages/__tests-cy__/chatbot-message-answer.test.js +85 -0
  81. package/dist/components/chatbot/messages/__tests-cy__/chatbot-message-answer.test.js.map +1 -0
  82. package/dist/components/chatbot/messages/__tests-cy__/chatbot-message-typing.test.d.ts +2 -0
  83. package/dist/components/chatbot/messages/__tests-cy__/chatbot-message-typing.test.d.ts.map +1 -0
  84. package/dist/components/chatbot/messages/__tests-cy__/chatbot-message-typing.test.js +57 -0
  85. package/dist/components/chatbot/messages/__tests-cy__/chatbot-message-typing.test.js.map +1 -0
  86. package/dist/components/chatbot/messages/chatbot-links.d.ts +15 -0
  87. package/dist/components/chatbot/messages/chatbot-links.d.ts.map +1 -0
  88. package/dist/components/chatbot/messages/chatbot-links.js +21 -0
  89. package/dist/components/chatbot/messages/chatbot-links.js.map +1 -0
  90. package/dist/components/chatbot/messages/chatbot-links.module.less +22 -0
  91. package/dist/components/chatbot/messages/chatbot-message-agent-footer.d.ts +8 -0
  92. package/dist/components/chatbot/messages/chatbot-message-agent-footer.d.ts.map +1 -0
  93. package/dist/components/chatbot/messages/chatbot-message-agent-footer.js +13 -0
  94. package/dist/components/chatbot/messages/chatbot-message-agent-footer.js.map +1 -0
  95. package/dist/components/chatbot/messages/chatbot-message-answer-readonly.d.ts +6 -0
  96. package/dist/components/chatbot/messages/chatbot-message-answer-readonly.d.ts.map +1 -0
  97. package/dist/components/chatbot/messages/chatbot-message-answer-readonly.js +17 -0
  98. package/dist/components/chatbot/messages/chatbot-message-answer-readonly.js.map +1 -0
  99. package/dist/components/chatbot/messages/chatbot-message-answer.d.ts +6 -0
  100. package/dist/components/chatbot/messages/chatbot-message-answer.d.ts.map +1 -0
  101. package/dist/components/chatbot/messages/chatbot-message-answer.js +29 -0
  102. package/dist/components/chatbot/messages/chatbot-message-answer.js.map +1 -0
  103. package/dist/components/chatbot/messages/chatbot-message-answer.module.less +3 -0
  104. package/dist/components/chatbot/messages/chatbot-message-timeout.d.ts +6 -0
  105. package/dist/components/chatbot/messages/chatbot-message-timeout.d.ts.map +1 -0
  106. package/dist/components/chatbot/messages/chatbot-message-timeout.js +17 -0
  107. package/dist/components/chatbot/messages/chatbot-message-timeout.js.map +1 -0
  108. package/dist/components/chatbot/messages/chatbot-message-typing.d.ts +8 -0
  109. package/dist/components/chatbot/messages/chatbot-message-typing.d.ts.map +1 -0
  110. package/dist/components/chatbot/messages/chatbot-message-typing.js +27 -0
  111. package/dist/components/chatbot/messages/chatbot-message-typing.js.map +1 -0
  112. package/dist/components/chatbot/messages/chatbot-message-welcome.d.ts +3 -0
  113. package/dist/components/chatbot/messages/chatbot-message-welcome.d.ts.map +1 -0
  114. package/dist/components/chatbot/messages/chatbot-message-welcome.js +4 -0
  115. package/dist/components/chatbot/messages/chatbot-message-welcome.js.map +1 -0
  116. package/dist/components/chatbot/templates/chatbot-message-template-agent-readonly.d.ts +6 -0
  117. package/dist/components/chatbot/templates/chatbot-message-template-agent-readonly.d.ts.map +1 -0
  118. package/dist/components/chatbot/templates/chatbot-message-template-agent-readonly.js +12 -0
  119. package/dist/components/chatbot/templates/chatbot-message-template-agent-readonly.js.map +1 -0
  120. package/dist/components/chatbot/templates/chatbot-message-template-agent.d.ts +6 -0
  121. package/dist/components/chatbot/templates/chatbot-message-template-agent.d.ts.map +1 -0
  122. package/dist/components/chatbot/templates/chatbot-message-template-agent.js +10 -0
  123. package/dist/components/chatbot/templates/chatbot-message-template-agent.js.map +1 -0
  124. package/dist/components/chatbot/templates/chatbot-message-template-user-readonly.d.ts +6 -0
  125. package/dist/components/chatbot/templates/chatbot-message-template-user-readonly.d.ts.map +1 -0
  126. package/dist/components/chatbot/templates/chatbot-message-template-user-readonly.js +7 -0
  127. package/dist/components/chatbot/templates/chatbot-message-template-user-readonly.js.map +1 -0
  128. package/dist/index.d.ts +6 -0
  129. package/dist/index.d.ts.map +1 -0
  130. package/dist/index.js +6 -0
  131. package/dist/index.js.map +1 -0
  132. package/dist/stores/__tests__/message-feedback-guardrail.store.test.d.ts +2 -0
  133. package/dist/stores/__tests__/message-feedback-guardrail.store.test.d.ts.map +1 -0
  134. package/dist/stores/__tests__/message-feedback-guardrail.store.test.js +49 -0
  135. package/dist/stores/__tests__/message-feedback-guardrail.store.test.js.map +1 -0
  136. package/dist/stores/__tests__/message-feedback.store.test.d.ts +2 -0
  137. package/dist/stores/__tests__/message-feedback.store.test.d.ts.map +1 -0
  138. package/dist/stores/__tests__/message-feedback.store.test.js +114 -0
  139. package/dist/stores/__tests__/message-feedback.store.test.js.map +1 -0
  140. package/dist/stores/__tests__/session-feedback.store.test.d.ts +2 -0
  141. package/dist/stores/__tests__/session-feedback.store.test.d.ts.map +1 -0
  142. package/dist/stores/__tests__/session-feedback.store.test.js +39 -0
  143. package/dist/stores/__tests__/session-feedback.store.test.js.map +1 -0
  144. package/dist/stores/message-feedback-base.store.d.ts +8 -0
  145. package/dist/stores/message-feedback-base.store.d.ts.map +1 -0
  146. package/dist/stores/message-feedback-base.store.js +2 -0
  147. package/dist/stores/message-feedback-base.store.js.map +1 -0
  148. package/dist/stores/message-feedback-guardrail.store.d.ts +16 -0
  149. package/dist/stores/message-feedback-guardrail.store.d.ts.map +1 -0
  150. package/dist/stores/message-feedback-guardrail.store.js +85 -0
  151. package/dist/stores/message-feedback-guardrail.store.js.map +1 -0
  152. package/dist/stores/message-feedback.store.d.ts +23 -0
  153. package/dist/stores/message-feedback.store.d.ts.map +1 -0
  154. package/dist/stores/message-feedback.store.js +145 -0
  155. package/dist/stores/message-feedback.store.js.map +1 -0
  156. package/dist/stores/session-feedback.store.d.ts +15 -0
  157. package/dist/stores/session-feedback.store.d.ts.map +1 -0
  158. package/dist/stores/session-feedback.store.js +75 -0
  159. package/dist/stores/session-feedback.store.js.map +1 -0
  160. package/dist/utils/__tests__/axios-utils.test.d.ts +2 -0
  161. package/dist/utils/__tests__/axios-utils.test.d.ts.map +1 -0
  162. package/dist/utils/__tests__/axios-utils.test.js +33 -0
  163. package/dist/utils/__tests__/axios-utils.test.js.map +1 -0
  164. package/dist/utils/axios-utils.d.ts +5 -0
  165. package/dist/utils/axios-utils.d.ts.map +1 -0
  166. package/dist/utils/axios-utils.js +23 -0
  167. package/dist/utils/axios-utils.js.map +1 -0
  168. package/dist/utils/test-utils.d.ts +5 -0
  169. package/dist/utils/test-utils.d.ts.map +1 -0
  170. package/dist/utils/test-utils.js +17 -0
  171. package/dist/utils/test-utils.js.map +1 -0
  172. package/package.json +59 -0
  173. package/src/components/chatbot/__tests-cy__/chatbot-help-center.test.tsx +210 -0
  174. package/src/components/chatbot/__tests-cy__/chatbot-live.test.tsx +120 -0
  175. package/src/components/chatbot/__tests-cy__/chatbot-titan-chatbot.test.tsx +206 -0
  176. package/src/components/chatbot/__tests-cy__/chatbot.test.tsx +181 -0
  177. package/src/components/chatbot/chatbot-to-chat-provider-adapter.tsx +36 -0
  178. package/src/components/chatbot/chatbot.tsx +95 -0
  179. package/src/components/chatbot/dialog/chatbot-restart-dialog.tsx +36 -0
  180. package/src/components/chatbot/dialog/chatbot-restart-link.tsx +26 -0
  181. package/src/components/chatbot/feedback/__tests-cy__/chatbot-message-feedback-form-guardrail.test.tsx +89 -0
  182. package/src/components/chatbot/feedback/__tests-cy__/chatbot-message-feedback-form.test.tsx +131 -0
  183. package/src/components/chatbot/feedback/chatbot-message-feedback-form-guardrail.tsx +35 -0
  184. package/src/components/chatbot/feedback/chatbot-message-feedback-form.module.less +15 -0
  185. package/src/components/chatbot/feedback/chatbot-message-feedback-form.module.less.d.ts +4 -0
  186. package/src/components/chatbot/feedback/chatbot-message-feedback-form.tsx +65 -0
  187. package/src/components/chatbot/feedback/chatbot-message-feedback-popover.module.less +10 -0
  188. package/src/components/chatbot/feedback/chatbot-message-feedback-popover.module.less.d.ts +4 -0
  189. package/src/components/chatbot/feedback/chatbot-message-feedback-popover.tsx +200 -0
  190. package/src/components/chatbot/feedback/chatbot-message-feedback.tsx +27 -0
  191. package/src/components/chatbot/feedback/chatbot-session-feedback-link.tsx +29 -0
  192. package/src/components/chatbot/feedback/chatbot-session-feedback-modal.tsx +96 -0
  193. package/src/components/chatbot/filters/chatbot-filter.module.css +19 -0
  194. package/src/components/chatbot/filters/chatbot-filter.module.css.d.ts +5 -0
  195. package/src/components/chatbot/filters/chatbot-filter.tsx +160 -0
  196. package/src/components/chatbot/filters/chatbot-filters.tsx +17 -0
  197. package/src/components/chatbot/messages/__tests-cy__/chatbot-message-answer.test.tsx +110 -0
  198. package/src/components/chatbot/messages/__tests-cy__/chatbot-message-typing.test.tsx +71 -0
  199. package/src/components/chatbot/messages/chatbot-links.module.less +22 -0
  200. package/src/components/chatbot/messages/chatbot-links.module.less.d.ts +4 -0
  201. package/src/components/chatbot/messages/chatbot-links.tsx +76 -0
  202. package/src/components/chatbot/messages/chatbot-message-agent-footer.tsx +35 -0
  203. package/src/components/chatbot/messages/chatbot-message-answer-readonly.tsx +39 -0
  204. package/src/components/chatbot/messages/chatbot-message-answer.module.less +3 -0
  205. package/src/components/chatbot/messages/chatbot-message-answer.module.less.d.ts +3 -0
  206. package/src/components/chatbot/messages/chatbot-message-answer.tsx +55 -0
  207. package/src/components/chatbot/messages/chatbot-message-timeout.tsx +20 -0
  208. package/src/components/chatbot/messages/chatbot-message-typing.tsx +39 -0
  209. package/src/components/chatbot/messages/chatbot-message-welcome.tsx +16 -0
  210. package/src/components/chatbot/templates/chatbot-message-template-agent-readonly.tsx +25 -0
  211. package/src/components/chatbot/templates/chatbot-message-template-agent.tsx +25 -0
  212. package/src/components/chatbot/templates/chatbot-message-template-user-readonly.tsx +16 -0
  213. package/src/cypress.d.ts +10 -0
  214. package/src/index.ts +5 -0
  215. package/src/stores/__tests__/message-feedback-guardrail.store.test.ts +61 -0
  216. package/src/stores/__tests__/message-feedback.store.test.ts +121 -0
  217. package/src/stores/__tests__/session-feedback.store.test.ts +47 -0
  218. package/src/stores/message-feedback-base.store.ts +8 -0
  219. package/src/stores/message-feedback-guardrail.store.ts +60 -0
  220. package/src/stores/message-feedback.store.ts +113 -0
  221. package/src/stores/session-feedback.store.ts +44 -0
  222. package/src/utils/__tests__/axios-utils.test.ts +40 -0
  223. package/src/utils/axios-utils.ts +25 -0
  224. package/src/utils/test-utils.ts +22 -0
  225. package/tsconfig.json +25 -0
  226. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,121 @@
1
+ import { expect } from '@jest/globals';
2
+ import { Container } from '@servicetitan/react-ioc';
3
+ import { CHATBOT_UI_STORE_TOKEN, IChatbotUiStore, Models } from '@servicetitan/titan-chatbot-api';
4
+ import { initTestContainer } from '../../utils/test-utils';
5
+ import { MessageFeedbackStore } from '../message-feedback.store';
6
+
7
+ const initContainer = initTestContainer(MessageFeedbackStore, container => {
8
+ class ChatbotUiStoreMock {
9
+ customizations = {
10
+ feedback: {
11
+ isCommentAlwaysRequired: false,
12
+ },
13
+ };
14
+ setCustomizationContext(context: any) {
15
+ this.customizations = context;
16
+ }
17
+ }
18
+ container
19
+ .bind<IChatbotUiStore>(CHATBOT_UI_STORE_TOKEN)
20
+ .toConstantValue(new ChatbotUiStoreMock() as IChatbotUiStore);
21
+ });
22
+
23
+ describe('[MessageFeedbackStore]', () => {
24
+ let container: Container;
25
+ let store: MessageFeedbackStore;
26
+
27
+ beforeEach(() => {
28
+ container = initContainer();
29
+ store = container.get(MessageFeedbackStore);
30
+ });
31
+
32
+ test('should have proper form state', async () => {
33
+ expect(store.isValid).toBe(false);
34
+ expect(store.formState.$.notFull.value).toBe(false);
35
+ expect(store.formState.$.unclear.value).toBe(false);
36
+ expect(store.formState.$.unrelated.value).toBe(false);
37
+ expect(store.formState.$.incorrect.value).toBe(false);
38
+ expect(store.formState.$.other.value).toBe(false);
39
+ expect(store.formState.$.otherComment.value).toBe('');
40
+ expect(store.export()).toEqual({
41
+ description: undefined,
42
+ linkUrl: undefined,
43
+ options: [],
44
+ rating: Models.FeedbackRatings.ThumbsDown,
45
+ });
46
+
47
+ await store.formState.validate();
48
+ expect(store.formState.error).toBe('At least one item has to be selected.');
49
+ });
50
+
51
+ test('should export form state', async () => {
52
+ store.formState.$.unclear.onChange(true);
53
+ store.formState.$.notFull.onChange(true);
54
+ store.formState.$.unrelated.onChange(true);
55
+ store.formState.$.incorrect.onChange(true);
56
+ store.formState.$.other.onChange(true);
57
+ store.formState.$.otherComment.onChange('Some comment');
58
+ await store.formState.validate();
59
+
60
+ expect(store.export()).toEqual({
61
+ description: 'Some comment',
62
+ linkUrl: undefined,
63
+ options: expect.arrayContaining([
64
+ Models.FeedbackOptions.Unclear,
65
+ Models.FeedbackOptions.Incomplete,
66
+ Models.FeedbackOptions.Unrelated,
67
+ Models.FeedbackOptions.Incorrect,
68
+ Models.FeedbackOptions.Other,
69
+ ]),
70
+ rating: Models.FeedbackRatings.ThumbsDown,
71
+ });
72
+ });
73
+
74
+ test('should validate comment for non-internal users', async () => {
75
+ // Other is selected and comment is empty, validation should fail
76
+ store.formState.$.other.onChange(true);
77
+ store.formState.$.otherComment.onChange('');
78
+ await store.formState.validate();
79
+ expect(store.formState.$.otherComment.error).toBe('Enter details.');
80
+
81
+ // If "Other" is not selected, comment should not be required
82
+ store.formState.$.other.onChange(false);
83
+ store.formState.$.otherComment.onChange('');
84
+ await store.formState.validate();
85
+ expect(store.formState.$.otherComment.error).toBeFalsy();
86
+
87
+ // If "Other" is selected and comment is provided, validation should pass
88
+ store.formState.$.other.onChange(true);
89
+ store.formState.$.otherComment.onChange('Some comment');
90
+ await store.formState.validate();
91
+ expect(store.formState.$.otherComment.error).toBeFalsy();
92
+ });
93
+
94
+ test('should validate comment for internal users', async () => {
95
+ const chatbotUiStore = container.get<IChatbotUiStore>(CHATBOT_UI_STORE_TOKEN);
96
+ chatbotUiStore.setCustomizationContext({
97
+ feedback: {
98
+ isCommentAlwaysRequired: true,
99
+ },
100
+ });
101
+ expect(store.isCommentAlwaysRequired).toBe(true);
102
+
103
+ // If "Other" is not selected, comment should be required
104
+ store.formState.$.other.onChange(false);
105
+ store.formState.$.otherComment.onChange('');
106
+ await store.formState.validate();
107
+ expect(store.formState.$.otherComment.error).toBe('Enter details.');
108
+
109
+ // If "Other" is selected, comment should be required
110
+ store.formState.$.other.onChange(true);
111
+ store.formState.$.otherComment.onChange('');
112
+ await store.formState.validate();
113
+ expect(store.formState.$.otherComment.error).toBe('Enter details.');
114
+
115
+ // If "Other" is selected and comment is provided, validation should pass
116
+ store.formState.$.other.onChange(true);
117
+ store.formState.$.otherComment.onChange('Some comment');
118
+ await store.formState.validate();
119
+ expect(store.formState.$.otherComment.error).toBeFalsy();
120
+ });
121
+ });
@@ -0,0 +1,47 @@
1
+ import { expect } from '@jest/globals';
2
+ import { Container } from '@servicetitan/react-ioc';
3
+ import { initTestContainer } from '../../utils/test-utils';
4
+ import { SessionFeedbackStore } from '../session-feedback.store';
5
+
6
+ const initContainer = initTestContainer(SessionFeedbackStore, () => {});
7
+
8
+ describe('[SessionFeedbackStore]', () => {
9
+ let container: Container;
10
+ let store: SessionFeedbackStore;
11
+
12
+ beforeEach(() => {
13
+ container = initContainer();
14
+ store = container.get(SessionFeedbackStore);
15
+ });
16
+
17
+ test('should have proper form state', () => {
18
+ expect(store.isValid).toBe(false);
19
+ expect(store.formState.$.thumbs.value).toBe(0);
20
+ expect(store.formState.$.comment.value).toBe('');
21
+ expect(store.feedback).toEqual({
22
+ rating: 0,
23
+ description: undefined,
24
+ });
25
+ });
26
+
27
+ test('should export feedback', async () => {
28
+ store.formState.$.thumbs.onChange(1);
29
+ store.formState.$.comment.onChange('Great service!');
30
+ await store.formState.validate();
31
+
32
+ expect(store.feedback).toEqual({
33
+ rating: 1,
34
+ description: 'Great service!',
35
+ });
36
+ });
37
+
38
+ test('should validate thumbs up and down', () => {
39
+ store.formState.$.thumbs.onChange(1);
40
+ expect(store.isThumbsUp).toBe(true);
41
+ expect(store.isThumbsDown).toBe(false);
42
+
43
+ store.formState.$.thumbs.onChange(-1);
44
+ expect(store.isThumbsUp).toBe(false);
45
+ expect(store.isThumbsDown).toBe(true);
46
+ });
47
+ });
@@ -0,0 +1,8 @@
1
+ import { Models } from '@servicetitan/titan-chatbot-api';
2
+ import { FormState } from 'formstate';
3
+
4
+ export interface IMessageFeedbackBaseStore<T extends FormState<{}> = FormState<{}>> {
5
+ isValid: boolean;
6
+ formState: T;
7
+ export(): Models.IFeedback;
8
+ }
@@ -0,0 +1,60 @@
1
+ import { InputFieldState, TextAreaFieldState, formStateToJS } from '@servicetitan/form';
2
+ import { injectable } from '@servicetitan/react-ioc';
3
+ import { Models } from '@servicetitan/titan-chatbot-api';
4
+ import { FormState } from 'formstate';
5
+ import { action, computed, makeObservable, observable } from 'mobx';
6
+ import { IMessageFeedbackBaseStore } from './message-feedback-base.store';
7
+
8
+ export type FeedbackFormState = FormState<{
9
+ linkUrl: InputFieldState<string>;
10
+ comment: TextAreaFieldState<string>;
11
+ }>;
12
+
13
+ @injectable()
14
+ export class MessageFeedbackGuardrailStore implements IMessageFeedbackBaseStore<FeedbackFormState> {
15
+ @observable formState!: FeedbackFormState;
16
+
17
+ @computed get isValid() {
18
+ return Boolean(this.formState.$.linkUrl.value) || Boolean(this.formState.$.comment.value);
19
+ }
20
+
21
+ constructor() {
22
+ this.createFormState();
23
+ makeObservable(this);
24
+ }
25
+
26
+ export(): Models.Feedback {
27
+ const state = formStateToJS(this.formState);
28
+ let feedback: Models.IFeedback = {
29
+ rating: Models.FeedbackRatings.GuardrailFeedback,
30
+ };
31
+ if (state.linkUrl) {
32
+ feedback = {
33
+ ...feedback,
34
+ linkUrl: state.linkUrl,
35
+ };
36
+ }
37
+ if (state.comment) {
38
+ feedback = {
39
+ ...feedback,
40
+ description: state.comment,
41
+ };
42
+ }
43
+ return new Models.Feedback(feedback);
44
+ }
45
+
46
+ @action
47
+ private createFormState = () => {
48
+ const formState = new FormState({
49
+ linkUrl: new InputFieldState(''),
50
+ comment: new TextAreaFieldState(''),
51
+ });
52
+ formState.validators($ => {
53
+ const nothingSelected = !$.linkUrl.value && !$.comment.value;
54
+ const result = nothingSelected ? 'At least one input must be provided.' : false;
55
+ return result;
56
+ });
57
+ formState.disableAutoValidation();
58
+ this.formState = formState;
59
+ };
60
+ }
@@ -0,0 +1,113 @@
1
+ import {
2
+ CheckboxFieldState,
3
+ FormValidators,
4
+ TextAreaFieldState,
5
+ formStateToJS,
6
+ } from '@servicetitan/form';
7
+ import { inject, injectable } from '@servicetitan/react-ioc';
8
+ import { CHATBOT_UI_STORE_TOKEN, IChatbotUiStore, Models } from '@servicetitan/titan-chatbot-api';
9
+ import { FormState } from 'formstate';
10
+ import { action, computed, makeObservable, observable } from 'mobx';
11
+ import { IMessageFeedbackBaseStore } from './message-feedback-base.store';
12
+
13
+ export type MessageFeedbackFormState = FormState<{
14
+ unrelated: CheckboxFieldState;
15
+ unclear: CheckboxFieldState;
16
+ notFull: CheckboxFieldState;
17
+ incorrect: CheckboxFieldState;
18
+ other: CheckboxFieldState;
19
+ otherComment: TextAreaFieldState<string>;
20
+ }>;
21
+
22
+ @injectable()
23
+ export class MessageFeedbackStore implements IMessageFeedbackBaseStore<MessageFeedbackFormState> {
24
+ @observable formState!: MessageFeedbackFormState;
25
+
26
+ @computed get isCommentAlwaysRequired() {
27
+ return this.chatbotUiStore.customizations.feedback?.isCommentAlwaysRequired ?? false;
28
+ }
29
+
30
+ @computed get isTextAreaVisible() {
31
+ return this.isCommentAlwaysRequired || this.formState.$.other.value;
32
+ }
33
+
34
+ @computed get isValid() {
35
+ return (
36
+ this.formState.$.unrelated.value ||
37
+ this.formState.$.unclear.value ||
38
+ this.formState.$.notFull.value ||
39
+ this.formState.$.incorrect.value ||
40
+ this.formState.$.other.value
41
+ );
42
+ }
43
+
44
+ constructor(@inject(CHATBOT_UI_STORE_TOKEN) private readonly chatbotUiStore: IChatbotUiStore) {
45
+ this.formState = this.createFormState();
46
+ makeObservable(this);
47
+ }
48
+
49
+ export(): Models.IFeedback {
50
+ const state = formStateToJS(this.formState);
51
+ const options: Models.FeedbackOptions[] = [];
52
+ if (state.unrelated) {
53
+ options.push(Models.FeedbackOptions.Unrelated);
54
+ }
55
+ if (state.unclear) {
56
+ options.push(Models.FeedbackOptions.Unclear);
57
+ }
58
+ if (state.notFull) {
59
+ options.push(Models.FeedbackOptions.Incomplete);
60
+ }
61
+ if (state.incorrect) {
62
+ options.push(Models.FeedbackOptions.Incorrect);
63
+ }
64
+ if (state.other) {
65
+ options.push(Models.FeedbackOptions.Other);
66
+ }
67
+ let feedback: Models.IFeedback = {
68
+ rating: Models.FeedbackRatings.ThumbsDown,
69
+ options,
70
+ };
71
+ if (state.otherComment) {
72
+ feedback = {
73
+ ...feedback,
74
+ description: state.otherComment,
75
+ };
76
+ }
77
+ return new Models.Feedback(feedback);
78
+ }
79
+
80
+ @action
81
+ private createFormState = (): MessageFeedbackFormState => {
82
+ const formState = new FormState({
83
+ unrelated: new CheckboxFieldState(false),
84
+ unclear: new CheckboxFieldState(false),
85
+ notFull: new CheckboxFieldState(false),
86
+ incorrect: new CheckboxFieldState(false),
87
+ other: new CheckboxFieldState(false),
88
+ otherComment: new TextAreaFieldState('')
89
+ .validators($ => {
90
+ if (this.isCommentAlwaysRequired) {
91
+ // Internal users can't submit empty feedback
92
+ return FormValidators.requiredWithCustomMessage('Enter details.')($);
93
+ }
94
+ // External users can submit empty feedback if "Other" is not selected
95
+ if (!this.formState.$.other.value) {
96
+ return false;
97
+ }
98
+ return FormValidators.requiredWithCustomMessage('Enter details.')($);
99
+ })
100
+ .disableAutoValidation(),
101
+ }).validators($ => {
102
+ const nothingSelected =
103
+ !$.unrelated.value &&
104
+ !$.unclear.value &&
105
+ !$.notFull.value &&
106
+ !$.incorrect.value &&
107
+ !$.other.value;
108
+ return nothingSelected ? 'At least one item has to be selected.' : false;
109
+ });
110
+ formState.disableAutoValidation();
111
+ return formState;
112
+ };
113
+ }
@@ -0,0 +1,44 @@
1
+ import { InputFieldState, TextAreaFieldState } from '@servicetitan/form';
2
+ import { injectable } from '@servicetitan/react-ioc';
3
+ import { Models } from '@servicetitan/titan-chatbot-api';
4
+ import { FormState } from 'formstate';
5
+ import { computed, makeObservable, observable } from 'mobx';
6
+
7
+ @injectable()
8
+ export class SessionFeedbackStore {
9
+ @observable
10
+ formState = new FormState({
11
+ thumbs: new InputFieldState<number>(0),
12
+ comment: new TextAreaFieldState<string>(''),
13
+ });
14
+
15
+ @computed
16
+ get isValid() {
17
+ return this.formState.$.thumbs.value !== 0;
18
+ }
19
+
20
+ @computed
21
+ get feedback(): Models.Feedback {
22
+ return new Models.Feedback({
23
+ rating:
24
+ this.formState.$.thumbs.value > 0
25
+ ? Models.FeedbackRatings.ThumbsUp
26
+ : Models.FeedbackRatings.ThumbsDown,
27
+ description: this.formState.$.comment.value || undefined,
28
+ });
29
+ }
30
+
31
+ @computed
32
+ get isThumbsUp() {
33
+ return this.formState.$.thumbs.value > 0;
34
+ }
35
+
36
+ @computed
37
+ get isThumbsDown() {
38
+ return this.formState.$.thumbs.value < 0;
39
+ }
40
+
41
+ constructor() {
42
+ makeObservable(this);
43
+ }
44
+ }
@@ -0,0 +1,40 @@
1
+ import { expect } from '@jest/globals';
2
+ import { withTimeout } from '../axios-utils';
3
+
4
+ describe('axios-utils', () => {
5
+ beforeEach(() => {
6
+ jest.useFakeTimers();
7
+ jest.setSystemTime(new Date('2000-01-01T00:00:00.000Z'));
8
+ });
9
+
10
+ afterEach(() => {
11
+ jest.clearAllMocks();
12
+ jest.useRealTimers();
13
+ });
14
+
15
+ test('should withTimeout (timeout first)', async () => {
16
+ const abortController = new AbortController();
17
+ const spyAbort = jest.spyOn(abortController, 'abort');
18
+ const action = new Promise(resolve => setTimeout(() => resolve('done'), 100));
19
+ const timeoutMs = 50;
20
+
21
+ const p = expect(withTimeout(action, timeoutMs, abortController)).rejects.toThrow(
22
+ 'The request is timed out'
23
+ );
24
+ await jest.advanceTimersByTimeAsync(50);
25
+ await p;
26
+ expect(spyAbort).toHaveBeenCalledWith('The request is timed out');
27
+ });
28
+
29
+ test('should withTimeout (resolves first)', async () => {
30
+ const abortController = new AbortController();
31
+ const spyAbort = jest.spyOn(abortController, 'abort');
32
+ const action = new Promise(resolve => setTimeout(() => resolve('done'), 100));
33
+ const timeoutMs = 150;
34
+
35
+ const p = expect(withTimeout(action, timeoutMs, abortController)).resolves.toEqual('done');
36
+ await jest.advanceTimersByTimeAsync(100);
37
+ await p;
38
+ expect(spyAbort).not.toHaveBeenCalled();
39
+ });
40
+ });
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Aborts the action if it takes longer than the specified timeout.
3
+ */
4
+ export async function withTimeout<T>(
5
+ action: Promise<T>,
6
+ timeoutMs: number,
7
+ abortController: AbortController
8
+ ): Promise<T> {
9
+ let timeoutId: ReturnType<typeof setTimeout> | undefined = undefined;
10
+ const timeoutPromise = new Promise<T>((_, reject) => {
11
+ timeoutId = setTimeout(() => {
12
+ abortController.abort('The request is timed out');
13
+ reject(new Error('The request is timed out'));
14
+ }, timeoutMs);
15
+ });
16
+ const promise = Promise.race([action, timeoutPromise]);
17
+ try {
18
+ return await promise;
19
+ } finally {
20
+ if (timeoutId) {
21
+ clearTimeout(timeoutId);
22
+ timeoutId = undefined;
23
+ }
24
+ }
25
+ }
@@ -0,0 +1,22 @@
1
+ import { Container } from '@servicetitan/react-ioc';
2
+
3
+ type Newable<T> = new (...args: never[]) => T;
4
+
5
+ export const initTestContainer = (
6
+ serviceIdentifier: Newable<unknown> | Newable<unknown>[],
7
+ initDependenciesFn: (container: Container) => void
8
+ ) => {
9
+ const rootContainer = new Container();
10
+ if (Array.isArray(serviceIdentifier)) {
11
+ serviceIdentifier.forEach(identifier => rootContainer.bind(identifier).toSelf());
12
+ } else {
13
+ rootContainer.bind(serviceIdentifier).toSelf();
14
+ }
15
+
16
+ return () => {
17
+ const container = new Container();
18
+ container.parent = rootContainer;
19
+ initDependenciesFn(container);
20
+ return container;
21
+ };
22
+ };
package/tsconfig.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "extends": "@servicetitan/startup/tsconfig/base",
3
+ "compilerOptions": {
4
+ "composite": true,
5
+ "outDir": "dist",
6
+ "rootDir": "src",
7
+ "jsx": "react-jsx",
8
+ "moduleResolution": "bundler"
9
+ },
10
+ "include": ["src/**/*"],
11
+ "exclude": [
12
+ "node_modules"
13
+ ],
14
+ "references": [
15
+ {
16
+ "path": "../titan-chat-ui"
17
+ },
18
+ {
19
+ "path": "../titan-chatbot-ui-cypress"
20
+ },
21
+ {
22
+ "path": "../titan-chat-ui-common"
23
+ }
24
+ ]
25
+ }