@patternfly/chatbot 6.5.0-prerelease.1 → 6.5.0-prerelease.11

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 (118) 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 +7 -20
  17. package/dist/cjs/Message/Message.js +11 -8
  18. package/dist/cjs/Message/Message.test.js +42 -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 +84 -13
  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 +7 -20
  53. package/dist/esm/Message/Message.js +11 -8
  54. package/dist/esm/Message/Message.test.js +43 -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/BotMessage.tsx +1 -0
  74. package/patternfly-docs/content/extensions/chatbot/examples/Messages/UserMessage.tsx +1 -0
  75. package/patternfly-docs/content/extensions/chatbot/examples/Messages/UserMessageWithExtraContent.tsx +3 -1
  76. package/patternfly-docs/content/extensions/chatbot/examples/UI/ChatbotHeaderDrawerWithActions.tsx +14 -14
  77. package/patternfly-docs/content/extensions/chatbot/examples/UI/ChatbotHeaderDrawerWithSelection.tsx +14 -14
  78. package/patternfly-docs/content/extensions/chatbot/examples/UI/ChatbotMessageBarAttach.tsx +2 -2
  79. package/patternfly-docs/content/extensions/chatbot/examples/UI/ChatbotMessageBarIndicatorThinking.tsx +15 -0
  80. package/patternfly-docs/content/extensions/chatbot/examples/UI/Settings.tsx +1 -1
  81. package/patternfly-docs/content/extensions/chatbot/examples/UI/UI.md +10 -0
  82. package/patternfly-docs/content/extensions/chatbot/examples/demos/Chatbot.md +12 -4
  83. package/patternfly-docs/content/extensions/chatbot/examples/demos/ChatbotAttachmentMenu.tsx +2 -2
  84. package/patternfly-docs/content/extensions/chatbot/examples/demos/ChatbotTranscripts.tsx +1 -1
  85. package/patternfly-docs/content/extensions/chatbot/examples/demos/WhiteEmbeddedChatbot.tsx +451 -0
  86. package/patternfly-docs/patternfly-docs.config.js +1 -0
  87. package/src/AttachMenu/AttachMenu.tsx +26 -11
  88. package/src/Chatbot/Chatbot.scss +23 -1
  89. package/src/ChatbotContent/ChatbotContent.scss +4 -0
  90. package/src/ChatbotContent/ChatbotContent.test.tsx +5 -0
  91. package/src/ChatbotContent/ChatbotContent.tsx +4 -1
  92. package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.test.tsx +5 -0
  93. package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.tsx +7 -4
  94. package/src/ChatbotFooter/ChatbotFooter.scss +21 -0
  95. package/src/ChatbotFooter/ChatbotFooter.test.tsx +10 -1
  96. package/src/ChatbotFooter/ChatbotFooter.tsx +10 -3
  97. package/src/ChatbotHeader/ChatbotHeader.scss +19 -0
  98. package/src/CodeModal/CodeModal.tsx +58 -7
  99. package/src/FileDetailsLabel/FileDetailsLabel.tsx +2 -2
  100. package/src/Message/CodeBlockMessage/CodeBlockMessage.scss +7 -2
  101. package/src/Message/CodeBlockMessage/CodeBlockMessage.tsx +9 -2
  102. package/src/Message/Message.scss +3 -3
  103. package/src/Message/Message.test.tsx +60 -1
  104. package/src/Message/Message.tsx +23 -33
  105. package/src/Message/MessageLoading.scss +7 -0
  106. package/src/Message/MessageLoading.tsx +2 -2
  107. package/src/Message/TableMessage/TableMessage.scss +4 -0
  108. package/src/Message/TableMessage/TableMessage.tsx +6 -2
  109. package/src/Message/TextMessage/TextMessage.scss +12 -0
  110. package/src/Message/TextMessage/TextMessage.tsx +11 -2
  111. package/src/Message/UserFeedback/UserFeedback.scss +2 -1
  112. package/src/MessageBar/AttachButton.test.tsx +4 -0
  113. package/src/MessageBar/AttachButton.tsx +4 -1
  114. package/src/MessageBar/MessageBar.scss +11 -5
  115. package/src/MessageBar/MessageBar.test.tsx +102 -1
  116. package/src/MessageBar/MessageBar.tsx +44 -11
  117. package/src/__mocks__/monaco-editor.ts +19 -0
  118. package/src/__mocks__/rehype-highlight.ts +3 -0
