@patternfly/chatbot 6.5.0-prerelease.27 → 6.5.0-prerelease.29

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 (151) hide show
  1. package/dist/cjs/MarkdownContent/MarkdownContent.d.ts +7 -2
  2. package/dist/cjs/Message/ErrorMessage/ErrorMessage.d.ts +15 -1
  3. package/dist/cjs/Message/ErrorMessage/ErrorMessage.js +5 -3
  4. package/dist/cjs/Message/ErrorMessage/ErrorMessage.test.d.ts +1 -0
  5. package/dist/cjs/Message/ErrorMessage/ErrorMessage.test.js +30 -0
  6. package/dist/cjs/Message/Message.d.ts +2 -0
  7. package/dist/cjs/Message/Message.js +5 -5
  8. package/dist/cjs/Message/MessageAndActions/MessageAndActions.d.ts +14 -0
  9. package/dist/cjs/Message/MessageAndActions/MessageAndActions.js +22 -0
  10. package/dist/cjs/Message/MessageAndActions/MessageAndActions.test.d.ts +1 -0
  11. package/dist/cjs/Message/MessageAndActions/MessageAndActions.test.js +25 -0
  12. package/dist/cjs/Message/MessageAndActions/index.d.ts +1 -0
  13. package/dist/cjs/Message/MessageAndActions/index.js +17 -0
  14. package/dist/cjs/Message/MessageAttachments/MessageAttachmentItem.d.ts +13 -0
  15. package/dist/cjs/Message/MessageAttachments/MessageAttachmentItem.js +22 -0
  16. package/dist/cjs/Message/MessageAttachments/MessageAttachmentItem.test.d.ts +1 -0
  17. package/dist/cjs/Message/MessageAttachments/MessageAttachmentItem.test.js +25 -0
  18. package/dist/cjs/Message/MessageAttachments/MessageAttachmentsContainer.d.ts +13 -0
  19. package/dist/cjs/Message/MessageAttachments/MessageAttachmentsContainer.js +22 -0
  20. package/dist/cjs/Message/MessageAttachments/MessageAttachmentsContainer.test.d.ts +1 -0
  21. package/dist/cjs/Message/MessageAttachments/MessageAttachmentsContainer.test.js +25 -0
  22. package/dist/cjs/Message/MessageAttachments/index.d.ts +2 -0
  23. package/dist/cjs/Message/MessageAttachments/index.js +18 -0
  24. package/dist/cjs/Message/MessageInput.d.ts +1 -1
  25. package/dist/cjs/Message/MessageInput.js +3 -1
  26. package/dist/cjs/Message/MessageLoading.d.ts +13 -4
  27. package/dist/cjs/Message/MessageLoading.js +19 -5
  28. package/dist/cjs/Message/MessageLoading.test.d.ts +1 -0
  29. package/dist/cjs/Message/MessageLoading.test.js +25 -0
  30. package/dist/cjs/Message/QuickResponse/QuickResponse.js +3 -2
  31. package/dist/cjs/Message/QuickResponse/QuickResponse.test.d.ts +1 -0
  32. package/dist/cjs/Message/QuickResponse/QuickResponse.test.js +109 -0
  33. package/dist/cjs/Message/QuickResponse/index.d.ts +1 -0
  34. package/dist/cjs/Message/QuickResponse/index.js +17 -0
  35. package/dist/cjs/Message/QuickStarts/QuickStartTile.d.ts +1 -1
  36. package/dist/cjs/Message/QuickStarts/QuickStartTile.js +3 -2
  37. package/dist/cjs/Message/QuickStarts/index.d.ts +2 -0
  38. package/dist/cjs/Message/QuickStarts/index.js +18 -0
  39. package/dist/cjs/Message/UserFeedback/UserFeedback.d.ts +1 -1
  40. package/dist/cjs/Message/UserFeedback/UserFeedback.js +3 -1
  41. package/dist/cjs/Message/UserFeedback/UserFeedbackComplete.d.ts +1 -1
  42. package/dist/cjs/Message/UserFeedback/UserFeedbackComplete.js +3 -2
  43. package/dist/cjs/Message/UserFeedback/index.d.ts +2 -0
  44. package/dist/cjs/Message/UserFeedback/index.js +18 -0
  45. package/dist/cjs/Message/index.d.ts +8 -0
  46. package/dist/cjs/Message/index.js +8 -0
  47. package/dist/cjs/MessageBar/MessageBar.d.ts +6 -0
  48. package/dist/cjs/MessageBar/MessageBar.js +24 -12
  49. package/dist/cjs/MessageBar/MessageBar.test.js +12 -0
  50. package/dist/cjs/ResponseActions/ResponseActions.d.ts +4 -0
  51. package/dist/cjs/ResponseActions/ResponseActionsGroups.d.ts +13 -0
  52. package/dist/cjs/ResponseActions/ResponseActionsGroups.js +22 -0
  53. package/dist/cjs/ResponseActions/ResponseActionsGroups.test.d.ts +1 -0
  54. package/dist/cjs/ResponseActions/ResponseActionsGroups.test.js +25 -0
  55. package/dist/cjs/ResponseActions/index.d.ts +1 -0
  56. package/dist/cjs/ResponseActions/index.js +1 -0
  57. package/dist/css/main.css +13 -1
  58. package/dist/css/main.css.map +1 -1
  59. package/dist/esm/MarkdownContent/MarkdownContent.d.ts +7 -2
  60. package/dist/esm/Message/ErrorMessage/ErrorMessage.d.ts +15 -1
  61. package/dist/esm/Message/ErrorMessage/ErrorMessage.js +3 -3
  62. package/dist/esm/Message/ErrorMessage/ErrorMessage.test.d.ts +1 -0
  63. package/dist/esm/Message/ErrorMessage/ErrorMessage.test.js +25 -0
  64. package/dist/esm/Message/Message.d.ts +2 -0
  65. package/dist/esm/Message/Message.js +5 -5
  66. package/dist/esm/Message/MessageAndActions/MessageAndActions.d.ts +14 -0
  67. package/dist/esm/Message/MessageAndActions/MessageAndActions.js +18 -0
  68. package/dist/esm/Message/MessageAndActions/MessageAndActions.test.d.ts +1 -0
  69. package/dist/esm/Message/MessageAndActions/MessageAndActions.test.js +20 -0
  70. package/dist/esm/Message/MessageAndActions/index.d.ts +1 -0
  71. package/dist/esm/Message/MessageAndActions/index.js +1 -0
  72. package/dist/esm/Message/MessageAttachments/MessageAttachmentItem.d.ts +13 -0
  73. package/dist/esm/Message/MessageAttachments/MessageAttachmentItem.js +18 -0
  74. package/dist/esm/Message/MessageAttachments/MessageAttachmentItem.test.d.ts +1 -0
  75. package/dist/esm/Message/MessageAttachments/MessageAttachmentItem.test.js +20 -0
  76. package/dist/esm/Message/MessageAttachments/MessageAttachmentsContainer.d.ts +13 -0
  77. package/dist/esm/Message/MessageAttachments/MessageAttachmentsContainer.js +18 -0
  78. package/dist/esm/Message/MessageAttachments/MessageAttachmentsContainer.test.d.ts +1 -0
  79. package/dist/esm/Message/MessageAttachments/MessageAttachmentsContainer.test.js +20 -0
  80. package/dist/esm/Message/MessageAttachments/index.d.ts +2 -0
  81. package/dist/esm/Message/MessageAttachments/index.js +2 -0
  82. package/dist/esm/Message/MessageInput.d.ts +1 -1
  83. package/dist/esm/Message/MessageInput.js +1 -1
  84. package/dist/esm/Message/MessageLoading.d.ts +13 -4
  85. package/dist/esm/Message/MessageLoading.js +16 -4
  86. package/dist/esm/Message/MessageLoading.test.d.ts +1 -0
  87. package/dist/esm/Message/MessageLoading.test.js +20 -0
  88. package/dist/esm/Message/QuickResponse/QuickResponse.js +3 -2
  89. package/dist/esm/Message/QuickResponse/QuickResponse.test.d.ts +1 -0
  90. package/dist/esm/Message/QuickResponse/QuickResponse.test.js +104 -0
  91. package/dist/esm/Message/QuickResponse/index.d.ts +1 -0
  92. package/dist/esm/Message/QuickResponse/index.js +1 -0
  93. package/dist/esm/Message/QuickStarts/QuickStartTile.d.ts +1 -1
  94. package/dist/esm/Message/QuickStarts/QuickStartTile.js +1 -1
  95. package/dist/esm/Message/QuickStarts/index.d.ts +2 -0
  96. package/dist/esm/Message/QuickStarts/index.js +2 -0
  97. package/dist/esm/Message/UserFeedback/UserFeedback.d.ts +1 -1
  98. package/dist/esm/Message/UserFeedback/UserFeedback.js +1 -1
  99. package/dist/esm/Message/UserFeedback/UserFeedbackComplete.d.ts +1 -1
  100. package/dist/esm/Message/UserFeedback/UserFeedbackComplete.js +1 -2
  101. package/dist/esm/Message/UserFeedback/index.d.ts +2 -0
  102. package/dist/esm/Message/UserFeedback/index.js +2 -0
  103. package/dist/esm/Message/index.d.ts +8 -0
  104. package/dist/esm/Message/index.js +8 -0
  105. package/dist/esm/MessageBar/MessageBar.d.ts +6 -0
  106. package/dist/esm/MessageBar/MessageBar.js +24 -12
  107. package/dist/esm/MessageBar/MessageBar.test.js +12 -0
  108. package/dist/esm/ResponseActions/ResponseActions.d.ts +4 -0
  109. package/dist/esm/ResponseActions/ResponseActionsGroups.d.ts +13 -0
  110. package/dist/esm/ResponseActions/ResponseActionsGroups.js +18 -0
  111. package/dist/esm/ResponseActions/ResponseActionsGroups.test.d.ts +1 -0
  112. package/dist/esm/ResponseActions/ResponseActionsGroups.test.js +20 -0
  113. package/dist/esm/ResponseActions/index.d.ts +1 -0
  114. package/dist/esm/ResponseActions/index.js +1 -0
  115. package/dist/tsconfig.tsbuildinfo +1 -1
  116. package/package.json +1 -1
  117. package/patternfly-docs/content/extensions/chatbot/examples/Messages/MessageWithCustomStructure.tsx +102 -0
  118. package/patternfly-docs/content/extensions/chatbot/examples/Messages/Messages.md +55 -10
  119. package/patternfly-docs/content/extensions/chatbot/examples/UI/ChatbotMessageBarCustomActions.tsx +190 -0
  120. package/patternfly-docs/content/extensions/chatbot/examples/UI/UI.md +15 -2
  121. package/src/MarkdownContent/MarkdownContent.tsx +7 -2
  122. package/src/Message/ErrorMessage/ErrorMessage.test.tsx +38 -0
  123. package/src/Message/ErrorMessage/ErrorMessage.tsx +17 -2
  124. package/src/Message/Message.tsx +75 -64
  125. package/src/Message/MessageAndActions/MessageAndActions.test.tsx +23 -0
  126. package/src/Message/MessageAndActions/MessageAndActions.tsx +22 -0
  127. package/src/Message/MessageAndActions/index.ts +1 -0
  128. package/src/Message/MessageAttachments/MessageAttachmentItem.test.tsx +23 -0
  129. package/src/Message/MessageAttachments/MessageAttachmentItem.tsx +25 -0
  130. package/src/Message/MessageAttachments/MessageAttachmentsContainer.test.tsx +23 -0
  131. package/src/Message/MessageAttachments/MessageAttachmentsContainer.tsx +25 -0
  132. package/src/Message/MessageAttachments/index.ts +2 -0
  133. package/src/Message/MessageInput.tsx +1 -1
  134. package/src/Message/MessageLoading.test.tsx +23 -0
  135. package/src/Message/MessageLoading.tsx +17 -2
  136. package/src/Message/QuickResponse/QuickResponse.test.tsx +131 -0
  137. package/src/Message/QuickResponse/QuickResponse.tsx +3 -2
  138. package/src/Message/QuickResponse/index.ts +1 -0
  139. package/src/Message/QuickStarts/QuickStartTile.tsx +1 -1
  140. package/src/Message/QuickStarts/index.ts +2 -0
  141. package/src/Message/UserFeedback/UserFeedback.tsx +1 -1
  142. package/src/Message/UserFeedback/UserFeedbackComplete.tsx +1 -4
  143. package/src/Message/UserFeedback/index.ts +2 -0
  144. package/src/Message/index.ts +8 -0
  145. package/src/MessageBar/MessageBar.scss +15 -1
  146. package/src/MessageBar/MessageBar.test.tsx +27 -0
  147. package/src/MessageBar/MessageBar.tsx +86 -50
  148. package/src/ResponseActions/ResponseActions.tsx +6 -0
  149. package/src/ResponseActions/ResponseActionsGroups.test.tsx +23 -0
  150. package/src/ResponseActions/ResponseActionsGroups.tsx +28 -0
  151. package/src/ResponseActions/index.ts +1 -0
