@patternfly/chatbot 6.4.1 → 6.5.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.
Files changed (115) hide show
  1. package/dist/cjs/AttachMenu/AttachMenu.d.ts +8 -2
  2. package/dist/cjs/AttachMenu/AttachMenu.js +2 -2
  3. package/dist/cjs/ChatbotContent/ChatbotContent.d.ts +2 -0
  4. package/dist/cjs/ChatbotContent/ChatbotContent.js +2 -2
  5. package/dist/cjs/ChatbotContent/ChatbotContent.test.js +4 -0
  6. package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.d.ts +3 -1
  7. package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.js +3 -3
  8. package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.test.js +4 -0
  9. package/dist/cjs/ChatbotFooter/ChatbotFooter.d.ts +5 -2
  10. package/dist/cjs/ChatbotFooter/ChatbotFooter.js +2 -2
  11. package/dist/cjs/ChatbotFooter/ChatbotFooter.test.js +5 -1
  12. package/dist/cjs/CodeModal/CodeModal.js +40 -4
  13. package/dist/cjs/FileDetailsLabel/FileDetailsLabel.d.ts +2 -1
  14. package/dist/cjs/Message/CodeBlockMessage/CodeBlockMessage.d.ts +5 -1
  15. package/dist/cjs/Message/CodeBlockMessage/CodeBlockMessage.js +3 -3
  16. package/dist/cjs/Message/Message.d.ts +6 -19
  17. package/dist/cjs/Message/Message.js +10 -7
  18. package/dist/cjs/Message/Message.test.js +38 -0
  19. package/dist/cjs/Message/MessageLoading.d.ts +2 -1
  20. package/dist/cjs/Message/MessageLoading.js +1 -1
  21. package/dist/cjs/Message/TableMessage/TableMessage.d.ts +4 -1
  22. package/dist/cjs/Message/TableMessage/TableMessage.js +2 -2
  23. package/dist/cjs/Message/TextMessage/TextMessage.d.ts +4 -1
  24. package/dist/cjs/Message/TextMessage/TextMessage.js +2 -2
  25. package/dist/cjs/MessageBar/AttachButton.d.ts +2 -0
  26. package/dist/cjs/MessageBar/AttachButton.js +2 -2
  27. package/dist/cjs/MessageBar/AttachButton.test.js +4 -0
  28. package/dist/cjs/MessageBar/MessageBar.d.ts +16 -6
  29. package/dist/cjs/MessageBar/MessageBar.js +6 -5
  30. package/dist/cjs/MessageBar/MessageBar.test.js +62 -0
  31. package/dist/cjs/__mocks__/monaco-editor.d.ts +11 -0
  32. package/dist/cjs/__mocks__/monaco-editor.js +18 -0
  33. package/dist/cjs/__mocks__/rehype-highlight.d.ts +2 -0
  34. package/dist/cjs/__mocks__/rehype-highlight.js +4 -0
  35. package/dist/css/main.css +81 -10
  36. package/dist/css/main.css.map +1 -1
  37. package/dist/esm/AttachMenu/AttachMenu.d.ts +8 -2
  38. package/dist/esm/AttachMenu/AttachMenu.js +2 -2
  39. package/dist/esm/ChatbotContent/ChatbotContent.d.ts +2 -0
  40. package/dist/esm/ChatbotContent/ChatbotContent.js +2 -2
  41. package/dist/esm/ChatbotContent/ChatbotContent.test.js +4 -0
  42. package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.d.ts +3 -1
  43. package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.js +3 -3
  44. package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.test.js +4 -0
  45. package/dist/esm/ChatbotFooter/ChatbotFooter.d.ts +5 -2
  46. package/dist/esm/ChatbotFooter/ChatbotFooter.js +2 -2
  47. package/dist/esm/ChatbotFooter/ChatbotFooter.test.js +5 -1
  48. package/dist/esm/CodeModal/CodeModal.js +42 -6
  49. package/dist/esm/FileDetailsLabel/FileDetailsLabel.d.ts +2 -1
  50. package/dist/esm/Message/CodeBlockMessage/CodeBlockMessage.d.ts +5 -1
  51. package/dist/esm/Message/CodeBlockMessage/CodeBlockMessage.js +3 -3
  52. package/dist/esm/Message/Message.d.ts +6 -19
  53. package/dist/esm/Message/Message.js +10 -7
  54. package/dist/esm/Message/Message.test.js +39 -1
  55. package/dist/esm/Message/MessageLoading.d.ts +2 -1
  56. package/dist/esm/Message/MessageLoading.js +1 -1
  57. package/dist/esm/Message/TableMessage/TableMessage.d.ts +4 -1
  58. package/dist/esm/Message/TableMessage/TableMessage.js +2 -2
  59. package/dist/esm/Message/TextMessage/TextMessage.d.ts +4 -1
  60. package/dist/esm/Message/TextMessage/TextMessage.js +2 -2
  61. package/dist/esm/MessageBar/AttachButton.d.ts +2 -0
  62. package/dist/esm/MessageBar/AttachButton.js +2 -2
  63. package/dist/esm/MessageBar/AttachButton.test.js +4 -0
  64. package/dist/esm/MessageBar/MessageBar.d.ts +16 -6
  65. package/dist/esm/MessageBar/MessageBar.js +6 -5
  66. package/dist/esm/MessageBar/MessageBar.test.js +62 -0
  67. package/dist/esm/__mocks__/monaco-editor.d.ts +11 -0
  68. package/dist/esm/__mocks__/monaco-editor.js +18 -0
  69. package/dist/esm/__mocks__/rehype-highlight.d.ts +2 -0
  70. package/dist/esm/__mocks__/rehype-highlight.js +2 -0
  71. package/dist/tsconfig.tsbuildinfo +1 -1
  72. package/package.json +5 -2
  73. package/patternfly-docs/content/extensions/chatbot/examples/Messages/UserMessageWithExtraContent.tsx +3 -1
  74. package/patternfly-docs/content/extensions/chatbot/examples/UI/ChatbotHeaderDrawerWithActions.tsx +14 -14
  75. package/patternfly-docs/content/extensions/chatbot/examples/UI/ChatbotHeaderDrawerWithSelection.tsx +14 -14
  76. package/patternfly-docs/content/extensions/chatbot/examples/UI/ChatbotMessageBarAttach.tsx +2 -2
  77. package/patternfly-docs/content/extensions/chatbot/examples/UI/ChatbotMessageBarIndicatorThinking.tsx +15 -0
  78. package/patternfly-docs/content/extensions/chatbot/examples/UI/Settings.tsx +1 -1
  79. package/patternfly-docs/content/extensions/chatbot/examples/UI/UI.md +10 -0
  80. package/patternfly-docs/content/extensions/chatbot/examples/demos/Chatbot.md +12 -4
  81. package/patternfly-docs/content/extensions/chatbot/examples/demos/ChatbotAttachmentMenu.tsx +2 -2
  82. package/patternfly-docs/content/extensions/chatbot/examples/demos/ChatbotTranscripts.tsx +1 -1
  83. package/patternfly-docs/content/extensions/chatbot/examples/demos/WhiteEmbeddedChatbot.tsx +451 -0
  84. package/patternfly-docs/patternfly-docs.config.js +1 -0
  85. package/src/AttachMenu/AttachMenu.tsx +26 -11
  86. package/src/Chatbot/Chatbot.scss +23 -1
  87. package/src/ChatbotContent/ChatbotContent.scss +4 -0
  88. package/src/ChatbotContent/ChatbotContent.test.tsx +5 -0
  89. package/src/ChatbotContent/ChatbotContent.tsx +4 -1
  90. package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.test.tsx +5 -0
  91. package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.tsx +7 -4
  92. package/src/ChatbotFooter/ChatbotFooter.scss +21 -0
  93. package/src/ChatbotFooter/ChatbotFooter.test.tsx +10 -1
  94. package/src/ChatbotFooter/ChatbotFooter.tsx +10 -3
  95. package/src/ChatbotHeader/ChatbotHeader.scss +19 -0
  96. package/src/CodeModal/CodeModal.tsx +58 -7
  97. package/src/FileDetailsLabel/FileDetailsLabel.tsx +2 -2
  98. package/src/Message/CodeBlockMessage/CodeBlockMessage.scss +7 -2
  99. package/src/Message/CodeBlockMessage/CodeBlockMessage.tsx +9 -2
  100. package/src/Message/Message.test.tsx +56 -1
  101. package/src/Message/Message.tsx +14 -26
  102. package/src/Message/MessageLoading.scss +7 -0
  103. package/src/Message/MessageLoading.tsx +2 -2
  104. package/src/Message/TableMessage/TableMessage.scss +4 -0
  105. package/src/Message/TableMessage/TableMessage.tsx +6 -2
  106. package/src/Message/TextMessage/TextMessage.scss +12 -0
  107. package/src/Message/TextMessage/TextMessage.tsx +11 -2
  108. package/src/Message/UserFeedback/UserFeedback.scss +2 -1
  109. package/src/MessageBar/AttachButton.test.tsx +4 -0
  110. package/src/MessageBar/AttachButton.tsx +4 -1
  111. package/src/MessageBar/MessageBar.scss +11 -5
  112. package/src/MessageBar/MessageBar.test.tsx +102 -1
  113. package/src/MessageBar/MessageBar.tsx +44 -11
  114. package/src/__mocks__/monaco-editor.ts +19 -0
  115. package/src/__mocks__/rehype-highlight.ts +3 -0
