@stack-spot/ai-chat-widget 1.0.0 → 1.1.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 (317) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dist/StackspotAIWidget.d.ts +20 -0
  3. package/dist/StackspotAIWidget.d.ts.map +1 -1
  4. package/dist/StackspotAIWidget.js +7 -3
  5. package/dist/StackspotAIWidget.js.map +1 -1
  6. package/dist/chat-interceptors/CustomInputs.d.ts +4 -1
  7. package/dist/chat-interceptors/CustomInputs.d.ts.map +1 -1
  8. package/dist/chat-interceptors/CustomInputs.js +10 -1
  9. package/dist/chat-interceptors/CustomInputs.js.map +1 -1
  10. package/dist/chat-interceptors/quick-command-questions.d.ts +10 -0
  11. package/dist/chat-interceptors/quick-command-questions.d.ts.map +1 -1
  12. package/dist/chat-interceptors/quick-command-questions.js +12 -2
  13. package/dist/chat-interceptors/quick-command-questions.js.map +1 -1
  14. package/dist/chat-interceptors/quick-commands.d.ts +11 -0
  15. package/dist/chat-interceptors/quick-commands.d.ts.map +1 -1
  16. package/dist/chat-interceptors/quick-commands.js +42 -3
  17. package/dist/chat-interceptors/quick-commands.js.map +1 -1
  18. package/dist/chat-interceptors/send-message.d.ts +10 -0
  19. package/dist/chat-interceptors/send-message.d.ts.map +1 -1
  20. package/dist/chat-interceptors/send-message.js +18 -0
  21. package/dist/chat-interceptors/send-message.js.map +1 -1
  22. package/dist/components/Accordion.d.ts +10 -0
  23. package/dist/components/Accordion.d.ts.map +1 -1
  24. package/dist/components/Accordion.js +3 -0
  25. package/dist/components/Accordion.js.map +1 -1
  26. package/dist/components/AdaptiveTextArea.d.ts +12 -0
  27. package/dist/components/AdaptiveTextArea.d.ts.map +1 -1
  28. package/dist/components/AdaptiveTextArea.js +3 -0
  29. package/dist/components/AdaptiveTextArea.js.map +1 -1
  30. package/dist/components/AutoFocus.d.ts +17 -0
  31. package/dist/components/AutoFocus.d.ts.map +1 -1
  32. package/dist/components/AutoFocus.js +6 -5
  33. package/dist/components/AutoFocus.js.map +1 -1
  34. package/dist/components/Fading.d.ts +19 -2
  35. package/dist/components/Fading.d.ts.map +1 -1
  36. package/dist/components/Fading.js +6 -4
  37. package/dist/components/Fading.js.map +1 -1
  38. package/dist/components/FadingOverflow.d.ts +25 -0
  39. package/dist/components/FadingOverflow.d.ts.map +1 -1
  40. package/dist/components/FadingOverflow.js +11 -2
  41. package/dist/components/FadingOverflow.js.map +1 -1
  42. package/dist/components/FallbackBoundary/index.d.ts +6 -0
  43. package/dist/components/FallbackBoundary/index.d.ts.map +1 -1
  44. package/dist/components/FallbackBoundary/index.js.map +1 -1
  45. package/dist/components/HistoryList.d.ts +15 -0
  46. package/dist/components/HistoryList.d.ts.map +1 -1
  47. package/dist/components/HistoryList.js +3 -1
  48. package/dist/components/HistoryList.js.map +1 -1
  49. package/dist/components/IconInput.d.ts +3 -0
  50. package/dist/components/IconInput.d.ts.map +1 -1
  51. package/dist/components/IconInput.js +3 -0
  52. package/dist/components/IconInput.js.map +1 -1
  53. package/dist/components/OverlayMenu.d.ts +11 -0
  54. package/dist/components/OverlayMenu.d.ts.map +1 -1
  55. package/dist/components/OverlayMenu.js +5 -1
  56. package/dist/components/OverlayMenu.js.map +1 -1
  57. package/dist/components/ProgressBar.d.ts +22 -0
  58. package/dist/components/ProgressBar.d.ts.map +1 -1
  59. package/dist/components/ProgressBar.js +5 -0
  60. package/dist/components/ProgressBar.js.map +1 -1
  61. package/dist/components/QuickStartButton.d.ts.map +1 -1
  62. package/dist/components/QuickStartButton.js +3 -0
  63. package/dist/components/QuickStartButton.js.map +1 -1
  64. package/dist/components/RightPanelForm.d.ts +3 -0
  65. package/dist/components/RightPanelForm.d.ts.map +1 -1
  66. package/dist/components/RightPanelForm.js +3 -0
  67. package/dist/components/RightPanelForm.js.map +1 -1
  68. package/dist/components/RightPanelTabs.d.ts +3 -0
  69. package/dist/components/RightPanelTabs.d.ts.map +1 -1
  70. package/dist/components/RightPanelTabs.js +3 -0
  71. package/dist/components/RightPanelTabs.js.map +1 -1
  72. package/dist/components/TabManager.d.ts +6 -0
  73. package/dist/components/TabManager.d.ts.map +1 -1
  74. package/dist/components/TabManager.js +8 -0
  75. package/dist/components/TabManager.js.map +1 -1
  76. package/dist/components/Tooltip/Tooltip.d.ts +23 -0
  77. package/dist/components/Tooltip/Tooltip.d.ts.map +1 -1
  78. package/dist/components/Tooltip/Tooltip.js +4 -0
  79. package/dist/components/Tooltip/Tooltip.js.map +1 -1
  80. package/dist/components/Tooltip/TooltipAPI.d.ts +16 -0
  81. package/dist/components/Tooltip/TooltipAPI.d.ts.map +1 -1
  82. package/dist/components/Tooltip/TooltipAPI.js +17 -0
  83. package/dist/components/Tooltip/TooltipAPI.js.map +1 -1
  84. package/dist/components/Tooltip/types.d.ts +13 -0
  85. package/dist/components/Tooltip/types.d.ts.map +1 -1
  86. package/dist/components/form/DescribedCheckboxGroup.d.ts +4 -0
  87. package/dist/components/form/DescribedCheckboxGroup.d.ts.map +1 -1
  88. package/dist/components/form/DescribedCheckboxGroup.js +4 -0
  89. package/dist/components/form/DescribedCheckboxGroup.js.map +1 -1
  90. package/dist/components/form/DescribedRadioGroup.d.ts +4 -0
  91. package/dist/components/form/DescribedRadioGroup.d.ts.map +1 -1
  92. package/dist/components/form/DescribedRadioGroup.js +4 -0
  93. package/dist/components/form/DescribedRadioGroup.js.map +1 -1
  94. package/dist/components/form/types.d.ts +34 -0
  95. package/dist/components/form/types.d.ts.map +1 -1
  96. package/dist/context/AIWidgetProvider.d.ts +19 -0
  97. package/dist/context/AIWidgetProvider.d.ts.map +1 -1
  98. package/dist/context/AIWidgetProvider.js +19 -0
  99. package/dist/context/AIWidgetProvider.js.map +1 -1
  100. package/dist/context/hooks.d.ts +56 -0
  101. package/dist/context/hooks.d.ts.map +1 -1
  102. package/dist/context/hooks.js +56 -1
  103. package/dist/context/hooks.js.map +1 -1
  104. package/dist/features.d.ts +28 -0
  105. package/dist/features.d.ts.map +1 -1
  106. package/dist/features.js +1 -0
  107. package/dist/features.js.map +1 -1
  108. package/dist/right-panel/DefaultPanel.d.ts +3 -0
  109. package/dist/right-panel/DefaultPanel.d.ts.map +1 -1
  110. package/dist/right-panel/DefaultPanel.js +4 -3
  111. package/dist/right-panel/DefaultPanel.js.map +1 -1
  112. package/dist/right-panel/RightPanel.d.ts +3 -0
  113. package/dist/right-panel/RightPanel.d.ts.map +1 -1
  114. package/dist/right-panel/RightPanel.js +3 -0
  115. package/dist/right-panel/RightPanel.js.map +1 -1
  116. package/dist/right-panel/RightPanelProvider.d.ts +15 -0
  117. package/dist/right-panel/RightPanelProvider.d.ts.map +1 -1
  118. package/dist/right-panel/RightPanelProvider.js.map +1 -1
  119. package/dist/right-panel/hooks.d.ts +6 -0
  120. package/dist/right-panel/hooks.d.ts.map +1 -1
  121. package/dist/right-panel/hooks.js +6 -0
  122. package/dist/right-panel/hooks.js.map +1 -1
  123. package/dist/state/ChatEntry.d.ts +58 -2
  124. package/dist/state/ChatEntry.d.ts.map +1 -1
  125. package/dist/state/ChatEntry.js +20 -1
  126. package/dist/state/ChatEntry.js.map +1 -1
  127. package/dist/state/ChatState.d.ts +73 -8
  128. package/dist/state/ChatState.d.ts.map +1 -1
  129. package/dist/state/ChatState.js +24 -7
  130. package/dist/state/ChatState.js.map +1 -1
  131. package/dist/state/ChatTabsController.d.ts +31 -0
  132. package/dist/state/ChatTabsController.d.ts.map +1 -1
  133. package/dist/state/ChatTabsController.js +31 -0
  134. package/dist/state/ChatTabsController.js.map +1 -1
  135. package/dist/state/ObservableState.d.ts +14 -0
  136. package/dist/state/ObservableState.d.ts.map +1 -1
  137. package/dist/state/ObservableState.js +14 -0
  138. package/dist/state/ObservableState.js.map +1 -1
  139. package/dist/state/WidgetState.d.ts +5 -0
  140. package/dist/state/WidgetState.d.ts.map +1 -1
  141. package/dist/state/WidgetState.js +5 -0
  142. package/dist/state/WidgetState.js.map +1 -1
  143. package/dist/types.d.ts +10 -0
  144. package/dist/types.d.ts.map +1 -1
  145. package/dist/utils/chat.d.ts +13 -0
  146. package/dist/utils/chat.d.ts.map +1 -1
  147. package/dist/utils/chat.js +14 -0
  148. package/dist/utils/chat.js.map +1 -1
  149. package/dist/utils/date.d.ts +25 -0
  150. package/dist/utils/date.d.ts.map +1 -1
  151. package/dist/utils/date.js +25 -0
  152. package/dist/utils/date.js.map +1 -1
  153. package/dist/utils/download.d.ts +5 -0
  154. package/dist/utils/download.d.ts.map +1 -1
  155. package/dist/utils/download.js +5 -0
  156. package/dist/utils/download.js.map +1 -1
  157. package/dist/utils/knowledge-source.d.ts +10 -0
  158. package/dist/utils/knowledge-source.d.ts.map +1 -1
  159. package/dist/utils/knowledge-source.js +16 -0
  160. package/dist/utils/knowledge-source.js.map +1 -1
  161. package/dist/utils/string.d.ts +5 -0
  162. package/dist/utils/string.d.ts.map +1 -1
  163. package/dist/utils/string.js +5 -1
  164. package/dist/utils/string.js.map +1 -1
  165. package/dist/views/Agents.js +3 -0
  166. package/dist/views/Agents.js.map +1 -1
  167. package/dist/views/Chat/AgentInfo.d.ts +3 -0
  168. package/dist/views/Chat/AgentInfo.d.ts.map +1 -1
  169. package/dist/views/Chat/AgentInfo.js +3 -0
  170. package/dist/views/Chat/AgentInfo.js.map +1 -1
  171. package/dist/views/Chat/ChatMessage.d.ts +17 -2
  172. package/dist/views/Chat/ChatMessage.d.ts.map +1 -1
  173. package/dist/views/Chat/ChatMessage.js +6 -32
  174. package/dist/views/Chat/ChatMessage.js.map +1 -1
  175. package/dist/views/Chat/ChatMessages.d.ts +3 -0
  176. package/dist/views/Chat/ChatMessages.d.ts.map +1 -1
  177. package/dist/views/Chat/ChatMessages.js +3 -0
  178. package/dist/views/Chat/ChatMessages.js.map +1 -1
  179. package/dist/views/Chat/chat-scroll.d.ts +1 -1
  180. package/dist/views/Chat/chat-scroll.js +1 -1
  181. package/dist/views/Chat/events.d.ts +22 -0
  182. package/dist/views/Chat/events.d.ts.map +1 -0
  183. package/dist/views/Chat/events.js +66 -0
  184. package/dist/views/Chat/events.js.map +1 -0
  185. package/dist/views/Chat/index.d.ts +6 -0
  186. package/dist/views/Chat/index.d.ts.map +1 -1
  187. package/dist/views/Chat/index.js +3 -0
  188. package/dist/views/Chat/index.js.map +1 -1
  189. package/dist/views/ChatHistory/ChatHistoryPanel.d.ts +3 -0
  190. package/dist/views/ChatHistory/ChatHistoryPanel.d.ts.map +1 -1
  191. package/dist/views/ChatHistory/ChatHistoryPanel.js +3 -0
  192. package/dist/views/ChatHistory/ChatHistoryPanel.js.map +1 -1
  193. package/dist/views/ChatHistory/HistoryItem.d.ts +3 -0
  194. package/dist/views/ChatHistory/HistoryItem.d.ts.map +1 -1
  195. package/dist/views/ChatHistory/HistoryItem.js +3 -0
  196. package/dist/views/ChatHistory/HistoryItem.js.map +1 -1
  197. package/dist/views/ChatHistory/index.d.ts +10 -2
  198. package/dist/views/ChatHistory/index.d.ts.map +1 -1
  199. package/dist/views/ChatHistory/index.js +3 -0
  200. package/dist/views/ChatHistory/index.js.map +1 -1
  201. package/dist/views/ChatHistory/utils.d.ts +14 -0
  202. package/dist/views/ChatHistory/utils.d.ts.map +1 -1
  203. package/dist/views/ChatHistory/utils.js +14 -0
  204. package/dist/views/ChatHistory/utils.js.map +1 -1
  205. package/dist/views/ChatTabSelection.d.ts +3 -0
  206. package/dist/views/ChatTabSelection.d.ts.map +1 -1
  207. package/dist/views/ChatTabSelection.js +3 -0
  208. package/dist/views/ChatTabSelection.js.map +1 -1
  209. package/dist/views/Editor.d.ts +3 -0
  210. package/dist/views/Editor.d.ts.map +1 -1
  211. package/dist/views/Editor.js +4 -0
  212. package/dist/views/Editor.js.map +1 -1
  213. package/dist/views/Home.d.ts +8 -0
  214. package/dist/views/Home.d.ts.map +1 -1
  215. package/dist/views/Home.js +5 -0
  216. package/dist/views/Home.js.map +1 -1
  217. package/dist/views/KSDocument.d.ts +3 -0
  218. package/dist/views/KSDocument.d.ts.map +1 -1
  219. package/dist/views/KSDocument.js +3 -0
  220. package/dist/views/KSDocument.js.map +1 -1
  221. package/dist/views/KnowledgeSources.js +3 -0
  222. package/dist/views/KnowledgeSources.js.map +1 -1
  223. package/dist/views/MessageInput/ButtonGroup.d.ts +22 -0
  224. package/dist/views/MessageInput/ButtonGroup.d.ts.map +1 -1
  225. package/dist/views/MessageInput/ButtonGroup.js +4 -0
  226. package/dist/views/MessageInput/ButtonGroup.js.map +1 -1
  227. package/dist/views/MessageInput/InfoBar.d.ts +7 -0
  228. package/dist/views/MessageInput/InfoBar.d.ts.map +1 -1
  229. package/dist/views/MessageInput/InfoBar.js +7 -0
  230. package/dist/views/MessageInput/InfoBar.js.map +1 -1
  231. package/dist/views/MessageInput/QuickCommandSelector.d.ts +7 -0
  232. package/dist/views/MessageInput/QuickCommandSelector.d.ts.map +1 -1
  233. package/dist/views/MessageInput/QuickCommandSelector.js +4 -0
  234. package/dist/views/MessageInput/QuickCommandSelector.js.map +1 -1
  235. package/dist/views/MessageInput/index.d.ts +8 -0
  236. package/dist/views/MessageInput/index.d.ts.map +1 -1
  237. package/dist/views/MessageInput/index.js +5 -0
  238. package/dist/views/MessageInput/index.js.map +1 -1
  239. package/dist/views/MinimizedHeader.d.ts +4 -0
  240. package/dist/views/MinimizedHeader.d.ts.map +1 -1
  241. package/dist/views/MinimizedHeader.js +4 -0
  242. package/dist/views/MinimizedHeader.js.map +1 -1
  243. package/dist/views/Stacks.d.ts +3 -0
  244. package/dist/views/Stacks.d.ts.map +1 -1
  245. package/dist/views/Stacks.js +3 -0
  246. package/dist/views/Stacks.js.map +1 -1
  247. package/dist/views/Workspaces.d.ts +3 -0
  248. package/dist/views/Workspaces.d.ts.map +1 -1
  249. package/dist/views/Workspaces.js +3 -0
  250. package/dist/views/Workspaces.js.map +1 -1
  251. package/package.json +6 -6
  252. package/src/StackspotAIWidget.tsx +23 -2
  253. package/src/chat-interceptors/CustomInputs.ts +14 -1
  254. package/src/chat-interceptors/quick-command-questions.ts +12 -2
  255. package/src/chat-interceptors/quick-commands.ts +42 -3
  256. package/src/chat-interceptors/send-message.ts +18 -0
  257. package/src/components/Accordion.tsx +10 -0
  258. package/src/components/AdaptiveTextArea.tsx +12 -0
  259. package/src/components/AutoFocus.tsx +19 -5
  260. package/src/components/Fading.tsx +25 -5
  261. package/src/components/FadingOverflow.tsx +31 -3
  262. package/src/components/FallbackBoundary/index.tsx +6 -0
  263. package/src/components/HistoryList.tsx +15 -1
  264. package/src/components/IconInput.tsx +3 -0
  265. package/src/components/OverlayMenu.tsx +17 -1
  266. package/src/components/ProgressBar.tsx +23 -0
  267. package/src/components/QuickStartButton.tsx +3 -0
  268. package/src/components/RightPanelForm.tsx +3 -0
  269. package/src/components/RightPanelTabs.tsx +3 -0
  270. package/src/components/TabManager.tsx +8 -0
  271. package/src/components/Tooltip/Tooltip.tsx +23 -0
  272. package/src/components/Tooltip/TooltipAPI.ts +17 -0
  273. package/src/components/Tooltip/types.ts +13 -0
  274. package/src/components/form/DescribedCheckboxGroup.tsx +4 -0
  275. package/src/components/form/DescribedRadioGroup.tsx +4 -0
  276. package/src/components/form/types.ts +34 -0
  277. package/src/context/AIWidgetProvider.tsx +19 -0
  278. package/src/context/hooks.ts +56 -1
  279. package/src/features.ts +29 -0
  280. package/src/right-panel/DefaultPanel.tsx +12 -13
  281. package/src/right-panel/RightPanel.tsx +3 -0
  282. package/src/right-panel/RightPanelProvider.tsx +15 -0
  283. package/src/right-panel/hooks.tsx +6 -0
  284. package/src/state/ChatEntry.ts +60 -2
  285. package/src/state/ChatState.ts +74 -9
  286. package/src/state/ChatTabsController.ts +31 -0
  287. package/src/state/ObservableState.ts +14 -0
  288. package/src/state/WidgetState.ts +5 -0
  289. package/src/types.ts +10 -0
  290. package/src/utils/chat.ts +14 -0
  291. package/src/utils/date.ts +25 -1
  292. package/src/utils/download.ts +5 -0
  293. package/src/utils/knowledge-source.ts +16 -0
  294. package/src/utils/string.ts +5 -1
  295. package/src/views/Agents.tsx +3 -0
  296. package/src/views/Chat/AgentInfo.tsx +3 -0
  297. package/src/views/Chat/ChatMessage.tsx +21 -32
  298. package/src/views/Chat/ChatMessages.tsx +3 -0
  299. package/src/views/Chat/chat-scroll.ts +1 -1
  300. package/src/views/Chat/events.ts +69 -0
  301. package/src/views/Chat/index.tsx +6 -0
  302. package/src/views/ChatHistory/ChatHistoryPanel.tsx +3 -0
  303. package/src/views/ChatHistory/HistoryItem.tsx +3 -0
  304. package/src/views/ChatHistory/index.tsx +11 -1
  305. package/src/views/ChatHistory/utils.ts +14 -0
  306. package/src/views/ChatTabSelection.tsx +3 -0
  307. package/src/views/Editor.tsx +4 -0
  308. package/src/views/Home.tsx +8 -0
  309. package/src/views/KSDocument.tsx +3 -0
  310. package/src/views/KnowledgeSources.tsx +3 -0
  311. package/src/views/MessageInput/ButtonGroup.tsx +22 -0
  312. package/src/views/MessageInput/InfoBar.tsx +7 -0
  313. package/src/views/MessageInput/QuickCommandSelector.tsx +7 -0
  314. package/src/views/MessageInput/index.tsx +8 -0
  315. package/src/views/MinimizedHeader.tsx +4 -0
  316. package/src/views/Stacks.tsx +3 -0
  317. package/src/views/Workspaces.tsx +3 -0