@@ -0,0 +1,102 @@
1
+ import { FunctionComponent } from 'react';
2
+ import Message, {
3
+ ErrorMessage,
4
+ MessageAndActions,
5
+ MessageAttachmentItem,
6
+ MessageAttachmentsContainer,
7
+ MessageLoading
8
+ } from '@patternfly/chatbot/dist/dynamic/Message';
9
+ import MarkdownContent from '@patternfly/chatbot/dist/dynamic/MarkdownContent';
10
+ import ToolCall from '@patternfly/chatbot/dist/dynamic/ToolCall';
11
+ import ToolResponse from '@patternfly/chatbot/dist/dynamic/ToolResponse';
12
+ import FileDetailsLabel from '@patternfly/chatbot/dist/dynamic/FileDetailsLabel';
13
+ import ResponseActions, { ResponseActionsGroups } from '@patternfly/chatbot/dist/dynamic/ResponseActions';
14
+ import patternflyAvatar from './patternfly_avatar.jpg';
15
+ import userAvatar from './user_avatar.svg';
16
+
17
+ const handlePositiveResponse = () => {
18
+ // Handle positive response
19
+ };
20
+
21
+ const handleNegativeResponse = () => {
22
+ // Handle negative response
23
+ };
24
+
25
+ const handleCopy = () => {
26
+ // Handle copy action
27
+ };
28
+
29
+ const handleDownload = () => {
30
+ // Handle download action
31
+ };
32
+
33
+ const handleListen = () => {
34
+ // Handle listen action
35
+ };
36
+
37
+ export const MessageWithCustomStructure: FunctionComponent = () => (
38
+ <>
39
+ <Message name="Bot" role="bot" avatar={patternflyAvatar}>
40
+ <MessageAndActions>
41
+ <MarkdownContent
42
+ content={`This is a basic message with a more custom, fine-tuned structure. You can pass markdown to the MarkdownContent component, such as **bold content with double asterisks** or _italic content with single underscores_.`}
43
+ />
44
+ <ToolCall titleText="Calling 'awesome_tool'" loadingText="Loading 'awesome_tool'" isLoading={true} />
45
+ <ToolResponse
46
+ toggleContent="Tool response: fetch_user_data"
47
+ subheading="Executed in 0.3 seconds"
48
+ body="Successfully retrieved user data from the database."
49
+ cardTitle="User Data Response"
50
+ cardBody="The tool returned 150 user records matching the specified criteria."
51
+ />
52
+ <ErrorMessage title="An issue placed within this custom structure." />
53
+ <MarkdownContent
54
+ isMarkdownDisabled
55
+ textComponent={`You can also pass plain text without markdown to the MarkdownContent component by passing the isMarkdownDisabled prop. Optionally, you can also use the textComponent prop instead of content.`}
56
+ />
57
+ <ToolCall titleText="Calling 'more_awesome_tool'" loadingText="Loading 'more_awesome_tool'" isLoading={true} />
58
+ <ToolCall titleText="Calling 'even_more_awesome_tool'" loadingText="Loading 'even_more_awesome_tool'" />
59
+ <MarkdownContent content={`You can even place a message loading state in the middle of a message:`} />
60
+ <MessageLoading loadingWord="Loading something in the middle of a custom structured message" />
61
+ <ResponseActionsGroups>
62
+ <ResponseActions
63
+ actions={{
64
+ positive: { onClick: handlePositiveResponse, ariaLabel: 'Good response' },
65
+ negative: { onClick: handleNegativeResponse, ariaLabel: 'Bad response' }
66
+ }}
67
+ persistActionSelection={true}
68
+ />
69
+ <ResponseActions
70
+ actions={{
71
+ copy: { onClick: handleCopy, ariaLabel: 'Copy' },
72
+ download: { onClick: handleDownload, ariaLabel: 'Download' }
73
+ }}
74
+ persistActionSelection={false}
75
+ />
76
+ <ResponseActions
77
+ actions={{
78
+ listen: { onClick: handleListen, ariaLabel: 'Listen' }
79
+ }}
80
+ persistActionSelection={true}
81
+ />
82
+ </ResponseActionsGroups>
83
+ </MessageAndActions>
84
+ </Message>
85
+ <Message name="User" role="user" avatar={userAvatar}>
86
+ <MessageAndActions>
87
+ <MarkdownContent content="This message is in the MessageAndActions container, and the file attachments below are in their own separate MessageAttachmentsContainer!" />
88
+ </MessageAndActions>
89
+ <MessageAttachmentsContainer>
90
+ <MessageAttachmentItem>
91
+ <FileDetailsLabel fileName="project-report.pdf" />
92
+ </MessageAttachmentItem>
93
+ <MessageAttachmentItem>
94
+ <FileDetailsLabel fileName="data-analysis.csv" />
95
+ </MessageAttachmentItem>
96
+ <MessageAttachmentItem>
97
+ <FileDetailsLabel fileName="presentation-slides.pptx" />
98
+ </MessageAttachmentItem>
99
+ </MessageAttachmentsContainer>
100
+ </Message>
101
+ </>
102
+ );
@@ -14,24 +14,40 @@ propComponents:
14
14
  [
15
15
  'AttachMenu',
16
16
  'AttachmentEdit',
17
- 'FileDetailsProps',
18
- 'FileDetailsLabelProps',
19
17
  'FileDropZone',
20
- 'PreviewAttachment',
21
18
  'Message',
22
- 'MessageExtraContent',
23
- 'PreviewAttachment',
19
+ 'ErrorMessage',
20
+ 'MessageLoadingProps',
21
+ 'MessageInputProps',
22
+ 'MessageAndActionsProps',
23
+ 'MarkdownContent',
24
+ 'QuickResponseProps',
25
+ 'QuickStartTileProps',
26
+ 'UserFeedback',
27
+ 'UserFeedbackComplete',
28
+ 'DeepThinking',
29
+ 'ToolCall',
30
+ 'ToolResponse',
31
+ 'SourcesCard',
32
+ 'ResponseActionsGroupsProps',
33
+ 'ResponseActionProps',
24
34
  'ActionProps',
25
- 'SourcesCardProps',
26
- 'UserFeedbackProps',
27
- 'UserFeedbackCompleteProps',
28
- 'QuickResponseProps'
35
+ 'MessageAttachmentsContainerProps',
36
+ 'MessageAttachmentItemProps',
37
+ 'FileDetailsProps',
38
+ 'FileDetailsLabelProps',
39
+ 'MessageExtraContent',
40
+ 'PreviewAttachment'
29
41
  ]