@@ -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)}
@@ -11,6 +11,10 @@
11
11
  flex-direction: column;
12
12
  row-gap: var(--pf-chatbot__footer--RowGap);
13
13
  position: relative; // this is so focus ring on parent chatbot doesn't include footer
14
+
15
+ &.pf-m-primary {
16
+ background-color: var(--pf-t--global--background--color--primary--default);
17
+ }
14
18
  }
15
19
  .pf-chatbot__footer-container {
16
20
  padding: 0 var(--pf-t--global--spacer--lg) var(--pf-t--global--spacer--lg) var(--pf-t--global--spacer--lg);
@@ -62,3 +66,20 @@
62
66
  padding: 0 var(--pf-t--global--spacer--sm) var(--pf-t--global--spacer--sm) var(--pf-t--global--spacer--sm);
63
67
  row-gap: var(--pf-t--global--spacer--sm);
64
68
  }
69
+
70
+ // ============================================================================
71
+ // High contrast
72
+ // ============================================================================
73
+ :root:where(.pf-v6-theme-high-contrast) {
74
+ // Chatbot Display Mode - Fullscreen and Embedded
75
+ @media screen and (min-width: 64rem) {
76
+ .pf-chatbot--embedded,
77
+ .pf-chatbot--fullscreen {
78
+ .pf-chatbot__footer {
79
+ .pf-v6-c-divider {
80
+ display: var(--pf-v6-hidden-visible--Display);
81
+ }
82
+ }
83
+ }
84
+ }
85
+ }
@@ -15,10 +15,19 @@ describe('ChatbotFooter', () => {
15
15
 
16
16
  it('should handle isCompact', () => {
17
17
  render(
18
- <ChatbotFooter className="custom-class" isCompact data-testid="footer">
18
+ <ChatbotFooter isCompact data-testid="footer">
19
19
  Chatbot Content
20
20
  </ChatbotFooter>
21
21
  );
22
22
  expect(screen.getByTestId('footer')).toHaveClass('pf-m-compact');
23
23
  });
24
+
25
+ it('should handle isPrimary', () => {
26
+ render(
27
+ <ChatbotFooter isPrimary data-testid="footer">
28
+ Chatbot Content
29
+ </ChatbotFooter>
30
+ );
31
+ expect(screen.getByTestId('footer')).toHaveClass('pf-m-primary');
32
+ });
24
33
  });
@@ -13,20 +13,27 @@ import type { HTMLProps, FunctionComponent } from 'react';
13
13
  import { Divider } from '@patternfly/react-core';
14
14
 
15
15
  export interface ChatbotFooterProps extends HTMLProps<HTMLDivElement> {
16
- /** Children for the Footer that supports MessageBar and FootNote components*/
16
+ /** Children for the footer - supports MessageBar and FootNote components*/
17
17
  children?: React.ReactNode;
18
- /** Custom classname for the Footer component */
18
+ /** Custom classname for the footer component */
19
19
  className?: string;
20
+ /** Sets footer to compact styling. */
20
21
  isCompact?: boolean;
22
+ /** Sets background color to primary */
23
+ isPrimary?: boolean;
21
24
  }
22
25
 
