@patternfly/chatbot 6.9.0-prerelease.2 → 6.9.0-prerelease.4

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