30
42
  sortValue: 3
31
43
  ---
32
44
 
33
- import Message from '@patternfly/chatbot/dist/dynamic/Message';
45
+ import Message, { ErrorMessage, MessageAndActions, MessageLoading, MessageAttachmentItem, MessageAttachmentsContainer } from '@patternfly/chatbot/dist/dynamic/Message';
46
+ import MarkdownContent from '@patternfly/chatbot/dist/dynamic/MarkdownContent';
34
47
  import MessageDivider from '@patternfly/chatbot/dist/dynamic/MessageDivider';
48
+ import ToolCall from '@patternfly/chatbot/dist/dynamic/ToolCall';
49
+ import ResponseActions, { ResponseActionsGroups } from '@patternfly/chatbot/dist/dynamic/ResponseActions';
50
+ import ToolResponse from '@patternfly/chatbot/dist/dynamic/ToolResponse';
35
51
  import { rehypeCodeBlockToggle } from '@patternfly/chatbot/dist/esm/Message/Plugins/rehypeCodeBlockToggle';
36
52
  import SourcesCard from '@patternfly/chatbot/dist/dynamic/SourcesCard';
37
53
  import { ArrowCircleDownIcon, ArrowRightIcon, CheckCircleIcon, CopyIcon, CubeIcon, CubesIcon, DownloadIcon, InfoCircleIcon, OutlinedQuestionCircleIcon, RedoIcon, RobotIcon, WrenchIcon } from '@patternfly/react-icons';
