@patternfly/chatbot 6.9.0-prerelease.1 → 6.9.0-prerelease.11

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 (67) 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 +201 -66
  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.scss +2 -7
  48. package/src/ChatbotModal/ChatbotModal.tsx +1 -0
  49. package/src/ChatbotToggle/ChatbotToggle.scss +3 -2
  50. package/src/ChatbotToggle/ChatbotToggle.test.tsx +4 -0
  51. package/src/ChatbotWelcomePrompt/ChatbotWelcomePrompt.scss +9 -0
  52. package/src/ChatbotWelcomePrompt/ChatbotWelcomePrompt.tsx +4 -2
  53. package/src/FileDetails/__snapshots__/FileDetails.test.tsx.snap +2 -6
  54. package/src/FileDetailsLabel/__snapshots__/FileDetailsLabel.test.tsx.snap +2 -6
  55. package/src/Message/Message.scss +9 -0
  56. package/src/MessageBar/AttachButton.scss +4 -21
  57. package/src/MessageBar/MessageBar.scss +2 -2
  58. package/src/MessageBar/MessageBar.test.tsx +20 -0
  59. package/src/MessageBar/MessageBar.tsx +7 -3
  60. package/src/MessageBar/SendButton.scss +1 -32
  61. package/src/MessageBar/SendButton.tsx +2 -2
  62. package/src/SourcesCard/SourcesCard.scss +6 -0
  63. package/src/SourcesCard/SourcesCard.test.tsx +16 -0
  64. package/src/SourcesCard/SourcesCard.tsx +2 -0
  65. package/src/SourcesCardBase/SourcesCardBase.test.tsx +62 -0
  66. package/src/SourcesCardBase/SourcesCardBase.tsx +8 -0
  67. package/src/main.scss +1 -0
