@stack-spot/ai-chat-widget 1.5.2 → 1.7.0-beta.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 (70) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/app-metadata.json +7 -7
  3. package/dist/chat-interceptors/send-message.d.ts.map +1 -1
  4. package/dist/chat-interceptors/send-message.js +16 -8
  5. package/dist/chat-interceptors/send-message.js.map +1 -1
  6. package/dist/components/AdaptiveTextArea.d.ts.map +1 -1
  7. package/dist/components/AdaptiveTextArea.js +7 -1
  8. package/dist/components/AdaptiveTextArea.js.map +1 -1
  9. package/dist/components/Selector/index.d.ts +20 -0
  10. package/dist/components/Selector/index.d.ts.map +1 -0
  11. package/dist/components/Selector/index.js +134 -0
  12. package/dist/components/Selector/index.js.map +1 -0
  13. package/dist/components/Selector/styled.d.ts +2 -0
  14. package/dist/components/Selector/styled.d.ts.map +1 -0
  15. package/dist/components/Selector/styled.js +144 -0
  16. package/dist/components/Selector/styled.js.map +1 -0
  17. package/dist/regex.d.ts +1 -0
  18. package/dist/regex.d.ts.map +1 -1
  19. package/dist/regex.js +1 -0
  20. package/dist/regex.js.map +1 -1
  21. package/dist/views/Agents/AgentsTab.js +4 -4
  22. package/dist/views/Agents/AgentsTab.js.map +1 -1
  23. package/dist/views/Chat/AgentInfo.js +1 -1
  24. package/dist/views/Home/CustomAgent.js +3 -3
  25. package/dist/views/Home/CustomAgent.js.map +1 -1
  26. package/dist/views/Home/styled.js +1 -1
  27. package/dist/views/MessageInput/AgentSelector.d.ts +4 -0
  28. package/dist/views/MessageInput/AgentSelector.d.ts.map +1 -0
  29. package/dist/views/MessageInput/AgentSelector.js +31 -0
  30. package/dist/views/MessageInput/AgentSelector.js.map +1 -0
  31. package/dist/views/MessageInput/ButtonAgent.d.ts +2 -0
  32. package/dist/views/MessageInput/ButtonAgent.d.ts.map +1 -0
  33. package/dist/views/MessageInput/ButtonAgent.js +17 -0
  34. package/dist/views/MessageInput/ButtonAgent.js.map +1 -0
  35. package/dist/views/MessageInput/ButtonGroup.d.ts +1 -1
  36. package/dist/views/MessageInput/ButtonGroup.d.ts.map +1 -1
  37. package/dist/views/MessageInput/ButtonGroup.js +4 -6
  38. package/dist/views/MessageInput/ButtonGroup.js.map +1 -1
  39. package/dist/views/MessageInput/QuickCommandSelector.d.ts +2 -11
  40. package/dist/views/MessageInput/QuickCommandSelector.d.ts.map +1 -1
  41. package/dist/views/MessageInput/QuickCommandSelector.js +17 -130
  42. package/dist/views/MessageInput/QuickCommandSelector.js.map +1 -1
  43. package/dist/views/MessageInput/dictionary.d.ts +1 -1
  44. package/dist/views/MessageInput/dictionary.d.ts.map +1 -1
  45. package/dist/views/MessageInput/dictionary.js +4 -2
  46. package/dist/views/MessageInput/dictionary.js.map +1 -1
  47. package/dist/views/MessageInput/index.d.ts.map +1 -1
  48. package/dist/views/MessageInput/index.js +4 -1
  49. package/dist/views/MessageInput/index.js.map +1 -1
  50. package/dist/views/MessageInput/styled.d.ts.map +1 -1
  51. package/dist/views/MessageInput/styled.js +51 -144
  52. package/dist/views/MessageInput/styled.js.map +1 -1
  53. package/package.json +1 -1
  54. package/src/app-metadata.json +7 -7
  55. package/src/chat-interceptors/send-message.ts +14 -7
  56. package/src/components/AdaptiveTextArea.tsx +8 -1
  57. package/src/components/Selector/index.tsx +245 -0
  58. package/src/components/Selector/styled.ts +145 -0
  59. package/src/regex.ts +1 -0
  60. package/src/views/Agents/AgentsTab.tsx +4 -4
  61. package/src/views/Chat/AgentInfo.tsx +1 -1
  62. package/src/views/Home/CustomAgent.tsx +3 -3
  63. package/src/views/Home/styled.ts +1 -1
  64. package/src/views/MessageInput/AgentSelector.tsx +35 -0
  65. package/src/views/MessageInput/ButtonAgent.tsx +36 -0
  66. package/src/views/MessageInput/ButtonGroup.tsx +3 -10
  67. package/src/views/MessageInput/QuickCommandSelector.tsx +21 -205
  68. package/src/views/MessageInput/dictionary.ts +4 -2
  69. package/src/views/MessageInput/index.tsx +8 -3
  70. package/src/views/MessageInput/styled.ts +51 -144
