@patternfly/chatbot 6.3.0 → 6.4.0-prerelease.2

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 (94) hide show
  1. package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.d.ts +5 -1
  2. package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.js +3 -3
  3. package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.test.js +17 -0
  4. package/dist/cjs/FileDropZone/FileDropZone.d.ts +1 -2
  5. package/dist/cjs/Message/CodeBlockMessage/CodeBlockMessage.js +1 -10
  6. package/dist/cjs/Message/Message.d.ts +4 -2
  7. package/dist/cjs/Message/Message.js +4 -4
  8. package/dist/cjs/Message/Message.test.js +26 -0
  9. package/dist/cjs/Message/MessageInput.d.ts +3 -1
  10. package/dist/cjs/Message/MessageInput.js +2 -2
  11. package/dist/cjs/MessageBar/AttachButton.d.ts +2 -2
  12. package/dist/cjs/MessageBar/MessageBar.d.ts +2 -2
  13. package/dist/cjs/MessageBox/MessageBox.js +1 -1
  14. package/dist/cjs/MessageDivider/MessageDivider.d.ts +9 -0
  15. package/dist/cjs/MessageDivider/MessageDivider.js +23 -0
  16. package/dist/cjs/MessageDivider/MessageDivider.test.d.ts +1 -0
  17. package/dist/cjs/MessageDivider/MessageDivider.test.js +29 -0
  18. package/dist/cjs/MessageDivider/index.d.ts +2 -0
  19. package/dist/cjs/MessageDivider/index.js +23 -0
  20. package/dist/cjs/ResponseActions/ResponseActions.d.ts +1 -0
  21. package/dist/cjs/ResponseActions/ResponseActions.js +4 -4
  22. package/dist/cjs/ResponseActions/ResponseActions.test.js +6 -1
  23. package/dist/cjs/index.d.ts +2 -0
  24. package/dist/cjs/index.js +4 -1
  25. package/dist/css/main.css +56 -55
  26. package/dist/css/main.css.map +1 -1
  27. package/dist/dynamic/MessageDivider/package.json +1 -0
  28. package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.d.ts +5 -1
  29. package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.js +5 -5
  30. package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.test.js +17 -0
  31. package/dist/esm/FileDropZone/FileDropZone.d.ts +1 -2
  32. package/dist/esm/Message/CodeBlockMessage/CodeBlockMessage.js +1 -7
  33. package/dist/esm/Message/Message.d.ts +4 -2
  34. package/dist/esm/Message/Message.js +4 -4
  35. package/dist/esm/Message/Message.test.js +26 -0
  36. package/dist/esm/Message/MessageInput.d.ts +3 -1
  37. package/dist/esm/Message/MessageInput.js +2 -2
  38. package/dist/esm/MessageBar/AttachButton.d.ts +2 -2
  39. package/dist/esm/MessageBar/MessageBar.d.ts +2 -2
  40. package/dist/esm/MessageBox/MessageBox.js +1 -1
  41. package/dist/esm/MessageDivider/MessageDivider.d.ts +9 -0
  42. package/dist/esm/MessageDivider/MessageDivider.js +21 -0
  43. package/dist/esm/MessageDivider/MessageDivider.test.d.ts +1 -0
  44. package/dist/esm/MessageDivider/MessageDivider.test.js +24 -0
  45. package/dist/esm/MessageDivider/index.d.ts +2 -0
  46. package/dist/esm/MessageDivider/index.js +2 -0
  47. package/dist/esm/ResponseActions/ResponseActions.d.ts +1 -0
  48. package/dist/esm/ResponseActions/ResponseActions.js +5 -5
  49. package/dist/esm/ResponseActions/ResponseActions.test.js +6 -1
  50. package/dist/esm/index.d.ts +2 -0
  51. package/dist/esm/index.js +2 -0
  52. package/dist/tsconfig.tsbuildinfo +1 -1
  53. package/package.json +5 -3
  54. package/patternfly-docs/content/extensions/chatbot/examples/Messages/MessageWithDividers.tsx +24 -0
  55. package/patternfly-docs/content/extensions/chatbot/examples/Messages/Messages.md +15 -1
  56. package/patternfly-docs/content/extensions/chatbot/examples/Messages/UserMessage.tsx +39 -7
  57. package/patternfly-docs/content/extensions/chatbot/examples/UI/ChatbotHeaderDrawer.tsx +9 -0
  58. package/patternfly-docs/content/extensions/chatbot/examples/UI/ChatbotHeaderDrawerWithPin.tsx +196 -0
  59. package/patternfly-docs/content/extensions/chatbot/examples/UI/UI.md +9 -1
  60. package/patternfly-docs/content/extensions/chatbot/examples/demos/Chatbot.md +33 -1
  61. package/patternfly-docs/content/extensions/chatbot/examples/demos/ChatbotDisplayMode.tsx +486 -0
  62. package/patternfly-docs/content/extensions/chatbot/examples/demos/ChatbotTranscripts.tsx +565 -0
  63. package/src/Chatbot/Chatbot.scss +1 -1
  64. package/src/ChatbotContent/ChatbotContent.scss +1 -1
  65. package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.scss +14 -2
  66. package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.test.tsx +58 -0
  67. package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.tsx +29 -12
  68. package/src/ChatbotFooter/ChatbotFooter.scss +1 -1
  69. package/src/ChatbotHeader/ChatbotHeader.scss +3 -3
  70. package/src/ChatbotToggle/ChatbotToggle.scss +2 -2
  71. package/src/FileDropZone/FileDropZone.tsx +2 -2
  72. package/src/Message/CodeBlockMessage/CodeBlockMessage.tsx +3 -27
  73. package/src/Message/Message.scss +9 -7
  74. package/src/Message/Message.test.tsx +35 -0
  75. package/src/Message/Message.tsx +8 -4
  76. package/src/Message/MessageInput.tsx +5 -1
  77. package/src/MessageBar/AttachButton.tsx +2 -2
  78. package/src/MessageBar/MessageBar.tsx +2 -2
  79. package/src/MessageBar/SendButton.scss +3 -3
  80. package/src/MessageBox/JumpButton.scss +1 -1
  81. package/src/MessageBox/MessageBox.tsx +1 -1
  82. package/src/MessageDivider/MessageDivider.scss +45 -0
  83. package/src/MessageDivider/MessageDivider.test.tsx +24 -0
  84. package/src/MessageDivider/MessageDivider.tsx +35 -0
  85. package/src/MessageDivider/index.ts +3 -0
  86. package/src/ResponseActions/ResponseActions.test.tsx +6 -1
  87. package/src/ResponseActions/ResponseActions.tsx +24 -3
  88. package/src/index.ts +3 -0
  89. package/src/main.scss +1 -52
  90. package/dist/cjs/Message/CodeBlockMessage/ExpandableSectionForSyntaxHighlighter.d.ts +0 -62
  91. package/dist/cjs/Message/CodeBlockMessage/ExpandableSectionForSyntaxHighlighter.js +0 -139
  92. package/dist/esm/Message/CodeBlockMessage/ExpandableSectionForSyntaxHighlighter.d.ts +0 -62
  93. package/dist/esm/Message/CodeBlockMessage/ExpandableSectionForSyntaxHighlighter.js +0 -133
  94. package/src/Message/CodeBlockMessage/ExpandableSectionForSyntaxHighlighter.tsx +0 -223
