@patternfly/chatbot 2.2.0 → 6.3.0-prerelease.1

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 (96) hide show
  1. package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.d.ts +4 -0
  2. package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.js +7 -1
  3. package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.test.js +23 -0
  4. package/dist/cjs/Message/Message.d.ts +17 -1
  5. package/dist/cjs/Message/Message.js +53 -34
  6. package/dist/cjs/Message/Message.test.js +52 -0
  7. package/dist/cjs/Message/MessageInput.d.ts +18 -0
  8. package/dist/cjs/Message/MessageInput.js +34 -0
  9. package/dist/cjs/MessageBar/MicrophoneButton.js +1 -1
  10. package/dist/cjs/MessageBox/MessageBox.js +5 -5
  11. package/dist/cjs/SourcesCard/SourcesCard.d.ts +7 -1
  12. package/dist/cjs/SourcesCard/SourcesCard.js +16 -10
  13. package/dist/cjs/SourcesCard/SourcesCard.test.js +25 -15
  14. package/dist/cjs/tracking/console_tracking_provider.d.ts +4 -5
  15. package/dist/cjs/tracking/console_tracking_provider.js +22 -15
  16. package/dist/cjs/tracking/posthog_tracking_provider.d.ts +2 -2
  17. package/dist/cjs/tracking/posthog_tracking_provider.js +21 -12
  18. package/dist/cjs/tracking/segment_tracking_provider.d.ts +2 -2
  19. package/dist/cjs/tracking/segment_tracking_provider.js +21 -12
  20. package/dist/cjs/tracking/trackingProviderProxy.d.ts +1 -1
  21. package/dist/cjs/tracking/trackingProviderProxy.js +2 -2
  22. package/dist/cjs/tracking/tracking_api.d.ts +1 -1
  23. package/dist/cjs/tracking/tracking_registry.js +46 -12
  24. package/dist/cjs/tracking/tracking_spi.d.ts +15 -5
  25. package/dist/cjs/tracking/tracking_spi.js +9 -0
  26. package/dist/cjs/tracking/umami_tracking_provider.d.ts +6 -2
  27. package/dist/cjs/tracking/umami_tracking_provider.js +66 -22
  28. package/dist/css/main.css +7 -7
  29. package/dist/css/main.css.map +1 -1
  30. package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.d.ts +4 -0
  31. package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.js +7 -1
  32. package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.test.js +23 -0
  33. package/dist/esm/Message/Message.d.ts +17 -1
  34. package/dist/esm/Message/Message.js +53 -34
  35. package/dist/esm/Message/Message.test.js +52 -0
  36. package/dist/esm/Message/MessageInput.d.ts +18 -0
  37. package/dist/esm/Message/MessageInput.js +29 -0
  38. package/dist/esm/MessageBar/MicrophoneButton.js +1 -1
  39. package/dist/esm/MessageBox/MessageBox.js +5 -5
  40. package/dist/esm/SourcesCard/SourcesCard.d.ts +7 -1
  41. package/dist/esm/SourcesCard/SourcesCard.js +17 -11
  42. package/dist/esm/SourcesCard/SourcesCard.test.js +25 -15
  43. package/dist/esm/tracking/console_tracking_provider.d.ts +4 -5
  44. package/dist/esm/tracking/console_tracking_provider.js +22 -15
  45. package/dist/esm/tracking/posthog_tracking_provider.d.ts +2 -2
  46. package/dist/esm/tracking/posthog_tracking_provider.js +21 -12
  47. package/dist/esm/tracking/segment_tracking_provider.d.ts +2 -2
  48. package/dist/esm/tracking/segment_tracking_provider.js +21 -12
  49. package/dist/esm/tracking/trackingProviderProxy.d.ts +1 -1
  50. package/dist/esm/tracking/trackingProviderProxy.js +2 -2
  51. package/dist/esm/tracking/tracking_api.d.ts +1 -1
  52. package/dist/esm/tracking/tracking_registry.js +46 -12
  53. package/dist/esm/tracking/tracking_spi.d.ts +15 -5
  54. package/dist/esm/tracking/tracking_spi.js +8 -1
  55. package/dist/esm/tracking/umami_tracking_provider.d.ts +6 -2
  56. package/dist/esm/tracking/umami_tracking_provider.js +66 -22
  57. package/dist/tsconfig.tsbuildinfo +1 -1
  58. package/package.json +2 -2
  59. package/patternfly-docs/content/extensions/chatbot/examples/Analytics/Analytics.md +18 -14
  60. package/patternfly-docs/content/extensions/chatbot/examples/Messages/BotMessage.tsx +74 -104
  61. package/patternfly-docs/content/extensions/chatbot/examples/Messages/FileDetailsLabel.tsx +48 -37
  62. package/patternfly-docs/content/extensions/chatbot/examples/Messages/MessageWithQuickResponses.tsx +10 -0
  63. package/patternfly-docs/content/extensions/chatbot/examples/Messages/MessageWithSources.tsx +51 -14
  64. package/patternfly-docs/content/extensions/chatbot/examples/Messages/Messages.md +3 -1
  65. package/patternfly-docs/content/extensions/chatbot/examples/Messages/UserMessage.tsx +80 -104
  66. package/patternfly-docs/content/extensions/chatbot/examples/UI/ChatbotHeaderDrawer.tsx +35 -2
  67. package/patternfly-docs/content/extensions/chatbot/examples/UI/ChatbotHeaderDrawerResizable.tsx +13 -2
  68. package/patternfly-docs/content/extensions/chatbot/examples/UI/UI.md +1 -1
  69. package/patternfly-docs/content/extensions/chatbot/examples/demos/Chatbot.tsx +6 -3
  70. package/patternfly-docs/content/extensions/chatbot/examples/demos/ChatbotAttachment.tsx +2 -0
  71. package/patternfly-docs/content/extensions/chatbot/examples/demos/ChatbotAttachmentMenu.tsx +2 -0
  72. package/patternfly-docs/content/extensions/chatbot/examples/demos/ChatbotInDrawer.tsx +2 -0
  73. package/patternfly-docs/content/extensions/chatbot/examples/demos/EmbeddedChatbot.tsx +2 -0
  74. package/patternfly-docs/content/extensions/chatbot/examples/demos/EmbeddedComparisonChatbot.tsx +62 -57
  75. package/patternfly-docs/content/extensions/chatbot/examples/demos/Feedback.tsx +2 -0
  76. package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.test.tsx +53 -0
  77. package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.tsx +14 -0
  78. package/src/FileDropZone/__snapshots__/FileDropZone.test.tsx.snap +1 -0
  79. package/src/Message/Message.scss +4 -0
  80. package/src/Message/Message.test.tsx +62 -0
  81. package/src/Message/Message.tsx +111 -53
  82. package/src/Message/MessageInput.tsx +59 -0
  83. package/src/MessageBar/MicrophoneButton.tsx +1 -1
  84. package/src/MessageBox/MessageBox.tsx +5 -5
  85. package/src/SourcesCard/SourcesCard.scss +3 -7
  86. package/src/SourcesCard/SourcesCard.test.tsx +30 -22
  87. package/src/SourcesCard/SourcesCard.tsx +54 -12
  88. package/src/tracking/console_tracking_provider.ts +21 -17
  89. package/src/tracking/posthog_tracking_provider.ts +20 -13
  90. package/src/tracking/segment_tracking_provider.ts +20 -13
  91. package/src/tracking/trackingProviderProxy.ts +2 -2
  92. package/src/tracking/tracking_api.ts +1 -1
  93. package/src/tracking/tracking_registry.ts +46 -13
  94. package/src/tracking/tracking_spi.ts +18 -7
  95. package/src/tracking/umami_tracking_provider.ts +76 -20
  96. package/src/SourcesCard/__snapshots__/SourcesCard.test.tsx.snap +0 -34