@@ -271,6 +287,35 @@ You can add custom content to specific parts of a `<Message>` via the `extraCont
271
287
 
272
288
  ```
273
289
 
290
+ ### Custom message structure
291
+
292
+ For more advanced use cases, you can build completely custom message structures by passing children directly to `<Message>`. This approach is useful when you need to customize the order or structure of message elements beyond what the standard props allow.
293
+
294
+ When creating custom message structures, you must follow an intended composable structure.
295
+
296
+ 1. **Message content and actions:** Wrap in `<MessageAndActions>`. This includes, but is not limited to:
297
+
298
+ - `<MarkdownContent>`: For rendering markdown or plain text content
299
+ - `<ErrorMessage>`
300
+ - `<MessageLoading>`
301
+ - `<MessageInput>`
302
+ - `<ToolCall>`
303
+ - `<ToolResponse>`
304
+ - `<DeepThinking>`
305
+ - `<QuickResponse>`
306
+ - `<QuickStartTile>`
307
+ - `<UserFeedback>` and `<UserFeedbackComplete>`
308
+ - `<SourcesCard>`
309
+ - `<ResponseActionsGroups>` and `<ResponseActions>`
310
+
311
+ 2. **File attachments:** Placed outside `<MessageAndActions>` and wrapped in attachment containers:
312
+ - `<MessageAttachmentsContainer>`: Container for all attachments
313
+ - `<MessageAttachmentItem>`: Individual attachment wrapper (contains `<FileDetailsLabel>` or other attachment components)
314
+
315
+ ```ts file="./MessageWithCustomStructure.tsx"
316
+
317
+ ```
318
+
274
319
  ## File attachments
