@patternfly/chatbot 6.4.0-prerelease.2 → 6.4.0-prerelease.20

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 (186) hide show
  1. package/dist/cjs/Chatbot/Chatbot.js +1 -7
  2. package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.d.ts +2 -0
  3. package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.js +2 -2
  4. package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.d.ts +22 -2
  5. package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.js +15 -9
  6. package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.test.js +40 -2
  7. package/dist/cjs/ChatbotHeader/ChatbotHeaderMenu.js +1 -1
  8. package/dist/cjs/ChatbotHeader/ChatbotHeaderMenu.test.js +1 -1
  9. package/dist/cjs/ChatbotHeader/ChatbotHeaderNewChatButton.d.ts +18 -0
  10. package/dist/cjs/ChatbotHeader/ChatbotHeaderNewChatButton.js +25 -0
  11. package/dist/cjs/ChatbotHeader/ChatbotHeaderNewChatButton.test.d.ts +1 -0
  12. package/dist/cjs/ChatbotHeader/ChatbotHeaderNewChatButton.test.js +22 -0
  13. package/dist/cjs/ChatbotHeader/index.d.ts +1 -0
  14. package/dist/cjs/ChatbotHeader/index.js +1 -0
  15. package/dist/cjs/DeepThinking/DeepThinking.d.ts +18 -0
  16. package/dist/cjs/DeepThinking/DeepThinking.js +18 -0
  17. package/dist/cjs/DeepThinking/DeepThinking.test.d.ts +1 -0
  18. package/dist/cjs/DeepThinking/DeepThinking.test.js +48 -0
  19. package/dist/cjs/DeepThinking/index.d.ts +2 -0
  20. package/dist/cjs/DeepThinking/index.js +23 -0
  21. package/dist/cjs/FilePreview/FilePreview.d.ts +26 -0
  22. package/dist/cjs/FilePreview/FilePreview.js +26 -0
  23. package/dist/cjs/FilePreview/FilePreview.test.d.ts +1 -0
  24. package/dist/cjs/FilePreview/FilePreview.test.js +97 -0
  25. package/dist/cjs/FilePreview/index.d.ts +2 -0
  26. package/dist/cjs/FilePreview/index.js +23 -0
  27. package/dist/cjs/Message/CodeBlockMessage/CodeBlockMessage.js +3 -3
  28. package/dist/cjs/Message/LinkMessage/LinkMessage.d.ts +2 -1
  29. package/dist/cjs/Message/LinkMessage/LinkMessage.js +7 -3
  30. package/dist/cjs/Message/ListMessage/ListItemMessage.d.ts +1 -1
  31. package/dist/cjs/Message/ListMessage/ListItemMessage.js +16 -1
  32. package/dist/cjs/Message/Message.d.ts +15 -0
  33. package/dist/cjs/Message/Message.js +129 -32
  34. package/dist/cjs/Message/Message.test.js +71 -0
  35. package/dist/cjs/Message/SuperscriptMessage/SuperscriptMessage.d.ts +3 -0
  36. package/dist/cjs/Message/SuperscriptMessage/SuperscriptMessage.js +5 -0
  37. package/dist/cjs/Message/UserFeedback/UserFeedback.d.ts +15 -1
  38. package/dist/cjs/Message/UserFeedback/UserFeedback.js +4 -4
  39. package/dist/cjs/Message/UserFeedback/UserFeedback.test.js +44 -0
  40. package/dist/cjs/MessageBar/MessageBar.js +19 -4
  41. package/dist/cjs/MessageBox/JumpButton.d.ts +5 -0
  42. package/dist/cjs/MessageBox/JumpButton.js +1 -1
  43. package/dist/cjs/MessageBox/JumpButton.test.js +4 -4
  44. package/dist/cjs/MessageBox/MessageBox.d.ts +9 -0
  45. package/dist/cjs/MessageBox/MessageBox.js +2 -2
  46. package/dist/cjs/MessageBox/MessageBox.test.js +2 -2
  47. package/dist/cjs/SourcesCard/SourcesCard.d.ts +13 -1
  48. package/dist/cjs/SourcesCard/SourcesCard.js +6 -6
  49. package/dist/cjs/SourcesCard/SourcesCard.test.js +49 -0
  50. package/dist/cjs/ToolResponse/ToolResponse.d.ts +30 -0
  51. package/dist/cjs/ToolResponse/ToolResponse.js +18 -0
  52. package/dist/cjs/ToolResponse/ToolResponse.test.d.ts +1 -0
  53. package/dist/cjs/ToolResponse/ToolResponse.test.js +60 -0
  54. package/dist/cjs/ToolResponse/index.d.ts +2 -0
  55. package/dist/cjs/ToolResponse/index.js +23 -0
  56. package/dist/cjs/index.d.ts +6 -0
  57. package/dist/cjs/index.js +10 -1
  58. package/dist/css/main.css +273 -17
  59. package/dist/css/main.css.map +1 -1
  60. package/dist/dynamic/DeepThinking/package.json +1 -0
  61. package/dist/dynamic/FilePreview/package.json +1 -0
  62. package/dist/dynamic/ToolResponse/package.json +1 -0
  63. package/dist/esm/Chatbot/Chatbot.js +1 -7
  64. package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.d.ts +2 -0
  65. package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.js +2 -2
  66. package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.d.ts +22 -2
  67. package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.js +17 -11
  68. package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.test.js +41 -3
  69. package/dist/esm/ChatbotHeader/ChatbotHeaderMenu.js +1 -1
  70. package/dist/esm/ChatbotHeader/ChatbotHeaderMenu.test.js +1 -1
  71. package/dist/esm/ChatbotHeader/ChatbotHeaderNewChatButton.d.ts +18 -0
  72. package/dist/esm/ChatbotHeader/ChatbotHeaderNewChatButton.js +22 -0
  73. package/dist/esm/ChatbotHeader/ChatbotHeaderNewChatButton.test.d.ts +1 -0
  74. package/dist/esm/ChatbotHeader/ChatbotHeaderNewChatButton.test.js +20 -0
  75. package/dist/esm/ChatbotHeader/index.d.ts +1 -0
  76. package/dist/esm/ChatbotHeader/index.js +1 -0
  77. package/dist/esm/DeepThinking/DeepThinking.d.ts +18 -0
  78. package/dist/esm/DeepThinking/DeepThinking.js +14 -0
  79. package/dist/esm/DeepThinking/DeepThinking.test.d.ts +1 -0
  80. package/dist/esm/DeepThinking/DeepThinking.test.js +43 -0
  81. package/dist/esm/DeepThinking/index.d.ts +2 -0
  82. package/dist/esm/DeepThinking/index.js +2 -0
  83. package/dist/esm/FilePreview/FilePreview.d.ts +26 -0
  84. package/dist/esm/FilePreview/FilePreview.js +21 -0
  85. package/dist/esm/FilePreview/FilePreview.test.d.ts +1 -0
  86. package/dist/esm/FilePreview/FilePreview.test.js +92 -0
  87. package/dist/esm/FilePreview/index.d.ts +2 -0
  88. package/dist/esm/FilePreview/index.js +2 -0
  89. package/dist/esm/Message/CodeBlockMessage/CodeBlockMessage.js +5 -5
  90. package/dist/esm/Message/LinkMessage/LinkMessage.d.ts +2 -1
  91. package/dist/esm/Message/LinkMessage/LinkMessage.js +7 -3
  92. package/dist/esm/Message/ListMessage/ListItemMessage.d.ts +1 -1
  93. package/dist/esm/Message/ListMessage/ListItemMessage.js +16 -1
  94. package/dist/esm/Message/Message.d.ts +15 -0
  95. package/dist/esm/Message/Message.js +129 -32
  96. package/dist/esm/Message/Message.test.js +71 -0
  97. package/dist/esm/Message/SuperscriptMessage/SuperscriptMessage.d.ts +3 -0
  98. package/dist/esm/Message/SuperscriptMessage/SuperscriptMessage.js +3 -0
  99. package/dist/esm/Message/UserFeedback/UserFeedback.d.ts +15 -1
  100. package/dist/esm/Message/UserFeedback/UserFeedback.js +4 -4
  101. package/dist/esm/Message/UserFeedback/UserFeedback.test.js +45 -1
  102. package/dist/esm/MessageBar/MessageBar.js +19 -4
  103. package/dist/esm/MessageBox/JumpButton.d.ts +5 -0
  104. package/dist/esm/MessageBox/JumpButton.js +1 -1
  105. package/dist/esm/MessageBox/JumpButton.test.js +4 -4
  106. package/dist/esm/MessageBox/MessageBox.d.ts +9 -0
  107. package/dist/esm/MessageBox/MessageBox.js +2 -2
  108. package/dist/esm/MessageBox/MessageBox.test.js +2 -2
  109. package/dist/esm/SourcesCard/SourcesCard.d.ts +13 -1
  110. package/dist/esm/SourcesCard/SourcesCard.js +6 -6
  111. package/dist/esm/SourcesCard/SourcesCard.test.js +50 -1
  112. package/dist/esm/ToolResponse/ToolResponse.d.ts +30 -0
  113. package/dist/esm/ToolResponse/ToolResponse.js +14 -0
  114. package/dist/esm/ToolResponse/ToolResponse.test.d.ts +1 -0
  115. package/dist/esm/ToolResponse/ToolResponse.test.js +55 -0
  116. package/dist/esm/ToolResponse/index.d.ts +2 -0
  117. package/dist/esm/ToolResponse/index.js +2 -0
  118. package/dist/esm/index.d.ts +6 -0
  119. package/dist/esm/index.js +6 -0
  120. package/dist/tsconfig.tsbuildinfo +1 -1
  121. package/package.json +7 -6
  122. package/patternfly-docs/content/extensions/chatbot/examples/Messages/BotMessage.tsx +101 -3
  123. package/patternfly-docs/content/extensions/chatbot/examples/Messages/FilePreview.tsx +33 -0
  124. package/patternfly-docs/content/extensions/chatbot/examples/Messages/MessageWithDeepThinking.tsx +17 -0
  125. package/patternfly-docs/content/extensions/chatbot/examples/Messages/MessageWithFeedback.tsx +111 -85
  126. package/patternfly-docs/content/extensions/chatbot/examples/Messages/MessageWithSources.tsx +70 -0
  127. package/patternfly-docs/content/extensions/chatbot/examples/Messages/MessageWithToolResponse.tsx +135 -0
  128. package/patternfly-docs/content/extensions/chatbot/examples/Messages/Messages.md +28 -4
  129. package/patternfly-docs/content/extensions/chatbot/examples/Messages/UserMessage.tsx +107 -2
  130. package/patternfly-docs/content/extensions/chatbot/examples/Messages/UserMessageWithExtraContent.tsx +616 -3
  131. package/patternfly-docs/content/extensions/chatbot/examples/UI/ChatbotConversationEditing.tsx +202 -0
  132. package/patternfly-docs/content/extensions/chatbot/examples/UI/ChatbotHeaderBasic.tsx +17 -3
  133. package/patternfly-docs/content/extensions/chatbot/examples/UI/ChatbotHeaderDrawer.tsx +36 -5
  134. package/patternfly-docs/content/extensions/chatbot/examples/UI/ChatbotHeaderDrawerWithPin.tsx +12 -2
  135. package/patternfly-docs/content/extensions/chatbot/examples/UI/UI.md +22 -3
  136. package/patternfly-docs/content/extensions/chatbot/examples/demos/Chatbot.md +1 -1
  137. package/patternfly-docs/patternfly-docs.config.js +1 -1
  138. package/src/Chatbot/Chatbot.scss +9 -2
  139. package/src/Chatbot/Chatbot.tsx +18 -31
  140. package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.tsx +5 -1
  141. package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.scss +28 -10
  142. package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.test.tsx +132 -3
  143. package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.tsx +80 -33
  144. package/src/ChatbotHeader/ChatbotHeaderMenu.test.tsx +1 -1
  145. package/src/ChatbotHeader/ChatbotHeaderMenu.tsx +2 -2
  146. package/src/ChatbotHeader/ChatbotHeaderNewChatButton.test.tsx +25 -0
  147. package/src/ChatbotHeader/ChatbotHeaderNewChatButton.tsx +64 -0
  148. package/src/ChatbotHeader/index.ts +1 -0
  149. package/src/ChatbotModal/ChatbotModal.scss +1 -1
  150. package/src/DeepThinking/DeepThinking.scss +24 -0
  151. package/src/DeepThinking/DeepThinking.test.tsx +61 -0
  152. package/src/DeepThinking/DeepThinking.tsx +68 -0
  153. package/src/DeepThinking/index.ts +3 -0
  154. package/src/FileDetails/__snapshots__/FileDetails.test.tsx.snap +6 -9
  155. package/src/FileDetailsLabel/__snapshots__/FileDetailsLabel.test.tsx.snap +6 -9
  156. package/src/FilePreview/FilePreview.scss +22 -0
  157. package/src/FilePreview/FilePreview.test.tsx +112 -0
  158. package/src/FilePreview/FilePreview.tsx +58 -0
  159. package/src/FilePreview/index.ts +3 -0
  160. package/src/Message/CodeBlockMessage/CodeBlockMessage.scss +2 -1
  161. package/src/Message/CodeBlockMessage/CodeBlockMessage.tsx +6 -5
  162. package/src/Message/LinkMessage/LinkMessage.tsx +6 -2
  163. package/src/Message/ListMessage/ListItemMessage.tsx +5 -1
  164. package/src/Message/ListMessage/ListMessage.scss +17 -0
  165. package/src/Message/Message.scss +44 -0
  166. package/src/Message/Message.test.tsx +90 -0
  167. package/src/Message/Message.tsx +171 -46
  168. package/src/Message/SuperscriptMessage/SuperscriptMessage.scss +8 -0
  169. package/src/Message/SuperscriptMessage/SuperscriptMessage.tsx +13 -0
  170. package/src/Message/TextMessage/TextMessage.scss +46 -5
  171. package/src/Message/UserFeedback/UserFeedback.test.tsx +107 -0
  172. package/src/Message/UserFeedback/UserFeedback.tsx +41 -6
  173. package/src/MessageBar/MessageBar.tsx +23 -3
  174. package/src/MessageBox/JumpButton.test.tsx +4 -4
  175. package/src/MessageBox/JumpButton.tsx +20 -4
  176. package/src/MessageBox/MessageBox.test.tsx +2 -2
  177. package/src/MessageBox/MessageBox.tsx +22 -1
  178. package/src/SourcesCard/SourcesCard.scss +17 -0
  179. package/src/SourcesCard/SourcesCard.test.tsx +93 -0
  180. package/src/SourcesCard/SourcesCard.tsx +116 -80
  181. package/src/ToolResponse/ToolResponse.scss +36 -0
  182. package/src/ToolResponse/ToolResponse.test.tsx +78 -0
  183. package/src/ToolResponse/ToolResponse.tsx +95 -0
  184. package/src/ToolResponse/index.ts +3 -0
  185. package/src/index.ts +9 -0
  186. package/src/main.scss +3 -0