@@ -0,0 +1,697 @@
1
+ import { ReactNode, useEffect, useRef, useState } from 'react';
2
+ import { useDropzone } from 'react-dropzone';
3
+ import {
4
+ Avatar,
5
+ Brand,
6
+ Button,
7
+ Divider,
8
+ Dropdown,
9
+ DropdownItem,
10
+ DropdownList,
11
+ Drawer,
12
+ DrawerActions,
13
+ DrawerContent,
14
+ DrawerContentBody,
15
+ DrawerHead,
16
+ DrawerPanelContent,
17
+ Flex,
18
+ FlexItem,
19
+ Label,
20
+ Masthead,
21
+ MastheadBrand,
22
+ MastheadContent,
23
+ MastheadLogo,
24
+ MastheadMain,
25
+ MastheadToggle,
26
+ MenuToggle,
27
+ Nav,
28
+ NavItem,
29
+ NavList,
30
+ Select,
31
+ SelectList,
32
+ SelectOption,
33
+ Switch,
34
+ Title,
35
+ PageToggleButton,
36
+ Popover,
37
+ Tooltip
38
+ } from '@patternfly/react-core';
39
+ import { CodeEditor, CodeEditorControl, Language } from '@patternfly/react-code-editor';
40
+ import Chatbot, { ChatbotDisplayMode } from '@patternfly/chatbot/dist/dynamic/Chatbot';
41
+ import ChatbotContent from '@patternfly/chatbot/dist/dynamic/ChatbotContent';
42
+ import ChatbotFooter, { ChatbotFootnote } from '@patternfly/chatbot/dist/dynamic/ChatbotFooter';
43
+ import MessageBar from '@patternfly/chatbot/dist/dynamic/MessageBar';
44
+ import MessageBox from '@patternfly/chatbot/dist/dynamic/MessageBox';
45
+ import Message from '@patternfly/chatbot/dist/dynamic/Message';
46
+ import ChatbotConversationHistoryNav from '@patternfly/chatbot/dist/dynamic/ChatbotConversationHistoryNav';
47
+ import { RhUiAddIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-add-icon';
48
+ import { RhUiAiInfoIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-ai-info-icon';
49
+ import { RhUiCalendarFillIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-calendar-fill-icon';
50
+ import { RhUiExportIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-export-icon';
51
+ import { RhUiImageFillIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-image-fill-icon';
52
+ import { RhUiNotificationFillIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-notification-fill-icon';
53
+ import { RhUiEditFillIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-edit-fill-icon';
54
+ import { RhUiRedoIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-redo-icon';
55
+ import { RhUiServerUploadFillIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-server-upload-fill-icon';
56
+ import { RhUiSettingsFillIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-settings-fill-icon';
57
+ import { RhUiTaskFillIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-task-fill-icon';
58
+ import { RhUiUndoIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-undo-icon';
59
+ import { RhMicronsCloseIcon } from '@patternfly/react-icons/dist/esm/icons/rh-microns-close-icon';
60
+ import PFIconLogoColor from '../UI/PF-IconLogo-Color.svg';
61
+ import userAvatar from '../Messages/user_avatar.svg';
62
+ import '@patternfly/react-core/dist/styles/base.css';
63
+ import '@patternfly/chatbot/dist/css/main.css';
64
+
65
+ const sampleCode = `apiVersion: v1
66
+ kind: ConfigMap
67
+ metadata:
68
+ name: canvas-demo
69
+ data:
70
+ greeting: Hello, how can I help you today?
71
+ `;
72
+
73
+ const initialMessages = [
74
+ {
75
+ id: '1',
76
+ role: 'bot',
77
+ content: 'Text message from the bot. This message can wrap several lines.',
78
+ name: 'Bot',
79
+ timestamp: '1:30 PM'
80
+ },
81
+ {
82
+ id: '2',
83
+ role: 'user',
84
+ content: 'Canvas mode enabled - I want to live edit code',
85
+ name: 'You',
86
+ avatar: userAvatar,
87
+ avatarProps: { isBordered: true },
88
+ timestamp: '1:30 PM'
89
+ },
90
+ {
91
+ id: '3',
92
+ role: 'bot',
93
+ content: 'The canvas mode text editor is ready. Edit, share or ask me to change something for you.',
94
+ name: 'Bot',
95
+ timestamp: '1:30 PM',
96
+ attachments: [{ name: 'canvas mode.yaml', id: 'canvas-mode' }]
97
+ }
98
+ ];
99
+
100
+ const conversations = [
101
+ { id: '1', text: 'Canvas mode enabled - I want to live edit code' },
102
+ { id: '2', text: 'Review deployment options' }
103
+ ];
104
+
105
+ interface PlainTextSettingsFormProps {
106
+ fields: { id: string; label: string; field: ReactNode }[];
107
+ }
108
+
109
+ const PlainTextSettingsForm = ({ fields }: PlainTextSettingsFormProps) => (
110
+ <div className="pf-chatbot__settings-form-container">
111
+ <form className="pf-chatbot__settings-form">
112
+ {fields.map(({ id, label, field }) => (
113
+ <div className="pf-chatbot__settings-form-row" key={id}>
114
+ <div className="pf-chatbot__settings-label" id={`${id}-label`}>
115
+ {label}
116
+ </div>
117
+ {field}
118
+ </div>
119
+ ))}
120
+ </form>
121
+ </div>
122
+ );
123
+
124
+ const hamburgerHoverStyles = `
125
+ .pf-chatbot__canvas-docked-nav .pf-v6-c-masthead__logo.pf-m-compact {
126
+ display: revert;
127
+ }
128
+
129
+ .pf-chatbot__canvas-docked-nav .pf-v6-c-nav.pf-m-docked .pf-v6-c-nav__link-text {
130
+ display: none;
131
+ }
132
+
133
+ .pf-chatbot__canvas-history-toggle.pf-v6-c-button.pf-m-hamburger:is(:hover, :focus-visible) {
134
+ --pf-v6-c-button--hamburger-icon--top--path: var(--pf-v6-c-button--hamburger-icon--top--collapse--path);
135
+ --pf-v6-c-button--hamburger-icon--arrow--path: var(--pf-v6-c-button--hamburger-icon--arrow--collapse--path);
136
+ --pf-v6-c-button--hamburger-icon--bottom--path: var(--pf-v6-c-button--hamburger-icon--bottom--collapse--path);
137
+ --pf-v6-c-button__icon--TransitionDelay: 0s;
138
+ --pf-v6-c-button__icon--ScaleX: var(--pf-v6-c-button--m-hamburger__icon--m-expand--ScaleX);
139
+ }
140
+
141
+ .pf-chatbot__canvas-history-toggle.pf-v6-c-button.pf-m-hamburger[aria-expanded="true"]:is(:hover, :focus-visible) {
142
+ --pf-v6-c-button__icon--ScaleX: var(--pf-v6-c-button--m-hamburger__icon--m-collapse--ScaleX);
143
+ }
144
+ `;
145
+
146
+ const SettingsPanel = ({ onClose }) => {
147
+ const [openDropdown, setOpenDropdown] = useState<string>();
148
+ const [theme, setTheme] = useState('System');
149
+ const [language, setLanguage] = useState('Auto-detect');
150
+ const [voice, setVoice] = useState('Bot');
151
+ const [isAnalyticsShared, setIsAnalyticsShared] = useState(true);
152
+ const closeButtonRef = useRef<HTMLButtonElement>(null);
153
+
154
+ useEffect(() => {
155
+ window.requestAnimationFrame(() => closeButtonRef.current?.focus());
156
+ }, []);
157
+
158
+ const dropdownField = (id, label, value, options, onSelect) => (
159
+ <Dropdown
160
+ isOpen={openDropdown === id}
161
+ onSelect={(_event, selectedValue) => {
162
+ onSelect(String(selectedValue));
163
+ setOpenDropdown(undefined);
164
+ }}
165
+ onOpenChange={(isOpen: boolean) => setOpenDropdown(isOpen ? id : undefined)}
166
+ shouldFocusToggleOnSelect
167
+ shouldFocusFirstItemOnOpen
168
+ toggle={(toggleRef) => (
169
+ <MenuToggle
170
+ id={id}
171
+ ref={toggleRef}
172
+ onClick={() => setOpenDropdown(openDropdown === id ? undefined : id)}
173
+ isExpanded={openDropdown === id}
174
+ aria-label={`${value}, ${label}`}
175
+ >
176
+ {value}
177
+ </MenuToggle>
178
+ )}
179
+ >
180
+ <DropdownList>
181
+ {options.map((option) => (
182
+ <DropdownItem value={option} key={option}>
183
+ {option}
184
+ </DropdownItem>
185
+ ))}
186
+ </DropdownList>
187
+ </Dropdown>
188
+ );
189
+
190
+ const fields = [
191
+ {
192
+ id: 'theme',
193
+ label: 'Theme',
194
+ field: dropdownField('theme', 'Theme', theme, ['System', 'Light', 'Dark'], setTheme)
195
+ },
196
+ {
197
+ id: 'language',
198
+ label: 'Language',
199
+ field: dropdownField('language', 'Language', language, ['Auto-detect', 'English'], setLanguage)
200
+ },
201
+ { id: 'voice', label: 'Voice', field: dropdownField('voice', 'Voice', voice, ['Bot', 'User'], setVoice) },
202
+ {
203
+ id: 'analytics',
204
+ label: 'Share analytics',
205
+ field: (
206
+ <Switch
207
+ id="analytics"
208
+ aria-labelledby="analytics-label"
209
+ isChecked={isAnalyticsShared}
210
+ onChange={(_event, checked) => setIsAnalyticsShared(checked)}
211
+ />
212
+ )
213
+ },
214
+ { id: 'archived-chat', label: 'Archived chats', field: <Button id="archived-chat">Manage</Button> },
215
+ { id: 'archive-all', label: 'Archived all chats', field: <Button id="archive-all">Archive all</Button> },
216
+ {
217
+ id: 'delete-all',
218
+ label: 'Delete all chats',
219
+ field: (
220
+ <Button id="delete-all" variant="danger">
221
+ Delete all
222
+ </Button>
223
+ )
224
+ }
225
+ ];
226
+
227
+ return (
228
+ <div className="pf-chatbot__canvas">
229
+ <div className="pf-v6-u-w-100 pf-v6-u-mx-auto" style={{ maxWidth: '60rem' }}>
230
+ <Flex
231
+ justifyContent={{ default: 'justifyContentSpaceBetween' }}
232
+ alignItems={{ default: 'alignItemsCenter' }}
233
+ className="pf-v6-u-p-lg"
234
+ >
235
+ <Title headingLevel="h1" size="2xl">
236
+ Settings
237
+ </Title>
238
+ <Tooltip triggerRef={closeButtonRef} content="Close settings" position="bottom" aria="none" />
239
+ <Button
240
+ ref={closeButtonRef}
241
+ variant="plain"
242
+ icon={<RhMicronsCloseIcon />}
243
+ aria-label="Close settings"
244
+ onClick={onClose}
245
+ />
246
+ </Flex>
247
+ <Divider />
248
+ <PlainTextSettingsForm fields={fields} />
249
+ </div>
250
+ </div>
251
+ );
252
+ };
253
+
254
+ export const CanvasWithDockedNavDemo = () => {
255
+ const [messages, setMessages] = useState(initialMessages);
256
+ const [code, setCode] = useState(sampleCode);
257
+ const [isDrawerOpen, setIsDrawerOpen] = useState(false);
258
+ const [areSettingsOpen, setAreSettingsOpen] = useState(false);
259
+ const [isCanvasOpen, setIsCanvasOpen] = useState(true);
260
+ const [showCanvasLabel, setShowCanvasLabel] = useState(true);
261
+ const [isGeneratedAiPopoverOpen, setIsGeneratedAiPopoverOpen] = useState(false);
262
+ const [isAttachMenuOpen, setIsAttachMenuOpen] = useState(false);
263
+ const [isModelSelectOpen, setIsModelSelectOpen] = useState(false);
264
+ const [selectedModel, setSelectedModel] = useState('Granite 7B');
265
+ const [announcement, setAnnouncement] = useState();
266
+ const [isSendButtonDisabled, setIsSendButtonDisabled] = useState(false);
267
+ const newChatRef = useRef<HTMLAnchorElement>(null);
268
+ const settingsRef = useRef<HTMLAnchorElement>(null);
269
+ const messageInputRef = useRef<HTMLTextAreaElement>(null);
270
+ const canvasSectionRef = useRef<HTMLElement>(null);
271
+ const scrollToBottomRef = useRef<HTMLDivElement>(null);
272
+ const messageActionsRef = useRef<HTMLButtonElement>(null);
273
+ const canvasCloseButtonRef = useRef<HTMLButtonElement>(null);
274
+ const focusCanvasAfterAttachSelection = useRef(false);
275
+
276
+ const closeSettings = () => {
277
+ setAreSettingsOpen(false);
278
+ window.requestAnimationFrame(() => settingsRef.current?.focus());
279
+ };
280
+
281
+ const focusMessageInput = () => {
282
+ window.requestAnimationFrame(() => messageInputRef.current?.focus());
283
+ };
284
+
285
+ const focusMessageActions = () => {
286
+ window.requestAnimationFrame(() => messageActionsRef.current?.focus());
287
+ };
288
+
289
+ const focusCanvasSection = () => {
290
+ window.requestAnimationFrame(() => {
291
+ canvasSectionRef.current?.focus();
292
+ focusCanvasAfterAttachSelection.current = false;
293
+ });
294
+ };
295
+
296
+ const handleGeneratedAiLabelKeyDown = (event: React.KeyboardEvent) => {
297
+ if (event.key === 'Enter' || event.key === ' ') {
298
+ event.preventDefault();
299
+ event.currentTarget.click();
300
+ }
301
+ };
302
+ const { open, getInputProps } = useDropzone({
303
+ multiple: true,
304
+ // eslint-disable-next-line no-console
305
+ onDropAccepted: () => console.log('fileUploaded')
306
+ });
307
+
308
+ const startNewChat = () => {
309
+ setMessages([]);
310
+ setIsDrawerOpen(false);
311
+ setAreSettingsOpen(false);
312
+ };
313
+
314
+ const openCanvas = () => {
315
+ setShowCanvasLabel(true);
316
+ setIsCanvasOpen(true);
317
+ focusCanvasSection();
318
+ };
319
+
320
+ const closeCanvasMode = () => {
321
+ setShowCanvasLabel(false);
322
+ setIsCanvasOpen(false);
323
+ focusMessageInput();
324
+ };
325
+
326
+ const sendMessage = (content) => {
327
+ const date = new Date();
328
+ const userMessage = {
329
+ id: Date.now().toString(),
330
+ role: 'user',
331
+ content,
332
+ name: 'You',
333
+ avatar: userAvatar,
334
+ avatarProps: { isBordered: true },
335
+ timestamp: `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`
336
+ };
337
+ const newMessages = [...messages, userMessage];
338
+ newMessages.push({
339
+ id: `${Date.now()}-loading`,
340
+ role: 'bot',
341
+ name: 'Bot',
342
+ isLoading: true,
343
+ timestamp: `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`
344
+ });
345
+ setMessages(newMessages);
346
+ setAnnouncement(`Message from You: ${content}. Message from Bot is loading.`);
347
+ setIsSendButtonDisabled(true);
348
+ setTimeout(() => {
349
+ const loadedMessages = [...newMessages];
350
+ loadedMessages.pop();
351
+ loadedMessages.push({
352
+ id: `${Date.now()}-bot`,
353
+ role: 'bot',
354
+ content: 'API response from Bot goes here',
355
+ name: 'Bot',
356
+ isLoading: false,
357
+ actions: {
358
+ // eslint-disable-next-line no-console
359
+ positive: { onClick: () => console.log('Good response') },
360
+ // eslint-disable-next-line no-console
361
+ negative: { onClick: () => console.log('Bad response') },
362
+ // eslint-disable-next-line no-console
363
+ copy: { onClick: () => console.log('Copy') },
364
+ // eslint-disable-next-line no-console
365
+ download: { onClick: () => console.log('Download') },
366
+ // eslint-disable-next-line no-console
367
+ listen: { onClick: () => console.log('Listen') }
368
+ },
369
+ timestamp: date.toLocaleString()
370
+ });
371
+ setMessages(loadedMessages);
372
+ setAnnouncement('Message from Bot: API response from Bot goes here');
373
+ setIsSendButtonDisabled(false);
374
+ }, 5000);
375
+ };
376
+
377
+ const attachMenuItems = (
378
+ <>
379
+ <DropdownList>
380
+ <DropdownItem value="Alerts" id="alerts" icon={<RhUiNotificationFillIcon />}>
381
+ Alerts
382
+ </DropdownItem>
383
+ <DropdownItem value="Events" id="events" icon={<RhUiCalendarFillIcon />}>
384
+ Events
385
+ </DropdownItem>
386
+ <DropdownItem value="Logs" id="logs" icon={<RhUiTaskFillIcon />}>
387
+ Logs
388
+ </DropdownItem>
389
+ </DropdownList>
390
+ <Divider />
391
+ <DropdownList>
392
+ <DropdownItem value="Upload from computer" id="upload" icon={<RhUiServerUploadFillIcon />} onClick={open}>
393
+ Upload from computer
394
+ </DropdownItem>
395
+ <DropdownItem value="canvas" id="canvas" icon={<RhUiImageFillIcon />}>
396
+ {`${isCanvasOpen ? 'Disable' : 'Enable'} canvas mode`}
397
+ </DropdownItem>
398
+ </DropdownList>
399
+ </>
400
+ );
401
+
402
+ const dockedNav = (
403
+ <>
404
+ <style>{hamburgerHoverStyles}</style>
405
+ <div className="pf-chatbot__canvas-docked-nav pf-v6-u-h-100 pf-v6-u-p-sm">
406
+ <Masthead variant="docked">
407
+ <MastheadMain>
408
+ <MastheadToggle>
409
+ <PageToggleButton
410
+ className="pf-chatbot__canvas-history-toggle"
411
+ aria-label="Chat history"
412
+ isHamburgerButton
413
+ isSidebarOpen={isDrawerOpen}
414
+ onSidebarToggle={() => setIsDrawerOpen((open) => !open)}
415
+ />
416
+ </MastheadToggle>
417
+ <MastheadBrand>
418
+ <MastheadLogo isCompact>
419
+ <Brand src={PFIconLogoColor} alt="PatternFly" heights={{ default: '37px' }} />
420
+ </MastheadLogo>
421
+ </MastheadBrand>
422
+ </MastheadMain>
423
+ <MastheadContent>
424
+ <Divider />
425
+ <Nav
426
+ variant="docked"
427
+ aria-label="Canvas navigation"
428
+ className="pf-v6-u-flex-1 pf-v6-u-align-content-space-between"
429
+ >
430
+ <NavList>
431
+ <NavItem
432
+ itemId="new-chat"
433
+ aria-label="New chat"
434
+ icon={<RhUiEditFillIcon />}
435
+ component="button"
436
+ preventDefault
437
+ anchorRef={newChatRef}
438
+ onClick={startNewChat}
439
+ >
440
+ New chat
441
+ </NavItem>
442
+ </NavList>
443
+ <NavList className="pf-v6-u-mt-auto">
444
+ <NavItem
445
+ itemId="settings"
446
+ aria-label="Settings"
447
+ icon={<RhUiSettingsFillIcon />}
448
+ component="button"
449
+ preventDefault
450
+ anchorRef={settingsRef}
451
+ onClick={() => {
452
+ setIsDrawerOpen(false);
453
+ setAreSettingsOpen(true);
454
+ }}
455
+ >
456
+ Settings
457
+ </NavItem>
458
+ </NavList>
459
+ </Nav>
460
+ <Tooltip aria="none" aria-live="off" triggerRef={newChatRef} content="New chat" />
461
+ <Tooltip aria="none" aria-live="off" triggerRef={settingsRef} content="Settings" />
462
+ <Avatar
463
+ className="pf-v6-u-mt-md pf-v6-u-mb-md pf-v6-u-mx-auto"
464
+ src={userAvatar}
465
+ alt="User profile"
466
+ size="md"
467
+ isBordered
468
+ />
469
+ </MastheadContent>
470
+ </Masthead>
471
+ </div>
472
+ </>
473
+ );
474
+
475
+ const editorControls = [
476
+ { icon: <RhUiUndoIcon />, label: 'Undo' },
477
+ { icon: <RhUiRedoIcon />, label: 'Redo' },
478
+ { icon: <RhUiExportIcon />, label: 'Export' }
479
+ ].map(({ icon, label }) => (
480
+ <CodeEditorControl key={label} icon={icon} aria-label={label} tooltipProps={{ content: label, aria: 'none' }} />
481
+ ));
482
+
483
+ const canvasPanel = (
484
+ <DrawerPanelContent isResizable isGlass defaultSize="60%" minSize="30%" className="pf-chatbot__canvas-panel">
485
+ <section ref={canvasSectionRef} className="pf-chatbot__canvas-section" tabIndex={-1} aria-label="Canvas">
486
+ <DrawerHead className="pf-chatbot__canvas-head">
487
+ <Flex spaceItems={{ default: 'spaceItemsMd' }} alignItems={{ default: 'alignItemsCenter' }}>
488
+ <FlexItem>
489
+ <Title headingLevel="h2" size="lg">
490
+ Edit code
491
+ </Title>
492
+ </FlexItem>
493
+ <FlexItem>
494
+ <Popover
495
+ headerContent="What is canvas mode?"
496
+ bodyContent="This canvas is a collaborative workspace that blends AI-generated content with manual human edits."
497
+ onShow={() => setIsGeneratedAiPopoverOpen(true)}
498
+ onHide={() => setIsGeneratedAiPopoverOpen(false)}
499
+ >
500
+ <Label
501
+ isClickable
502
+ variant="outline"
503
+ icon={<RhUiAiInfoIcon aria-hidden />}
504
+ role="button"
505
+ tabIndex={0}
506
+ aria-expanded={isGeneratedAiPopoverOpen}
507
+ onKeyDown={handleGeneratedAiLabelKeyDown}
508
+ >
509
+ Generated with AI
510
+ </Label>
511
+ </Popover>
512
+ </FlexItem>
513
+ </Flex>
514
+ <DrawerActions>
515
+ <Tooltip triggerRef={canvasCloseButtonRef} content="Close canvas" position="bottom" aria="none" />
516
+ <div className="pf-v6-c-drawer__close">
517
+ <Button
518
+ ref={canvasCloseButtonRef}
519
+ variant="plain"
520
+ aria-label="Exit canvas mode"
521
+ icon={<RhMicronsCloseIcon />}
522
+ onClick={closeCanvasMode}
523
+ />
524
+ </div>
525
+ </DrawerActions>
526
+ </DrawerHead>
527
+ <div className="pf-chatbot__canvas-panel-body">
528
+ <div className="pf-chatbot__canvas-editor">
529
+ <CodeEditor
530
+ isFullHeight
531
+ isLineNumbersVisible
532
+ isLanguageLabelVisible
533
+ isCopyEnabled
534
+ isDownloadEnabled
535
+ downloadFileName="canvas-mode"
536
+ customControls={editorControls}
537
+ code={code}
538
+ language={Language.yaml}
539
+ onCodeChange={setCode}
540
+ />
541
+ </div>
542
+ </div>
543
+ </section>
544
+ </DrawerPanelContent>
545
+ );
546
+
547
+ return (
548
+ <>
549
+ <input {...getInputProps()} hidden />
550
+ <Chatbot displayMode={ChatbotDisplayMode.fullscreen} dockedNav={dockedNav}>
551
+ <div className="pf-chatbot__canvas">
552
+ <ChatbotConversationHistoryNav
553
+ displayMode={ChatbotDisplayMode.fullscreen}
554
+ isDrawerOpen={isDrawerOpen}
555
+ setIsDrawerOpen={setIsDrawerOpen}
556
+ onDrawerToggle={() => setIsDrawerOpen((open) => !open)}
557
+ activeItemId="1"
558
+ conversations={{ Today: conversations }}
559
+ onNewChat={startNewChat}
560
+ drawerCloseButtonProps={{ 'aria-label': 'Close chat history' }}
561
+ drawerContent={
562
+ <div style={{ display: 'contents' }}>
563
+ {areSettingsOpen ? (
564
+ <SettingsPanel onClose={closeSettings} />
565
+ ) : (
566
+ <Drawer isExpanded={isCanvasOpen} isInline position="end">
567
+ <DrawerContent panelContent={canvasPanel}>
568
+ <DrawerContentBody className="pf-chatbot__canvas-body">
569
+ <div className="pf-chatbot__canvas-column">
570
+ <ChatbotContent className="pf-chatbot__canvas-chat-content">
571
+ <MessageBox
572
+ ariaLabel="Scrollable message log for ChatBot"
573
+ announcement={announcement}
574
+ position="bottom"
575
+ >
576
+ {messages.map((message) => (
577
+ <Message
578
+ key={message.id}
579
+ {...message}
580
+ attachments={message.attachments?.map((attachment) => ({
581
+ ...attachment,
582
+ onClick: openCanvas
583
+ }))}
584
+ />
585
+ ))}
586
+ <div ref={scrollToBottomRef}></div>
587
+ </MessageBox>
588
+ </ChatbotContent>
589
+ <ChatbotFooter className="pf-chatbot__canvas-chat-footer">
590
+ <MessageBar
591
+ onSendMessage={sendMessage}
592
+ attachButtonPosition="start"
593
+ alwayShowSendButton
594
+ isSendButtonDisabled={isSendButtonDisabled}
595
+ innerRef={messageInputRef}
596
+ attachMenuProps={{
597
+ isAttachMenuOpen,
598
+ setIsAttachMenuOpen,
599
+ attachMenuItems,
600
+ onAttachMenuOnOpenChangeKeys: ['Escape', 'Tab'],
601
+ onAttachMenuOpenChange: (isOpen) => {
602
+ if (!isOpen) {
603
+ if (focusCanvasAfterAttachSelection.current) {
604
+ focusCanvasAfterAttachSelection.current = false;
605
+ } else {
606
+ focusMessageActions();
607
+ }
608
+ }
609
+ },
610
+ onAttachMenuSelect: (_event, value) => {
611
+ if (value === 'canvas') {
612
+ if (showCanvasLabel) {
613
+ closeCanvasMode();
614
+ } else {
615
+ focusCanvasAfterAttachSelection.current = true;
616
+ openCanvas();
617
+ setIsAttachMenuOpen(false);
618
+ return;
619
+ }
620
+ }
621
+ setIsAttachMenuOpen(false);
622
+ focusMessageActions();
623
+ }
624
+ }}
625
+ buttonProps={{
626
+ attach: {
627
+ innerRef: messageActionsRef,
628
+ icon: <RhUiAddIcon />,
629
+ tooltipContent: 'Message actions',
630
+ 'aria-label': 'Message actions'
631
+ }
632
+ }}
633
+ additionalActions={
634
+ <>
635
+ {showCanvasLabel && (
636
+ <Label
637
+ closeBtnAriaLabel="Exit canvas mode"
638
+ onClose={closeCanvasMode}
639
+ icon={<RhUiImageFillIcon aria-hidden />}
640
+ >
641
+ Canvas
642
+ </Label>
643
+ )}
644
+ <Select
645
+ isOpen={isModelSelectOpen}
646
+ selected={selectedModel}
647
+ onSelect={(_event, value) => {
648
+ setSelectedModel(String(value));
649
+ setIsModelSelectOpen(false);
650
+ }}
651
+ shouldFocusToggleOnSelect
652
+ onOpenChange={setIsModelSelectOpen}
653
+ toggle={(toggleRef) => (
654
+ <MenuToggle
655
+ ref={toggleRef}
656
+ variant="plainText"
657
+ onClick={() => setIsModelSelectOpen((open) => !open)}
658
+ isExpanded={isModelSelectOpen}
659
+ aria-label={`${selectedModel}, Select a model`}
660
+ >
661
+ {selectedModel}
662
+ </MenuToggle>
663
+ )}
664
+ >
665
+ <SelectList>
666
+ {['Granite 7B', 'Granite 8B', 'Llama 3'].map((option) => (
667
+ <SelectOption key={option} value={option}>
668
+ {option}
669
+ </SelectOption>
670
+ ))}
671
+ </SelectList>
672
+ </Select>
673
+ </>
674
+ }
675
+ />
676
+ <ChatbotFootnote
677
+ label="Always review AI-generated content prior to use."
678
+ popover={{
679
+ title: 'AI-generated content',
680
+ description: 'Always review AI-generated content prior to use.',
681
+ showClose: true
682
+ }}
683
+ />
684
+ </ChatbotFooter>
685
+ </div>
686
+ </DrawerContentBody>
687
+ </DrawerContent>
688
+ </Drawer>
689
+ )}
690
+ </div>
691
+ }
692
+ />
693
+ </div>
694
+ </Chatbot>
695
+ </>
696
+ );
697
+ };