@@ -2,38 +2,48 @@ import React from 'react';
2
2
 
3
3
  import Message from '@patternfly/chatbot/dist/dynamic/Message';
4
4
  import userAvatar from './user_avatar.svg';
5
- import { AlertActionLink, Form, FormGroup, Radio } from '@patternfly/react-core';
5
+ import {
6
+ AlertActionLink,
7
+ MenuToggle,
8
+ MenuToggleElement,
9
+ Select,
10
+ SelectList,
11
+ SelectOption
12
+ } from '@patternfly/react-core';
6
13
 
7
14
  export const UserMessageExample: React.FunctionComponent = () => {
8
- const [variant, setVariant] = React.useState('code');
15
+ const [variant, setVariant] = React.useState<string>('Code');
16
+ const [isEditable, setIsEditable] = React.useState<boolean>(true);
17
+ const [isOpen, setIsOpen] = React.useState<boolean>(false);
18
+ const [selected, setSelected] = React.useState<string>('Message content type');
9
19
 
10
20
  /* eslint-disable indent */
11
21
  const renderContent = () => {
12
22
  switch (variant) {
13
- case 'code':
23
+ case 'Code':
14
24
  return code;
15
- case 'inlineCode':
25
+ case 'Inline code':
16
26
  return inlineCode;
17
- case 'heading':
27
+ case 'Heading':
18
28
  return heading;
19
- case 'emphasis':
29
+ case 'Emphasis':
20
30
  return emphasis;
21
- case 'blockQuotes':
31
+ case 'Block quotes':
22
32
  return blockQuotes;
23
- case 'orderedList':
33
+ case 'Ordered list':
24
34
  return orderedList;
25
- case 'unorderedList':
35
+ case 'Unordered list':
26
36
  return unorderedList;
27
- case 'moreComplexList':
37
+ case 'More complex list':
28
38
  return moreComplexList;
29
- case 'link':
39
+ case 'Link':
30
40
  return link;
31
- case 'table':
41
+ case 'Table':
32
42
  return table;
33
- case 'image':
43
+ case 'Image':
34
44
  return image;
35
45
  default:
36
- return;
46
+ return '';
37
47
  }
38
48
  };
39
49
  /* eslint-enable indent */
@@ -155,6 +165,32 @@ _Italic text, formatted with single underscores_
155
165
  )
