@eeacms/volto-eea-chatbot 4.1.1 → 4.1.2

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.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,18 @@ All notable changes to this project will be documented in this file. Dates are d
4
4
 
5
5
  Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
6
6
 
7
+ ### [4.1.2](https://github.com/eea/volto-eea-chatbot/compare/4.1.1...4.1.2) - 17 September 2026
8
+
9
+ #### :rocket: New Features
10
+
11
+ - feat: make floating pill the default stop button mode [Tiberiu Ichim - [`c7ce2f0`](https://github.com/eea/volto-eea-chatbot/commit/c7ce2f0eb7e0e9fd843eceadf12f5555b807cc1a)]
12
+ - feat: support stop button modes (input, floating pill, message loader) with pulse animation [Tiberiu Ichim - [`fb29273`](https://github.com/eea/volto-eea-chatbot/commit/fb2927319872a1e39f1fdc0557f1b290d76c42dd)]
13
+ - feat: add stop generation button with block toggle option [Tiberiu Ichim - [`b964ae3`](https://github.com/eea/volto-eea-chatbot/commit/b964ae3dce9abb7f4dd8c4acf2c1374bd5004358)]
14
+
15
+ #### :bug: Bug Fixes
16
+
17
+ - fix: avoid duplicate stop buttons when using message_loader option [Tiberiu Ichim - [`4f0c854`](https://github.com/eea/volto-eea-chatbot/commit/4f0c854bef40172b754db0ca46405426f1a2fd60)]
18
+
7
19
  ### [4.1.1](https://github.com/eea/volto-eea-chatbot/compare/4.1.0...4.1.1) - 14 September 2026
8
20
 
9
21
  ### [4.1.0](https://github.com/eea/volto-eea-chatbot/compare/4.0.0...4.1.0) - 31 August 2026
@@ -46,6 +46,18 @@ msgstr ""
46
46
  msgid "Invalid assistant page URL."
47
47
  msgstr ""
48
48
 
49
+ #. Default: "Send"
50
+ #: ChatBlock/components/AutoResizeTextarea
51
+ msgid "Send"
52
+ msgstr ""
53
+
54
+ #. Default: "Stop generating"
55
+ #: ChatBlock/chat/AIMessage
56
+ #: ChatBlock/chat/ChatWindow
57
+ #: ChatBlock/components/AutoResizeTextarea
58
+ msgid "Stop generating"
59
+ msgstr ""
60
+
49
61
  #. Default: "Submit question"
50
62
  #: AISearchInput/components/SearchInput
51
63
  #: AISearchInput/messages
@@ -46,6 +46,18 @@ msgstr ""
46
46
  msgid "Invalid assistant page URL."
47
47
  msgstr ""
48
48
 
49
+ #. Default: "Send"
50
+ #: ChatBlock/components/AutoResizeTextarea
51
+ msgid "Send"
52
+ msgstr ""
53
+
54
+ #. Default: "Stop generating"
55
+ #: ChatBlock/chat/AIMessage
56
+ #: ChatBlock/chat/ChatWindow
57
+ #: ChatBlock/components/AutoResizeTextarea
58
+ msgid "Stop generating"
59
+ msgstr ""
60
+
49
61
  #. Default: "Submit question"
50
62
  #: AISearchInput/components/SearchInput
51
63
  #: AISearchInput/messages
@@ -46,6 +46,18 @@ msgstr ""
46
46
  msgid "Invalid assistant page URL."
47
47
  msgstr ""
48
48
 
49
+ #. Default: "Send"
50
+ #: ChatBlock/components/AutoResizeTextarea
51
+ msgid "Send"
52
+ msgstr ""
53
+
54
+ #. Default: "Stop generating"
55
+ #: ChatBlock/chat/AIMessage
56
+ #: ChatBlock/chat/ChatWindow
57
+ #: ChatBlock/components/AutoResizeTextarea
58
+ msgid "Stop generating"
59
+ msgstr ""
60
+
49
61
  #. Default: "Submit question"
50
62
  #: AISearchInput/components/SearchInput
51
63
  #: AISearchInput/messages
@@ -46,6 +46,18 @@ msgstr ""
46
46
  msgid "Invalid assistant page URL."
47
47
  msgstr ""
48
48
 
49
+ #. Default: "Send"
50
+ #: ChatBlock/components/AutoResizeTextarea
51
+ msgid "Send"
52
+ msgstr ""
53
+
54
+ #. Default: "Stop generating"
55
+ #: ChatBlock/chat/AIMessage
56
+ #: ChatBlock/chat/ChatWindow
57
+ #: ChatBlock/components/AutoResizeTextarea
58
+ msgid "Stop generating"
59
+ msgstr ""
60
+
49
61
  #. Default: "Submit question"
50
62
  #: AISearchInput/components/SearchInput
51
63
  #: AISearchInput/messages
package/locales/volto.pot CHANGED
@@ -1,7 +1,7 @@
1
1
  msgid ""
2
2
  msgstr ""
3
3
  "Project-Id-Version: Plone\n"
4
- "POT-Creation-Date: 2026-07-07T08:22:35.802Z\n"
4
+ "POT-Creation-Date: 2026-09-17T10:07:41.673Z\n"
5
5
  "Last-Translator: Plone i18n <plone-i18n@lists.sourceforge.net>\n"
6
6
  "Language-Team: Plone i18n <plone-i18n@lists.sourceforge.net>\n"
7
7
  "Content-Type: text/plain; charset=utf-8\n"
@@ -48,6 +48,18 @@ msgstr ""
48
48
  msgid "Invalid assistant page URL."
49
49
  msgstr ""
50
50
 
51
+ #. Default: "Send"
52
+ #: ChatBlock/components/AutoResizeTextarea
53
+ msgid "Send"
54
+ msgstr ""
55
+
56
+ #. Default: "Stop generating"
57
+ #: ChatBlock/chat/AIMessage
58
+ #: ChatBlock/chat/ChatWindow
59
+ #: ChatBlock/components/AutoResizeTextarea
60
+ msgid "Stop generating"
61
+ msgstr ""
62
+
51
63
  #. Default: "Submit question"
52
64
  #: AISearchInput/components/SearchInput
53
65
  #: AISearchInput/messages
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eeacms/volto-eea-chatbot",
3
- "version": "4.1.1",
3
+ "version": "4.1.2",
4
4
  "description": "@eeacms/volto-eea-chatbot: Volto add-on",
5
5
  "main": "src/index.js",
6
6
  "author": "European Environment Agency: IDM2 A-Team",
@@ -23,6 +23,9 @@ import { addCitations } from '@eeacms/volto-eea-chatbot/ChatBlock/utils/citation
23
23
  import SVGIcon from '@eeacms/volto-eea-chatbot/ChatBlock/components/Icon';
24
24
  import BotIcon from '@eeacms/volto-eea-chatbot/icons/bot.svg';
25
25
  import ClearIcon from '@eeacms/volto-eea-chatbot/icons/clear.svg';
26
+ import StopIcon from '@eeacms/volto-eea-chatbot/icons/stop.svg';
27
+ import { trackEvent } from '@eeacms/volto-matomo/utils';
28
+ import { FormattedMessage } from 'react-intl';
26
29
 
27
30
  // Lazy load heavy components
28
31
  const SourceDetails: any = loadable(
@@ -214,6 +217,8 @@ export function AIMessage({
214
217
  extraRemarkPlugins,
215
218
  extraMarkdownComponents,
216
219
  extraRehypePlugins,
220
+ stopButton,
221
+ onCancel,
217
222
  }: ChatMessageProps) {
218
223
  const [allToolsDisplayed, setAllToolsDisplayed] = useState(false);
219
224
  const [messageDisplayed, setMessageDisplayed] = useState(false);
@@ -470,6 +475,42 @@ export function AIMessage({
470
475
  </RendererComponent>
471
476
  </div>
472
477
  ))}
478
+
479
+ {stopButton === 'message_loader' &&
480
+ Boolean(message.isFinalMessageComing) &&
481
+ isLoading &&
482
+ isLastMessage &&
483
+ onCancel && (
484
+ <div className="inline-message-stop">
485
+ <Button
486
+ className="inline-message-stop-btn"
487
+ type="button"
488
+ aria-label="Stop generating"
489
+ onClick={() => {
490
+ if (enableMatomoTracking) {
491
+ trackEvent({
492
+ category: persona ? `Chatbot - ${persona}` : 'Chatbot',
493
+ action: 'Chatbot: Stop generating',
494
+ name: 'Message generation stopped',
495
+ });
496
+ }
497
+ onCancel();
498
+ }}
499
+ >
500
+ <div className="stop-btn-inner">
501
+ <div className="stop-icon-circle">
502
+ <SVGIcon name={StopIcon} size={8} color="#fff" />
503
+ </div>
504
+ <span>
505
+ <FormattedMessage
506
+ id="Stop generating"
507
+ defaultMessage="Stop generating"
508
+ />
509
+ </span>
510
+ </div>
511
+ </Button>
512
+ </div>
513
+ )}
473
514
  </div>
474
515
 
475
516
  {/* Total fail message */}
@@ -10,6 +10,7 @@ import type { Persona } from '@eeacms/volto-eea-chatbot/ChatBlock/types/interfac
10
10
  import { Button, Form, Segment, Checkbox } from 'semantic-ui-react';
11
11
  import { injectLazyLibs } from '@plone/volto/helpers/Loadable';
12
12
  import { trackEvent } from '@eeacms/volto-matomo/utils';
13
+ import { FormattedMessage } from 'react-intl';
13
14
 
14
15
  import { ChatMessage } from '.';
15
16
  import { PacketType } from '@eeacms/volto-eea-chatbot/ChatBlock/types/streamingModels';
@@ -19,6 +20,7 @@ import EmptyState from '@eeacms/volto-eea-chatbot/ChatBlock/components/EmptyStat
19
20
  import { useChatController } from '@eeacms/volto-eea-chatbot/ChatBlock/hooks';
20
21
  import SVGIcon from '@eeacms/volto-eea-chatbot/ChatBlock/components/Icon';
21
22
  import PenIcon from '@eeacms/volto-eea-chatbot/icons/square-pen.svg';
23
+ import StopIcon from '@eeacms/volto-eea-chatbot/icons/stop.svg';
22
24
 
23
25
  import '@eeacms/volto-eea-chatbot/ChatBlock/style.less';
24
26
 
@@ -33,6 +35,8 @@ interface ChatWindowProps {
33
35
  qgenAsistantId?: number;
34
36
  enableQgen?: boolean;
35
37
  enableFeedback?: boolean;
38
+ stopButton?: 'disabled' | 'input' | 'floating' | 'message_loader';
39
+ enableStopButton?: boolean;
36
40
  scrollToInput?: boolean;
37
41
  feedbackReasons?: string[];
38
42
  qualityCheck?: string;
@@ -74,6 +78,8 @@ function ChatWindow({
74
78
  qgenAsistantId,
75
79
  enableQgen,
76
80
  enableFeedback = true,
81
+ stopButton,
82
+ enableStopButton = true,
77
83
  scrollToInput,
78
84
  feedbackReasons,
79
85
  qualityCheck = 'disabled',
@@ -97,6 +103,14 @@ function ChatWindow({
97
103
  extraMarkdownComponents,
98
104
  extraRehypePlugins,
99
105
  } = data;
106
+
107
+ const stopButtonSetting:
108
+ | 'disabled'
109
+ | 'input'
110
+ | 'floating'
111
+ | 'message_loader' =
112
+ stopButton || (enableStopButton === false ? 'disabled' : 'floating');
113
+
100
114
  const [qualityCheckEnabled, setQualityCheckEnabled] = useState(
101
115
  onDemandInputToggle ?? true,
102
116
  );
@@ -123,6 +137,7 @@ function ChatWindow({
123
137
  isStreaming,
124
138
  isFetchingRelatedQuestions,
125
139
  clearChat,
140
+ cancelStreaming,
126
141
  setIsDeepResearchEnabled,
127
142
  isDeepResearchEnabled,
128
143
  } = useChatController({
@@ -249,6 +264,8 @@ function ChatWindow({
249
264
  extraRemarkPlugins={extraRemarkPlugins}
250
265
  extraMarkdownComponents={extraMarkdownComponents}
251
266
  extraRehypePlugins={extraRehypePlugins}
267
+ stopButton={stopButtonSetting}
268
+ onCancel={cancelStreaming}
252
269
  />
253
270
  </React.Fragment>
254
271
  ))}
@@ -261,6 +278,43 @@ function ChatWindow({
261
278
  <div className="comment-content">
262
279
  <div className="loader-container">
263
280
  <div className="loader" />
281
+ {stopButtonSetting === 'message_loader' && (
282
+ <div className="message-loader-stop">
283
+ <Button
284
+ className="message-loader-stop-btn"
285
+ type="button"
286
+ aria-label="Stop generating"
287
+ onClick={() => {
288
+ if (enableMatomoTracking) {
289
+ trackEvent({
290
+ category: persona?.name
291
+ ? `Chatbot - ${persona.name}`
292
+ : 'Chatbot',
293
+ action: 'Chatbot: Stop generating',
294
+ name: 'Message generation stopped',
295
+ });
296
+ }
297
+ cancelStreaming();
298
+ }}
299
+ >
300
+ <div className="stop-btn-inner">
301
+ <div className="stop-icon-circle">
302
+ <SVGIcon
303
+ name={StopIcon}
304
+ size={8}
305
+ color="#fff"
306
+ />
307
+ </div>
308
+ <span>
309
+ <FormattedMessage
310
+ id="Stop generating"
311
+ defaultMessage="Stop generating"
312
+ />
313
+ </span>
314
+ </div>
315
+ </Button>
316
+ </div>
317
+ )}
264
318
  </div>
265
319
  </div>
266
320
  </div>
@@ -271,6 +325,39 @@ function ChatWindow({
271
325
  </div>
272
326
 
273
327
  <div className="chat-form">
328
+ {stopButtonSetting === 'floating' && isStreaming && (
329
+ <div className="chat-floating-stop">
330
+ <Button
331
+ className="floating-stop-btn"
332
+ type="button"
333
+ aria-label="Stop generating"
334
+ onClick={() => {
335
+ if (enableMatomoTracking) {
336
+ trackEvent({
337
+ category: persona?.name
338
+ ? `Chatbot - ${persona.name}`
339
+ : 'Chatbot',
340
+ action: 'Chatbot: Stop generating',
341
+ name: 'Message generation stopped',
342
+ });
343
+ }
344
+ cancelStreaming();
345
+ }}
346
+ >
347
+ <div className="stop-btn-inner">
348
+ <div className="stop-icon-circle">
349
+ <SVGIcon name={StopIcon} size={8} color="#fff" />
350
+ </div>
351
+ <span>
352
+ <FormattedMessage
353
+ id="Stop generating"
354
+ defaultMessage="Stop generating"
355
+ />
356
+ </span>
357
+ </div>
358
+ </Button>
359
+ </div>
360
+ )}
274
361
  {/* @ts-ignore */}
275
362
  <Form>
276
363
  <div className="textarea-wrapper">
@@ -283,6 +370,8 @@ function ChatWindow({
283
370
  messages.length > 0 ? 'Ask follow-up...' : placeholderPrompt
284
371
  }
285
372
  isStreaming={isStreaming}
373
+ stopButton={stopButtonSetting}
374
+ onCancel={cancelStreaming}
286
375
  enableMatomoTracking={enableMatomoTracking}
287
376
  persona={persona}
288
377
  onSubmit={onSubmit}
@@ -1,14 +1,36 @@
1
1
  import React from 'react';
2
+ import { useIntl, defineMessages } from 'react-intl';
2
3
  import { Button } from 'semantic-ui-react';
3
4
  import { trackEvent } from '@eeacms/volto-matomo/utils';
4
5
  import TextareaAutosize from 'react-textarea-autosize';
5
6
 
6
7
  import SVGIcon from './Icon';
7
8
  import SendIcon from '@eeacms/volto-eea-chatbot/icons/send.svg';
9
+ import StopIcon from '@eeacms/volto-eea-chatbot/icons/stop.svg';
10
+
11
+ const messages = defineMessages({
12
+ send: {
13
+ id: 'Send',
14
+ defaultMessage: 'Send',
15
+ },
16
+ stopGenerating: {
17
+ id: 'Stop generating',
18
+ defaultMessage: 'Stop generating',
19
+ },
20
+ });
8
21
 
9
22
  export default React.forwardRef(function AutoResizeTextarea(props, ref) {
10
- const { onSubmit, isStreaming, enableMatomoTracking, persona, ...rest } =
11
- props;
23
+ const {
24
+ onSubmit,
25
+ isStreaming,
26
+ stopButton,
27
+ enableStopButton = true,
28
+ onCancel,
29
+ enableMatomoTracking,
30
+ persona,
31
+ ...rest
32
+ } = props;
33
+ const intl = useIntl();
12
34
  const [input, setInput] = React.useState('');
13
35
 
14
36
  const handleSubmit = (e) => {
@@ -27,6 +49,24 @@ export default React.forwardRef(function AutoResizeTextarea(props, ref) {
27
49
  }
28
50
  };
29
51
 
52
+ const handleCancel = (e) => {
53
+ e.preventDefault();
54
+ if (enableMatomoTracking) {
55
+ trackEvent({
56
+ category: persona?.name ? `Chatbot - ${persona.name}` : 'Chatbot',
57
+ action: 'Chatbot: Stop generating',
58
+ name: 'Message generation stopped',
59
+ });
60
+ }
61
+ if (onCancel) {
62
+ onCancel();
63
+ }
64
+ };
65
+
66
+ const resolvedStopButton =
67
+ stopButton || (enableStopButton === false ? 'disabled' : 'floating');
68
+ const showStopButton = Boolean(resolvedStopButton === 'input' && isStreaming);
69
+
30
70
  return (
31
71
  <>
32
72
  <TextareaAutosize
@@ -46,22 +86,35 @@ export default React.forwardRef(function AutoResizeTextarea(props, ref) {
46
86
  />
47
87
 
48
88
  <div className="chat-right-actions">
49
- <Button
50
- className="submit-btn"
51
- type="submit"
52
- aria-label="Send"
53
- onKeyDown={(e) => {
54
- handleSubmit(e);
55
- }}
56
- disabled={isStreaming || input.trim() === ''}
57
- onClick={(e) => {
58
- handleSubmit(e);
59
- }}
60
- >
61
- <div className="btn-icon">
62
- <SVGIcon name={SendIcon} size="28" />
63
- </div>
64
- </Button>
89
+ {showStopButton ? (
90
+ <Button
91
+ className="stop-btn"
92
+ type="button"
93
+ aria-label={intl.formatMessage(messages.stopGenerating)}
94
+ onClick={handleCancel}
95
+ >
96
+ <div className="btn-icon">
97
+ <SVGIcon name={StopIcon} size={14} color="#fff" />
98
+ </div>
99
+ </Button>
100
+ ) : (
101
+ <Button
102
+ className="submit-btn"
103
+ type="submit"
104
+ aria-label={intl.formatMessage(messages.send)}
105
+ onKeyDown={(e) => {
106
+ handleSubmit(e);
107
+ }}
108
+ disabled={isStreaming || input.trim() === ''}
109
+ onClick={(e) => {
110
+ handleSubmit(e);
111
+ }}
112
+ >
113
+ <div className="btn-icon">
114
+ <SVGIcon name={SendIcon} size="28" />
115
+ </div>
116
+ </Button>
117
+ )}
65
118
  </div>
66
119
  </>
67
120
  );
@@ -100,6 +100,7 @@ export function ChatBlockSchema({ assistants, data }) {
100
100
  : []),
101
101
  'enableFeedback',
102
102
  ...(data.enableFeedback ? ['feedbackReasons'] : []),
103
+ 'stopButton',
103
104
  'showTools',
104
105
  'enableMatomoTracking',
105
106
  'scrollToInput',
@@ -175,6 +176,18 @@ export function ChatBlockSchema({ assistants, data }) {
175
176
  type: 'boolean',
176
177
  default: true,
177
178
  },
179
+ stopButton: {
180
+ title: 'Stop generation button',
181
+ choices: [
182
+ ['disabled', 'Disabled'],
183
+ ['input', 'Inline text input'],
184
+ ['floating', 'Floating pill'],
185
+ ['message_loader', 'Inline message loader'],
186
+ ],
187
+ default: 'floating',
188
+ description:
189
+ 'Choose where and how the stop generation button appears during response generation',
190
+ },
178
191
  qualityCheck: {
179
192
  title: 'Quality checks',
180
193
  choices: [
@@ -123,6 +123,20 @@
123
123
  }
124
124
  }
125
125
 
126
+ @keyframes stopButtonPulse {
127
+ 0% {
128
+ box-shadow: 0 0 0 0 rgba(0, 0, 0, 0.45);
129
+ }
130
+
131
+ 70% {
132
+ box-shadow: 0 0 0 7px rgba(0, 0, 0, 0);
133
+ }
134
+
135
+ 100% {
136
+ box-shadow: 0 0 0 0 rgba(0, 0, 0, 0);
137
+ }
138
+ }
139
+
126
140
  @keyframes popIn {
127
141
  0% {
128
142
  opacity: 0;
@@ -1353,6 +1367,122 @@ mark {
1353
1367
  color: @grey;
1354
1368
  }
1355
1369
  }
1370
+
1371
+ .ui.button.stop-btn {
1372
+ display: flex;
1373
+ width: 28px;
1374
+ height: 28px;
1375
+ align-items: center;
1376
+ justify-content: center;
1377
+ padding: 0;
1378
+ border-radius: 50%;
1379
+ margin: 0 0.5em 0 0.8em;
1380
+ animation: stopButtonPulse 1.8s cubic-bezier(0.66, 0, 0, 1) infinite;
1381
+ background: #000;
1382
+ cursor: pointer;
1383
+ transition:
1384
+ background-color 0.2s ease,
1385
+ transform 0.15s ease;
1386
+
1387
+ &:hover {
1388
+ background: #333;
1389
+ transform: scale(1.1);
1390
+ }
1391
+
1392
+ &:focus {
1393
+ background: #000;
1394
+ }
1395
+
1396
+ .btn-icon {
1397
+ display: flex;
1398
+ width: 100%;
1399
+ height: 100%;
1400
+ align-items: center;
1401
+ justify-content: center;
1402
+
1403
+ svg {
1404
+ width: 12px;
1405
+ height: 12px;
1406
+ color: #fff;
1407
+ fill: #fff;
1408
+ }
1409
+ }
1410
+ }
1411
+ }
1412
+
1413
+ .chat-floating-stop {
1414
+ display: flex;
1415
+ justify-content: center;
1416
+ margin-bottom: 0.75em;
1417
+ animation: fadeInScale 0.2s ease;
1418
+ }
1419
+
1420
+ .message-loader-stop {
1421
+ display: inline-flex;
1422
+ align-items: center;
1423
+ margin-left: 20px;
1424
+ }
1425
+
1426
+ .inline-message-stop {
1427
+ display: flex;
1428
+ margin-top: 12px;
1429
+ animation: fadeIn 0.2s ease;
1430
+ }
1431
+
1432
+ .ui.button.floating-stop-btn,
1433
+ .ui.button.inline-message-stop-btn,
1434
+ .ui.button.message-loader-stop-btn {
1435
+ display: inline-flex;
1436
+ align-items: center;
1437
+ justify-content: center;
1438
+ padding: 6px 14px;
1439
+ border: 1px solid #d0d5dd;
1440
+ border-radius: 20px;
1441
+ background: #fff;
1442
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
1443
+ color: #344054;
1444
+ cursor: pointer;
1445
+ font-size: 13px;
1446
+ font-weight: 500;
1447
+ line-height: 1;
1448
+ transition: all 0.2s ease;
1449
+
1450
+ &:hover {
1451
+ border-color: #98a2b3;
1452
+ background: #f9fafb;
1453
+ box-shadow: 0 4px 10px rgba(0, 0, 0, 0.12);
1454
+ color: #1d2939;
1455
+ transform: translateY(-1px);
1456
+ }
1457
+
1458
+ &:focus {
1459
+ background: #fff;
1460
+ color: #344054;
1461
+ }
1462
+
1463
+ .stop-btn-inner {
1464
+ display: inline-flex;
1465
+ align-items: center;
1466
+ gap: 8px;
1467
+ }
1468
+
1469
+ .stop-icon-circle {
1470
+ display: flex;
1471
+ width: 18px;
1472
+ height: 18px;
1473
+ align-items: center;
1474
+ justify-content: center;
1475
+ border-radius: 50%;
1476
+ animation: stopButtonPulse 1.8s cubic-bezier(0.66, 0, 0, 1) infinite;
1477
+ background: #000;
1478
+
1479
+ svg {
1480
+ width: 8px;
1481
+ height: 8px;
1482
+ color: #fff;
1483
+ fill: #fff;
1484
+ }
1485
+ }
1356
1486
  }
1357
1487
 
1358
1488
  #quality-check-toggle[disabled] ~ label {
@@ -171,6 +171,8 @@ export interface ChatMessageProps {
171
171
  extraMarkdownComponents?: any;
172
172
  /** Extra rehype plugins merged after the built-in ones. */
173
173
  extraRehypePlugins?: any[];
174
+ stopButton?: 'disabled' | 'input' | 'floating' | 'message_loader';
175
+ onCancel?: () => void;
174
176
  }
175
177
 
176
178
  export interface Persona {