@patternfly/chatbot 6.4.0-prerelease.24 → 6.4.0-prerelease.25

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.
@@ -158,6 +158,8 @@ export interface MessageProps extends Omit<HTMLProps<HTMLDivElement>, 'role'> {
158
158
  remarkGfmProps?: Options;
159
159
  /** Props for a tool call message */
160
160
  toolCall?: ToolCallProps;
161
+ /** Whether user messages default to stripping out images in markdown */
162
+ hasNoImagesInUserMessages?: boolean;
161
163
  }
162
164
  export declare const MessageBase: FunctionComponent<MessageProps>;
163
165
  declare const Message: import("react").ForwardRefExoticComponent<Omit<MessageProps, "ref"> & import("react").RefAttributes<HTMLDivElement>>;
@@ -55,7 +55,7 @@ const DeepThinking_1 = __importDefault(require("../DeepThinking"));
55
55
  const SuperscriptMessage_1 = __importDefault(require("./SuperscriptMessage/SuperscriptMessage"));
56
56
  const ToolCall_1 = __importDefault(require("../ToolCall"));
57
57
  const MessageBase = (_a) => {
58
- var { role, content, extraContent, name, avatar, timestamp, isLoading, actions, sources, botWord = 'AI', loadingWord = 'Loading message', codeBlockProps, quickResponses, quickResponseContainerProps = { numLabels: 5 }, attachments, hasRoundAvatar = true, avatarProps, quickStarts, userFeedbackForm, userFeedbackComplete, isLiveRegion = true, innerRef, tableProps, openLinkInNewTab = true, additionalRehypePlugins = [], additionalRemarkPlugins = [], linkProps, error, isEditable, editPlaceholder = 'Edit prompt message...', updateWord = 'Update', cancelWord = 'Cancel', onEditUpdate, onEditCancel, inputRef, editFormProps, isCompact, isMarkdownDisabled, reactMarkdownProps, toolResponse, deepThinking, remarkGfmProps, toolCall } = _a, props = __rest(_a, ["role", "content", "extraContent", "name", "avatar", "timestamp", "isLoading", "actions", "sources", "botWord", "loadingWord", "codeBlockProps", "quickResponses", "quickResponseContainerProps", "attachments", "hasRoundAvatar", "avatarProps", "quickStarts", "userFeedbackForm", "userFeedbackComplete", "isLiveRegion", "innerRef", "tableProps", "openLinkInNewTab", "additionalRehypePlugins", "additionalRemarkPlugins", "linkProps", "error", "isEditable", "editPlaceholder", "updateWord", "cancelWord", "onEditUpdate", "onEditCancel", "inputRef", "editFormProps", "isCompact", "isMarkdownDisabled", "reactMarkdownProps", "toolResponse", "deepThinking", "remarkGfmProps", "toolCall"]);
58
+ var { role, content, extraContent, name, avatar, timestamp, isLoading, actions, sources, botWord = 'AI', loadingWord = 'Loading message', codeBlockProps, quickResponses, quickResponseContainerProps = { numLabels: 5 }, attachments, hasRoundAvatar = true, avatarProps, quickStarts, userFeedbackForm, userFeedbackComplete, isLiveRegion = true, innerRef, tableProps, openLinkInNewTab = true, additionalRehypePlugins = [], additionalRemarkPlugins = [], linkProps, error, isEditable, editPlaceholder = 'Edit prompt message...', updateWord = 'Update', cancelWord = 'Cancel', onEditUpdate, onEditCancel, inputRef, editFormProps, isCompact, isMarkdownDisabled, reactMarkdownProps, toolResponse, deepThinking, remarkGfmProps, toolCall, hasNoImagesInUserMessages = true } = _a, props = __rest(_a, ["role", "content", "extraContent", "name", "avatar", "timestamp", "isLoading", "actions", "sources", "botWord", "loadingWord", "codeBlockProps", "quickResponses", "quickResponseContainerProps", "attachments", "hasRoundAvatar", "avatarProps", "quickStarts", "userFeedbackForm", "userFeedbackComplete", "isLiveRegion", "innerRef", "tableProps", "openLinkInNewTab", "additionalRehypePlugins", "additionalRemarkPlugins", "linkProps", "error", "isEditable", "editPlaceholder", "updateWord", "cancelWord", "onEditUpdate", "onEditCancel", "inputRef", "editFormProps", "isCompact", "isMarkdownDisabled", "reactMarkdownProps", "toolResponse", "deepThinking", "remarkGfmProps", "toolCall", "hasNoImagesInUserMessages"]);
59
59
  const [messageText, setMessageText] = (0, react_1.useState)(content);
60
60
  (0, react_1.useEffect)(() => {
61
61
  setMessageText(content);
@@ -77,6 +77,10 @@ const MessageBase = (_a) => {
77
77
  // Keep timestamps consistent between Timestamp component and aria-label
78
78
  const date = new Date();
79
79
  const dateString = timestamp !== null && timestamp !== void 0 ? timestamp : `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`;
80
+ const disallowedElements = role === 'user' && hasNoImagesInUserMessages ? ['img'] : [];
81
+ if (reactMarkdownProps && reactMarkdownProps.disallowedElements) {
82
+ disallowedElements.push(...reactMarkdownProps.disallowedElements);
83
+ }
80
84
  const handleMarkdown = () => {
81
85
  if (isMarkdownDisabled) {
82
86
  return ((0, jsx_runtime_1.jsx)(TextMessage_1.default, Object.assign({ component: react_core_1.ContentVariants.p }, props, { children: messageText })));
@@ -198,7 +202,7 @@ const MessageBase = (_a) => {
198
202
  }
199
203
  }, remarkPlugins: [[remark_gfm_1.default, Object.assign({}, remarkGfmProps)], ...additionalRemarkPlugins], rehypePlugins: rehypePlugins }, reactMarkdownProps, { remarkRehypeOptions: Object.assign({
200
204
  // removes sr-only class from footnote labels applied by default
201
- footnoteLabelProperties: { className: [''] } }, reactMarkdownProps === null || reactMarkdownProps === void 0 ? void 0 : reactMarkdownProps.remarkRehypeOptions), children: messageText })));
205
+ footnoteLabelProperties: { className: [''] } }, reactMarkdownProps === null || reactMarkdownProps === void 0 ? void 0 : reactMarkdownProps.remarkRehypeOptions), disallowedElements: disallowedElements, children: messageText })));
202
206
  };