@@ -6,12 +6,33 @@ import { ObservableState } from './ObservableState'
6
6
  import { Labeled, LabeledWithImage } from './types'
7
7
 
8
8
  export interface ChatProperties {
9
+ /**
10
+ * The name of the chat.
11
+ */
9
12
  label: string,
13
+ /**
14
+ * The current AI agent.
15
+ */
10
16
  agent?: LabeledWithImage & { builtIn: boolean },
17
+ /**
18
+ * The current workspace.
19
+ */
11
20
  workspace?: Labeled,
21
+ /**
22
+ * The current stack.
23
+ */
12
24
  stack?: Labeled,
25
+ /**
26
+ * The current knowledge sources.
27
+ */
13
28
  knowledgeSources?: Labeled[],
29
+ /**
30
+ * Whether or not the chat is in a loading state.
31
+ */
14
32
  isLoading?: boolean,
33
+ /**
34
+ * The value of the next message. This is the value of the text typed in the textarea below the chat.
35
+ */
15
36
  nextMessage?: string,
16
37
  /**
17
38
  * The current code in the editor.
@@ -22,7 +43,7 @@ export interface ChatProperties {
22
43
  */
23
44
  codeLanguage?: string,
24
45
  /**
25
- * The current selection in the editor
46
+ * The current selection in the editor.
26
47
  */
27
48
  codeSelection?: string,
28
49
  }
