@patternfly/chatbot 6.5.0-prerelease.22 → 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 (94) hide show
  1. package/dist/cjs/DeepThinking/DeepThinking.d.ts +11 -0
  2. package/dist/cjs/DeepThinking/DeepThinking.js +30 -2
  3. package/dist/cjs/DeepThinking/DeepThinking.test.js +39 -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 +9 -0
  24. package/dist/cjs/ToolCall/ToolCall.js +19 -3
  25. package/dist/cjs/ToolCall/ToolCall.test.js +31 -0
  26. package/dist/cjs/ToolResponse/ToolResponse.d.ts +15 -0
  27. package/dist/cjs/ToolResponse/ToolResponse.js +48 -2
  28. package/dist/cjs/ToolResponse/ToolResponse.test.js +60 -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 +11 -0
  35. package/dist/esm/DeepThinking/DeepThinking.js +27 -2
  36. package/dist/esm/DeepThinking/DeepThinking.test.js +39 -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 +9 -0
  57. package/dist/esm/ToolCall/ToolCall.js +16 -3
  58. package/dist/esm/ToolCall/ToolCall.test.js +31 -0
  59. package/dist/esm/ToolResponse/ToolResponse.d.ts +15 -0
  60. package/dist/esm/ToolResponse/ToolResponse.js +45 -2
  61. package/dist/esm/ToolResponse/ToolResponse.test.js +60 -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/MessageWithMarkdownDeepThinking.tsx +26 -0
  67. package/patternfly-docs/content/extensions/chatbot/examples/Messages/MessageWithMarkdownToolCall.tsx +29 -0
  68. package/patternfly-docs/content/extensions/chatbot/examples/Messages/MessageWithMarkdownToolResponse.tsx +200 -0
  69. package/patternfly-docs/content/extensions/chatbot/examples/Messages/Messages.md +32 -0
  70. package/patternfly-docs/content/extensions/chatbot/examples/demos/Chatbot.md +15 -1
  71. package/src/DeepThinking/DeepThinking.test.tsx +48 -0
  72. package/src/DeepThinking/DeepThinking.tsx +50 -4
  73. package/src/MarkdownContent/MarkdownContent.test.tsx +207 -0
  74. package/src/MarkdownContent/MarkdownContent.tsx +264 -0
  75. package/src/MarkdownContent/index.ts +2 -0
  76. package/src/Message/CodeBlockMessage/CodeBlockMessage.scss +4 -0
  77. package/src/Message/CodeBlockMessage/CodeBlockMessage.tsx +5 -1
  78. package/src/Message/LinkMessage/LinkMessage.scss +5 -0
  79. package/src/Message/LinkMessage/LinkMessage.tsx +24 -2
  80. package/src/Message/ListMessage/ListMessage.scss +8 -0
  81. package/src/Message/ListMessage/OrderedListMessage.tsx +16 -2
  82. package/src/Message/ListMessage/UnorderedListMessage.tsx +12 -2
  83. package/src/Message/Message.tsx +21 -181
  84. package/src/Message/TableMessage/TableMessage.scss +11 -0
  85. package/src/Message/TableMessage/TableMessage.tsx +18 -2
  86. package/src/Message/TextMessage/TextMessage.scss +8 -0
  87. package/src/Message/TextMessage/TextMessage.tsx +29 -2
  88. package/src/ToolCall/ToolCall.test.tsx +40 -0
  89. package/src/ToolCall/ToolCall.tsx +37 -3
  90. package/src/ToolResponse/ToolResponse.scss +10 -0
  91. package/src/ToolResponse/ToolResponse.test.tsx +75 -0
  92. package/src/ToolResponse/ToolResponse.tsx +78 -6
  93. package/src/index.ts +3 -0
  94. package/src/main.scss +1 -0
@@ -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
  );
@@ -3,14 +3,12 @@
3
3
  // ============================================================================
4
4
  import { forwardRef, ReactNode, useEffect, useState } from 'react';
5
5
  import type { FunctionComponent, HTMLProps, MouseEvent as ReactMouseEvent, Ref } from 'react';
