@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.
- package/dist/cjs/Chatbot/Chatbot.d.ts +2 -0
- package/dist/cjs/Chatbot/Chatbot.js +2 -2
- package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.js +1 -1
- package/dist/cjs/ChatbotFooter/ChatbotFootnote.js +1 -1
- package/dist/cjs/ChatbotModal/ChatbotModal.js +1 -1
- package/dist/cjs/ChatbotWelcomePrompt/ChatbotWelcomePrompt.js +1 -1
- package/dist/cjs/MessageBar/MessageBar.d.ts +1 -1
- package/dist/cjs/MessageBar/MessageBar.js +4 -3
- package/dist/cjs/MessageBar/SendButton.js +2 -2
- package/dist/cjs/SourcesCard/SourcesCard.d.ts +2 -0
- package/dist/cjs/SourcesCardBase/SourcesCardBase.d.ts +2 -0
- package/dist/cjs/SourcesCardBase/SourcesCardBase.js +2 -2
- package/dist/css/main.css +201 -66
- package/dist/css/main.css.map +1 -1
- package/dist/esm/Chatbot/Chatbot.d.ts +2 -0
- package/dist/esm/Chatbot/Chatbot.js +3 -3
- package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.js +1 -1
- package/dist/esm/ChatbotFooter/ChatbotFootnote.js +1 -1
- package/dist/esm/ChatbotModal/ChatbotModal.js +1 -1
- package/dist/esm/ChatbotWelcomePrompt/ChatbotWelcomePrompt.js +1 -1
- package/dist/esm/MessageBar/MessageBar.d.ts +1 -1
- package/dist/esm/MessageBar/MessageBar.js +4 -3
- package/dist/esm/MessageBar/SendButton.js +2 -2
- package/dist/esm/SourcesCard/SourcesCard.d.ts +2 -0
- package/dist/esm/SourcesCardBase/SourcesCardBase.d.ts +2 -0
- package/dist/esm/SourcesCardBase/SourcesCardBase.js +2 -2
- package/package.json +9 -9
- package/patternfly-docs/content/extensions/chatbot/chatbot.md +1 -0
- package/patternfly-docs/content/extensions/chatbot/examples/Analytics/Analytics.md +1 -1
- package/patternfly-docs/content/extensions/chatbot/examples/Messages/MessageWithSources.tsx +31 -0
- package/patternfly-docs/content/extensions/chatbot/examples/Messages/Messages.md +2 -0
- package/patternfly-docs/content/extensions/chatbot/examples/demos/Canvas.md +132 -0
- package/patternfly-docs/content/extensions/chatbot/examples/demos/Canvas.tsx +615 -0
- package/patternfly-docs/content/extensions/chatbot/examples/demos/CanvasWithDockedNav.tsx +697 -0
- package/patternfly-docs/content/extensions/chatbot/examples/demos/Chatbot.md +39 -5
- package/patternfly-docs/content/extensions/chatbot/examples/demos/FullscreenDockedNav.tsx +385 -0
- package/patternfly-docs/patternfly-docs.config.js +1 -1
- package/src/Canvas/Canvas.scss +150 -0
- package/src/Chatbot/Chatbot.scss +13 -1
- package/src/Chatbot/Chatbot.test.tsx +11 -0
- package/src/Chatbot/Chatbot.tsx +5 -1
- package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.scss +91 -77
- package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.test.tsx +32 -13
- package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.tsx +1 -0
- package/src/ChatbotFooter/ChatbotFooternote.test.tsx +50 -0
- package/src/ChatbotFooter/ChatbotFootnote.tsx +1 -1
- package/src/ChatbotModal/ChatbotModal.scss +2 -7
- package/src/ChatbotModal/ChatbotModal.tsx +1 -0
- package/src/ChatbotToggle/ChatbotToggle.scss +3 -2
- package/src/ChatbotToggle/ChatbotToggle.test.tsx +4 -0
- package/src/ChatbotWelcomePrompt/ChatbotWelcomePrompt.scss +9 -0
- package/src/ChatbotWelcomePrompt/ChatbotWelcomePrompt.tsx +4 -2
- package/src/FileDetails/__snapshots__/FileDetails.test.tsx.snap +2 -6
- package/src/FileDetailsLabel/__snapshots__/FileDetailsLabel.test.tsx.snap +2 -6
- package/src/Message/Message.scss +9 -0
- package/src/MessageBar/AttachButton.scss +4 -21
- package/src/MessageBar/MessageBar.scss +2 -2
- package/src/MessageBar/MessageBar.test.tsx +20 -0
- package/src/MessageBar/MessageBar.tsx +7 -3
- package/src/MessageBar/SendButton.scss +1 -32
- package/src/MessageBar/SendButton.tsx +2 -2
- package/src/SourcesCard/SourcesCard.scss +6 -0
- package/src/SourcesCard/SourcesCard.test.tsx +16 -0
- package/src/SourcesCard/SourcesCard.tsx +2 -0
- package/src/SourcesCardBase/SourcesCardBase.test.tsx +62 -0
- package/src/SourcesCardBase/SourcesCardBase.tsx +8 -0
- 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
|
+
};
|