23
26
  export const ChatbotFooter: FunctionComponent<ChatbotFooterProps> = ({
24
27
  children,
25
28
  className,
26
29
  isCompact,
30
+ isPrimary,
27
31
  ...props
28
32
  }: ChatbotFooterProps) => (
29
- <div className={`pf-chatbot__footer ${isCompact ? 'pf-m-compact' : ''} ${className ?? ''}`} {...props}>
33
+ <div
34
+ className={`pf-chatbot__footer ${isCompact ? 'pf-m-compact' : ''} ${isPrimary ? 'pf-m-primary' : ''} ${className ?? ''}`}
35
+ {...props}
36
+ >
30
37
  <Divider />
31
38
  <div className="pf-chatbot__footer-container">{children}</div>
32
39
  </div>
@@ -91,6 +91,10 @@
91
91
  align-items: center;
92
92
  justify-content: center;
93
93
 
94
+ &::before {
95
+ border-radius: inherit;
96
+ }
97
+
94
98
  .pf-v6-c-button__icon,
95
99
  .pf-v6-c-menu-toggle__icon,
96
100
  .pf-v6-c-icon__content {
@@ -162,3 +166,18 @@
162
166
  .pf-chatbot__header .pf-chatbot__actions .pf-v6-c-menu-toggle.pf-m-secondary.pf-m-compact {
163
167
  width: initial;
164
168
  }
169
+
170
+ // ============================================================================
171
+ // High contrast
172
+ // ============================================================================
173
+ :root:where(.pf-v6-theme-high-contrast) {
174
+ // Chatbot Display Mode - Fullscreen and Embedded
175
+ @media screen and (min-width: 64rem) {
176
+ .pf-chatbot--fullscreen,
177
+ .pf-chatbot--embedded {
178
+ .pf-chatbot__header__divider {
179
+ display: var(--pf-v6-hidden-visible--Display);
180
+ }
181
+ }
182
+ }
183
+ }
@@ -1,17 +1,31 @@
1
1
  // ============================================================================
2
2
  // Code Modal - Chatbot Modal with Code Editor
3
3
  // ============================================================================
4
+
4
5
  import type { FunctionComponent, MouseEvent } from 'react';
5
- import { useState } from 'react';
6
+ import { useState, useEffect, useRef } from 'react';
6
7
  import path from 'path-browserify';
8
+ import * as monaco from 'monaco-editor';
9
+ import { loader } from '@monaco-editor/react';
7
10
 
8
11
  // Import PatternFly components
9
12
  import { CodeEditor } from '@patternfly/react-code-editor';
10
- import { Button, ModalBody, ModalFooter, ModalHeader, Stack, StackItem } from '@patternfly/react-core';
13
+ import {
14
+ Button,
15
+ getResizeObserver,
16
+ ModalBody,
17
+ ModalFooter,
18
+ ModalHeader,
19
+ Stack,
20
+ StackItem
21
+ } from '@patternfly/react-core';
11
22
  import FileDetails, { extensionToLanguage } from '../FileDetails';
12
23
  import { ChatbotDisplayMode } from '../Chatbot';
13
24
  import ChatbotModal from '../ChatbotModal/ChatbotModal';
14
25
 
26
+ // Configure Monaco loader to use the npm package instead of CDN
27
+ loader.config({ monaco });
28
+
15
29
  export interface CodeModalProps {
16
30
  /** Class applied to code editor */
17
31
  codeEditorControlClassName?: string;
@@ -73,6 +87,34 @@ export const CodeModal: FunctionComponent<CodeModalProps> = ({
73
87
  ...props
74
88
  }: CodeModalProps) => {
75
89
  const [newCode, setNewCode] = useState(code);
90
+ const [editorInstance, setEditorInstance] = useState<monaco.editor.IStandaloneCodeEditor | null>(null);
91
+ const [isEditorReady, setIsEditorReady] = useState(false);
92
+ const containerRef = useRef<HTMLDivElement>(null);
93
+
94
+ useEffect(() => {
95
+ if (!isModalOpen || !isEditorReady || !editorInstance || !containerRef.current) {
96
+ return;
97
+ }
98
+
99
+ const handleResize = () => {
100
+ if (editorInstance && isEditorReady && isModalOpen) {
101
+ try {
102
+ window.requestAnimationFrame(() => {
103
+ editorInstance.layout();
104
+ });
105
+ } catch (error) {
106
+ // eslint-disable-next-line no-console
107
+ console.error('ChatBot code modal layout error:', error);
108
+ }
109
+ }
110
+ };
111
+
112
+ const observer = getResizeObserver(containerRef.current, handleResize);
113
+
114
+ return () => {
115
+ observer();
116
+ };
117
+ }, [editorInstance, isEditorReady, isModalOpen]);
76
118
 
77
119
  const handlePrimaryAction = (_event: MouseEvent | MouseEvent | KeyboardEvent) => {
78
120
  handleModalToggle(_event);
@@ -89,9 +131,15 @@ export const CodeModal: FunctionComponent<CodeModalProps> = ({
89
131
  };
90
132
 
91
133
  const onEditorDidMount = (editor, monaco) => {
92
- editor.layout();
93
- editor.focus();
134
+ setEditorInstance(editor);
135
+
94
136
  monaco.editor.getModels()[0].updateOptions({ tabSize: 5 });
137
+
138
+ if (containerRef.current) {
139
+ setIsEditorReady(true);
140
+ editor.layout();
141
+ editor.focus();
142
+ }
95
143
  };
96
144
 
97
145
  const onCodeChange = (value: string) => {
@@ -117,7 +165,7 @@ export const CodeModal: FunctionComponent<CodeModalProps> = ({
117
165
  <StackItem className="pf-chatbot__code-modal-file-details">
118
166
  <FileDetails fileName={fileName} />
119
167
  </StackItem>
120
- <StackItem className="pf-chatbot__code-modal-editor">
168
+ <div className="pf-v6-l-stack__item pf-chatbot__code-modal-editor" ref={containerRef}>
121
169
  <CodeEditor
122
170
  isDarkTheme
123
171
  isLineNumbersVisible={isLineNumbersVisible}
@@ -132,11 +180,14 @@ export const CodeModal: FunctionComponent<CodeModalProps> = ({
132
180
  isFullHeight
133
181
  options={{
134
182
  glyphMargin: false,
135
- folding: false
183
+ folding: false,
184
+ // prevents Monaco from handling resizing itself
185
+ // was causing ResizeObserver issues
186
+ automaticLayout: false
136
187
  }}
137
188
  {...props}
138
189
  />
139
- </StackItem>
190
+ </div>
140
191
  </Stack>
141
192
  </ModalBody>
142
193
  <ModalFooter className={modalFooterClassName}>
@@ -1,10 +1,10 @@
1
1
  import { PropsWithChildren } from 'react';
2
- import { Button, Label } from '@patternfly/react-core';
2
+ import { Button, Label, LabelProps } from '@patternfly/react-core';
3
3
  import FileDetails from '../FileDetails';
4
4
  import { Spinner } from '@patternfly/react-core';
5
5
  import { TimesIcon } from '@patternfly/react-icons';
6
6
 
7
- export interface FileDetailsLabelProps {
7
+ export interface FileDetailsLabelProps extends Omit<LabelProps, 'onClose' | 'onClick'> {
8
8
  /** Name of file, including extension */
9
9
  fileName: string;
10
10
  /** Unique id of file */
@@ -30,7 +30,8 @@
30
30
  font-family: var(--pf-t--global--font--family--body);
31
31
  }
32
32
 
33
- .pf-v6-c-code-block__actions-item {
33
+ // we are overriding some default PatternFly positioning here - it's only necessary for the first two items
34
+ .pf-chatbot__message-code-block-default-action {
34
35
  display: flex;
35
36
  align-items: center;
36
37
  justify-content: space-between;
@@ -39,7 +40,7 @@
39
40
  font-weight: var(--pf-t--global--font--weight--body--bold);
40
41
  }
41
42
 
42
- .pf-chatbot__button--copy.pf-v6-c-button {
43
+ .pf-v6-c-code-block__actions-item > .pf-v6-c-button.pf-m-plain {
43
44
  color: var(--pf-t--color--white); // same in light + dark theme
44
45
 
45
46
  &:hover,
@@ -80,6 +81,10 @@
80
81
  --pf-chatbot-message-text-inline-code-font-size: var(--pf-t--global--font--size--body--default);
81
82
  background-color: var(--pf-t--global--background--color--tertiary--default);
82
83
  font-size: var(--pf-chatbot-message-text-inline-code-font-size);
84
+
85
+ &.pf-m-primary {
86
+ background-color: var(--pf-t--global--background--color--secondary--default);
87
+ }
83
88
  }
84
89
 
85
90
  .pf-chatbot__message-code-toggle {
@@ -37,6 +37,10 @@ export interface CodeBlockMessageProps {
37
37
  expandedText?: string;
38
38
  /** Link text applied to expandable toggle when collapsed */
39
39
  collapsedText?: string;
40
+ /** Custom actions added to header of code block, after any default actions such as the "copy" action. */
41
+ customActions?: React.ReactNode;
42
+ /** Sets background colors to be appropriate on primary chatbot background */
43
+ isPrimary?: boolean;
40
44
  }
41
45
 
42
46
  const DEFAULT_EXPANDED_TEXT = 'Show less';
@@ -51,6 +55,8 @@ const CodeBlockMessage = ({
51
55
  expandableSectionToggleProps,
52
56
  expandedText = DEFAULT_EXPANDED_TEXT,
53
57
  collapsedText = DEFAULT_COLLAPSED_TEXT,
58
+ customActions,
59
+ isPrimary,
54
60
  ...props
55
61
  }: CodeBlockMessageProps) => {
56
62
  const [copied, setCopied] = useState(false);
@@ -105,7 +111,7 @@ const CodeBlockMessage = ({
105
111
 
106
112
  if (!String(children).includes('\n')) {
107
113
  return (
108
- <code {...props} className="pf-chatbot__message-inline-code">
114
+ <code {...props} className={`pf-chatbot__message-inline-code ${isPrimary ? 'pf-m-primary' : ''}`}>
109
115
  {children}
110
116
  </code>
111
117
  );
@@ -114,7 +120,7 @@ const CodeBlockMessage = ({
114
120
  // Setup code block header
115
121
  const actions = (
116
122
  <>
117
- <CodeBlockAction>
123
+ <CodeBlockAction className="pf-chatbot__message-code-block-default-action">
118
124
  {language && <div className="pf-chatbot__message-code-block-language">{language}</div>}
119
125
  <Button
120
126
  ref={buttonRef}
@@ -127,6 +133,7 @@ const CodeBlockMessage = ({
127
133
  </Button>
128
134
  <Tooltip id={tooltipID} content="Copy" position="top" triggerRef={buttonRef} />
129
135
  </CodeBlockAction>
136
+ {customActions}
130
137
  </>
131
138
  );
132
139
 
@@ -5,7 +5,7 @@
5
5
  display: flex;
6
6
  align-items: flex-start;
7
7
  gap: var(--pf-t--global--spacer--lg);
8
- padding-bottom: var(--pf-t--global--spacer--xl);
8
+ padding-bottom: var(--pf-t--global--spacer--lg);
9
9
 
10
10
  // Avatar
11
11
  // --------------------------------------------------------------------------
@@ -156,8 +156,8 @@
156
156
  // ============================================================================
157
157
  .pf-chatbot.pf-m-compact {
158
158
  .pf-chatbot__message {
159
- gap: var(--pf-t--global--spacer--md);
160
- padding-bottom: var(--pf-t--global--spacer--sm);
159
+ gap: var(--pf-t--global--spacer--sm);
160
+ padding-bottom: var(--pf-t--global--spacer--md);
161
161
 
162
162
  .pf-chatbot__message-contents  {
163
163
  gap: var(--pf-t--global--spacer--xs);
@@ -6,7 +6,7 @@ import userEvent from '@testing-library/user-event';
6
6
  import { monitorSampleAppQuickStart } from './QuickStarts/monitor-sampleapp-quickstart';
7
7
  import { monitorSampleAppQuickStartWithImage } from './QuickStarts/monitor-sampleapp-quickstart-with-image';
8
8
  import rehypeExternalLinks from '../__mocks__/rehype-external-links';
9
- import { AlertActionLink } from '@patternfly/react-core';
9
+ import { AlertActionLink, Button, CodeBlockAction } from '@patternfly/react-core';
10
10
  import { DeepThinkingProps } from '../DeepThinking';
11
11
 
12
12
  const ALL_ACTIONS = [
@@ -228,6 +228,10 @@ describe('Message', () => {
228
228
  render(<Message avatar="./testImg" role="bot" name="Bot" content="Hi" />);
229
229
  expect(screen.getByRole('img')).toHaveAttribute('src', './testImg');
230
230
  });
231
+ it('should not render avatar if no avatar prop is passed', () => {
232
+ render(<Message role="bot" name="Bot" content="Hi" />);
233
+ expect(screen.queryByRole('img')).not.toBeInTheDocument();
234
+ });
231
235
  it('should render botWord correctly', () => {
232
236
  render(<Message avatar="./img" role="bot" name="Bot" content="Hi" botWord="人工知能" />);
233
237
  expect(screen.getByText('Bot')).toBeTruthy();
@@ -612,6 +616,24 @@ describe('Message', () => {
612
616
  );
613
617
  expect(screen.getByRole('button', { name: 'test' })).toBeTruthy();
614
618
  });
619
+ it('should be able to add custom actions to CodeMessage', () => {
620
+ render(
621
+ <Message
622
+ avatar="./img"
623
+ role="user"
624
+ name="User"
625
+ content={CODE_MESSAGE}
626
+ codeBlockProps={{
627
+ customActions: (
628
+ <CodeBlockAction>
629
+ <Button>New custom action</Button>
630
+ </CodeBlockAction>
631
+ )
632
+ }}
633
+ />
634
+ );
635
+ expect(screen.getByRole('button', { name: /New custom action/i })).toBeTruthy();
636
+ });
615
637
  it('should handle hasRoundAvatar correctly when it is true', () => {
616
638
  render(<Message avatar="./img" role="user" name="User" content="Hi" hasRoundAvatar />);
617
639
  expect(screen.getByRole('img')).toBeTruthy();
@@ -1075,4 +1097,41 @@ describe('Message', () => {
1075
1097
  expect(screen.getByText('Thought for 3 seconds')).toBeTruthy();
1076
1098
  expect(screen.getByText("Here's why I said this.")).toBeTruthy();
1077
1099
  });
1100
+ it('should handle isPrimary correctly for inline code when it is true', () => {
1101
+ const { container } = render(<Message avatar="./img" role="user" name="User" content={INLINE_CODE} isPrimary />);
1102
+ expect(container.querySelector('.pf-m-primary')).toBeTruthy();
1103
+ });
1104
+ it('should handle isPrimary correctly for inline code when it is false', () => {
1105
+ const { container } = render(<Message avatar="./img" role="user" name="User" content={INLINE_CODE} />);
1106
+ expect(container.querySelector('.pf-m-primary')).toBeFalsy();
1107
+ });
1108
+ it('should handle isPrimary correctly for table when it is true', () => {
1109
+ const { container } = render(<Message avatar="./img" role="user" name="User" content={TABLE} isPrimary />);
1110
+ expect(container.querySelector('.pf-m-primary')).toBeTruthy();
1111
+ });
1112
+ it('should handle isPrimary correctly for table when it is false', () => {
1113
+ const { container } = render(<Message avatar="./img" role="user" name="User" content={TABLE} />);
1114
+ expect(container.querySelector('.pf-m-primary')).toBeFalsy();
1115
+ });
1116
+ it('should handle isPrimary correctly for loading when it is true', () => {
1117
+ const { container } = render(<Message avatar="./img" role="user" name="User" content="" isPrimary isLoading />);
1118
+ expect(container.querySelector('.pf-m-primary')).toBeTruthy();
1119
+ });
1120
+ it('should handle isPrimary correctly for loading when it is false', () => {
1121
+ const { container } = render(<Message avatar="./img" role="user" name="User" content="" isLoading />);
1122
+
1123
+ expect(container.querySelector('.pf-m-primary')).toBeFalsy();
1124
+ });
1125
+ it('should handle isPrimary correctly for attachments when it is true', () => {
1126
+ const { container } = render(
1127
+ <Message avatar="./img" role="user" name="User" content="" isPrimary attachments={[{ name: 'testAttachment' }]} />
1128
+ );
1129
+ expect(container.querySelector('.pf-m-outline')).toBeTruthy();
1130
+ });
1131
+ it('should handle isPrimary correctly for attachments when it is false', () => {
1132
+ const { container } = render(
1133
+ <Message avatar="./img" role="user" name="User" content="" attachments={[{ name: 'testAttachment' }]} />
1134
+ );
1135
+ expect(container.querySelector('.pf-m-outline')).toBeFalsy();
1136
+ });
1078
1137
  });
@@ -11,8 +11,6 @@ import {
11
11
  AvatarProps,
12
12
  ButtonProps,
13
13
  ContentVariants,
14
- ExpandableSectionProps,
15
- ExpandableSectionToggleProps,
16
14
  FormProps,
17
15
  Label,
18
16
  LabelGroupProps,
@@ -20,7 +18,7 @@ import {
20
18
  Truncate
21
19
  } from '@patternfly/react-core';
22
20
  import MessageLoading from './MessageLoading';
23
- import CodeBlockMessage from './CodeBlockMessage/CodeBlockMessage';
21
+ import CodeBlockMessage, { CodeBlockMessageProps } from './CodeBlockMessage/CodeBlockMessage';
24
22
  import TextMessage from './TextMessage/TextMessage';
25
23
  import FileDetailsLabel from '../FileDetailsLabel/FileDetailsLabel';
26
24
  import ResponseActions, { ActionProps } from '../ResponseActions/ResponseActions';
@@ -44,6 +42,9 @@ import ImageMessage from './ImageMessage/ImageMessage';
44
42
  import rehypeUnwrapImages from 'rehype-unwrap-images';
45
43
  import rehypeExternalLinks from 'rehype-external-links';
46
44
  import rehypeSanitize from 'rehype-sanitize';
45
+ import rehypeHighlight from 'rehype-highlight';
46
+ // see the full list of styles here: https://highlightjs.org/examples
47
+ import 'highlight.js/styles/vs2015.css';
47
48
  import { PluggableList } from 'unified';
48
49
  import LinkMessage from './LinkMessage/LinkMessage';
49
50
  import ErrorMessage from './ErrorMessage/ErrorMessage';
@@ -96,7 +97,7 @@ export interface MessageProps extends Omit<HTMLProps<HTMLDivElement>, 'role'> {
96
97
  /** Name of the user */
97
98
  name?: string;
98
99
  /** Avatar src for the user */
99
- avatar: string;
100
+ avatar?: string;
100
101
  /** Timestamp for the message */
101
102
  timestamp?: string;
102
103
  /** Set this to true if message is being loaded */
@@ -114,24 +115,7 @@ export interface MessageProps extends Omit<HTMLProps<HTMLDivElement>, 'role'> {
114
115
  /** Label for the English "Loading message," displayed to screenreaders when loading a message */
115
116
  loadingWord?: string;
116
117
  /** Props for code blocks */
117
- codeBlockProps?: {
118
- /** Aria label applied to code blocks */
119
- 'aria-label'?: string;
120
- /** Class name applied to code blocks */
121
- className?: string;
122
- /** Whether code blocks are expandable */
123
- isExpandable?: boolean;
124
- /** Length of text initially shown in expandable code blocks; defaults to 10 characters */
125
- maxLength?: number;
126
- /** Additional props passed to expandable section if isExpandable is applied */
127
- expandableSectionProps?: Omit<ExpandableSectionProps, 'ref'>;
128
- /** Additional props passed to expandable toggle if isExpandable is applied */
129
- expandableSectionToggleProps?: ExpandableSectionToggleProps;
130
- /** Link text applied to expandable toggle when expanded */
131
- expandedText?: string;
132
- /** Link text applied to expandable toggle when collapsed */
133
- collapsedText?: string;
134
- };
118
+ codeBlockProps?: CodeBlockMessageProps;
135
119
  /** Props for quick responses */
136
120
  quickResponses?: QuickResponse[];
137
121
  /** Props for quick responses container */
@@ -205,6 +189,8 @@ export interface MessageProps extends Omit<HTMLProps<HTMLDivElement>, 'role'> {
205
189
  toolCall?: ToolCallProps;
206
190
  /** Whether user messages default to stripping out images in markdown */
207
191
  hasNoImagesInUserMessages?: boolean;
192
+ /** Sets background colors to be appropriate on primary chatbot background */
193
+ isPrimary?: boolean;
208
194
  }
209
195
 
210
196
  export const MessageBase: FunctionComponent<MessageProps> = ({
@@ -252,6 +238,7 @@ export const MessageBase: FunctionComponent<MessageProps> = ({
252
238
  remarkGfmProps,
253
239
  toolCall,
254
240
  hasNoImagesInUserMessages = true,
241
+ isPrimary,
255
242
  ...props
256
243
  }: MessageProps) => {
257
244
  const [messageText, setMessageText] = useState(content);
@@ -261,7 +248,7 @@ export const MessageBase: FunctionComponent<MessageProps> = ({
261
248
  }, [content]);
262
249
 
263
250
  const { beforeMainContent, afterMainContent, endContent } = extraContent || {};
264
- let rehypePlugins: PluggableList = [rehypeUnwrapImages, rehypeMoveImagesOutOfParagraphs];
251
+ let rehypePlugins: PluggableList = [rehypeUnwrapImages, rehypeMoveImagesOutOfParagraphs, rehypeHighlight];
265
252
  if (openLinkInNewTab) {
266
253
  rehypePlugins = rehypePlugins.concat([[rehypeExternalLinks, { target: '_blank' }, rehypeSanitize]]);
267
254
  }
@@ -302,13 +289,13 @@ export const MessageBase: FunctionComponent<MessageProps> = ({
302
289
  p: (props) => {
303
290
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
304
291
  const { node, ...rest } = props;
305
- return <TextMessage component={ContentVariants.p} {...rest} />;
292
+ return <TextMessage component={ContentVariants.p} {...rest} isPrimary={isPrimary} />;
306
293
  },
307
294
  code: ({ children, ...props }) => {
308
295
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
309
296
  const { node, ...codeProps } = props;
310
297
  return (
311
- <CodeBlockMessage {...codeProps} {...codeBlockProps}>
298
+ <CodeBlockMessage {...codeProps} {...codeBlockProps} isPrimary={isPrimary}>
312
299
  {children}
313
300
  </CodeBlockMessage>
314
301
  );
@@ -364,7 +351,7 @@ export const MessageBase: FunctionComponent<MessageProps> = ({
364
351
  return <ListItemMessage {...rest} />;
365
352
  },
366
353
  // table requires node attribute for calculating headers for mobile breakpoint
367
- table: (props) => <TableMessage {...props} {...tableProps} />,
354
+ table: (props) => <TableMessage {...props} {...tableProps} isPrimary={isPrimary} />,
368
355
  tbody: (props) => {
369
356
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
370
357
  const { node, ...rest } = props;
@@ -432,7 +419,7 @@ export const MessageBase: FunctionComponent<MessageProps> = ({
432
419
 
433
420
  const renderMessage = () => {
434
421
  if (isLoading) {
435
- return <MessageLoading loadingWord={loadingWord} />;
422
+ return <MessageLoading loadingWord={loadingWord} isPrimary={isPrimary} />;
436
423
  }
437
424
  if (isEditable) {
438
425
  return (
@@ -472,12 +459,14 @@ export const MessageBase: FunctionComponent<MessageProps> = ({
472
459
  {...props}
473
460
  >
474
461
  {/* We are using an empty alt tag intentionally in order to reduce noise on screen readers */}
475
- <Avatar
476
- className={`pf-chatbot__message-avatar ${hasRoundAvatar ? 'pf-chatbot__message-avatar--round' : ''} ${avatarClassName ? avatarClassName : ''}`}
477
- src={avatar}
478
- alt=""
479
- {...avatarProps}
480
- />
462
+ {avatar && (
463
+ <Avatar
464
+ className={`pf-chatbot__message-avatar ${hasRoundAvatar ? 'pf-chatbot__message-avatar--round' : ''} ${avatarClassName ? avatarClassName : ''}`}
465
+ src={avatar}
466
+ alt=""
467
+ {...avatarProps}
468
+ />
469
+ )}
481
470
  <div className="pf-chatbot__message-contents">
482
471
  <div className="pf-chatbot__message-meta">
483
472
  {name && (
@@ -538,6 +527,7 @@ export const MessageBase: FunctionComponent<MessageProps> = ({
538
527
  closeButtonAriaLabel={attachment.closeButtonAriaLabel}
539
528
  languageTestId={attachment.languageTestId}
540
529
  spinnerTestId={attachment.spinnerTestId}
530
+ variant={isPrimary ? 'outline' : undefined}
541
531
  />
542
532
  </div>
543
533
  ))}
@@ -6,6 +6,9 @@
6
6
  padding: var(--pf-t--global--spacer--sm);
7
7
  background-color: var(--pf-t--global--background--color--tertiary--default);
8
8
  border-radius: var(--pf-t--global--border--radius--small);
9
+ // for high contrast support
10
+ border: var(--pf-t--global--border--width--high-contrast--regular) solid
11
+ var(--pf-t--global--border--color--high-contrast);
9
12
 
10
13
  &-dots {
11
14
  position: relative;
@@ -50,4 +53,8 @@
50
53
  background-color: rgba(41, 41, 41, 0.25);
51
54
  }
52
55
  }
56
+
57
+ &.pf-m-primary {
58
+ background-color: var(--pf-t--global--background--color--secondary--default);
59
+ }
53
60
  }
@@ -2,8 +2,8 @@
2
2
  // Chatbot Main - Message - Processing
3
3
  // ============================================================================
4
4
 
5
- const MessageLoading = ({ loadingWord }) => (
6
- <div className="pf-chatbot__message-loading">
5
+ const MessageLoading = ({ loadingWord, isPrimary }) => (
6
+ <div className={`pf-chatbot__message-loading ${isPrimary ? 'pf-m-primary' : ''}`}>
7
7
  <span className="pf-chatbot__message-loading-dots">
8
8
  <span className="pf-v6-screen-reader">{loadingWord}</span>
9
9
  </span>
@@ -8,6 +8,10 @@
8
8
  border-block-start: 0;
9
9
  }
10
10
 
11
+ &.pf-m-primary {
12
+ --pf-v6-c-table--BackgroundColor: var(--pf-t--global--background--color--secondary--default) !important;
13
+ }
14
+
11
15
  tbody {
12
16
  border-radius: var(--pf-t--global--border--radius--small);
13
17
  }