@patternfly/chatbot 6.4.0-prerelease.2 → 6.4.0-prerelease.4
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.
- package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.js +1 -1
- package/dist/cjs/ChatbotHeader/ChatbotHeaderNewChatButton.d.ts +18 -0
- package/dist/cjs/ChatbotHeader/ChatbotHeaderNewChatButton.js +25 -0
- package/dist/cjs/ChatbotHeader/ChatbotHeaderNewChatButton.test.d.ts +1 -0
- package/dist/cjs/ChatbotHeader/ChatbotHeaderNewChatButton.test.js +22 -0
- package/dist/cjs/ChatbotHeader/index.d.ts +1 -0
- package/dist/cjs/ChatbotHeader/index.js +1 -0
- package/dist/cjs/Message/Message.d.ts +5 -0
- package/dist/cjs/Message/Message.js +37 -31
- package/dist/cjs/Message/Message.test.js +11 -0
- package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.js +2 -2
- package/dist/esm/ChatbotHeader/ChatbotHeaderNewChatButton.d.ts +18 -0
- package/dist/esm/ChatbotHeader/ChatbotHeaderNewChatButton.js +22 -0
- package/dist/esm/ChatbotHeader/ChatbotHeaderNewChatButton.test.d.ts +1 -0
- package/dist/esm/ChatbotHeader/ChatbotHeaderNewChatButton.test.js +20 -0
- package/dist/esm/ChatbotHeader/index.d.ts +1 -0
- package/dist/esm/ChatbotHeader/index.js +1 -0
- package/dist/esm/Message/Message.d.ts +5 -0
- package/dist/esm/Message/Message.js +37 -31
- package/dist/esm/Message/Message.test.js +11 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/patternfly-docs/content/extensions/chatbot/examples/UI/ChatbotHeaderBasic.tsx +17 -3
- package/patternfly-docs/content/extensions/chatbot/examples/UI/UI.md +7 -2
- package/patternfly-docs/content/extensions/chatbot/examples/demos/Chatbot.md +1 -1
- package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.tsx +7 -2
- package/src/ChatbotHeader/ChatbotHeaderNewChatButton.test.tsx +25 -0
- package/src/ChatbotHeader/ChatbotHeaderNewChatButton.tsx +64 -0
- package/src/ChatbotHeader/index.ts +1 -0
- package/src/FileDetails/__snapshots__/FileDetails.test.tsx.snap +6 -9
- package/src/FileDetailsLabel/__snapshots__/FileDetailsLabel.test.tsx.snap +6 -9
- package/src/Message/Message.test.tsx +19 -0
- package/src/Message/Message.tsx +62 -46
|
@@ -65,7 +65,7 @@ const ChatbotConversationHistoryNav = (_a) => {
|
|
|
65
65
|
};
|
|
66
66
|
const renderDrawerContent = () => ((0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: (0, jsx_runtime_1.jsx)(react_core_1.DrawerPanelBody, Object.assign({}, drawerPanelBodyProps, { children: renderMenuContent() })) }));
|
|
67
67
|
const renderPanelContent = () => {
|
|
68
|
-
const drawer = ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(react_core_1.DrawerHead, Object.assign({}, drawerHeadProps, { children: (0, jsx_runtime_1.jsxs)(react_core_1.DrawerActions, Object.assign({ "data-testid": drawerActionsTestId, className: reverseButtonOrder ? 'pf-v6-c-drawer__actions--reversed' : '' }, drawerActionsProps, { children: [(0, jsx_runtime_1.jsx)(react_core_1.DrawerCloseButton, Object.assign({ onClick: onDrawerToggle }, drawerCloseButtonProps)), onNewChat && ((0, jsx_runtime_1.jsx)(react_core_1.Button, Object.assign({ size: isCompact ? 'sm' : undefined, onClick: onNewChat }, newChatButtonProps, { children: newChatButtonText })))] })) })), (0, jsx_runtime_1.jsxs)("div", { className: "pf-chatbot__title-container", children: [(0, jsx_runtime_1.jsxs)(react_core_1.Title, { headingLevel: "h3", children: [(0, jsx_runtime_1.jsx)(react_core_1.Icon, { size: "lg", className: "pf-chatbot__title-icon", children: (0, jsx_runtime_1.jsx)(react_icons_1.OutlinedClockIcon, {}) }), title] }), !isLoading && handleTextInputChange && ((0, jsx_runtime_1.jsx)("div", { className: "pf-chatbot__input", children: (0, jsx_runtime_1.jsx)(react_core_1.SearchInput, { "aria-label": searchInputAriaLabel, onChange: (_event, value) => handleTextInputChange(value), placeholder: searchInputPlaceholder }) }))] }), isLoading ? (0, jsx_runtime_1.jsx)(LoadingState_1.default, Object.assign({}, loadingState)) : renderDrawerContent()] }));
|
|
68
|
+
const drawer = ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(react_core_1.DrawerHead, Object.assign({}, drawerHeadProps, { children: (0, jsx_runtime_1.jsxs)(react_core_1.DrawerActions, Object.assign({ "data-testid": drawerActionsTestId, className: reverseButtonOrder ? 'pf-v6-c-drawer__actions--reversed' : '' }, drawerActionsProps, { children: [(0, jsx_runtime_1.jsx)(react_core_1.DrawerCloseButton, Object.assign({ onClick: onDrawerToggle }, drawerCloseButtonProps)), onNewChat && ((0, jsx_runtime_1.jsx)(react_core_1.Button, Object.assign({ size: isCompact ? 'sm' : undefined, onClick: onNewChat, icon: (0, jsx_runtime_1.jsx)(react_icons_1.PenToSquareIcon, {}) }, newChatButtonProps, { children: newChatButtonText })))] })) })), (0, jsx_runtime_1.jsxs)("div", { className: "pf-chatbot__title-container", children: [(0, jsx_runtime_1.jsxs)(react_core_1.Title, { headingLevel: "h3", children: [(0, jsx_runtime_1.jsx)(react_core_1.Icon, { size: "lg", className: "pf-chatbot__title-icon", children: (0, jsx_runtime_1.jsx)(react_icons_1.OutlinedClockIcon, {}) }), title] }), !isLoading && handleTextInputChange && ((0, jsx_runtime_1.jsx)("div", { className: "pf-chatbot__input", children: (0, jsx_runtime_1.jsx)(react_core_1.SearchInput, { "aria-label": searchInputAriaLabel, onChange: (_event, value) => handleTextInputChange(value), placeholder: searchInputPlaceholder }) }))] }), isLoading ? (0, jsx_runtime_1.jsx)(LoadingState_1.default, Object.assign({}, loadingState)) : renderDrawerContent()] }));
|
|
69
69
|
return ((0, jsx_runtime_1.jsx)(react_core_1.DrawerPanelContent, Object.assign({ "aria-live": "polite", focusTrap: { enabled: true }, defaultSize: "384px" }, drawerPanelContentProps, { children: drawer })));
|
|
70
70
|
};
|
|
71
71
|
// An onKeyDown property must be passed to the Drawer component to handle closing
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { ButtonProps, TooltipProps } from '@patternfly/react-core';
|
|
2
|
+
export interface ChatbotHeaderNewChatButtonProps extends ButtonProps {
|
|
3
|
+
/** Callback function for when button is clicked */
|
|
4
|
+
onClick: () => void;
|
|
5
|
+
/** Custom classname for the header component */
|
|
6
|
+
className?: string;
|
|
7
|
+
/** Props spread to the PF Tooltip component wrapping the display mode dropdown */
|
|
8
|
+
tooltipProps?: TooltipProps;
|
|
9
|
+
/** Aria label for menu */
|
|
10
|
+
menuAriaLabel?: string;
|
|
11
|
+
/** Ref applied to menu */
|
|
12
|
+
innerRef?: React.Ref<HTMLButtonElement>;
|
|
13
|
+
/** Content used in tooltip */
|
|
14
|
+
tooltipContent?: string;
|
|
15
|
+
/** Sets button to compact styling. */
|
|
16
|
+
isCompact?: boolean;
|
|
17
|
+
}
|
|
18
|
+
export declare const ChatbotHeaderNewChatButton: import("react").ForwardRefExoticComponent<ChatbotHeaderNewChatButtonProps & import("react").RefAttributes<HTMLButtonElement>>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
3
|
+
var t = {};
|
|
4
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
5
|
+
t[p] = s[p];
|
|
6
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
7
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
8
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
9
|
+
t[p[i]] = s[p[i]];
|
|
10
|
+
}
|
|
11
|
+
return t;
|
|
12
|
+
};
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.ChatbotHeaderNewChatButton = void 0;
|
|
15
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
16
|
+
const react_1 = require("react");
|
|
17
|
+
const react_core_1 = require("@patternfly/react-core");
|
|
18
|
+
const pen_to_square_icon_1 = require("@patternfly/react-icons/dist/esm/icons/pen-to-square-icon");
|
|
19
|
+
const ChatbotHeaderNewChatButtonBase = (_a) => {
|
|
20
|
+
var { className, onClick, tooltipProps, menuAriaLabel = 'New chat', innerRef, tooltipContent = 'New chat', isCompact } = _a, props = __rest(_a, ["className", "onClick", "tooltipProps", "menuAriaLabel", "innerRef", "tooltipContent", "isCompact"]);
|
|
21
|
+
return ((0, jsx_runtime_1.jsx)("div", { className: `pf-chatbot__menu${className ? ` ${className}` : ''}`, children: (0, jsx_runtime_1.jsx)(react_core_1.Tooltip, Object.assign({ content: tooltipContent, position: "bottom",
|
|
22
|
+
// prevents VO announcements of both aria label and tooltip
|
|
23
|
+
aria: "none" }, tooltipProps, { children: (0, jsx_runtime_1.jsx)(react_core_1.Button, Object.assign({ className: `pf-chatbot__button--toggle-menu ${isCompact ? 'pf-m-compact' : ''}`, variant: "plain", onClick: onClick, "aria-label": menuAriaLabel, ref: innerRef, icon: (0, jsx_runtime_1.jsx)(react_core_1.Icon, { size: isCompact ? 'lg' : 'xl', isInline: true, children: (0, jsx_runtime_1.jsx)(pen_to_square_icon_1.PenToSquareIcon, {}) }), size: isCompact ? 'sm' : undefined }, props)) })) }));
|
|
24
|
+
};
|
|
25
|
+
exports.ChatbotHeaderNewChatButton = (0, react_1.forwardRef)((props, ref) => ((0, jsx_runtime_1.jsx)(ChatbotHeaderNewChatButtonBase, Object.assign({ innerRef: ref }, props))));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import '@testing-library/jest-dom';
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
4
|
+
const react_1 = require("@testing-library/react");
|
|
5
|
+
const ChatbotHeaderNewChatButton_1 = require("./ChatbotHeaderNewChatButton");
|
|
6
|
+
require("@testing-library/jest-dom");
|
|
7
|
+
describe('ChatbotHeaderNewChatButton', () => {
|
|
8
|
+
it('should render ChatbotHeaderNewChatButton', () => {
|
|
9
|
+
const { container } = (0, react_1.render)((0, jsx_runtime_1.jsx)(ChatbotHeaderNewChatButton_1.ChatbotHeaderNewChatButton, { className: "custom-header-new-chat-button", onClick: jest.fn() }));
|
|
10
|
+
expect(container.querySelector('.custom-header-new-chat-button')).toBeTruthy();
|
|
11
|
+
});
|
|
12
|
+
it('should call onClick handler when new chat button is pressed', () => {
|
|
13
|
+
const onClick = jest.fn();
|
|
14
|
+
(0, react_1.render)((0, jsx_runtime_1.jsx)(ChatbotHeaderNewChatButton_1.ChatbotHeaderNewChatButton, { className: "custom-header-new-chat-button", onClick: onClick }));
|
|
15
|
+
react_1.fireEvent.click(react_1.screen.getByRole('button', { name: 'New chat' }));
|
|
16
|
+
expect(onClick).toHaveBeenCalled();
|
|
17
|
+
});
|
|
18
|
+
it('should render button with isCompact', () => {
|
|
19
|
+
(0, react_1.render)((0, jsx_runtime_1.jsx)(ChatbotHeaderNewChatButton_1.ChatbotHeaderNewChatButton, { "data-testid": "new-chat-button", onClick: jest.fn(), isCompact: true }));
|
|
20
|
+
expect(react_1.screen.getByTestId('new-chat-button')).toHaveClass('pf-m-compact');
|
|
21
|
+
});
|
|
22
|
+
});
|
|
@@ -28,3 +28,4 @@ __exportStar(require("./ChatbotHeaderTitle"), exports);
|
|
|
28
28
|
__exportStar(require("./ChatbotHeaderOptionsDropdown"), exports);
|
|
29
29
|
__exportStar(require("./ChatbotHeaderSelectorDropdown"), exports);
|
|
30
30
|
__exportStar(require("./ChatbotHeaderCloseButton"), exports);
|
|
31
|
+
__exportStar(require("./ChatbotHeaderNewChatButton"), exports);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ReactNode } from 'react';
|
|
2
2
|
import type { FunctionComponent, HTMLProps, MouseEvent as ReactMouseEvent, Ref } from 'react';
|
|
3
|
+
import { Options } from 'react-markdown';
|
|
3
4
|
import { AlertProps, AvatarProps, ButtonProps, ExpandableSectionProps, ExpandableSectionToggleProps, FormProps, LabelGroupProps } from '@patternfly/react-core';
|
|
4
5
|
import { ActionProps } from '../ResponseActions/ResponseActions';
|
|
5
6
|
import { SourcesCardProps } from '../SourcesCard';
|
|
@@ -140,6 +141,10 @@ export interface MessageProps extends Omit<HTMLProps<HTMLDivElement>, 'role'> {
|
|
|
140
141
|
editFormProps?: FormProps;
|
|
141
142
|
/** Sets message to compact styling. */
|
|
142
143
|
isCompact?: boolean;
|
|
144
|
+
/** Disables markdown parsing for message, allowing only text input */
|
|
145
|
+
isMarkdownDisabled?: boolean;
|
|
146
|
+
/** 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 */
|
|
147
|
+
reactMarkdownProps?: Options;
|
|
143
148
|
}
|
|
144
149
|
export declare const MessageBase: FunctionComponent<MessageProps>;
|
|
145
150
|
declare const Message: import("react").ForwardRefExoticComponent<Omit<MessageProps, "ref"> & import("react").RefAttributes<HTMLDivElement>>;
|
|
@@ -51,7 +51,7 @@ const ErrorMessage_1 = __importDefault(require("./ErrorMessage/ErrorMessage"));
|
|
|
51
51
|
const MessageInput_1 = __importDefault(require("./MessageInput"));
|
|
52
52
|
const rehypeMoveImagesOutOfParagraphs_1 = require("./Plugins/rehypeMoveImagesOutOfParagraphs");
|
|
53
53
|
const MessageBase = (_a) => {
|
|
54
|
-
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 = [], linkProps, error, isEditable, editPlaceholder = 'Edit prompt message...', updateWord = 'Update', cancelWord = 'Cancel', onEditUpdate, onEditCancel, inputRef, editFormProps, isCompact } = _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", "linkProps", "error", "isEditable", "editPlaceholder", "updateWord", "cancelWord", "onEditUpdate", "onEditCancel", "inputRef", "editFormProps", "isCompact"]);
|
|
54
|
+
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 = [], linkProps, error, isEditable, editPlaceholder = 'Edit prompt message...', updateWord = 'Update', cancelWord = 'Cancel', onEditUpdate, onEditCancel, inputRef, editFormProps, isCompact, isMarkdownDisabled, reactMarkdownProps } = _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", "linkProps", "error", "isEditable", "editPlaceholder", "updateWord", "cancelWord", "onEditUpdate", "onEditCancel", "inputRef", "editFormProps", "isCompact", "isMarkdownDisabled", "reactMarkdownProps"]);
|
|
55
55
|
const [messageText, setMessageText] = (0, react_1.useState)(content);
|
|
56
56
|
(0, react_1.useEffect)(() => {
|
|
57
57
|
setMessageText(content);
|
|
@@ -73,6 +73,41 @@ const MessageBase = (_a) => {
|
|
|
73
73
|
// Keep timestamps consistent between Timestamp component and aria-label
|
|
74
74
|
const date = new Date();
|
|
75
75
|
const dateString = timestamp !== null && timestamp !== void 0 ? timestamp : `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`;
|
|
76
|
+
const handleMarkdown = () => {
|
|
77
|
+
if (isMarkdownDisabled) {
|
|
78
|
+
return ((0, jsx_runtime_1.jsx)(TextMessage_1.default, Object.assign({ component: react_core_1.ContentVariants.p }, props, { children: messageText })));
|
|
79
|
+
}
|
|
80
|
+
return ((0, jsx_runtime_1.jsx)(react_markdown_1.default, Object.assign({ components: {
|
|
81
|
+
p: (props) => (0, jsx_runtime_1.jsx)(TextMessage_1.default, Object.assign({ component: react_core_1.ContentVariants.p }, props)),
|
|
82
|
+
code: (_a) => {
|
|
83
|
+
var { children } = _a, props = __rest(_a, ["children"]);
|
|
84
|
+
return ((0, jsx_runtime_1.jsx)(CodeBlockMessage_1.default, Object.assign({}, props, codeBlockProps, { children: children })));
|
|
85
|
+
},
|
|
86
|
+
h1: (props) => (0, jsx_runtime_1.jsx)(TextMessage_1.default, Object.assign({ component: react_core_1.ContentVariants.h1 }, props)),
|
|
87
|
+
h2: (props) => (0, jsx_runtime_1.jsx)(TextMessage_1.default, Object.assign({ component: react_core_1.ContentVariants.h2 }, props)),
|
|
88
|
+
h3: (props) => (0, jsx_runtime_1.jsx)(TextMessage_1.default, Object.assign({ component: react_core_1.ContentVariants.h3 }, props)),
|
|
89
|
+
h4: (props) => (0, jsx_runtime_1.jsx)(TextMessage_1.default, Object.assign({ component: react_core_1.ContentVariants.h4 }, props)),
|
|
90
|
+
h5: (props) => (0, jsx_runtime_1.jsx)(TextMessage_1.default, Object.assign({ component: react_core_1.ContentVariants.h5 }, props)),
|
|
91
|
+
h6: (props) => (0, jsx_runtime_1.jsx)(TextMessage_1.default, Object.assign({ component: react_core_1.ContentVariants.h6 }, props)),
|
|
92
|
+
blockquote: (props) => (0, jsx_runtime_1.jsx)(TextMessage_1.default, Object.assign({ component: react_core_1.ContentVariants.blockquote }, props)),
|
|
93
|
+
ul: (props) => (0, jsx_runtime_1.jsx)(UnorderedListMessage_1.default, Object.assign({}, props)),
|
|
94
|
+
ol: (props) => (0, jsx_runtime_1.jsx)(OrderedListMessage_1.default, Object.assign({}, props)),
|
|
95
|
+
li: (props) => (0, jsx_runtime_1.jsx)(ListItemMessage_1.default, Object.assign({}, props)),
|
|
96
|
+
table: (props) => (0, jsx_runtime_1.jsx)(TableMessage_1.default, Object.assign({}, props, tableProps)),
|
|
97
|
+
tbody: (props) => (0, jsx_runtime_1.jsx)(TbodyMessage_1.default, Object.assign({}, props)),
|
|
98
|
+
thead: (props) => (0, jsx_runtime_1.jsx)(TheadMessage_1.default, Object.assign({}, props)),
|
|
99
|
+
tr: (props) => (0, jsx_runtime_1.jsx)(TrMessage_1.default, Object.assign({}, props)),
|
|
100
|
+
td: (props) => {
|
|
101
|
+
// Conflicts with Td type
|
|
102
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
103
|
+
const { width } = props, rest = __rest(props, ["width"]);
|
|
104
|
+
return (0, jsx_runtime_1.jsx)(TdMessage_1.default, Object.assign({}, rest));
|
|
105
|
+
},
|
|
106
|
+
th: (props) => (0, jsx_runtime_1.jsx)(ThMessage_1.default, Object.assign({}, props)),
|
|
107
|
+
img: (props) => (0, jsx_runtime_1.jsx)(ImageMessage_1.default, Object.assign({}, props)),
|
|
108
|
+
a: (props) => ((0, jsx_runtime_1.jsx)(LinkMessage_1.default, Object.assign({ href: props.href, rel: props.rel, target: props.target }, linkProps, { children: props.children })))
|
|
109
|
+
}, remarkPlugins: [remark_gfm_1.default], rehypePlugins: rehypePlugins }, reactMarkdownProps, { children: messageText })));
|
|
110
|
+
};
|
|
76
111
|
const renderMessage = () => {
|
|
77
112
|
if (isLoading) {
|
|
78
113
|
return (0, jsx_runtime_1.jsx)(MessageLoading_1.default, { loadingWord: loadingWord });
|
|
@@ -83,36 +118,7 @@ const MessageBase = (_a) => {
|
|
|
83
118
|
setMessageText(value);
|
|
84
119
|
}, onEditCancel: onEditCancel, inputRef: inputRef }, editFormProps))] }));
|
|
85
120
|
}
|
|
86
|
-
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [beforeMainContent && (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: beforeMainContent }), error ? (
|
|
87
|
-
p: (props) => (0, jsx_runtime_1.jsx)(TextMessage_1.default, Object.assign({ component: react_core_1.ContentVariants.p }, props)),
|
|
88
|
-
code: (_a) => {
|
|
89
|
-
var { children } = _a, props = __rest(_a, ["children"]);
|
|
90
|
-
return ((0, jsx_runtime_1.jsx)(CodeBlockMessage_1.default, Object.assign({}, props, codeBlockProps, { children: children })));
|
|
91
|
-
},
|
|
92
|
-
h1: (props) => (0, jsx_runtime_1.jsx)(TextMessage_1.default, Object.assign({ component: react_core_1.ContentVariants.h1 }, props)),
|
|
93
|
-
h2: (props) => (0, jsx_runtime_1.jsx)(TextMessage_1.default, Object.assign({ component: react_core_1.ContentVariants.h2 }, props)),
|
|
94
|
-
h3: (props) => (0, jsx_runtime_1.jsx)(TextMessage_1.default, Object.assign({ component: react_core_1.ContentVariants.h3 }, props)),
|
|
95
|
-
h4: (props) => (0, jsx_runtime_1.jsx)(TextMessage_1.default, Object.assign({ component: react_core_1.ContentVariants.h4 }, props)),
|
|
96
|
-
h5: (props) => (0, jsx_runtime_1.jsx)(TextMessage_1.default, Object.assign({ component: react_core_1.ContentVariants.h5 }, props)),
|
|
97
|
-
h6: (props) => (0, jsx_runtime_1.jsx)(TextMessage_1.default, Object.assign({ component: react_core_1.ContentVariants.h6 }, props)),
|
|
98
|
-
blockquote: (props) => (0, jsx_runtime_1.jsx)(TextMessage_1.default, Object.assign({ component: react_core_1.ContentVariants.blockquote }, props)),
|
|
99
|
-
ul: (props) => (0, jsx_runtime_1.jsx)(UnorderedListMessage_1.default, Object.assign({}, props)),
|
|
100
|
-
ol: (props) => (0, jsx_runtime_1.jsx)(OrderedListMessage_1.default, Object.assign({}, props)),
|
|
101
|
-
li: (props) => (0, jsx_runtime_1.jsx)(ListItemMessage_1.default, Object.assign({}, props)),
|
|
102
|
-
table: (props) => (0, jsx_runtime_1.jsx)(TableMessage_1.default, Object.assign({}, props, tableProps)),
|
|
103
|
-
tbody: (props) => (0, jsx_runtime_1.jsx)(TbodyMessage_1.default, Object.assign({}, props)),
|
|
104
|
-
thead: (props) => (0, jsx_runtime_1.jsx)(TheadMessage_1.default, Object.assign({}, props)),
|
|
105
|
-
tr: (props) => (0, jsx_runtime_1.jsx)(TrMessage_1.default, Object.assign({}, props)),
|
|
106
|
-
td: (props) => {
|
|
107
|
-
// Conflicts with Td type
|
|
108
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
109
|
-
const { width } = props, rest = __rest(props, ["width"]);
|
|
110
|
-
return (0, jsx_runtime_1.jsx)(TdMessage_1.default, Object.assign({}, rest));
|
|
111
|
-
},
|
|
112
|
-
th: (props) => (0, jsx_runtime_1.jsx)(ThMessage_1.default, Object.assign({}, props)),
|
|
113
|
-
img: (props) => (0, jsx_runtime_1.jsx)(ImageMessage_1.default, Object.assign({}, props)),
|
|
114
|
-
a: (props) => ((0, jsx_runtime_1.jsx)(LinkMessage_1.default, Object.assign({ href: props.href, rel: props.rel, target: props.target }, linkProps, { children: props.children })))
|
|
115
|
-
}, remarkPlugins: [remark_gfm_1.default], rehypePlugins: rehypePlugins, children: messageText }))] }));
|
|
121
|
+
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [beforeMainContent && (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: beforeMainContent }), error ? (0, jsx_runtime_1.jsx)(ErrorMessage_1.default, Object.assign({}, error)) : handleMarkdown()] }));
|
|
116
122
|
};
|
|
117
123
|
return ((0, jsx_runtime_1.jsxs)("section", Object.assign({ "aria-label": `Message from ${role} - ${dateString}`, className: `pf-chatbot__message pf-chatbot__message--${role}`, "aria-live": isLiveRegion ? 'polite' : undefined, "aria-atomic": isLiveRegion ? false : undefined, ref: innerRef }, props, { children: [(0, jsx_runtime_1.jsx)(react_core_1.Avatar, Object.assign({ className: `pf-chatbot__message-avatar ${hasRoundAvatar ? 'pf-chatbot__message-avatar--round' : ''} ${avatarClassName ? avatarClassName : ''}`, src: avatar, alt: "" }, avatarProps)), (0, jsx_runtime_1.jsxs)("div", { className: "pf-chatbot__message-contents", children: [(0, jsx_runtime_1.jsxs)("div", { className: "pf-chatbot__message-meta", children: [name && ((0, jsx_runtime_1.jsx)("span", { className: "pf-chatbot__message-name", children: (0, jsx_runtime_1.jsx)(react_core_1.Truncate, { content: name }) })), role === 'bot' && ((0, jsx_runtime_1.jsx)(react_core_1.Label, { variant: "outline", isCompact: true, children: botWord })), (0, jsx_runtime_1.jsx)(react_core_1.Timestamp, { date: date, children: timestamp })] }), (0, jsx_runtime_1.jsxs)("div", { className: "pf-chatbot__message-response", children: [(0, jsx_runtime_1.jsxs)("div", { className: "pf-chatbot__message-and-actions", children: [renderMessage(), afterMainContent && (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: afterMainContent }), !isLoading && sources && (0, jsx_runtime_1.jsx)(SourcesCard_1.default, Object.assign({}, sources, { isCompact: isCompact })), quickStarts && quickStarts.quickStart && ((0, jsx_runtime_1.jsx)(QuickStartTile_1.default, { quickStart: quickStarts.quickStart, onSelectQuickStart: quickStarts.onSelectQuickStart, minuteWord: quickStarts.minuteWord, minuteWordPlural: quickStarts.minuteWordPlural, prerequisiteWord: quickStarts.prerequisiteWord, prerequisiteWordPlural: quickStarts.prerequisiteWordPlural, quickStartButtonAriaLabel: quickStarts.quickStartButtonAriaLabel, isCompact: isCompact })), !isLoading && !isEditable && actions && (0, jsx_runtime_1.jsx)(ResponseActions_1.default, { actions: actions }), userFeedbackForm && (0, jsx_runtime_1.jsx)(UserFeedback_1.default, Object.assign({}, userFeedbackForm, { timestamp: dateString, isCompact: isCompact })), userFeedbackComplete && ((0, jsx_runtime_1.jsx)(UserFeedbackComplete_1.default, Object.assign({}, userFeedbackComplete, { timestamp: dateString, isCompact: isCompact }))), !isLoading && quickResponses && ((0, jsx_runtime_1.jsx)(QuickResponse_1.default, { quickResponses: quickResponses, quickResponseContainerProps: quickResponseContainerProps, isCompact: isCompact }))] }), attachments && ((0, jsx_runtime_1.jsx)("div", { className: "pf-chatbot__message-attachments-container", children: attachments.map((attachment) => {
|
|
118
124
|
var _a;
|
|
@@ -739,4 +739,15 @@ describe('Message', () => {
|
|
|
739
739
|
const form = container.querySelector('form');
|
|
740
740
|
expect(form).toHaveClass('test');
|
|
741
741
|
});
|
|
742
|
+
it('should be able to disable markdown parsing', () => {
|
|
743
|
+
(0, react_2.render)((0, jsx_runtime_1.jsx)(Message_1.default, { avatar: "./img", role: "user", name: "User", content: CODE_MESSAGE, isMarkdownDisabled: true }));
|
|
744
|
+
// this is looking for markdown syntax that is ordinarily stripped
|
|
745
|
+
expect(react_2.screen.getByText(/~~~yaml/i)).toBeTruthy();
|
|
746
|
+
});
|
|
747
|
+
it('should be able to pass props to react-markdown, such as disabling tags', () => {
|
|
748
|
+
(0, react_2.render)((0, jsx_runtime_1.jsx)(Message_1.default, { avatar: "./img", role: "user", name: "User", content: CODE_MESSAGE, reactMarkdownProps: { disallowedElements: ['code'] } }));
|
|
749
|
+
expect(react_2.screen.getByText('Here is some YAML code:')).toBeTruthy();
|
|
750
|
+
// code block isn't rendering
|
|
751
|
+
expect(react_2.screen.queryByRole('button', { name: 'Copy code' })).toBeFalsy();
|
|
752
|
+
});
|
|
742
753
|
});
|
|
@@ -13,7 +13,7 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
|
|
|
13
13
|
import { useRef, Fragment } from 'react';
|
|
14
14
|
// Import PatternFly components
|
|
15
15
|
import { Button, Drawer, DrawerPanelContent, DrawerContent, DrawerPanelBody, DrawerHead, DrawerActions, DrawerCloseButton, DrawerContentBody, SearchInput, Menu, MenuList, MenuGroup, MenuItem, MenuContent, Title, Icon } from '@patternfly/react-core';
|
|
16
|
-
import { OutlinedClockIcon, OutlinedCommentAltIcon } from '@patternfly/react-icons';
|
|
16
|
+
import { OutlinedClockIcon, OutlinedCommentAltIcon, PenToSquareIcon } from '@patternfly/react-icons';
|
|
17
17
|
import { ChatbotDisplayMode } from '../Chatbot/Chatbot';
|
|
18
18
|
import ConversationHistoryDropdown from './ChatbotConversationHistoryDropdown';
|
|
19
19
|
import LoadingState from './LoadingState';
|
|
@@ -59,7 +59,7 @@ export const ChatbotConversationHistoryNav = (_a) => {
|
|
|
59
59
|
};
|
|
60
60
|
const renderDrawerContent = () => (_jsx(_Fragment, { children: _jsx(DrawerPanelBody, Object.assign({}, drawerPanelBodyProps, { children: renderMenuContent() })) }));
|
|
61
61
|
const renderPanelContent = () => {
|
|
62
|
-
const drawer = (_jsxs(_Fragment, { children: [_jsx(DrawerHead, Object.assign({}, drawerHeadProps, { children: _jsxs(DrawerActions, Object.assign({ "data-testid": drawerActionsTestId, className: reverseButtonOrder ? 'pf-v6-c-drawer__actions--reversed' : '' }, drawerActionsProps, { children: [_jsx(DrawerCloseButton, Object.assign({ onClick: onDrawerToggle }, drawerCloseButtonProps)), onNewChat && (_jsx(Button, Object.assign({ size: isCompact ? 'sm' : undefined, onClick: onNewChat }, newChatButtonProps, { children: newChatButtonText })))] })) })), _jsxs("div", { className: "pf-chatbot__title-container", children: [_jsxs(Title, { headingLevel: "h3", children: [_jsx(Icon, { size: "lg", className: "pf-chatbot__title-icon", children: _jsx(OutlinedClockIcon, {}) }), title] }), !isLoading && handleTextInputChange && (_jsx("div", { className: "pf-chatbot__input", children: _jsx(SearchInput, { "aria-label": searchInputAriaLabel, onChange: (_event, value) => handleTextInputChange(value), placeholder: searchInputPlaceholder }) }))] }), isLoading ? _jsx(LoadingState, Object.assign({}, loadingState)) : renderDrawerContent()] }));
|
|
62
|
+
const drawer = (_jsxs(_Fragment, { children: [_jsx(DrawerHead, Object.assign({}, drawerHeadProps, { children: _jsxs(DrawerActions, Object.assign({ "data-testid": drawerActionsTestId, className: reverseButtonOrder ? 'pf-v6-c-drawer__actions--reversed' : '' }, drawerActionsProps, { children: [_jsx(DrawerCloseButton, Object.assign({ onClick: onDrawerToggle }, drawerCloseButtonProps)), onNewChat && (_jsx(Button, Object.assign({ size: isCompact ? 'sm' : undefined, onClick: onNewChat, icon: _jsx(PenToSquareIcon, {}) }, newChatButtonProps, { children: newChatButtonText })))] })) })), _jsxs("div", { className: "pf-chatbot__title-container", children: [_jsxs(Title, { headingLevel: "h3", children: [_jsx(Icon, { size: "lg", className: "pf-chatbot__title-icon", children: _jsx(OutlinedClockIcon, {}) }), title] }), !isLoading && handleTextInputChange && (_jsx("div", { className: "pf-chatbot__input", children: _jsx(SearchInput, { "aria-label": searchInputAriaLabel, onChange: (_event, value) => handleTextInputChange(value), placeholder: searchInputPlaceholder }) }))] }), isLoading ? _jsx(LoadingState, Object.assign({}, loadingState)) : renderDrawerContent()] }));
|
|
63
63
|
return (_jsx(DrawerPanelContent, Object.assign({ "aria-live": "polite", focusTrap: { enabled: true }, defaultSize: "384px" }, drawerPanelContentProps, { children: drawer })));
|
|
64
64
|
};
|
|
65
65
|
// An onKeyDown property must be passed to the Drawer component to handle closing
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { ButtonProps, TooltipProps } from '@patternfly/react-core';
|
|
2
|
+
export interface ChatbotHeaderNewChatButtonProps extends ButtonProps {
|
|
3
|
+
/** Callback function for when button is clicked */
|
|
4
|
+
onClick: () => void;
|
|
5
|
+
/** Custom classname for the header component */
|
|
6
|
+
className?: string;
|
|
7
|
+
/** Props spread to the PF Tooltip component wrapping the display mode dropdown */
|
|
8
|
+
tooltipProps?: TooltipProps;
|
|
9
|
+
/** Aria label for menu */
|
|
10
|
+
menuAriaLabel?: string;
|
|
11
|
+
/** Ref applied to menu */
|
|
12
|
+
innerRef?: React.Ref<HTMLButtonElement>;
|
|
13
|
+
/** Content used in tooltip */
|
|
14
|
+
tooltipContent?: string;
|
|
15
|
+
/** Sets button to compact styling. */
|
|
16
|
+
isCompact?: boolean;
|
|
17
|
+
}
|
|
18
|
+
export declare const ChatbotHeaderNewChatButton: import("react").ForwardRefExoticComponent<ChatbotHeaderNewChatButtonProps & import("react").RefAttributes<HTMLButtonElement>>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
2
|
+
var t = {};
|
|
3
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
4
|
+
t[p] = s[p];
|
|
5
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
6
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
7
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
8
|
+
t[p[i]] = s[p[i]];
|
|
9
|
+
}
|
|
10
|
+
return t;
|
|
11
|
+
};
|
|
12
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
13
|
+
import { forwardRef } from 'react';
|
|
14
|
+
import { Button, Icon, Tooltip } from '@patternfly/react-core';
|
|
15
|
+
import { PenToSquareIcon } from '@patternfly/react-icons/dist/esm/icons/pen-to-square-icon';
|
|
16
|
+
const ChatbotHeaderNewChatButtonBase = (_a) => {
|
|
17
|
+
var { className, onClick, tooltipProps, menuAriaLabel = 'New chat', innerRef, tooltipContent = 'New chat', isCompact } = _a, props = __rest(_a, ["className", "onClick", "tooltipProps", "menuAriaLabel", "innerRef", "tooltipContent", "isCompact"]);
|
|
18
|
+
return (_jsx("div", { className: `pf-chatbot__menu${className ? ` ${className}` : ''}`, children: _jsx(Tooltip, Object.assign({ content: tooltipContent, position: "bottom",
|
|
19
|
+
// prevents VO announcements of both aria label and tooltip
|
|
20
|
+
aria: "none" }, tooltipProps, { children: _jsx(Button, Object.assign({ className: `pf-chatbot__button--toggle-menu ${isCompact ? 'pf-m-compact' : ''}`, variant: "plain", onClick: onClick, "aria-label": menuAriaLabel, ref: innerRef, icon: _jsx(Icon, { size: isCompact ? 'lg' : 'xl', isInline: true, children: _jsx(PenToSquareIcon, {}) }), size: isCompact ? 'sm' : undefined }, props)) })) }));
|
|
21
|
+
};
|
|
22
|
+
export const ChatbotHeaderNewChatButton = forwardRef((props, ref) => (_jsx(ChatbotHeaderNewChatButtonBase, Object.assign({ innerRef: ref }, props))));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import '@testing-library/jest-dom';
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { fireEvent, render, screen } from '@testing-library/react';
|
|
3
|
+
import { ChatbotHeaderNewChatButton } from './ChatbotHeaderNewChatButton';
|
|
4
|
+
import '@testing-library/jest-dom';
|
|
5
|
+
describe('ChatbotHeaderNewChatButton', () => {
|
|
6
|
+
it('should render ChatbotHeaderNewChatButton', () => {
|
|
7
|
+
const { container } = render(_jsx(ChatbotHeaderNewChatButton, { className: "custom-header-new-chat-button", onClick: jest.fn() }));
|
|
8
|
+
expect(container.querySelector('.custom-header-new-chat-button')).toBeTruthy();
|
|
9
|
+
});
|
|
10
|
+
it('should call onClick handler when new chat button is pressed', () => {
|
|
11
|
+
const onClick = jest.fn();
|
|
12
|
+
render(_jsx(ChatbotHeaderNewChatButton, { className: "custom-header-new-chat-button", onClick: onClick }));
|
|
13
|
+
fireEvent.click(screen.getByRole('button', { name: 'New chat' }));
|
|
14
|
+
expect(onClick).toHaveBeenCalled();
|
|
15
|
+
});
|
|
16
|
+
it('should render button with isCompact', () => {
|
|
17
|
+
render(_jsx(ChatbotHeaderNewChatButton, { "data-testid": "new-chat-button", onClick: jest.fn(), isCompact: true }));
|
|
18
|
+
expect(screen.getByTestId('new-chat-button')).toHaveClass('pf-m-compact');
|
|
19
|
+
});
|
|
20
|
+
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ReactNode } from 'react';
|
|
2
2
|
import type { FunctionComponent, HTMLProps, MouseEvent as ReactMouseEvent, Ref } from 'react';
|
|
3
|
+
import { Options } from 'react-markdown';
|
|
3
4
|
import { AlertProps, AvatarProps, ButtonProps, ExpandableSectionProps, ExpandableSectionToggleProps, FormProps, LabelGroupProps } from '@patternfly/react-core';
|
|
4
5
|
import { ActionProps } from '../ResponseActions/ResponseActions';
|
|
5
6
|
import { SourcesCardProps } from '../SourcesCard';
|
|
@@ -140,6 +141,10 @@ export interface MessageProps extends Omit<HTMLProps<HTMLDivElement>, 'role'> {
|
|
|
140
141
|
editFormProps?: FormProps;
|
|
141
142
|
/** Sets message to compact styling. */
|
|
142
143
|
isCompact?: boolean;
|
|
144
|
+
/** Disables markdown parsing for message, allowing only text input */
|
|
145
|
+
isMarkdownDisabled?: boolean;
|
|
146
|
+
/** 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 */
|
|
147
|
+
reactMarkdownProps?: Options;
|
|
143
148
|
}
|
|
144
149
|
export declare const MessageBase: FunctionComponent<MessageProps>;
|
|
145
150
|
declare const Message: import("react").ForwardRefExoticComponent<Omit<MessageProps, "ref"> & import("react").RefAttributes<HTMLDivElement>>;
|
|
@@ -45,7 +45,7 @@ import ErrorMessage from './ErrorMessage/ErrorMessage';
|
|
|
45
45
|
import MessageInput from './MessageInput';
|
|
46
46
|
import { rehypeMoveImagesOutOfParagraphs } from './Plugins/rehypeMoveImagesOutOfParagraphs';
|
|
47
47
|
export const MessageBase = (_a) => {
|
|
48
|
-
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 = [], linkProps, error, isEditable, editPlaceholder = 'Edit prompt message...', updateWord = 'Update', cancelWord = 'Cancel', onEditUpdate, onEditCancel, inputRef, editFormProps, isCompact } = _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", "linkProps", "error", "isEditable", "editPlaceholder", "updateWord", "cancelWord", "onEditUpdate", "onEditCancel", "inputRef", "editFormProps", "isCompact"]);
|
|
48
|
+
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 = [], linkProps, error, isEditable, editPlaceholder = 'Edit prompt message...', updateWord = 'Update', cancelWord = 'Cancel', onEditUpdate, onEditCancel, inputRef, editFormProps, isCompact, isMarkdownDisabled, reactMarkdownProps } = _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", "linkProps", "error", "isEditable", "editPlaceholder", "updateWord", "cancelWord", "onEditUpdate", "onEditCancel", "inputRef", "editFormProps", "isCompact", "isMarkdownDisabled", "reactMarkdownProps"]);
|
|
49
49
|
const [messageText, setMessageText] = useState(content);
|
|
50
50
|
useEffect(() => {
|
|
51
51
|
setMessageText(content);
|
|
@@ -67,6 +67,41 @@ export const MessageBase = (_a) => {
|
|
|
67
67
|
// Keep timestamps consistent between Timestamp component and aria-label
|
|
68
68
|
const date = new Date();
|
|
69
69
|
const dateString = timestamp !== null && timestamp !== void 0 ? timestamp : `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`;
|
|
70
|
+
const handleMarkdown = () => {
|
|
71
|
+
if (isMarkdownDisabled) {
|
|
72
|
+
return (_jsx(TextMessage, Object.assign({ component: ContentVariants.p }, props, { children: messageText })));
|
|
73
|
+
}
|
|
74
|
+
return (_jsx(Markdown, Object.assign({ components: {
|
|
75
|
+
p: (props) => _jsx(TextMessage, Object.assign({ component: ContentVariants.p }, props)),
|
|
76
|
+
code: (_a) => {
|
|
77
|
+
var { children } = _a, props = __rest(_a, ["children"]);
|
|
78
|
+
return (_jsx(CodeBlockMessage, Object.assign({}, props, codeBlockProps, { children: children })));
|
|
79
|
+
},
|
|
80
|
+
h1: (props) => _jsx(TextMessage, Object.assign({ component: ContentVariants.h1 }, props)),
|
|
81
|
+
h2: (props) => _jsx(TextMessage, Object.assign({ component: ContentVariants.h2 }, props)),
|
|
82
|
+
h3: (props) => _jsx(TextMessage, Object.assign({ component: ContentVariants.h3 }, props)),
|
|
83
|
+
h4: (props) => _jsx(TextMessage, Object.assign({ component: ContentVariants.h4 }, props)),
|
|
84
|
+
h5: (props) => _jsx(TextMessage, Object.assign({ component: ContentVariants.h5 }, props)),
|
|
85
|
+
h6: (props) => _jsx(TextMessage, Object.assign({ component: ContentVariants.h6 }, props)),
|
|
86
|
+
blockquote: (props) => _jsx(TextMessage, Object.assign({ component: ContentVariants.blockquote }, props)),
|
|
87
|
+
ul: (props) => _jsx(UnorderedListMessage, Object.assign({}, props)),
|
|
88
|
+
ol: (props) => _jsx(OrderedListMessage, Object.assign({}, props)),
|
|
89
|
+
li: (props) => _jsx(ListItemMessage, Object.assign({}, props)),
|
|
90
|
+
table: (props) => _jsx(TableMessage, Object.assign({}, props, tableProps)),
|
|
91
|
+
tbody: (props) => _jsx(TbodyMessage, Object.assign({}, props)),
|
|
92
|
+
thead: (props) => _jsx(TheadMessage, Object.assign({}, props)),
|
|
93
|
+
tr: (props) => _jsx(TrMessage, Object.assign({}, props)),
|
|
94
|
+
td: (props) => {
|
|
95
|
+
// Conflicts with Td type
|
|
96
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
97
|
+
const { width } = props, rest = __rest(props, ["width"]);
|
|
98
|
+
return _jsx(TdMessage, Object.assign({}, rest));
|
|
99
|
+
},
|
|
100
|
+
th: (props) => _jsx(ThMessage, Object.assign({}, props)),
|
|
101
|
+
img: (props) => _jsx(ImageMessage, Object.assign({}, props)),
|
|
102
|
+
a: (props) => (_jsx(LinkMessage, Object.assign({ href: props.href, rel: props.rel, target: props.target }, linkProps, { children: props.children })))
|
|
103
|
+
}, remarkPlugins: [remarkGfm], rehypePlugins: rehypePlugins }, reactMarkdownProps, { children: messageText })));
|
|
104
|
+
};
|
|
70
105
|
const renderMessage = () => {
|
|
71
106
|
if (isLoading) {
|
|
72
107
|
return _jsx(MessageLoading, { loadingWord: loadingWord });
|
|
@@ -77,36 +112,7 @@ export const MessageBase = (_a) => {
|
|
|
77
112
|
setMessageText(value);
|
|
78
113
|
}, onEditCancel: onEditCancel, inputRef: inputRef }, editFormProps))] }));
|
|
79
114
|
}
|
|
80
|
-
return (_jsxs(_Fragment, { children: [beforeMainContent && _jsx(_Fragment, { children: beforeMainContent }), error ?
|
|
81
|
-
p: (props) => _jsx(TextMessage, Object.assign({ component: ContentVariants.p }, props)),
|
|
82
|
-
code: (_a) => {
|
|
83
|
-
var { children } = _a, props = __rest(_a, ["children"]);
|
|
84
|
-
return (_jsx(CodeBlockMessage, Object.assign({}, props, codeBlockProps, { children: children })));
|
|
85
|
-
},
|
|
86
|
-
h1: (props) => _jsx(TextMessage, Object.assign({ component: ContentVariants.h1 }, props)),
|
|
87
|
-
h2: (props) => _jsx(TextMessage, Object.assign({ component: ContentVariants.h2 }, props)),
|
|
88
|
-
h3: (props) => _jsx(TextMessage, Object.assign({ component: ContentVariants.h3 }, props)),
|
|
89
|
-
h4: (props) => _jsx(TextMessage, Object.assign({ component: ContentVariants.h4 }, props)),
|
|
90
|
-
h5: (props) => _jsx(TextMessage, Object.assign({ component: ContentVariants.h5 }, props)),
|
|
91
|
-
h6: (props) => _jsx(TextMessage, Object.assign({ component: ContentVariants.h6 }, props)),
|
|
92
|
-
blockquote: (props) => _jsx(TextMessage, Object.assign({ component: ContentVariants.blockquote }, props)),
|
|
93
|
-
ul: (props) => _jsx(UnorderedListMessage, Object.assign({}, props)),
|
|
94
|
-
ol: (props) => _jsx(OrderedListMessage, Object.assign({}, props)),
|
|
95
|
-
li: (props) => _jsx(ListItemMessage, Object.assign({}, props)),
|
|
96
|
-
table: (props) => _jsx(TableMessage, Object.assign({}, props, tableProps)),
|
|
97
|
-
tbody: (props) => _jsx(TbodyMessage, Object.assign({}, props)),
|
|
98
|
-
thead: (props) => _jsx(TheadMessage, Object.assign({}, props)),
|
|
99
|
-
tr: (props) => _jsx(TrMessage, Object.assign({}, props)),
|
|
100
|
-
td: (props) => {
|
|
101
|
-
// Conflicts with Td type
|
|
102
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
103
|
-
const { width } = props, rest = __rest(props, ["width"]);
|
|
104
|
-
return _jsx(TdMessage, Object.assign({}, rest));
|
|
105
|
-
},
|
|
106
|
-
th: (props) => _jsx(ThMessage, Object.assign({}, props)),
|
|
107
|
-
img: (props) => _jsx(ImageMessage, Object.assign({}, props)),
|
|
108
|
-
a: (props) => (_jsx(LinkMessage, Object.assign({ href: props.href, rel: props.rel, target: props.target }, linkProps, { children: props.children })))
|
|
109
|
-
}, remarkPlugins: [remarkGfm], rehypePlugins: rehypePlugins, children: messageText }))] }));
|
|
115
|
+
return (_jsxs(_Fragment, { children: [beforeMainContent && _jsx(_Fragment, { children: beforeMainContent }), error ? _jsx(ErrorMessage, Object.assign({}, error)) : handleMarkdown()] }));
|
|
110
116
|
};
|
|
111
117
|
return (_jsxs("section", Object.assign({ "aria-label": `Message from ${role} - ${dateString}`, className: `pf-chatbot__message pf-chatbot__message--${role}`, "aria-live": isLiveRegion ? 'polite' : undefined, "aria-atomic": isLiveRegion ? false : undefined, ref: innerRef }, props, { children: [_jsx(Avatar, Object.assign({ className: `pf-chatbot__message-avatar ${hasRoundAvatar ? 'pf-chatbot__message-avatar--round' : ''} ${avatarClassName ? avatarClassName : ''}`, src: avatar, alt: "" }, avatarProps)), _jsxs("div", { className: "pf-chatbot__message-contents", children: [_jsxs("div", { className: "pf-chatbot__message-meta", children: [name && (_jsx("span", { className: "pf-chatbot__message-name", children: _jsx(Truncate, { content: name }) })), role === 'bot' && (_jsx(Label, { variant: "outline", isCompact: true, children: botWord })), _jsx(Timestamp, { date: date, children: timestamp })] }), _jsxs("div", { className: "pf-chatbot__message-response", children: [_jsxs("div", { className: "pf-chatbot__message-and-actions", children: [renderMessage(), afterMainContent && _jsx(_Fragment, { children: afterMainContent }), !isLoading && sources && _jsx(SourcesCard, Object.assign({}, sources, { isCompact: isCompact })), quickStarts && quickStarts.quickStart && (_jsx(QuickStartTile, { quickStart: quickStarts.quickStart, onSelectQuickStart: quickStarts.onSelectQuickStart, minuteWord: quickStarts.minuteWord, minuteWordPlural: quickStarts.minuteWordPlural, prerequisiteWord: quickStarts.prerequisiteWord, prerequisiteWordPlural: quickStarts.prerequisiteWordPlural, quickStartButtonAriaLabel: quickStarts.quickStartButtonAriaLabel, isCompact: isCompact })), !isLoading && !isEditable && actions && _jsx(ResponseActions, { actions: actions }), userFeedbackForm && _jsx(UserFeedback, Object.assign({}, userFeedbackForm, { timestamp: dateString, isCompact: isCompact })), userFeedbackComplete && (_jsx(UserFeedbackComplete, Object.assign({}, userFeedbackComplete, { timestamp: dateString, isCompact: isCompact }))), !isLoading && quickResponses && (_jsx(QuickResponse, { quickResponses: quickResponses, quickResponseContainerProps: quickResponseContainerProps, isCompact: isCompact }))] }), attachments && (_jsx("div", { className: "pf-chatbot__message-attachments-container", children: attachments.map((attachment) => {
|
|
112
118
|
var _a;
|
|
@@ -734,4 +734,15 @@ describe('Message', () => {
|
|
|
734
734
|
const form = container.querySelector('form');
|
|
735
735
|
expect(form).toHaveClass('test');
|
|
736
736
|
});
|
|
737
|
+
it('should be able to disable markdown parsing', () => {
|
|
738
|
+
render(_jsx(Message, { avatar: "./img", role: "user", name: "User", content: CODE_MESSAGE, isMarkdownDisabled: true }));
|
|
739
|
+
// this is looking for markdown syntax that is ordinarily stripped
|
|
740
|
+
expect(screen.getByText(/~~~yaml/i)).toBeTruthy();
|
|
741
|
+
});
|
|
742
|
+
it('should be able to pass props to react-markdown, such as disabling tags', () => {
|
|
743
|
+
render(_jsx(Message, { avatar: "./img", role: "user", name: "User", content: CODE_MESSAGE, reactMarkdownProps: { disallowedElements: ['code'] } }));
|
|
744
|
+
expect(screen.getByText('Here is some YAML code:')).toBeTruthy();
|
|
745
|
+
// code block isn't rendering
|
|
746
|
+
expect(screen.queryByRole('button', { name: 'Copy code' })).toBeFalsy();
|
|
747
|
+
});
|
|
737
748
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"root":["../src/index.ts","../src/AttachMenu/AttachMenu.tsx","../src/AttachMenu/index.ts","../src/AttachmentEdit/AttachmentEdit.test.tsx","../src/AttachmentEdit/AttachmentEdit.tsx","../src/AttachmentEdit/index.ts","../src/Chatbot/Chatbot.test.tsx","../src/Chatbot/Chatbot.tsx","../src/Chatbot/index.ts","../src/ChatbotAlert/ChatbotAlert.test.tsx","../src/ChatbotAlert/ChatbotAlert.tsx","../src/ChatbotAlert/index.ts","../src/ChatbotContent/ChatbotContent.test.tsx","../src/ChatbotContent/ChatbotContent.tsx","../src/ChatbotContent/index.ts","../src/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.test.tsx","../src/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.tsx","../src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.test.tsx","../src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.tsx","../src/ChatbotConversationHistoryNav/EmptyState.tsx","../src/ChatbotConversationHistoryNav/LoadingState.tsx","../src/ChatbotConversationHistoryNav/index.ts","../src/ChatbotFooter/ChatbotFooter.test.tsx","../src/ChatbotFooter/ChatbotFooter.tsx","../src/ChatbotFooter/ChatbotFooternote.test.tsx","../src/ChatbotFooter/ChatbotFootnote.tsx","../src/ChatbotFooter/index.ts","../src/ChatbotHeader/ChatbotHeader.test.tsx","../src/ChatbotHeader/ChatbotHeader.tsx","../src/ChatbotHeader/ChatbotHeaderActions.test.tsx","../src/ChatbotHeader/ChatbotHeaderActions.tsx","../src/ChatbotHeader/ChatbotHeaderCloseButton.test.tsx","../src/ChatbotHeader/ChatbotHeaderCloseButton.tsx","../src/ChatbotHeader/ChatbotHeaderMain.test.tsx","../src/ChatbotHeader/ChatbotHeaderMain.tsx","../src/ChatbotHeader/ChatbotHeaderMenu.test.tsx","../src/ChatbotHeader/ChatbotHeaderMenu.tsx","../src/ChatbotHeader/ChatbotHeaderOptionsDropdown.test.tsx","../src/ChatbotHeader/ChatbotHeaderOptionsDropdown.tsx","../src/ChatbotHeader/ChatbotHeaderSelectorDropdown.test.tsx","../src/ChatbotHeader/ChatbotHeaderSelectorDropdown.tsx","../src/ChatbotHeader/ChatbotHeaderTitle.test.tsx","../src/ChatbotHeader/ChatbotHeaderTitle.tsx","../src/ChatbotHeader/index.ts","../src/ChatbotModal/ChatbotModal.test.tsx","../src/ChatbotModal/ChatbotModal.tsx","../src/ChatbotModal/index.ts","../src/ChatbotPopover/ChatbotPopover.tsx","../src/ChatbotPopover/index.ts","../src/ChatbotToggle/ChatbotToggle.test.tsx","../src/ChatbotToggle/ChatbotToggle.tsx","../src/ChatbotToggle/index.ts","../src/ChatbotWelcomePrompt/ChatbotWelcomePrompt.test.tsx","../src/ChatbotWelcomePrompt/ChatbotWelcomePrompt.tsx","../src/ChatbotWelcomePrompt/index.ts","../src/CodeModal/CodeModal.test.tsx","../src/CodeModal/CodeModal.tsx","../src/CodeModal/index.ts","../src/Compare/Compare.test.tsx","../src/Compare/Compare.tsx","../src/Compare/index.ts","../src/FileDetails/FileDetails.test.tsx","../src/FileDetails/FileDetails.tsx","../src/FileDetails/index.ts","../src/FileDetailsLabel/FileDetailsLabel.test.tsx","../src/FileDetailsLabel/FileDetailsLabel.tsx","../src/FileDetailsLabel/index.ts","../src/FileDropZone/FileDropZone.test.tsx","../src/FileDropZone/FileDropZone.tsx","../src/FileDropZone/index.ts","../src/LoadingMessage/LoadingMessage.test.tsx","../src/LoadingMessage/LoadingMessage.tsx","../src/LoadingMessage/index.ts","../src/Message/Message.test.tsx","../src/Message/Message.tsx","../src/Message/MessageInput.tsx","../src/Message/MessageLoading.tsx","../src/Message/index.ts","../src/Message/CodeBlockMessage/CodeBlockMessage.tsx","../src/Message/ErrorMessage/ErrorMessage.tsx","../src/Message/ImageMessage/ImageMessage.tsx","../src/Message/LinkMessage/LinkMessage.tsx","../src/Message/ListMessage/ListItemMessage.tsx","../src/Message/ListMessage/OrderedListMessage.tsx","../src/Message/ListMessage/UnorderedListMessage.tsx","../src/Message/Plugins/index.ts","../src/Message/Plugins/rehypeCodeBlockToggle.ts","../src/Message/Plugins/rehypeMoveImagesOutOfParagraphs.ts","../src/Message/QuickResponse/QuickResponse.tsx","../src/Message/QuickStarts/FallbackImg.tsx","../src/Message/QuickStarts/QuickStartTile.tsx","../src/Message/QuickStarts/QuickStartTileDescription.test.tsx","../src/Message/QuickStarts/QuickStartTileDescription.tsx","../src/Message/QuickStarts/QuickStartTileHeader.tsx","../src/Message/QuickStarts/monitor-sampleapp-quickstart-with-image.ts","../src/Message/QuickStarts/monitor-sampleapp-quickstart.ts","../src/Message/QuickStarts/types.ts","../src/Message/TableMessage/TableMessage.tsx","../src/Message/TableMessage/TbodyMessage.tsx","../src/Message/TableMessage/TdMessage.tsx","../src/Message/TableMessage/ThMessage.tsx","../src/Message/TableMessage/TheadMessage.tsx","../src/Message/TableMessage/TrMessage.tsx","../src/Message/TextMessage/TextMessage.tsx","../src/Message/UserFeedback/CloseButton.tsx","../src/Message/UserFeedback/UserFeedback.test.tsx","../src/Message/UserFeedback/UserFeedback.tsx","../src/Message/UserFeedback/UserFeedbackComplete.test.tsx","../src/Message/UserFeedback/UserFeedbackComplete.tsx","../src/MessageBar/AttachButton.test.tsx","../src/MessageBar/AttachButton.tsx","../src/MessageBar/MessageBar.test.tsx","../src/MessageBar/MessageBar.tsx","../src/MessageBar/MicrophoneButton.tsx","../src/MessageBar/SendButton.test.tsx","../src/MessageBar/SendButton.tsx","../src/MessageBar/StopButton.test.tsx","../src/MessageBar/StopButton.tsx","../src/MessageBar/index.ts","../src/MessageBox/JumpButton.test.tsx","../src/MessageBox/JumpButton.tsx","../src/MessageBox/MessageBox.test.tsx","../src/MessageBox/MessageBox.tsx","../src/MessageBox/index.ts","../src/MessageDivider/MessageDivider.test.tsx","../src/MessageDivider/MessageDivider.tsx","../src/MessageDivider/index.ts","../src/PreviewAttachment/PreviewAttachment.test.tsx","../src/PreviewAttachment/PreviewAttachment.tsx","../src/PreviewAttachment/index.ts","../src/ResponseActions/ResponseActionButton.test.tsx","../src/ResponseActions/ResponseActionButton.tsx","../src/ResponseActions/ResponseActions.test.tsx","../src/ResponseActions/ResponseActions.tsx","../src/ResponseActions/index.ts","../src/Settings/SettingsForm.test.tsx","../src/Settings/SettingsForm.tsx","../src/Settings/index.ts","../src/SourceDetailsMenuItem/SourceDetailsMenuItem.tsx","../src/SourceDetailsMenuItem/index.ts","../src/SourcesCard/SourcesCard.test.tsx","../src/SourcesCard/SourcesCard.tsx","../src/SourcesCard/index.ts","../src/TermsOfUse/TermsOfUse.test.tsx","../src/TermsOfUse/TermsOfUse.tsx","../src/TermsOfUse/index.ts","../src/__mocks__/rehype-external-links.ts","../src/__mocks__/rehype-sanitize.ts","../src/__mocks__/rehype-unwrap-images.tsx","../src/tracking/console_tracking_provider.ts","../src/tracking/index.ts","../src/tracking/posthog_tracking_provider.ts","../src/tracking/segment_tracking_provider.ts","../src/tracking/trackingProviderProxy.ts","../src/tracking/tracking_api.ts","../src/tracking/tracking_registry.ts","../src/tracking/tracking_spi.ts","../src/tracking/umami_tracking_provider.ts"],"version":"5.6.3"}
|
|
1
|
+
{"root":["../src/index.ts","../src/AttachMenu/AttachMenu.tsx","../src/AttachMenu/index.ts","../src/AttachmentEdit/AttachmentEdit.test.tsx","../src/AttachmentEdit/AttachmentEdit.tsx","../src/AttachmentEdit/index.ts","../src/Chatbot/Chatbot.test.tsx","../src/Chatbot/Chatbot.tsx","../src/Chatbot/index.ts","../src/ChatbotAlert/ChatbotAlert.test.tsx","../src/ChatbotAlert/ChatbotAlert.tsx","../src/ChatbotAlert/index.ts","../src/ChatbotContent/ChatbotContent.test.tsx","../src/ChatbotContent/ChatbotContent.tsx","../src/ChatbotContent/index.ts","../src/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.test.tsx","../src/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.tsx","../src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.test.tsx","../src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.tsx","../src/ChatbotConversationHistoryNav/EmptyState.tsx","../src/ChatbotConversationHistoryNav/LoadingState.tsx","../src/ChatbotConversationHistoryNav/index.ts","../src/ChatbotFooter/ChatbotFooter.test.tsx","../src/ChatbotFooter/ChatbotFooter.tsx","../src/ChatbotFooter/ChatbotFooternote.test.tsx","../src/ChatbotFooter/ChatbotFootnote.tsx","../src/ChatbotFooter/index.ts","../src/ChatbotHeader/ChatbotHeader.test.tsx","../src/ChatbotHeader/ChatbotHeader.tsx","../src/ChatbotHeader/ChatbotHeaderActions.test.tsx","../src/ChatbotHeader/ChatbotHeaderActions.tsx","../src/ChatbotHeader/ChatbotHeaderCloseButton.test.tsx","../src/ChatbotHeader/ChatbotHeaderCloseButton.tsx","../src/ChatbotHeader/ChatbotHeaderMain.test.tsx","../src/ChatbotHeader/ChatbotHeaderMain.tsx","../src/ChatbotHeader/ChatbotHeaderMenu.test.tsx","../src/ChatbotHeader/ChatbotHeaderMenu.tsx","../src/ChatbotHeader/ChatbotHeaderNewChatButton.test.tsx","../src/ChatbotHeader/ChatbotHeaderNewChatButton.tsx","../src/ChatbotHeader/ChatbotHeaderOptionsDropdown.test.tsx","../src/ChatbotHeader/ChatbotHeaderOptionsDropdown.tsx","../src/ChatbotHeader/ChatbotHeaderSelectorDropdown.test.tsx","../src/ChatbotHeader/ChatbotHeaderSelectorDropdown.tsx","../src/ChatbotHeader/ChatbotHeaderTitle.test.tsx","../src/ChatbotHeader/ChatbotHeaderTitle.tsx","../src/ChatbotHeader/index.ts","../src/ChatbotModal/ChatbotModal.test.tsx","../src/ChatbotModal/ChatbotModal.tsx","../src/ChatbotModal/index.ts","../src/ChatbotPopover/ChatbotPopover.tsx","../src/ChatbotPopover/index.ts","../src/ChatbotToggle/ChatbotToggle.test.tsx","../src/ChatbotToggle/ChatbotToggle.tsx","../src/ChatbotToggle/index.ts","../src/ChatbotWelcomePrompt/ChatbotWelcomePrompt.test.tsx","../src/ChatbotWelcomePrompt/ChatbotWelcomePrompt.tsx","../src/ChatbotWelcomePrompt/index.ts","../src/CodeModal/CodeModal.test.tsx","../src/CodeModal/CodeModal.tsx","../src/CodeModal/index.ts","../src/Compare/Compare.test.tsx","../src/Compare/Compare.tsx","../src/Compare/index.ts","../src/FileDetails/FileDetails.test.tsx","../src/FileDetails/FileDetails.tsx","../src/FileDetails/index.ts","../src/FileDetailsLabel/FileDetailsLabel.test.tsx","../src/FileDetailsLabel/FileDetailsLabel.tsx","../src/FileDetailsLabel/index.ts","../src/FileDropZone/FileDropZone.test.tsx","../src/FileDropZone/FileDropZone.tsx","../src/FileDropZone/index.ts","../src/LoadingMessage/LoadingMessage.test.tsx","../src/LoadingMessage/LoadingMessage.tsx","../src/LoadingMessage/index.ts","../src/Message/Message.test.tsx","../src/Message/Message.tsx","../src/Message/MessageInput.tsx","../src/Message/MessageLoading.tsx","../src/Message/index.ts","../src/Message/CodeBlockMessage/CodeBlockMessage.tsx","../src/Message/ErrorMessage/ErrorMessage.tsx","../src/Message/ImageMessage/ImageMessage.tsx","../src/Message/LinkMessage/LinkMessage.tsx","../src/Message/ListMessage/ListItemMessage.tsx","../src/Message/ListMessage/OrderedListMessage.tsx","../src/Message/ListMessage/UnorderedListMessage.tsx","../src/Message/Plugins/index.ts","../src/Message/Plugins/rehypeCodeBlockToggle.ts","../src/Message/Plugins/rehypeMoveImagesOutOfParagraphs.ts","../src/Message/QuickResponse/QuickResponse.tsx","../src/Message/QuickStarts/FallbackImg.tsx","../src/Message/QuickStarts/QuickStartTile.tsx","../src/Message/QuickStarts/QuickStartTileDescription.test.tsx","../src/Message/QuickStarts/QuickStartTileDescription.tsx","../src/Message/QuickStarts/QuickStartTileHeader.tsx","../src/Message/QuickStarts/monitor-sampleapp-quickstart-with-image.ts","../src/Message/QuickStarts/monitor-sampleapp-quickstart.ts","../src/Message/QuickStarts/types.ts","../src/Message/TableMessage/TableMessage.tsx","../src/Message/TableMessage/TbodyMessage.tsx","../src/Message/TableMessage/TdMessage.tsx","../src/Message/TableMessage/ThMessage.tsx","../src/Message/TableMessage/TheadMessage.tsx","../src/Message/TableMessage/TrMessage.tsx","../src/Message/TextMessage/TextMessage.tsx","../src/Message/UserFeedback/CloseButton.tsx","../src/Message/UserFeedback/UserFeedback.test.tsx","../src/Message/UserFeedback/UserFeedback.tsx","../src/Message/UserFeedback/UserFeedbackComplete.test.tsx","../src/Message/UserFeedback/UserFeedbackComplete.tsx","../src/MessageBar/AttachButton.test.tsx","../src/MessageBar/AttachButton.tsx","../src/MessageBar/MessageBar.test.tsx","../src/MessageBar/MessageBar.tsx","../src/MessageBar/MicrophoneButton.tsx","../src/MessageBar/SendButton.test.tsx","../src/MessageBar/SendButton.tsx","../src/MessageBar/StopButton.test.tsx","../src/MessageBar/StopButton.tsx","../src/MessageBar/index.ts","../src/MessageBox/JumpButton.test.tsx","../src/MessageBox/JumpButton.tsx","../src/MessageBox/MessageBox.test.tsx","../src/MessageBox/MessageBox.tsx","../src/MessageBox/index.ts","../src/MessageDivider/MessageDivider.test.tsx","../src/MessageDivider/MessageDivider.tsx","../src/MessageDivider/index.ts","../src/PreviewAttachment/PreviewAttachment.test.tsx","../src/PreviewAttachment/PreviewAttachment.tsx","../src/PreviewAttachment/index.ts","../src/ResponseActions/ResponseActionButton.test.tsx","../src/ResponseActions/ResponseActionButton.tsx","../src/ResponseActions/ResponseActions.test.tsx","../src/ResponseActions/ResponseActions.tsx","../src/ResponseActions/index.ts","../src/Settings/SettingsForm.test.tsx","../src/Settings/SettingsForm.tsx","../src/Settings/index.ts","../src/SourceDetailsMenuItem/SourceDetailsMenuItem.tsx","../src/SourceDetailsMenuItem/index.ts","../src/SourcesCard/SourcesCard.test.tsx","../src/SourcesCard/SourcesCard.tsx","../src/SourcesCard/index.ts","../src/TermsOfUse/TermsOfUse.test.tsx","../src/TermsOfUse/TermsOfUse.tsx","../src/TermsOfUse/index.ts","../src/__mocks__/rehype-external-links.ts","../src/__mocks__/rehype-sanitize.ts","../src/__mocks__/rehype-unwrap-images.tsx","../src/tracking/console_tracking_provider.ts","../src/tracking/index.ts","../src/tracking/posthog_tracking_provider.ts","../src/tracking/segment_tracking_provider.ts","../src/tracking/trackingProviderProxy.ts","../src/tracking/tracking_api.ts","../src/tracking/tracking_registry.ts","../src/tracking/tracking_spi.ts","../src/tracking/umami_tracking_provider.ts"],"version":"5.6.3"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@patternfly/chatbot",
|
|
3
|
-
"version": "6.4.0-prerelease.
|
|
3
|
+
"version": "6.4.0-prerelease.4",
|
|
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",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"react-dom": "^17 || ^18 || ^19"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
|
-
"@patternfly/documentation-framework": "6.
|
|
55
|
+
"@patternfly/documentation-framework": "6.16.0",
|
|
56
56
|
"@patternfly/patternfly": "^6.1.0",
|
|
57
57
|
"@patternfly/patternfly-a11y": "^5.0.0",
|
|
58
58
|
"@types/dom-speech-recognition": "^0.0.4",
|
|
@@ -17,7 +17,8 @@ import {
|
|
|
17
17
|
ChatbotHeaderActions,
|
|
18
18
|
ChatbotHeaderTitle,
|
|
19
19
|
ChatbotHeaderOptionsDropdown,
|
|
20
|
-
ChatbotHeaderSelectorDropdown
|
|
20
|
+
ChatbotHeaderSelectorDropdown,
|
|
21
|
+
ChatbotHeaderNewChatButton
|
|
21
22
|
} from '@patternfly/chatbot/dist/dynamic/ChatbotHeader';
|
|
22
23
|
import { ChatbotDisplayMode } from '@patternfly/chatbot/dist/dynamic/Chatbot';
|
|
23
24
|
import OutlinedWindowRestoreIcon from '@patternfly/react-icons/dist/esm/icons/outlined-window-restore-icon';
|
|
@@ -31,6 +32,7 @@ export const BasicDemo: FunctionComponent = () => {
|
|
|
31
32
|
const [selectedModel, setSelectedModel] = useState('Granite Code 7B');
|
|
32
33
|
const [showAll, setShowAll] = useState<boolean>(true);
|
|
33
34
|
const [showMenu, setShowMenu] = useState<boolean>(true);
|
|
35
|
+
const [showNewChatButton, setShowNewChatButton] = useState<boolean>(true);
|
|
34
36
|
const [showLogo, setShowLogo] = useState<boolean>(false);
|
|
35
37
|
const [showCenteredLogo, setShowCenteredLogo] = useState<boolean>(true);
|
|
36
38
|
const [showSelectorDropdown, setShowSelectorDropdown] = useState<boolean>(true);
|
|
@@ -58,9 +60,10 @@ export const BasicDemo: FunctionComponent = () => {
|
|
|
58
60
|
<Stack hasGutter>
|
|
59
61
|
<FormGroup role="radiogroup" isInline fieldId="header-variant-form-radio-group" label="Variant">
|
|
60
62
|
<Checkbox
|
|
61
|
-
isChecked={showMenu && showCenteredLogo && showSelectorDropdown && showOptionsDropdown}
|
|
63
|
+
isChecked={showMenu && showNewChatButton && showCenteredLogo && showSelectorDropdown && showOptionsDropdown}
|
|
62
64
|
onChange={() => {
|
|
63
65
|
setShowMenu(true);
|
|
66
|
+
setShowNewChatButton(true);
|
|
64
67
|
setShowCenteredLogo(true);
|
|
65
68
|
setShowSelectorDropdown(true);
|
|
66
69
|
setShowOptionsDropdown(true);
|
|
@@ -80,6 +83,16 @@ export const BasicDemo: FunctionComponent = () => {
|
|
|
80
83
|
label="With menu"
|
|
81
84
|
id="menu"
|
|
82
85
|
/>
|
|
86
|
+
<Checkbox
|
|
87
|
+
isChecked={showNewChatButton}
|
|
88
|
+
onChange={() => {
|
|
89
|
+
setShowNewChatButton(!showNewChatButton);
|
|
90
|
+
showAll && setShowAll(!showAll);
|
|
91
|
+
}}
|
|
92
|
+
name="basic-inline-radio"
|
|
93
|
+
label="With new chat button"
|
|
94
|
+
id="new-chat-button"
|
|
95
|
+
/>
|
|
83
96
|
<Checkbox
|
|
84
97
|
isChecked={showLogo}
|
|
85
98
|
onChange={() => {
|
|
@@ -124,9 +137,10 @@ export const BasicDemo: FunctionComponent = () => {
|
|
|
124
137
|
</FormGroup>
|
|
125
138
|
|
|
126
139
|
<ChatbotHeader>
|
|
127
|
-
{(showMenu || showLogo || showCenteredLogo) && (
|
|
140
|
+
{(showMenu || showNewChatButton || showLogo || showCenteredLogo) && (
|
|
128
141
|
<ChatbotHeaderMain>
|
|
129
142
|
{showMenu && <ChatbotHeaderMenu onMenuToggle={() => alert('Menu toggle clicked')} />}
|
|
143
|
+
{showNewChatButton && <ChatbotHeaderNewChatButton onClick={() => alert('New chat button clicked')} />}
|
|
130
144
|
{(showLogo || showCenteredLogo) && (
|
|
131
145
|
<ChatbotHeaderTitle>{showCenteredLogo ? <Bullseye>{title}</Bullseye> : title}</ChatbotHeaderTitle>
|
|
132
146
|
)}
|
|
@@ -61,7 +61,8 @@ ChatbotHeaderMenu,
|
|
|
61
61
|
ChatbotHeaderActions,
|
|
62
62
|
ChatbotHeaderTitle,
|
|
63
63
|
ChatbotHeaderOptionsDropdown,
|
|
64
|
-
ChatbotHeaderSelectorDropdown
|
|
64
|
+
ChatbotHeaderSelectorDropdown,
|
|
65
|
+
ChatbotHeaderNewChatButton
|
|
65
66
|
} from '@patternfly/chatbot/dist/dynamic/ChatbotHeader';
|
|
66
67
|
import { ChatbotFooter, ChatbotFootnote } from '@patternfly/chatbot/dist/dynamic/ChatbotFooter';
|
|
67
68
|
import { MessageBar } from '@patternfly/chatbot/dist/dynamic/MessageBar';
|
|
@@ -78,6 +79,7 @@ import OutlinedWindowRestoreIcon from '@patternfly/react-icons/dist/esm/icons/ou
|
|
|
78
79
|
import ExpandIcon from '@patternfly/react-icons/dist/esm/icons/expand-icon';
|
|
79
80
|
import OpenDrawerRightIcon from '@patternfly/react-icons/dist/esm/icons/open-drawer-right-icon';
|
|
80
81
|
import CogIcon from '@patternfly/react-icons/dist/esm/icons/cog-icon';
|
|
82
|
+
import PenToSquareIcon from '@patternfly/react-icons/dist/esm/icons/pen-to-square-icon';
|
|
81
83
|
import PFHorizontalLogoColor from './PF-HorizontalLogo-Color.svg';
|
|
82
84
|
import PFHorizontalLogoReverse from './PF-HorizontalLogo-Reverse.svg';
|
|
83
85
|
import userAvatar from '../Messages/user_avatar.svg';
|
|
@@ -184,9 +186,10 @@ The ChatBot header is persistent, and contains the title for the ChatBot window,
|
|
|
184
186
|
|
|
185
187
|
The `<ChatbotHeader>` has 2 sections:
|
|
186
188
|
|
|
187
|
-
- `<ChatbotHeaderMain>` contains the title and an optional menu toggle:
|
|
189
|
+
- `<ChatbotHeaderMain>` contains the title and an optional menu toggle or new chat button:
|
|
188
190
|
- `<ChatbotHeaderTitle>` handles the layout and display of a title or image at different responsive sizes.
|
|
189
191
|
- `<ChatbotHeaderMenu>` (optional) is placed on the left side of the header and used to toggle a chat history menu.
|
|
192
|
+
- `<ChatbotHeaderNewChatButton>` (optional) is placed on the left side of the header and used to initiate a new chat.
|
|
190
193
|
- `<ChatbotHeaderActions>` contains any additional controls:
|
|
191
194
|
- The `<ChatbotHeaderSelectorDropdown>` component is a standard PatternFly dropdown that matches the ChatBot styles.
|
|
192
195
|
- The `<ChatbotHeaderOptionsDropdown>` component is a dropdown with a menu toggle that is intended to be used to update ChatBot settings (like the display mode).
|
|
@@ -197,6 +200,7 @@ Your `<ChatbotHeader>` code structure should look like this:
|
|
|
197
200
|
<ChatbotHeader>
|
|
198
201
|
<ChatbotHeaderMain>
|
|
199
202
|
<ChatbotHeaderMenu ... />
|
|
203
|
+
<ChatbotHeaderNewChatButton ... />
|
|
200
204
|
<ChatbotHeaderTitle ... />
|
|
201
205
|
</ChatbotHeaderMain>
|
|
202
206
|
<ChatbotHeaderActions>
|
|
@@ -221,6 +225,7 @@ There are a variety of options and customizations you can make to the header, to
|
|
|
221
225
|
In this example, select the respective checkbox to toggle these features:
|
|
222
226
|
|
|
223
227
|
- **Menu:** Users can select the menu toggle to open a menu of additional options or actions.
|
|
228
|
+
- **New chat button:** Used to start a new chat session. The header button can be used in addition to or in place of a new chat button within the [conversation history drawer](/patternfly-ai/chatbot/ui/#drawer-with-search-and-new-chat-button).
|
|
224
229
|
- **Left-aligned logo**
|
|
225
230
|
- **Centered logo**
|
|
226
231
|
- **Selector dropdown:** Users can choose from preselected options in a dropdown menu. For example, they can toggle between AI models.
|
|
@@ -45,7 +45,7 @@ ChatbotHeaderTitle,
|
|
|
45
45
|
ChatbotHeaderActions,
|
|
46
46
|
ChatbotHeaderSelectorDropdown,
|
|
47
47
|
ChatbotHeaderOptionsDropdown,
|
|
48
|
-
ChatbotHeaderCloseButton
|
|
48
|
+
ChatbotHeaderCloseButton,
|
|
49
49
|
} from '@patternfly/chatbot/dist/dynamic/ChatbotHeader';
|
|
50
50
|
|
|
51
51
|
import ExpandIcon from '@patternfly/react-icons/dist/esm/icons/expand-icon';
|
|
@@ -38,7 +38,7 @@ import {
|
|
|
38
38
|
ButtonProps
|
|
39
39
|
} from '@patternfly/react-core';
|
|
40
40
|
|
|
41
|
-
import { OutlinedClockIcon, OutlinedCommentAltIcon } from '@patternfly/react-icons';
|
|
41
|
+
import { OutlinedClockIcon, OutlinedCommentAltIcon, PenToSquareIcon } from '@patternfly/react-icons';
|
|
42
42
|
import { ChatbotDisplayMode } from '../Chatbot/Chatbot';
|
|
43
43
|
import ConversationHistoryDropdown from './ChatbotConversationHistoryDropdown';
|
|
44
44
|
import LoadingState from './LoadingState';
|
|
@@ -262,7 +262,12 @@ export const ChatbotConversationHistoryNav: FunctionComponent<ChatbotConversatio
|
|
|
262
262
|
>
|
|
263
263
|
<DrawerCloseButton onClick={onDrawerToggle} {...drawerCloseButtonProps} />
|
|
264
264
|
{onNewChat && (
|
|
265
|
-
<Button
|
|
265
|
+
<Button
|
|
266
|
+
size={isCompact ? 'sm' : undefined}
|
|
267
|
+
onClick={onNewChat}
|
|
268
|
+
icon={<PenToSquareIcon />}
|
|
269
|
+
{...newChatButtonProps}
|
|
270
|
+
>
|
|
266
271
|
{newChatButtonText}
|
|
267
272
|
</Button>
|
|
268
273
|
)}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { fireEvent, render, screen } from '@testing-library/react';
|
|
2
|
+
import { ChatbotHeaderNewChatButton } from './ChatbotHeaderNewChatButton';
|
|
3
|
+
import '@testing-library/jest-dom';
|
|
4
|
+
|
|
5
|
+
describe('ChatbotHeaderNewChatButton', () => {
|
|
6
|
+
it('should render ChatbotHeaderNewChatButton', () => {
|
|
7
|
+
const { container } = render(
|
|
8
|
+
<ChatbotHeaderNewChatButton className="custom-header-new-chat-button" onClick={jest.fn()} />
|
|
9
|
+
);
|
|
10
|
+
|
|
11
|
+
expect(container.querySelector('.custom-header-new-chat-button')).toBeTruthy();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('should call onClick handler when new chat button is pressed', () => {
|
|
15
|
+
const onClick = jest.fn();
|
|
16
|
+
render(<ChatbotHeaderNewChatButton className="custom-header-new-chat-button" onClick={onClick} />);
|
|
17
|
+
fireEvent.click(screen.getByRole('button', { name: 'New chat' }));
|
|
18
|
+
expect(onClick).toHaveBeenCalled();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('should render button with isCompact', () => {
|
|
22
|
+
render(<ChatbotHeaderNewChatButton data-testid="new-chat-button" onClick={jest.fn()} isCompact />);
|
|
23
|
+
expect(screen.getByTestId('new-chat-button')).toHaveClass('pf-m-compact');
|
|
24
|
+
});
|
|
25
|
+
});
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { Ref, FunctionComponent } from 'react';
|
|
2
|
+
import { forwardRef } from 'react';
|
|
3
|
+
|
|
4
|
+
import { Button, ButtonProps, Icon, Tooltip, TooltipProps } from '@patternfly/react-core';
|
|
5
|
+
import { PenToSquareIcon } from '@patternfly/react-icons/dist/esm/icons/pen-to-square-icon';
|
|
6
|
+
|
|
7
|
+
export interface ChatbotHeaderNewChatButtonProps extends ButtonProps {
|
|
8
|
+
/** Callback function for when button is clicked */
|
|
9
|
+
onClick: () => void;
|
|
10
|
+
/** Custom classname for the header component */
|
|
11
|
+
className?: string;
|
|
12
|
+
/** Props spread to the PF Tooltip component wrapping the display mode dropdown */
|
|
13
|
+
tooltipProps?: TooltipProps;
|
|
14
|
+
/** Aria label for menu */
|
|
15
|
+
menuAriaLabel?: string;
|
|
16
|
+
/** Ref applied to menu */
|
|
17
|
+
innerRef?: React.Ref<HTMLButtonElement>;
|
|
18
|
+
/** Content used in tooltip */
|
|
19
|
+
tooltipContent?: string;
|
|
20
|
+
/** Sets button to compact styling. */
|
|
21
|
+
isCompact?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const ChatbotHeaderNewChatButtonBase: FunctionComponent<ChatbotHeaderNewChatButtonProps> = ({
|
|
25
|
+
className,
|
|
26
|
+
onClick,
|
|
27
|
+
tooltipProps,
|
|
28
|
+
menuAriaLabel = 'New chat',
|
|
29
|
+
innerRef,
|
|
30
|
+
tooltipContent = 'New chat',
|
|
31
|
+
isCompact,
|
|
32
|
+
...props
|
|
33
|
+
}: ChatbotHeaderNewChatButtonProps) => (
|
|
34
|
+
<div className={`pf-chatbot__menu${className ? ` ${className}` : ''}`}>
|
|
35
|
+
<Tooltip
|
|
36
|
+
content={tooltipContent}
|
|
37
|
+
position="bottom"
|
|
38
|
+
// prevents VO announcements of both aria label and tooltip
|
|
39
|
+
aria="none"
|
|
40
|
+
{...tooltipProps}
|
|
41
|
+
>
|
|
42
|
+
<Button
|
|
43
|
+
className={`pf-chatbot__button--toggle-menu ${isCompact ? 'pf-m-compact' : ''}`}
|
|
44
|
+
variant="plain"
|
|
45
|
+
onClick={onClick}
|
|
46
|
+
aria-label={menuAriaLabel}
|
|
47
|
+
ref={innerRef}
|
|
48
|
+
icon={
|
|
49
|
+
<Icon size={isCompact ? 'lg' : 'xl'} isInline>
|
|
50
|
+
<PenToSquareIcon />
|
|
51
|
+
</Icon>
|
|
52
|
+
}
|
|
53
|
+
size={isCompact ? 'sm' : undefined}
|
|
54
|
+
{...props}
|
|
55
|
+
/>
|
|
56
|
+
</Tooltip>
|
|
57
|
+
</div>
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
export const ChatbotHeaderNewChatButton = forwardRef(
|
|
61
|
+
(props: ChatbotHeaderNewChatButtonProps, ref: Ref<HTMLButtonElement>) => (
|
|
62
|
+
<ChatbotHeaderNewChatButtonBase innerRef={ref} {...props} />
|
|
63
|
+
)
|
|
64
|
+
);
|
|
@@ -48,19 +48,16 @@ exports[`FileDetails should render file details 1`] = `
|
|
|
48
48
|
<span
|
|
49
49
|
class="pf-chatbot__code-fileName"
|
|
50
50
|
>
|
|
51
|
-
<
|
|
52
|
-
|
|
51
|
+
<span
|
|
52
|
+
class="pf-v6-c-truncate"
|
|
53
|
+
tabindex="0"
|
|
53
54
|
>
|
|
54
55
|
<span
|
|
55
|
-
class="pf-v6-c-
|
|
56
|
+
class="pf-v6-c-truncate__start"
|
|
56
57
|
>
|
|
57
|
-
|
|
58
|
-
class="pf-v6-c-truncate__start"
|
|
59
|
-
>
|
|
60
|
-
test
|
|
61
|
-
</span>
|
|
58
|
+
test
|
|
62
59
|
</span>
|
|
63
|
-
</
|
|
60
|
+
</span>
|
|
64
61
|
</span>
|
|
65
62
|
</div>
|
|
66
63
|
<div
|
|
@@ -60,19 +60,16 @@ exports[`FileDetailsLabel should render file details label 1`] = `
|
|
|
60
60
|
<span
|
|
61
61
|
class="pf-chatbot__code-fileName"
|
|
62
62
|
>
|
|
63
|
-
<
|
|
64
|
-
|
|
63
|
+
<span
|
|
64
|
+
class="pf-v6-c-truncate"
|
|
65
|
+
tabindex="0"
|
|
65
66
|
>
|
|
66
67
|
<span
|
|
67
|
-
class="pf-v6-c-
|
|
68
|
+
class="pf-v6-c-truncate__start"
|
|
68
69
|
>
|
|
69
|
-
|
|
70
|
-
class="pf-v6-c-truncate__start"
|
|
71
|
-
>
|
|
72
|
-
test
|
|
73
|
-
</span>
|
|
70
|
+
test
|
|
74
71
|
</span>
|
|
75
|
-
</
|
|
72
|
+
</span>
|
|
76
73
|
</span>
|
|
77
74
|
</div>
|
|
78
75
|
<div
|
|
@@ -962,4 +962,23 @@ describe('Message', () => {
|
|
|
962
962
|
const form = container.querySelector('form');
|
|
963
963
|
expect(form).toHaveClass('test');
|
|
964
964
|
});
|
|
965
|
+
it('should be able to disable markdown parsing', () => {
|
|
966
|
+
render(<Message avatar="./img" role="user" name="User" content={CODE_MESSAGE} isMarkdownDisabled />);
|
|
967
|
+
// this is looking for markdown syntax that is ordinarily stripped
|
|
968
|
+
expect(screen.getByText(/~~~yaml/i)).toBeTruthy();
|
|
969
|
+
});
|
|
970
|
+
it('should be able to pass props to react-markdown, such as disabling tags', () => {
|
|
971
|
+
render(
|
|
972
|
+
<Message
|
|
973
|
+
avatar="./img"
|
|
974
|
+
role="user"
|
|
975
|
+
name="User"
|
|
976
|
+
content={CODE_MESSAGE}
|
|
977
|
+
reactMarkdownProps={{ disallowedElements: ['code'] }}
|
|
978
|
+
/>
|
|
979
|
+
);
|
|
980
|
+
expect(screen.getByText('Here is some YAML code:')).toBeTruthy();
|
|
981
|
+
// code block isn't rendering
|
|
982
|
+
expect(screen.queryByRole('button', { name: 'Copy code' })).toBeFalsy();
|
|
983
|
+
});
|
|
965
984
|
});
|
package/src/Message/Message.tsx
CHANGED
|
@@ -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,
|
|
@@ -185,6 +185,10 @@ export interface MessageProps extends Omit<HTMLProps<HTMLDivElement>, 'role'> {
|
|
|
185
185
|
editFormProps?: FormProps;
|
|
186
186
|
/** Sets message to compact styling. */
|
|
187
187
|
isCompact?: boolean;
|
|
188
|
+
/** Disables markdown parsing for message, allowing only text input */
|
|
189
|
+
isMarkdownDisabled?: boolean;
|
|
190
|
+
/** 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 */
|
|
191
|
+
reactMarkdownProps?: Options;
|
|
188
192
|
}
|
|
189
193
|
|
|
190
194
|
export const MessageBase: FunctionComponent<MessageProps> = ({
|
|
@@ -224,6 +228,8 @@ export const MessageBase: FunctionComponent<MessageProps> = ({
|
|
|
224
228
|
inputRef,
|
|
225
229
|
editFormProps,
|
|
226
230
|
isCompact,
|
|
231
|
+
isMarkdownDisabled,
|
|
232
|
+
reactMarkdownProps,
|
|
227
233
|
...props
|
|
228
234
|
}: MessageProps) => {
|
|
229
235
|
const [messageText, setMessageText] = useState(content);
|
|
@@ -250,6 +256,60 @@ export const MessageBase: FunctionComponent<MessageProps> = ({
|
|
|
250
256
|
const date = new Date();
|
|
251
257
|
const dateString = timestamp ?? `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`;
|
|
252
258
|
|
|
259
|
+
const handleMarkdown = () => {
|
|
260
|
+
if (isMarkdownDisabled) {
|
|
261
|
+
return (
|
|
262
|
+
<TextMessage component={ContentVariants.p} {...props}>
|
|
263
|
+
{messageText}
|
|
264
|
+
</TextMessage>
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
return (
|
|
268
|
+
<Markdown
|
|
269
|
+
components={{
|
|
270
|
+
p: (props) => <TextMessage component={ContentVariants.p} {...props} />,
|
|
271
|
+
code: ({ children, ...props }) => (
|
|
272
|
+
<CodeBlockMessage {...props} {...codeBlockProps}>
|
|
273
|
+
{children}
|
|
274
|
+
</CodeBlockMessage>
|
|
275
|
+
),
|
|
276
|
+
h1: (props) => <TextMessage component={ContentVariants.h1} {...props} />,
|
|
277
|
+
h2: (props) => <TextMessage component={ContentVariants.h2} {...props} />,
|
|
278
|
+
h3: (props) => <TextMessage component={ContentVariants.h3} {...props} />,
|
|
279
|
+
h4: (props) => <TextMessage component={ContentVariants.h4} {...props} />,
|
|
280
|
+
h5: (props) => <TextMessage component={ContentVariants.h5} {...props} />,
|
|
281
|
+
h6: (props) => <TextMessage component={ContentVariants.h6} {...props} />,
|
|
282
|
+
blockquote: (props) => <TextMessage component={ContentVariants.blockquote} {...props} />,
|
|
283
|
+
ul: (props) => <UnorderedListMessage {...props} />,
|
|
284
|
+
ol: (props) => <OrderedListMessage {...props} />,
|
|
285
|
+
li: (props) => <ListItemMessage {...props} />,
|
|
286
|
+
table: (props) => <TableMessage {...props} {...tableProps} />,
|
|
287
|
+
tbody: (props) => <TbodyMessage {...props} />,
|
|
288
|
+
thead: (props) => <TheadMessage {...props} />,
|
|
289
|
+
tr: (props) => <TrMessage {...props} />,
|
|
290
|
+
td: (props) => {
|
|
291
|
+
// Conflicts with Td type
|
|
292
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
293
|
+
const { width, ...rest } = props;
|
|
294
|
+
return <TdMessage {...rest} />;
|
|
295
|
+
},
|
|
296
|
+
th: (props) => <ThMessage {...props} />,
|
|
297
|
+
img: (props) => <ImageMessage {...props} />,
|
|
298
|
+
a: (props) => (
|
|
299
|
+
<LinkMessage href={props.href} rel={props.rel} target={props.target} {...linkProps}>
|
|
300
|
+
{props.children}
|
|
301
|
+
</LinkMessage>
|
|
302
|
+
)
|
|
303
|
+
}}
|
|
304
|
+
remarkPlugins={[remarkGfm]}
|
|
305
|
+
rehypePlugins={rehypePlugins}
|
|
306
|
+
{...reactMarkdownProps}
|
|
307
|
+
>
|
|
308
|
+
{messageText}
|
|
309
|
+
</Markdown>
|
|
310
|
+
);
|
|
311
|
+
};
|
|
312
|
+
|
|
253
313
|
const renderMessage = () => {
|
|
254
314
|
if (isLoading) {
|
|
255
315
|
return <MessageLoading loadingWord={loadingWord} />;
|
|
@@ -277,51 +337,7 @@ export const MessageBase: FunctionComponent<MessageProps> = ({
|
|
|
277
337
|
return (
|
|
278
338
|
<>
|
|
279
339
|
{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
|
-
)}
|
|
340
|
+
{error ? <ErrorMessage {...error} /> : handleMarkdown()}
|
|
325
341
|
</>
|
|
326
342
|
);
|
|
327
343
|
};
|