@@ -3,7 +3,7 @@
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 from 'react-markdown';
6
+ import Markdown, { Options } from 'react-markdown';
7
7
  import remarkGfm from 'remark-gfm';
8
8
  import {
9
9
  AlertProps,
@@ -49,6 +49,9 @@ import LinkMessage from './LinkMessage/LinkMessage';
49
49
  import ErrorMessage from './ErrorMessage/ErrorMessage';
50
50
  import MessageInput from './MessageInput';
51
51
  import { rehypeMoveImagesOutOfParagraphs } from './Plugins/rehypeMoveImagesOutOfParagraphs';
52
+ import ToolResponse, { ToolResponseProps } from '../ToolResponse';
53
+ import DeepThinking, { DeepThinkingProps } from '../DeepThinking';
54
+ import SuperscriptMessage from './SuperscriptMessage/SuperscriptMessage';
52
55
 
53
56
  export interface MessageAttachment {
54
57
  /** Name of file attached to the message */
@@ -161,6 +164,8 @@ export interface MessageProps extends Omit<HTMLProps<HTMLDivElement>, 'role'> {
161
164
  tableProps?: Required<Pick<TableProps, 'aria-label'>> & TableProps;
162
165
  /** Additional rehype plugins passed from the consumer */
163
166
  additionalRehypePlugins?: PluggableList;
167
+ /** Additional remark plugins passed from the consumer */
168
+ additionalRemarkPlugins?: PluggableList;
164
169
  /** Whether to open links in message in new tab. */
165
170
  openLinkInNewTab?: boolean;
166
171
  /** Optional inline error message that can be displayed in the message */
@@ -185,6 +190,16 @@ export interface MessageProps extends Omit<HTMLProps<HTMLDivElement>, 'role'> {
185
190
  editFormProps?: FormProps;
186
191
  /** Sets message to compact styling. */
187
192
  isCompact?: boolean;
193
+ /** Disables markdown parsing for message, allowing only text input */
194
+ isMarkdownDisabled?: boolean;
195
+ /** 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 */
196
+ reactMarkdownProps?: Options;
197
+ /** Props for tool response card */
198
+ toolResponse?: ToolResponseProps;
199
+ /** Props for deep thinking card */
200
+ deepThinking?: DeepThinkingProps;
201
+ /** Allows passing additional props down to remark-gfm. See https://github.com/remarkjs/remark-gfm?tab=readme-ov-file#options for options */
202
+ remarkGfmProps?: Options;
188
203
  }
189
204
 
190
205
  export const MessageBase: FunctionComponent<MessageProps> = ({
@@ -213,6 +228,7 @@ export const MessageBase: FunctionComponent<MessageProps> = ({
213
228
  tableProps,
214
229
  openLinkInNewTab = true,
215
230
  additionalRehypePlugins = [],
231
+ additionalRemarkPlugins = [],
216
232
  linkProps,
217
233
  error,
218
234
  isEditable,
@@ -224,6 +240,11 @@ export const MessageBase: FunctionComponent<MessageProps> = ({
224
240
  inputRef,
225
241
  editFormProps,
226
242
  isCompact,
243
+ isMarkdownDisabled,
244
+ reactMarkdownProps,
245
+ toolResponse,
246
+ deepThinking,
247
+ remarkGfmProps,
227
248
  ...props
228
249
  }: MessageProps) => {
229
250
  const [messageText, setMessageText] = useState(content);
@@ -250,6 +271,152 @@ export const MessageBase: FunctionComponent<MessageProps> = ({
250
271
  const date = new Date();
251
272
  const dateString = timestamp ?? `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`;
252
273
 
274
+ const handleMarkdown = () => {
275
+ if (isMarkdownDisabled) {
276
+ return (
277
+ <TextMessage component={ContentVariants.p} {...props}>
278
+ {messageText}
279
+ </TextMessage>
280
+ );
281
+ }
282
+ return (
283
+ <Markdown
284
+ components={{
285
+ section: (props) => {
286
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
287
+ const { node, ...rest } = props;
288
+ return <section {...rest} className={`pf-chatbot__message-text ${rest?.className}`} />;
289
+ },
290
+ p: (props) => {
291
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
292
+ const { node, ...rest } = props;
293
+ return <TextMessage component={ContentVariants.p} {...rest} />;
294
+ },
295
+ code: ({ children, ...props }) => {
296
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
297
+ const { node, ...codeProps } = props;
298
+ return (
299
+ <CodeBlockMessage {...codeProps} {...codeBlockProps}>
300
+ {children}
301
+ </CodeBlockMessage>
302
+ );
303
+ },
304
+ h1: (props) => {
305
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
306
+ const { node, ...rest } = props;
307
+ return <TextMessage component={ContentVariants.h1} {...rest} />;
308
+ },
309
+ h2: (props) => {
310
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
311
+ const { node, ...rest } = props;
312
+ return <TextMessage component={ContentVariants.h2} {...rest} />;
313
+ },
314
+ h3: (props) => {
315
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
316
+ const { node, ...rest } = props;
317
+ return <TextMessage component={ContentVariants.h3} {...rest} />;
318
+ },
319
+ h4: (props) => {
320
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
321
+ const { node, ...rest } = props;
322
+ return <TextMessage component={ContentVariants.h4} {...rest} />;
323
+ },
324
+ h5: (props) => {
325
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
326
+ const { node, ...rest } = props;
327
+ return <TextMessage component={ContentVariants.h5} {...rest} />;
328
+ },
329
+ h6: (props) => {
330
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
331
+ const { node, ...rest } = props;
332
+ return <TextMessage component={ContentVariants.h6} {...rest} />;
333
+ },
334
+ blockquote: (props) => {
335
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
336
+ const { node, ...rest } = props;
337
+ return <TextMessage component={ContentVariants.blockquote} {...rest} />;
338
+ },
339
+ ul: (props) => {
340
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
341
+ const { node, ...rest } = props;
342
+ return <UnorderedListMessage {...rest} />;
343
+ },
344
+ ol: (props) => {
345
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
346
+ const { node, ...rest } = props;
347
+ return <OrderedListMessage {...rest} />;
348
+ },
349
+ li: (props) => {
350
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
351
+ const { node, ...rest } = props;
352
+ return <ListItemMessage {...rest} />;
353
+ },
354
+ // table requires node attribute for calculating headers for mobile breakpoint
355
+ table: (props) => <TableMessage {...props} {...tableProps} />,
356
+ tbody: (props) => {
357
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
358
+ const { node, ...rest } = props;
359
+ return <TbodyMessage {...rest} />;
360
+ },
361
+ thead: (props) => {
362
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
363
+ const { node, ...rest } = props;
364
+ return <TheadMessage {...rest} />;
365
+ },
366
+ tr: (props) => {
367
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
368
+ const { node, ...rest } = props;
369
+ return <TrMessage {...rest} />;
370
+ },
371
+ td: (props) => {
372
+ // Conflicts with Td type
373
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
374
+ const { node, width, ...rest } = props;
375
+ return <TdMessage {...rest} />;
376
+ },
377
+ th: (props) => {
378
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
379
+ const { node, ...rest } = props;
380
+ return <ThMessage {...rest} />;
381
+ },
382
+ img: (props) => {
383
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
384
+ const { node, ...rest } = props;
385
+ return <ImageMessage {...rest} />;
386
+ },
387
+ a: (props) => {
388
+ // node is just the details of the document structure - not needed
389
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
390
+ const { node, ...rest } = props;
391
+ return (
392
+ // some a types conflict with ButtonProps, but it's ok because we are using an a tag
393
+ // there are too many to handle manually
394
+ <LinkMessage {...(rest as any)} {...linkProps}>
395
+ {props.children}
396
+ </LinkMessage>
397
+ );
398
+ },
399
+ // used for footnotes
400
+ sup: (props) => {
401
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
402
+ const { node, ...rest } = props;
403
+ return <SuperscriptMessage {...rest} />;
404
+ }
405
+ }}
406
+ remarkPlugins={[[remarkGfm, { ...remarkGfmProps }], ...additionalRemarkPlugins]}
407
+ rehypePlugins={rehypePlugins}
408
+ {...reactMarkdownProps}
409
+ remarkRehypeOptions={{
410
+ // removes sr-only class from footnote labels applied by default
411
+ footnoteLabelProperties: { className: [''] },
412
+ ...reactMarkdownProps?.remarkRehypeOptions
413
+ }}
414
+ >
415
+ {messageText}
416
+ </Markdown>
417
+ );
418
+ };
419
+
253
420
  const renderMessage = () => {
254
421
  if (isLoading) {
255
422
  return <MessageLoading loadingWord={loadingWord} />;
@@ -277,51 +444,7 @@ export const MessageBase: FunctionComponent<MessageProps> = ({
277
444
  return (
278
445
  <>
279
446
  {beforeMainContent && <>{beforeMainContent}</>}
280
- {error ? (
281
- <ErrorMessage {...error} />
282
- ) : (
283
- <Markdown
284
- components={{
285
- p: (props) => <TextMessage component={ContentVariants.p} {...props} />,
286
- code: ({ children, ...props }) => (
287
- <CodeBlockMessage {...props} {...codeBlockProps}>
288
- {children}
289
- </CodeBlockMessage>
290
- ),
291
- h1: (props) => <TextMessage component={ContentVariants.h1} {...props} />,
292
- h2: (props) => <TextMessage component={ContentVariants.h2} {...props} />,
293
- h3: (props) => <TextMessage component={ContentVariants.h3} {...props} />,
294
- h4: (props) => <TextMessage component={ContentVariants.h4} {...props} />,
295
- h5: (props) => <TextMessage component={ContentVariants.h5} {...props} />,
296
- h6: (props) => <TextMessage component={ContentVariants.h6} {...props} />,
297
- blockquote: (props) => <TextMessage component={ContentVariants.blockquote} {...props} />,
298
- ul: (props) => <UnorderedListMessage {...props} />,
299
- ol: (props) => <OrderedListMessage {...props} />,
300
- li: (props) => <ListItemMessage {...props} />,
301
- table: (props) => <TableMessage {...props} {...tableProps} />,
302
- tbody: (props) => <TbodyMessage {...props} />,
303
- thead: (props) => <TheadMessage {...props} />,
304
- tr: (props) => <TrMessage {...props} />,
305
- td: (props) => {
306
- // Conflicts with Td type
307
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
308
- const { width, ...rest } = props;
309
- return <TdMessage {...rest} />;
310
- },
311
- th: (props) => <ThMessage {...props} />,
312
- img: (props) => <ImageMessage {...props} />,
313
- a: (props) => (
314
- <LinkMessage href={props.href} rel={props.rel} target={props.target} {...linkProps}>
315
- {props.children}
316
- </LinkMessage>
317
- )
318
- }}
319
- remarkPlugins={[remarkGfm]}
320
- rehypePlugins={rehypePlugins}
321
- >
322
- {messageText}
323
- </Markdown>
324
- )}
447
+ {error ? <ErrorMessage {...error} /> : handleMarkdown()}
325
448
  </>
326
449
  );
327
450
  };
@@ -360,6 +483,8 @@ export const MessageBase: FunctionComponent<MessageProps> = ({
360
483
  <div className="pf-chatbot__message-and-actions">
361
484
  {renderMessage()}
362
485
  {afterMainContent && <>{afterMainContent}</>}
486
+ {toolResponse && <ToolResponse {...toolResponse} />}
487
+ {deepThinking && <DeepThinking {...deepThinking} />}
363
488
  {!isLoading && sources && <SourcesCard {...sources} isCompact={isCompact} />}
364
489
  {quickStarts && quickStarts.quickStart && (
365
490
  <QuickStartTile
@@ -0,0 +1,8 @@
1
+ .pf-chatbot__message-superscript {
2
+ font-size: smaller;
3
+ vertical-align: super;
4
+ .pf-v6-c-button.pf-m-link.pf-m-inline {
5
+ font-size: inherit;
6
+ vertical-align: inherit;
7
+ }
8
+ }
@@ -0,0 +1,13 @@
1
+ // ============================================================================
2
+ // Chatbot Main - Message - Content - Superscript (like for footnotes)
3
+ // ============================================================================
4
+
5
+ import { ExtraProps } from 'react-markdown';
6
+
7
+ const SuperscriptMessage = ({ children }: JSX.IntrinsicElements['sup'] & ExtraProps) => (
8
+ <span className="pf-chatbot__message-superscript">
9
+ <sup>{children}</sup>
10
+ </span>
11
+ );
12
+
13
+ export default SuperscriptMessage;
@@ -17,9 +17,10 @@
17
17
  width: fit-content;
18
18
  padding: var(--pf-t--global--spacer--sm) 0 var(--pf-t--global--spacer--sm) 0;
19
19
  border-radius: var(--pf-t--global--border--radius--small);
20
+ --pf-chatbot-message-text-font-size: var(--pf-t--global--font--size--md);
20
21
 
21
22
  .pf-v6-c-button.pf-m-link {
22
- font-size: var(--pf-t--global--font--size--md);
23
+ font-size: var(--pf-chatbot-message-text-font-size);
23
24
  }
24
25
 
25
26
  .pf-v6-c-content,
@@ -27,15 +28,49 @@
27
28
  .pf-v6-c-content--blockquote,
28
29
  p,
29
30
  a {
30
- --pf-v6-c-content--FontSize: var(--pf-t--global--font--size--md);
31
+ --pf-v6-c-content--FontSize: var(--pf-chatbot-message-text-font-size);
31
32
  }
32
33
 
33
34
  code {
34
35
  background-color: var(--pf-t--global--background--color--tertiary--default);
35
- font-size: var(--pf-t--global--font--size--body--default);
36
+ font-size: var(--pf-chatbot-message-text-inline-code-font-size);
37
+ }
38
+
39
+ // Hide message text that contains sr-only content
40
+ // https://css-tricks.com/inclusively-hidden/
41
+ &:has(.sr-only) {
42
+ clip: rect(0 0 0 0);
43
+ clip-path: inset(50%);
44
+ height: 1px;
45
+ overflow: hidden;
46
+ position: absolute;
47
+ white-space: nowrap;
48
+ width: 1px;
36
49
  }
37
50
  }
38
51
 
52
+ // ============================================================================
53
+ // Footnote spacing styles
54
+ // ============================================================================
55
+
56
+ // Add spacing to paragraphs in multi-paragraph footnotes
57
+ // Only target p tags that are direct children of message-text spans (not inside blockquotes, etc.)
58
+ li[id*='user-content-fn-']:has(> span > .pf-chatbot__message-text + .pf-chatbot__message-text)
59
+ > span
60
+ > .pf-chatbot__message-text
61
+ > p {
62
+ margin-block-end: var(--pf-t--global--spacer--md);
63
+ }
64
+
65
+ // Handle user message footnotes which may have extra span wrappers
66
+ li[id*='user-content-fn-']:has(> span > span > .pf-chatbot__message-text + .pf-chatbot__message-text)
67
+ > span
68
+ > span
69
+ > .pf-chatbot__message-text
70
+ > p {
71
+ margin-block-end: var(--pf-t--global--spacer--md);
72
+ }
73
+
39
74
  .pf-chatbot__message--user {
40
75
  .pf-chatbot__message-text {
41
76
  background-color: var(--pf-t--global--color--brand--default);
@@ -54,6 +89,11 @@
54
89
  color: var(--pf-t--global--text--color--on-brand--default);
55
90
  }
56
91
  }
92
+
93
+ .pf-chatbot__message-text > .pf-chatbot__message-text {
94
+ background-color: initial;
95
+ padding: initial;
96
+ }
57
97
  }
58
98
 
59
99
  // ============================================================================
@@ -62,8 +102,9 @@
62
102
  .pf-chatbot.pf-m-compact {
63
103
  // Need to inline shorter text
64
104
  .pf-chatbot__message-text {
105
+ --pf-chatbot-message-text-font-size: var(--pf-t--global--font--size--sm);
65
106
  .pf-v6-c-button.pf-m-link {
66
- font-size: var(--pf-t--global--font--size--sm);
107
+ font-size: var(--pf-chatbot-message-text-font-size);
67
108
  }
68
109
 
69
110
  .pf-v6-c-content,
@@ -71,7 +112,7 @@
71
112
  .pf-v6-c-content--blockquote,
72
113
  p,
73
114
  a {
74
- --pf-v6-c-content--FontSize: var(--pf-t--global--font--size--sm);
115
+ --pf-v6-c-content--FontSize: var(--pf-chatbot-message-text-font-size);
75
116
  }
76
117
 
77
118
  .pf-v6-c-content--blockquote {
@@ -245,4 +245,111 @@ describe('UserFeedback', () => {
245
245
  );
246
246
  expect(screen.getByTestId('card')).toHaveClass('pf-m-compact');
247
247
  });
248
+ it('should pass buttonProps to submit button', () => {
249
+ render(
250
+ <UserFeedback
251
+ timestamp="12/12/12"
252
+ onClose={jest.fn}
253
+ onSubmit={jest.fn}
254
+ quickResponses={MOCK_RESPONSES}
255
+ submitButtonProps={{ variant: 'secondary', isDisabled: true }}
256
+ />
257
+ );
258
+ const submitButton = screen.getByRole('button', { name: /Submit/i });
259
+ expect(submitButton).toHaveClass('pf-v6-c-button pf-m-secondary');
260
+ expect(submitButton).toBeDisabled();
261
+ });
262
+ it('should pass cardHeaderProps to card header', () => {
263
+ render(
264
+ <UserFeedback
265
+ timestamp="12/12/12"
266
+ onClose={jest.fn}
267
+ onSubmit={jest.fn}
268
+ quickResponses={MOCK_RESPONSES}
269
+ cardHeaderProps={{ 'data-testid': 'card-header', className: 'custom-header' } as any}
270
+ />
271
+ );
272
+ const cardHeader = screen.getByTestId('card-header');
273
+ expect(cardHeader).toHaveClass('custom-header');
274
+ });
275
+ it('should pass cardBodyProps to card body', () => {
276
+ render(
277
+ <UserFeedback
278
+ timestamp="12/12/12"
279
+ onClose={jest.fn}
280
+ onSubmit={jest.fn}
281
+ quickResponses={MOCK_RESPONSES}
282
+ cardBodyProps={{ 'data-testid': 'card-body', className: 'custom-body' } as any}
283
+ />
284
+ );
285
+ const cardBody = screen.getByTestId('card-body');
286
+ expect(cardBody).toHaveClass('custom-body');
287
+ });
288
+ it('should pass headingLevelProps to title heading', () => {
289
+ render(
290
+ <UserFeedback
291
+ timestamp="12/12/12"
292
+ onClose={jest.fn}
293
+ onSubmit={jest.fn}
294
+ quickResponses={MOCK_RESPONSES}
295
+ headingLevelProps={{ className: 'custom-heading', id: 'feedback-title' }}
296
+ />
297
+ );
298
+ const heading = screen.getByRole('heading', { level: 1, name: /Why did you choose this rating?/i });
299
+ expect(heading).toHaveClass('custom-heading');
300
+ expect(heading).toHaveAttribute('id', 'feedback-title');
301
+ });
302
+
303
+ it('should pass formProps to form', () => {
304
+ render(
305
+ <UserFeedback
306
+ timestamp="12/12/12"
307
+ onClose={jest.fn}
308
+ onSubmit={jest.fn}
309
+ quickResponses={MOCK_RESPONSES}
310
+ formProps={{ 'data-testid': 'feedback-form', className: 'custom-form' } as any}
311
+ />
312
+ );
313
+ const form = screen.getByTestId('feedback-form');
314
+ expect(form).toHaveClass('custom-form');
315
+ });
316
+ it('should pass textAreaProps to text area when hasTextArea is true', () => {
317
+ render(
318
+ <UserFeedback
319
+ timestamp="12/12/12"
320
+ onClose={jest.fn}
321
+ onSubmit={jest.fn}
322
+ quickResponses={MOCK_RESPONSES}
323
+ hasTextArea
324
+ textAreaProps={{ 'data-testid': 'custom-textarea', rows: 5 } as any}
325
+ />
326
+ );
327
+ const textArea = screen.getByTestId('custom-textarea');
328
+ expect(textArea).toHaveAttribute('rows', '5');
329
+ expect(textArea).toHaveAttribute('data-testid', 'custom-textarea');
330
+ });
331
+ it('should pass actionGroupProps to action group', () => {
332
+ render(
333
+ <UserFeedback
334
+ timestamp="12/12/12"
335
+ onClose={jest.fn}
336
+ onSubmit={jest.fn}
337
+ quickResponses={MOCK_RESPONSES}
338
+ actionGroupProps={{ 'data-testid': 'action-group', className: 'custom-actions' } as any}
339
+ />
340
+ );
341
+ const actionGroup = screen.getByTestId('action-group');
342
+ expect(actionGroup).toHaveClass('custom-actions');
343
+ });
344
+ it('should render children', () => {
345
+ render(
346
+ <UserFeedback timestamp="12/12/12" onClose={jest.fn} onSubmit={jest.fn} quickResponses={MOCK_RESPONSES}>
347
+ <div data-testid="custom-content">Custom feedback content</div>
348
+ <p>Additional paragraph</p>
349
+ </UserFeedback>
350
+ );
351
+ expect(screen.getByTestId('custom-content')).toBeInTheDocument();
352
+ expect(screen.getByText('Custom feedback content')).toBeInTheDocument();
353
+ expect(screen.getByText('Additional paragraph')).toBeInTheDocument();
354
+ });
248
355
  });
@@ -8,15 +8,21 @@ import { useState, useRef, useEffect } from 'react';
8
8
  // Import PatternFly components
9
9
  import {
10
10
  ActionGroup,
11
+ ActionGroupProps,
11
12
  Button,
13
+ ButtonProps,
12
14
  Card,
13
15
  CardBody,
16
+ CardBodyProps,
14
17
  CardHeader,
18
+ CardHeaderProps,
15
19
  CardProps,
16
20
  Form,
21
+ FormProps,
17
22
  LabelGroupProps,
18
23
  OUIAProps,
19
- TextArea
24
+ TextArea,
25
+ TextAreaProps
20
26
  } from '@patternfly/react-core';
21
27
  import QuickResponse from '../QuickResponse/QuickResponse';
22
28
  import CloseButton from './CloseButton';
@@ -54,6 +60,20 @@ export interface UserFeedbackProps extends Omit<CardProps, 'onSubmit'>, OUIAProp
54
60
  focusOnLoad?: boolean;
55
61
  /** Timestamp passed in by Message for more context in aria announcements */
56
62
  timestamp?: string;
63
+ /** Additional props passed to submit button */
64
+ submitButtonProps?: ButtonProps;
65
+ /** Additional props passed to card header */
66
+ cardHeaderProps?: CardHeaderProps;
67
+ /** Additional props passed to card body */
68
+ cardBodyProps?: CardBodyProps;
69
+ /** Additional props passed to title heading */
70
+ headingLevelProps?: React.HTMLAttributes<HTMLHeadingElement>;
71
+ /** Additional props passed to form */
72
+ formProps?: FormProps;
73
+ /** Additional props passed to text area */
74
+ textAreaProps?: TextAreaProps;
75
+ /** Additional props passed to action group */
76
+ actionGroupProps?: ActionGroupProps;
57
77
  }
58
78
 
59
79
  const UserFeedback: FunctionComponent<UserFeedbackProps> = ({
@@ -74,6 +94,14 @@ const UserFeedback: FunctionComponent<UserFeedbackProps> = ({
74
94
  headingLevel: HeadingLevel = 'h1',
75
95
  focusOnLoad = true,
76
96
  isCompact,
97
+ children,
98
+ cardHeaderProps,
99
+ cardBodyProps,
100
+ headingLevelProps,
101
+ formProps,
102
+ textAreaProps,
103
+ actionGroupProps,
104
+ submitButtonProps,
77
105
  ...props
78
106
  }: UserFeedbackProps) => {
79
107
  const [selectedResponse, setSelectedResponse] = useState<string>();
@@ -94,11 +122,14 @@ const UserFeedback: FunctionComponent<UserFeedbackProps> = ({
94
122
  actions={{
95
123
  actions: <CloseButton onClose={onClose} ariaLabel={closeButtonAriaLabel} />
96
124
  }}
125
+ {...cardHeaderProps}
97
126
  >
98
- <HeadingLevel className="pf-chatbot__feedback-card-title">{title}</HeadingLevel>
127
+ <HeadingLevel className="pf-chatbot__feedback-card-title" {...headingLevelProps}>
128
+ {title}
129
+ </HeadingLevel>
99
130
  </CardHeader>
100
- <CardBody>
101
- <Form className={`pf-chatbot__feedback-card-form ${isCompact ? 'pf-m-compact' : ''}`}>
131
+ <CardBody {...cardBodyProps}>
132
+ <Form className={`pf-chatbot__feedback-card-form ${isCompact ? 'pf-m-compact' : ''}`} {...formProps}>
102
133
  {quickResponses && (
103
134
  <QuickResponse
104
135
  quickResponses={quickResponses}
@@ -117,10 +148,14 @@ const UserFeedback: FunctionComponent<UserFeedbackProps> = ({
117
148
  placeholder={textAreaPlaceholder}
118
149
  aria-label={textAreaAriaLabel}
119
150
  resizeOrientation="vertical"
151
+ {...textAreaProps}
120
152
  />
121
153
  )}
122
- <ActionGroup>
123
- <Button onClick={() => onSubmit(selectedResponse, value)}>{submitWord}</Button>
154
+ {children}
155
+ <ActionGroup {...actionGroupProps}>
156
+ <Button onClick={() => onSubmit(selectedResponse, value)} {...submitButtonProps}>
157
+ {submitWord}
158
+ </Button>
124
159
  </ActionGroup>
125
160
  </Form>
126
161
  </CardBody>
@@ -141,6 +141,7 @@ export const MessageBarBase: FunctionComponent<MessageBarProps> = ({
141
141
  const [message, setMessage] = useState<string | number>(value ?? '');
142
142
  const [isListeningMessage, setIsListeningMessage] = useState<boolean>(false);
143
143
  const [hasSentMessage, setHasSentMessage] = useState(false);
144
+ const [isComposing, setIsComposing] = useState(false);
144
145
  const inputRef = useRef<HTMLTextAreaElement>(null);
145
146
  const textareaRef = (innerRef as React.RefObject<HTMLTextAreaElement>) ?? inputRef;
146
147
  const attachButtonRef = useRef<HTMLButtonElement>(null);
@@ -285,21 +286,38 @@ export const MessageBarBase: FunctionComponent<MessageBarProps> = ({
285
286
 
286
287
  const handleKeyDown = useCallback(
287
288
  (event: ReactKeyboardEvent) => {
288
- if (event.key === 'Enter' && !event.shiftKey) {
289
+ // Japanese and other languages may use IME for character input.
290
+ // In these cases, enter is used to select the final input, so we need to check for composition end instead.
291
+ // See more info at https://www.stum.de/2016/06/24/handling-ime-events-in-javascript/
292
+ // Chrome, Edge, and Firefox seem to work well with just the compose event.
293
+ // Safari is a little bit special. We need to handle 229 as well in this case.
294
+ const nativeEvent = event.nativeEvent as KeyboardEvent;
295
+ const isCompositionKey = nativeEvent.which === 229;
296
+ const isCurrentlyComposing = isComposing || isCompositionKey;
297
+
298
+ if (event.key === 'Enter' && !isCurrentlyComposing && !event.shiftKey) {
289
299
  event.preventDefault();
290
300
  if (!isSendButtonDisabled && !hasStopButton) {
291
301
  handleSend(message);
292
302
  }
293
303
  }
294
- if (event.key === 'Enter' && event.shiftKey) {
304
+ if (event.key === 'Enter' && !isCurrentlyComposing && event.shiftKey) {
295
305
  if (textareaRef.current) {
296
306
  handleNewLine(textareaRef.current);
297
307
  }
298
308
  }
299
309
  },
300
- [isSendButtonDisabled, hasStopButton, handleSend, message]
310
+ [isSendButtonDisabled, hasStopButton, handleSend, message, isComposing]
301
311
  );
302
312
 
313
+ const handleCompositionStart = useCallback(() => {
314
+ setIsComposing(true);
315
+ }, []);
316
+
317
+ const handleCompositionEnd = useCallback(() => {
318
+ setIsComposing(false);
319
+ }, []);
320
+
303
321
  const handleAttachMenuToggle = () => {
304
322
  attachMenuProps?.setIsAttachMenuOpen && attachMenuProps?.setIsAttachMenuOpen(!attachMenuProps?.isAttachMenuOpen);
305
323
  attachMenuProps?.onAttachMenuToggleClick();
@@ -402,6 +420,8 @@ export const MessageBarBase: FunctionComponent<MessageBarProps> = ({
402
420
  placeholder={isListeningMessage ? listeningText : placeholder}
403
421
  ref={textareaRef}
404
422
  onKeyDown={handleKeyDown}
423
+ onCompositionStart={handleCompositionStart}
424
+ onCompositionEnd={handleCompositionEnd}
405
425
  {...props}
406
426
  />
407
427
  </div>