275
320
 
276
321
  ### Messages with attachments
@@ -0,0 +1,190 @@
1
+ import { useState, FunctionComponent, ReactNode } from 'react';
2
+ import { MessageBar } from '@patternfly/chatbot/dist/dynamic/MessageBar';
3
+ import {
4
+ Divider,
5
+ DropdownItem,
6
+ DropdownList,
7
+ Label,
8
+ MenuToggle,
9
+ Select,
10
+ SelectList,
11
+ SelectOption
12
+ } from '@patternfly/react-core';
13
+ import { PlusIcon, ClipboardIcon, CodeIcon, UploadIcon } from '@patternfly/react-icons';
14
+ import { useDropzone } from 'react-dropzone';
15
+
16
+ export const ChatbotMessageBarCustomActionsExample: FunctionComponent = () => {
17
+ const [isFirstMenuOpen, setIsFirstMenuOpen] = useState<boolean>(false);
18
+ const [isSecondMenuOpen, setIsSecondMenuOpen] = useState<boolean>(false);
19
+ const [isModelSelectOpen, setIsModelSelectOpen] = useState<boolean>(false);
20
+ const [selectedModel, setSelectedModel] = useState<string>('GPT-4');
21
+ const [showCanvasLabel, setShowCanvasLabel] = useState<boolean>(true);
22
+
23
+ const handleSend = (message: string | number) => alert(message);
24
+
25
+ const { open, getInputProps } = useDropzone({
26
+ multiple: true,
27
+ // eslint-disable-next-line no-console
28
+ onDropAccepted: () => console.log('fileUploaded')
29
+ });
30
+
31
+ const onFirstMenuToggle = () => {
32
+ setIsFirstMenuOpen(!isFirstMenuOpen);
33
+ };
34
+
35
+ const onSecondMenuToggle = () => {
36
+ setIsSecondMenuOpen(!isSecondMenuOpen);
37
+ };
38
+
39
+ const onModelSelect = (
40
+ _event: React.MouseEvent<Element, MouseEvent> | undefined,
41
+ value: string | number | undefined
42
+ ) => {
43
+ setSelectedModel(value as string);
44
+ setIsModelSelectOpen(false);
45
+ };
46
+
47
+ const firstMenuItems: ReactNode = (
48
+ <DropdownList>
49
+ <DropdownItem value="Logs" id="logs" icon={<ClipboardIcon />}>
50
+ Logs
51
+ </DropdownItem>
52
+ <DropdownItem value="YAML - Status" id="yaml-status" icon={<CodeIcon />}>
53
+ YAML - Status
54
+ </DropdownItem>
55
+ <DropdownItem value="YAML - All contents" id="yaml-all" icon={<CodeIcon />}>
56
+ YAML - All contents
57
+ </DropdownItem>
58
+ <Divider key="divider" />
59
+ <DropdownItem value="Upload from computer" id="upload" icon={<UploadIcon />} onClick={open}>
60
+ Upload from computer
61
+ </DropdownItem>
62
+ </DropdownList>
63
+ );
64
+
65
+ const secondMenuItems: ReactNode = (
66
+ <DropdownList>
67
+ <DropdownItem value="canvas" id="canvas">
68
+ {showCanvasLabel ? 'Disable' : 'Enable'} Canvas
69
+ </DropdownItem>
70
+ <Divider key="divider-1" />
71
+ <DropdownItem value="Logs" id="logs" icon={<ClipboardIcon />}>
72
+ Logs
73
+ </DropdownItem>
74
+ <DropdownItem value="YAML - Status" id="yaml-status" icon={<CodeIcon />}>
75
+ YAML - Status
76
+ </DropdownItem>
77
+ <DropdownItem value="YAML - All contents" id="yaml-all" icon={<CodeIcon />}>
78
+ YAML - All contents
79
+ </DropdownItem>
80
+ <Divider key="divider-2" />
81
+ <DropdownItem value="Upload from computer" id="upload" icon={<UploadIcon />} onClick={open}>
82
+ Upload from computer
83
+ </DropdownItem>
84
+ </DropdownList>
85
+ );
86
+
87
+ const modelOptions = ['GPT-4', 'GPT-3.5', 'Claude', 'Llama 2'];
88
+
89
+ return (
90
+ <>
91
+ {/* This is required for react-dropzone to work in Safari and Firefox */}
92
+ <input {...getInputProps()} hidden />
93
+ <div style={{ marginBottom: '1rem' }}>
94
+ <h4 style={{ marginBottom: '0.5rem' }}>Custom attach menu with a PlusIcon at the start</h4>
95
+ <MessageBar
96
+ onSendMessage={handleSend}
97
+ attachButtonPosition="start"
98
+ attachMenuProps={{
99
+ isAttachMenuOpen: isFirstMenuOpen,
100
+ setIsAttachMenuOpen: setIsFirstMenuOpen,
101
+ attachMenuItems: firstMenuItems,
102
+ onAttachMenuSelect: (_ev, value) => {
103
+ // eslint-disable-next-line no-console
104
+ console.log('selected', value);
105
+ setIsFirstMenuOpen(false);
106
+ },
107
+ attachMenuInputPlaceholder: 'Search options...',
108
+ onAttachMenuToggleClick: onFirstMenuToggle,
109
+ onAttachMenuOnOpenChangeKeys: ['Escape', 'Tab']
110
+ }}
111
+ buttonProps={{
112
+ attach: {
113
+ icon: <PlusIcon />,
114
+ tooltipContent: 'Message actions',
115
+ 'aria-label': 'Message actions'
116
+ }
117
+ }}
118
+ />
119
+ </div>
120
+
121
+ <div>
122
+ <h4 style={{ marginBottom: '0.5rem' }}>Custom attach menu with additional actions</h4>
123
+ <MessageBar
124
+ onSendMessage={handleSend}
125
+ attachButtonPosition="start"
126
+ attachMenuProps={{
127
+ isAttachMenuOpen: isSecondMenuOpen,
128
+ setIsAttachMenuOpen: setIsSecondMenuOpen,
129
+ attachMenuItems: secondMenuItems,
130
+ onAttachMenuOnOpenChangeKeys: ['Escape', 'Tab'],
131
+ onAttachMenuSelect: (_ev, value) => {
132
+ // eslint-disable-next-line no-console
133
+ console.log('selected', value);
134
+ if (value === 'canvas') {
135
+ setShowCanvasLabel(!showCanvasLabel);
136
+ }
137
+ setIsSecondMenuOpen(false);
138
+ },
139
+ onAttachMenuToggleClick: onSecondMenuToggle
140
+ }}
141
+ buttonProps={{
142
+ attach: {
143
+ icon: <PlusIcon />,
144
+ tooltipContent: 'Message actions',
145
+ 'aria-label': 'Message actions'
146
+ }
147
+ }}
148
+ additionalActions={
149
+ <>
150
+ <Select
151
+ isOpen={isModelSelectOpen}
152
+ selected={selectedModel}
153
+ shouldFocusToggleOnSelect
154
+ onSelect={onModelSelect}
155
+ onOpenChange={(isOpen) => setIsModelSelectOpen(isOpen)}
156
+ toggle={(toggleRef) => (
157
+ <MenuToggle
158
+ ref={toggleRef}
159
+ variant="plainText"
160
+ onClick={() => setIsModelSelectOpen(!isModelSelectOpen)}
161
+ isExpanded={isModelSelectOpen}
162
+ aria-label={`${selectedModel}, Select a model`}
163
+ style={{
164
+ minWidth: '120px'
165
+ }}
166
+ >
167
+ {selectedModel}
168
+ </MenuToggle>
169
+ )}
170
+ >
171
+ <SelectList>
172
+ {modelOptions.map((option) => (
173
+ <SelectOption key={option} value={option}>
174
+ {option}
175
+ </SelectOption>
176
+ ))}
177
+ </SelectList>
178
+ </Select>
179
+ {showCanvasLabel && (
180
+ <Label closeBtnAriaLabel="Remove Canvas mode" onClose={() => setShowCanvasLabel(false)}>
181
+ Canvas
182
+ </Label>
183
+ )}
184
+ </>
185
+ }
186
+ />
187
+ </div>
188
+ </>
189
+ );
190
+ };
@@ -70,11 +70,11 @@ import { MessageBar } from '@patternfly/chatbot/dist/dynamic/MessageBar';
70
70
  import SourceDetailsMenuItem from '@patternfly/chatbot/dist/dynamic/SourceDetailsMenuItem';
