@patternfly/chatbot 6.9.0-prerelease.1 → 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.
- 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 +199 -61
- 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.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,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
|
+
};
|