@@ -0,0 +1,565 @@
1
+ import { useEffect, useRef, useState, FunctionComponent, MouseEvent } from 'react';
2
+ import { Bullseye, Brand, DropdownList, DropdownItem, DropdownGroup, SkipToContent } from '@patternfly/react-core';
3
+
4
+ import ChatbotToggle from '@patternfly/chatbot/dist/dynamic/ChatbotToggle';
5
+ import Chatbot, { ChatbotDisplayMode } from '@patternfly/chatbot/dist/dynamic/Chatbot';
6
+ import ChatbotContent from '@patternfly/chatbot/dist/dynamic/ChatbotContent';
7
+ import ChatbotWelcomePrompt from '@patternfly/chatbot/dist/dynamic/ChatbotWelcomePrompt';
8
+ import ChatbotFooter, { ChatbotFootnote } from '@patternfly/chatbot/dist/dynamic/ChatbotFooter';
9
+ import MessageBar from '@patternfly/chatbot/dist/dynamic/MessageBar';
10
+ import MessageBox from '@patternfly/chatbot/dist/dynamic/MessageBox';
11
+ import Message, { MessageProps } from '@patternfly/chatbot/dist/dynamic/Message';
12
+ import ChatbotConversationHistoryNav, {
13
+ Conversation
14
+ } from '@patternfly/chatbot/dist/dynamic/ChatbotConversationHistoryNav';
15
+ import ChatbotHeader, {
16
+ ChatbotHeaderMenu,
17
+ ChatbotHeaderMain,
18
+ ChatbotHeaderTitle,
19
+ ChatbotHeaderActions,
20
+ ChatbotHeaderSelectorDropdown,
21
+ ChatbotHeaderOptionsDropdown
22
+ } from '@patternfly/chatbot/dist/dynamic/ChatbotHeader';
23
+
24
+ import ExpandIcon from '@patternfly/react-icons/dist/esm/icons/expand-icon';
25
+ import OpenDrawerRightIcon from '@patternfly/react-icons/dist/esm/icons/open-drawer-right-icon';
26
+ import OutlinedWindowRestoreIcon from '@patternfly/react-icons/dist/esm/icons/outlined-window-restore-icon';
27
+
28
+ import PFHorizontalLogoColor from '../UI/PF-HorizontalLogo-Color.svg';
29
+ import PFHorizontalLogoReverse from '../UI/PF-HorizontalLogo-Reverse.svg';
30
+ import PFIconLogoColor from '../UI/PF-IconLogo-Color.svg';
31
+ import PFIconLogoReverse from '../UI/PF-IconLogo-Reverse.svg';
32
+ import userAvatar from '../Messages/user_avatar.svg';
33
+ import patternflyAvatar from '../Messages/patternfly_avatar.jpg';
34
+ import '@patternfly/react-core/dist/styles/base.css';
35
+ import '@patternfly/chatbot/dist/css/main.css';
36
+ import saveAs from 'file-saver';
37
+
38
+ const footnoteProps = {
39
+ label: 'ChatBot uses AI. Check for mistakes.',
40
+ popover: {
41
+ title: 'Verify information',
42
+ description: `While ChatBot strives for accuracy, AI is experimental and can make mistakes. We cannot guarantee that all information provided by ChatBot is up to date or without error. You should always verify responses using reliable sources, especially for crucial information and decision making.`,
43
+ bannerImage: {
44
+ src: 'https://cdn.dribbble.com/userupload/10651749/file/original-8a07b8e39d9e8bf002358c66fce1223e.gif',
45
+ alt: 'Example image for footnote popover'
46
+ },
47
+ cta: {
48
+ label: 'Dismiss',
49
+ onClick: () => {
50
+ alert('Do something!');
51
+ }
52
+ },
53
+ link: {
54
+ label: 'View AI policy',
55
+ url: 'https://www.redhat.com/'
56
+ }
57
+ }
58
+ };
59
+
60
+ const markdown = `A paragraph with *emphasis* and **strong importance**.
61
+
62
+ > A block quote with ~strikethrough~ and a URL: https://reactjs.org.
63
+
64
+ Here is an inline code - \`() => void\`
65
+
66
+ Here is some YAML code:
67
+
68
+ ~~~yaml
69
+ apiVersion: helm.openshift.io/v1beta1/
70
+ kind: HelmChartRepository
71
+ metadata:
72
+ name: azure-sample-repo0oooo00ooo
73
+ spec:
74
+ connectionConfig:
75
+ url: https://raw.githubusercontent.com/Azure-Samples/helm-charts/master/docs
76
+ ~~~
77
+
78
+ Here is some JavaScript code:
79
+
80
+ ~~~js
81
+ const MessageLoading = () => (
82
+ <div className="pf-chatbot__message-loading">
83
+ <span className="pf-chatbot__message-loading-dots">
84
+ <span className="pf-v6-screen-reader">Loading message</span>
85
+ </span>
86
+ </div>
87
+ );
88
+
89
+ export default MessageLoading;
90
+
91
+ ~~~
92
+ `;
93
+
94
+ // It's important to set a date and timestamp prop since the Message components re-render.
95
+ // The timestamps re-render with them.
96
+ const date = new Date();
97
+
98
+ // Generate a file with individual message content
99
+ const downloadMessageContent = (message: MessageProps, selectedModel: string) => {
100
+ const currentDate = new Date();
101
+ const dateStr = currentDate.toISOString().split('T')[0]; // YYYY-MM-DD
102
+ const timeStr = currentDate.toTimeString().split(' ')[0]; // HH:MM:SS
103
+
104
+ const messageContent = `# AI Chat Transcript
105
+
106
+ **Date:** ${dateStr}
107
+ **Time:** ${timeStr}
108
+ **Location:** PatternFly Chatbot Demo Platform
109
+
110
+ ---
111
+
112
+ ## Participants:
113
+ **User:** User
114
+ **AI:** ${selectedModel}
115
+
116
+ ---
117
+
118
+ ## Conversation Log
119
+
120
+ ${message.timestamp ? `**${message.timestamp?.toLocaleString()} AI:**` : `**AI:**`} ${message.content}
121
+
122
+ ---
123
+
124
+ ## Notes:
125
+ **Downloaded:** ${dateStr} at ${timeStr}
126
+ `;
127
+
128
+ const blob = new Blob([messageContent], { type: 'text/plain;charset=utf-8' });
129
+ const fileName = `message-${message.role}-${message.id}-${dateStr}.md`;
130
+ saveAs(blob, fileName);
131
+ };
132
+
133
+ // Generate transcript for a specific conversation
134
+ const generateConversationTranscript = (conversationText: string, selectedModel: string) => {
135
+ const currentDate = new Date();
136
+ const dateStr = currentDate.toISOString().split('T')[0]; // YYYY-MM-DD
137
+ const timeStr = currentDate.toTimeString().split(' ')[0]; // HH:MM:SS
138
+
139
+ const transcript = `# AI Chat Transcript
140
+
141
+ **Conversation Title:** ${conversationText}
142
+
143
+ **Date:** ${dateStr}
144
+ **Time:** ${timeStr}
145
+ **Location:** PatternFly Chatbot Demo Platform
146
+
147
+ ---
148
+
149
+ ## Participants:
150
+ **User:** User
151
+ **AI:** ${selectedModel}
152
+
153
+ ---
154
+
155
+ ## Conversation Log
156
+
157
+ **[${timeStr}] User:** ${conversationText}
158
+ **[${timeStr}] AI:** ${markdown}
159
+
160
+ ---
161
+
162
+ ## Notes:
163
+
164
+ This is a sample conversation from your chat history. In a real implementation, this would contain the actual messages from this specific conversation.
165
+
166
+ **Downloaded:** ${dateStr} at ${timeStr}
167
+ `;
168
+
169
+ return transcript;
170
+ };
171
+
172
+ const welcomePrompts = [
173
+ {
174
+ title: 'Set up account',
175
+ message: 'Choose the necessary settings and preferences for your account.'
176
+ },
177
+ {
178
+ title: 'Troubleshoot issue',
179
+ message: 'Find documentation and instructions to resolve your issue.'
180
+ }
181
+ ];
182
+
183
+ export const ChatbotDemo: FunctionComponent = () => {
184
+ const [chatbotVisible, setChatbotVisible] = useState<boolean>(true);
185
+ const [displayMode, setDisplayMode] = useState<ChatbotDisplayMode>(ChatbotDisplayMode.default);
186
+ const [selectedModel, setSelectedModel] = useState('Granite 7B');
187
+
188
+ const initialMessages: MessageProps[] = [
189
+ {
190
+ id: '1',
191
+ role: 'user',
192
+ content: 'Hello, can you give me an example of what you can do?',
193
+ name: 'User',
194
+ avatar: userAvatar,
195
+ timestamp: date.toLocaleString(),
196
+ avatarProps: { isBordered: true }
197
+ },
198
+ {
199
+ id: '2',
200
+ role: 'bot',
201
+ content: markdown,
202
+ name: 'Bot',
203
+ avatar: patternflyAvatar,
204
+ timestamp: date.toLocaleString(),
205
+ actions: {
206
+ download: {
207
+ onClick: () =>
208
+ downloadMessageContent(
209
+ {
210
+ id: '2',
211
+ role: 'bot',
212
+ content: markdown,
213
+ name: 'Bot',
214
+ avatar: patternflyAvatar,
215
+ timestamp: date.toLocaleString()
216
+ },
217
+ selectedModel
218
+ )
219
+ }
220
+ }
221
+ }
222
+ ];
223
+ const [messages, setMessages] = useState<MessageProps[]>(initialMessages);
224
+ const [isSendButtonDisabled, setIsSendButtonDisabled] = useState(false);
225
+ const [isDrawerOpen, setIsDrawerOpen] = useState(false);
226
+
227
+ const initialConversations = {
228
+ Today: [
229
+ {
230
+ id: '1',
231
+ text: 'Hello, can you give me an example of what you can do?',
232
+ menuItems: (
233
+ <DropdownList key={`list-1`}>
234
+ <DropdownItem
235
+ value="Download"
236
+ id={`Download-1`}
237
+ onClick={() => {
238
+ const transcriptContent = generateConversationTranscript(
239
+ 'Hello, can you give me an example of what you can do?',
240
+ selectedModel
241
+ );
242
+ const blob = new Blob([transcriptContent], { type: 'text/plain;charset=utf-8' });
243
+ const fileName = `conversation-1-${new Date().toISOString().split('T')[0]}.md`;
244
+ saveAs(blob, fileName);
245
+ }}
246
+ >
247
+ Download transcript
248
+ </DropdownItem>
249
+ </DropdownList>
250
+ )
251
+ }
252
+ ]
253
+ };
254
+
255
+ const [conversations, setConversations] = useState<Conversation[] | { [key: string]: Conversation[] }>(
256
+ initialConversations
257
+ );
258
+ const [announcement, setAnnouncement] = useState<string>();
259
+ const scrollToBottomRef = useRef<HTMLDivElement>(null);
260
+ const toggleRef = useRef<HTMLButtonElement>(null);
261
+ const chatbotRef = useRef<HTMLDivElement>(null);
262
+ const historyRef = useRef<HTMLButtonElement>(null);
263
+
264
+ // Auto-scrolls to the latest message
265
+ useEffect(() => {
266
+ // don't scroll the first load - in this demo, we know we start with two messages
267
+ if (messages.length > 2) {
268
+ scrollToBottomRef.current?.scrollIntoView({ behavior: 'smooth' });
269
+ }
270
+ }, [messages]);
271
+
272
+ const onSelectModel = (_event: MouseEvent<Element, MouseEvent> | undefined, value: string | number | undefined) => {
273
+ setSelectedModel(value as string);
274
+ };
275
+
276
+ const onSelectDisplayMode = (
277
+ _event: MouseEvent<Element, MouseEvent> | undefined,
278
+ value: string | number | undefined
279
+ ) => {
280
+ setDisplayMode(value as ChatbotDisplayMode);
281
+ };
282
+
283
+ // you will likely want to come up with your own unique id function; this is for demo purposes only
284
+ const generateId = () => {
285
+ const id = Date.now() + Math.random();
286
+ return id.toString();
287
+ };
288
+
289
+ const handleSend = (message: string) => {
290
+ setIsSendButtonDisabled(true);
291
+ const newMessages: MessageProps[] = [];
292
+ // We can't use structuredClone since messages contains functions, but we can't mutate
293
+ // items that are going into state or the UI won't update correctly
294
+ messages.forEach((message) => newMessages.push(message));
295
+ // It's important to set a timestamp prop since the Message components re-render.
296
+ // The timestamps re-render with them.
297
+ const date = new Date();
298
+ newMessages.push({
299
+ id: generateId(),
300
+ role: 'user',
301
+ content: message,
302
+ name: 'User',
303
+ avatar: userAvatar,
304
+ timestamp: date.toLocaleString(),
305
+ avatarProps: { isBordered: true }
306
+ });
307
+ newMessages.push({
308
+ id: generateId(),
309
+ role: 'bot',
310
+ content: 'API response goes here',
311
+ name: 'Bot',
312
+ isLoading: true,
313
+ avatar: patternflyAvatar,
314
+ timestamp: date.toLocaleString()
315
+ });
316
+ setMessages(newMessages);
317
+ // make announcement to assistive devices that new messages have been added
318
+ setAnnouncement(`Message from User: ${message}. Message from Bot is loading.`);
319
+
320
+ // this is for demo purposes only; in a real situation, there would be an API response we would wait for
321
+ setTimeout(() => {
322
+ const loadedMessages: MessageProps[] = [];
323
+ // We can't use structuredClone since messages contains functions, but we can't mutate
324
+ // items that are going into state or the UI won't update correctly
325
+ newMessages.forEach((message) => loadedMessages.push(message));
326
+ loadedMessages.pop();
327
+ const id = generateId();
328
+ const timestamp = date.toLocaleString();
329
+ const avatar = patternflyAvatar;
330
+ const name = 'Bot';
331
+ const content = 'API response goes here';
332
+ const role = 'bot';
333
+ loadedMessages.push({
334
+ id,
335
+ role,
336
+ content,
337
+ name,
338
+ isLoading: false,
339
+ avatar,
340
+ timestamp,
341
+ actions: {
342
+ download: {
343
+ onClick: () =>
344
+ downloadMessageContent(
345
+ {
346
+ id,
347
+ role,
348
+ content,
349
+ name,
350
+ avatar,
351
+ timestamp
352
+ },
353
+ selectedModel
354
+ )
355
+ }
356
+ }
357
+ });
358
+ setMessages(loadedMessages);
359
+ // make announcement to assistive devices that new message has loaded
360
+ setAnnouncement(`Message from Bot: API response goes here`);
361
+ setIsSendButtonDisabled(false);
362
+ }, 5000);
363
+ };
364
+
365
+ const findMatchingItems = (targetValue: string) => {
366
+ let filteredConversations = Object.entries(initialConversations).reduce((acc, [key, items]) => {
367
+ const filteredItems = items.filter((item) => item.text.toLowerCase().includes(targetValue.toLowerCase()));
368
+ if (filteredItems.length > 0) {
369
+ acc[key] = filteredItems;
370
+ }
371
+ return acc;
372
+ }, {});
373
+
374
+ // append message if no items are found
375
+ if (Object.keys(filteredConversations).length === 0) {
376
+ filteredConversations = [{ id: '13', noIcon: true, text: 'No results found' }];
377
+ }
378
+ return filteredConversations;
379
+ };
380
+
381
+ const horizontalLogo = (
382
+ <Bullseye>
383
+ <Brand className="show-light" src={PFHorizontalLogoColor} alt="PatternFly" />
384
+ <Brand className="show-dark" src={PFHorizontalLogoReverse} alt="PatternFly" />
385
+ </Bullseye>
386
+ );
387
+
388
+ const iconLogo = (
389
+ <>
390
+ <Brand className="show-light" src={PFIconLogoColor} alt="PatternFly" />
391
+ <Brand className="show-dark" src={PFIconLogoReverse} alt="PatternFly" />
392
+ </>
393
+ );
394
+
395
+ const handleSkipToContent = (e) => {
396
+ e.preventDefault();
397
+ /* eslint-disable indent */
398
+ switch (displayMode) {
399
+ case ChatbotDisplayMode.default:
400
+ if (!chatbotVisible && toggleRef.current) {
401
+ toggleRef.current.focus();
402
+ }
403
+ if (chatbotVisible && chatbotRef.current) {
404
+ chatbotRef.current.focus();
405
+ }
406
+ break;
407
+
408
+ case ChatbotDisplayMode.docked:
409
+ if (chatbotRef.current) {
410
+ chatbotRef.current.focus();
411
+ }
412
+ break;
413
+ default:
414
+ if (historyRef.current) {
415
+ historyRef.current.focus();
416
+ }
417
+ break;
418
+ }
419
+ /* eslint-enable indent */
420
+ };
421
+
422
+ return (
423
+ <>
424
+ <SkipToContent onClick={handleSkipToContent} href="#">
425
+ Skip to chatbot
426
+ </SkipToContent>
427
+ <ChatbotToggle
428
+ tooltipLabel="Chatbot"
429
+ isChatbotVisible={chatbotVisible}
430
+ onToggleChatbot={function () {
431
+ setChatbotVisible(!chatbotVisible);
432
+ }}
433
+ id="chatbot-toggle"
434
+ ref={toggleRef}
435
+ />
436
+ <Chatbot isVisible={chatbotVisible} displayMode={displayMode} ref={chatbotRef}>
437
+ <ChatbotConversationHistoryNav
438
+ displayMode={displayMode}
439
+ onDrawerToggle={() => {
440
+ setIsDrawerOpen(!isDrawerOpen);
441
+ setConversations(initialConversations);
442
+ }}
443
+ isDrawerOpen={isDrawerOpen}
444
+ setIsDrawerOpen={setIsDrawerOpen}
445
+ activeItemId="1"
446
+ // eslint-disable-next-line no-console
447
+ onSelectActiveItem={(e, selectedItem) => console.log(`Selected history item with id ${selectedItem}`)}
448
+ conversations={conversations}
449
+ onNewChat={() => {
450
+ setIsDrawerOpen(!isDrawerOpen);
451
+ setMessages([]);
452
+ setConversations(initialConversations);
453
+ }}
454
+ handleTextInputChange={(value: string) => {
455
+ if (value === '') {
456
+ setConversations(initialConversations);
457
+ }
458
+ // this is where you would perform search on the items in the drawer
459
+ // and update the state
460
+ const newConversations: { [key: string]: Conversation[] } = findMatchingItems(value);
461
+ setConversations(newConversations);
462
+ }}
463
+ drawerContent={
464
+ <>
465
+ <ChatbotHeader>
466
+ <ChatbotHeaderMain>
467
+ <ChatbotHeaderMenu
468
+ ref={historyRef}
469
+ aria-expanded={isDrawerOpen}
470
+ onMenuToggle={() => setIsDrawerOpen(!isDrawerOpen)}
471
+ />
472
+ <ChatbotHeaderTitle
473
+ displayMode={displayMode}
474
+ showOnFullScreen={horizontalLogo}
475
+ showOnDefault={iconLogo}
476
+ ></ChatbotHeaderTitle>
477
+ </ChatbotHeaderMain>
478
+ <ChatbotHeaderActions>
479
+ <ChatbotHeaderSelectorDropdown value={selectedModel} onSelect={onSelectModel}>
480
+ <DropdownList>
481
+ <DropdownItem value="Granite 7B" key="granite">
482
+ Granite 7B
483
+ </DropdownItem>
484
+ <DropdownItem value="Llama 3.0" key="llama">
485
+ Llama 3.0
486
+ </DropdownItem>
487
+ <DropdownItem value="Mistral 3B" key="mistral">
488
+ Mistral 3B
489
+ </DropdownItem>
490
+ </DropdownList>
491
+ </ChatbotHeaderSelectorDropdown>
492
+ <ChatbotHeaderOptionsDropdown onSelect={onSelectDisplayMode}>
493
+ <DropdownGroup label="Display mode">
494
+ <DropdownList>
495
+ <DropdownItem
496
+ value={ChatbotDisplayMode.default}
497
+ key="switchDisplayOverlay"
498
+ icon={<OutlinedWindowRestoreIcon aria-hidden />}
499
+ isSelected={displayMode === ChatbotDisplayMode.default}
500
+ >
501
+ <span>Overlay</span>
502
+ </DropdownItem>
503
+ <DropdownItem
504
+ value={ChatbotDisplayMode.docked}
505
+ key="switchDisplayDock"
506
+ icon={<OpenDrawerRightIcon aria-hidden />}
507
+ isSelected={displayMode === ChatbotDisplayMode.docked}
508
+ >
509
+ <span>Dock to window</span>
510
+ </DropdownItem>
511
+ <DropdownItem
512
+ value={ChatbotDisplayMode.fullscreen}
513
+ key="switchDisplayFullscreen"
514
+ icon={<ExpandIcon aria-hidden />}
515
+ isSelected={displayMode === ChatbotDisplayMode.fullscreen}
516
+ >
517
+ <span>Fullscreen</span>
518
+ </DropdownItem>
519
+ </DropdownList>
520
+ </DropdownGroup>
521
+ </ChatbotHeaderOptionsDropdown>
522
+ </ChatbotHeaderActions>
523
+ </ChatbotHeader>
524
+ <ChatbotContent>
525
+ {/* Update the announcement prop on MessageBox whenever a new message is sent
526
+ so that users of assistive devices receive sufficient context */}
527
+ <MessageBox announcement={announcement}>
528
+ <ChatbotWelcomePrompt
529
+ title="Hi, ChatBot User!"
530
+ description="How can I help you today?"
531
+ prompts={welcomePrompts}
532
+ />
533
+ {/* This code block enables scrolling to the top of the last message.
534
+ You can instead choose to move the div with scrollToBottomRef on it below
535
+ the map of messages, so that users are forced to scroll to the bottom.
536
+ If you are using streaming, you will want to take a different approach;
537
+ see: https://github.com/patternfly/chatbot/issues/201#issuecomment-2400725173 */}
538
+ {messages.map((message, index) => {
539
+ if (index === messages.length - 1) {
540
+ return (
541
+ <>
542
+ <div ref={scrollToBottomRef}></div>
543
+ <Message key={message.id} {...message} />
544
+ </>
545
+ );
546
+ }
547
+ return <Message key={message.id} {...message} />;
548
+ })}
549
+ </MessageBox>
550
+ </ChatbotContent>
551
+ <ChatbotFooter>
552
+ <MessageBar
553
+ onSendMessage={handleSend}
554
+ hasMicrophoneButton
555
+ isSendButtonDisabled={isSendButtonDisabled}
556
+ />
557
+ <ChatbotFootnote {...footnoteProps} />
558
+ </ChatbotFooter>
559
+ </>
560
+ }
561
+ ></ChatbotConversationHistoryNav>
562
+ </Chatbot>
563
+ </>
564
+ );
565
+ };
@@ -9,7 +9,7 @@
9
9
  flex-direction: column;