156
166
  };
157
167
 
168
+ const onSelect = (_event: React.MouseEvent<Element, MouseEvent> | undefined, value: string | number | undefined) => {
169
+ setVariant(value);
170
+ setSelected(value as string);
171
+ setIsOpen(false);
172
+ };
173
+
174
+ const onToggleClick = () => {
175
+ setIsOpen(!isOpen);
176
+ };
177
+
178
+ const toggle = (toggleRef: React.Ref<MenuToggleElement>) => (
179
+ <MenuToggle
180
+ className="pf-v6-u-mb-md"
181
+ ref={toggleRef}
182
+ onClick={onToggleClick}
183
+ isExpanded={isOpen}
184
+ style={
185
+ {
186
+ width: '200px'
187
+ } as React.CSSProperties
188
+ }
189
+ >
190
+ {selected}
191
+ </MenuToggle>
192
+ );
193
+
158
194
  return (
159
195
  <>
160
196
  <Message
@@ -171,103 +207,43 @@ _Italic text, formatted with single underscores_
171
207
  avatar={userAvatar}
172
208
  avatarProps={{ isBordered: true }}
173
209
  />
174
- <Form>
175
- <FormGroup role="radiogroup" isInline fieldId="user-message-type" label="Message content type">
176
- <Radio
177
- isChecked={variant === 'code'}
178
- onChange={() => setVariant('code')}
179
- name="user-message-type"
180
- label="Code"
181
- id="user-code"
182
- />
183
- <Radio
184
- isChecked={variant === 'inlineCode'}
185
- onChange={() => setVariant('inlineCode')}
186
- name="user-message-type"
187
- label="Inline code"
188
- id="user-inline-code"
189
- />
190
- <Radio
191
- isChecked={variant === 'heading'}
192
- onChange={() => setVariant('heading')}
193
- name="user-message-type"
194
- label="Heading"
195
- id="user-heading"
196
- />
197
- <Radio
198
- isChecked={variant === 'blockQuotes'}
199
- onChange={() => setVariant('blockQuotes')}
200
- name="user-message-type"
201
- label="Block quote"
202
- id="user-block-quotes"
203
- />
204
- <Radio
205
- isChecked={variant === 'emphasis'}
206
- onChange={() => setVariant('emphasis')}
207
- name="user-message-type"
208
- label="Emphasis"
209
- id="user-emphasis"
210
- />
211
- <Radio
212
- isChecked={variant === 'link'}
213
- onChange={() => setVariant('link')}
214
- name="user-message-type"
215
- label="Link"
216
- id="user-link"
217
- />
218
- <Radio
219
- isChecked={variant === 'unorderedList'}
220
- onChange={() => setVariant('unorderedList')}
221
- name="user-message-type"
222
- label="Unordered list"
223
- id="user-unordered-list"
224
- />
225
- <Radio
226
- isChecked={variant === 'orderedList'}
227
- onChange={() => setVariant('orderedList')}
228
- name="user-message-type"
229
- label="Ordered list"
230
- id="user-ordered-list"
231
- />
232
- <Radio
233
- isChecked={variant === 'moreComplexList'}
234
- onChange={() => setVariant('moreComplexList')}
235
- name="user-message-type"
236
- label="More complex list"
237
- id="user-more-complex-list"
238
- />
239
- <Radio
240
- isChecked={variant === 'table'}
241
- onChange={() => setVariant('table')}
242
- name="user-message-type"
243
- label="Table"
244
- id="user-table"
245
- />
246
- <Radio
247
- isChecked={variant === 'image'}
248
- onChange={() => setVariant('image')}
249
- name="user-message-type"
250
- label="Image"
251
- id="user-image"
252
- />
253
- <Radio
254
- isChecked={variant === 'error'}
255
- onChange={() => setVariant('error')}
256
- name="user-message-error"
257
- label="Error"
258
- id="user-error"
259
- />
260
- </FormGroup>
261
- </Form>
210
+ <Select
211
+ id="single-select"
212
+ isOpen={isOpen}
213
+ selected={selected}
214
+ onSelect={onSelect}
215
+ onOpenChange={(isOpen) => setIsOpen(isOpen)}
216
+ toggle={toggle}
217
+ shouldFocusToggleOnSelect
218
+ >
219
+ <SelectList>
220
+ <SelectOption value="Code">Code</SelectOption>
221
+ <SelectOption value="Inline code">Inline code</SelectOption>
222
+ <SelectOption value="Heading">Heading</SelectOption>
223
+ <SelectOption value="Block quotes">Block quotes</SelectOption>
224
+ <SelectOption value="Emphasis">Emphasis</SelectOption>
225
+ <SelectOption value="Link">Link</SelectOption>
226
+ <SelectOption value="Unordered list">Unordered list</SelectOption>
227
+ <SelectOption value="Ordered list">Ordered list</SelectOption>
228
+ <SelectOption value="More complex list">More complex list</SelectOption>
229
+ <SelectOption value="Table">Table</SelectOption>
230
+ <SelectOption value="Image">Image</SelectOption>
231
+ <SelectOption value="Error">Error</SelectOption>
232
+ <SelectOption value="Editable">Editable</SelectOption>
233
+ </SelectList>
234
+ </Select>
262
235
  <Message
263
236
  name="User"
264
237
  role="user"
265
238
  content={renderContent()}
266
239
  avatar={userAvatar}
267
240
  tableProps={
268
- variant === 'table' ? { 'aria-label': 'App information and user roles for user messages' } : undefined
241
+ variant === 'Table' ? { 'aria-label': 'App information and user roles for user messages' } : undefined
269
242
  }
270
- error={variant === 'error' ? error : undefined}
243
+ isEditable={variant === 'Editable' ? isEditable : false}
244
+ error={variant === 'Error' ? error : undefined}
245
+ onEditUpdate={() => setIsEditable(false)}
246
+ onEditCancel={() => setIsEditable(false)}
271
247
  />
272
248
  </>
273
249
  );
