@patternfly/chatbot 6.8.0-prerelease.9 → 6.9.0-prerelease.10

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 (66) hide show
  1. package/dist/cjs/Chatbot/Chatbot.d.ts +2 -0
  2. package/dist/cjs/Chatbot/Chatbot.js +2 -2
  3. package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.js +1 -1
  4. package/dist/cjs/ChatbotFooter/ChatbotFootnote.js +1 -1
  5. package/dist/cjs/ChatbotModal/ChatbotModal.js +1 -1
  6. package/dist/cjs/ChatbotWelcomePrompt/ChatbotWelcomePrompt.js +1 -1
  7. package/dist/cjs/MessageBar/MessageBar.d.ts +1 -1
  8. package/dist/cjs/MessageBar/MessageBar.js +4 -3
  9. package/dist/cjs/MessageBar/SendButton.js +2 -2
  10. package/dist/cjs/SourcesCard/SourcesCard.d.ts +2 -0
  11. package/dist/cjs/SourcesCardBase/SourcesCardBase.d.ts +2 -0
  12. package/dist/cjs/SourcesCardBase/SourcesCardBase.js +2 -2
  13. package/dist/css/main.css +199 -61
  14. package/dist/css/main.css.map +1 -1
  15. package/dist/esm/Chatbot/Chatbot.d.ts +2 -0
  16. package/dist/esm/Chatbot/Chatbot.js +3 -3
  17. package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.js +1 -1
  18. package/dist/esm/ChatbotFooter/ChatbotFootnote.js +1 -1
  19. package/dist/esm/ChatbotModal/ChatbotModal.js +1 -1
  20. package/dist/esm/ChatbotWelcomePrompt/ChatbotWelcomePrompt.js +1 -1
  21. package/dist/esm/MessageBar/MessageBar.d.ts +1 -1
  22. package/dist/esm/MessageBar/MessageBar.js +4 -3
  23. package/dist/esm/MessageBar/SendButton.js +2 -2
  24. package/dist/esm/SourcesCard/SourcesCard.d.ts +2 -0
  25. package/dist/esm/SourcesCardBase/SourcesCardBase.d.ts +2 -0
  26. package/dist/esm/SourcesCardBase/SourcesCardBase.js +2 -2
  27. package/package.json +9 -9
  28. package/patternfly-docs/content/extensions/chatbot/chatbot.md +1 -0
  29. package/patternfly-docs/content/extensions/chatbot/examples/Analytics/Analytics.md +1 -1
  30. package/patternfly-docs/content/extensions/chatbot/examples/Messages/MessageWithSources.tsx +31 -0
  31. package/patternfly-docs/content/extensions/chatbot/examples/Messages/Messages.md +2 -0
  32. package/patternfly-docs/content/extensions/chatbot/examples/demos/Canvas.md +132 -0
  33. package/patternfly-docs/content/extensions/chatbot/examples/demos/Canvas.tsx +615 -0
  34. package/patternfly-docs/content/extensions/chatbot/examples/demos/CanvasWithDockedNav.tsx +697 -0
  35. package/patternfly-docs/content/extensions/chatbot/examples/demos/Chatbot.md +39 -5
  36. package/patternfly-docs/content/extensions/chatbot/examples/demos/FullscreenDockedNav.tsx +385 -0
  37. package/patternfly-docs/patternfly-docs.config.js +1 -1
  38. package/src/Canvas/Canvas.scss +150 -0
  39. package/src/Chatbot/Chatbot.scss +13 -1
  40. package/src/Chatbot/Chatbot.test.tsx +11 -0
  41. package/src/Chatbot/Chatbot.tsx +5 -1
  42. package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.scss +91 -77
  43. package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.test.tsx +32 -13
  44. package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.tsx +1 -0
  45. package/src/ChatbotFooter/ChatbotFooternote.test.tsx +50 -0
  46. package/src/ChatbotFooter/ChatbotFootnote.tsx +1 -1
  47. package/src/ChatbotModal/ChatbotModal.tsx +1 -0
  48. package/src/ChatbotToggle/ChatbotToggle.scss +3 -2
  49. package/src/ChatbotToggle/ChatbotToggle.test.tsx +4 -0
  50. package/src/ChatbotWelcomePrompt/ChatbotWelcomePrompt.scss +9 -0
  51. package/src/ChatbotWelcomePrompt/ChatbotWelcomePrompt.tsx +4 -2
  52. package/src/FileDetails/__snapshots__/FileDetails.test.tsx.snap +2 -6
  53. package/src/FileDetailsLabel/__snapshots__/FileDetailsLabel.test.tsx.snap +2 -6
  54. package/src/Message/Message.scss +9 -0
  55. package/src/MessageBar/AttachButton.scss +4 -21
  56. package/src/MessageBar/MessageBar.scss +2 -2
  57. package/src/MessageBar/MessageBar.test.tsx +20 -0
  58. package/src/MessageBar/MessageBar.tsx +7 -3
  59. package/src/MessageBar/SendButton.scss +1 -32
  60. package/src/MessageBar/SendButton.tsx +2 -2
  61. package/src/SourcesCard/SourcesCard.scss +6 -0
  62. package/src/SourcesCard/SourcesCard.test.tsx +16 -0
  63. package/src/SourcesCard/SourcesCard.tsx +2 -0
  64. package/src/SourcesCardBase/SourcesCardBase.test.tsx +62 -0
  65. package/src/SourcesCardBase/SourcesCardBase.tsx +8 -0
  66. package/src/main.scss +1 -0