@@ -86,12 +86,12 @@ export const MessageInputBox = styled.div `
86
86
  display: flex;
87
87
  position: relative;
88
88
  flex-direction: row;
89
- gap: 4px;
89
+ gap: 8px;
90
90
  align-items: end;
91
91
  border-radius: 4px;
92
92
  border: 1px solid ${theme.color.light[500]};
93
93
  background-color: ${theme.color.light[300]};
94
- padding: 8px 10px;
94
+ padding: 10px 8px;
95
95
  transition: border-color 0.3s, background-color 0.3s;
96
96
 
97
97
  &.focused {
@@ -148,12 +148,16 @@ export const MessageInputBox = styled.div `
148
148
  svg {
149
149
  transform: rotate(180deg);
150
150
  }
151
- }
152
-
153
- &.agent img {
154
- width: 80%;
155
- height: 80%;
156
- border-radius: 50%;
151
+ }
152
+
153
+ &.agent {
154
+ border-radius: 50%;
155
+ opacity: 1;
156
+ img {
157
+ width: 100%;
158
+ height: 100%;
159
+ border-radius: 50%;
160
+ }
157
161
  }
158
162
  }
159
163
 
@@ -189,6 +193,45 @@ export const MessageInputBox = styled.div `
189
193
  width: 24px;
190
194
  height: 24px;
191
195
  }
196
+
197
+ .group-agent {
198
+ display: flex;
199
+ margin-left: 0.5rem;
200
+ margin-right: 0.5rem;
201
+
202
+ button {
203
+ margin-right: -0.5rem;
204
+ margin-left: -0.5rem;
205
+ border-radius: 50%;
206
+ background-color: ${theme.color.light[300]};
207
+ border: 1px solid ${theme.color.light[600]};
208
+ display: flex;
209
+
210
+ &.agent-selected:hover:before {
211
+ content: '';
212
+ width: 22px;
213
+ height: 22px;
214
+ position: absolute;
215
+ background-color: ${theme.color.light[300]};
216
+ border-radius: 100%;
217
+ opacity: 0.4;
218
+ }
219
+
220
+ .icon-remove {
221
+ display: none;
222
+ fill: ${theme.color.light.contrastText};
223
+ position: absolute;
224
+ }
225
+
226
+ &:hover .icon-remove {
227
+ display: flex;
228
+ }
229
+
230
+ .image {
231
+ margin: 1px;
232
+ }
233
+ }
234
+ }
192
235
  }
193
236
 
194
237
  textarea {
@@ -204,141 +247,5 @@ export const MessageInputBox = styled.div `
204
247
  box-shadow: none;
205
248
  }
206
249
  }