6
- import Markdown, { Options } from 'react-markdown';
7
- import remarkGfm from 'remark-gfm';
6
+ import { Options } from 'react-markdown';
8
7
  import {
9
8
  AlertProps,
10
9
  Avatar,
11
10
  AvatarProps,
12
11
  ButtonProps,
13
- ContentVariants,
14
12
  FormProps,
15
13
  Label,
16
14
  LabelGroupProps,
@@ -18,42 +16,25 @@ import {
18
16
  Truncate
19
17
  } from '@patternfly/react-core';
20
18
  import MessageLoading from './MessageLoading';
21
- import CodeBlockMessage, { CodeBlockMessageProps } from './CodeBlockMessage/CodeBlockMessage';
22
- import TextMessage from './TextMessage/TextMessage';
19
+ import { CodeBlockMessageProps } from './CodeBlockMessage/CodeBlockMessage';
23
20
  import FileDetailsLabel from '../FileDetailsLabel/FileDetailsLabel';
24
21
  import ResponseActions, { ActionProps } from '../ResponseActions/ResponseActions';
25
22
  import SourcesCard, { SourcesCardProps } from '../SourcesCard';
26
- import ListItemMessage from './ListMessage/ListItemMessage';
27
- import UnorderedListMessage from './ListMessage/UnorderedListMessage';
28
- import OrderedListMessage from './ListMessage/OrderedListMessage';
29
23
  import QuickStartTile from './QuickStarts/QuickStartTile';
30
24
  import { QuickStart, QuickstartAction } from './QuickStarts/types';
31
25
  import QuickResponse from './QuickResponse/QuickResponse';
32
26
  import UserFeedback, { UserFeedbackProps } from './UserFeedback/UserFeedback';
33
27
  import UserFeedbackComplete, { UserFeedbackCompleteProps } from './UserFeedback/UserFeedbackComplete';
34
- import TableMessage from './TableMessage/TableMessage';
35
- import TrMessage from './TableMessage/TrMessage';
36
- import TdMessage from './TableMessage/TdMessage';
37
- import TbodyMessage from './TableMessage/TbodyMessage';
38
- import TheadMessage from './TableMessage/TheadMessage';
39
- import ThMessage from './TableMessage/ThMessage';
40
28
  import { TableProps } from '@patternfly/react-table';
41
- import ImageMessage from './ImageMessage/ImageMessage';
42
- import rehypeUnwrapImages from 'rehype-unwrap-images';
43
- import rehypeExternalLinks from 'rehype-external-links';
44
- import rehypeSanitize from 'rehype-sanitize';
45
- import rehypeHighlight from 'rehype-highlight';
46
29
  // see the full list of styles here: https://highlightjs.org/examples
47
30
  import 'highlight.js/styles/vs2015.css';
48
31
  import { PluggableList } from 'unified';
49
- import LinkMessage from './LinkMessage/LinkMessage';
50
32
  import ErrorMessage from './ErrorMessage/ErrorMessage';
51
33
  import MessageInput from './MessageInput';
52
- import { rehypeMoveImagesOutOfParagraphs } from './Plugins/rehypeMoveImagesOutOfParagraphs';
53
34
  import ToolResponse, { ToolResponseProps } from '../ToolResponse';
54
35
  import DeepThinking, { DeepThinkingProps } from '../DeepThinking';
55
- import SuperscriptMessage from './SuperscriptMessage/SuperscriptMessage';
56
36
  import ToolCall, { ToolCallProps } from '../ToolCall';
37
+ import MarkdownContent from '../MarkdownContent';
57
38
 
58
39
  export interface MessageAttachment {
59
40
  /** Name of file attached to the message */
@@ -267,14 +248,8 @@ export const MessageBase: FunctionComponent<MessageProps> = ({
267
248
  }, [content]);
268
249
 
269
250
  const { beforeMainContent, afterMainContent, endContent } = extraContent || {};
270
- let rehypePlugins: PluggableList = [rehypeUnwrapImages, rehypeMoveImagesOutOfParagraphs, rehypeHighlight];
271
- if (openLinkInNewTab) {
272
- rehypePlugins = rehypePlugins.concat([[rehypeExternalLinks, { target: '_blank' }, rehypeSanitize]]);
273
- }
274
- if (additionalRehypePlugins) {
275
- rehypePlugins.push(...additionalRehypePlugins);
276
- }
277
- let avatarClassName;
251
+
252
+ let avatarClassName: string | undefined;
278
253
  if (avatarProps && 'className' in avatarProps) {
279
254
  const { className, ...rest } = avatarProps;
280
255
  avatarClassName = className;
@@ -284,157 +259,22 @@ export const MessageBase: FunctionComponent<MessageProps> = ({
284
259
  const date = new Date();
285
260
  const dateString = timestamp ?? `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`;
286
261
 
287
- const disallowedElements = role === 'user' && hasNoImagesInUserMessages ? ['img'] : [];
288
- if (reactMarkdownProps && reactMarkdownProps.disallowedElements) {
289
- disallowedElements.push(...reactMarkdownProps.disallowedElements);
290
- }
291
-
292
- const handleMarkdown = () => {
293
- if (isMarkdownDisabled) {
294
- return (
295
- <TextMessage component={ContentVariants.p} {...props}>
296
- {messageText}
297
- </TextMessage>
298
- );
299
- }
300
- return (
301
- <Markdown
302
- components={{
303
- section: (props) => {
304
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
305
- const { node, ...rest } = props;
306
- return <section {...rest} className={`pf-chatbot__message-text ${rest?.className}`} />;
307
- },
308
- p: (props) => {
309
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
310
- const { node, ...rest } = props;
311
- return <TextMessage component={ContentVariants.p} {...rest} isPrimary={isPrimary} />;
312
- },
313
- code: ({ children, ...props }) => {
314
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
315
- const { node, ...codeProps } = props;
316
- return (
317
- <CodeBlockMessage {...codeProps} {...codeBlockProps} isPrimary={isPrimary}>
318
- {children}
319
- </CodeBlockMessage>
320
- );
321
- },
322
- h1: (props) => {
323
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
324
- const { node, ...rest } = props;
325
- return <TextMessage component={ContentVariants.h1} {...rest} />;
326
- },
327
- h2: (props) => {
328
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
329
- const { node, ...rest } = props;
330
- return <TextMessage component={ContentVariants.h2} {...rest} />;
331
- },
332
- h3: (props) => {
333
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
334
- const { node, ...rest } = props;
335
- return <TextMessage component={ContentVariants.h3} {...rest} />;
336
- },
337
- h4: (props) => {
338
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
339
- const { node, ...rest } = props;
340
- return <TextMessage component={ContentVariants.h4} {...rest} />;
341
- },
342
- h5: (props) => {
343
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
344
- const { node, ...rest } = props;
345
- return <TextMessage component={ContentVariants.h5} {...rest} />;
346
- },
347
- h6: (props) => {
348
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
349
- const { node, ...rest } = props;
350
- return <TextMessage component={ContentVariants.h6} {...rest} />;
351
- },
352
- blockquote: (props) => {
353
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
354
- const { node, ...rest } = props;
355
- return <TextMessage component={ContentVariants.blockquote} {...rest} />;
356
- },
357
- ul: (props) => {
358
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
359
- const { node, ...rest } = props;
360
- return <UnorderedListMessage {...rest} />;
361
- },
362
- ol: (props) => {
363
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
364
- const { node, ...rest } = props;
365
- return <OrderedListMessage {...rest} />;
366
- },
367
- li: (props) => {
368
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
369
- const { node, ...rest } = props;
370
- return <ListItemMessage {...rest} />;
371
- },
372
- // table requires node attribute for calculating headers for mobile breakpoint
373
- table: (props) => <TableMessage {...props} {...tableProps} isPrimary={isPrimary} />,
374
- tbody: (props) => {
375
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
376
- const { node, ...rest } = props;
377
- return <TbodyMessage {...rest} />;
378
- },
379
- thead: (props) => {
380
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
381
- const { node, ...rest } = props;
382
- return <TheadMessage {...rest} />;
383
- },
384
- tr: (props) => {
385
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
386
- const { node, ...rest } = props;
387
- return <TrMessage {...rest} />;
388
- },
389
- td: (props) => {
390
- // Conflicts with Td type
391
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
392
- const { node, width, ...rest } = props;
393
- return <TdMessage {...rest} />;
394
- },
395
- th: (props) => {
396
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
397
- const { node, ...rest } = props;
398
- return <ThMessage {...rest} />;
399
- },
400
- img: (props) => {
401
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
402
- const { node, ...rest } = props;
403
- return <ImageMessage {...rest} />;
404
- },
405
- a: (props) => {
406
- // node is just the details of the document structure - not needed
407
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
408
- const { node, ...rest } = props;
409
- return (
410
- // some a types conflict with ButtonProps, but it's ok because we are using an a tag
411
- // there are too many to handle manually
412
- <LinkMessage {...(rest as any)} {...linkProps}>
413
- {props.children}
414
- </LinkMessage>
415
- );
416
- },
417
- // used for footnotes
418
- sup: (props) => {
419
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
420
- const { node, ...rest } = props;
421
- return <SuperscriptMessage {...rest} />;
422
- }
423
- }}
424
- remarkPlugins={[[remarkGfm, { ...remarkGfmProps }], ...additionalRemarkPlugins]}
425
- rehypePlugins={rehypePlugins}
426
- {...reactMarkdownProps}
427
- remarkRehypeOptions={{
428
- // removes sr-only class from footnote labels applied by default
429
- footnoteLabelProperties: { className: [''] },
430
- ...reactMarkdownProps?.remarkRehypeOptions
431
- }}
432
- disallowedElements={disallowedElements}
433
- >
434
- {messageText}
435
- </Markdown>
436
- );
437
- };
262
+ const handleMarkdown = () => (
263
+ <MarkdownContent
264
+ content={messageText}
265
+ isMarkdownDisabled={isMarkdownDisabled}
266
+ codeBlockProps={codeBlockProps}
267
+ tableProps={tableProps}
268
+ openLinkInNewTab={openLinkInNewTab}
269
+ additionalRehypePlugins={additionalRehypePlugins}
270
+ additionalRemarkPlugins={additionalRemarkPlugins}
271
+ linkProps={linkProps}
272
+ reactMarkdownProps={reactMarkdownProps}
273
+ remarkGfmProps={remarkGfmProps}
274
+ hasNoImages={role === 'user' && hasNoImagesInUserMessages}
275
+ isPrimary={isPrimary}
276
+ />
277
+ );
438
278
 
439
279
  const renderMessage = () => {
440
280
  if (isLoading) {
@@ -25,4 +25,15 @@
25
25
  .pf-v6-c-table__tr:last-of-type {
26
26
  border-block-end: 0;
27
27
  }
28
+
29
+ &.pf-m-markdown {
30
+ table,
31
+ tbody,
32
+ td,
33
+ thead,
34
+ th,
35
+ tr {
36
+ font-size: inherit;
37
+ }
38
+ }
28
39
  }
@@ -5,6 +5,7 @@
5
5
  import { Children, cloneElement } from 'react';
6
6
  import { ExtraProps } from 'react-markdown';
7
7
  import { Table, TableProps } from '@patternfly/react-table';
8
+ import { css } from '@patternfly/react-styles';
8
9
 
9
10
  interface Properties {
10
11
  line: number;
@@ -20,10 +21,20 @@ export interface TableNode {
20
21
  }
21
22
 
22
23
  export interface TableMessageProps {
24
+ /** Content of the table */
25
+ children?: React.ReactNode;
26
+ /** Flag indicating whether primary styles should be applied. */
23
27
  isPrimary?: boolean;
28
+ /** Flag indicating that the content should retain message styles when using Markdown. */
29
+ shouldRetainStyles?: boolean;
24
30
  }
25
31
 
26
- const TableMessage = ({ children, isPrimary, ...props }: Omit<TableProps, 'ref'> & ExtraProps & TableMessageProps) => {
32
+ const TableMessage = ({
33
+ children,
34
+ isPrimary,
35
+ shouldRetainStyles,
36
+ ...props
37
+ }: Omit<TableProps, 'ref'> & ExtraProps & TableMessageProps) => {
27
38
  const { className, ...rest } = props;
28
39
 
29
40
  // This allows us to parse the nested data we get back from the 3rd party Markdown parser
@@ -76,7 +87,12 @@ const TableMessage = ({ children, isPrimary, ...props }: Omit<TableProps, 'ref'>
76
87
  <Table
77
88
  aria-label={props['aria-label']}
78
89
  gridBreakPoint="grid"
79
- className={`pf-chatbot__message-table ${isPrimary ? 'pf-m-primary' : ''} ${className ? className : ''}`}
90
+ className={css(
91
+ 'pf-chatbot__message-table',
92
+ isPrimary && 'pf-m-primary',
93
+ shouldRetainStyles && 'pf-m-markdown',
94
+ className
95
+ )}
80
96
  {...rest}
81
97
  >
82
98
  {modifyChildren(children)}
@@ -53,6 +53,14 @@
53
53
  background-color: var(--pf-t--global--background--color--secondary--default);
54
54
  }
55
55
  }
56
+
57
+ &.pf-m-markdown {
58
+ display: block;
59
+ }
60
+ &.pf-m-markdown > [class^='pf-v6-c-content'] {
61
+ font-size: inherit;
62
+ color: inherit;
63
+ }
56
64
  }
57
65
 
58
66
  // ============================================================================
@@ -4,19 +4,46 @@
4
4
 
5
5
  import { ExtraProps } from 'react-markdown';
6
6
  import { Content, ContentProps } from '@patternfly/react-core';
7
+ import { css } from '@patternfly/react-styles';
7
8
 
8
9
  export interface TextMessageProps {
10
+ /** The text message content */
11
+ children?: React.ReactNode;
12
+ /** Flag indicating whether primary styling is applied. */
9
13
  isPrimary?: boolean;
14
+ /** The wrapper component to use for the PatternFly Content component. Defaults to a div. */
15
+ component?:
16
+ | 'h1'
17
+ | 'h2'
18
+ | 'h3'
19
+ | 'h4'
20
+ | 'h5'
21
+ | 'h6'
22
+ | 'p'
23
+ | 'a'
24
+ | 'small'
25
+ | 'blockquote'
26
+ | 'pre'
27
+ | 'hr'
28
+ | 'ul'
29
+ | 'ol'
30
+ | 'dl'
31
+ | 'li'
32
+ | 'dt'
33
+ | 'dd';
34
+ /** Flag indicating that the content should retain message styles when using Markdown. */
35
+ shouldRetainStyles?: boolean;
10
36
  }
11
37
 
12
38
  const TextMessage = ({
13
39
  component,
14
40
  children,
15
41
  isPrimary,
42
+ shouldRetainStyles,
16
43
  ...props
17
44
  }: Omit<ContentProps, 'ref'> & ExtraProps & TextMessageProps) => (
18
- <span className={`pf-chatbot__message-text ${isPrimary ? 'pf-m-primary' : ''}`}>
19
- <Content component={component} {...props}>
45
+ <span className={css('pf-chatbot__message-text', isPrimary && 'pf-m-primary', shouldRetainStyles && 'pf-m-markdown')}>
46
+ <Content component={component} {...props} className={css(props?.className)}>
20
47
  {children}
21
48
  </Content>
22
49
  </span>
@@ -232,4 +232,44 @@ describe('ToolCall', () => {
232
232
  expect(toggleButton).toHaveAttribute('aria-expanded', 'false');
233
233
  expect(screen.queryByText('Expandable Content')).not.toBeVisible();
234
234
  });
235
+
236
+ it('should render titleText as markdown when isTitleMarkdown is true', () => {
237
+ const titleText = '**Bold title**';
238
+ const { container } = render(<ToolCall titleText={titleText} isTitleMarkdown />);
239
+ expect(container.querySelector('strong')).toBeTruthy();
240
+ expect(screen.getByText('Bold title')).toBeTruthy();
241
+ });
242
+
243
+ it('should not render titleText as markdown when isTitleMarkdown is false', () => {
244
+ const titleText = '**Bold title**';
245
+ render(<ToolCall titleText={titleText} />);
246
+ expect(screen.getByText('**Bold title**')).toBeTruthy();
247
+ });
248
+
249
+ it('should render expandableContent as markdown when isExpandableContentMarkdown is true', async () => {
250
+ const user = userEvent.setup();
251
+ const expandableContent = '**Bold expandable content**';
252
+ const { container } = render(
253
+ <ToolCall {...defaultProps} expandableContent={expandableContent} isExpandableContentMarkdown />
254
+ );
255
+ await user.click(screen.getByRole('button', { name: defaultProps.titleText }));
256
+ expect(container.querySelector('strong')).toBeTruthy();
257
+ expect(screen.getByText('Bold expandable content')).toBeTruthy();
258
+ });
259
+
260
+ it('should not render expandableContent as markdown when isExpandableContentMarkdown is false', async () => {
261
+ const user = userEvent.setup();
262
+ const expandableContent = '**Bold expandable content**';
263
+ render(<ToolCall {...defaultProps} expandableContent={expandableContent} />);
264
+ await user.click(screen.getByRole('button', { name: defaultProps.titleText }));
265
+ expect(screen.getByText('**Bold expandable content**')).toBeTruthy();
266
+ });
267
+
268
+ it('should pass markdownContentProps to MarkdownContent component', () => {
269
+ const titleText = '**Bold title**';
270
+ const { container } = render(
271
+ <ToolCall titleText={titleText} isTitleMarkdown markdownContentProps={{ isPrimary: true }} />
272
+ );
273
+ expect(container.querySelector('.pf-m-primary')).toBeTruthy();
274
+ });
235
275
  });
@@ -19,6 +19,8 @@ import {
19
19
  Spinner,
20
20
  SpinnerProps
21
21
  } from '@patternfly/react-core';
22
+ import MarkdownContent from '../MarkdownContent';
23
+ import type { MarkdownContentProps } from '../MarkdownContent';
22
24
 
23
25
  export interface ToolCallProps {
24
26
  /** Title text for the tool call. */
@@ -61,6 +63,14 @@ export interface ToolCallProps {
61
63
  cardFooterProps?: CardFooterProps;
62
64
  /** Additional props for the expandable section when expandableContent is passed. */
63
65
  expandableSectionProps?: Omit<ExpandableSectionProps, 'ref'>;
66
+ /** Whether to enable markdown rendering for titleText. When true, titleText will be parsed as markdown. */
67
+ isTitleMarkdown?: boolean;
68
+ /** Whether to enable markdown rendering for expandableContent. When true and expandableContent is a string, it will be parsed as markdown. */
69
+ isExpandableContentMarkdown?: boolean;
70
+ /** Props passed to MarkdownContent component when markdown is enabled */
71
+ markdownContentProps?: Omit<MarkdownContentProps, 'content'>;
72
+ /** Whether to retain styles in the MarkdownContent component. Defaults to false. */
73
+ shouldRetainStyles?: boolean;
64
74
  }
65
75
 
66
76
  export const ToolCall: FunctionComponent<ToolCallProps> = ({
@@ -83,7 +93,11 @@ export const ToolCall: FunctionComponent<ToolCallProps> = ({
83
93
  cardBodyProps,
84
94
  cardFooterProps,
85
95
  expandableSectionProps,
86
- spinnerProps
96
+ spinnerProps,
97
+ isTitleMarkdown,
98
+ isExpandableContentMarkdown,
99
+ markdownContentProps,
100
+ shouldRetainStyles = false
87
101
  }: ToolCallProps) => {
88
102
  const [isExpanded, setIsExpanded] = useState(isDefaultExpanded);
89
103
 
@@ -91,6 +105,13 @@ export const ToolCall: FunctionComponent<ToolCallProps> = ({
91
105
  setIsExpanded(isExpanded);
92
106
  };
93
107
 
108
+ const renderTitle = () => {
109
+ if (isTitleMarkdown) {
110
+ return <MarkdownContent shouldRetainStyles={shouldRetainStyles} content={titleText} {...markdownContentProps} />;
111
+ }
112
+ return titleText;
113
+ };
114
+
94
115
  const titleContent = (
95
116
  <span className={`pf-chatbot__tool-call-title-content`}>
96
117
  {isLoading ? (
@@ -99,10 +120,23 @@ export const ToolCall: FunctionComponent<ToolCallProps> = ({
99
120
  {<span className="pf-chatbot__tool-call-title-text">{loadingText}</span>}
100
121
  </>
101
122
  ) : (
102
- <span className="pf-chatbot__tool-call-title-text">{titleText}</span>
123
+ <span className="pf-chatbot__tool-call-title-text">{renderTitle()}</span>
103
124
  )}
104
125
  </span>
105
126
  );
127
+
128
+ const renderExpandableContent = () => {
129
+ if (isExpandableContentMarkdown && typeof expandableContent === 'string') {
130
+ return (
131
+ <MarkdownContent
132
+ shouldRetainStyles={shouldRetainStyles}
133
+ content={expandableContent}
134
+ {...markdownContentProps}
135
+ />
136
+ );
137
+ }
138
+ return expandableContent;
139
+ };
106
140
  const defaultActions = (
107
141
  <>
108
142
  <ActionListItem {...actionListItemProps} {...cancelActionItemProps}>
@@ -138,7 +172,7 @@ export const ToolCall: FunctionComponent<ToolCallProps> = ({
138
172
  isIndented
139
173
  {...expandableSectionProps}
140
174
  >
141
- {expandableContent}
175
+ {renderExpandableContent()}
142
176
  </ExpandableSection>
143
177
  ) : (
144
178
  titleContent
@@ -34,3 +34,13 @@
34
34
  --pf-v6-c-divider--Color: var(--pf-t--global--border--color--default);
35
35
  }
36
36
  }
37
+
38
+ .pf-chatbot__tool-response-expandable-section .pf-v6-c-expandable-section__toggle .pf-m-markdown {
39
+ padding: inherit;
40
+ }
41
+
42
+ .pf-chatbot__tool-response {
43
+ .pf-chatbot__message-image {
44
+ max-width: 100%;
45
+ }
46
+ }