10
10
  width: 30rem;
11
11
  height: 70vh;
12
- background-color: var(--pf-t--chatbot--background);
12
+ background-color: var(--pf-t--global--background--color--secondary--default);
13
13
  border-radius: var(--pf-t--global--border--radius--medium);
14
14
  box-shadow: var(--pf-t--global--box-shadow--lg);
15
15
  font-size: var(--pf-t--global--font--size--md);
@@ -3,7 +3,7 @@
3
3
  // ============================================================================
4
4
  .pf-chatbot__content {
5
5
  position: relative;
6
- background-color: var(--pf-t--chatbot--background);
6
+ background-color: var(--pf-t--global--background--color--secondary--default);
7
7
  overflow-y: auto;
8
8
  overflow: hidden; // needed in Red Hat Developer Hub workspace
9
9
  flex: 1; // needed in Composer AI
@@ -9,10 +9,22 @@
9
9
  // Drawer input
10
10
  // ----------------------------------------------------------------------------
11
11
  .pf-chatbot__input {
12
+ }
13
+ // Drawer title
14
+ // ----------------------------------------------------------------------------
15
+ .pf-chatbot__title-container {
12
16
  padding-inline-start: var(--pf-t--global--spacer--lg);
13
17
  padding-inline-end: var(--pf-t--global--spacer--lg);
18
+ display: flex;
19
+ flex-direction: column;
20
+ row-gap: var(--pf-t--global--spacer--sm);
21
+ }
22
+ // Drawer title icon
23
+ // ----------------------------------------------------------------------------
24
+ .pf-chatbot__title-icon {
25
+ padding-inline-end: var(--pf-t--global--spacer--md);
26
+ padding-inline-start: var(--pf-t--global--spacer--sm);
14
27
  }
15
-
16
28
  // Drawer menu
17
29
  // ----------------------------------------------------------------------------
18
30
  .pf-v6-c-menu {
@@ -75,7 +87,7 @@
75
87
  --pf-v6-c-drawer__panel--BackgroundColor: var(--pf-t--global--background--color--floating--default);
76
88
  --pf-v6-c-drawer__panel--PaddingBlockStart: var(--pf-t--global--spacer--lg);
77
89
  --pf-v6-c-drawer__panel--PaddingBlockEnd: var(--pf-t--global--spacer--lg);
78
- --pf-v6-c-drawer__panel--RowGap: var(--pf-t--global--spacer--lg);
90
+ --pf-v6-c-drawer__panel--RowGap: var(--pf-t--global--spacer--gap--group-to-group--vertical--default);
79
91
  overflow-x: hidden;
80
92
  overflow-y: hidden;
81
93
  }
@@ -104,6 +104,23 @@ describe('ChatbotConversationHistoryNav', () => {
104
104
  expect(screen.getByTestId('chatbot-nav-drawer-actions')).toHaveClass('pf-v6-c-drawer__actions--reversed');
105
105
  });
106
106
 
107
+ it('should disable new chat button', () => {
108
+ render(
109
+ <ChatbotConversationHistoryNav
110
+ onDrawerToggle={onDrawerToggle}
111
+ isDrawerOpen={true}
112
+ displayMode={ChatbotDisplayMode.fullscreen}
113
+ setIsDrawerOpen={jest.fn()}
114
+ reverseButtonOrder
115
+ conversations={initialConversations}
116
+ newChatButtonProps={{ isDisabled: true }}
117
+ onNewChat={jest.fn()}
118
+ />
119
+ );
120
+
121
+ expect(screen.getByRole('button', { name: 'New chat' })).toBeDisabled();
122
+ });
123
+
107
124
  it('should not apply the reversed class when reverseButtonOrder is false', () => {
108
125
  render(
109
126
  <ChatbotConversationHistoryNav
@@ -433,4 +450,45 @@ describe('ChatbotConversationHistoryNav', () => {
433
450
  );
434
451
  expect(screen.getByTestId('drawer')).toHaveClass('pf-m-compact');
435
452
  });
453
+
454
+ it('should display the default title', () => {
455
+ render(
456
+ <ChatbotConversationHistoryNav
457
+ onDrawerToggle={onDrawerToggle}
458
+ isDrawerOpen={true}
459
+ displayMode={ChatbotDisplayMode.fullscreen}
460
+ setIsDrawerOpen={jest.fn()}
461
+ conversations={initialConversations}
462
+ />
463
+ );
464
+ expect(screen.getByText('Chat history')).toBeInTheDocument();
465
+ });
466
+
467
+ it('should display the custom title', () => {
468
+ render(
469
+ <ChatbotConversationHistoryNav
470
+ title="PatternFly history"
471
+ onDrawerToggle={onDrawerToggle}
472
+ isDrawerOpen={true}
473
+ displayMode={ChatbotDisplayMode.fullscreen}
474
+ setIsDrawerOpen={jest.fn()}
475
+ conversations={initialConversations}
476
+ />
477
+ );
478
+ expect(screen.getByText('PatternFly history')).toBeInTheDocument();
479
+ });
480
+
481
+ it('should display the clock icon', () => {
482
+ const { container } = render(
483
+ <ChatbotConversationHistoryNav
484
+ onDrawerToggle={onDrawerToggle}
485
+ isDrawerOpen={true}
486
+ displayMode={ChatbotDisplayMode.fullscreen}
487
+ setIsDrawerOpen={jest.fn()}
488
+ conversations={initialConversations}
489
+ />
490
+ );
491
+ const iconElement = container.querySelector('.pf-chatbot__title-icon');
492
+ expect(iconElement).toBeInTheDocument();
493
+ });
436
494
  });