@@ -34,28 +55,56 @@ export type MessageInterceptor = (
34
55
  ) => boolean | undefined | void | Promise<boolean | undefined | void>
35
56
 
36
57
  interface Options {
58
+ /**
59
+ * The unique identifier for this chat (conversationId).
60
+ */
37
61
  id: string,
62
+ /**
63
+ * The initial value for the state of this chat.
64
+ */
38
65
  initial: ChatProperties,
66
+ /**
67
+ * The interceptors to use for the messages pushed to this chat.
68
+ *
69
+ * The interceptors are run whenever a new message (chat entry) is added to the chat. They're always run in the order of the array and if
70
+ * any returns a promise, the promise is awaited before running the next.
71
+ *
72
+ * If an interceptor returns false or resolves to false (if a promise), the execution is interrupted, i.e. the next interceptors are not
73
+ * run.
74
+ *
75
+ * An interceptor receives three parameters:
76
+ * 1. the chat entry added;
77
+ * 2. the chat state;
78
+ * 3. an AbortSignal that can be aborted by calling `abort()` on the ChatState. Once the signal is aborted, the interceptor must abort all
79
+ * of its operations.
80
+ *
81
+ * Attention: when multiple messages are added at once, only the last goes through the interceptors. Furthermore, messages created by the
82
+ * constructor don't go through the interceptors, only messages added via `pushMessage` do.
83
+ */
39
84
  interceptors?: MessageInterceptor[],
85
+ /**
86
+ * The content of this chat, i.e. its messages.
87
+ */
40
88
  entries?: ChatEntry[],
41
89
  }
