@patternfly/chatbot 6.5.0-prerelease.21 → 6.5.0-prerelease.23

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 (97) hide show
  1. package/dist/cjs/DeepThinking/DeepThinking.d.ts +13 -0
  2. package/dist/cjs/DeepThinking/DeepThinking.js +31 -3
  3. package/dist/cjs/DeepThinking/DeepThinking.test.js +80 -0
  4. package/dist/cjs/MarkdownContent/MarkdownContent.d.ts +39 -0
  5. package/dist/cjs/MarkdownContent/MarkdownContent.js +181 -0
  6. package/dist/cjs/MarkdownContent/MarkdownContent.test.d.ts +1 -0
  7. package/dist/cjs/MarkdownContent/MarkdownContent.test.js +192 -0
  8. package/dist/cjs/MarkdownContent/index.d.ts +2 -0
  9. package/dist/cjs/MarkdownContent/index.js +23 -0
  10. package/dist/cjs/Message/CodeBlockMessage/CodeBlockMessage.d.ts +3 -1
  11. package/dist/cjs/Message/CodeBlockMessage/CodeBlockMessage.js +3 -2
  12. package/dist/cjs/Message/LinkMessage/LinkMessage.d.ts +5 -1
  13. package/dist/cjs/Message/LinkMessage/LinkMessage.js +4 -3
  14. package/dist/cjs/Message/ListMessage/OrderedListMessage.d.ts +9 -1
  15. package/dist/cjs/Message/ListMessage/OrderedListMessage.js +2 -1
  16. package/dist/cjs/Message/ListMessage/UnorderedListMessage.d.ts +7 -1
  17. package/dist/cjs/Message/ListMessage/UnorderedListMessage.js +2 -1
  18. package/dist/cjs/Message/Message.js +2 -155
  19. package/dist/cjs/Message/TableMessage/TableMessage.d.ts +6 -1
  20. package/dist/cjs/Message/TableMessage/TableMessage.js +3 -2
  21. package/dist/cjs/Message/TextMessage/TextMessage.d.ts +8 -1
  22. package/dist/cjs/Message/TextMessage/TextMessage.js +3 -2
  23. package/dist/cjs/ToolCall/ToolCall.d.ts +11 -0
  24. package/dist/cjs/ToolCall/ToolCall.js +24 -3
  25. package/dist/cjs/ToolCall/ToolCall.test.js +57 -0
  26. package/dist/cjs/ToolResponse/ToolResponse.d.ts +17 -0
  27. package/dist/cjs/ToolResponse/ToolResponse.js +49 -3
  28. package/dist/cjs/ToolResponse/ToolResponse.test.js +100 -0
  29. package/dist/cjs/index.d.ts +2 -0
  30. package/dist/cjs/index.js +4 -1
  31. package/dist/css/main.css +48 -0
  32. package/dist/css/main.css.map +1 -1
  33. package/dist/dynamic/MarkdownContent/package.json +1 -0
  34. package/dist/esm/DeepThinking/DeepThinking.d.ts +13 -0
  35. package/dist/esm/DeepThinking/DeepThinking.js +28 -3
  36. package/dist/esm/DeepThinking/DeepThinking.test.js +80 -0
  37. package/dist/esm/MarkdownContent/MarkdownContent.d.ts +39 -0
  38. package/dist/esm/MarkdownContent/MarkdownContent.js +174 -0
  39. package/dist/esm/MarkdownContent/MarkdownContent.test.d.ts +1 -0
  40. package/dist/esm/MarkdownContent/MarkdownContent.test.js +187 -0
  41. package/dist/esm/MarkdownContent/index.d.ts +2 -0
  42. package/dist/esm/MarkdownContent/index.js +2 -0
  43. package/dist/esm/Message/CodeBlockMessage/CodeBlockMessage.d.ts +3 -1
  44. package/dist/esm/Message/CodeBlockMessage/CodeBlockMessage.js +3 -2
  45. package/dist/esm/Message/LinkMessage/LinkMessage.d.ts +5 -1
  46. package/dist/esm/Message/LinkMessage/LinkMessage.js +4 -3
  47. package/dist/esm/Message/ListMessage/OrderedListMessage.d.ts +9 -1
  48. package/dist/esm/Message/ListMessage/OrderedListMessage.js +2 -1
  49. package/dist/esm/Message/ListMessage/UnorderedListMessage.d.ts +7 -1
  50. package/dist/esm/Message/ListMessage/UnorderedListMessage.js +2 -1
  51. package/dist/esm/Message/Message.js +3 -156
  52. package/dist/esm/Message/TableMessage/TableMessage.d.ts +6 -1
  53. package/dist/esm/Message/TableMessage/TableMessage.js +3 -2
  54. package/dist/esm/Message/TextMessage/TextMessage.d.ts +8 -1
  55. package/dist/esm/Message/TextMessage/TextMessage.js +3 -2
  56. package/dist/esm/ToolCall/ToolCall.d.ts +11 -0
  57. package/dist/esm/ToolCall/ToolCall.js +21 -3
  58. package/dist/esm/ToolCall/ToolCall.test.js +57 -0
  59. package/dist/esm/ToolResponse/ToolResponse.d.ts +17 -0
  60. package/dist/esm/ToolResponse/ToolResponse.js +46 -3
  61. package/dist/esm/ToolResponse/ToolResponse.test.js +100 -0
  62. package/dist/esm/index.d.ts +2 -0
  63. package/dist/esm/index.js +2 -0
  64. package/dist/tsconfig.tsbuildinfo +1 -1
  65. package/package.json +1 -1
  66. package/patternfly-docs/content/extensions/chatbot/examples/Messages/MessageWithDeepThinking.tsx +25 -11
  67. package/patternfly-docs/content/extensions/chatbot/examples/Messages/MessageWithMarkdownDeepThinking.tsx +26 -0
  68. package/patternfly-docs/content/extensions/chatbot/examples/Messages/MessageWithMarkdownToolCall.tsx +29 -0
  69. package/patternfly-docs/content/extensions/chatbot/examples/Messages/MessageWithMarkdownToolResponse.tsx +200 -0
  70. package/patternfly-docs/content/extensions/chatbot/examples/Messages/MessageWithToolCall.tsx +14 -1
  71. package/patternfly-docs/content/extensions/chatbot/examples/Messages/MessageWithToolResponse.tsx +222 -105
  72. package/patternfly-docs/content/extensions/chatbot/examples/Messages/Messages.md +32 -0
  73. package/patternfly-docs/content/extensions/chatbot/examples/demos/Chatbot.md +15 -1
  74. package/src/DeepThinking/DeepThinking.test.tsx +109 -0
  75. package/src/DeepThinking/DeepThinking.tsx +54 -5
  76. package/src/MarkdownContent/MarkdownContent.test.tsx +207 -0
  77. package/src/MarkdownContent/MarkdownContent.tsx +264 -0
  78. package/src/MarkdownContent/index.ts +2 -0
  79. package/src/Message/CodeBlockMessage/CodeBlockMessage.scss +4 -0
  80. package/src/Message/CodeBlockMessage/CodeBlockMessage.tsx +5 -1
  81. package/src/Message/LinkMessage/LinkMessage.scss +5 -0
  82. package/src/Message/LinkMessage/LinkMessage.tsx +24 -2
  83. package/src/Message/ListMessage/ListMessage.scss +8 -0
  84. package/src/Message/ListMessage/OrderedListMessage.tsx +16 -2
  85. package/src/Message/ListMessage/UnorderedListMessage.tsx +12 -2
  86. package/src/Message/Message.tsx +21 -181
  87. package/src/Message/TableMessage/TableMessage.scss +11 -0
  88. package/src/Message/TableMessage/TableMessage.tsx +18 -2
  89. package/src/Message/TextMessage/TextMessage.scss +8 -0
  90. package/src/Message/TextMessage/TextMessage.tsx +29 -2
  91. package/src/ToolCall/ToolCall.test.tsx +91 -0
  92. package/src/ToolCall/ToolCall.tsx +49 -4
  93. package/src/ToolResponse/ToolResponse.scss +10 -0
  94. package/src/ToolResponse/ToolResponse.test.tsx +119 -0
  95. package/src/ToolResponse/ToolResponse.tsx +82 -7
  96. package/src/index.ts +3 -0
  97. package/src/main.scss +1 -0