@@ -0,0 +1,615 @@
1
+ import { useState, useRef, useCallback, useEffect, FunctionComponent, ReactNode } from 'react';
2
+ import {
3
+ Brand,
4
+ Button,
5
+ Divider,
6
+ Drawer,
7
+ DrawerActions,
8
+ DrawerContent,
9
+ DrawerContentBody,
10
+ DrawerHead,
11
+ DrawerPanelContent,
12
+ DropdownItem,
13
+ DropdownList,
14
+ Flex,
15
+ FlexItem,
16
+ Label,
17
+ MenuToggle,
18
+ Popover,
19
+ Select,
20
+ SelectList,
21
+ SelectOption,
22
+ Title,
23
+ Tooltip
24
+ } from '@patternfly/react-core';
25
+ import { CodeEditor, CodeEditorControl, Language } from '@patternfly/react-code-editor';
26
+ import Chatbot, { ChatbotDisplayMode } from '@patternfly/chatbot/dist/dynamic/Chatbot';
27
+ import ChatbotContent from '@patternfly/chatbot/dist/dynamic/ChatbotContent';
28
+ import ChatbotFooter, { ChatbotFootnote } from '@patternfly/chatbot/dist/dynamic/ChatbotFooter';
29
+ import MessageBar from '@patternfly/chatbot/dist/dynamic/MessageBar';
30
+ import MessageBox from '@patternfly/chatbot/dist/dynamic/MessageBox';
31
+ import Message, { MessageProps } from '@patternfly/chatbot/dist/dynamic/Message';
32
+ import ChatbotConversationHistoryNav, {
33
+ Conversation
34
+ } from '@patternfly/chatbot/dist/dynamic/ChatbotConversationHistoryNav';
35
+ import ChatbotHeader, {
36
+ ChatbotHeaderActions,
37
+ ChatbotHeaderCloseButton,
38
+ ChatbotHeaderMain,
39
+ ChatbotHeaderMenu,
40
+ ChatbotHeaderTitle
41
+ } from '@patternfly/chatbot/dist/dynamic/ChatbotHeader';
42
+ import { RhUiAiInfoIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-ai-info-icon';
43
+ import { RhUiImageFillIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-image-fill-icon';
44
+ import { RhUiAddIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-add-icon';
45
+ import { RhUiTaskFillIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-task-fill-icon';
46
+ import { RhUiSaveIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-save-icon';
47
+ import { RhUiRedoIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-redo-icon';
48
+ import { RhUiUndoIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-undo-icon';
49
+ import { RhUiServerUploadFillIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-server-upload-fill-icon';
50
+ import { RhUiNotificationFillIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-notification-fill-icon';
51
+ import { RhUiCalendarFillIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-calendar-fill-icon';
52
+ import { RhMicronsCloseIcon } from '@patternfly/react-icons/dist/esm/icons/rh-microns-close-icon';
53
+ import { useDropzone } from 'react-dropzone';
54
+ import PFIconLogoColor from '../UI/PF-IconLogo-Color.svg';
55
+ import PFIconLogoReverse from '../UI/PF-IconLogo-Reverse.svg';
56
+ import userAvatar from '../Messages/user_avatar.svg';
57
+ import '@patternfly/react-core/dist/styles/base.css';
58
+ import '@patternfly/chatbot/dist/css/main.css';
59
+
60
+ const sampleCode = `apiVersion: v1
61
+ kind: ConfigMap
62
+ metadata:
63
+ name: canvas-demo
64
+ data:
65
+ greeting: Hello, how can I help you today?
66
+ `;
67
+
68
+ const date = new Date();
69
+
70
+ const initialMessages: MessageProps[] = [
71
+ {
72
+ id: '1',
73
+ role: 'bot',
74
+ content: 'How can I help?',
75
+ name: 'Bot',
76
+ timestamp: date.toLocaleString()
77
+ },
78
+ {
79
+ id: '2',
80
+ role: 'user',
81
+ content: 'I want to live edit YAML code using canvas mode',
82
+ name: 'You',
83
+ avatar: userAvatar,
84
+ avatarProps: { isBordered: true },
85
+ timestamp: date.toLocaleString()
86
+ },
87
+ {
88
+ id: '3',
89
+ role: 'bot',
90
+ content: 'The canvas mode text editor is ready. Edit, share or ask me to change something for you.',
91
+ name: 'Bot',
92
+ timestamp: date.toLocaleString(),
93
+ attachments: [{ name: 'canvas mode.yaml', id: 'canvas-mode' }]
94
+ }
95
+ ];
96
+
97
+ const initialConversations = {
98
+ Today: [{ id: '1', text: 'I want to live edit YAML code using canvas mode' }],
99
+ 'This month': [
100
+ { id: '2', text: 'Enterprise Linux installation and setup' },
101
+ { id: '3', text: 'Troubleshoot system crash' }
102
+ ]
103
+ };
104
+
105
+ const footnoteProps = {
106
+ label: 'Always review AI-generated content prior to use.'
107
+ };
108
+
109
+ const modelOptions = ['GPT-4', 'GPT-3.5', 'Claude', 'Llama 2'];
110
+
111
+ interface CanvasEditor {
112
+ trigger: (source: string, handlerId: string, payload?: unknown) => void;
113
+ }
114
+
115
+ const iconLogo = (
116
+ <>
117
+ <Brand className="show-light" src={PFIconLogoColor} alt="PatternFly" />
118
+ <Brand className="show-dark" src={PFIconLogoReverse} alt="PatternFly" />
119
+ </>
120
+ );
121
+
122
+ export const Canvas: FunctionComponent = () => {
123
+ const [messages, setMessages] = useState<MessageProps[]>(initialMessages);
124
+ const [announcement, setAnnouncement] = useState<string>();
125
+ const [isSendButtonDisabled, setIsSendButtonDisabled] = useState(false);
126
+ const [isAttachMenuOpen, setIsAttachMenuOpen] = useState(false);
127
+ const [isModelSelectOpen, setIsModelSelectOpen] = useState(false);
128
+ const [selectedModel, setSelectedModel] = useState('GPT-4');
129
+ const [showCanvasLabel, setShowCanvasLabel] = useState(true);
130
+ const [isCanvasOpen, setIsCanvasOpen] = useState(true);
131
+ const [isGeneratedAiPopoverOpen, setIsGeneratedAiPopoverOpen] = useState(false);
132
+ const [isDrawerOpen, setIsDrawerOpen] = useState(false);
133
+ const [chatbotVisible, setChatbotVisible] = useState(true);
134
+ const [conversations, setConversations] = useState<Conversation[] | { [key: string]: Conversation[] }>(
135
+ initialConversations
136
+ );
137
+ const [code, setCode] = useState(sampleCode);
138
+ const scrollToBottomRef = useRef<HTMLDivElement>(null);
139
+ const historyRef = useRef<HTMLButtonElement>(null);
140
+ const editorRef = useRef<CanvasEditor>(null);
141
+ const messageInputRef = useRef<HTMLTextAreaElement>(null);
142
+ const messageActionsRef = useRef<HTMLButtonElement>(null);
143
+ const canvasCloseButtonRef = useRef<HTMLButtonElement>(null);
144
+ const canvasSectionRef = useRef<HTMLElement>(null);
145
+ const wasCanvasOpen = useRef(isCanvasOpen);
146
+ const shouldFocusCanvasCloseButton = useRef(false);
147
+ const displayMode = ChatbotDisplayMode.fullscreen;
148
+
149
+ const { open, getInputProps } = useDropzone({
150
+ multiple: true,
151
+ // eslint-disable-next-line no-console
152
+ onDropAccepted: () => console.log('fileUploaded')
153
+ });
154
+
155
+ const generateId = () => {
156
+ const id = Date.now() + Math.random();
157
+ return id.toString();
158
+ };
159
+
160
+ const handleSend = useCallback(
161
+ (input: string) => {
162
+ const date = new Date();
163
+ const newMessages: MessageProps[] = [];
164
+ messages.forEach((message) => newMessages.push(message));
165
+ newMessages.push({
166
+ avatar: userAvatar,
167
+ avatarProps: { isBordered: true },
168
+ id: generateId(),
169
+ name: 'You',
170
+ role: 'user',
171
+ content: input,
172
+ timestamp: `${date?.toLocaleDateString()} ${date?.toLocaleTimeString()}`
173
+ });
174
+ newMessages.push({
175
+ id: generateId(),
176
+ name: 'Bot',
177
+ role: 'bot',
178
+ timestamp: `${date?.toLocaleDateString()} ${date?.toLocaleTimeString()}`,
179
+ isLoading: true
180
+ });
181
+ setMessages(newMessages);
182
+ setAnnouncement(`Message from You: ${input}. Message from Bot is loading.`);
183
+ setIsSendButtonDisabled(true);
184
+
185
+ setTimeout(() => {
186
+ const loadedMessages: MessageProps[] = [];
187
+ newMessages.forEach((message) => loadedMessages.push(message));
188
+ loadedMessages.pop();
189
+ loadedMessages.push({
190
+ id: generateId(),
191
+ role: 'bot',
192
+ content: 'API response from Bot goes here',
193
+ name: 'Bot',
194
+ isLoading: false,
195
+ actions: {
196
+ // eslint-disable-next-line no-console
197
+ positive: { onClick: () => console.log('Good response') },
198
+ // eslint-disable-next-line no-console
199
+ negative: { onClick: () => console.log('Bad response') },
200
+ // eslint-disable-next-line no-console
201
+ copy: { onClick: () => console.log('Copy') },
202
+ // eslint-disable-next-line no-console
203
+ download: { onClick: () => console.log('Download') },
204
+ // eslint-disable-next-line no-console
205
+ listen: { onClick: () => console.log('Listen') }
206
+ },
207
+ timestamp: date.toLocaleString()
208
+ });
209
+ setMessages(loadedMessages);
210
+ setAnnouncement('Message from Bot: API response goes here');
211
+ setIsSendButtonDisabled(false);
212
+ }, 5000);
213
+ },
214
+ [messages]
215
+ );
216
+
217
+ useEffect(() => {
218
+ if (messages.length > 3) {
219
+ scrollToBottomRef.current?.scrollIntoView();
220
+ }
221
+ }, [messages]);
222
+
223
+ const onModelSelect = (
224
+ _event: React.MouseEvent<Element, MouseEvent> | undefined,
225
+ value: string | number | undefined
226
+ ) => {
227
+ setSelectedModel(value as string);
228
+ setIsModelSelectOpen(false);
229
+ };
230
+
231
+ const openCanvas = () => {
232
+ setShowCanvasLabel(true);
233
+ setIsCanvasOpen(true);
234
+ };
235
+
236
+ const closeCanvasMode = () => {
237
+ setShowCanvasLabel(false);
238
+ setIsCanvasOpen(false);
239
+ window.requestAnimationFrame(() => messageInputRef.current?.focus());
240
+ };
241
+
242
+ const focusCanvasSection = () => {
243
+ canvasSectionRef.current?.focus();
244
+ };
245
+
246
+ useEffect(() => {
247
+ const wasClosed = !wasCanvasOpen.current;
248
+ wasCanvasOpen.current = isCanvasOpen;
249
+
250
+ if (isCanvasOpen && wasClosed) {
251
+ shouldFocusCanvasCloseButton.current = true;
252
+ } else if (!isCanvasOpen) {
253
+ shouldFocusCanvasCloseButton.current = false;
254
+ }
255
+ }, [isCanvasOpen]);
256
+
257
+ const onCanvasExpand = () => {
258
+ if (shouldFocusCanvasCloseButton.current) {
259
+ shouldFocusCanvasCloseButton.current = false;
260
+ focusCanvasSection();
261
+ }
262
+ };
263
+
264
+ const focusMessageActions = () => {
265
+ window.requestAnimationFrame(() => messageActionsRef.current?.focus());
266
+ };
267
+
268
+ const handleGeneratedAiLabelKeyDown = (event: React.KeyboardEvent) => {
269
+ if (event.key === 'Enter' || event.key === ' ') {
270
+ event.preventDefault();
271
+ event.currentTarget.click();
272
+ }
273
+ };
274
+
275
+ const findMatchingItems = (targetValue: string) => {
276
+ let filteredConversations = Object.entries(initialConversations).reduce((acc, [key, items]) => {
277
+ const filteredItems = items.filter((item) => item.text.toLowerCase().includes(targetValue.toLowerCase()));
278
+ if (filteredItems.length > 0) {
279
+ acc[key] = filteredItems;
280
+ }
281
+ return acc;
282
+ }, {});
283
+
284
+ if (Object.keys(filteredConversations).length === 0) {
285
+ filteredConversations = [{ id: '13', noIcon: true, text: 'No results found' }];
286
+ }
287
+ return filteredConversations;
288
+ };
289
+
290
+ const attachMenuItems: ReactNode = (
291
+ <>
292
+ <DropdownList>
293
+ <DropdownItem value="Alerts" id="logs" icon={<RhUiNotificationFillIcon />}>
294
+ Alerts
295
+ </DropdownItem>
296
+ <DropdownItem value="Events" id="events" icon={<RhUiCalendarFillIcon />}>
297
+ Events
298
+ </DropdownItem>
299
+ <DropdownItem value="Logs" id="logs" icon={<RhUiTaskFillIcon />}>
300
+ Logs
301
+ </DropdownItem>
302
+ </DropdownList>
303
+ <Divider key="divider-1" />
304
+ <DropdownList>
305
+ <DropdownItem value="Upload from computer" id="upload" icon={<RhUiServerUploadFillIcon />} onClick={open}>
306
+ Upload from computer
307
+ </DropdownItem>
308
+ <DropdownItem value="canvas" id="canvas" icon={<RhUiImageFillIcon />}>
309
+ {`${isCanvasOpen ? 'Disable' : 'Enable'} canvas mode`}
310
+ </DropdownItem>
311
+ </DropdownList>
312
+ </>
313
+ );
314
+
315
+ const customControls = [
316
+ <CodeEditorControl
317
+ key="undo"
318
+ icon={<RhUiUndoIcon />}
319
+ aria-label="Undo"
320
+ tooltipProps={{
321
+ content: 'Undo',
322
+ // prevents VO announcements of both aria label and tooltip
323
+ aria: 'none'
324
+ }}
325
+ onClick={() => editorRef.current?.trigger('keyboard', 'undo', null)}
326
+ />,
327
+ <CodeEditorControl
328
+ key="redo"
329
+ icon={<RhUiRedoIcon />}
330
+ aria-label="Redo"
331
+ tooltipProps={{
332
+ content: 'Redo',
333
+ // prevents VO announcements of both aria label and tooltip
334
+ aria: 'none'
335
+ }}
336
+ onClick={() => editorRef.current?.trigger('keyboard', 'redo', null)}
337
+ />,
338
+ <CodeEditorControl
339
+ key="export"
340
+ icon={<RhUiSaveIcon />}
341
+ aria-label="Save"
342
+ tooltipProps={{
343
+ content: 'Save',
344
+ // prevents VO announcements of both aria label and tooltip
345
+ aria: 'none'
346
+ }}
347
+ onClick={(value) => {
348
+ // eslint-disable-next-line no-console
349
+ console.log('Export', value);
350
+ }}
351
+ />
352
+ ];
353
+
354
+ const panelContent = (
355
+ <DrawerPanelContent
356
+ isResizable
357
+ isGlass
358
+ defaultSize="50%"
359
+ minSize="20%"
360
+ id="chatbot-canvas-panel"
361
+ resizeAriaLabel="Resize canvas"
362
+ className="pf-chatbot__canvas-panel"
363
+ >
364
+ <section
365
+ ref={canvasSectionRef}
366
+ className="pf-chatbot__canvas-section"
367
+ tabIndex={-1}
368
+ aria-label="Canvas"
369
+ aria-labelledby="chatbot-canvas-section chatbot-canvas-title"
370
+ id="chatbot-canvas-section"
371
+ >
372
+ <DrawerHead className="pf-chatbot__canvas-head">
373
+ <Flex spaceItems={{ default: 'spaceItemsMd' }} alignItems={{ default: 'alignItemsCenter' }}>
374
+ <FlexItem>
375
+ <Title id="chatbot-canvas-title" headingLevel="h2" size="lg">
376
+ Edit code
377
+ </Title>
378
+ </FlexItem>
379
+ <FlexItem>
380
+ <Popover
381
+ headerContent={
382
+ <Flex spaceItems={{ default: 'spaceItemsSm' }}>
383
+ <FlexItem>
384
+ <RhUiAiInfoIcon aria-hidden />
385
+ </FlexItem>
386
+ <FlexItem>What is canvas mode?</FlexItem>
387
+ </Flex>
388
+ }
389
+ bodyContent={
390
+ <>
391
+ <div className="pf-v6-u-font-size-sm pf-v6-u-mb-md">
392
+ This canvas is a collaborative workspace that blends AI-generated content with manual human edits.
393
+ You can accept an AI-generated baseline and immediately mold it with direct keystrokes, creating a
394
+ seamless partnership over a shared deliverable.
395
+ </div>
396
+ <div className="pf-v6-u-font-size-xs">Always review AI-generated code prior to use.</div>
397
+ </>
398
+ }
399
+ onShow={() => setIsGeneratedAiPopoverOpen(true)}
400
+ onHide={() => setIsGeneratedAiPopoverOpen(false)}
401
+ >
402
+ <Label
403
+ isClickable
404
+ variant="outline"
405
+ icon={<RhUiAiInfoIcon aria-hidden />}
406
+ role="button"
407
+ tabIndex={0}
408
+ aria-expanded={isGeneratedAiPopoverOpen}
409
+ onKeyDown={handleGeneratedAiLabelKeyDown}
410
+ >
411
+ Generated with AI
412
+ </Label>
413
+ </Popover>
414
+ </FlexItem>
415
+ </Flex>
416
+ <DrawerActions>
417
+ <Tooltip triggerRef={canvasCloseButtonRef} content="Close canvas" position="bottom" aria="none" />
418
+ <div className="pf-v6-c-drawer__close">
419
+ <Button
420
+ ref={canvasCloseButtonRef}
421
+ variant="plain"
422
+ aria-label="Exit canvas mode"
423
+ icon={<RhMicronsCloseIcon />}
424
+ onClick={closeCanvasMode}
425
+ />
426
+ </div>
427
+ </DrawerActions>
428
+ </DrawerHead>
429
+ <div className="pf-chatbot__canvas-panel-body">
430
+ <div className="pf-chatbot__canvas-editor">
431
+ <CodeEditor
432
+ isFullHeight
433
+ isLineNumbersVisible
434
+ isLanguageLabelVisible
435
+ isCopyEnabled
436
+ isDownloadEnabled
437
+ downloadFileName="canvas-mode"
438
+ customControls={customControls}
439
+ code={code}
440
+ language={Language.yaml}
441
+ onCodeChange={setCode}
442
+ onEditorDidMount={(editor) => {
443
+ editorRef.current = editor;
444
+ }}
445
+ />
446
+ </div>
447
+ </div>
448
+ </section>
449
+ </DrawerPanelContent>
450
+ );
451
+
452
+ return (
453
+ <>
454
+ {/* This is required for react-dropzone to work in Safari and Firefox */}
455
+ <input {...getInputProps()} hidden />
456
+ <Chatbot displayMode={displayMode} isVisible={chatbotVisible}>
457
+ <div className="pf-chatbot__canvas">
458
+ <ChatbotConversationHistoryNav
459
+ displayMode={displayMode}
460
+ onDrawerToggle={() => {
461
+ setIsDrawerOpen(!isDrawerOpen);
462
+ setConversations(initialConversations);
463
+ }}
464
+ isDrawerOpen={isDrawerOpen}
465
+ setIsDrawerOpen={setIsDrawerOpen}
466
+ activeItemId="1"
467
+ conversations={conversations}
468
+ drawerCloseButtonProps={{ 'aria-label': 'Close chat history' }}
469
+ onNewChat={() => {
470
+ setIsDrawerOpen(!isDrawerOpen);
471
+ setMessages([]);
472
+ setConversations(initialConversations);
473
+ }}
474
+ handleTextInputChange={(value: string) => {
475
+ if (value === '') {
476
+ setConversations(initialConversations);
477
+ }
478
+ const newConversations: { [key: string]: Conversation[] } = findMatchingItems(value);
479
+ setConversations(newConversations);
480
+ }}
481
+ drawerContent={
482
+ <Drawer
483
+ className="pf-chatbot__canvas-drawer"
484
+ isExpanded={isCanvasOpen}
485
+ isInline
486
+ position="end"
487
+ onExpand={onCanvasExpand}
488
+ >
489
+ <DrawerContent panelContent={panelContent}>
490
+ <DrawerContentBody className="pf-chatbot__canvas-body">
491
+ <div className="pf-chatbot__canvas-column">
492
+ <ChatbotHeader>
493
+ <ChatbotHeaderMain>
494
+ <ChatbotHeaderMenu
495
+ ref={historyRef}
496
+ aria-expanded={isDrawerOpen}
497
+ onMenuToggle={() => setIsDrawerOpen(!isDrawerOpen)}
498
+ tooltipContent="Chat history"
499
+ menuAriaLabel="Chat history"
500
+ />
501
+ <ChatbotHeaderTitle>{iconLogo}</ChatbotHeaderTitle>
502
+ </ChatbotHeaderMain>
503
+ <ChatbotHeaderActions>
504
+ <ChatbotHeaderCloseButton
505
+ tooltipContent="Close ChatBot"
506
+ menuAriaLabel="Close ChatBot"
507
+ onClick={() => setChatbotVisible(false)}
508
+ />
509
+ </ChatbotHeaderActions>
510
+ </ChatbotHeader>
511
+ <ChatbotContent>
512
+ <MessageBox ariaLabel="Scrollable message log for ChatBot" announcement={announcement}>
513
+ {messages.map((message) => (
514
+ <Message
515
+ key={message.id}
516
+ {...message}
517
+ attachments={message.attachments?.map((attachment) => ({
518
+ ...attachment,
519
+ onClick: openCanvas
520
+ }))}
521
+ />
522
+ ))}
523
+ <div ref={scrollToBottomRef}></div>
524
+ </MessageBox>
525
+ </ChatbotContent>
526
+ <ChatbotFooter>
527
+ <MessageBar
528
+ onSendMessage={handleSend}
529
+ innerRef={messageInputRef}
530
+ attachButtonPosition="start"
531
+ alwayShowSendButton
532
+ isSendButtonDisabled={isSendButtonDisabled}
533
+ attachMenuProps={{
534
+ isAttachMenuOpen,
535
+ setIsAttachMenuOpen,
536
+ attachMenuItems,
537
+ onAttachMenuOnOpenChangeKeys: ['Escape', 'Tab'],
538
+ onAttachMenuSelect: (_ev, value) => {
539
+ // eslint-disable-next-line no-console
540
+ console.log('selected', value);
541
+ if (value === 'canvas') {
542
+ if (showCanvasLabel) {
543
+ closeCanvasMode();
544
+ } else {
545
+ setShowCanvasLabel(true);
546
+ setIsCanvasOpen(true);
547
+ }
548
+ }
549
+ setIsAttachMenuOpen(false);
550
+ focusMessageActions();
551
+ },
552
+ onAttachMenuToggleClick: () => setIsAttachMenuOpen(!isAttachMenuOpen)
553
+ }}
554
+ buttonProps={{
555
+ attach: {
556
+ innerRef: messageActionsRef,
557
+ icon: <RhUiAddIcon />,
558
+ tooltipContent: 'Message actions',
559
+ 'aria-label': 'Message actions'
560
+ }
561
+ }}
562
+ additionalActions={
563
+ <>
564
+ {showCanvasLabel && (
565
+ <Label
566
+ closeBtnAriaLabel="Exit canvas mode"
567
+ onClose={closeCanvasMode}
568
+ icon={<RhUiImageFillIcon />}
569
+ >
570
+ Canvas
571
+ </Label>
572
+ )}
573
+ <Select
574
+ isOpen={isModelSelectOpen}
575
+ selected={selectedModel}
576
+ shouldFocusToggleOnSelect
577
+ onSelect={onModelSelect}
578
+ onOpenChange={(isOpen) => setIsModelSelectOpen(isOpen)}
579
+ toggle={(toggleRef) => (
580
+ <MenuToggle
581
+ ref={toggleRef}
582
+ variant="plainText"
583
+ className="pf-chatbot__canvas-model-toggle"
584
+ onClick={() => setIsModelSelectOpen(!isModelSelectOpen)}
585
+ isExpanded={isModelSelectOpen}
586
+ aria-label={`${selectedModel}, Select a model`}
587
+ >
588
+ {selectedModel}
589
+ </MenuToggle>
590
+ )}
591
+ >
592
+ <SelectList>
593
+ {modelOptions.map((option) => (
594
+ <SelectOption key={option} value={option}>
595
+ {option}
596
+ </SelectOption>
597
+ ))}
598
+ </SelectList>
599
+ </Select>
600
+ </>
601
+ }
602
+ />
603
+ <ChatbotFootnote {...footnoteProps} />
604
+ </ChatbotFooter>
605
+ </div>
606
+ </DrawerContentBody>
607
+ </DrawerContent>
608
+ </Drawer>
609
+ }
610
+ />
611
+ </div>
612
+ </Chatbot>
613
+ </>
614
+ );
615
+ };