@@ -0,0 +1,451 @@
1
+ import { useEffect, useRef, useState, FunctionComponent, MouseEvent } from 'react';
2
+
3
+ import {
4
+ Bullseye,
5
+ Brand,
6
+ DropdownList,
7
+ DropdownItem,
8
+ Page,
9
+ Masthead,
10
+ MastheadMain,
11
+ MastheadBrand,
12
+ MastheadLogo,
13
+ PageSidebarBody,
14
+ PageSidebar,
15
+ MastheadToggle,
16
+ PageToggleButton,
17
+ SkipToContent
18
+ } from '@patternfly/react-core';
19
+
20
+ import Chatbot, { ChatbotDisplayMode } from '@patternfly/chatbot/dist/dynamic/Chatbot';
21
+ import ChatbotContent from '@patternfly/chatbot/dist/dynamic/ChatbotContent';
22
+ import ChatbotWelcomePrompt from '@patternfly/chatbot/dist/dynamic/ChatbotWelcomePrompt';
23
+ import ChatbotFooter, { ChatbotFootnote } from '@patternfly/chatbot/dist/dynamic/ChatbotFooter';
24
+ import MessageBar from '@patternfly/chatbot/dist/dynamic/MessageBar';
25
+ import MessageBox from '@patternfly/chatbot/dist/dynamic/MessageBox';
26
+ import Message, { MessageProps } from '@patternfly/chatbot/dist/dynamic/Message';
27
+ import ChatbotConversationHistoryNav, {
28
+ Conversation
29
+ } from '@patternfly/chatbot/dist/dynamic/ChatbotConversationHistoryNav';
30
+ import ChatbotHeader, {
31
+ ChatbotHeaderMenu,
32
+ ChatbotHeaderMain,
33
+ ChatbotHeaderTitle,
34
+ ChatbotHeaderActions,
35
+ ChatbotHeaderSelectorDropdown
36
+ } from '@patternfly/chatbot/dist/dynamic/ChatbotHeader';
37
+
38
+ import PFHorizontalLogoColor from '../UI/PF-HorizontalLogo-Color.svg';
39
+ import PFHorizontalLogoReverse from '../UI/PF-HorizontalLogo-Reverse.svg';
40
+ import { BarsIcon } from '@patternfly/react-icons';
41
+ import userAvatar from '../Messages/user_avatar.svg';
42
+ import patternflyAvatar from '../Messages/patternfly_avatar.jpg';
43
+ import '@patternfly/react-core/dist/styles/base.css';
44
+ import '@patternfly/chatbot/dist/css/main.css';
45
+
46
+ const footnoteProps = {
47
+ label: 'ChatBot uses AI. Check for mistakes.',
48
+ popover: {
49
+ title: 'Verify information',
50
+ 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.`,
51
+ bannerImage: {
52
+ src: 'https://cdn.dribbble.com/userupload/10651749/file/original-8a07b8e39d9e8bf002358c66fce1223e.gif',
53
+ alt: 'Example image for footnote popover'
54
+ },
55
+ cta: {
56
+ label: 'Dismiss',
57
+ onClick: () => {
58
+ alert('Do something!');
59
+ }
60
+ },
61
+ link: {
62
+ label: 'View AI policy',
63
+ url: 'https://www.redhat.com/'
64
+ }
65
+ }
66
+ };
67
+
68
+ const markdown = `A paragraph with *emphasis* and **strong importance**.
69
+
70
+ > A block quote with ~strikethrough~ and a URL: https://reactjs.org.
71
+
72
+ Here is an inline code - \`() => void\`
73
+
74
+ Here is some YAML code:
75
+
76
+ ~~~yaml
77
+ apiVersion: helm.openshift.io/v1beta1/
78
+ kind: HelmChartRepository
79
+ metadata:
80
+ name: azure-sample-repo0oooo00ooo
81
+ spec:
82
+ connectionConfig:
83
+ url: https://raw.githubusercontent.com/Azure-Samples/helm-charts/master/docs
84
+ ~~~
85
+
86
+ Here is some JavaScript code:
87
+
88
+ ~~~js
89
+ const MessageLoading = () => (
90
+ <div className="pf-chatbot__message-loading">
91
+ <span className="pf-chatbot__message-loading-dots">
92
+ <span className="pf-v6-screen-reader">Loading message</span>
93
+ </span>
94
+ </div>
95
+ );
96
+
97
+ export default MessageLoading;
98
+
99
+ ~~~
100
+
101
+ Here is a table:
102
+
103
+ | Version | GA date | User role
104
+ |-|-|-|
105
+ | 2.5 | September 30, 2024 | Administrator |
106
+ | 2.5 | June 27, 2023 | Editor |
107
+ | 3.0 | April 1, 2025 | Administrator
108
+ `;
109
+
110
+ // It's important to set a date and timestamp prop since the Message components re-render.
111
+ // The timestamps re-render with them.
112
+ const date = new Date();
113
+
114
+ const initialMessages: MessageProps[] = [
115
+ {
116
+ id: '1',
117
+ role: 'user',
118
+ content: 'Hello, can you give me an example of what you can do?',
119
+ name: 'User',
120
+ avatar: userAvatar,
121
+ timestamp: date.toLocaleString(),
122
+ avatarProps: { isBordered: true },
123
+ isPrimary: true
124
+ },
125
+ {
126
+ id: '2',
127
+ role: 'bot',
128
+ content: markdown,
129
+ name: 'Bot',
130
+ avatar: patternflyAvatar,
131
+ timestamp: date.toLocaleString(),
132
+ actions: {
133
+ // eslint-disable-next-line no-console
134
+ positive: { onClick: () => console.log('Good response') },
135
+ // eslint-disable-next-line no-console
136
+ negative: { onClick: () => console.log('Bad response') },
137
+ // eslint-disable-next-line no-console
138
+ copy: { onClick: () => console.log('Copy') },
139
+ // eslint-disable-next-line no-console
140
+ download: { onClick: () => console.log('Download') },
141
+ // eslint-disable-next-line no-console
142
+ listen: { onClick: () => console.log('Listen') }
143
+ },
144
+ isPrimary: true,
145
+ attachments: [{ name: 'auth-operator.yml', id: '1' }]
146
+ }
147
+ ];
148
+
149
+ const welcomePrompts = [
150
+ {
151
+ title: 'Set up account',
152
+ message: 'Choose the necessary settings and preferences for your account.'
153
+ },
154
+ {
155
+ title: 'Troubleshoot issue',
156
+ message: 'Find documentation and instructions to resolve your issue.'
157
+ }
158
+ ];
159
+
160
+ const initialConversations = {
161
+ Today: [{ id: '1', text: 'Hello, can you give me an example of what you can do?' }],
162
+ 'This month': [
163
+ {
164
+ id: '2',
165
+ text: 'Enterprise Linux installation and setup'
166
+ },
167
+ { id: '3', text: 'Troubleshoot system crash' }
168
+ ],
169
+ March: [
170
+ { id: '4', text: 'Ansible security and updates' },
171
+ { id: '5', text: 'Red Hat certification' },
172
+ { id: '6', text: 'Lightspeed user documentation' }
173
+ ],
174
+ February: [
175
+ { id: '7', text: 'Crashing pod assistance' },
176
+ { id: '8', text: 'OpenShift AI pipelines' },
177
+ { id: '9', text: 'Updating subscription plan' },
178
+ { id: '10', text: 'Red Hat licensing options' }
179
+ ],
180
+ January: [
181
+ { id: '11', text: 'RHEL system performance' },
182
+ { id: '12', text: 'Manage user accounts' }
183
+ ]
184
+ };
185
+
186
+ export const EmbeddedChatbotDemo: FunctionComponent = () => {
187
+ const [messages, setMessages] = useState<MessageProps[]>(initialMessages);
188
+ const [selectedModel, setSelectedModel] = useState('Granite 7B');
189
+ const [isSendButtonDisabled, setIsSendButtonDisabled] = useState(false);
190
+ const [isDrawerOpen, setIsDrawerOpen] = useState(false);
191
+ const [conversations, setConversations] = useState<Conversation[] | { [key: string]: Conversation[] }>(
192
+ initialConversations
193
+ );
194
+ const [isSidebarOpen, setIsSidebarOpen] = useState(false);
195
+ const [announcement, setAnnouncement] = useState<string>();
196
+ const scrollToBottomRef = useRef<HTMLDivElement>(null);
197
+ const historyRef = useRef<HTMLButtonElement>(null);
198
+
199
+ const displayMode = ChatbotDisplayMode.embedded;
200
+ // Auto-scrolls to the latest message
201
+ useEffect(() => {
202
+ // don't scroll the first load - in this demo, we know we start with two messages
203
+ if (messages.length > 2) {
204
+ scrollToBottomRef.current?.scrollIntoView({ behavior: 'smooth' });
205
+ }
206
+ }, [messages]);
207
+
208
+ const onSelectModel = (_event: MouseEvent<Element, MouseEvent> | undefined, value: string | number | undefined) => {
209
+ setSelectedModel(value as string);
210
+ };
211
+
212
+ // you will likely want to come up with your own unique id function; this is for demo purposes only
213
+ const generateId = () => {
214
+ const id = Date.now() + Math.random();
215
+ return id.toString();
216
+ };
217
+
218
+ const handleSend = (message: string) => {
219
+ setIsSendButtonDisabled(true);
220
+ const newMessages: MessageProps[] = [];
221
+ // We can't use structuredClone since messages contains functions, but we can't mutate
222
+ // items that are going into state or the UI won't update correctly
223
+ messages.forEach((message) => newMessages.push(message));
224
+ // It's important to set a timestamp prop since the Message components re-render.
225
+ // The timestamps re-render with them.
226
+ const date = new Date();
227
+ newMessages.push({
228
+ id: generateId(),
229
+ role: 'user',
230
+ content: message,
231
+ name: 'User',
232
+ avatar: userAvatar,
233
+ timestamp: date.toLocaleString(),
234
+ avatarProps: { isBordered: true },
235
+ isPrimary: true
236
+ });
237
+ newMessages.push({
238
+ id: generateId(),
239
+ role: 'bot',
240
+ content: 'API response goes here',
241
+ name: 'Bot',
242
+ avatar: patternflyAvatar,
243
+ isLoading: true,
244
+ timestamp: date.toLocaleString(),
245
+ isPrimary: true
246
+ });
247
+ setMessages(newMessages);
248
+ // make announcement to assistive devices that new messages have been added
249
+ setAnnouncement(`Message from User: ${message}. Message from Bot is loading.`);
250
+
251
+ // this is for demo purposes only; in a real situation, there would be an API response we would wait for
252
+ setTimeout(() => {
253
+ const loadedMessages: MessageProps[] = [];
254
+ // we can't use structuredClone since messages contains functions, but we can't mutate
255
+ // items that are going into state or the UI won't update correctly
256
+ newMessages.forEach((message) => loadedMessages.push(message));
257
+ loadedMessages.pop();
258
+ loadedMessages.push({
259
+ id: generateId(),
260
+ role: 'bot',
261
+ content: 'API response goes here',
262
+ name: 'Bot',
263
+ avatar: patternflyAvatar,
264
+ isLoading: false,
265
+ actions: {
266
+ // eslint-disable-next-line no-console
267
+ positive: { onClick: () => console.log('Good response') },
268
+ // eslint-disable-next-line no-console
269
+ negative: { onClick: () => console.log('Bad response') },
270
+ // eslint-disable-next-line no-console
271
+ copy: { onClick: () => console.log('Copy') },
272
+ // eslint-disable-next-line no-console
273
+ download: { onClick: () => console.log('Download') },
274
+ // eslint-disable-next-line no-console
275
+ listen: { onClick: () => console.log('Listen') }
276
+ },
277
+ timestamp: date.toLocaleString(),
278
+ isPrimary: true
279
+ });
280
+ setMessages(loadedMessages);
281
+ // make announcement to assistive devices that new message has loaded
282
+ setAnnouncement(`Message from Bot: API response goes here`);
283
+ setIsSendButtonDisabled(false);
284
+ }, 5000);
285
+ };
286
+
287
+ const findMatchingItems = (targetValue: string) => {
288
+ let filteredConversations = Object.entries(initialConversations).reduce((acc, [key, items]) => {
289
+ const filteredItems = items.filter((item) => item.text.toLowerCase().includes(targetValue.toLowerCase()));
290
+ if (filteredItems.length > 0) {
291
+ acc[key] = filteredItems;
292
+ }
293
+ return acc;
294
+ }, {});
295
+
296
+ // append message if no items are found
297
+ if (Object.keys(filteredConversations).length === 0) {
298
+ filteredConversations = [{ id: '13', noIcon: true, text: 'No results found' }];
299
+ }
300
+ return filteredConversations;
301
+ };
302
+
303
+ const horizontalLogo = (
304
+ <Bullseye>
305
+ <Brand className="show-light" src={PFHorizontalLogoColor} alt="PatternFly" />
306
+ <Brand className="show-dark" src={PFHorizontalLogoReverse} alt="PatternFly" />
307
+ </Bullseye>
308
+ );
309
+
310
+ const masthead = (
311
+ <Masthead>
312
+ <MastheadMain>
313
+ <MastheadToggle>
314
+ <PageToggleButton
315
+ variant="plain"
316
+ aria-label="Global navigation"
317
+ isSidebarOpen={isSidebarOpen}
318
+ onSidebarToggle={() => setIsSidebarOpen(!isSidebarOpen)}
319
+ id="fill-nav-toggle"
320
+ >
321
+ <BarsIcon />
322
+ </PageToggleButton>
323
+ </MastheadToggle>
324
+ <MastheadBrand>
325
+ <MastheadLogo href="https://patternfly.org" target="_blank">
326
+ Logo
327
+ </MastheadLogo>
328
+ </MastheadBrand>
329
+ </MastheadMain>
330
+ </Masthead>
331
+ );
332
+
333
+ const sidebar = (
334
+ <PageSidebar isSidebarOpen={isSidebarOpen} id="fill-sidebar">
335
+ <PageSidebarBody>Navigation</PageSidebarBody>
336
+ </PageSidebar>
337
+ );
338
+
339
+ const skipToChatbot = (event: MouseEvent) => {
340
+ event.preventDefault();
341
+ if (historyRef.current) {
342
+ historyRef.current.focus();
343
+ }
344
+ };
345
+
346
+ const skipToContent = (
347
+ /* You can also add a SkipToContent for your main content here */
348
+ <SkipToContent href="#" onClick={skipToChatbot}>
349
+ Skip to chatbot
350
+ </SkipToContent>
351
+ );
352
+
353
+ return (
354
+ <Page skipToContent={skipToContent} masthead={masthead} sidebar={sidebar} isContentFilled>
355
+ <Chatbot displayMode={displayMode}>
356
+ <ChatbotConversationHistoryNav
357
+ displayMode={displayMode}
358
+ onDrawerToggle={() => {
359
+ setIsDrawerOpen(!isDrawerOpen);
360
+ setConversations(initialConversations);
361
+ }}
362
+ isDrawerOpen={isDrawerOpen}
363
+ setIsDrawerOpen={setIsDrawerOpen}
364
+ activeItemId="1"
365
+ // eslint-disable-next-line no-console
366
+ onSelectActiveItem={(e, selectedItem) => console.log(`Selected history item with id ${selectedItem}`)}
367
+ conversations={conversations}
368
+ onNewChat={() => {
369
+ setIsDrawerOpen(!isDrawerOpen);
370
+ setMessages([]);
371
+ setConversations(initialConversations);
372
+ }}
373
+ handleTextInputChange={(value: string) => {
374
+ if (value === '') {
375
+ setConversations(initialConversations);
376
+ }
377
+ // this is where you would perform search on the items in the drawer
378
+ // and update the state
379
+ const newConversations: { [key: string]: Conversation[] } = findMatchingItems(value);
380
+ setConversations(newConversations);
381
+ }}
382
+ drawerContent={
383
+ <>
384
+ <ChatbotHeader>
385
+ <ChatbotHeaderMain>
386
+ <ChatbotHeaderMenu
387
+ ref={historyRef}
388
+ aria-expanded={isDrawerOpen}
389
+ onMenuToggle={() => setIsDrawerOpen(!isDrawerOpen)}
390
+ />
391
+ <ChatbotHeaderTitle>{horizontalLogo}</ChatbotHeaderTitle>
392
+ </ChatbotHeaderMain>
393
+ <ChatbotHeaderActions>
394
+ <ChatbotHeaderSelectorDropdown value={selectedModel} onSelect={onSelectModel}>
395
+ <DropdownList>
396
+ <DropdownItem value="Granite 7B" key="granite">
397
+ Granite 7B
398
+ </DropdownItem>
399
+ <DropdownItem value="Llama 3.0" key="llama">
400
+ Llama 3.0
401
+ </DropdownItem>
402
+ <DropdownItem value="Mistral 3B" key="mistral">
403
+ Mistral 3B
404
+ </DropdownItem>
405
+ </DropdownList>
406
+ </ChatbotHeaderSelectorDropdown>
407
+ </ChatbotHeaderActions>
408
+ </ChatbotHeader>
409
+ <ChatbotContent isPrimary>
410
+ {/* Update the announcement prop on MessageBox whenever a new message is sent
411
+ so that users of assistive devices receive sufficient context */}
412
+ <MessageBox announcement={announcement}>
413
+ <ChatbotWelcomePrompt
414
+ title="Hi, ChatBot User!"
415
+ description="How can I help you today?"
416
+ prompts={welcomePrompts}
417
+ />
418
+ {/* This code block enables scrolling to the top of the last message.
419
+ You can instead choose to move the div with scrollToBottomRef on it below
420
+ the map of messages, so that users are forced to scroll to the bottom.
421
+ If you are using streaming, you will want to take a different approach;
422
+ see: https://github.com/patternfly/chatbot/issues/201#issuecomment-2400725173 */}
423
+ {messages.map((message, index) => {
424
+ if (index === messages.length - 1) {
425
+ return (
426
+ <>
427
+ <div ref={scrollToBottomRef}></div>
428
+ <Message key={message.id} {...message} />
429
+ </>
430
+ );
431
+ }
432
+ return <Message key={message.id} {...message} />;
433
+ })}
434
+ </MessageBox>
435
+ </ChatbotContent>
436
+ <ChatbotFooter isPrimary>
437
+ <MessageBar
438
+ isPrimary
439
+ onSendMessage={handleSend}
440
+ hasMicrophoneButton
441
+ isSendButtonDisabled={isSendButtonDisabled}
442
+ />
443
+ <ChatbotFootnote {...footnoteProps} />
444
+ </ChatbotFooter>
445
+ </>
446
+ }
447
+ ></ChatbotConversationHistoryNav>
448
+ </Chatbot>
449
+ </Page>
450
+ );
451
+ };
@@ -4,5 +4,6 @@ module.exports = {
4
4
  topNavItems: [],
5
5
  hasThemeSwitcher: true,
6
6
  hasRTLSwitcher: true,
7
+ hasHighContrastSwitcher: true,
7
8
  port: 8006
8
9
  };
@@ -11,14 +11,17 @@ import {
11
11
  DropdownProps,
12
12
  Dropdown,
13
13
  DropdownToggleProps,
14
- PopperOptions
14
+ PopperOptions,
15
+ MenuSearchInputProps,
16
+ SearchInputProps,
17
+ MenuSearchProps
15
18
  } from '@patternfly/react-core';
16
19
 
17
20
  export interface AttachMenuProps extends DropdownProps {
18
21
  /** Items in menu */
19
22
  filteredItems: React.ReactNode;
20
23
  /** A callback for when the input value changes. */
21
- handleTextInputChange: (value: string) => void;
24
+ handleTextInputChange?: (value: string) => void;
22
25
  /** Flag to indicate if menu is opened. */
23
26
  isOpen: boolean;
24
27
  /** Additional properties to pass to the Popper */
@@ -35,6 +38,12 @@ export interface AttachMenuProps extends DropdownProps {
35
38
  searchInputAriaLabel?: string;
36
39
  /** Toggle to be rendered */
37
40
  toggle: DropdownToggleProps | ((toggleRef: React.RefObject<any>) => React.ReactNode);
41
+ /** Additional props passed to MenuSearch component */
42
+ menuSearchProps?: Omit<MenuSearchProps, 'ref'>;
43
+ /** Additional props passed to MenuSearchInput component */
44
+ menuSearchInputProps?: Omit<MenuSearchInputProps, 'ref'>;
45
+ /** Additional props passed to SearchInput component */
46
+ searchInputProps?: SearchInputProps;
38
47
  }
39
48
 
40
49
  export const AttachMenu: FunctionComponent<AttachMenuProps> = ({
@@ -49,6 +58,9 @@ export const AttachMenu: FunctionComponent<AttachMenuProps> = ({
49
58
  searchInputPlaceholder,
50
59
  searchInputAriaLabel = 'Filter menu items',
51
60
  toggle,
61
+ menuSearchProps,
62
+ menuSearchInputProps,
63
+ searchInputProps,
52
64
  ...props
53
65
  }: AttachMenuProps) => (
54
66
  <Dropdown
@@ -61,15 +73,18 @@ export const AttachMenu: FunctionComponent<AttachMenuProps> = ({
61
73
  onSelect={onSelect}
62
74
  {...props}
63
75
  >
64
- <MenuSearch>
65
- <MenuSearchInput>
66
- <SearchInput
67
- aria-label={searchInputAriaLabel}
68
- onChange={(_event, value) => handleTextInputChange(value)}
69
- placeholder={searchInputPlaceholder}
70
- />
71
- </MenuSearchInput>
72
- </MenuSearch>
76
+ {handleTextInputChange && (
77
+ <MenuSearch {...menuSearchProps}>
78
+ <MenuSearchInput {...menuSearchInputProps}>
79
+ <SearchInput
80
+ aria-label={searchInputAriaLabel}
81
+ onChange={(_event, value) => handleTextInputChange(value)}
82
+ placeholder={searchInputPlaceholder}
83
+ {...searchInputProps}
84
+ />
85
+ </MenuSearchInput>
86
+ </MenuSearch>
87
+ )}
73
88
  {filteredItems}
74
89
  </Dropdown>
75
90
  );
@@ -28,6 +28,9 @@
28
28
  opacity: 1;
29
29
  transform: translateY(0);
30
30
  }
31
+ // for high contrast support
32
+ border: var(--pf-t--global--border--width--high-contrast--regular) solid
33
+ var(--pf-t--global--border--color--high-contrast);
31
34
 
32
35
  // 32 rem is the width of the overlay chatbot plus the insets
33
36
  // if the screen is smaller, we want to be 100%
@@ -46,6 +49,10 @@
46
49
  // Chatbot Display Mode - Docked
47
50
  // ============================================================================
48
51
  .pf-chatbot--docked {
52
+ // for high contrast support
53
+ border: unset;
54
+ border-left: var(--pf-t--global--border--width--high-contrast--regular) solid
55
+ var(--pf-t--global--border--color--high-contrast);
49
56
  inset-block-end: 0;
50
57
  inset-inline-end: 0;
51
58
  padding: 0;
@@ -65,6 +72,8 @@
65
72
  // Chatbot Display Mode - Fullscreen
66
73
  // ============================================================================
67
74
  .pf-chatbot--fullscreen {
75
+ // for high contrast support
76
+ border: unset;
68
77
  inset-block-end: 0;
69
78
  inset-inline-end: 0;
70
79
  padding: 0;
@@ -78,6 +87,8 @@
78
87
  // Chatbot Display Mode - Embedded
79
88
  // ============================================================================
80
89
  .pf-chatbot--embedded {
90
+ // for high contrast support
91
+ border: unset;
81
92
  position: static;
82
93
  width: 100%;
83
94
  min-height: 100%;
@@ -125,12 +136,23 @@
125
136
  height: 100%;
126
137
  border-radius: 0;
127
138
  box-shadow: none;
128
- border-left: var(--pf-t--global--border--width--divider--default) solid;
129
139
  border-color: var(--pf-t--global--border--color--default);
130
140
 
131
141
  .pf-chatbot-container {
132
142
  border-radius: var(--pf-t--global--border--radius--sharp);
133
143
  }
144
+
145
+ @media screen and (min-width: 768px) {
146
+ // only want if drawer open - drawer closes/stops being inline on mobile
147
+ border-left: var(--pf-t--global--border--width--divider--default) solid;
148
+ }
149
+ }
150
+
151
+ // for high contrast support
152
+ :root:where(.pf-v6-theme-high-contrast) {
153
+ .pf-chatbot--drawer {
154
+ border: unset;
155
+ }
134
156
  }
135
157
 
136
158
  // ============================================================================
@@ -12,6 +12,10 @@
12
12
  @media screen and (max-height: 518px) {
13
13
  overflow: unset;
14
14
  }
15
+
16
+ &.pf-m-primary {
17
+ background-color: var(--pf-t--global--background--color--primary--default);
18
+ }
15
19
  }
16
20
 
17
21
  // ============================================================================
@@ -11,4 +11,9 @@ describe('ChatbotContent', () => {
11
11
  const { container } = render(<ChatbotContent className="custom-class">Chatbot Content</ChatbotContent>);
12
12
  expect(container.querySelector('.custom-class')).toBeTruthy();
13
13
  });
14
+
15
+ it('should render ChatbotContent with primary class', () => {
16
+ const { container } = render(<ChatbotContent isPrimary>Chatbot Content</ChatbotContent>);
17
+ expect(container.querySelector('.pf-m-primary')).toBeTruthy();
18
+ });
14
19
  });
@@ -8,14 +8,17 @@ export interface ChatbotContentProps extends HTMLProps<HTMLDivElement> {
8
8
  children: React.ReactNode;
9
9
  /** Custom classname for the ChatbotContent component */
10
10
  className?: string;
11
+ /** Sets background color to primary */
12
+ isPrimary?: boolean;
11
13
  }
12
14
 
13
15
  export const ChatbotContent: FunctionComponent<ChatbotContentProps> = ({
14
16
  children,
15
17
  className,
18
+ isPrimary,
16
19
  ...props
17
20
  }: ChatbotContentProps) => (
18
- <div className={`pf-chatbot__content ${className ?? ''}`} {...props}>
21
+ <div className={`pf-chatbot__content ${isPrimary ? 'pf-m-primary' : ''} ${className ?? ''}`} {...props}>
19
22
  {children}
20
23
  </div>
21
24
  );
@@ -78,4 +78,9 @@ describe('ChatbotConversationHistoryDropdown', () => {
78
78
  expect(screen.queryByText('Actions dropdown')).toBeInTheDocument();
79
79
  });
80
80
  });
81
+
82
+ it('should be able to set a custom aria-label', () => {
83
+ render(<ChatbotConversationHistoryDropdown menuItems={menuItems} aria-label="Custom conversation options" />);
84
+ expect(screen.queryByRole('menuitem', { name: /Custom conversation options/i })).toBeInTheDocument();
85
+ });
81
86
  });
@@ -15,8 +15,10 @@ export interface ChatbotConversationHistoryDropdownProps extends Omit<DropdownPr
15
15
  menuItems: React.ReactNode;
16
16
  /** Optional classname applied to conversation settings dropdown */
17
17
  menuClassName?: string;
18
- /** Tooltip content and aria-label applied to conversation settings dropdown */
18
+ /** Tooltip content applied to conversation settings dropdown */
19
19
  label?: string;
20
+ /** Aria-label applied to conversation settings dropdown */
21
+ 'aria-label'?: string;
20
22
  /** Callback for when user selects item. */
21
23
  onSelect?: (event?: React.MouseEvent, value?: string | number) => void;
22
24
  /** Id applied to dropdown menu toggle */
@@ -27,7 +29,8 @@ export const ChatbotConversationHistoryDropdown: FunctionComponent<ChatbotConver
27
29
  menuItems,
28
30
  menuClassName,
29
31
  onSelect,
30
- label,
32
+ label = 'Conversation options',
33
+ 'aria-label': ariaLabel,
31
34
  id
32
35
  }: ChatbotConversationHistoryDropdownProps) => {
33
36
  const [isOpen, setIsOpen] = useState(false);
@@ -35,7 +38,7 @@ export const ChatbotConversationHistoryDropdown: FunctionComponent<ChatbotConver
35
38
  const toggle = (toggleRef: Ref<MenuToggleElement>) => (
36
39
  <Tooltip
37
40
  className="pf-chatbot__tooltip"
38
- content={label ?? 'Conversation options'}
41
+ content={label}
39
42
  position="bottom"
40
43
  // prevents VO announcements of both aria label and tooltip
41
44
  aria="none"
@@ -43,7 +46,7 @@ export const ChatbotConversationHistoryDropdown: FunctionComponent<ChatbotConver
43
46
  <MenuToggle
44
47
  className="pf-chatbot__history-actions"
45
48
  variant="plain"
46
- aria-label={label ?? 'Conversation options'}
49
+ aria-label={ariaLabel ?? label}
47
50
  ref={toggleRef}
48
51
  isExpanded={isOpen}
49
52
  onClick={() => setIsOpen(!isOpen)}