203
207
  const renderMessage = () => {
204
208
  if (isLoading) {
@@ -705,12 +705,16 @@ describe('Message', () => {
705
705
  expect(react_2.screen.getByTestId('after-main-content')).toContainHTML('<strong>Bold after content</strong>');
706
706
  expect(react_2.screen.getByTestId('end-main-content')).toContainHTML('<strong>Bold end content</strong>');
707
707
  });
708
- it('should handle image correctly', () => {
708
+ it('should handle image correctly for user', () => {
709
709
  (0, react_2.render)((0, jsx_runtime_1.jsx)(Message_1.default, { avatar: "./img", role: "user", name: "User", content: IMAGE }));
710
+ expect(react_2.screen.queryByRole('img', { name: /Multi-colored wavy lines on a black background/i })).toBeFalsy();
711
+ });
712
+ it('should handle image correctly for bot', () => {
713
+ (0, react_2.render)((0, jsx_runtime_1.jsx)(Message_1.default, { avatar: "./img", role: "bot", name: "Bot", content: IMAGE }));
710
714
  expect(react_2.screen.getByRole('img', { name: /Multi-colored wavy lines on a black background/i })).toBeTruthy();
711
715
  });
712
716
  it('inline image parent should have class pf-chatbot__message-and-actions', () => {
713
- (0, react_2.render)((0, jsx_runtime_1.jsx)(Message_1.default, { avatar: "./img", role: "user", name: "User", content: INLINE_IMAGE }));
717
+ (0, react_2.render)((0, jsx_runtime_1.jsx)(Message_1.default, { avatar: "./img", role: "bot", name: "Bot", content: INLINE_IMAGE }));
714
718
  expect(react_2.screen.getByRole('img', { name: /Multi-colored wavy lines on a black background/i })).toBeTruthy();
715
719
  expect(react_2.screen.getByRole('img', { name: /Multi-colored wavy lines on a black background/i }).parentElement).toHaveClass('pf-chatbot__message-and-actions');
716
720
  });
@@ -804,6 +808,17 @@ describe('Message', () => {
804
808
  // code block isn't rendering
805
809
  expect(react_2.screen.queryByRole('button', { name: 'Copy code' })).toBeFalsy();
806
810
  });
811
+ it('should disable images and additional tags for user messages', () => {
812
+ (0, react_2.render)((0, jsx_runtime_1.jsx)(Message_1.default, { avatar: "./img", role: "user", name: "User", content: `${IMAGE} ${CODE_MESSAGE}`, reactMarkdownProps: { disallowedElements: ['code'] } }));
813
+ expect(react_2.screen.getByText('Here is some YAML code:')).toBeTruthy();
814
+ // code block isn't rendering
815
+ expect(react_2.screen.queryByRole('button', { name: 'Copy code' })).toBeFalsy();
816
+ expect(react_2.screen.queryByRole('img', { name: /Multi-colored wavy lines on a black background/i })).toBeFalsy();
817
+ });
818
+ it('can override image tag removal default for user messages', () => {
819
+ (0, react_2.render)((0, jsx_runtime_1.jsx)(Message_1.default, { avatar: "./img", role: "user", name: "User", content: IMAGE, hasNoImagesInUserMessages: false }));
820
+ expect(react_2.screen.getByRole('img', { name: /Multi-colored wavy lines on a black background/i })).toBeTruthy();
821
+ });
807
822
  it('should render deep thinking section correctly', () => {
808
823
  (0, react_2.render)((0, jsx_runtime_1.jsx)(Message_1.default, { avatar: "./img", role: "user", name: "User", content: "", deepThinking: DEEP_THINKING }));
809
824
  expect(react_2.screen.getByRole('button', { name: /Show thinking/i })).toBeTruthy();
@@ -158,6 +158,8 @@ export interface MessageProps extends Omit<HTMLProps<HTMLDivElement>, 'role'> {
158
158
  remarkGfmProps?: Options;
159
159
  /** Props for a tool call message */
160
160
  toolCall?: ToolCallProps;
161
+ /** Whether user messages default to stripping out images in markdown */
162
+ hasNoImagesInUserMessages?: boolean;
161
163
  }
162
164
  export declare const MessageBase: FunctionComponent<MessageProps>;
163
165
  declare const Message: import("react").ForwardRefExoticComponent<Omit<MessageProps, "ref"> & import("react").RefAttributes<HTMLDivElement>>;
@@ -49,7 +49,7 @@ import DeepThinking from '../DeepThinking';
49
49
  import SuperscriptMessage from './SuperscriptMessage/SuperscriptMessage';
50
50
  import ToolCall from '../ToolCall';
51
51
  export const MessageBase = (_a) => {
52
- var { role, content, extraContent, name, avatar, timestamp, isLoading, actions, sources, botWord = 'AI', loadingWord = 'Loading message', codeBlockProps, quickResponses, quickResponseContainerProps = { numLabels: 5 }, attachments, hasRoundAvatar = true, avatarProps, quickStarts, userFeedbackForm, userFeedbackComplete, isLiveRegion = true, innerRef, tableProps, openLinkInNewTab = true, additionalRehypePlugins = [], additionalRemarkPlugins = [], linkProps, error, isEditable, editPlaceholder = 'Edit prompt message...', updateWord = 'Update', cancelWord = 'Cancel', onEditUpdate, onEditCancel, inputRef, editFormProps, isCompact, isMarkdownDisabled, reactMarkdownProps, toolResponse, deepThinking, remarkGfmProps, toolCall } = _a, props = __rest(_a, ["role", "content", "extraContent", "name", "avatar", "timestamp", "isLoading", "actions", "sources", "botWord", "loadingWord", "codeBlockProps", "quickResponses", "quickResponseContainerProps", "attachments", "hasRoundAvatar", "avatarProps", "quickStarts", "userFeedbackForm", "userFeedbackComplete", "isLiveRegion", "innerRef", "tableProps", "openLinkInNewTab", "additionalRehypePlugins", "additionalRemarkPlugins", "linkProps", "error", "isEditable", "editPlaceholder", "updateWord", "cancelWord", "onEditUpdate", "onEditCancel", "inputRef", "editFormProps", "isCompact", "isMarkdownDisabled", "reactMarkdownProps", "toolResponse", "deepThinking", "remarkGfmProps", "toolCall"]);
52
+ var { role, content, extraContent, name, avatar, timestamp, isLoading, actions, sources, botWord = 'AI', loadingWord = 'Loading message', codeBlockProps, quickResponses, quickResponseContainerProps = { numLabels: 5 }, attachments, hasRoundAvatar = true, avatarProps, quickStarts, userFeedbackForm, userFeedbackComplete, isLiveRegion = true, innerRef, tableProps, openLinkInNewTab = true, additionalRehypePlugins = [], additionalRemarkPlugins = [], linkProps, error, isEditable, editPlaceholder = 'Edit prompt message...', updateWord = 'Update', cancelWord = 'Cancel', onEditUpdate, onEditCancel, inputRef, editFormProps, isCompact, isMarkdownDisabled, reactMarkdownProps, toolResponse, deepThinking, remarkGfmProps, toolCall, hasNoImagesInUserMessages = true } = _a, props = __rest(_a, ["role", "content", "extraContent", "name", "avatar", "timestamp", "isLoading", "actions", "sources", "botWord", "loadingWord", "codeBlockProps", "quickResponses", "quickResponseContainerProps", "attachments", "hasRoundAvatar", "avatarProps", "quickStarts", "userFeedbackForm", "userFeedbackComplete", "isLiveRegion", "innerRef", "tableProps", "openLinkInNewTab", "additionalRehypePlugins", "additionalRemarkPlugins", "linkProps", "error", "isEditable", "editPlaceholder", "updateWord", "cancelWord", "onEditUpdate", "onEditCancel", "inputRef", "editFormProps", "isCompact", "isMarkdownDisabled", "reactMarkdownProps", "toolResponse", "deepThinking", "remarkGfmProps", "toolCall", "hasNoImagesInUserMessages"]);
53
53
  const [messageText, setMessageText] = useState(content);
54
54
  useEffect(() => {
55
55
  setMessageText(content);
@@ -71,6 +71,10 @@ export const MessageBase = (_a) => {
71
71
  // Keep timestamps consistent between Timestamp component and aria-label
72
72
  const date = new Date();
73
73
  const dateString = timestamp !== null && timestamp !== void 0 ? timestamp : `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`;
74
+ const disallowedElements = role === 'user' && hasNoImagesInUserMessages ? ['img'] : [];
75
+ if (reactMarkdownProps && reactMarkdownProps.disallowedElements) {
76
+ disallowedElements.push(...reactMarkdownProps.disallowedElements);
77
+ }
74
78
  const handleMarkdown = () => {
75
79
  if (isMarkdownDisabled) {
76
80
  return (_jsx(TextMessage, Object.assign({ component: ContentVariants.p }, props, { children: messageText })));
@@ -192,7 +196,7 @@ export const MessageBase = (_a) => {
192
196
  }
193
197
  }, remarkPlugins: [[remarkGfm, Object.assign({}, remarkGfmProps)], ...additionalRemarkPlugins], rehypePlugins: rehypePlugins }, reactMarkdownProps, { remarkRehypeOptions: Object.assign({
194
198
  // removes sr-only class from footnote labels applied by default
195
- footnoteLabelProperties: { className: [''] } }, reactMarkdownProps === null || reactMarkdownProps === void 0 ? void 0 : reactMarkdownProps.remarkRehypeOptions), children: messageText })));
199
+ footnoteLabelProperties: { className: [''] } }, reactMarkdownProps === null || reactMarkdownProps === void 0 ? void 0 : reactMarkdownProps.remarkRehypeOptions), disallowedElements: disallowedElements, children: messageText })));
196
200
  };