207
-
208
- .quick-command-selector {
209
- position: absolute;
210
- border-radius: 4px;
211
- border: 1px solid ${theme.color.light[600]};
212
- background-color: ${theme.color.light[500]};
213
- box-shadow: 0px 2px 16px 0px #0000005C;
214
- display: flex;
215
- flex-direction: column;
216
- width: 480px;
217
- bottom: 55px;
218
-
219
- .loading, .error {
220
- padding-bottom: 26px;
221
- p {
222
- width: 200px;
223
- text-align: center;
224
- line-height: 20px;
225
- }
226
- }
227
-
228
- .empty {
229
- padding-bottom: 26px;
230
- width: 200px;
231
- text-align: center;
232
- line-height: 20px;
233
- margin: auto;
234
- }
235
-
236
- header {
237
- display: flex;
238
- flex-direction: row;
239
- gap: 8px;
240
- align-items: center;
241
- padding: 8px;
242
- font-family: 'San Francisco';
243
- font-weight: 500;
244
- font-size: 11px;
245
- }
246
-
247
- .body {
248
- display: flex;
249
- flex-direction: row;
250
- align-items: center;
251
- }
252
-
253
- ul {
254
- margin: 0;
255
- padding: 0;
256
- list-style: none;
257
- }
258
-
259
- ul.tabs {
260
- display: flex;
261
- flex-direction: column;
262
-
263
- li {
264
- display: flex;
265
- flex-direction: column;
266
- }
267
-
268
- button {
269
- box-sizing: border-box;
270
- color: ${theme.color.light[700]};
271
- text-align: left;
272
- padding: 10px;
273
- font-weight: 600;
274
- font-size: 12px;
275
- transition: background-color 0.3s;
276
- border-top-right-radius: 4px;
277
- border-bottom-right-radius: 4px;
278
- background-color: transparent;
279
- border: none;
280
- cursor: pointer;
281
- outline: none;
282
-
283
- &:hover, &.active, &:focus {
284
- background-color: ${theme.color.light[600]};
285
- }
286
-
287
- &.active {
288
- border-left: 1px solid ${theme.color.light.contrastText};
289
- color: ${theme.color.light.contrastText};
290
- }
291
- }
292
- }
293
-
294
- ul.command-list {
295
- align-self: stretch;
296
- display: flex;
297
- flex-direction: column;
298
- gap: 2px;
299
- overflow-y: auto;
300
- flex: 1;
301
- max-height: 170px;
302
-
303
- li {
304
- display: flex;
305
- flex-direction: row;
306
- align-items: center;
307
- gap: 8px;
308
- padding: 8px;
309
- border-radius: 4px;
310
-
311
- &:hover, &.focus {
312
- background-color: ${theme.color.light[600]};
313
- }
314
-
315
- button.qc {
316
- flex: 1;
317
- border: none;
318
- text-align: left;
319
- background-color: transparent;
320
- text-align: left;
321
- outline: none;
322
- overflow: hidden;
323
- cursor: pointer;
324
-
325
- .qc-title {
326
- font-size: 11px;
327
- margin: 0 0 4px 0;
328
- color: ${theme.color.light.contrastText};
329
- text-transform: uppercase;
330
- text-overflow: ellipsis;
331
- overflow: hidden;
332
- }
333
-
334
- .qc-description {
335
- color: ${theme.color.light[700]};
336
- font-size: 12px;
337
- margin: 0;
338
- }
339
- }
340
- }
341
- }
342
- }
343
250
  `;
344
251
  //# sourceMappingURL=styled.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"styled.js","sourceRoot":"","sources":["../../../src/views/MessageInput/styled.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AACvC,OAAO,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAA;AAChD,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAA;AAE1C,MAAM,eAAe,GAAG,EAAE,CAAA;AAC1B,MAAM,qBAAqB,GAAG,CAAC,CAAA;AAC/B,MAAM,CAAC,MAAM,gBAAgB,GAAG,GAAG,CAAA;AACnC,MAAM,CAAC,MAAM,gBAAgB,GAAG,EAAE,CAAA;AAElC,MAAM,CAAC,MAAM,eAAe,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;kBAYvB,eAAe,GAAG,qBAAqB;;;;;;;;;;;;;;;;gBAgBzC,eAAe;;0BAEL,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UA+BtC,UAAU;;;;;;;;;;;;;;;;;;;;;;;wBAuBI,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC;wBACtB,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC;;;;;sBAKxB,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC;;;;0BAIpB,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC;;;;;;;;;;;;;;;;;;;;gBAoBhC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC;;;;;;4BAMV,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC;;;;4BAItB,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC;;;;;kBAKlC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;MAmD5C,UAAU;;;;;;;;;;;cAWF,gBAAgB;;;;;;;;;;;;wBAYN,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC;wBACtB,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA0D7B,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC;;;;;;;;;;;;;;8BAcT,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC;;;;mCAIjB,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,YAAY;mBAC9C,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,YAAY;;;;;;;;;;;;;;;;;;;;;;;8BAuBnB,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC;;;;;;;;;;;;;;;;qBAgB/B,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,YAAY;;;;;;;qBAO9B,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC;;;;;;;;CAQ1C,CAAA"}
1
+ {"version":3,"file":"styled.js","sourceRoot":"","sources":["../../../src/views/MessageInput/styled.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AACvC,OAAO,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAA;AAChD,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAA;AAE1C,MAAM,eAAe,GAAG,EAAE,CAAA;AAC1B,MAAM,qBAAqB,GAAG,CAAC,CAAA;AAC/B,MAAM,CAAC,MAAM,gBAAgB,GAAG,GAAG,CAAA;AACnC,MAAM,CAAC,MAAM,gBAAgB,GAAG,EAAE,CAAA;AAElC,MAAM,CAAC,MAAM,eAAe,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;kBAYvB,eAAe,GAAG,qBAAqB;;;;;;;;;;;;;;;;gBAgBzC,eAAe;;0BAEL,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UA+BtC,UAAU;;;;;;;;;;;;;;;;;;;;;;;wBAuBI,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC;wBACtB,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC;;;;;sBAKxB,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC;;;;0BAIpB,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC;;;;;;;;;;;;;;;;;;;;gBAoBhC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC;;;;;;4BAMV,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC;;;;4BAItB,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC;;;;;kBAKlC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;MAuD5C,UAAU;;;;;;;;;;;;;;8BAcc,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC;8BACtB,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC;;;;;;;;gCAQpB,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC;;;;;;;oBAOlC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,YAAY;;;;;;;;;;;;;;;;;;;;cAoBpC,gBAAgB;;;;;;;;CAQ7B,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stack-spot/ai-chat-widget",
3
- "version": "1.5.2",
3
+ "version": "1.7.0-beta.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@stack-spot/ai-chat-widget",
3
- "version": "1.5.2",
4
- "date": "Wed Jan 08 2025 15:01:28 GMT+0000 (Coordinated Universal Time)",
3
+ "version": "1.7.0-beta.0",
4
+ "date": "Tue Jan 21 2025 08:16:05 GMT-0300 (Brasilia Standard Time)",
5
5
  "dependencies": [
6
6
  {
7
7
  "name": "@stack-spot/app-metadata",
@@ -89,15 +89,15 @@
89
89
  },
90
90
  {
91
91
  "name": "@citric/core",
92
- "version": "6.2.0(lodash@4.17.21)(react@18.2.0)(styled-components@6.1.10(react-dom@18.2.0(react@18.2.0))(react@18.2.0))"
92
+ "version": "6.4.0(lodash@4.17.21)(react@18.2.0)(styled-components@6.1.10(react-dom@18.2.0(react@18.2.0))(react@18.2.0))"
93
93
  },
94
94
  {
95
95
  "name": "@citric/icons",
96
- "version": "5.7.7(react@18.2.0)"
96
+ "version": "5.9.0(react@18.2.0)"
97
97
  },
98
98
  {
99
99
  "name": "@citric/ui",
100
- "version": "6.1.2(@citric/core@6.2.0(lodash@4.17.21)(react@18.2.0)(styled-components@6.1.10(react-dom@18.2.0(react@18.2.0))(react@18.2.0)))(@citric/icons@5.7.7(react@18.2.0))(lodash@4.17.21)(react@18.2.0)(styled-components@6.1.10(react-dom@18.2.0(react@18.2.0))(react@18.2.0))"
100
+ "version": "6.5.5(@citric/core@6.4.0(lodash@4.17.21)(react@18.2.0)(styled-components@6.1.10(react-dom@18.2.0(react@18.2.0))(react@18.2.0)))(@citric/icons@5.9.0(react@18.2.0))(lodash@4.17.21)(react@18.2.0)(styled-components@6.1.10(react-dom@18.2.0(react@18.2.0))(react@18.2.0))"
101
101
  },
102
102
  {
103
103
  "name": "@monaco-editor/react",
@@ -105,7 +105,7 @@
105
105
  },
106
106
  {
107
107
  "name": "@stack-spot/portal-components",
108
- "version": "2.8.1(@citric/core@6.2.0(lodash@4.17.21)(react@18.2.0)(styled-components@6.1.10(react-dom@18.2.0(react@18.2.0))(react@18.2.0)))(@citric/icons@5.7.7(react@18.2.0))(@citric/ui@6.1.2(@citric/core@6.2.0(lodash@4.17.21)(react@18.2.0)(styled-components@6.1.10(react-dom@18.2.0(react@18.2.0))(react@18.2.0)))(@citric/icons@5.7.7(react@18.2.0))(lodash@4.17.21)(react@18.2.0)(styled-components@6.1.10(react-dom@18.2.0(react@18.2.0))(react@18.2.0)))(@stack-spot/portal-theme@1.1.0(@citric/core@6.2.0(lodash@4.17.21)(react@18.2.0)(styled-components@6.1.10(react-dom@18.2.0(react@18.2.0))(react@18.2.0)))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(styled-components@6.1.10(react-dom@18.2.0(react@18.2.0))(react@18.2.0)))(@stack-spot/portal-translate@1.1.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@types/react@18.3.11)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)"
108
+ "version": "2.8.1(@citric/core@6.4.0(lodash@4.17.21)(react@18.2.0)(styled-components@6.1.10(react-dom@18.2.0(react@18.2.0))(react@18.2.0)))(@citric/icons@5.9.0(react@18.2.0))(@citric/ui@6.5.5(@citric/core@6.4.0(lodash@4.17.21)(react@18.2.0)(styled-components@6.1.10(react-dom@18.2.0(react@18.2.0))(react@18.2.0)))(@citric/icons@5.9.0(react@18.2.0))(lodash@4.17.21)(react@18.2.0)(styled-components@6.1.10(react-dom@18.2.0(react@18.2.0))(react@18.2.0)))(@stack-spot/portal-theme@1.1.0(@citric/core@6.4.0(lodash@4.17.21)(react@18.2.0)(styled-components@6.1.10(react-dom@18.2.0(react@18.2.0))(react@18.2.0)))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(styled-components@6.1.10(react-dom@18.2.0(react@18.2.0))(react@18.2.0)))(@stack-spot/portal-translate@1.1.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@types/react@18.3.11)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)"
109
109
  },
110
110
  {
111
111
  "name": "@stack-spot/portal-network",
@@ -113,7 +113,7 @@
113
113
  },
114
114
  {
115
115
  "name": "@stack-spot/portal-theme",
116
- "version": "1.1.0(@citric/core@6.2.0(lodash@4.17.21)(react@18.2.0)(styled-components@6.1.10(react-dom@18.2.0(react@18.2.0))(react@18.2.0)))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(styled-components@6.1.10(react-dom@18.2.0(react@18.2.0))(react@18.2.0))"
116
+ "version": "1.1.0(@citric/core@6.4.0(lodash@4.17.21)(react@18.2.0)(styled-components@6.1.10(react-dom@18.2.0(react@18.2.0))(react@18.2.0)))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(styled-components@6.1.10(react-dom@18.2.0(react@18.2.0))(react@18.2.0))"
117
117
  },
118
118
  {
119
119
  "name": "@stack-spot/portal-translate",
@@ -1,4 +1,4 @@
1
- import { aiClient, StackspotAPIError } from '@stack-spot/portal-network'
1
+ import { aiClient, StackspotAPIError, StreamCanceledError } from '@stack-spot/portal-network'
2
2
  import { ChatResponse3 } from '@stack-spot/portal-network/api/ai'
3
3
  import { ChatEntry, KnowledgeSource, TextChatEntry } from '../state/ChatEntry'
4
4
  import { ChatState } from '../state/ChatState'
@@ -63,8 +63,20 @@ export async function sendMessageInterceptor(entry: ChatEntry, chat: ChatState,
63
63
  }
64
64
  botEntry.setValue(createEntryValueFromChatResponse(value, knowledgeSources, chat.get('agent')))
65
65
  })
66
+ let finalValue: Partial<ChatResponse3> | undefined
66
67
  try {
67
- const finalValue = await stream.getValue()
68
+ finalValue = await stream.getValue()
69
+ } catch (error: any) {
70
+ if (error instanceof StreamCanceledError) {
71
+ finalValue = stream.getPartialValue()
72
+ } else {
73
+ botEntry.setValue({
74
+ ...botEntry.getValue(),
75
+ error: error instanceof StackspotAPIError ? error.translate() : (error.message ?? `${error}`),
76
+ })
77
+ }
78
+ }
79
+ if (finalValue?.answer) {
68
80
  botEntry.setValue(createEntryValueFromChatResponse(finalValue, knowledgeSources, chat.get('agent'), true))
69
81
  aiClient.chat.invalidate({ conversationId: chat.id })
70
82
  if (isFirstMessage) {
@@ -84,11 +96,6 @@ export async function sendMessageInterceptor(entry: ChatEntry, chat: ChatState,
84
96
  // this makes sure to update the chat history
85
97
  aiClient.chats.invalidate()
86
98
  }
87
- } catch (error: any) {
88
- botEntry.setValue({
89
- ...botEntry.getValue(),
90
- error: error instanceof StackspotAPIError ? error.translate() : (error.message ?? `${error}`),
91
- })
92
99
  }
93
100
  chat.set('isLoading', false)
94
101
  }
@@ -28,6 +28,13 @@ export const AdaptiveTextArea = forwardRef<HTMLTextAreaElement, Props>((
28
28
  const localRef = useRef<HTMLTextAreaElement>(null)
29
29
  const ref = externalRef as React.RefObject<HTMLTextAreaElement> ?? localRef
30
30
 
31
+ const handleIncreaseSize = (newHeight: number) => {
32
+ onIncreaseSize?.()
33
+ if (maxHeight && newHeight > maxHeight && ref.current) {
34
+ ref.current.style.overflowY = 'auto'
35
+ }
36
+ }
37
+
31
38
  useEffect(() => {
32
39
  if (!ref.current) return
33
40
  const height = ref.current.clientHeight
@@ -36,7 +43,7 @@ export const AdaptiveTextArea = forwardRef<HTMLTextAreaElement, Props>((
36
43
  ref.current.style.overflowY = 'hidden'
37
44
  if (height < scroll) {
38
45
  ref.current.style.height = `${scroll}px`
39
- onIncreaseSize?.()
46
+ handleIncreaseSize(scroll)
40
47
  }
41
48
  } else if (maxHeight) {
42
49
  ref.current.style.overflowY = 'auto'
@@ -0,0 +1,245 @@
1
+ import { IconBox, Image, Text } from '@citric/core'
2
+ import { Agent, ExternalLink } from '@citric/icons'
3
+ import { IconButton } from '@citric/ui'
4
+ import { useKeyboardControls } from '@stack-spot/portal-components'
5
+ import { VisibilityLevelEnum } from '@stack-spot/portal-network/api/ai'
6
+ import { Dictionary, useTranslate } from '@stack-spot/portal-translate'
7
+ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
8
+ import { useCurrentChatState } from '../../context/hooks'
9
+ import { getUrlToStackSpotAI } from '../../utils/url'
10
+ import { Fading } from '../Fading'
11
+ import { FallbackBoundary } from '../FallbackBoundary'
12
+ import { SelectorBox } from './styled'
13
+
14
+ const sections = [undefined, 'personal', 'workspace', 'account', 'shared'] as const
15
+
16
+ type SelectorShortcut = '/' | '@'
17
+
18
+ interface Item {
19
+ id: string,
20
+ slug: string,
21
+ description: string,
22
+ visibility_level: VisibilityLevelEnum,
23
+ }
24
+
25
+ interface ListProps<T> {
26
+ filter?: string,
27
+ visibility?: VisibilityLevelEnum,
28
+ selectorConfig: SelectorConfig<T>,
29
+ onSelect: (item: T) => void,
30
+ }
31
+
32
+ interface ListItemProps<T> extends ListProps<T> {
33
+ item: T,
34
+ }
35
+
36
+ interface ContentProps<T> {
37
+ filter?: string,
38
+ onClose: () => void,
39
+ inputRef: React.RefObject<HTMLTextAreaElement | HTMLInputElement>,
40
+ selectorConfig: SelectorConfig<T>,
41
+ }
42
+
43
+ interface SelectorConfig<T> {
44
+ resourceName: string,
45
+ shortcut: SelectorShortcut,
46
+ icon: React.ReactElement,
47
+ regex: RegExp,
48
+ url: string,
49
+ imageProp?: keyof T,
50
+ data: () => T[],
51
+ isEnabled: boolean,
52
+ onSelect: (item: T) => void,
53
+ }
54
+
55
+ interface SelectorProps<T> {
56
+ selectorConfig: SelectorConfig<T>,
57
+ inputRef: React.RefObject<HTMLTextAreaElement | HTMLInputElement>,
58
+ }
59
+
60
+ const ListItem = <T extends Item>({ item, selectorConfig, onSelect }: ListItemProps<T>) => {
61
+ const t = useTranslate(dictionary)
62
+ const { shortcut, url } = selectorConfig
63
+ const hasImage = !!selectorConfig.imageProp
64
+ const image = item[selectorConfig.imageProp!] as string
65
+ const linkTitle = t.open.replace('%s', item.slug)
66
+
67
+ return (
68
+ <li>
69
+ {!!hasImage && image ? <Image width="32" height="32" radius="full" src={image} /> : <IconBox size="md"><Agent /></IconBox>}
70
+ <button
71
+ className="selector"
72
+ onClick={() => onSelect(item)}
73
+ onKeyDown={(e) => e.key === 'Enter' && e.preventDefault()}
74
+ onFocus={(e) => e.target.closest('li')?.classList.add('focus')}
75
+ onBlur={(e) => e.target.closest('li')?.classList.remove('focus')}
76
+ >
77
+ <p className="selector-title">{!hasImage ? shortcut : ''}{item.slug}</p>
78
+ <p className="selector-description">{item.description}</p>
79
+ </button>
80
+ <IconButton as="a" title={linkTitle} aria-label={linkTitle} href={`${getUrlToStackSpotAI()}${url}${item.slug}`} target="_blank">
81
+ <ExternalLink />
82
+ </IconButton>
83
+ </li>
84
+ )
85
+ }
86
+
87
+ const List = <T extends Item>({ selectorConfig, filter, visibility, onSelect }: ListProps<T>) => {
88
+ const t = useTranslate(dictionary)
89
+ const items = selectorConfig.data()
90
+ const filtered = useMemo(() => {
91
+ if (!filter && !visibility) return items
92
+ const lowerFilter = filter?.toLowerCase() ?? ''
93
+
94
+ return items.filter(item =>
95
+ item.slug.toLocaleLowerCase().startsWith(lowerFilter) && (!visibility || item?.visibility_level?.toLowerCase() === visibility),
96
+ )
97
+ }, [filter, items, visibility])
98
+
99
+ if (!items.length) return <Text className="empty" colorScheme="light.700">{t.noData.replace('%', selectorConfig.resourceName)}</Text>
100
+ if (!filtered.length)
101
+ <Text className="empty" colorScheme="light.700">{t.noResults.replace('%', selectorConfig.resourceName)}</Text>
102
+
103
+ return (
104
+ <ul className="selector-list">
105
+ {filtered.map((item) => (
106
+ <ListItem key={item.id} item={item} selectorConfig={selectorConfig} onSelect={onSelect} />
107
+ ))}
108
+ </ul>
109
+ )
110
+ }
111
+
112
+ const SelectorContent = ({ filter, onClose, selectorConfig }: ContentProps<any>) => {
113
+ const t = useTranslate(dictionary)
114
+ const ref = useRef<HTMLDivElement>(null)
115
+ const [visibility, setVisibility] = useState<VisibilityLevelEnum | undefined>()
116
+ const { resourceName, icon, onSelect } = selectorConfig
117
+ const onSelectItem = useCallback((slug: string) => {
118
+ onSelect(slug)
119
+ onClose()
120
+ }, [])
121
+
122
+ useKeyboardControls({
123
+ querySelectors: '.tabs button, button.selector',
124
+ disableTabBehavior: true,
125
+ onPressEscape: onClose,
126
+ onPressArrowLeft: () => (ref.current?.querySelector('.tabs button.active') as HTMLElement)?.focus(),
127
+ onPressArrowRight: () => (ref.current?.querySelector('button.selector') as HTMLElement)?.focus(),
128
+ ref,
129
+ }, [])
130
+
131
+ function createSectionItem(action: VisibilityLevelEnum | undefined) {
132
+ return (
133
+ <li key={action ?? 'all'}>
134
+ <button className={visibility === action ? 'active' : ''} onFocus={() => setVisibility(action)}>
135
+ {t[action || 'all']}
136
+ </button>
137
+ </li>
138
+ )
139
+ }
140
+
141
+ return (
142
+ <div ref={ref}>
143
+ <header>
144
+ <IconBox>{icon}</IconBox>
145
+ <Text as="h3" className="uppercase"> {resourceName} </Text>
146
+ </header>
147
+ <div className="body">
148
+ <ul className="tabs">{sections.map(createSectionItem)}</ul>
149
+ <FallbackBoundary message={t.error.replace('%s', selectorConfig.resourceName)} mini>
150
+ <List filter={filter} visibility={visibility} selectorConfig={selectorConfig} onSelect={onSelectItem} />
151
+ </FallbackBoundary>
152
+ </div>
153
+ </div>
154
+ )
155
+ }
156
+
157
+ export const Selector = <T, >({ inputRef, selectorConfig }: SelectorProps<T>) => {
158
+ const { shortcut, regex, isEnabled } = selectorConfig
159
+ const [isClosed, setClosed] = useState(false)
160
+ const selectorRef = useRef<HTMLDivElement>(null)
161
+ const value = useCurrentChatState('nextMessage') ?? ''
162
+
163
+ const filter = useMemo(() => value === shortcut || regex.test(value) ? value.substring(1) : undefined, [value])
164
+ const shouldRender = isEnabled && filter !== undefined && !isClosed
165
+
166
+ // Resets the closed state whenever the message input is cleared
167
+ useEffect(() => {
168
+ if (!value) setClosed(false)
169
+ }, [value])
170
+
171
+ // Creates the following behavior while the user types in the message input:
172
+ // auto-complete on tab; move focus to the resource panel on press up or down; and close the resource panel on esc.
173
+ useEffect(() => {
174
+ function getFirst() {
175
+ return selectorRef.current?.querySelector('.selector') as HTMLElement | null
176
+ }
177
+
178
+ function onKeyDown(event: Event) {
179
+ const key = (event as KeyboardEvent).key
180
+ if (!selectorRef.current) return
181
+ if (key === 'Tab') {
182
+ getFirst()?.click()
183
+ event.preventDefault()
184
+ } else if (key === 'ArrowDown' || key === 'ArrowUp') {
185
+ getFirst()?.focus()
186
+ event.preventDefault()
187
+ }
188
+ if (key === 'Escape') {
189
+ setClosed(true)
190
+ }
191
+ }
192
+
193
+ inputRef.current?.addEventListener('keydown', onKeyDown)
194
+ return () => inputRef.current?.removeEventListener('keydown', onKeyDown)
195
+ }, [])
196
+
197
+ // Closes the panel when the user clicks outside the panel or the message input.
198
+ useEffect(() => {
199
+ if (!shouldRender) return
200
+ function onClickOut(e: Event) {
201
+ const target = e.target as HTMLElement | null
202
+ if (!selectorRef.current?.contains(target) && !inputRef.current?.contains(target)) setClosed(true)
203
+ }
204
+ document.addEventListener('click', onClickOut)
205
+ return () => document.removeEventListener('click', onClickOut)
206
+ }, [shouldRender])
207
+
208
+ return (
209
+ <SelectorBox>
210
+ <Fading visible={shouldRender} ref={selectorRef} className="box-selector">
211
+ <SelectorContent
212
+ selectorConfig={selectorConfig}
213
+ filter={filter}
214
+ onClose={() => setClosed(true)}
215
+ inputRef={inputRef}
216
+ />
217
+ </Fading>
218
+ </SelectorBox>
219
+ )
220
+ }
221
+
222
+ const dictionary = {
223
+ en: {
224
+ all: 'All',
225
+ personal: 'Personal',
226
+ account: 'Account',
227
+ shared: 'Shared',
228
+ workspace: 'Workspace',
229
+ error: 'Could not load the %ss.',
230
+ noData: 'You don\'t have any %s yet.',
231
+ noResults: 'There are no %ss to show here.',
232
+ open: 'Open this %s settings in a new tab.',
233
+ },
234
+ pt: {
235
+ all: 'Todos',
236
+ personal: 'Pessoal',
237
+ account: 'Conta',
238
+ shared: 'Compartilhado',
239
+ workspace: 'Workspace',
240
+ error: 'Não foi possível carregar os %ss.',
241
+ noData: 'Você ainda não possui %ss.',
242
+ noResults: 'Não há %ss para mostrar aqui.',
243
+ open: 'Abra as configurações deste %s em uma nova aba.',
244
+ },
245
+ } satisfies Dictionary