@@ -0,0 +1,207 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import '@testing-library/jest-dom';
3
+ import MarkdownContent from './MarkdownContent';
4
+ import rehypeExternalLinks from '../__mocks__/rehype-external-links';
5
+
6
+ const BOLD_TEXT = '**Bold text**';
7
+ const ITALIC_TEXT = '*Italic text*';
8
+ const INLINE_CODE = 'Here is inline code: `const x = 5`';
9
+ const CODE_BLOCK = `\`\`\`javascript
10
+ function hello() {
11
+ console.log('Hello, world!');
12
+ }
13
+ \`\`\``;
14
+ const HEADING = '# Heading 1';
15
+ const LINK = '[PatternFly](https://www.patternfly.org/)';
16
+ const UNORDERED_LIST = `
17
+ * Item 1
18
+ * Item 2
19
+ * Item 3
20
+ `;
21
+ const ORDERED_LIST = `
22
+ 1. First item
23
+ 2. Second item
24
+ 3. Third item
25
+ `;
26
+ const TABLE = `
27
+ | Column 1 | Column 2 |
28
+ |----------|----------|
29
+ | Cell 1 | Cell 2 |
30
+ | Cell 3 | Cell 4 |
31
+ `;
32
+ const BLOCKQUOTE = '> This is a blockquote';
33
+ const IMAGE = '![Alt text](https://example.com/image.png)';
34
+
35
+ describe('MarkdownContent', () => {
36
+ beforeEach(() => {
37
+ jest.clearAllMocks();
38
+ });
39
+
40
+ it('should render bold text correctly', () => {
41
+ const { container } = render(<MarkdownContent content={BOLD_TEXT} />);
42
+ expect(container.querySelector('strong')).toBeTruthy();
43
+ expect(screen.getByText('Bold text')).toBeTruthy();
44
+ });
45
+
46
+ it('should render italic text correctly', () => {
47
+ const { container } = render(<MarkdownContent content={ITALIC_TEXT} />);
48
+ expect(container.querySelector('em')).toBeTruthy();
49
+ expect(screen.getByText('Italic text')).toBeTruthy();
50
+ });
51
+
52
+ it('should render inline code correctly', () => {
53
+ render(<MarkdownContent content={INLINE_CODE} />);
54
+ expect(screen.getByText(/const x = 5/)).toBeTruthy();
55
+ });
56
+
57
+ it('should render code blocks correctly', () => {
58
+ render(<MarkdownContent content={CODE_BLOCK} />);
59
+
60
+ expect(screen.getByText(/function hello/)).toBeVisible();
61
+ expect(screen.getByText(/console.log/)).toBeVisible();
62
+ expect(screen.getByRole('button', { name: 'Copy code' })).toBeVisible();
63
+ });
64
+
65
+ it('should render headings correctly', () => {
66
+ render(<MarkdownContent content={HEADING} />);
67
+ expect(screen.getByRole('heading', { name: /Heading 1/i })).toBeTruthy();
68
+ });
69
+
70
+ it('should render links correctly', () => {
71
+ render(<MarkdownContent content={LINK} />);
72
+ expect(screen.getByRole('link', { name: /PatternFly/i })).toBeTruthy();
73
+ });
74
+
75
+ it('should render unordered lists correctly', () => {
76
+ render(<MarkdownContent content={UNORDERED_LIST} />);
77
+ expect(screen.getByText('Item 1')).toBeTruthy();
78
+ expect(screen.getByText('Item 2')).toBeTruthy();
79
+ expect(screen.getByText('Item 3')).toBeTruthy();
80
+ expect(screen.getAllByRole('listitem')).toHaveLength(3);
81
+ });
82
+
83
+ it('should render ordered lists correctly', () => {
84
+ render(<MarkdownContent content={ORDERED_LIST} />);
85
+ expect(screen.getByText('First item')).toBeTruthy();
86
+ expect(screen.getByText('Second item')).toBeTruthy();
87
+ expect(screen.getByText('Third item')).toBeTruthy();
88
+ expect(screen.getAllByRole('listitem')).toHaveLength(3);
89
+ });
90
+
91
+ it('should render tables correctly', () => {
92
+ render(<MarkdownContent content={TABLE} tableProps={{ 'aria-label': 'Test table' }} />);
93
+ expect(screen.getByRole('grid', { name: /Test table/i })).toBeTruthy();
94
+ expect(screen.getByRole('columnheader', { name: /Column 1/i })).toBeTruthy();
95
+ expect(screen.getByRole('columnheader', { name: /Column 2/i })).toBeTruthy();
96
+ expect(screen.getByRole('cell', { name: /Cell 1/i })).toBeTruthy();
97
+ expect(screen.getByRole('cell', { name: /Cell 2/i })).toBeTruthy();
98
+ });
99
+
100
+ it('should render blockquotes correctly', () => {
101
+ render(<MarkdownContent content={BLOCKQUOTE} />);
102
+
103
+ const quote = screen.getByText(/This is a blockquote/);
104
+ expect(quote).toBeVisible();
105
+ expect(quote.closest('.pf-v6-c-content--blockquote')?.tagName).toBe('BLOCKQUOTE');
106
+ });
107
+
108
+ it('should render images when hasNoImages is false', () => {
109
+ render(<MarkdownContent content={IMAGE} hasNoImages={false} />);
110
+ expect(screen.getByRole('img', { name: /Alt text/i })).toBeTruthy();
111
+ });
112
+
113
+ it('should not render images when hasNoImages is true', () => {
114
+ render(<MarkdownContent content={IMAGE} hasNoImages />);
115
+ expect(screen.queryByRole('img', { name: /Alt text/i })).toBeFalsy();
116
+ });
117
+
118
+ it('should disable markdown rendering when isMarkdownDisabled is true', () => {
119
+ render(<MarkdownContent content={BOLD_TEXT} isMarkdownDisabled />);
120
+ expect(screen.getByText('**Bold text**')).toBeTruthy();
121
+ });
122
+
123
+ it('should render text component when isMarkdownDisabled is true and textComponent is provided', () => {
124
+ const textComponent = <div data-testid="custom-text">Custom text component</div>;
125
+ render(<MarkdownContent content={BOLD_TEXT} isMarkdownDisabled textComponent={textComponent} />);
126
+ expect(screen.getByTestId('custom-text')).toBeTruthy();
127
+ expect(screen.getByText('Custom text component')).toBeTruthy();
128
+ });
129
+
130
+ it('should apply isPrimary prop to elements', () => {
131
+ const { container } = render(<MarkdownContent content={INLINE_CODE} isPrimary />);
132
+ expect(container.querySelector('.pf-m-primary')).toBeTruthy();
133
+ });
134
+
135
+ it('should apply shouldRetainStyles prop to elements', () => {
136
+ const { container } = render(<MarkdownContent content={BOLD_TEXT} shouldRetainStyles />);
137
+ expect(container.querySelector('.pf-m-markdown')).toBeTruthy();
138
+ });
139
+
140
+ it('should pass codeBlockProps to code blocks', () => {
141
+ render(<MarkdownContent content={CODE_BLOCK} codeBlockProps={{ 'aria-label': 'Custom code block' }} />);
142
+ expect(screen.getByRole('button', { name: /Custom code block/i })).toBeTruthy();
143
+ });
144
+
145
+ it('should pass tableProps to tables', () => {
146
+ render(<MarkdownContent content={TABLE} tableProps={{ 'aria-label': 'Custom table label' }} />);
147
+ expect(screen.getByRole('grid', { name: /Custom table label/i })).toBeTruthy();
148
+ });
149
+
150
+ it('should open links in new tab when openLinkInNewTab is true', () => {
151
+ render(<MarkdownContent content={LINK} openLinkInNewTab />);
152
+ expect(rehypeExternalLinks).toHaveBeenCalledTimes(1);
153
+ });
154
+
155
+ it('should not open links in new tab when openLinkInNewTab is false', () => {
156
+ render(<MarkdownContent content={LINK} openLinkInNewTab={false} />);
157
+ expect(rehypeExternalLinks).not.toHaveBeenCalled();
158
+ });
159
+
160
+ it('should pass linkProps to links', async () => {
161
+ const onClick = jest.fn();
162
+ render(<MarkdownContent content={LINK} linkProps={{ onClick }} />);
163
+ const link = screen.getByRole('link', { name: /PatternFly/i });
164
+ link.click();
165
+ expect(onClick).toHaveBeenCalledTimes(1);
166
+ });
167
+
168
+ it('should handle reactMarkdownProps.disallowedElements', () => {
169
+ render(<MarkdownContent content={CODE_BLOCK} reactMarkdownProps={{ disallowedElements: ['code'] }} />);
170
+ // Code block should not render when disallowed
171
+ expect(screen.queryByRole('button', { name: /Copy code/i })).toBeFalsy();
172
+ });
173
+
174
+ it('should render plain text when no markdown is present', () => {
175
+ render(<MarkdownContent content="Plain text without markdown" />);
176
+ expect(screen.getByText('Plain text without markdown')).toBeTruthy();
177
+ });
178
+
179
+ it('should handle empty content', () => {
180
+ const { container } = render(<MarkdownContent content="" />);
181
+ expect(container.textContent).toBe('');
182
+ });
183
+
184
+ it('should handle undefined content', () => {
185
+ const { container } = render(<MarkdownContent />);
186
+ expect(container.textContent).toBe('');
187
+ });
188
+
189
+ it('should render multiple markdown elements together', () => {
190
+ const content = `# Heading
191
+
192
+ **Bold text** and *italic text*
193
+
194
+ \`\`\`javascript
195
+ const x = 5;
196
+ \`\`\`
197
+
198
+ [Link](https://example.com)`;
199
+
200
+ render(<MarkdownContent content={content} />);
201
+ expect(screen.getByRole('heading', { name: /Heading/i })).toBeTruthy();
202
+ expect(screen.getByText('Bold text')).toBeTruthy();
203
+ expect(screen.getByText('italic text')).toBeTruthy();
204
+ expect(screen.getByText(/const x = 5/)).toBeTruthy();
205
+ expect(screen.getByRole('link', { name: /Link/i })).toBeTruthy();
206
+ });
207
+ });
@@ -0,0 +1,264 @@
1
+ // ============================================================================
2
+ // Markdown Content - Shared component for rendering markdown
3
+ // ============================================================================
4
+ import { type FunctionComponent, ReactNode } from 'react';
5
+ import Markdown, { Options } from 'react-markdown';
6
+ import remarkGfm from 'remark-gfm';
7
+ import { ContentVariants } from '@patternfly/react-core';
8
+ import CodeBlockMessage, { CodeBlockMessageProps } from '../Message/CodeBlockMessage/CodeBlockMessage';
9
+ import TextMessage from '../Message/TextMessage/TextMessage';
10
+ import ListItemMessage from '../Message/ListMessage/ListItemMessage';
11
+ import UnorderedListMessage from '../Message/ListMessage/UnorderedListMessage';
12
+ import OrderedListMessage from '../Message/ListMessage/OrderedListMessage';
13
+ import TableMessage from '../Message/TableMessage/TableMessage';
14
+ import TrMessage from '../Message/TableMessage/TrMessage';
15
+ import TdMessage from '../Message/TableMessage/TdMessage';
16
+ import TbodyMessage from '../Message/TableMessage/TbodyMessage';
17
+ import TheadMessage from '../Message/TableMessage/TheadMessage';
18
+ import ThMessage from '../Message/TableMessage/ThMessage';
19
+ import { TableProps } from '@patternfly/react-table';
20
+ import ImageMessage from '../Message/ImageMessage/ImageMessage';
21
+ import rehypeUnwrapImages from 'rehype-unwrap-images';
22
+ import rehypeExternalLinks from 'rehype-external-links';
23
+ import rehypeSanitize from 'rehype-sanitize';
24
+ import rehypeHighlight from 'rehype-highlight';
25
+ import 'highlight.js/styles/vs2015.css';
26
+ import { PluggableList } from 'unified';
27
+ import LinkMessage from '../Message/LinkMessage/LinkMessage';
28
+ import { rehypeMoveImagesOutOfParagraphs } from '../Message/Plugins/rehypeMoveImagesOutOfParagraphs';
29
+ import SuperscriptMessage from '../Message/SuperscriptMessage/SuperscriptMessage';
30
+ import { ButtonProps } from '@patternfly/react-core';
31
+ import { css } from '@patternfly/react-styles';
32
+
33
+ export interface MarkdownContentProps {
34
+ /** The markdown content to render */
35
+ content?: string;
36
+ /** Disables markdown parsing, allowing only text input */
37
+ isMarkdownDisabled?: boolean;
38
+ /** Props for code blocks */
39
+ codeBlockProps?: CodeBlockMessageProps;
40
+ /** Props for table message. It is important to include a detailed aria-label that describes the purpose of the table. */
41
+ tableProps?: Required<Pick<TableProps, 'aria-label'>> & TableProps;
42
+ /** Additional rehype plugins passed from the consumer */
43
+ additionalRehypePlugins?: PluggableList;
44
+ /** Additional remark plugins passed from the consumer */
45
+ additionalRemarkPlugins?: PluggableList;
46
+ /** Whether to open links in message in new tab. */
47
+ openLinkInNewTab?: boolean;
48
+ /** Props for links */
49
+ linkProps?: ButtonProps;
50
+ /** Allows passing additional props down to markdown parser react-markdown, such as allowedElements and disallowedElements. See https://github.com/remarkjs/react-markdown?tab=readme-ov-file#options for options */
51
+ reactMarkdownProps?: Options;
52
+ /** Allows passing additional props down to remark-gfm. See https://github.com/remarkjs/remark-gfm?tab=readme-ov-file#options for options */
53
+ remarkGfmProps?: Options;
54
+ /** Whether to strip out images in markdown */
55
+ hasNoImages?: boolean;
56
+ /** Sets background colors to be appropriate on primary chatbot background */
57
+ isPrimary?: boolean;
58
+ /** Custom component to render when markdown is disabled */
59
+ textComponent?: ReactNode;
60
+ /** Flag indicating whether content should retain various styles of its context (typically font-size and text color). */
61
+ shouldRetainStyles?: boolean;
62
+ }
63
+
64
+ export const MarkdownContent: FunctionComponent<MarkdownContentProps> = ({
65
+ content,
66
+ isMarkdownDisabled,
67
+ codeBlockProps,
68
+ tableProps,
69
+ openLinkInNewTab = true,
70
+ additionalRehypePlugins = [],
71
+ additionalRemarkPlugins = [],
72
+ linkProps,
73
+ reactMarkdownProps,
74
+ remarkGfmProps,
75
+ hasNoImages = false,
76
+ isPrimary,
77
+ textComponent,
78
+ shouldRetainStyles
79
+ }: MarkdownContentProps) => {
80
+ let rehypePlugins: PluggableList = [rehypeUnwrapImages, rehypeMoveImagesOutOfParagraphs, rehypeHighlight];
81
+ if (openLinkInNewTab) {
82
+ rehypePlugins = rehypePlugins.concat([[rehypeExternalLinks, { target: '_blank' }, rehypeSanitize]]);
83
+ }
84
+ if (additionalRehypePlugins) {
85
+ rehypePlugins.push(...additionalRehypePlugins);
86
+ }
87
+
88
+ const disallowedElements = hasNoImages ? ['img'] : [];
89
+ if (reactMarkdownProps && reactMarkdownProps.disallowedElements) {
90
+ disallowedElements.push(...reactMarkdownProps.disallowedElements);
91
+ }
92
+
93
+ if (isMarkdownDisabled) {
94
+ if (textComponent) {
95
+ return <>{textComponent}</>;
96
+ }
97
+ return (
98
+ <TextMessage component={ContentVariants.p} isPrimary={isPrimary}>
99
+ {content}
100
+ </TextMessage>
101
+ );
102
+ }
103
+
104
+ return (
105
+ <Markdown
106
+ components={{
107
+ section: (props) => {
108
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
109
+ const { node, ...rest } = props;
110
+ return (
111
+ <section
112
+ {...rest}
113
+ className={css('pf-chatbot__message-text', shouldRetainStyles && 'pf-m-markdown', rest?.className)}
114
+ />
115
+ );
116
+ },
117
+ p: (props) => {
118
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
119
+ const { node, ...rest } = props;
120
+ return (
121
+ <TextMessage
122
+ shouldRetainStyles={shouldRetainStyles}
123
+ component={ContentVariants.p}
124
+ {...rest}
125
+ isPrimary={isPrimary}
126
+ />
127
+ );
128
+ },
129
+ code: ({ children, ...props }) => {
130
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
131
+ const { node, ...codeProps } = props;
132
+ return (
133
+ <CodeBlockMessage
134
+ {...codeProps}
135
+ {...codeBlockProps}
136
+ isPrimary={isPrimary}
137
+ shouldRetainStyles={shouldRetainStyles}
138
+ >
139
+ {children}
140
+ </CodeBlockMessage>
141
+ );
142
+ },
143
+ h1: (props) => {
144
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
145
+ const { node, ...rest } = props;
146
+ return <TextMessage shouldRetainStyles={shouldRetainStyles} component={ContentVariants.h1} {...rest} />;
147
+ },
148
+ h2: (props) => {
149
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
150
+ const { node, ...rest } = props;
151
+ return <TextMessage shouldRetainStyles={shouldRetainStyles} component={ContentVariants.h2} {...rest} />;
152
+ },
153
+ h3: (props) => {
154
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
155
+ const { node, ...rest } = props;
156
+ return <TextMessage shouldRetainStyles={shouldRetainStyles} component={ContentVariants.h3} {...rest} />;
157
+ },
158
+ h4: (props) => {
159
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
160
+ const { node, ...rest } = props;
161
+ return <TextMessage shouldRetainStyles={shouldRetainStyles} component={ContentVariants.h4} {...rest} />;
162
+ },
163
+ h5: (props) => {
164
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
165
+ const { node, ...rest } = props;
166
+ return <TextMessage shouldRetainStyles={shouldRetainStyles} component={ContentVariants.h5} {...rest} />;
167
+ },
168
+ h6: (props) => {
169
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
170
+ const { node, ...rest } = props;
171
+ return <TextMessage shouldRetainStyles={shouldRetainStyles} component={ContentVariants.h6} {...rest} />;
172
+ },
173
+ blockquote: (props) => {
174
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
175
+ const { node, ...rest } = props;
176
+ return (
177
+ <TextMessage shouldRetainStyles={shouldRetainStyles} component={ContentVariants.blockquote} {...rest} />
178
+ );
179
+ },
180
+ ul: (props) => {
181
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
182
+ const { node, ...rest } = props;
183
+ return <UnorderedListMessage shouldRetainStyles={shouldRetainStyles} {...rest} />;
184
+ },
185
+ ol: (props) => {
186
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
187
+ const { node, ...rest } = props;
188
+ return <OrderedListMessage shouldRetainStyles={shouldRetainStyles} {...rest} />;
189
+ },
190
+ li: (props) => {
191
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
192
+ const { node, ...rest } = props;
193
+ return <ListItemMessage {...rest} />;
194
+ },
195
+ // table requires node attribute for calculating headers for mobile breakpoint
196
+ table: (props) => (
197
+ <TableMessage shouldRetainStyles={shouldRetainStyles} {...props} {...tableProps} isPrimary={isPrimary} />
198
+ ),
199
+ tbody: (props) => {
200
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
201
+ const { node, ...rest } = props;
202
+ return <TbodyMessage {...rest} />;
203
+ },
204
+ thead: (props) => {
205
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
206
+ const { node, ...rest } = props;
207
+ return <TheadMessage {...rest} />;
208
+ },
209
+ tr: (props) => {
210
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
211
+ const { node, ...rest } = props;
212
+ return <TrMessage {...rest} />;
213
+ },
214
+ td: (props) => {
215
+ // Conflicts with Td type
216
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
217
+ const { node, width, ...rest } = props;
218
+ return <TdMessage {...rest} />;
219
+ },
220
+ th: (props) => {
221
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
222
+ const { node, ...rest } = props;
223
+ return <ThMessage {...rest} />;
224
+ },
225
+ img: (props) => {
226
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
227
+ const { node, ...rest } = props;
228
+ return <ImageMessage {...rest} />;
229
+ },
230
+ a: (props) => {
231
+ // node is just the details of the document structure - not needed
232
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
233
+ const { node, ...rest } = props;
234
+ return (
235
+ // some a types conflict with ButtonProps, but it's ok because we are using an a tag
236
+ // there are too many to handle manually
237
+ <LinkMessage shouldRetainStyles={shouldRetainStyles} {...(rest as any)} {...linkProps}>
238
+ {props.children}
239
+ </LinkMessage>
240
+ );
241
+ },
242
+ // used for footnotes
243
+ sup: (props) => {
244
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
245
+ const { node, ...rest } = props;
246
+ return <SuperscriptMessage {...rest} />;
247
+ }
248
+ }}
249
+ remarkPlugins={[[remarkGfm, { ...remarkGfmProps }], ...additionalRemarkPlugins]}
250
+ rehypePlugins={rehypePlugins}
251
+ {...reactMarkdownProps}
252
+ remarkRehypeOptions={{
253
+ // removes sr-only class from footnote labels applied by default
254
+ footnoteLabelProperties: { className: [''] },
255
+ ...reactMarkdownProps?.remarkRehypeOptions
256
+ }}
257
+ disallowedElements={disallowedElements}
258
+ >
259
+ {content}
260
+ </Markdown>
261
+ );
262
+ };
263
+
264
+ export default MarkdownContent;
@@ -0,0 +1,2 @@
1
+ export { default } from './MarkdownContent';
2
+ export * from './MarkdownContent';
@@ -75,6 +75,10 @@
75
75
  overflow: hidden !important;