197
201
  const renderMessage = () => {
198
202
  if (isLoading) {
@@ -700,12 +700,16 @@ describe('Message', () => {
700
700
  expect(screen.getByTestId('after-main-content')).toContainHTML('<strong>Bold after content</strong>');
701
701
  expect(screen.getByTestId('end-main-content')).toContainHTML('<strong>Bold end content</strong>');
702
702
  });
703
- it('should handle image correctly', () => {
703
+ it('should handle image correctly for user', () => {
704
704
  render(_jsx(Message, { avatar: "./img", role: "user", name: "User", content: IMAGE }));
705
+ expect(screen.queryByRole('img', { name: /Multi-colored wavy lines on a black background/i })).toBeFalsy();
706
+ });
707
+ it('should handle image correctly for bot', () => {
708
+ render(_jsx(Message, { avatar: "./img", role: "bot", name: "Bot", content: IMAGE }));
705
709
  expect(screen.getByRole('img', { name: /Multi-colored wavy lines on a black background/i })).toBeTruthy();
706
710
  });
707
711
  it('inline image parent should have class pf-chatbot__message-and-actions', () => {
708
- render(_jsx(Message, { avatar: "./img", role: "user", name: "User", content: INLINE_IMAGE }));
712
+ render(_jsx(Message, { avatar: "./img", role: "bot", name: "Bot", content: INLINE_IMAGE }));
709
713
  expect(screen.getByRole('img', { name: /Multi-colored wavy lines on a black background/i })).toBeTruthy();
710
714
  expect(screen.getByRole('img', { name: /Multi-colored wavy lines on a black background/i }).parentElement).toHaveClass('pf-chatbot__message-and-actions');
711
715
  });
@@ -799,6 +803,17 @@ describe('Message', () => {
799
803
  // code block isn't rendering
800
804
  expect(screen.queryByRole('button', { name: 'Copy code' })).toBeFalsy();
801
805
  });
806
+ it('should disable images and additional tags for user messages', () => {
807
+ render(_jsx(Message, { avatar: "./img", role: "user", name: "User", content: `${IMAGE} ${CODE_MESSAGE}`, reactMarkdownProps: { disallowedElements: ['code'] } }));
808
+ expect(screen.getByText('Here is some YAML code:')).toBeTruthy();
809
+ // code block isn't rendering
810
+ expect(screen.queryByRole('button', { name: 'Copy code' })).toBeFalsy();
811
+ expect(screen.queryByRole('img', { name: /Multi-colored wavy lines on a black background/i })).toBeFalsy();
812
+ });
813
+ it('can override image tag removal default for user messages', () => {
814
+ render(_jsx(Message, { avatar: "./img", role: "user", name: "User", content: IMAGE, hasNoImagesInUserMessages: false }));
815
+ expect(screen.getByRole('img', { name: /Multi-colored wavy lines on a black background/i })).toBeTruthy();
816
+ });
802
817
  it('should render deep thinking section correctly', () => {
803
818
  render(_jsx(Message, { avatar: "./img", role: "user", name: "User", content: "", deepThinking: DEEP_THINKING }));
804
819
  expect(screen.getByRole('button', { name: /Show thinking/i })).toBeTruthy();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@patternfly/chatbot",
3
- "version": "6.4.0-prerelease.24",
3
+ "version": "6.4.0-prerelease.25",
4
4
  "description": "This library provides React components based on PatternFly 6 that can be used to build chatbots.",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -392,6 +392,7 @@ _Italic text, formatted with single underscores_
392
392
  clobberPrefix: 'user-message-'
393
393
  }
394
394
  }}
395
+ hasNoImagesInUserMessages={false}
395
396
  />
396
397
  </>
397
398
  );
@@ -919,12 +919,16 @@ describe('Message', () => {
919
919
  expect(screen.getByTestId('after-main-content')).toContainHTML('<strong>Bold after content</strong>');
920
920
  expect(screen.getByTestId('end-main-content')).toContainHTML('<strong>Bold end content</strong>');
921
921
  });
922
- it('should handle image correctly', () => {
922
+ it('should handle image correctly for user', () => {
923
923
  render(<Message avatar="./img" role="user" name="User" content={IMAGE} />);
924
+ expect(screen.queryByRole('img', { name: /Multi-colored wavy lines on a black background/i })).toBeFalsy();
925
+ });
926
+ it('should handle image correctly for bot', () => {
927
+ render(<Message avatar="./img" role="bot" name="Bot" content={IMAGE} />);
924
928
  expect(screen.getByRole('img', { name: /Multi-colored wavy lines on a black background/i })).toBeTruthy();
925
929
  });
926
930
  it('inline image parent should have class pf-chatbot__message-and-actions', () => {
927
- render(<Message avatar="./img" role="user" name="User" content={INLINE_IMAGE} />);
931
+ render(<Message avatar="./img" role="bot" name="Bot" content={INLINE_IMAGE} />);
928
932
  expect(screen.getByRole('img', { name: /Multi-colored wavy lines on a black background/i })).toBeTruthy();
929
933
  expect(
930
934
  screen.getByRole('img', { name: /Multi-colored wavy lines on a black background/i }).parentElement
@@ -1046,6 +1050,25 @@ describe('Message', () => {
1046
1050
  // code block isn't rendering
1047
1051
  expect(screen.queryByRole('button', { name: 'Copy code' })).toBeFalsy();
1048
1052
  });
1053
+ it('should disable images and additional tags for user messages', () => {
1054
+ render(
1055
+ <Message
1056
+ avatar="./img"
1057
+ role="user"
1058
+ name="User"
1059
+ content={`${IMAGE} ${CODE_MESSAGE}`}
1060
+ reactMarkdownProps={{ disallowedElements: ['code'] }}
1061
+ />
1062
+ );
1063
+ expect(screen.getByText('Here is some YAML code:')).toBeTruthy();
1064
+ // code block isn't rendering
1065
+ expect(screen.queryByRole('button', { name: 'Copy code' })).toBeFalsy();
1066
+ expect(screen.queryByRole('img', { name: /Multi-colored wavy lines on a black background/i })).toBeFalsy();
1067
+ });
1068
+ it('can override image tag removal default for user messages', () => {
1069
+ render(<Message avatar="./img" role="user" name="User" content={IMAGE} hasNoImagesInUserMessages={false} />);
1070
+ expect(screen.getByRole('img', { name: /Multi-colored wavy lines on a black background/i })).toBeTruthy();
1071
+ });
1049
1072
  it('should render deep thinking section correctly', () => {
1050
1073
  render(<Message avatar="./img" role="user" name="User" content="" deepThinking={DEEP_THINKING} />);
1051
1074
  expect(screen.getByRole('button', { name: /Show thinking/i })).toBeTruthy();
@@ -203,6 +203,8 @@ export interface MessageProps extends Omit<HTMLProps<HTMLDivElement>, 'role'> {
203
203
  remarkGfmProps?: Options;
204
204
  /** Props for a tool call message */
205
205
  toolCall?: ToolCallProps;
206
+ /** Whether user messages default to stripping out images in markdown */
207
+ hasNoImagesInUserMessages?: boolean;
206
208
  }
207
209
 
208
210
  export const MessageBase: FunctionComponent<MessageProps> = ({
@@ -249,6 +251,7 @@ export const MessageBase: FunctionComponent<MessageProps> = ({
249
251
  deepThinking,
250
252
  remarkGfmProps,
251
253
  toolCall,
254
+ hasNoImagesInUserMessages = true,
252
255
  ...props
253
256
  }: MessageProps) => {
254
257
  const [messageText, setMessageText] = useState(content);
@@ -275,6 +278,11 @@ export const MessageBase: FunctionComponent<MessageProps> = ({
275
278
  const date = new Date();
276
279
  const dateString = timestamp ?? `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`;
277
280
 
281
+ const disallowedElements = role === 'user' && hasNoImagesInUserMessages ? ['img'] : [];
282
+ if (reactMarkdownProps && reactMarkdownProps.disallowedElements) {
283
+ disallowedElements.push(...reactMarkdownProps.disallowedElements);
284
+ }
285
+
278
286
  const handleMarkdown = () => {
279
287
  if (isMarkdownDisabled) {
280
288
  return (
@@ -415,6 +423,7 @@ export const MessageBase: FunctionComponent<MessageProps> = ({
415
423
  footnoteLabelProperties: { className: [''] },
416
424
  ...reactMarkdownProps?.remarkRehypeOptions
417
425
  }}
426
+ disallowedElements={disallowedElements}
418
427
  >
419
428
  {messageText}
420
429
  </Markdown>