@patternfly/chatbot 6.4.0-prerelease.20 → 6.4.0-prerelease.22
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/FileDetails/FileDetails.d.ts +22 -3
- package/dist/cjs/FileDetails/FileDetails.js +27 -912
- package/dist/cjs/FileDetails/FileDetails.test.js +16 -0
- package/dist/cjs/FileDetailsLabel/FileDetailsLabel.d.ts +8 -2
- package/dist/cjs/FileDetailsLabel/FileDetailsLabel.js +14 -2
- package/dist/cjs/FileDetailsLabel/FileDetailsLabel.test.js +19 -1
- package/dist/cjs/ImagePreview/ImagePreview.d.ts +53 -0
- package/dist/cjs/ImagePreview/ImagePreview.js +47 -0
- package/dist/cjs/ImagePreview/ImagePreview.test.d.ts +1 -0
- package/dist/cjs/ImagePreview/ImagePreview.test.js +225 -0
- package/dist/cjs/ImagePreview/index.d.ts +2 -0
- package/dist/cjs/ImagePreview/index.js +23 -0
- package/dist/cjs/Message/Message.d.ts +3 -0
- package/dist/cjs/Message/Message.js +3 -2
- package/dist/cjs/MessageBox/MessageBox.js +1 -1
- package/dist/cjs/ToolCall/ToolCall.d.ts +44 -0
- package/dist/cjs/ToolCall/ToolCall.js +14 -0
- package/dist/cjs/ToolCall/ToolCall.test.d.ts +1 -0
- package/dist/cjs/ToolCall/ToolCall.test.js +144 -0
- package/dist/cjs/ToolCall/index.d.ts +2 -0
- package/dist/cjs/ToolCall/index.js +23 -0
- package/dist/cjs/index.d.ts +4 -0
- package/dist/cjs/index.js +7 -1
- package/dist/css/main.css +104 -19
- package/dist/css/main.css.map +1 -1
- package/dist/dynamic/ImagePreview/package.json +1 -0
- package/dist/dynamic/ToolCall/package.json +1 -0
- package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.js +1 -1
- package/dist/esm/FileDetails/FileDetails.d.ts +22 -3
- package/dist/esm/FileDetails/FileDetails.js +27 -912
- package/dist/esm/FileDetails/FileDetails.test.js +16 -0
- package/dist/esm/FileDetailsLabel/FileDetailsLabel.d.ts +8 -2
- package/dist/esm/FileDetailsLabel/FileDetailsLabel.js +14 -2
- package/dist/esm/FileDetailsLabel/FileDetailsLabel.test.js +19 -1
- package/dist/esm/ImagePreview/ImagePreview.d.ts +53 -0
- package/dist/esm/ImagePreview/ImagePreview.js +42 -0
- package/dist/esm/ImagePreview/ImagePreview.test.d.ts +1 -0
- package/dist/esm/ImagePreview/ImagePreview.test.js +220 -0
- package/dist/esm/ImagePreview/index.d.ts +2 -0
- package/dist/esm/ImagePreview/index.js +2 -0
- package/dist/esm/Message/Message.d.ts +3 -0
- package/dist/esm/Message/Message.js +3 -2
- package/dist/esm/MessageBox/MessageBox.js +1 -1
- package/dist/esm/ToolCall/ToolCall.d.ts +44 -0
- package/dist/esm/ToolCall/ToolCall.js +10 -0
- package/dist/esm/ToolCall/ToolCall.test.d.ts +1 -0
- package/dist/esm/ToolCall/ToolCall.test.js +139 -0
- package/dist/esm/ToolCall/index.d.ts +2 -0
- package/dist/esm/ToolCall/index.js +2 -0
- package/dist/esm/index.d.ts +4 -0
- package/dist/esm/index.js +4 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/patternfly-docs/content/extensions/chatbot/examples/Messages/AttachmentEdit.tsx +1 -1
- package/patternfly-docs/content/extensions/chatbot/examples/Messages/ImagePreview.tsx +53 -0
- package/patternfly-docs/content/extensions/chatbot/examples/Messages/MessageWithToolCall.tsx +45 -0
- package/patternfly-docs/content/extensions/chatbot/examples/Messages/Messages.md +21 -1
- package/patternfly-docs/content/extensions/chatbot/examples/Messages/PreviewAttachment.tsx +1 -1
- package/patternfly-docs/content/extensions/chatbot/examples/Messages/file-preview.svg +9 -0
- package/patternfly-docs/content/extensions/chatbot/examples/demos/Chatbot.md +1 -1
- package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.scss +0 -12
- package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.tsx +1 -1
- package/src/FileDetails/FileDetails.scss +10 -0
- package/src/FileDetails/FileDetails.test.tsx +16 -0
- package/src/FileDetails/FileDetails.tsx +89 -32
- package/src/FileDetails/__snapshots__/FileDetails.test.tsx.snap +25 -16
- package/src/FileDetailsLabel/FileDetailsLabel.test.tsx +21 -1
- package/src/FileDetailsLabel/FileDetailsLabel.tsx +16 -3
- package/src/FileDetailsLabel/__snapshots__/FileDetailsLabel.test.tsx.snap +25 -16
- package/src/ImagePreview/ImagePreview.scss +61 -0
- package/src/ImagePreview/ImagePreview.test.tsx +253 -0
- package/src/ImagePreview/ImagePreview.tsx +200 -0
- package/src/ImagePreview/index.ts +3 -0
- package/src/Message/Message.tsx +5 -0
- package/src/MessageBox/MessageBox.scss +0 -12
- package/src/MessageBox/MessageBox.tsx +1 -1
- package/src/ToolCall/ToolCall.scss +37 -0
- package/src/ToolCall/ToolCall.test.tsx +184 -0
- package/src/ToolCall/ToolCall.tsx +147 -0
- package/src/ToolCall/index.ts +3 -0
- package/src/index.ts +6 -0
- package/src/main.scss +14 -0
|
@@ -15,6 +15,7 @@ describe('FileDetails', () => {
|
|
|
15
15
|
it('should render file details correctly if an extension we support is passed in', () => {
|
|
16
16
|
(0, react_1.render)((0, jsx_runtime_1.jsx)(FileDetails_1.default, { fileName: "test.txt", languageTestId: "language" }));
|
|
17
17
|
expect(react_1.screen.getByText('test')).toBeTruthy();
|
|
18
|
+
expect(react_1.screen.queryByText('test.txt')).toBeFalsy();
|
|
18
19
|
expect(react_1.screen.getByText('TEXT')).toBeTruthy();
|
|
19
20
|
expect(react_1.screen.getByTestId('language')).toBeTruthy();
|
|
20
21
|
});
|
|
@@ -23,4 +24,19 @@ describe('FileDetails', () => {
|
|
|
23
24
|
expect(react_1.screen.getByText('test')).toBeTruthy();
|
|
24
25
|
expect(react_1.screen.queryByTestId('language')).toBeFalsy();
|
|
25
26
|
});
|
|
27
|
+
it('should support image formats by rendering extension differently', () => {
|
|
28
|
+
(0, react_1.render)((0, jsx_runtime_1.jsx)(FileDetails_1.default, { fileName: "test.svg", languageTestId: "language" }));
|
|
29
|
+
expect(react_1.screen.getByText('test')).toBeTruthy();
|
|
30
|
+
expect(react_1.screen.queryByText('test.svg')).toBeFalsy();
|
|
31
|
+
expect(react_1.screen.queryByTestId('language')).toBeFalsy();
|
|
32
|
+
});
|
|
33
|
+
it('should handle truncation differently', () => {
|
|
34
|
+
(0, react_1.render)((0, jsx_runtime_1.jsx)(FileDetails_1.default, { fileName: "test.svg", languageTestId: "language", hasTruncation: false }));
|
|
35
|
+
expect(react_1.screen.getByText('test.svg')).toBeTruthy();
|
|
36
|
+
expect(react_1.screen.queryByTestId('language')).toBeFalsy();
|
|
37
|
+
});
|
|
38
|
+
it('should include file size if prop passed in', () => {
|
|
39
|
+
(0, react_1.render)((0, jsx_runtime_1.jsx)(FileDetails_1.default, { fileName: "test.joke", languageTestId: "language", fileSize: "100MB" }));
|
|
40
|
+
expect(react_1.screen.getByText('100MB')).toBeTruthy();
|
|
41
|
+
});
|
|
26
42
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { PropsWithChildren } from 'react';
|
|
2
|
-
interface FileDetailsLabelProps {
|
|
2
|
+
export interface FileDetailsLabelProps {
|
|
3
3
|
/** Name of file, including extension */
|
|
4
4
|
fileName: string;
|
|
5
5
|
/** Unique id of file */
|
|
@@ -16,6 +16,12 @@ interface FileDetailsLabelProps {
|
|
|
16
16
|
languageTestId?: string;
|
|
17
17
|
/** Custom test id for the loading spinner in the component */
|
|
18
18
|
spinnerTestId?: string;
|
|
19
|
+
/** File size */
|
|
20
|
+
fileSize?: string;
|
|
21
|
+
/** Whether to truncate file name */
|
|
22
|
+
hasTruncation?: boolean;
|
|
23
|
+
/** Icon used for close button */
|
|
24
|
+
closeButtonIcon?: React.ReactNode;
|
|
19
25
|
}
|
|
20
|
-
export declare const FileDetailsLabel: ({ fileName, fileId, isLoading, onClick, onClose, closeButtonAriaLabel, languageTestId, spinnerTestId }: PropsWithChildren<FileDetailsLabelProps>) => import("react/jsx-runtime").JSX.Element;
|
|
26
|
+
export declare const FileDetailsLabel: ({ fileName, fileId, isLoading, onClick, onClose, closeButtonAriaLabel, languageTestId, spinnerTestId, fileSize, hasTruncation, closeButtonIcon, ...props }: PropsWithChildren<FileDetailsLabelProps>) => import("react/jsx-runtime").JSX.Element;
|
|
21
27
|
export default FileDetailsLabel;
|
|
@@ -1,4 +1,15 @@
|
|
|
1
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
|
+
};
|
|
2
13
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
14
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
15
|
};
|
|
@@ -9,11 +20,12 @@ const react_core_1 = require("@patternfly/react-core");
|
|
|
9
20
|
const FileDetails_1 = __importDefault(require("../FileDetails"));
|
|
10
21
|
const react_core_2 = require("@patternfly/react-core");
|
|
11
22
|
const react_icons_1 = require("@patternfly/react-icons");
|
|
12
|
-
const FileDetailsLabel = (
|
|
23
|
+
const FileDetailsLabel = (_a) => {
|
|
24
|
+
var { fileName, fileId, isLoading, onClick, onClose, closeButtonAriaLabel, languageTestId, spinnerTestId, fileSize, hasTruncation = true, closeButtonIcon = (0, jsx_runtime_1.jsx)(react_icons_1.TimesIcon, {}) } = _a, props = __rest(_a, ["fileName", "fileId", "isLoading", "onClick", "onClose", "closeButtonAriaLabel", "languageTestId", "spinnerTestId", "fileSize", "hasTruncation", "closeButtonIcon"]);
|
|
13
25
|
const handleClose = (event) => {
|
|
14
26
|
onClose && onClose(event, fileName, fileId);
|
|
15
27
|
};
|
|
16
|
-
return ((0, jsx_runtime_1.jsx)(react_core_1.Label, Object.assign({ className: "pf-chatbot__file-label" }, (onClose && { onClose: (event) => onClose(event, fileName, fileId) }), { closeBtn: (0, jsx_runtime_1.jsx)(react_core_1.Button, { type: "button", variant: "plain", "aria-label": closeButtonAriaLabel !== null && closeButtonAriaLabel !== void 0 ? closeButtonAriaLabel : `Close ${fileName}`, icon:
|
|
28
|
+
return ((0, jsx_runtime_1.jsx)(react_core_1.Label, Object.assign({ className: "pf-chatbot__file-label" }, (onClose && { onClose: (event) => onClose(event, fileName, fileId) }), { closeBtn: (0, jsx_runtime_1.jsx)(react_core_1.Button, { type: "button", variant: "plain", "aria-label": closeButtonAriaLabel !== null && closeButtonAriaLabel !== void 0 ? closeButtonAriaLabel : `Close ${fileName}`, icon: closeButtonIcon, onClick: handleClose }) }, (onClick && { onClick: (event) => onClick(event, fileName, fileId) }), props, { children: (0, jsx_runtime_1.jsxs)("div", { className: "pf-chatbot__file-label-contents", children: [(0, jsx_runtime_1.jsx)(FileDetails_1.default, { className: isLoading ? 'pf-chatbot__file-label-loading' : undefined, fileName: fileName, languageTestId: languageTestId, fileSize: fileSize, hasTruncation: hasTruncation }), isLoading && (0, jsx_runtime_1.jsx)(react_core_2.Spinner, { "data-testid": spinnerTestId, size: "sm" })] }) })));
|
|
17
29
|
};
|
|
18
30
|
exports.FileDetailsLabel = FileDetailsLabel;
|
|
19
31
|
exports.default = exports.FileDetailsLabel;
|
|
@@ -17,6 +17,7 @@ const react_1 = require("@testing-library/react");
|
|
|
17
17
|
require("@testing-library/jest-dom");
|
|
18
18
|
const FileDetailsLabel_1 = __importDefault(require("./FileDetailsLabel"));
|
|
19
19
|
const user_event_1 = __importDefault(require("@testing-library/user-event"));
|
|
20
|
+
const react_icons_1 = require("@patternfly/react-icons");
|
|
20
21
|
describe('FileDetailsLabel', () => {
|
|
21
22
|
it('should render file details label', () => {
|
|
22
23
|
const { container } = (0, react_1.render)((0, jsx_runtime_1.jsx)(FileDetailsLabel_1.default, { fileName: "test.txt" }));
|
|
@@ -32,6 +33,19 @@ describe('FileDetailsLabel', () => {
|
|
|
32
33
|
expect(react_1.screen.getByText('test')).toBeTruthy();
|
|
33
34
|
expect(react_1.screen.queryByTestId('language')).toBeFalsy();
|
|
34
35
|
});
|
|
36
|
+
it('should pass file size down', () => {
|
|
37
|
+
(0, react_1.render)((0, jsx_runtime_1.jsx)(FileDetailsLabel_1.default, { fileName: "test.svg", fileSize: "100MB" }));
|
|
38
|
+
expect(react_1.screen.getByText('100MB')).toBeTruthy();
|
|
39
|
+
});
|
|
40
|
+
it('should pass truncation prop down as true by default', () => {
|
|
41
|
+
(0, react_1.render)((0, jsx_runtime_1.jsx)(FileDetailsLabel_1.default, { fileName: "test.svg" }));
|
|
42
|
+
expect(react_1.screen.getByText('test')).toBeTruthy();
|
|
43
|
+
expect(react_1.screen.queryByText('test.svg')).toBeFalsy();
|
|
44
|
+
});
|
|
45
|
+
it('should pass truncation prop down when false', () => {
|
|
46
|
+
(0, react_1.render)((0, jsx_runtime_1.jsx)(FileDetailsLabel_1.default, { fileName: "test.svg", hasTruncation: false }));
|
|
47
|
+
expect(react_1.screen.getByText('test.svg')).toBeTruthy();
|
|
48
|
+
});
|
|
35
49
|
it('should not show spinner by default', () => {
|
|
36
50
|
(0, react_1.render)((0, jsx_runtime_1.jsx)(FileDetailsLabel_1.default, { fileName: "test.txt", spinnerTestId: "spinner" }));
|
|
37
51
|
expect(react_1.screen.queryByTestId('spinner')).toBeFalsy();
|
|
@@ -56,6 +70,10 @@ describe('FileDetailsLabel', () => {
|
|
|
56
70
|
}));
|
|
57
71
|
it('should use closeButtonAriaLabel prop appropriately', () => {
|
|
58
72
|
(0, react_1.render)((0, jsx_runtime_1.jsx)(FileDetailsLabel_1.default, { fileName: "test.txt", onClose: jest.fn(), closeButtonAriaLabel: "Delete file" }));
|
|
59
|
-
react_1.screen.getByRole('button', { name: /Delete file/i });
|
|
73
|
+
expect(react_1.screen.getByRole('button', { name: /Delete file/i })).toBeTruthy();
|
|
74
|
+
});
|
|
75
|
+
it('should support custom close icon', () => {
|
|
76
|
+
(0, react_1.render)((0, jsx_runtime_1.jsx)(FileDetailsLabel_1.default, { fileName: "test.txt", onClose: jest.fn(), closeButtonIcon: (0, jsx_runtime_1.jsx)(react_icons_1.BellIcon, { "data-testid": "bell" }) }));
|
|
77
|
+
expect(react_1.screen.getByTestId('bell')).toBeTruthy();
|
|
60
78
|
});
|
|
61
79
|
});
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { ModalBodyProps, ModalHeaderProps } from '@patternfly/react-core';
|
|
2
|
+
import { type FunctionComponent } from 'react';
|
|
3
|
+
import { ChatbotDisplayMode } from '../Chatbot';
|
|
4
|
+
import { ChatbotModalProps } from '../ChatbotModal';
|
|
5
|
+
import { FileDetailsLabelProps } from '../FileDetailsLabel';
|
|
6
|
+
export interface ImagePreviewProps extends Omit<ChatbotModalProps, 'children'> {
|
|
7
|
+
/** Class applied to modal */
|
|
8
|
+
className?: string;
|
|
9
|
+
/** Function that handles modal toggle */
|
|
10
|
+
handleModalToggle: (event: React.MouseEvent | MouseEvent | KeyboardEvent) => void;
|
|
11
|
+
/** Whether modal is open */
|
|
12
|
+
isModalOpen: boolean;
|
|
13
|
+
/** Title of modal */
|
|
14
|
+
title?: string;
|
|
15
|
+
/** Display mode for the Chatbot parent; this influences the styles applied */
|
|
16
|
+
displayMode?: ChatbotDisplayMode;
|
|
17
|
+
/** Sets modal to compact styling. */
|
|
18
|
+
isCompact?: boolean;
|
|
19
|
+
/** Additional props passed to modal header */
|
|
20
|
+
modalHeaderProps?: ModalHeaderProps;
|
|
21
|
+
/** Additional props passed to modal body */
|
|
22
|
+
modalBodyProps?: ModalBodyProps;
|
|
23
|
+
/** Images displayed in modal */
|
|
24
|
+
images: {
|
|
25
|
+
fileName: string;
|
|
26
|
+
fileSize?: string;
|
|
27
|
+
image: React.ReactNode;
|
|
28
|
+
}[];
|
|
29
|
+
/** Flag indicating if the pagination is disabled. */
|
|
30
|
+
isDisabled?: boolean;
|
|
31
|
+
/** Accessible label for the pagination component. */
|
|
32
|
+
paginationAriaLabel?: string;
|
|
33
|
+
/** Accessible label for the button which moves to the next page. */
|
|
34
|
+
toNextPageAriaLabel?: string;
|
|
35
|
+
/** Accessible label for the button which moves to the previous page. */
|
|
36
|
+
toPreviousPageAriaLabel?: string;
|
|
37
|
+
/** Function called when user clicks to navigate to next page. */
|
|
38
|
+
onNextClick?: (event: React.SyntheticEvent<HTMLButtonElement>, page: number) => void;
|
|
39
|
+
/** Function called when user clicks to navigate to previous page. */
|
|
40
|
+
onPreviousClick?: (event: React.SyntheticEvent<HTMLButtonElement>, page: number) => void;
|
|
41
|
+
/** Function called when page is changed. */
|
|
42
|
+
onSetPage?: (event: React.MouseEvent | React.KeyboardEvent | MouseEvent, newPage: number) => void;
|
|
43
|
+
/** Callback function for when file details label close button is clicked */
|
|
44
|
+
onCloseFileDetailsLabel?: (event: React.MouseEvent, fileName: string, fileId?: string | number) => void;
|
|
45
|
+
/** Props passed to file details label */
|
|
46
|
+
fileDetailsLabelProps?: Omit<FileDetailsLabelProps, 'fileName'>;
|
|
47
|
+
/** Text shown in navigation */
|
|
48
|
+
paginationContent?: string;
|
|
49
|
+
/** Navigation progress announced to assistive devices. Should state the current page/image. */
|
|
50
|
+
screenreaderText?: string;
|
|
51
|
+
}
|
|
52
|
+
declare const ImagePreview: FunctionComponent<ImagePreviewProps>;
|
|
53
|
+
export default ImagePreview;
|
|
@@ -0,0 +1,47 @@
|
|
|
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
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
14
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
18
|
+
const react_core_1 = require("@patternfly/react-core");
|
|
19
|
+
const react_1 = require("react");
|
|
20
|
+
const Chatbot_1 = require("../Chatbot");
|
|
21
|
+
const ChatbotModal_1 = __importDefault(require("../ChatbotModal"));
|
|
22
|
+
const FileDetailsLabel_1 = __importDefault(require("../FileDetailsLabel"));
|
|
23
|
+
const react_icons_1 = require("@patternfly/react-icons");
|
|
24
|
+
const ImagePreview = (_a) => {
|
|
25
|
+
var { isModalOpen, displayMode = Chatbot_1.ChatbotDisplayMode.default, isCompact, className, handleModalToggle, title = 'Preview images', modalHeaderProps, modalBodyProps, images, isDisabled, onSetPage, onPreviousClick, toNextPageAriaLabel = 'Go to next image', toPreviousPageAriaLabel = 'Go to previous image', onNextClick, paginationAriaLabel, onCloseFileDetailsLabel, fileDetailsLabelProps, paginationContent, screenreaderText } = _a, props = __rest(_a, ["isModalOpen", "displayMode", "isCompact", "className", "handleModalToggle", "title", "modalHeaderProps", "modalBodyProps", "images", "isDisabled", "onSetPage", "onPreviousClick", "toNextPageAriaLabel", "toPreviousPageAriaLabel", "onNextClick", "paginationAriaLabel", "onCloseFileDetailsLabel", "fileDetailsLabelProps", "paginationContent", "screenreaderText"]);
|
|
26
|
+
const [page, setPage] = (0, react_1.useState)(1);
|
|
27
|
+
const paginationText = paginationContent || `${page}/${images.length}`;
|
|
28
|
+
(0, react_1.useEffect)(() => {
|
|
29
|
+
if (images.length === 0 || page > images.length) {
|
|
30
|
+
setPage(1);
|
|
31
|
+
}
|
|
32
|
+
}, [images.length, page]);
|
|
33
|
+
const handleNewPage = (_evt, newPage) => {
|
|
34
|
+
setPage(newPage);
|
|
35
|
+
onSetPage && onSetPage(_evt, newPage);
|
|
36
|
+
};
|
|
37
|
+
return ((0, jsx_runtime_1.jsxs)(ChatbotModal_1.default, Object.assign({ isOpen: isModalOpen, className: `pf-chatbot__image-preview-modal pf-chatbot__image-preview-modal--${displayMode} ${isCompact ? 'pf-m-compact' : ''} ${className ? className : ''}`, displayMode: displayMode, onClose: handleModalToggle, isCompact: isCompact }, props, { children: [(0, jsx_runtime_1.jsx)(react_core_1.ModalHeader, Object.assign({ title: title }, modalHeaderProps)), (0, jsx_runtime_1.jsx)(react_core_1.ModalBody, Object.assign({ className: "pf-chatbot__image-preview-body" }, modalBodyProps, { children: images.length > 0 && images[page - 1] && ((0, jsx_runtime_1.jsxs)(react_core_1.Stack, { hasGutter: true, className: "pf-chatbot__image-preview-stack", children: [(0, jsx_runtime_1.jsx)(react_core_1.StackItem, { children: (0, jsx_runtime_1.jsx)(FileDetailsLabel_1.default, Object.assign({ fileName: images[page - 1].fileName, fileSize: images[page - 1].fileSize, hasTruncation: false, onClose: onCloseFileDetailsLabel, closeButtonIcon: (0, jsx_runtime_1.jsx)(react_icons_1.TrashIcon, {}) }, fileDetailsLabelProps)) }), (0, jsx_runtime_1.jsx)(react_core_1.StackItem, { children: (0, jsx_runtime_1.jsx)("div", { className: "pf-chatbot__image-preview-body", children: images[page - 1].image }) })] })) })), images.length > 1 && ((0, jsx_runtime_1.jsx)(react_core_1.ModalFooter, { className: "pf-chatbot__image-preview-footer", children: (0, jsx_runtime_1.jsxs)("nav", { className: `pf-chatbot__image-preview-footer-buttons`, "aria-label": paginationAriaLabel, children: [(0, jsx_runtime_1.jsx)(react_core_1.Button, { variant: react_core_1.ButtonVariant.plain, isDisabled: isDisabled || page === 1, "data-action": "previous", onClick: (event) => {
|
|
38
|
+
const newPage = page > 1 ? page - 1 : 1;
|
|
39
|
+
handleNewPage(event, newPage);
|
|
40
|
+
onPreviousClick && onPreviousClick(event, newPage);
|
|
41
|
+
}, "aria-label": toPreviousPageAriaLabel, children: (0, jsx_runtime_1.jsx)(react_core_1.Icon, { iconSize: "lg", children: (0, jsx_runtime_1.jsx)("svg", { className: "pf-v6-svg", viewBox: "0 0 280 500", fill: "currentColor", "aria-hidden": "true", role: "img", width: "1em", height: "1em", children: (0, jsx_runtime_1.jsx)("path", { d: "M31.7 239l136-136c9.4-9.4 24.6-9.4 33.9 0l22.6 22.6c9.4 9.4 9.4 24.6 0 33.9L127.9 256l96.4 96.4c9.4 9.4 9.4 24.6 0 33.9L201.7 409c-9.4 9.4-24.6 9.4-33.9 0l-136-136c-9.5-9.4-9.5-24.6-.1-34z" }) }) }) }), (0, jsx_runtime_1.jsx)("span", { children: paginationText }), (0, jsx_runtime_1.jsx)("div", { className: "pf-chatbot-m-hidden", "aria-live": "polite", children: screenreaderText !== null && screenreaderText !== void 0 ? screenreaderText : `Image ${page} of ${images.length}` }), (0, jsx_runtime_1.jsx)(react_core_1.Button, { variant: react_core_1.ButtonVariant.plain, isDisabled: isDisabled || page === images.length, "aria-label": toNextPageAriaLabel, "data-action": "next", onClick: (event) => {
|
|
42
|
+
const newPage = page + 1 <= images.length ? page + 1 : images.length;
|
|
43
|
+
handleNewPage(event, newPage);
|
|
44
|
+
onNextClick && onNextClick(event, newPage);
|
|
45
|
+
}, children: (0, jsx_runtime_1.jsx)(react_core_1.Icon, { isInline: true, iconSize: "lg", children: (0, jsx_runtime_1.jsx)("svg", { className: "pf-v6-svg", viewBox: "0 0 180 500", fill: "currentColor", "aria-hidden": "true", role: "img", width: "1em", height: "1em", children: (0, jsx_runtime_1.jsx)("path", { d: "M224.3 273l-136 136c-9.4 9.4-24.6 9.4-33.9 0l-22.6-22.6c-9.4-9.4-9.4-24.6 0-33.9l96.4-96.4-96.4-96.4c-9.4-9.4-9.4-24.6 0-33.9L54.3 103c9.4-9.4 24.6-9.4 33.9 0l136 136c9.5 9.4 9.5 24.6.1 34z" }) }) }) })] }) }))] })));
|
|
46
|
+
};
|
|
47
|
+
exports.default = ImagePreview;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import '@testing-library/jest-dom';
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
7
|
+
const react_1 = require("@testing-library/react");
|
|
8
|
+
require("@testing-library/jest-dom");
|
|
9
|
+
const ImagePreview_1 = __importDefault(require("./ImagePreview"));
|
|
10
|
+
const Chatbot_1 = require("../Chatbot");
|
|
11
|
+
const mockImages = [
|
|
12
|
+
{
|
|
13
|
+
fileName: 'image1.jpg',
|
|
14
|
+
fileSize: '2.5 MB',
|
|
15
|
+
image: (0, jsx_runtime_1.jsx)("img", { src: "", alt: "Test image 1" })
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
fileName: 'image2.png',
|
|
19
|
+
fileSize: '1.8 MB',
|
|
20
|
+
image: (0, jsx_runtime_1.jsx)("img", { src: "", alt: "Test image 2" })
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
fileName: 'image3.gif',
|
|
24
|
+
image: (0, jsx_runtime_1.jsx)("img", { src: "", alt: "Test image 3" })
|
|
25
|
+
}
|
|
26
|
+
];
|
|
27
|
+
const defaultProps = {
|
|
28
|
+
isModalOpen: true,
|
|
29
|
+
handleModalToggle: jest.fn(),
|
|
30
|
+
images: mockImages
|
|
31
|
+
};
|
|
32
|
+
describe('ImagePreview', () => {
|
|
33
|
+
beforeEach(() => {
|
|
34
|
+
jest.clearAllMocks();
|
|
35
|
+
});
|
|
36
|
+
it('renders modal when isModalOpen is true', () => {
|
|
37
|
+
(0, react_1.render)((0, jsx_runtime_1.jsx)(ImagePreview_1.default, Object.assign({}, defaultProps)));
|
|
38
|
+
expect(react_1.screen.getByRole('dialog')).toBeInTheDocument();
|
|
39
|
+
});
|
|
40
|
+
it('does not render modal when isModalOpen is false', () => {
|
|
41
|
+
(0, react_1.render)((0, jsx_runtime_1.jsx)(ImagePreview_1.default, Object.assign({}, defaultProps, { isModalOpen: false })));
|
|
42
|
+
expect(react_1.screen.queryByRole('dialog')).not.toBeInTheDocument();
|
|
43
|
+
});
|
|
44
|
+
it('displays custom title when provided', () => {
|
|
45
|
+
const customTitle = 'Custom image preview';
|
|
46
|
+
(0, react_1.render)((0, jsx_runtime_1.jsx)(ImagePreview_1.default, Object.assign({}, defaultProps, { title: customTitle })));
|
|
47
|
+
expect(react_1.screen.getByRole('heading', { name: customTitle })).toBeInTheDocument();
|
|
48
|
+
});
|
|
49
|
+
it('displays default title when no title provided', () => {
|
|
50
|
+
(0, react_1.render)((0, jsx_runtime_1.jsx)(ImagePreview_1.default, Object.assign({}, defaultProps)));
|
|
51
|
+
expect(react_1.screen.getByRole('heading', { name: /Preview images/i })).toBeInTheDocument();
|
|
52
|
+
});
|
|
53
|
+
it('calls handleModalToggle when modal is closed', () => {
|
|
54
|
+
const mockHandleToggle = jest.fn();
|
|
55
|
+
(0, react_1.render)((0, jsx_runtime_1.jsx)(ImagePreview_1.default, Object.assign({}, defaultProps, { handleModalToggle: mockHandleToggle })));
|
|
56
|
+
const closeButton = react_1.screen.getByRole('button', { name: /close/i });
|
|
57
|
+
react_1.fireEvent.click(closeButton);
|
|
58
|
+
expect(mockHandleToggle).toHaveBeenCalledTimes(1);
|
|
59
|
+
});
|
|
60
|
+
it('displays first image by default', () => {
|
|
61
|
+
(0, react_1.render)((0, jsx_runtime_1.jsx)(ImagePreview_1.default, Object.assign({}, defaultProps)));
|
|
62
|
+
expect(react_1.screen.getByText('image1.jpg')).toBeInTheDocument();
|
|
63
|
+
expect(react_1.screen.getByText('2.5 MB')).toBeInTheDocument();
|
|
64
|
+
expect(react_1.screen.getByAltText('Test image 1')).toBeInTheDocument();
|
|
65
|
+
});
|
|
66
|
+
it('displays page counter correctly', () => {
|
|
67
|
+
(0, react_1.render)((0, jsx_runtime_1.jsx)(ImagePreview_1.default, Object.assign({}, defaultProps)));
|
|
68
|
+
expect(react_1.screen.getByText('1/3')).toBeInTheDocument();
|
|
69
|
+
});
|
|
70
|
+
it('navigates to next image when next button is clicked', () => {
|
|
71
|
+
const mockOnNextClick = jest.fn();
|
|
72
|
+
(0, react_1.render)((0, jsx_runtime_1.jsx)(ImagePreview_1.default, Object.assign({}, defaultProps, { onNextClick: mockOnNextClick })));
|
|
73
|
+
const nextButton = react_1.screen.getByRole('button', { name: /Go to next image/i });
|
|
74
|
+
react_1.fireEvent.click(nextButton);
|
|
75
|
+
expect(mockOnNextClick).toHaveBeenCalled();
|
|
76
|
+
expect(react_1.screen.getByText('2/3')).toBeInTheDocument();
|
|
77
|
+
expect(react_1.screen.getByText('image2.png')).toBeInTheDocument();
|
|
78
|
+
});
|
|
79
|
+
it('navigates to previous image when previous button is clicked', () => {
|
|
80
|
+
const mockOnPreviousClick = jest.fn();
|
|
81
|
+
(0, react_1.render)((0, jsx_runtime_1.jsx)(ImagePreview_1.default, Object.assign({}, defaultProps, { onPreviousClick: mockOnPreviousClick })));
|
|
82
|
+
// First go to page 2
|
|
83
|
+
const nextButton = react_1.screen.getByRole('button', { name: /Go to next image/i });
|
|
84
|
+
react_1.fireEvent.click(nextButton);
|
|
85
|
+
// Then go back to page 1
|
|
86
|
+
const previousButton = react_1.screen.getByRole('button', { name: /Go to previous image/i });
|
|
87
|
+
react_1.fireEvent.click(previousButton);
|
|
88
|
+
expect(mockOnPreviousClick).toHaveBeenCalled();
|
|
89
|
+
expect(react_1.screen.getByText('1/3')).toBeInTheDocument();
|
|
90
|
+
});
|
|
91
|
+
it('calls onSetPage when page changes', () => {
|
|
92
|
+
const mockOnSetPage = jest.fn();
|
|
93
|
+
(0, react_1.render)((0, jsx_runtime_1.jsx)(ImagePreview_1.default, Object.assign({}, defaultProps, { onSetPage: mockOnSetPage })));
|
|
94
|
+
const nextButton = react_1.screen.getByRole('button', { name: /Go to next image/i });
|
|
95
|
+
react_1.fireEvent.click(nextButton);
|
|
96
|
+
expect(mockOnSetPage).toHaveBeenCalledWith(expect.any(Object), 2);
|
|
97
|
+
});
|
|
98
|
+
it('disables previous button on first page', () => {
|
|
99
|
+
(0, react_1.render)((0, jsx_runtime_1.jsx)(ImagePreview_1.default, Object.assign({}, defaultProps)));
|
|
100
|
+
const previousButton = react_1.screen.getByRole('button', { name: /Go to previous image/i });
|
|
101
|
+
expect(previousButton).toBeDisabled();
|
|
102
|
+
});
|
|
103
|
+
it('disables next button on last page', () => {
|
|
104
|
+
(0, react_1.render)((0, jsx_runtime_1.jsx)(ImagePreview_1.default, Object.assign({}, defaultProps)));
|
|
105
|
+
// Navigate to last page
|
|
106
|
+
const nextButton = react_1.screen.getByRole('button', { name: /Go to next image/i });
|
|
107
|
+
react_1.fireEvent.click(nextButton); // page 2
|
|
108
|
+
react_1.fireEvent.click(nextButton); // page 3
|
|
109
|
+
expect(nextButton).toBeDisabled();
|
|
110
|
+
});
|
|
111
|
+
it('disables both navigation buttons when isDisabled is true', () => {
|
|
112
|
+
(0, react_1.render)((0, jsx_runtime_1.jsx)(ImagePreview_1.default, Object.assign({}, defaultProps, { isDisabled: true })));
|
|
113
|
+
const previousButton = react_1.screen.getByRole('button', { name: /Go to previous image/i });
|
|
114
|
+
const nextButton = react_1.screen.getByRole('button', { name: /Go to next image/i });
|
|
115
|
+
expect(previousButton).toBeDisabled();
|
|
116
|
+
expect(nextButton).toBeDisabled();
|
|
117
|
+
});
|
|
118
|
+
it('uses custom aria labels for pagination', () => {
|
|
119
|
+
const customLabels = {
|
|
120
|
+
paginationAriaLabel: 'Custom pagination',
|
|
121
|
+
toPreviousPageAriaLabel: 'Go to previous image',
|
|
122
|
+
toNextPageAriaLabel: 'Go to next image'
|
|
123
|
+
};
|
|
124
|
+
(0, react_1.render)((0, jsx_runtime_1.jsx)(ImagePreview_1.default, Object.assign({}, defaultProps, customLabels)));
|
|
125
|
+
expect(react_1.screen.getByRole('navigation', { name: 'Custom pagination' })).toBeInTheDocument();
|
|
126
|
+
expect(react_1.screen.getByRole('button', { name: 'Go to previous image' })).toBeInTheDocument();
|
|
127
|
+
expect(react_1.screen.getByRole('button', { name: 'Go to next image' })).toBeInTheDocument();
|
|
128
|
+
});
|
|
129
|
+
it('renders with compact mode when isCompact is true', () => {
|
|
130
|
+
(0, react_1.render)((0, jsx_runtime_1.jsx)(ImagePreview_1.default, Object.assign({}, defaultProps, { isCompact: true })));
|
|
131
|
+
const modal = react_1.screen.getByRole('dialog');
|
|
132
|
+
expect(modal).toHaveClass('pf-m-compact');
|
|
133
|
+
});
|
|
134
|
+
it('applies custom className when provided', () => {
|
|
135
|
+
const customClassName = 'custom-image-preview';
|
|
136
|
+
(0, react_1.render)((0, jsx_runtime_1.jsx)(ImagePreview_1.default, Object.assign({}, defaultProps, { className: customClassName })));
|
|
137
|
+
const modal = react_1.screen.getByRole('dialog');
|
|
138
|
+
expect(modal).toHaveClass(customClassName);
|
|
139
|
+
});
|
|
140
|
+
it('applies display mode class correctly', () => {
|
|
141
|
+
(0, react_1.render)((0, jsx_runtime_1.jsx)(ImagePreview_1.default, Object.assign({}, defaultProps, { displayMode: Chatbot_1.ChatbotDisplayMode.embedded })));
|
|
142
|
+
const modal = react_1.screen.getByRole('dialog');
|
|
143
|
+
expect(modal).toHaveClass('pf-chatbot__image-preview-modal--embedded');
|
|
144
|
+
});
|
|
145
|
+
it('passes additional props to ChatbotModal', () => {
|
|
146
|
+
const modalClass = 'custom-modal-class';
|
|
147
|
+
const additionalProps = {
|
|
148
|
+
'data-testid': 'modal',
|
|
149
|
+
className: modalClass
|
|
150
|
+
};
|
|
151
|
+
(0, react_1.render)((0, jsx_runtime_1.jsx)(ImagePreview_1.default, Object.assign({}, defaultProps, additionalProps)));
|
|
152
|
+
const modal = react_1.screen.getByTestId('modal');
|
|
153
|
+
expect(modal).toBeInTheDocument();
|
|
154
|
+
expect(modal).toBeInTheDocument();
|
|
155
|
+
expect(modal).toHaveClass(modalClass);
|
|
156
|
+
});
|
|
157
|
+
it('passes modalHeaderProps correctly', () => {
|
|
158
|
+
const headerClass = 'custom-modal-header-class';
|
|
159
|
+
const headerProps = {
|
|
160
|
+
'data-testid': 'header',
|
|
161
|
+
className: headerClass
|
|
162
|
+
};
|
|
163
|
+
(0, react_1.render)((0, jsx_runtime_1.jsx)(ImagePreview_1.default, Object.assign({}, defaultProps, { modalHeaderProps: headerProps })));
|
|
164
|
+
expect(react_1.screen.getByTestId('header')).toBeInTheDocument();
|
|
165
|
+
expect(react_1.screen.getByTestId('header')).toHaveClass(headerClass);
|
|
166
|
+
});
|
|
167
|
+
it('passes modalBodyProps correctly', () => {
|
|
168
|
+
const bodyClass = 'custom-modal-body-class';
|
|
169
|
+
const bodyProps = {
|
|
170
|
+
'data-testid': 'body',
|
|
171
|
+
className: bodyClass
|
|
172
|
+
};
|
|
173
|
+
(0, react_1.render)((0, jsx_runtime_1.jsx)(ImagePreview_1.default, Object.assign({}, defaultProps, { modalBodyProps: bodyProps })));
|
|
174
|
+
expect(react_1.screen.getByTestId('body')).toBeInTheDocument();
|
|
175
|
+
expect(react_1.screen.getByTestId('body')).toHaveClass(bodyClass);
|
|
176
|
+
});
|
|
177
|
+
it('handles single image without pagination', () => {
|
|
178
|
+
const singleImage = [mockImages[0]];
|
|
179
|
+
(0, react_1.render)((0, jsx_runtime_1.jsx)(ImagePreview_1.default, Object.assign({}, defaultProps, { images: singleImage })));
|
|
180
|
+
expect(react_1.screen.queryByText('1/1')).not.toBeInTheDocument();
|
|
181
|
+
expect(react_1.screen.queryByRole('button', { name: /Go to previous image/i })).not.toBeInTheDocument();
|
|
182
|
+
expect(react_1.screen.queryByRole('button', { name: /Go to next image/i })).not.toBeInTheDocument();
|
|
183
|
+
});
|
|
184
|
+
it('calls onCloseFileDetailsLabel when file details close button is clicked', () => {
|
|
185
|
+
const mockOnClose = jest.fn();
|
|
186
|
+
(0, react_1.render)((0, jsx_runtime_1.jsx)(ImagePreview_1.default, Object.assign({}, defaultProps, { onCloseFileDetailsLabel: mockOnClose })));
|
|
187
|
+
const closeButton = react_1.screen.getByRole('button', { name: /Close image1.jpg/i });
|
|
188
|
+
react_1.fireEvent.click(closeButton);
|
|
189
|
+
expect(mockOnClose).toHaveBeenCalled();
|
|
190
|
+
});
|
|
191
|
+
it('passes fileDetailsLabelProps correctly to FileDetailsLabel', () => {
|
|
192
|
+
const customFileDetailsProps = {
|
|
193
|
+
'data-testid': 'custom-file-details'
|
|
194
|
+
};
|
|
195
|
+
(0, react_1.render)((0, jsx_runtime_1.jsx)(ImagePreview_1.default, Object.assign({}, defaultProps, { fileDetailsLabelProps: customFileDetailsProps })));
|
|
196
|
+
expect(react_1.screen.getByTestId('custom-file-details')).toBeInTheDocument();
|
|
197
|
+
});
|
|
198
|
+
it('displays file details for current page when navigating', () => {
|
|
199
|
+
(0, react_1.render)((0, jsx_runtime_1.jsx)(ImagePreview_1.default, Object.assign({}, defaultProps)));
|
|
200
|
+
// Initially shows first image details
|
|
201
|
+
expect(react_1.screen.getByText('image1.jpg')).toBeInTheDocument();
|
|
202
|
+
expect(react_1.screen.getByText('2.5 MB')).toBeInTheDocument();
|
|
203
|
+
// Navigate to second page
|
|
204
|
+
const nextButton = react_1.screen.getByRole('button', { name: /Go to next image/i });
|
|
205
|
+
react_1.fireEvent.click(nextButton);
|
|
206
|
+
// Should now show second image details
|
|
207
|
+
expect(react_1.screen.getByText('image2.png')).toBeInTheDocument();
|
|
208
|
+
expect(react_1.screen.getByText('1.8 MB')).toBeInTheDocument();
|
|
209
|
+
// Navigate to third page
|
|
210
|
+
react_1.fireEvent.click(nextButton);
|
|
211
|
+
// Should now show third image details (no file size)
|
|
212
|
+
expect(react_1.screen.getByText('image3.gif')).toBeInTheDocument();
|
|
213
|
+
expect(react_1.screen.queryByText(/MB/)).not.toBeInTheDocument();
|
|
214
|
+
});
|
|
215
|
+
it('sets hasTruncation to false on FileDetailsLabel', () => {
|
|
216
|
+
const longFileName = 'very-long-filename-that-would-normally-be-truncated-in-other-contexts.jpg';
|
|
217
|
+
const imageWithLongName = {
|
|
218
|
+
fileName: longFileName,
|
|
219
|
+
fileSize: '1.0 MB',
|
|
220
|
+
image: (0, jsx_runtime_1.jsx)("img", { src: "", alt: "Test image with long name" })
|
|
221
|
+
};
|
|
222
|
+
(0, react_1.render)((0, jsx_runtime_1.jsx)(ImagePreview_1.default, Object.assign({}, defaultProps, { images: [imageWithLongName] })));
|
|
223
|
+
expect(react_1.screen.getByText(longFileName)).toBeInTheDocument();
|
|
224
|
+
});
|
|
225
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
17
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
18
|
+
};
|
|
19
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
+
exports.default = void 0;
|
|
21
|
+
var ImagePreview_1 = require("./ImagePreview");
|
|
22
|
+
Object.defineProperty(exports, "default", { enumerable: true, get: function () { return __importDefault(ImagePreview_1).default; } });
|
|
23
|
+
__exportStar(require("./ImagePreview"), exports);
|
|
@@ -12,6 +12,7 @@ import { TableProps } from '@patternfly/react-table';
|
|
|
12
12
|
import { PluggableList } from 'unified';
|
|
13
13
|
import { ToolResponseProps } from '../ToolResponse';
|
|
14
14
|
import { DeepThinkingProps } from '../DeepThinking';
|
|
15
|
+
import { ToolCallProps } from '../ToolCall';
|
|
15
16
|
export interface MessageAttachment {
|
|
16
17
|
/** Name of file attached to the message */
|
|
17
18
|
name: string;
|
|
@@ -155,6 +156,8 @@ export interface MessageProps extends Omit<HTMLProps<HTMLDivElement>, 'role'> {
|
|
|
155
156
|
deepThinking?: DeepThinkingProps;
|
|
156
157
|
/** Allows passing additional props down to remark-gfm. See https://github.com/remarkjs/remark-gfm?tab=readme-ov-file#options for options */
|
|
157
158
|
remarkGfmProps?: Options;
|
|
159
|
+
/** Props for a tool call message */
|
|
160
|
+
toolCall?: ToolCallProps;
|
|
158
161
|
}
|
|
159
162
|
export declare const MessageBase: FunctionComponent<MessageProps>;
|
|
160
163
|
declare const Message: import("react").ForwardRefExoticComponent<Omit<MessageProps, "ref"> & import("react").RefAttributes<HTMLDivElement>>;
|
|
@@ -53,8 +53,9 @@ const rehypeMoveImagesOutOfParagraphs_1 = require("./Plugins/rehypeMoveImagesOut
|
|
|
53
53
|
const ToolResponse_1 = __importDefault(require("../ToolResponse"));
|
|
54
54
|
const DeepThinking_1 = __importDefault(require("../DeepThinking"));
|
|
55
55
|
const SuperscriptMessage_1 = __importDefault(require("./SuperscriptMessage/SuperscriptMessage"));
|
|
56
|
+
const ToolCall_1 = __importDefault(require("../ToolCall"));
|
|
56
57
|
const MessageBase = (_a) => {
|
|
57
|
-
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 } = _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"]);
|
|
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
59
|
const [messageText, setMessageText] = (0, react_1.useState)(content);
|
|
59
60
|
(0, react_1.useEffect)(() => {
|
|
60
61
|
setMessageText(content);
|
|
@@ -211,7 +212,7 @@ const MessageBase = (_a) => {
|
|
|
211
212
|
}
|
|
212
213
|
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()] }));
|
|
213
214
|
};
|
|
214
|
-
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 }), toolResponse && (0, jsx_runtime_1.jsx)(ToolResponse_1.default, Object.assign({}, toolResponse)), deepThinking && (0, jsx_runtime_1.jsx)(DeepThinking_1.default, Object.assign({}, deepThinking)), !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) => {
|
|
215
|
+
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 }), toolResponse && (0, jsx_runtime_1.jsx)(ToolResponse_1.default, Object.assign({}, toolResponse)), deepThinking && (0, jsx_runtime_1.jsx)(DeepThinking_1.default, Object.assign({}, deepThinking)), toolCall && (0, jsx_runtime_1.jsx)(ToolCall_1.default, Object.assign({}, toolCall)), !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) => {
|
|
215
216
|
var _a;
|
|
216
217
|
return ((0, jsx_runtime_1.jsx)("div", { className: "pf-chatbot__message-attachment", children: (0, jsx_runtime_1.jsx)(FileDetailsLabel_1.default, { fileName: attachment.name, fileId: attachment.id, onClose: attachment.onClose, onClick: attachment.onClick, isLoading: attachment.isLoading, closeButtonAriaLabel: attachment.closeButtonAriaLabel, languageTestId: attachment.languageTestId, spinnerTestId: attachment.spinnerTestId }) }, (_a = attachment.id) !== null && _a !== void 0 ? _a : attachment.name));
|
|
217
218
|
}) })), !isLoading && endContent && (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: endContent })] })] })] })));
|
|
@@ -206,6 +206,6 @@ exports.MessageBox = (0, react_1.forwardRef)((_a, ref) => {
|
|
|
206
206
|
onTouchMove,
|
|
207
207
|
onTouchEnd
|
|
208
208
|
};
|
|
209
|
-
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(JumpButton_1.default, { position: "top", isHidden: isOverflowing && atTop, onClick: scrollToTop, jumpButtonProps: jumpButtonTopProps, jumpButtonTooltipProps: jumpButtonTopTooltipProps }), (0, jsx_runtime_1.jsxs)("div", Object.assign({ role: "region", tabIndex: 0, "aria-label": ariaLabel, className: `pf-chatbot__messagebox ${position === 'bottom' ? 'pf-chatbot__messagebox--bottom' : ''} ${className !== null && className !== void 0 ? className : ''}`, ref: messageBoxRef }, props, (enableSmartScroll ? Object.assign({}, smartScrollHandlers) : {}), { children: [children, (0, jsx_runtime_1.jsx)("div", { className: "pf-chatbot__messagebox-announcement", "aria-live": "polite", children: announcement })] })), (0, jsx_runtime_1.jsx)(JumpButton_1.default, { position: "bottom", isHidden: isOverflowing && atBottom, onClick: () => scrollToBottom({ resumeSmartScroll: true }), jumpButtonProps: jumpButtonBottomProps, jumpButtonTooltipProps: jumpButtonBottomTooltipProps })] }));
|
|
209
|
+
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(JumpButton_1.default, { position: "top", isHidden: isOverflowing && atTop, onClick: scrollToTop, jumpButtonProps: jumpButtonTopProps, jumpButtonTooltipProps: jumpButtonTopTooltipProps }), (0, jsx_runtime_1.jsxs)("div", Object.assign({ role: "region", tabIndex: 0, "aria-label": ariaLabel, className: `pf-chatbot__messagebox ${position === 'bottom' ? 'pf-chatbot__messagebox--bottom' : ''} ${className !== null && className !== void 0 ? className : ''}`, ref: messageBoxRef }, props, (enableSmartScroll ? Object.assign({}, smartScrollHandlers) : {}), { children: [children, (0, jsx_runtime_1.jsx)("div", { className: "pf-chatbot__messagebox-announcement pf-chatbot-m-hidden", "aria-live": "polite", children: announcement })] })), (0, jsx_runtime_1.jsx)(JumpButton_1.default, { position: "bottom", isHidden: isOverflowing && atBottom, onClick: () => scrollToBottom({ resumeSmartScroll: true }), jumpButtonProps: jumpButtonBottomProps, jumpButtonTooltipProps: jumpButtonBottomTooltipProps })] }));
|
|
210
210
|
});
|
|
211
211
|
exports.default = exports.MessageBox;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { type FunctionComponent } from 'react';
|
|
2
|
+
import { ActionListProps, ActionListGroupProps, ActionListItemProps, ButtonProps, CardProps, CardBodyProps, CardFooterProps, ExpandableSectionProps, SpinnerProps } from '@patternfly/react-core';
|
|
3
|
+
export interface ToolCallProps {
|
|
4
|
+
/** Title text for the tool call. */
|
|
5
|
+
titleText: string;
|
|
6
|
+
/** Loading text for the tool call. */
|
|
7
|
+
loadingText?: string;
|
|
8
|
+
/** Flag indicating whether the tool call is loading or not. */
|
|
9
|
+
isLoading?: boolean;
|
|
10
|
+
/** Additional props for the spinner that is rendered when isLoading is true. */
|
|
11
|
+
spinnerProps?: SpinnerProps;
|
|
12
|
+
/** Content to render within an expandable section. */
|
|
13
|
+
expandableContent?: React.ReactNode;
|
|
14
|
+
/** Text content for the "run" action button. */
|
|
15
|
+
runButtonText?: string;
|
|
16
|
+
/** Additional props for the "run" action button. */
|
|
17
|
+
runButtonProps?: ButtonProps;
|
|
18
|
+
/** Additional props for the "run" action list item. */
|
|
19
|
+
runActionItemProps?: ActionListItemProps;
|
|
20
|
+
/** Text content for the "cancel" action button. */
|
|
21
|
+
cancelButtonText?: string;
|
|
22
|
+
/** Additional props for the "cancel" action button. */
|
|
23
|
+
cancelButtonProps?: ButtonProps;
|
|
24
|
+
/** Additional props for the "cancel" action list item. */
|
|
25
|
+
cancelActionItemProps?: ActionListItemProps;
|
|
26
|
+
/** Custom actions to render, typically a "cancel" and "run" action. This will override the default actions. */
|
|
27
|
+
actions?: React.ReactNode[];
|
|
28
|
+
/** Additional props for the action list */
|
|
29
|
+
actionListProps?: ActionListProps;
|
|
30
|
+
/** Additional props for the action list group. */
|
|
31
|
+
actionListGroupProps?: ActionListGroupProps;
|
|
32
|
+
/** Additional props for all action list items. */
|
|
33
|
+
actionListItemProps?: ActionListItemProps;
|
|
34
|
+
/** Additional props for the card. */
|
|
35
|
+
cardProps?: CardProps;
|
|
36
|
+
/** Additional props for the card body that contains the main tool call content. */
|
|
37
|
+
cardBodyProps?: CardBodyProps;
|
|
38
|
+
/** Additional props for the card footer that contains the tool call actions. */
|
|
39
|
+
cardFooterProps?: CardFooterProps;
|
|
40
|
+
/** Additional props for the expandable section when expandableContent is passed. */
|
|
41
|
+
expandableSectionProps?: Omit<ExpandableSectionProps, 'ref'>;
|
|
42
|
+
}
|
|
43
|
+
export declare const ToolCall: FunctionComponent<ToolCallProps>;
|
|
44
|
+
export default ToolCall;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ToolCall = void 0;
|
|
4
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
+
const react_core_1 = require("@patternfly/react-core");
|
|
6
|
+
const ToolCall = ({ titleText, loadingText, isLoading, expandableContent, runButtonText = 'Run tool', runButtonProps, runActionItemProps, cancelButtonText = 'Cancel', cancelButtonProps, cancelActionItemProps, actions, actionListProps, actionListGroupProps, actionListItemProps, cardProps, cardBodyProps, cardFooterProps, expandableSectionProps, spinnerProps }) => {
|
|
7
|
+
const titleContent = ((0, jsx_runtime_1.jsx)("span", { className: `pf-chatbot__tool-call-title-content`, children: isLoading ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(react_core_1.Spinner, Object.assign({ diameter: "1em" }, spinnerProps)), ' ', (0, jsx_runtime_1.jsx)("span", { className: "pf-chatbot__tool-call-title-text", children: loadingText })] })) : ((0, jsx_runtime_1.jsx)("span", { className: "pf-chatbot__tool-call-title-text", children: titleText })) }));
|
|
8
|
+
const defaultActions = ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(react_core_1.ActionListItem, Object.assign({}, actionListItemProps, cancelActionItemProps, { children: (0, jsx_runtime_1.jsx)(react_core_1.Button, Object.assign({ variant: "link" }, cancelButtonProps, { children: cancelButtonText })) })), (0, jsx_runtime_1.jsx)(react_core_1.ActionListItem, Object.assign({}, actionListItemProps, runActionItemProps, { children: (0, jsx_runtime_1.jsx)(react_core_1.Button, Object.assign({ variant: "secondary" }, runButtonProps, { children: runButtonText })) }))] }));
|
|
9
|
+
const customActions = actions &&
|
|
10
|
+
actions.map((action, index) => ((0, jsx_runtime_1.jsx)(react_core_1.ActionListItem, Object.assign({}, actionListItemProps, { children: action }), index)));
|
|
11
|
+
return ((0, jsx_runtime_1.jsxs)(react_core_1.Card, Object.assign({ isCompact: true, className: "pf-chatbot__tool-call" }, cardProps, { children: [(0, jsx_runtime_1.jsx)(react_core_1.CardBody, Object.assign({ className: "pf-chatbot__tool-call-title" }, cardBodyProps, { children: expandableContent && !isLoading ? ((0, jsx_runtime_1.jsx)(react_core_1.ExpandableSection, Object.assign({ className: "pf-chatbot__tool-call-expandable-section", toggleContent: titleContent, isIndented: true }, expandableSectionProps, { children: expandableContent }))) : (titleContent) })), !isLoading && ((0, jsx_runtime_1.jsx)(react_core_1.CardFooter, Object.assign({}, cardFooterProps, { children: (0, jsx_runtime_1.jsx)(react_core_1.ActionList, Object.assign({ className: "pf-chatbot__tool-call-action-list" }, actionListProps, { children: (0, jsx_runtime_1.jsx)(react_core_1.ActionListGroup, Object.assign({}, actionListGroupProps, { children: customActions || defaultActions })) })) })))] })));
|
|
12
|
+
};
|
|
13
|
+
exports.ToolCall = ToolCall;
|
|
14
|
+
exports.default = exports.ToolCall;
|