@@ -4,6 +4,7 @@ import ChatbotConversationHistoryNav, {
4
4
  Conversation
5
5
  } from '@patternfly/chatbot/dist/dynamic/ChatbotConversationHistoryNav';
6
6
  import { Checkbox, EmptyStateStatus, Spinner } from '@patternfly/react-core';
7
+ import { OutlinedCommentsIcon, SearchIcon } from '@patternfly/react-icons';
7
8
 
8
9
  const initialConversations: { [key: string]: Conversation[] } = {
9
10
  Today: [{ id: '1', text: 'Red Hat products and services' }],
@@ -46,6 +47,18 @@ const ERROR = {
46
47
  onClick: () => alert('Clicked Reload')
47
48
  };
48
49
 
50
+ const NO_RESULTS = {
51
+ bodyText: 'Adjust your search query and try again. Check your spelling or try a more general term.',
52
+ titleText: 'No results found',
53
+ icon: SearchIcon
54
+ };
55
+
56
+ const EMPTY_STATE = {
57
+ bodyText: 'Access timely assistance by starting a conversation with an AI model.',
58
+ titleText: 'Start a new chat',
59
+ icon: OutlinedCommentsIcon
60
+ };
61
+
49
62
  export const ChatbotHeaderTitleDemo: React.FunctionComponent = () => {
50
63
  const [isOpen, setIsOpen] = React.useState(true);
51
64
  const [isButtonOrderReversed, setIsButtonOrderReversed] = React.useState(false);
@@ -54,10 +67,12 @@ export const ChatbotHeaderTitleDemo: React.FunctionComponent = () => {
54
67
  );
55
68
  const [isLoading, setIsLoading] = React.useState(false);
56
69
  const [hasError, setHasError] = React.useState(false);
70
+ const [isEmpty, setIsEmpty] = React.useState(false);
71
+ const [hasNoResults, setHasNoResults] = React.useState(false);
57
72
  const displayMode = ChatbotDisplayMode.embedded;
58
73
 
59
74
  const findMatchingItems = (targetValue: string) => {
60
- let filteredConversations = Object.entries(initialConversations).reduce((acc, [key, items]) => {
75
+ const filteredConversations = Object.entries(initialConversations).reduce((acc, [key, items]) => {
61
76
  const filteredItems = items.filter((item) => item.text.toLowerCase().includes(targetValue.toLowerCase()));
62
77
  if (filteredItems.length > 0) {
63
78
  acc[key] = filteredItems;
@@ -67,7 +82,9 @@ export const ChatbotHeaderTitleDemo: React.FunctionComponent = () => {
67
82
 
68
83
  // append message if no items are found
69
84
  if (Object.keys(filteredConversations).length === 0) {
70
- filteredConversations = [{ id: '13', noIcon: true, text: 'No results found' }];
85
+ setHasNoResults(true);
86
+ } else {
87
+ setHasNoResults(false);
71
88
  }
72
89
  return filteredConversations;
73
90
  };
@@ -105,6 +122,20 @@ export const ChatbotHeaderTitleDemo: React.FunctionComponent = () => {
105
122
  id="drawer-has-error"
106
123
  name="drawer-has-error"
107
124
  ></Checkbox>
125
+ <Checkbox
126
+ label="Show empty state"
127
+ isChecked={isEmpty}
128
+ onChange={() => setIsEmpty(!isEmpty)}
129
+ id="drawer-is-empty"
130
+ name="drawer-is-empty"
131
+ ></Checkbox>
132
+ <Checkbox
133
+ label="Show no results state"
134
+ isChecked={hasNoResults}
135
+ onChange={() => setHasNoResults(!hasNoResults)}
136
+ id="drawer-has-no-results"
137
+ name="drawer-has-no-results"
138
+ ></Checkbox>
108
139
  <ChatbotConversationHistoryNav
109
140
  displayMode={displayMode}
110
141
  onDrawerToggle={() => setIsOpen(!isOpen)}
@@ -129,6 +160,8 @@ export const ChatbotHeaderTitleDemo: React.FunctionComponent = () => {
129
160
  drawerContent={<div>Drawer content</div>}
130
161
  isLoading={isLoading}
131
162
  errorState={hasError ? ERROR : undefined}
163
+ emptyState={isEmpty ? EMPTY_STATE : undefined}
164
+ noResultsState={hasNoResults ? NO_RESULTS : undefined}
132
165
  />
133
166
  </>
134
167
  );
@@ -4,6 +4,7 @@ import ChatbotConversationHistoryNav, {
4
4
  Conversation
5
5
  } from '@patternfly/chatbot/dist/dynamic/ChatbotConversationHistoryNav';
6
6
  import { Checkbox } from '@patternfly/react-core';
7
+ import { SearchIcon } from '@patternfly/react-icons';
7
8
 
8
9
  const initialConversations: { [key: string]: Conversation[] } = {
9
10
  Today: [{ id: '1', text: 'Red Hat products and services' }],
@@ -31,15 +32,22 @@ const initialConversations: { [key: string]: Conversation[] } = {
31
32
  ]
32
33
  };
33
34
 
35
+ const NO_RESULTS = {
36
+ bodyText: 'Adjust your search query and try again. Check your spelling or try a more general term.',
37
+ titleText: 'No results found',
38
+ icon: SearchIcon
39
+ };
40
+
34
41
  export const ChatbotHeaderDrawerResizableDemo: React.FunctionComponent = () => {
35
42
  const [isOpen, setIsOpen] = React.useState(true);
36
43
  const [conversations, setConversations] = React.useState<Conversation[] | { [key: string]: Conversation[] }>(
37
44
  initialConversations
38
45
  );
46
+ const [showNoResults, setShowNoResults] = React.useState(false);
39
47
  const displayMode = ChatbotDisplayMode.embedded;
40
48
 
41
49
  const findMatchingItems = (targetValue: string) => {
42
- let filteredConversations = Object.entries(initialConversations).reduce((acc, [key, items]) => {
50
+ const filteredConversations = Object.entries(initialConversations).reduce((acc, [key, items]) => {
43
51
  const filteredItems = items.filter((item) => item.text.toLowerCase().includes(targetValue.toLowerCase()));
44
52
  if (filteredItems.length > 0) {
45
53
  acc[key] = filteredItems;
@@ -49,7 +57,9 @@ export const ChatbotHeaderDrawerResizableDemo: React.FunctionComponent = () => {
49
57
 
50
58
  // append message if no items are found
51
59
  if (Object.keys(filteredConversations).length === 0) {
52
- filteredConversations = [{ id: '13', noIcon: true, text: 'No results found' }];
60
+ setShowNoResults(true);
61
+ } else {
62
+ setShowNoResults(false);
53
63
  }
54
64
  return filteredConversations;
55
65
  };
@@ -88,6 +98,7 @@ export const ChatbotHeaderDrawerResizableDemo: React.FunctionComponent = () => {
88
98
  }}
89
99
  drawerContent={<div>Drawer content</div>}
90
100
  drawerPanelContentProps={{ isResizable: true, minSize: '200px' }}
101
+ emptyState={showNoResults ? NO_RESULTS : undefined}
91
102
  />
92
103
  </>
93
104
  );
@@ -83,7 +83,7 @@ import PFHorizontalLogoReverse from './PF-HorizontalLogo-Reverse.svg';
83
83
  import userAvatar from '../Messages/user_avatar.svg';
84
84
  import patternflyAvatar from '../Messages/patternfly_avatar.jpg';
85
85
  import termsAndConditionsHeader from './PF-TermsAndConditionsHeader.svg';
86
- import { CloseIcon } from '@patternfly/react-icons';
86
+ import { CloseIcon, SearchIcon, OutlinedCommentsIcon } from '@patternfly/react-icons';
87
87
 
88
88
  ## Structure
89
89
 
@@ -34,6 +34,8 @@ import userAvatar from '../Messages/user_avatar.svg';
34
34
  import patternflyAvatar from '../Messages/patternfly_avatar.jpg';
35
35
  import { getTrackingProviders } from '@patternfly/chatbot/dist/dynamic/tracking';
36
36
  import { InitProps } from '@patternfly/chatbot/dist/dynamic/tracking';
37
+ import '@patternfly/react-core/dist/styles/base.css';
38
+ import '@patternfly/chatbot/dist/css/main.css';
37
39
 
38
40
  const footnoteProps = {
39
41
  label: 'ChatBot uses AI. Check for mistakes.',
@@ -98,17 +100,18 @@ export default MessageLoading;
98
100
  const date = new Date();
99
101
 
100
102
  const initProps: InitProps = {
103
+ verbose: false,
101
104
  segmentKey: 'TODO-key', // TODO add your key here
102
105
  posthogKey: 'TODO-key',
103
106
  umamiKey: 'TODO-key',
104
- umamiHostUrl: 'http://localhost:3000', // TODO where is your JS provider?
107
+ umamiHostUrl: 'http://localhost:3000', // TODO where is your Umami installation?
105
108
  console: true,
106
109
  something: 'test'
107
110
  };
108
111
 
109
112
  const tracking = getTrackingProviders(initProps);
110
- tracking.identify('user-123'); // TODO get real user id
111
- tracking.trackPageView(window.document.documentURI);
113
+ tracking.identify('user-123', { superUser: true }); // TODO get real user id + properties
114
+ tracking.trackPageView(window.location.href);
112
115
 
113
116
  const actionEventName = 'MessageAction';
114
117
  const initialMessages: MessageProps[] = [
@@ -29,6 +29,8 @@ import PFIconLogoColor from '../UI/PF-IconLogo-Color.svg';
29
29
  import PFIconLogoReverse from '../UI/PF-IconLogo-Reverse.svg';
30
30
  import userAvatar from '../Messages/user_avatar.svg';
31
31
  import patternflyAvatar from '../Messages/patternfly_avatar.jpg';
32
+ import '@patternfly/react-core/dist/styles/base.css';
33
+ import '@patternfly/chatbot/dist/css/main.css';
32
34
 
33
35
  interface ModalData {
34
36
  code: string;
@@ -16,6 +16,8 @@ import { BellIcon, CalendarAltIcon, ClipboardIcon, CodeIcon, UploadIcon } from '
16
16
  import { useDropzone } from 'react-dropzone';
17
17
  import userAvatar from '../Messages/user_avatar.svg';
18
18
  import patternflyAvatar from '../Messages/patternfly_avatar.jpg';
19
+ import '@patternfly/react-core/dist/styles/base.css';
20
+ import '@patternfly/chatbot/dist/css/main.css';
19
21
 
20
22
  const initialMenuItems = [
21
23
  <DropdownList key="list-1">
@@ -42,6 +42,8 @@ import PFIconLogoReverse from '../UI/PF-IconLogo-Reverse.svg';
42
42
  import { BarsIcon } from '@patternfly/react-icons';
43
43
  import userAvatar from '../Messages/user_avatar.svg';
44
44
  import patternflyAvatar from '../Messages/patternfly_avatar.jpg';
45
+ import '@patternfly/react-core/dist/styles/base.css';
46
+ import '@patternfly/chatbot/dist/css/main.css';
45
47
 
46
48
  const footnoteProps = {
47
49
  label: 'ChatBot uses AI. Check for mistakes.',
@@ -40,6 +40,8 @@ import PFHorizontalLogoReverse from '../UI/PF-HorizontalLogo-Reverse.svg';
40
40
  import { BarsIcon } from '@patternfly/react-icons';
41
41
  import userAvatar from '../Messages/user_avatar.svg';
42
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';
43
45
 
44
46
  const footnoteProps = {
45
47
  label: 'ChatBot uses AI. Check for mistakes.',
@@ -23,6 +23,8 @@ import Compare from '@patternfly/chatbot/dist/dynamic/Compare';
23
23
  import { BarsIcon } from '@patternfly/react-icons';
24
24
  import userAvatar from '../Messages/user_avatar.svg';
25
25
  import patternflyAvatar from '../Messages/patternfly_avatar.jpg';
26
+ import '@patternfly/react-core/dist/styles/base.css';
27
+ import '@patternfly/chatbot/dist/css/main.css';
26
28
 
27
29
  export const CompareChild = ({ name, input, hasNewInput, setIsSendButtonDisabled }) => {
28
30
  const [messages, setMessages] = React.useState<MessageProps[]>([]);
@@ -36,71 +38,74 @@ export const CompareChild = ({ name, input, hasNewInput, setIsSendButtonDisabled
36
38
  return id.toString();
37
39
  };
38
40
 
39
- const handleSend = (input: string) => {
40
- const date = new Date();
41
- const newMessages: MessageProps[] = [];
42
- messages.forEach((message) => newMessages.push(message));
43
- newMessages.push({
44
- avatar: userAvatar,
45
- avatarProps: { isBordered: true },
46
- id: generateId(),
47
- name: 'You',
48
- role: 'user',
49
- content: input,
50
- timestamp: `${date?.toLocaleDateString()} ${date?.toLocaleTimeString()}`
51
- });
52
- newMessages.push({
53
- avatar: patternflyAvatar,
54
- id: generateId(),
55
- name,
56
- role: 'bot',
57
- timestamp: `${date?.toLocaleDateString()} ${date?.toLocaleTimeString()}`,
58
- isLoading: true
59
- });
60
- setMessages(newMessages);
61
- // make announcement to assistive devices that new messages have been added
62
- setAnnouncement(`Message from You: ${input}. Message from ${name} is loading.`);
63
-
64
- // this is for demo purposes only; in a real situation, there would be an API response we would wait for
65
- setTimeout(() => {
66
- const loadedMessages: MessageProps[] = [];
67
- // we can't use structuredClone since messages contains functions, but we can't mutate
68
- // items that are going into state or the UI won't update correctly
69
- newMessages.forEach((message) => loadedMessages.push(message));
70
- loadedMessages.pop();
71
- loadedMessages.push({
41
+ const handleSend = React.useCallback(
42
+ (input: string) => {
43
+ const date = new Date();
44
+ const newMessages: MessageProps[] = [];
45
+ messages.forEach((message) => newMessages.push(message));
46
+ newMessages.push({
47
+ avatar: userAvatar,
48
+ avatarProps: { isBordered: true },
72
49
  id: generateId(),
73
- role: 'bot',
74
- content: `API response from ${name} goes here`,
75
- name,
50
+ name: 'You',
51
+ role: 'user',
52
+ content: input,
53
+ timestamp: `${date?.toLocaleDateString()} ${date?.toLocaleTimeString()}`
54
+ });
55
+ newMessages.push({
76
56
  avatar: patternflyAvatar,
77
- isLoading: false,
78
- actions: {
79
- // eslint-disable-next-line no-console
80
- positive: { onClick: () => console.log('Good response') },
81
- // eslint-disable-next-line no-console
82
- negative: { onClick: () => console.log('Bad response') },
83
- // eslint-disable-next-line no-console
84
- copy: { onClick: () => console.log('Copy') },
85
- // eslint-disable-next-line no-console
86
- share: { onClick: () => console.log('Share') },
87
- // eslint-disable-next-line no-console
88
- listen: { onClick: () => console.log('Listen') }
89
- },
90
- timestamp: date.toLocaleString()
57
+ id: generateId(),
58
+ name,
59
+ role: 'bot',
60
+ timestamp: `${date?.toLocaleDateString()} ${date?.toLocaleTimeString()}`,
61
+ isLoading: true
91
62
  });
92
- setMessages(loadedMessages);
93
- // make announcement to assistive devices that new message has loaded
94
- setAnnouncement(`Message from ${name}: API response goes here`);
95
- setIsSendButtonDisabled(false);
96
- }, 5000);
97
- };
63
+ setMessages(newMessages);
64
+ // make announcement to assistive devices that new messages have been added
65
+ setAnnouncement(`Message from You: ${input}. Message from ${name} is loading.`);
66
+
67
+ // this is for demo purposes only; in a real situation, there would be an API response we would wait for
68
+ setTimeout(() => {
69
+ const loadedMessages: MessageProps[] = [];
70
+ // we can't use structuredClone since messages contains functions, but we can't mutate
71
+ // items that are going into state or the UI won't update correctly
72
+ newMessages.forEach((message) => loadedMessages.push(message));
73
+ loadedMessages.pop();
74
+ loadedMessages.push({
75
+ id: generateId(),
76
+ role: 'bot',
77
+ content: `API response from ${name} goes here`,
78
+ name,
79
+ avatar: patternflyAvatar,
80
+ isLoading: false,
81
+ actions: {
82
+ // eslint-disable-next-line no-console
83
+ positive: { onClick: () => console.log('Good response') },
84
+ // eslint-disable-next-line no-console
85
+ negative: { onClick: () => console.log('Bad response') },
86
+ // eslint-disable-next-line no-console
87
+ copy: { onClick: () => console.log('Copy') },
88
+ // eslint-disable-next-line no-console
89
+ share: { onClick: () => console.log('Share') },
90
+ // eslint-disable-next-line no-console
91
+ listen: { onClick: () => console.log('Listen') }
92
+ },
93
+ timestamp: date.toLocaleString()
94
+ });
95
+ setMessages(loadedMessages);
96
+ // make announcement to assistive devices that new message has loaded
97
+ setAnnouncement(`Message from ${name}: API response goes here`);
98
+ setIsSendButtonDisabled(false);
99
+ }, 5000);
100
+ },
101
+ [messages, name, setIsSendButtonDisabled]
102
+ );
98
103
 
99
104
  React.useEffect(() => {
100
105
  if (input) {
101
106
  handleSend(input);
102
107
  }
103
- }, [hasNewInput]);
108
+ }, [hasNewInput, input]);
104
109
 
105
110
  // Auto-scrolls to the latest message
106
111
  React.useEffect(() => {
@@ -1,6 +1,8 @@
1
1
  import React from 'react';
2
2
  import Message from '@patternfly/chatbot/dist/dynamic/Message';
3
3
  import patternflyAvatar from '../Messages/patternfly_avatar.jpg';
4
+ import '@patternfly/react-core/dist/styles/base.css';
5
+ import '@patternfly/chatbot/dist/css/main.css';
4
6
 
5
7
  export const MessageWithFeedbackExample: React.FunctionComponent = () => {
6
8
  const [showUserFeedbackForm, setShowUserFeedbackForm] = React.useState(false);
@@ -5,6 +5,7 @@ import { fireEvent, render, screen, waitFor } from '@testing-library/react';
5
5
  import { ChatbotDisplayMode } from '../Chatbot/Chatbot';
6
6
  import ChatbotConversationHistoryNav, { Conversation } from './ChatbotConversationHistoryNav';
7
7
  import { EmptyStateStatus, Spinner } from '@patternfly/react-core';
8
+ import { OutlinedCommentsIcon, SearchIcon } from '@patternfly/react-icons';
8
9
 
9
10
  const ERROR = {
10
11
  bodyText: (
@@ -21,6 +22,18 @@ const ERROR = {
21
22
  onClick: () => alert('Clicked Reload')
22
23
  };
23
24
 
25
+ const NO_RESULTS = {
26
+ bodyText: 'Adjust your search query and try again. Check your spelling or try a more general term.',
27
+ titleText: 'No results found',
28
+ icon: SearchIcon
29
+ };
30
+
31
+ const EMPTY_STATE = {
32
+ bodyText: 'Access timely assistance by starting a conversation with an AI model.',
33
+ titleText: 'Start a new chat',
34
+ icon: OutlinedCommentsIcon
35
+ };
36
+
24
37
  const ERROR_WITHOUT_BUTTON = {
25
38
  bodyText: (
26
39
  <>
@@ -362,4 +375,44 @@ describe('ChatbotConversationHistoryNav', () => {
362
375
  );
363
376
  expect(screen.getByRole('dialog', { name: /Loading/i })).toBeTruthy();
364
377
  });
378
+
379
+ it('should accept emptyState', () => {
380
+ render(
381
+ <ChatbotConversationHistoryNav
382
+ onDrawerToggle={onDrawerToggle}
383
+ isDrawerOpen={true}
384
+ displayMode={ChatbotDisplayMode.fullscreen}
385
+ setIsDrawerOpen={jest.fn()}
386
+ reverseButtonOrder={false}
387
+ handleTextInputChange={jest.fn()}
388
+ conversations={initialConversations}
389
+ emptyState={EMPTY_STATE}
390
+ />
391
+ );
392
+ expect(
393
+ screen.getByRole('dialog', {
394
+ name: /Start a new chat Access timely assistance by starting a conversation with an AI model./i
395
+ })
396
+ ).toBeTruthy();
397
+ });
398
+
399
+ it('should accept no results state', () => {
400
+ render(
401
+ <ChatbotConversationHistoryNav
402
+ onDrawerToggle={onDrawerToggle}
403
+ isDrawerOpen={true}
404
+ displayMode={ChatbotDisplayMode.fullscreen}
405
+ setIsDrawerOpen={jest.fn()}
406
+ reverseButtonOrder={false}
407
+ handleTextInputChange={jest.fn()}
408
+ conversations={initialConversations}
409
+ noResultsState={NO_RESULTS}
410
+ />
411
+ );
412
+ expect(
413
+ screen.getByRole('dialog', {
414
+ name: /No results found Adjust your search query and try again. Check your spelling or try a more general term./i
415
+ })
416
+ ).toBeTruthy();
417
+ });
365
418
  });