71
71
  import { ChatbotModal } from '@patternfly/chatbot/dist/dynamic/ChatbotModal';
72
72
  import SettingsForm from '@patternfly/chatbot/dist/dynamic/Settings';
73
- import { BellIcon, CalendarAltIcon, ClipboardIcon, CodeIcon, ThumbtackIcon, UploadIcon } from '@patternfly/react-icons';
73
+ import { BellIcon, CalendarAltIcon, ClipboardIcon, CodeIcon, PlusIcon, ThumbtackIcon, UploadIcon } from '@patternfly/react-icons';
74
74
  import { useDropzone } from 'react-dropzone';
75
75
 
76
76
  import ChatbotConversationHistoryNav from '@patternfly/chatbot/dist/dynamic/ChatbotConversationHistoryNav';
77
- import { Button, DropdownItem, DropdownList, Checkbox, MenuToggle, Select, SelectList, SelectOption } from '@patternfly/react-core';
77
+ import { Button, Label, DropdownItem, DropdownList, Checkbox, MenuToggle, Select, SelectList, SelectOption } from '@patternfly/react-core';
78
78
 
79
79
  import OutlinedWindowRestoreIcon from '@patternfly/react-icons/dist/esm/icons/outlined-window-restore-icon';
80
80
  import ExpandIcon from '@patternfly/react-icons/dist/esm/icons/expand-icon';