76
76
  }
77
77
  }
78
+
79
+ &.pf-m-markdown .pf-v6-c-code-block__code {
80
+ font-size: inherit;
81
+ }
78
82
  }
79
83
 
80
84
  .pf-chatbot__message-inline-code {
@@ -19,6 +19,7 @@ import {
19
19
 
20
20
  import { CheckIcon } from '@patternfly/react-icons/dist/esm/icons/check-icon';
21
21
  import { CopyIcon } from '@patternfly/react-icons/dist/esm/icons/copy-icon';
22
+ import { css } from '@patternfly/react-styles';
22
23
 
23
24
  export interface CodeBlockMessageProps {
24
25
  /** Content rendered in code block */
@@ -41,6 +42,8 @@ export interface CodeBlockMessageProps {
41
42
  customActions?: React.ReactNode;
42
43
  /** Sets background colors to be appropriate on primary chatbot background */
43
44
  isPrimary?: boolean;
45
+ /** Flag indicating that the content should retain message styles when using Markdown. */
46
+ shouldRetainStyles?: boolean;
44
47
  }
45
48
 
46
49
  const DEFAULT_EXPANDED_TEXT = 'Show less';
@@ -57,6 +60,7 @@ const CodeBlockMessage = ({
57
60
  collapsedText = DEFAULT_COLLAPSED_TEXT,
58
61
  customActions,
59
62
  isPrimary,
63
+ shouldRetainStyles,
60
64
  ...props
61
65
  }: CodeBlockMessageProps) => {
62
66
  const [copied, setCopied] = useState(false);
@@ -138,7 +142,7 @@ const CodeBlockMessage = ({
138
142
  );
139
143
 
140
144
  return (
141
- <div className="pf-chatbot__message-code-block" ref={codeBlockRef}>
145
+ <div className={css('pf-chatbot__message-code-block', shouldRetainStyles && 'pf-m-markdown')} ref={codeBlockRef}>
142
146
  <CodeBlock actions={actions}>
143
147
  <CodeBlockCode>
144
148
  <>
@@ -0,0 +1,5 @@
1
+ .pf-v6-c-button.pf-m-link.pf-m-inline {
2
+ &.pf-m-markdown {
3
+ font-size: inherit;
4
+ }
5
+ }
@@ -5,8 +5,21 @@
5
5
  import { Button, ButtonProps } from '@patternfly/react-core';
6
6
  import { ExternalLinkSquareAltIcon } from '@patternfly/react-icons';
7
7
  import { ExtraProps } from 'react-markdown';
8
+ import { css } from '@patternfly/react-styles';
8
9
 
9
- const LinkMessage = ({ children, target, href, id, ...props }: ButtonProps & ExtraProps) => {
10
+ export interface LinkMessageProps {
11
+ /** Flag indicating that the content should retain message styles when using Markdown. */
12
+ shouldRetainStyles?: boolean;
13
+ }
14
+
15
+ const LinkMessage = ({
16
+ children,
17
+ target,
18
+ href,
19
+ id,
20
+ shouldRetainStyles,
21
+ ...props
22
+ }: LinkMessageProps & ButtonProps & ExtraProps) => {
10
23
  if (target === '_blank') {
11
24
  return (
12
25
  <Button
@@ -20,6 +33,7 @@ const LinkMessage = ({ children, target, href, id, ...props }: ButtonProps & Ext
20
33
  // need to explicitly call this out or id doesn't seem to get passed - required for footnotes
21
34
  id={id}
22
35
  {...props}
36
+ className={css(shouldRetainStyles && 'pf-m-markdown', props?.className)}
23
37
  >
24
38
  {children}
25
39
  </Button>
@@ -28,7 +42,15 @@ const LinkMessage = ({ children, target, href, id, ...props }: ButtonProps & Ext
28
42
 
29
43
  return (
30
44
  // need to explicitly call this out or id doesn't seem to get passed - required for footnotes
31
- <Button isInline component="a" href={href} variant="link" id={id} {...props}>
45
+ <Button
46
+ isInline
47
+ component="a"
48
+ href={href}
49
+ variant="link"
50
+ id={id}
51
+ {...props}
52
+ className={css(shouldRetainStyles && 'pf-m-markdown', props?.className)}
53
+ >
32
54
  {children}
33
55
  </Button>
34
56
  );
@@ -13,6 +13,14 @@
13
13
  li {
14
14
  font-size: var(--pf-t--global--font--size--md);
15
15
  }
16
+
17
+ &.pf-m-markdown {
18
+ .pf-v6-c-list,
19
+ ul,
20
+ li {
21
+ font-size: inherit;
22
+ }
23
+ }
16
24
  }
17
25
 
18
26
  .pf-chatbot__message--user {
@@ -4,9 +4,23 @@
4
4
 
5
5
  import { ExtraProps } from 'react-markdown';
6
6
  import { List, ListComponent, OrderType } from '@patternfly/react-core';
7
+ import { css } from '@patternfly/react-styles';
7
8
 
8
- const OrderedListMessage = ({ children, start }: JSX.IntrinsicElements['ol'] & ExtraProps) => (
9
- <div className="pf-chatbot__message-ordered-list">
9
+ export interface OrderedListMessageProps {
10
+ /** The ordered list content */
11
+ children?: React.ReactNode;
12
+ /** The number to start the ordered list at. */
13
+ start?: number;
14
+ /** Flag indicating that the content should retain message styles when using Markdown. */
15
+ shouldRetainStyles?: boolean;
16
+ }
17
+
18
+ const OrderedListMessage = ({
19
+ children,
20
+ start,
21
+ shouldRetainStyles
22
+ }: OrderedListMessageProps & JSX.IntrinsicElements['ol'] & ExtraProps) => (
23
+ <div className={css('pf-chatbot__message-ordered-list', shouldRetainStyles && 'pf-m-markdown')}>
10
24
  <List component={ListComponent.ol} type={OrderType.number} start={start}>
11
25
  {children}
12
26
  </List>
@@ -4,9 +4,19 @@
4
4
 
5
5
  import { ExtraProps } from 'react-markdown';
6
6
  import { List } from '@patternfly/react-core';
7
+ import { css } from '@patternfly/react-styles';
8
+ export interface UnrderedListMessageProps {
9
+ /** The ordered list content */
10
+ children?: React.ReactNode;
11
+ /** Flag indicating that the content should retain message styles when using Markdown. */
12
+ shouldRetainStyles?: boolean;
13
+ }
7
14
 
8
- const UnorderedListMessage = ({ children }: JSX.IntrinsicElements['ul'] & ExtraProps) => (
9
- <div className="pf-chatbot__message-unordered-list">
15
+ const UnorderedListMessage = ({
16
+ children,
17
+ shouldRetainStyles
18
+ }: UnrderedListMessageProps & JSX.IntrinsicElements['ul'] & ExtraProps) => (
19
+ <div className={css('pf-chatbot__message-unordered-list', shouldRetainStyles && 'pf-m-markdown')}>
10
20
  <List>{children}</List>
11
21
  </div>
12
22
  );