42
90
 
91
+ /**
92
+ * A chat. Each chat tab is a different ChatState.
93
+ */
43
94
  export class ChatState extends ObservableState<ChatProperties> {
44
95
  readonly id: string
45
96
  private entries: ChatEntry[]
46
97
  private messagesListeners: ChatMessagesListener[] = []
47
98
  private readonly interceptors: MessageInterceptor[]
99
+ /**
100
+ * A memory space (map) to be used by interceptors.
101
+ */
48
102
  interceptorMemory = new Map<string, any>()
49
- private abortions: AbortController[] = []
50
-
51
103
  /**
52
- * @param id the id of the chat.
53
- * @param initial the initial state.
54
- * @param interceptors a list of interceptors to run whenever a new message (entry) is added to the chat. If an interception function
55
- * returns false, the next interceptors are not run. Attention: when multiple messages are added at once, only the last goes through the
56
- * interceptors. Furthermore, messages created by the constructor don't go through the interceptors, only messages added via `pushMessage`
57
- * do.
104
+ * Abort signals currently active.
58
105
  */
106
+ private abortions: AbortController[] = []
107
+
59
108
  constructor({ id, initial, entries = [], interceptors = [] }: Options) {
60
109
  super(initial)
61
110
  this.id = id
@@ -82,6 +131,10 @@ export class ChatState extends ObservableState<ChatProperties> {
82
131
  pull(this.abortions, abort)
83
132
  }
84
133
 
134
+ /**
135
+ * Adds one or more messages to the chat. Messages are appended to the end of the chat.
136
+ * @param entries the messages to add.
137
+ */
85
138
  pushMessage(...entries: ChatEntry[]) {
86
139
  if (!entries.length) return
87
140
  this.entries = [...this.entries, ...entries]
@@ -89,15 +142,27 @@ export class ChatState extends ObservableState<ChatProperties> {
89
142
  this.runInterceptors(last(entries)!)
90
143
  }
91
144
 
145
+ /**
146
+ * Removes one or more messages from the end of the chat.
147
+ * @param quantity the number of messages to remove. Defaults to 1.
148
+ */
92
149
  popMessage(quantity = 1) {
93
150
  this.entries = dropRight(this.entries, quantity)
94
151
  this.runMessagesListeners()
95
152
  }
96
153
 
154
+ /**
155
+ * @returns the current list of messages.
156
+ */
97
157
  getMessages() {
98
158
  return this.entries
99
159
  }
100
160
 
161
+ /**
162
+ * Watches this chat for changes in the list of messages.
163
+ * @param listener the function to call whenever the list of messages changes.
164
+ * @returns a function that removes the listener.
165
+ */
101
166
  onChangeMessages(listener: ChatMessagesListener) {
102
167
  this.messagesListeners.push(listener)
103
168
  return () => {
@@ -3,6 +3,9 @@ import { ChatState } from './ChatState'
3
3
 
4
4
  type TabChangeListener = (chats: ChatState[], activeId: string) => void
5
5
 
6
+ /**
7
+ * Controls the chat tabs.
8
+ */
6
9
  export class ChatTabsController {
7
10
  private chats: ChatState[] = []
8
11
  private activeChatId = ''
@@ -12,12 +15,20 @@ export class ChatTabsController {
12
15
  this.listeners.forEach(l => l(this.chats, this.activeChatId))
13
16
  }
14
17
 
18
+ /**
19
+ * Adds new chats to the tab view.
20
+ * @param chats the chats to add.
21
+ */
15
22
  add(...chats: ChatState[]) {
16
23
  if (!chats.length) return
17
24
  this.chats = [...this.chats, ...chats]
18
25
  this.runListeners()
19
26
  }
20
27
 
28
+ /**
29
+ * Removes chats from the tab view.
30
+ * @param ids the ids of the chats to remove.
31
+ */
21
32
  remove(...ids: string[]) {
22
33
  if (this.chats.length <= 1 || !ids.length) return
23
34
  const currentActiveIndex = this.chats.findIndex(c => c.id === this.activeChatId)
@@ -29,18 +40,34 @@ export class ChatTabsController {
29
40
  this.runListeners()
30
41
  }
31
42
 
43
+ /**
44
+ * @param id the id of the chat to retrieve.
45
+ * @returns a ChatState corresponding to the id.
46
+ */
32
47
  get(id: string) {
33
48
  return this.chats.find(c => c.id === id)
34
49
  }
35
50
 
51
+ /**
52
+ * @returns the if of the chat corresponding to the tab that is currently active.
53
+ */
36
54
  getActiveChatId() {
37
55
  return this.activeChatId
38
56
  }
39
57
 
58
+ /**
59
+ * @returns all chats in tab view.
60
+ */
40
61
  getAll() {
41
62
  return this.chats
42
63
  }
43
64
 
65
+ /**
66
+ * Watches for changes in the tab view.
67
+ * @param listener a function called whenever a tab is added, removed or selected. This function receives the list of chats as the first
68
+ * parameter and the id of the active chat as the second parameter.
69
+ * @returns a function to remove the listener.
70
+ */
44
71
  onChange(listener: TabChangeListener) {
45
72
  this.listeners.push(listener)
46
73
  return () => {
@@ -48,6 +75,10 @@ export class ChatTabsController {
48
75
  }
49
76
  }
50
77
 
78
+ /**
79
+ * Changes the chat (tab) that is currently active.
80
+ * @param id the id of the chat to select.
81
+ */
51
82
  select(id: string) {
52
83
  this.activeChatId = id
53
84
  this.runListeners()
@@ -6,6 +6,14 @@ type Listeners<T> = {
6
6
  [K in keyof T]?: ((value: T[K]) => void)[]
7
7
  }
8
8
 
9
+ /**
10
+ * A generic representation of a state that can be observed for changes.
11
+ *
12
+ * Building our own state instead of using React is important because:
13
+ * 1. We need to be able to control it even outside the react context.
14
+ * 2. We need performance, we can't have everything re-rendering because something changes. We need to be careful to watch only the
15
+ * properties we need in a component.
16
+ */
9
17
  export class ObservableState<T> {
10
18
  protected state: T
11
19
  private listeners: Listeners<T> = {}
@@ -25,6 +33,12 @@ export class ObservableState<T> {
25
33
  return this.state[key]
26
34
  }
27
35
 
36
+ /**
37
+ * Watches for changes in the state referred by the provided key.
38
+ * @param key the key of the state to watch.
39
+ * @param listener a function called whenever the value of the state referred by `key` changes.
40
+ * @returns a function to remove the listener.
41
+ */
28
42
  onChange<K extends keyof T>(key: K, listener: (value: T[K]) => void) {
29
43
  this.listeners[key] ??= []
30
44
  this.listeners[key]!.push(listener)
@@ -16,6 +16,11 @@ export interface WidgetProperties {
16
16
  isMinimized?: boolean,
17
17
  }
18
18
 
19
+ /**
20
+ * Holds the full state of the AI Chat Widget.
21
+ *
22
+ * This can be used to fully control the chat widget. This is the model of what gets rendered to the screen.
23
+ */
19
24
  export class WidgetState extends ObservableState<WidgetProperties> {
20
25
  readonly chatTabs: ChatTabsController
21
26
 
package/src/types.ts CHANGED
@@ -14,7 +14,17 @@ export interface ButtonAction extends WithStyle {
14
14
  }
15
15
 
16
16
  export interface MinimizedActions {
17
+ /**
18
+ * When the chat is minimized, a button to collapse the window is rendered. This function is called whenever this button is clicked.
19
+ */
17
20
  onCollapse?: () => void,
21
+ /**
22
+ * If `onCollapse` is clicked, the button to collapse is replaced by a button to expand. This function is called whenever the expand
23
+ * button is clicked.
24
+ */
18
25
  onExpand?: () => void,
26
+ /**
27
+ * When the chat is minimized, a button to close the window is rendered. This function is called whenever this button is clicked.
28
+ */
19
29
  onClose?: () => void,
20
30
  }
package/src/utils/chat.ts CHANGED
@@ -4,8 +4,14 @@ import { ChatState, MessageInterceptor } from '../state/ChatState'
4
4
  import { WidgetState } from '../state/WidgetState'
5
5
  import { defaultLanguage } from './programming-languages'
6
6
 
7
+ // helps with naming new chats
7
8
  let next = 1
8
9
 
10
+ /**
11
+ * Utility for creating a new chat in the chat tabs.
12
+ * @param widget the widget state.
13
+ * @param interceptors the interceptors to add the chat.
14
+ */
9
15
  export function createNewChat(widget: WidgetState, interceptors: MessageInterceptor[]) {
10
16
  const id = ulid()
11
17
  widget.chatTabs.add(new ChatState({ id, initial: { label: `Chat ${next}` }, interceptors }))
@@ -13,6 +19,14 @@ export function createNewChat(widget: WidgetState, interceptors: MessageIntercep
13
19
  next++
14
20
  }
15
21
 
22
+ /**
23
+ * Builds a conversation context from a ChatState.
24
+ *
25
+ * The conversation context is needed by most backend services.
26
+ *
27
+ * @param state the ChatState to build the context from.
28
+ * @returns the conversation context ready to be sent to the backend.
29
+ */
16
30
  export function buildConversationContext(state: ChatState): FixedChatRequest['context'] {
17
31
  return {
18
32
  workspace: state.get('workspace')?.id,
package/src/utils/date.ts CHANGED
@@ -8,7 +8,17 @@ const fullFormatOptions: Intl.DateTimeFormatOptions = {
8
8
  month: 'long',
9
9
  }
10
10
 
11
-
11
+ /**
12
+ * Formats a date to show at the footer of a chat message.
13
+ *
14
+ * @example
15
+ * - Today, 14:11
16
+ * - Yesterday, 09:00
17
+ * - October 26 at 10:53
18
+ * @param date the Date to format.
19
+ * @param language the language to use.
20
+ * @returns the formatted date.
21
+ */
12
22
  export function formatDateForChatMessage(date: Date, language: Language = getLanguage()) {
13
23
  const formatted: string[] = []
14
24
  const now = new Date()
@@ -21,13 +31,27 @@ export function formatDateForChatMessage(date: Date, language: Language = getLan
21
31
  return formatted.join(', ')
22
32
  }
23
33
 
34
+ /**
35
+ * Uses the current language to format dates.
36
+ * @returns an object containing functions for formatting dates.
37
+ */
24
38
  export function useDateFormatter() {
25
39
  const language = useLanguage()
26
40
  return {
41
+ /**
42
+ * @param date the date to format using {@link formatDateForChatMessage}.
43
+ * @returns the formatted date according to the current language. If the language changes, this result also changes.
44
+ */
27
45
  formatForChatMessage: (date: Date) => formatDateForChatMessage(date, language),
28
46
  }
29
47
  }
30
48
 
49
+ /**
50
+ * Subtracts `numberOfDays` from `date`.
51
+ * @param date
52
+ * @param numberOfDays
53
+ * @returns the resulting Date object.
54
+ */
31
55
  export function subtractDays(date: Date, numberOfDays: number) {
32
56
  return new Date(date.getTime() - 24 * numberOfDays * 60 * 60000)
33
57
  }
@@ -1,3 +1,8 @@
1
+ /**
2
+ * Downloads `text` as a file named `filename`.
3
+ * @param filename the name of the file.
4
+ * @param text the content of the file.
5
+ */
1
6
  export function download(filename: string, text: string) {
2
7
  const element = document.createElement('a')
3
8
  element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text))
@@ -1,6 +1,12 @@
1
1
  import { DocumentResponse, SourceKnowledgeSource, SourceProjectFile4, SourceStackAi } from '@stack-spot/portal-network/api/ai'
2
2
  import { KnowledgeSource } from '../state/ChatEntry'
3
3
 
4
+ /**
5
+ * A document can come from the backend as something that resembles a JSON, but it is not. This function attempts to solve some issues and
6
+ * still interpret as a JSON.
7
+ * @param str the malformed JSON.
8
+ * @returns the object corresponding to parsed JSON or undefined if it wasn't possible to fix it.
9
+ */
4
10
  function attemptToParseMalFormedJson(str: string) {
5
11
  try {
6
12
  return JSON.parse(str)
@@ -21,6 +27,11 @@ function attemptToParseMalFormedJson(str: string) {
21
27
  }
22
28
  }
23
29
 
30
+ /**
31
+ * Extracts the important part of a KS document to show for the user.
32
+ * @param document the document to parse.
33
+ * @returns an object containing the programming language of the KS, its code snippet and a textual description.
34
+ */
24
35
  export function extractCodeFromKSDocument(document: DocumentResponse): { language?: string, snippet: string, text?: string } {
25
36
  const language = (document?.metadata as any)?.language
26
37
  if (language) {
@@ -43,6 +54,11 @@ export function extractCodeFromKSDocument(document: DocumentResponse): { languag
43
54
  return typeof document === 'object' ? { language: 'json', snippet: JSON.stringify(document, null, 2) } : { snippet: String(document) }
44
55
  }
45
56
 
57
+ /**
58
+ * Extracts all knowledge sources from a mix of sources that can be used to shape an AI agent's answer.
59
+ * @param sources a list with sources of any kind.
60
+ * @returns a list of knowledge sources in the format expected by the chat.
61
+ */
46
62
  export function genericSourcesToKnowledgeSources(
47
63
  sources: (SourceStackAi | SourceKnowledgeSource | SourceProjectFile4)[] | undefined,
48
64
  ): KnowledgeSource[] | undefined {
@@ -1,4 +1,8 @@
1
- // gets the size of a string removing control characters and spaces
1
+ /**
2
+ * Gets the size of a string removing control characters and spaces
3
+ * @param str the string to count.
4
+ * @returns the count value.
5
+ */
2
6
  export function getSizeOfString(str: string): number {
3
7
  // eslint-disable-next-line no-control-regex
4
8
  const withoutSpacesAndControls = str.replace(/[\u0000-\u001F\u007F-\u009F\u061C\u200E\u200F\u202A-\u202E\u2066-\u2069\s]/g, '')
@@ -82,6 +82,9 @@ export const Agents = () => {
82
82
  return null
83
83
  }
84
84
 
85
+ /**
86
+ * Renders the Agent selection form in the Right Panel if this is the panel that is currently opened.
87
+ */
85
88
  const AgentsPanel = () => {
86
89
  const t = useTranslate(dictionary)
87
90
  const chat = useCurrentChat()
@@ -6,6 +6,9 @@ interface Props {
6
6
  agent?: LabeledWithImage,
7
7
  }
8
8
 
9
+ /**
10
+ * Renders the avatar of an agent in a message.
11
+ */
9
12
  export const AgentInfo = ({ agent }: Props) => (
10
13
  <>
11
14
  {agent?.image
@@ -1,39 +1,37 @@
1
1
  import { Button, IconBox, Text } from '@citric/core'
2
2
  import { Copy, Dislike, DislikeFill, Like, LikeFill, TimesCircle } from '@citric/icons'
3
3
  import { Avatar, Badge, IconButton } from '@citric/ui'
4
- import { aiClient } from '@stack-spot/portal-network'
5
4
  import { listToClass } from '@stack-spot/portal-theme'
6
5
  import { Dictionary, useTranslate } from '@stack-spot/portal-translate'
7
6
  import { useCallback, useMemo, useRef, useState } from 'react'
8
7
  import { Markdown } from '../../components/Markdown'
9
8
  import { useChatEntry, useCurrentChat, useWidget } from '../../context/hooks'
10
9
  import { ChatEntry, SerializableAction, TextChatEntry } from '../../state/ChatEntry'
11
- import { ChatState } from '../../state/ChatState'
12
- import { buildConversationContext } from '../../utils/chat'
13
10
  import { useDateFormatter } from '../../utils/date'
14
- import { getSizeOfString } from '../../utils/string'
15
11
  import { AgentInfo } from './AgentInfo'
16
12
  import { useChatScrollToBottomEffect } from './chat-scroll'
13
+ import { onCopyAll, onCopyCode, onLikeOrDislike } from './events'
17
14
 
18
- async function onCopyCode(code: string, messageId: string, chat: ChatState) {
19
- try {
20
- await aiClient.createEvent.mutate({
21
- body: [{
22
- type: 'code_copied',
23
- code,
24
- context: buildConversationContext(chat),
25
- size: getSizeOfString(code),
26
- generated_at: new Date().getTime(),
27
- message_id: messageId,
28
- }],
29
- })
30
- } catch (error) {
31
- // eslint-disable-next-line no-console
32
- console.warn('Failed to register event: code copied.')
33
- }
15
+ interface Props {
16
+ /**
17
+ * The ChatEntry to render.
18
+ */
19
+ message: ChatEntry,
20
+ /**
21
+ * The name of the user currently logged in (will be used if the agent type of the message is "user").
22
+ */
23
+ username: string,
24
+ /**
25
+ * Whether or not this is the last message in the chat. This is important for disabling action buttons in messages that are no longer
26
+ * relevant.
27
+ */
28
+ isLast: boolean,
34
29
  }
35
30
 
36
- export const ChatMessage = ({ message, username, isLast }: { message: ChatEntry, username: string, isLast: boolean }) => {
31
+ /**
32
+ * Renders a message (ChatEntry) in the chat.
33
+ */
34
+ export const ChatMessage = ({ message, username, isLast }: Props) => {
37
35
  const t = useTranslate(dictionary)
38
36
  const [liked, setLiked] = useState<boolean | undefined>()
39
37
  const entry = useChatEntry(message)
@@ -64,16 +62,7 @@ export const ChatMessage = ({ message, username, isLast }: { message: ChatEntry,
64
62
  if (!entry.messageId || like === liked) return
65
63
  setLiked(like)
66
64
  try {
67
- await aiClient.createEvent.mutate({
68
- body: [{
69
- feedback: like ? 'LIKE' : 'DISLIKE',
70
- message_id: entry.messageId,
71
- type: 'user_feedback_provided',
72
- code: '',
73
- generated_at: Math.floor(new Date().getTime() / 1000),
74
- size: 0,
75
- }],
76
- })
65
+ await onLikeOrDislike(entry.messageId, like)
77
66
  } catch {
78
67
  setLiked(liked)
79
68
  }
@@ -131,7 +120,7 @@ export const ChatMessage = ({ message, username, isLast }: { message: ChatEntry,
131
120
  {shouldShowFooter && <div className="message-footer">
132
121
  {entry.agentType === 'bot' && !entry.error && <div className="message-actions">
133
122
  {entry.type === 'md' && (
134
- <IconButton title={t.copy} aria-label={t.copy} onClick={() => navigator.clipboard.writeText(entry.content)}>
123
+ <IconButton title={t.copy} aria-label={t.copy} onClick={() => onCopyAll(entry, chat)}>
135
124
  <Copy />
136
125
  </IconButton>
137
126
  )}
@@ -8,6 +8,9 @@ interface Props {
8
8
  chatId: string,
9
9
  }
10
10
 
11
+ /**
12
+ * Renders all messages of a chat.
13
+ */
11
14
  export const ChatMessages = ({ chatId, username }: Props) => {
12
15
  const messages = useChatMessages(chatId)
13
16
  const items = useMemo(
@@ -2,7 +2,7 @@ import { useEffect, useRef } from 'react'
2
2
 
3
3
  /**
4
4
  * Scrolls the closest chat (upwards in the tree) to its bottom.
5
- * @param ref the reference element.
5
+ * @param ref the reference to the element contained the chat we want to scroll.
6
6
  * @param deps when the deps changes, the chat is scrolled.
7
7
  */
8
8
  export function useChatScrollToBottomEffect(ref: React.RefObject<HTMLElement>, deps: any[]) {
@@ -0,0 +1,69 @@
1
+ import { aiClient } from '@stack-spot/portal-network'
2
+ import { TextChatEntry } from '../../state/ChatEntry'
3
+ import { ChatState } from '../../state/ChatState'
4
+ import { buildConversationContext } from '../../utils/chat'
5
+ import { getSizeOfString } from '../../utils/string'
6
+
7
+ /**
8
+ * Creates the event of copying a code.
9
+ * @param code the code copied.
10
+ * @param messageId the id of the message containing the code.
11
+ * @param chat the chat state.
12
+ */
13
+ export async function onCopyCode(code: string, messageId: string, chat: ChatState) {
14
+ try {
15
+ await aiClient.createEvent.mutate({
16
+ body: [{
17
+ type: 'code_copied',
18
+ code,
19
+ context: buildConversationContext(chat),
20
+ size: getSizeOfString(code),
21
+ generated_at: new Date().getTime(),
22
+ message_id: messageId,
23
+ }],
24
+ })
25
+ } catch (error) {
26
+ // eslint-disable-next-line no-console
27
+ console.warn('Failed to register event: code copied.')
28
+ }
29
+ }
30
+
31
+ /**
32
+ * Creates the event of copying a whole message.
33
+ * @param entry the message copied.
34
+ * @param chat the chat state.
35
+ */
36
+ export function onCopyAll(entry: TextChatEntry, chat: ChatState) {
37
+ navigator.clipboard.writeText(entry.content)
38
+ return aiClient.createEvent.mutate({
39
+ body: [
40
+ {
41
+ code: entry.content,
42
+ size: getSizeOfString(entry.content),
43
+ generated_at: new Date().getTime(),
44
+ type: 'copied_all',
45
+ knowledge_sources: entry.knowledgeSources?.map(ks => ({ slug: ks.slug, type: 'knowledge_source', name: ks.name })),
46
+ context: buildConversationContext(chat),
47
+ message_id: entry.messageId,
48
+ },
49
+ ],
50
+ })
51
+ }
52
+
53
+ /**
54
+ * Creates an event of like or dislike for a message.
55
+ * @param messageId the id of the message.
56
+ * @param like true if it's a like, false if it's a dislike.
57
+ */
58
+ export function onLikeOrDislike(messageId: string, like: boolean) {
59
+ return aiClient.createEvent.mutate({
60
+ body: [{
61
+ feedback: like ? 'LIKE' : 'DISLIKE',
62
+ message_id: messageId,
63
+ type: 'user_feedback_provided',
64
+ code: '',
65
+ generated_at: Math.floor(new Date().getTime() / 1000),
66
+ size: 0,
67
+ }],
68
+ })
69
+ }
@@ -2,9 +2,15 @@ import { useChatTabs } from '../../context/hooks'
2
2
  import { ChatMessages } from './ChatMessages'
3
3
 
4
4
  interface Props {
5
+ /**
6
+ * The name of the user currently logged in.
7
+ */
5
8
  username: string,
6
9
  }
7
10
 
11
+ /**
12
+ * Renders the chat panel, with all of its messages.
13
+ */
8
14
  export const Chat = ({ username }: Props) => {
9
15
  const { active } = useChatTabs()
10
16
  return <ChatMessages key={active} chatId={active} username={username} />
@@ -5,6 +5,9 @@ import { HistoryList } from '../../components/HistoryList'
5
5
  import { MessageInterceptor } from '../../state/ChatState'
6
6
  import { HistoryItem } from './HistoryItem'
7
7
 
8
+ /**
9
+ * Renders the list of conversations (history).
10
+ */
8
11
  export const ChatHistoryPanel = ({ interceptors }: { interceptors: MessageInterceptor[] }) => {
9
12
  const [chats, { fetchNextPage, hasNextPage }] = aiClient.chats.useInfiniteQuery({ size: 40 })
10
13
  return (
@@ -18,6 +18,9 @@ import { useHistoryDictionary } from './dictionary'
18
18
  import { HistoryItemBox } from './styled'
19
19
  import { findStack, findWorkspace, getAllAgents } from './utils'
20
20
 
21
+ /**
22
+ * Renders an item of the list of conversations (history).
23
+ */
21
24
  export const HistoryItem = ({ item, interceptors }: { item: ConversationResponse, interceptors: MessageInterceptor[] }) => {
22
25
  const t = useHistoryDictionary()
23
26
  const [isLoading, setLoading] = useState(false)
@@ -6,7 +6,17 @@ import { MessageInterceptor } from '../../state/ChatState'
6
6
  import { ChatHistoryPanel } from './ChatHistoryPanel'
7
7
  import { useHistoryDictionary } from './dictionary'
8
8
 
9
- export const ChatHistory = ({ interceptors }: { interceptors: MessageInterceptor[] }) => {
9
+ interface Props {
10
+ /**
11
+ * The chat interceptors to use when recreating a ChatState from the history.
12
+ */
13
+ interceptors: MessageInterceptor[],
14
+ }
15
+
16
+ /**
17
+ * Renders the Chat History in the Right Panel if this is the panel that is currently opened.
18
+ */
19
+ export const ChatHistory = ({ interceptors }: Props) => {
10
20
  const t = useHistoryDictionary()
11
21
  const panel = useWidgetState('panel')
12
22
  const { open } = useRightPanel()