@@ -291,6 +291,19 @@ Attachments can also be added to the ChatBot via [drag and drop.](/extensions/ch
291
291
 
292
292
  ```
293
293
 
294
+ ### Message bar with custom attach menu and additional actions
295
+
296
+ You can move the attach button to the start of the message bar and customize it with a different icon. To include additional actions in the message bar you can also use the `additionalActions` prop.
297
+
298
+ This example shows two message bar variations:
299
+
300
+ 1. A message bar with a custom attach menu where a `PlusIcon` is positioned at the start
301
+ 2. The same custom attach menu with additional actions, including a model selector menu and a dismissable "Canvas" label
302
+
303
+ ```js file="./ChatbotMessageBarCustomActions.tsx"
304
+
305
+ ```
306
+
294
307
  ### Footer with message bar and footnote
295
308
 
296
309
  A simple footer with a message bar and footnote would have this code structure:
@@ -30,10 +30,15 @@ import SuperscriptMessage from '../Message/SuperscriptMessage/SuperscriptMessage
30
30
  import { ButtonProps } from '@patternfly/react-core';
31
31
  import { css } from '@patternfly/react-styles';
32
32
 
33
+ /**
34
+ * MarkdownContent renders content either as plain text or with content with markdown support.
35
+ *
36
+ * Use this component when passing children to Message to customize its structure.
37
+ */
33
38
  export interface MarkdownContentProps {
34
- /** The markdown content to render */
39
+ /** The content to render. Supports markdown formatting by default, or plain text when isMarkdownDisabled is true. */
35
40
  content?: string;
36
- /** Disables markdown parsing, allowing only text input */
41
+ /** Disables markdown parsing, allowing only plain text input */
37
42
  isMarkdownDisabled?: boolean;
38
43
  /** Props for code blocks */
39
44
  codeBlockProps?: CodeBlockMessageProps;
@@ -0,0 +1,38 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import '@testing-library/jest-dom';
3
+ import ErrorMessage from './ErrorMessage';
4
+
5
+ test('Renders with title', () => {
6
+ render(<ErrorMessage title="Error occurred" />);
7
+
8
+ expect(screen.getByText('Error occurred')).toBeVisible();
9
+ });
10
+
11
+ test('Renders with children', () => {
12
+ render(<ErrorMessage title="Error occurred">This is the error message body</ErrorMessage>);
13
+
14
+ expect(screen.getByText('This is the error message body')).toBeVisible();
15
+ });
16
+
17
+ test('Renders with action links', () => {
18
+ const actionLinks = (
19
+ <a href="#retry" data-testid="retry-link">
20
+ Retry action link
21
+ </a>
22
+ );
23
+ render(<ErrorMessage title="Error occurred" actionLinks={actionLinks} />);
24
+
25
+ expect(screen.getByText('Retry action link')).toBeVisible();
26
+ });
27
+
28
+ test('Renders with custom className', () => {
29
+ render(<ErrorMessage title="Error occurred" className="custom-error-class" />);
30
+
31
+ expect(screen.getByText('Error occurred').parentElement).toHaveClass('custom-error-class');
32
+ });
33
+
34
+ test('Renders with spread props', () => {
35
+ render(<ErrorMessage title="Error occurred" id="test-error-id" />);
36
+
37
+ expect(screen.getByText('Error occurred').parentElement).toHaveAttribute('id', 'test-error-id');
38
+ });
@@ -4,8 +4,23 @@
4
4
 
5
5
  import { Alert, AlertProps } from '@patternfly/react-core';
6
6
 
7
- const ErrorMessage = ({ title, actionLinks, children, ...props }: AlertProps) => (
8
- <Alert isInline variant="danger" title={title} actionLinks={actionLinks} {...props}>
7
+ /**
8
+ * ErrorMessage displays an inline danger alert for error states in messages.
9
+ * Use this component when passing children to Message to display error information.
10
+ */
11
+ export interface ErrorMessageProps extends Partial<AlertProps> {
12
+ /** Content to display in the error alert body */
13
+ children?: React.ReactNode;
14
+ /** Additional classes for the error alert */
15
+ className?: string;
16
+ /** Title of the error alert */
17
+ title?: React.ReactNode;
18
+ /** Action links to display in the alert footer */
19
+ actionLinks?: React.ReactNode;
20
+ }
21
+
22
+ export const ErrorMessage = ({ title, actionLinks, children, className, ...props }: ErrorMessageProps) => (
23
+ <Alert isInline variant="danger" title={title} actionLinks={actionLinks} className={className} {...props}>
9
24
  {children}
10
25
  </Alert>
11
26
  );
@@ -67,6 +67,8 @@ export interface MessageExtraContent {
67
67
  }
68
68
 
69
69
  export interface MessageProps extends Omit<HTMLProps<HTMLDivElement>, 'role'> {
70
+ /** Children to render instead of the default message structure, allowing more fine-tuned message control. When provided, this will override the default rendering of content, toolResponse, deepThinking, toolCall, sources, quickStarts, actions, etc. */
71
+ children?: ReactNode;
70
72
  /** Unique id for message */
71
73
  id?: string;
72
74
  /** Role of the user sending the message */
@@ -193,6 +195,7 @@ export interface MessageProps extends Omit<HTMLProps<HTMLDivElement>, 'role'> {
193
195
  }
194
196
 
195
197
  export const MessageBase: FunctionComponent<MessageProps> = ({
198
+ children,
196
199
  role,
197
200
  content,
198
201
  extraContent,
@@ -341,74 +344,82 @@ export const MessageBase: FunctionComponent<MessageProps> = ({
341
344
  <Timestamp date={date}>{timestamp}</Timestamp>
342
345
  </div>
343
346
  <div className="pf-chatbot__message-response">
344
- <div className="pf-chatbot__message-and-actions">
345
- {renderMessage()}
346
- {afterMainContent && <>{afterMainContent}</>}
347
- {toolResponse && <ToolResponse {...toolResponse} />}
348
- {deepThinking && <DeepThinking {...deepThinking} />}
349
- {toolCall && <ToolCall {...toolCall} />}
350
- {!isLoading && sources && <SourcesCard {...sources} isCompact={isCompact} />}
351
- {quickStarts && quickStarts.quickStart && (
352
- <QuickStartTile
353
- quickStart={quickStarts.quickStart}
354
- onSelectQuickStart={quickStarts.onSelectQuickStart}
355
- minuteWord={quickStarts.minuteWord}
356
- minuteWordPlural={quickStarts.minuteWordPlural}
357
- prerequisiteWord={quickStarts.prerequisiteWord}
358
- prerequisiteWordPlural={quickStarts.prerequisiteWordPlural}
359
- quickStartButtonAriaLabel={quickStarts.quickStartButtonAriaLabel}
360
- isCompact={isCompact}
361
- />
362
- )}
363
- {!isLoading && !isEditable && actions && (
364
- <>
365
- {Array.isArray(actions) ? (
366
- <div className="pf-chatbot__response-actions-groups">
367
- {actions.map((actionGroup, index) => (
368
- <ResponseActions
369
- key={index}
370
- actions={actionGroup.actions || actionGroup}
371
- persistActionSelection={persistActionSelection || actionGroup.persistActionSelection}
372
- />
373
- ))}
374
- </div>
375
- ) : (
376
- <ResponseActions actions={actions} persistActionSelection={persistActionSelection} />
347
+ {children ? (
348
+ <>{children}</>
349
+ ) : (
350
+ <>
351
+ <div className="pf-chatbot__message-and-actions">
352
+ {renderMessage()}
353
+ {afterMainContent && <>{afterMainContent}</>}
354
+ {toolResponse && <ToolResponse {...toolResponse} />}
355
+ {deepThinking && <DeepThinking {...deepThinking} />}
356
+ {toolCall && <ToolCall {...toolCall} />}
357
+ {!isLoading && sources && <SourcesCard {...sources} isCompact={isCompact} />}
358
+ {quickStarts && quickStarts.quickStart && (
359
+ <QuickStartTile
360
+ quickStart={quickStarts.quickStart}
361
+ onSelectQuickStart={quickStarts.onSelectQuickStart}
362
+ minuteWord={quickStarts.minuteWord}
363
+ minuteWordPlural={quickStarts.minuteWordPlural}
364
+ prerequisiteWord={quickStarts.prerequisiteWord}
365
+ prerequisiteWordPlural={quickStarts.prerequisiteWordPlural}
366
+ quickStartButtonAriaLabel={quickStarts.quickStartButtonAriaLabel}
367
+ isCompact={isCompact}
368
+ />
369
+ )}
370
+ {!isLoading && !isEditable && actions && (
371
+ <>
372
+ {Array.isArray(actions) ? (
373
+ <div className="pf-chatbot__response-actions-groups">
374
+ {actions.map((actionGroup, index) => (
375
+ <ResponseActions
376
+ key={index}
377
+ actions={actionGroup.actions || actionGroup}
378
+ persistActionSelection={persistActionSelection || actionGroup.persistActionSelection}
379
+ />
380
+ ))}
381
+ </div>
382
+ ) : (
383
+ <ResponseActions actions={actions} persistActionSelection={persistActionSelection} />
384
+ )}
385
+ </>
386
+ )}
387
+ {userFeedbackForm && (
388
+ <UserFeedback {...userFeedbackForm} timestamp={dateString} isCompact={isCompact} />
377
389
  )}
378
- </>
379
- )}
380
- {userFeedbackForm && <UserFeedback {...userFeedbackForm} timestamp={dateString} isCompact={isCompact} />}
381
- {userFeedbackComplete && (
382
- <UserFeedbackComplete {...userFeedbackComplete} timestamp={dateString} isCompact={isCompact} />
383
- )}
384
- {!isLoading && quickResponses && (
385
- <QuickResponse
386
- quickResponses={quickResponses}
387
- quickResponseContainerProps={quickResponseContainerProps}
388
- isCompact={isCompact}
389
- />
390
- )}
391
- </div>
392
- {attachments && (
393
- <div className="pf-chatbot__message-attachments-container">
394
- {attachments.map((attachment) => (
395
- <div key={attachment.id ?? attachment.name} className="pf-chatbot__message-attachment">
396
- <FileDetailsLabel
397
- fileName={attachment.name}
398
- fileId={attachment.id}
399
- onClose={attachment.onClose}
400
- onClick={attachment.onClick}
401
- isLoading={attachment.isLoading}
402
- closeButtonAriaLabel={attachment.closeButtonAriaLabel}
403
- languageTestId={attachment.languageTestId}
404
- spinnerTestId={attachment.spinnerTestId}
405
- variant={isPrimary ? 'outline' : undefined}
390
+ {userFeedbackComplete && (
391
+ <UserFeedbackComplete {...userFeedbackComplete} timestamp={dateString} isCompact={isCompact} />
392
+ )}
393
+ {!isLoading && quickResponses && (
394
+ <QuickResponse
395
+ quickResponses={quickResponses}
396
+ quickResponseContainerProps={quickResponseContainerProps}
397
+ isCompact={isCompact}
406
398
  />
399
+ )}
400
+ </div>
401
+ {attachments && (
402
+ <div className="pf-chatbot__message-attachments-container">
403
+ {attachments.map((attachment) => (
404
+ <div key={attachment.id ?? attachment.name} className="pf-chatbot__message-attachment">
405
+ <FileDetailsLabel
406
+ fileName={attachment.name}
407
+ fileId={attachment.id}
408
+ onClose={attachment.onClose}
409
+ onClick={attachment.onClick}
410
+ isLoading={attachment.isLoading}
411
+ closeButtonAriaLabel={attachment.closeButtonAriaLabel}
412
+ languageTestId={attachment.languageTestId}
413
+ spinnerTestId={attachment.spinnerTestId}
414
+ variant={isPrimary ? 'outline' : undefined}
415
+ />
416
+ </div>
417
+ ))}
407
418
  </div>
408
- ))}
409
- </div>
419
+ )}
420
+ {!isLoading && endContent && <>{endContent}</>}
421
+ </>
410
422
  )}
411
- {!isLoading && endContent && <>{endContent}</>}
412
423
  </div>
413
424
  </div>
414
425
  </section>