@patternfly/chatbot 6.5.0-prerelease.20 → 6.5.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/DeepThinking/DeepThinking.d.ts +2 -0
- package/dist/cjs/DeepThinking/DeepThinking.js +2 -2
- package/dist/cjs/DeepThinking/DeepThinking.test.js +41 -0
- package/dist/cjs/Message/Message.d.ts +15 -3
- package/dist/cjs/Message/Message.js +1 -1
- package/dist/cjs/Message/Message.test.js +125 -2
- package/dist/cjs/ToolCall/ToolCall.d.ts +2 -0
- package/dist/cjs/ToolCall/ToolCall.js +7 -2
- package/dist/cjs/ToolCall/ToolCall.test.js +26 -0
- package/dist/cjs/ToolResponse/ToolResponse.d.ts +2 -0
- package/dist/cjs/ToolResponse/ToolResponse.js +2 -2
- package/dist/cjs/ToolResponse/ToolResponse.test.js +40 -0
- package/dist/css/main.css +10 -0
- package/dist/css/main.css.map +1 -1
- package/dist/esm/DeepThinking/DeepThinking.d.ts +2 -0
- package/dist/esm/DeepThinking/DeepThinking.js +2 -2
- package/dist/esm/DeepThinking/DeepThinking.test.js +41 -0
- package/dist/esm/Message/Message.d.ts +15 -3
- package/dist/esm/Message/Message.js +1 -1
- package/dist/esm/Message/Message.test.js +125 -2
- package/dist/esm/ToolCall/ToolCall.d.ts +2 -0
- package/dist/esm/ToolCall/ToolCall.js +7 -2
- package/dist/esm/ToolCall/ToolCall.test.js +26 -0
- package/dist/esm/ToolResponse/ToolResponse.d.ts +2 -0
- package/dist/esm/ToolResponse/ToolResponse.js +2 -2
- package/dist/esm/ToolResponse/ToolResponse.test.js +40 -0
- package/package.json +1 -1
- package/patternfly-docs/content/extensions/chatbot/examples/Messages/MessageWithDeepThinking.tsx +25 -11
- package/patternfly-docs/content/extensions/chatbot/examples/Messages/MessageWithMultipleActionGroups.tsx +61 -0
- package/patternfly-docs/content/extensions/chatbot/examples/Messages/MessageWithToolCall.tsx +14 -1
- package/patternfly-docs/content/extensions/chatbot/examples/Messages/MessageWithToolResponse.tsx +222 -105
- package/patternfly-docs/content/extensions/chatbot/examples/Messages/Messages.md +18 -0
- package/src/DeepThinking/DeepThinking.test.tsx +61 -0
- package/src/DeepThinking/DeepThinking.tsx +4 -1
- package/src/Message/Message.test.tsx +198 -2
- package/src/Message/Message.tsx +35 -6
- package/src/ResponseActions/ResponseActions.scss +11 -0
- package/src/ToolCall/ToolCall.test.tsx +51 -0
- package/src/ToolCall/ToolCall.tsx +12 -1
- package/src/ToolResponse/ToolResponse.test.tsx +44 -0
- package/src/ToolResponse/ToolResponse.tsx +4 -1
|
@@ -60,12 +60,24 @@ export interface MessageProps extends Omit<HTMLProps<HTMLDivElement>, 'role'> {
|
|
|
60
60
|
isLoading?: boolean;
|
|
61
61
|
/** Array of attachments attached to a message */
|
|
62
62
|
attachments?: MessageAttachment[];
|
|
63
|
-
/** Props for message actions, such as feedback (positive or negative), copy button, edit message, share, and listen
|
|
63
|
+
/** Props for message actions, such as feedback (positive or negative), copy button, edit message, share, and listen.
|
|
64
|
+
* Can be a single actions object or an array of action group objects. When passing an array, you can pass an object of actions or
|
|
65
|
+
* an object that contains an actions property for finer control of selection persistence.
|
|
66
|
+
*/
|
|
64
67
|
actions?: {
|
|
65
68
|
[key: string]: ActionProps;
|
|
66
|
-
}
|
|
69
|
+
} | {
|
|
70
|
+
[key: string]: ActionProps;
|
|
71
|
+
}[] | {
|
|
72
|
+
actions: {
|
|
73
|
+
[key: string]: ActionProps;
|
|
74
|
+
};
|
|
75
|
+
persistActionSelection?: boolean;
|
|
76
|
+
}[];
|
|
67
77
|
/** When true, the selected action will persist even when clicking outside the component.
|
|
68
|
-
* When false (default), clicking outside or clicking another action will deselect the current selection.
|
|
78
|
+
* When false (default), clicking outside or clicking another action will deselect the current selection.
|
|
79
|
+
* For finer control of multiple action groups, use persistActionSelection on each group.
|
|
80
|
+
*/
|
|
69
81
|
persistActionSelection?: boolean;
|
|
70
82
|
/** Sources for message */
|
|
71
83
|
sources?: SourcesCardProps;
|
|
@@ -213,7 +213,7 @@ export const MessageBase = (_a) => {
|
|
|
213
213
|
}
|
|
214
214
|
return (_jsxs(_Fragment, { children: [beforeMainContent && _jsx(_Fragment, { children: beforeMainContent }), error ? _jsx(ErrorMessage, Object.assign({}, error)) : handleMarkdown()] }));
|
|
215
215
|
};
|
|
216
|
-
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: [avatar && (_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 }), toolResponse && _jsx(ToolResponse, Object.assign({}, toolResponse)), deepThinking && _jsx(DeepThinking, Object.assign({}, deepThinking)), toolCall && _jsx(ToolCall, Object.assign({}, toolCall)), !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, persistActionSelection: persistActionSelection })), 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) => {
|
|
216
|
+
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: [avatar && (_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 }), toolResponse && _jsx(ToolResponse, Object.assign({}, toolResponse)), deepThinking && _jsx(DeepThinking, Object.assign({}, deepThinking)), toolCall && _jsx(ToolCall, Object.assign({}, toolCall)), !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(_Fragment, { children: Array.isArray(actions) ? (_jsx("div", { className: "pf-chatbot__response-actions-groups", children: actions.map((actionGroup, index) => (_jsx(ResponseActions, { actions: actionGroup.actions || actionGroup, persistActionSelection: persistActionSelection || actionGroup.persistActionSelection }, index))) })) : (_jsx(ResponseActions, { actions: actions, persistActionSelection: persistActionSelection })) })), 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) => {
|
|
217
217
|
var _a;
|
|
218
218
|
return (_jsx("div", { className: "pf-chatbot__message-attachment", children: _jsx(FileDetailsLabel, { fileName: attachment.name, fileId: attachment.id, onClose: attachment.onClose, onClick: attachment.onClick, isLoading: attachment.isLoading, closeButtonAriaLabel: attachment.closeButtonAriaLabel, languageTestId: attachment.languageTestId, spinnerTestId: attachment.spinnerTestId, variant: isPrimary ? 'outline' : undefined }) }, (_a = attachment.id) !== null && _a !== void 0 ? _a : attachment.name));
|
|
219
219
|
}) })), !isLoading && endContent && _jsx(_Fragment, { children: endContent })] })] })] })));
|
|
@@ -355,7 +355,7 @@ describe('Message', () => {
|
|
|
355
355
|
expect(screen.queryByRole('button', { name: /No/i })).toBeFalsy();
|
|
356
356
|
expect(screen.getByRole('button', { name: /1 more/i }));
|
|
357
357
|
}));
|
|
358
|
-
it('
|
|
358
|
+
it('Renders response actions when a single actions object is passed', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
359
359
|
render(_jsx(Message, { avatar: "./img", role: "bot", name: "Bot", content: "Hi", actions: {
|
|
360
360
|
// eslint-disable-next-line no-console
|
|
361
361
|
positive: { onClick: () => console.log('Good response') },
|
|
@@ -373,9 +373,132 @@ describe('Message', () => {
|
|
|
373
373
|
listen: { onClick: () => console.log('Listen') }
|
|
374
374
|
} }));
|
|
375
375
|
ALL_ACTIONS.forEach(({ label }) => {
|
|
376
|
-
expect(screen.getByRole('button', { name: label })).
|
|
376
|
+
expect(screen.getByRole('button', { name: label })).toBeVisible();
|
|
377
377
|
});
|
|
378
378
|
}));
|
|
379
|
+
it('Renders response actions when an array of actions objects is passed', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
380
|
+
render(_jsx(Message, { avatar: "./img", role: "bot", name: "Bot", content: "Hi", actions: [
|
|
381
|
+
{
|
|
382
|
+
// eslint-disable-next-line no-console
|
|
383
|
+
positive: { onClick: () => console.log('Good response') },
|
|
384
|
+
// eslint-disable-next-line no-console
|
|
385
|
+
negative: { onClick: () => console.log('Bad response') }
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
// eslint-disable-next-line no-console
|
|
389
|
+
copy: { onClick: () => console.log('Copy') },
|
|
390
|
+
// eslint-disable-next-line no-console
|
|
391
|
+
edit: { onClick: () => console.log('Edit') },
|
|
392
|
+
// eslint-disable-next-line no-console
|
|
393
|
+
share: { onClick: () => console.log('Share') },
|
|
394
|
+
// eslint-disable-next-line no-console
|
|
395
|
+
download: { onClick: () => console.log('Download') }
|
|
396
|
+
},
|
|
397
|
+
{
|
|
398
|
+
// eslint-disable-next-line no-console
|
|
399
|
+
listen: { onClick: () => console.log('Listen') }
|
|
400
|
+
}
|
|
401
|
+
] }));
|
|
402
|
+
ALL_ACTIONS.forEach(({ label }) => {
|
|
403
|
+
expect(screen.getByRole('button', { name: label })).toBeVisible();
|
|
404
|
+
});
|
|
405
|
+
}));
|
|
406
|
+
it('Renders response actions when an array of objects containing actions objects is passed', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
407
|
+
render(_jsx(Message, { avatar: "./img", role: "bot", name: "Bot", content: "Hi", actions: [
|
|
408
|
+
{
|
|
409
|
+
actions: {
|
|
410
|
+
// eslint-disable-next-line no-console
|
|
411
|
+
positive: { onClick: () => console.log('Good response') },
|
|
412
|
+
// eslint-disable-next-line no-console
|
|
413
|
+
negative: { onClick: () => console.log('Bad response') }
|
|
414
|
+
}
|
|
415
|
+
},
|
|
416
|
+
{
|
|
417
|
+
actions: {
|
|
418
|
+
// eslint-disable-next-line no-console
|
|
419
|
+
copy: { onClick: () => console.log('Copy') },
|
|
420
|
+
// eslint-disable-next-line no-console
|
|
421
|
+
edit: { onClick: () => console.log('Edit') },
|
|
422
|
+
// eslint-disable-next-line no-console
|
|
423
|
+
share: { onClick: () => console.log('Share') },
|
|
424
|
+
// eslint-disable-next-line no-console
|
|
425
|
+
download: { onClick: () => console.log('Download') }
|
|
426
|
+
}
|
|
427
|
+
},
|
|
428
|
+
{
|
|
429
|
+
actions: {
|
|
430
|
+
// eslint-disable-next-line no-console
|
|
431
|
+
listen: { onClick: () => console.log('Listen') }
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
] }));
|
|
435
|
+
ALL_ACTIONS.forEach(({ label }) => {
|
|
436
|
+
expect(screen.getByRole('button', { name: label })).toBeVisible();
|
|
437
|
+
});
|
|
438
|
+
}));
|
|
439
|
+
it('should handle persistActionSelection correctly when a single actions object is passed', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
440
|
+
render(_jsx(Message, { avatar: "./img", role: "bot", name: "Bot", content: "Test message", persistActionSelection: true, actions: {
|
|
441
|
+
positive: { onClick: jest.fn() },
|
|
442
|
+
negative: { onClick: jest.fn() }
|
|
443
|
+
} }));
|
|
444
|
+
const goodBtn = screen.getByRole('button', { name: /Good response/i });
|
|
445
|
+
const badBtn = screen.getByRole('button', { name: /Bad response/i });
|
|
446
|
+
yield userEvent.click(goodBtn);
|
|
447
|
+
expect(screen.getByRole('button', { name: /Good response recorded/i })).toHaveClass('pf-chatbot__button--response-action-clicked');
|
|
448
|
+
yield userEvent.click(screen.getByText('Test message'));
|
|
449
|
+
expect(screen.getByRole('button', { name: /Good response recorded/i })).toHaveClass('pf-chatbot__button--response-action-clicked');
|
|
450
|
+
yield userEvent.click(badBtn);
|
|
451
|
+
expect(screen.getByRole('button', { name: /Bad response recorded/i })).toHaveClass('pf-chatbot__button--response-action-clicked');
|
|
452
|
+
expect(goodBtn).not.toHaveClass('pf-chatbot__button--response-action-clicked');
|
|
453
|
+
}));
|
|
454
|
+
it('should handle persistActionSelection correctly when an array of actions objects is passed', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
455
|
+
render(_jsx(Message, { avatar: "./img", role: "bot", name: "Bot", content: "Test message", persistActionSelection: true, actions: [
|
|
456
|
+
{
|
|
457
|
+
positive: { onClick: jest.fn() },
|
|
458
|
+
negative: { onClick: jest.fn() }
|
|
459
|
+
},
|
|
460
|
+
{
|
|
461
|
+
copy: { onClick: jest.fn() }
|
|
462
|
+
}
|
|
463
|
+
] }));
|
|
464
|
+
const goodBtn = screen.getByRole('button', { name: /Good response/i });
|
|
465
|
+
const copyBtn = screen.getByRole('button', { name: /Copy/i });
|
|
466
|
+
yield userEvent.click(goodBtn);
|
|
467
|
+
expect(screen.getByRole('button', { name: /Good response recorded/i })).toHaveClass('pf-chatbot__button--response-action-clicked');
|
|
468
|
+
yield userEvent.click(screen.getByText('Test message'));
|
|
469
|
+
expect(screen.getByRole('button', { name: /Good response recorded/i })).toHaveClass('pf-chatbot__button--response-action-clicked');
|
|
470
|
+
yield userEvent.click(copyBtn);
|
|
471
|
+
expect(screen.getByRole('button', { name: /Copied/i })).toHaveClass('pf-chatbot__button--response-action-clicked');
|
|
472
|
+
yield userEvent.click(screen.getByText('Test message'));
|
|
473
|
+
expect(screen.getByRole('button', { name: /Good response recorded/i })).toHaveClass('pf-chatbot__button--response-action-clicked');
|
|
474
|
+
expect(screen.getByRole('button', { name: /Copied/i })).toHaveClass('pf-chatbot__button--response-action-clicked');
|
|
475
|
+
}));
|
|
476
|
+
it('should handle persistActionSelection correctly when an array of objects containing actions objects is passed', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
477
|
+
render(_jsx(Message, { avatar: "./img", role: "bot", name: "Bot", content: "Test message", actions: [
|
|
478
|
+
{
|
|
479
|
+
actions: {
|
|
480
|
+
positive: { onClick: jest.fn() },
|
|
481
|
+
negative: { onClick: jest.fn() }
|
|
482
|
+
},
|
|
483
|
+
persistActionSelection: true
|
|
484
|
+
},
|
|
485
|
+
{
|
|
486
|
+
actions: {
|
|
487
|
+
copy: { onClick: jest.fn() }
|
|
488
|
+
},
|
|
489
|
+
persistActionSelection: false
|
|
490
|
+
}
|
|
491
|
+
] }));
|
|
492
|
+
const goodBtn = screen.getByRole('button', { name: /Good response/i });
|
|
493
|
+
const copyBtn = screen.getByRole('button', { name: /Copy/i });
|
|
494
|
+
yield userEvent.click(goodBtn);
|
|
495
|
+
expect(screen.getByRole('button', { name: /Good response recorded/i })).toHaveClass('pf-chatbot__button--response-action-clicked');
|
|
496
|
+
yield userEvent.click(copyBtn);
|
|
497
|
+
expect(screen.getByRole('button', { name: /Copied/i })).toHaveClass('pf-chatbot__button--response-action-clicked');
|
|
498
|
+
yield userEvent.click(screen.getByText('Test message'));
|
|
499
|
+
expect(screen.getByRole('button', { name: /Good response recorded/i })).toHaveClass('pf-chatbot__button--response-action-clicked');
|
|
500
|
+
expect(copyBtn).not.toHaveClass('pf-chatbot__button--response-action-clicked');
|
|
501
|
+
}));
|
|
379
502
|
it('should not show actions if loading', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
380
503
|
render(_jsx(Message, { avatar: "./img", role: "bot", name: "Bot", content: "Hi", isLoading: true, actions: {
|
|
381
504
|
// eslint-disable-next-line no-console
|
|
@@ -11,6 +11,8 @@ export interface ToolCallProps {
|
|
|
11
11
|
spinnerProps?: SpinnerProps;
|
|
12
12
|
/** Content to render within an expandable section. */
|
|
13
13
|
expandableContent?: React.ReactNode;
|
|
14
|
+
/** Flag indicating whether the expandable content is expanded by default. */
|
|
15
|
+
isDefaultExpanded?: boolean;
|
|
14
16
|
/** Text content for the "run" action button. */
|
|
15
17
|
runButtonText?: string;
|
|
16
18
|
/** Additional props for the "run" action button. */
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
2
3
|
import { ActionList, ActionListGroup, ActionListItem, Button, Card, CardBody, CardFooter, ExpandableSection, Spinner } from '@patternfly/react-core';
|
|
3
|
-
export const ToolCall = ({ titleText, loadingText, isLoading, expandableContent, runButtonText = 'Run tool', runButtonProps, runActionItemProps, cancelButtonText = 'Cancel', cancelButtonProps, cancelActionItemProps, actions, actionListProps, actionListGroupProps, actionListItemProps, cardProps, cardBodyProps, cardFooterProps, expandableSectionProps, spinnerProps }) => {
|
|
4
|
+
export const ToolCall = ({ titleText, loadingText, isLoading, expandableContent, isDefaultExpanded = false, runButtonText = 'Run tool', runButtonProps, runActionItemProps, cancelButtonText = 'Cancel', cancelButtonProps, cancelActionItemProps, actions, actionListProps, actionListGroupProps, actionListItemProps, cardProps, cardBodyProps, cardFooterProps, expandableSectionProps, spinnerProps }) => {
|
|
5
|
+
const [isExpanded, setIsExpanded] = useState(isDefaultExpanded);
|
|
6
|
+
const onToggle = (_event, isExpanded) => {
|
|
7
|
+
setIsExpanded(isExpanded);
|
|
8
|
+
};
|
|
4
9
|
const titleContent = (_jsx("span", { className: `pf-chatbot__tool-call-title-content`, children: isLoading ? (_jsxs(_Fragment, { children: [_jsx(Spinner, Object.assign({ diameter: "1em" }, spinnerProps)), ' ', _jsx("span", { className: "pf-chatbot__tool-call-title-text", children: loadingText })] })) : (_jsx("span", { className: "pf-chatbot__tool-call-title-text", children: titleText })) }));
|
|
5
10
|
const defaultActions = (_jsxs(_Fragment, { children: [_jsx(ActionListItem, Object.assign({}, actionListItemProps, cancelActionItemProps, { children: _jsx(Button, Object.assign({ variant: "link" }, cancelButtonProps, { children: cancelButtonText })) })), _jsx(ActionListItem, Object.assign({}, actionListItemProps, runActionItemProps, { children: _jsx(Button, Object.assign({ variant: "secondary" }, runButtonProps, { children: runButtonText })) }))] }));
|
|
6
11
|
const customActions = actions &&
|
|
7
12
|
actions.map((action, index) => (_jsx(ActionListItem, Object.assign({}, actionListItemProps, { children: action }), index)));
|
|
8
|
-
return (_jsxs(Card, Object.assign({ isCompact: true, className: "pf-chatbot__tool-call" }, cardProps, { children: [_jsx(CardBody, Object.assign({ className: "pf-chatbot__tool-call-title" }, cardBodyProps, { children: expandableContent && !isLoading ? (_jsx(ExpandableSection, Object.assign({ className: "pf-chatbot__tool-call-expandable-section", toggleContent: titleContent, isIndented: true }, expandableSectionProps, { children: expandableContent }))) : (titleContent) })), !isLoading && (_jsx(CardFooter, Object.assign({}, cardFooterProps, { children: _jsx(ActionList, Object.assign({ className: "pf-chatbot__tool-call-action-list" }, actionListProps, { children: _jsx(ActionListGroup, Object.assign({}, actionListGroupProps, { children: customActions || defaultActions })) })) })))] })));
|
|
13
|
+
return (_jsxs(Card, Object.assign({ isCompact: true, className: "pf-chatbot__tool-call" }, cardProps, { children: [_jsx(CardBody, Object.assign({ className: "pf-chatbot__tool-call-title" }, cardBodyProps, { children: expandableContent && !isLoading ? (_jsx(ExpandableSection, Object.assign({ className: "pf-chatbot__tool-call-expandable-section", toggleContent: titleContent, onToggle: onToggle, isExpanded: isExpanded, isIndented: true }, expandableSectionProps, { children: expandableContent }))) : (titleContent) })), !isLoading && (_jsx(CardFooter, Object.assign({}, cardFooterProps, { children: _jsx(ActionList, Object.assign({ className: "pf-chatbot__tool-call-action-list" }, actionListProps, { children: _jsx(ActionListGroup, Object.assign({}, actionListGroupProps, { children: customActions || defaultActions })) })) })))] })));
|
|
9
14
|
};
|
|
10
15
|
export default ToolCall;
|
|
@@ -136,4 +136,30 @@ describe('ToolCall', () => {
|
|
|
136
136
|
render(_jsx(ToolCall, Object.assign({}, defaultProps, { cardFooterProps: { id: 'card-footer-test-id' } })));
|
|
137
137
|
expect(screen.getByRole('button', { name: 'Run tool' }).closest('#card-footer-test-id')).toBeVisible();
|
|
138
138
|
});
|
|
139
|
+
it('Renders collapsed by default when expandableContent is provided', () => {
|
|
140
|
+
render(_jsx(ToolCall, Object.assign({}, defaultProps, { expandableContent: "Expandable Content" })));
|
|
141
|
+
expect(screen.getByRole('button', { name: defaultProps.titleText })).toHaveAttribute('aria-expanded', 'false');
|
|
142
|
+
expect(screen.queryByText('Expandable Content')).not.toBeVisible();
|
|
143
|
+
});
|
|
144
|
+
it('Renders expanded when isDefaultExpanded is true', () => {
|
|
145
|
+
render(_jsx(ToolCall, Object.assign({}, defaultProps, { isDefaultExpanded: true, expandableContent: "Expandable Content" })));
|
|
146
|
+
expect(screen.getByRole('button', { name: defaultProps.titleText })).toHaveAttribute('aria-expanded', 'true');
|
|
147
|
+
expect(screen.getByText('Expandable Content')).toBeVisible();
|
|
148
|
+
});
|
|
149
|
+
it('expandableSectionProps.isExpanded overrides isDefaultExpanded', () => {
|
|
150
|
+
render(_jsx(ToolCall, Object.assign({}, defaultProps, { isDefaultExpanded: false, expandableContent: "Expandable Content", expandableSectionProps: { isExpanded: true } })));
|
|
151
|
+
expect(screen.getByRole('button', { name: defaultProps.titleText })).toHaveAttribute('aria-expanded', 'true');
|
|
152
|
+
expect(screen.getByText('Expandable Content')).toBeVisible();
|
|
153
|
+
});
|
|
154
|
+
it('expandableSectionProps.onToggle overrides internal onToggle behavior', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
155
|
+
const user = userEvent.setup();
|
|
156
|
+
const customOnToggle = jest.fn();
|
|
157
|
+
render(_jsx(ToolCall, Object.assign({}, defaultProps, { isDefaultExpanded: false, expandableContent: "Expandable Content", expandableSectionProps: { onToggle: customOnToggle } })));
|
|
158
|
+
const toggleButton = screen.getByRole('button', { name: defaultProps.titleText });
|
|
159
|
+
expect(toggleButton).toHaveAttribute('aria-expanded', 'false');
|
|
160
|
+
yield user.click(toggleButton);
|
|
161
|
+
expect(customOnToggle).toHaveBeenCalledTimes(1);
|
|
162
|
+
expect(toggleButton).toHaveAttribute('aria-expanded', 'false');
|
|
163
|
+
expect(screen.queryByText('Expandable Content')).not.toBeVisible();
|
|
164
|
+
}));
|
|
139
165
|
});
|
|
@@ -3,6 +3,8 @@ import { type FunctionComponent } from 'react';
|
|
|
3
3
|
export interface ToolResponseProps {
|
|
4
4
|
/** Toggle content shown for expandable section */
|
|
5
5
|
toggleContent: React.ReactNode;
|
|
6
|
+
/** Flag indicating whether the expandable content is expanded by default. */
|
|
7
|
+
isDefaultExpanded?: boolean;
|
|
6
8
|
/** Additional props passed to expandable section */
|
|
7
9
|
expandableSectionProps?: Omit<ExpandableSectionProps, 'ref'>;
|
|
8
10
|
/** Subheading rendered inside expandable section */
|
|
@@ -4,8 +4,8 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
4
4
|
// ============================================================================
|
|
5
5
|
import { Card, CardBody, CardTitle, Divider, ExpandableSection } from '@patternfly/react-core';
|
|
6
6
|
import { useState } from 'react';
|
|
7
|
-
export const ToolResponse = ({ body, cardProps, expandableSectionProps, subheading, cardBody, cardTitle, cardBodyProps, toggleContent, toolResponseCardBodyProps, toolResponseCardDividerProps, toolResponseCardProps, toolResponseCardTitleProps }) => {
|
|
8
|
-
const [isExpanded, setIsExpanded] = useState(
|
|
7
|
+
export const ToolResponse = ({ body, cardProps, expandableSectionProps, subheading, cardBody, cardTitle, cardBodyProps, toggleContent, isDefaultExpanded = true, toolResponseCardBodyProps, toolResponseCardDividerProps, toolResponseCardProps, toolResponseCardTitleProps }) => {
|
|
8
|
+
const [isExpanded, setIsExpanded] = useState(isDefaultExpanded);
|
|
9
9
|
const onToggle = (_event, isExpanded) => {
|
|
10
10
|
setIsExpanded(isExpanded);
|
|
11
11
|
};
|
|
@@ -1,5 +1,15 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
1
10
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
11
|
import { render, screen } from '@testing-library/react';
|
|
12
|
+
import userEvent from '@testing-library/user-event';
|
|
3
13
|
import '@testing-library/jest-dom';
|
|
4
14
|
import ToolResponse from './ToolResponse';
|
|
5
15
|
describe('ToolResponse', () => {
|
|
@@ -76,4 +86,34 @@ describe('ToolResponse', () => {
|
|
|
76
86
|
const { container } = render(_jsx(ToolResponse, Object.assign({}, defaultProps, { cardBody: undefined })));
|
|
77
87
|
expect(container.querySelector('.pf-v6-c-divider')).toBeFalsy();
|
|
78
88
|
});
|
|
89
|
+
it('Renders expanded by default', () => {
|
|
90
|
+
render(_jsx(ToolResponse, Object.assign({}, defaultProps)));
|
|
91
|
+
expect(screen.getByRole('button', { name: defaultProps.toggleContent })).toHaveAttribute('aria-expanded', 'true');
|
|
92
|
+
expect(screen.getByText(defaultProps.cardTitle)).toBeVisible();
|
|
93
|
+
expect(screen.getByText(defaultProps.cardBody)).toBeVisible();
|
|
94
|
+
});
|
|
95
|
+
it('Renders collapsed when isDefaultExpanded is false', () => {
|
|
96
|
+
render(_jsx(ToolResponse, Object.assign({ isDefaultExpanded: false }, defaultProps)));
|
|
97
|
+
expect(screen.getByRole('button', { name: defaultProps.toggleContent })).toHaveAttribute('aria-expanded', 'false');
|
|
98
|
+
expect(screen.getByText(defaultProps.cardTitle)).not.toBeVisible();
|
|
99
|
+
expect(screen.getByText(defaultProps.cardBody)).not.toBeVisible();
|
|
100
|
+
});
|
|
101
|
+
it('expandableSectionProps.isExpanded overrides isDefaultExpanded', () => {
|
|
102
|
+
render(_jsx(ToolResponse, Object.assign({}, defaultProps, { isDefaultExpanded: false, expandableSectionProps: { isExpanded: true } })));
|
|
103
|
+
expect(screen.getByRole('button', { name: defaultProps.toggleContent })).toHaveAttribute('aria-expanded', 'true');
|
|
104
|
+
expect(screen.getByText(defaultProps.cardTitle)).toBeVisible();
|
|
105
|
+
expect(screen.getByText(defaultProps.cardBody)).toBeVisible();
|
|
106
|
+
});
|
|
107
|
+
it('expandableSectionProps.onToggle overrides internal onToggle behavior', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
108
|
+
const user = userEvent.setup();
|
|
109
|
+
const customOnToggle = jest.fn();
|
|
110
|
+
render(_jsx(ToolResponse, Object.assign({}, defaultProps, { isDefaultExpanded: false, expandableSectionProps: { onToggle: customOnToggle } })));
|
|
111
|
+
const toggleButton = screen.getByRole('button', { name: defaultProps.toggleContent });
|
|
112
|
+
expect(toggleButton).toHaveAttribute('aria-expanded', 'false');
|
|
113
|
+
yield user.click(toggleButton);
|
|
114
|
+
expect(customOnToggle).toHaveBeenCalledTimes(1);
|
|
115
|
+
expect(toggleButton).toHaveAttribute('aria-expanded', 'false');
|
|
116
|
+
expect(screen.getByText(defaultProps.cardTitle)).not.toBeVisible();
|
|
117
|
+
expect(screen.getByText(defaultProps.cardBody)).not.toBeVisible();
|
|
118
|
+
}));
|
|
79
119
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@patternfly/chatbot",
|
|
3
|
-
"version": "6.5.0-prerelease.
|
|
3
|
+
"version": "6.5.0-prerelease.22",
|
|
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",
|
package/patternfly-docs/content/extensions/chatbot/examples/Messages/MessageWithDeepThinking.tsx
CHANGED
|
@@ -3,15 +3,29 @@ import Message from '@patternfly/chatbot/dist/dynamic/Message';
|
|
|
3
3
|
import patternflyAvatar from './patternfly_avatar.jpg';
|
|
4
4
|
|
|
5
5
|
export const MessageWithDeepThinkingExample: FunctionComponent = () => (
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
6
|
+
<>
|
|
7
|
+
<Message
|
|
8
|
+
name="Bot"
|
|
9
|
+
role="bot"
|
|
10
|
+
avatar={patternflyAvatar}
|
|
11
|
+
content="This example has a body description that's within the recommended limit of 2 lines."
|
|
12
|
+
deepThinking={{
|
|
13
|
+
toggleContent: 'Show thinking',
|
|
14
|
+
subheading: 'Thought for 3 seconds',
|
|
15
|
+
body: "Here's why I said this."
|
|
16
|
+
}}
|
|
17
|
+
/>
|
|
18
|
+
<Message
|
|
19
|
+
name="Bot"
|
|
20
|
+
role="bot"
|
|
21
|
+
avatar={patternflyAvatar}
|
|
22
|
+
content="This example has deep thinking that is collapsed by default:"
|
|
23
|
+
deepThinking={{
|
|
24
|
+
isDefaultExpanded: false,
|
|
25
|
+
toggleContent: 'Show thinking',
|
|
26
|
+
subheading: 'Thought for 3 seconds',
|
|
27
|
+
body: "Here's why I said this."
|
|
28
|
+
}}
|
|
29
|
+
/>
|
|
30
|
+
</>
|
|
17
31
|
);
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { FunctionComponent } from 'react';
|
|
2
|
+
|
|
3
|
+
import Message from '@patternfly/chatbot/dist/dynamic/Message';
|
|
4
|
+
import patternflyAvatar from './patternfly_avatar.jpg';
|
|
5
|
+
|
|
6
|
+
export const MessageWithMultipleActionGroups: FunctionComponent = () => (
|
|
7
|
+
<>
|
|
8
|
+
<Message
|
|
9
|
+
name="Bot"
|
|
10
|
+
role="bot"
|
|
11
|
+
avatar={patternflyAvatar}
|
|
12
|
+
content="This message contains multiple action groups, each with their own selection persistence: \n1. Feedback actions (thumbs up/down) with persistent selections \n2. Utility actions (copy, download) with non-persistent selections \n3. Listen action with persistent selection"
|
|
13
|
+
actions={[
|
|
14
|
+
{
|
|
15
|
+
actions: {
|
|
16
|
+
// eslint-disable-next-line no-console
|
|
17
|
+
positive: { onClick: () => console.log('Good response') },
|
|
18
|
+
// eslint-disable-next-line no-console
|
|
19
|
+
negative: { onClick: () => console.log('Bad response') }
|
|
20
|
+
},
|
|
21
|
+
persistActionSelection: true
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
actions: {
|
|
25
|
+
// eslint-disable-next-line no-console
|
|
26
|
+
copy: { onClick: () => console.log('Copy') },
|
|
27
|
+
// eslint-disable-next-line no-console
|
|
28
|
+
download: { onClick: () => console.log('Download') }
|
|
29
|
+
},
|
|
30
|
+
persistActionSelection: false
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
actions: {
|
|
34
|
+
// eslint-disable-next-line no-console
|
|
35
|
+
listen: { onClick: () => console.log('Listen') }
|
|
36
|
+
},
|
|
37
|
+
persistActionSelection: true
|
|
38
|
+
}
|
|
39
|
+
]}
|
|
40
|
+
/>
|
|
41
|
+
<Message
|
|
42
|
+
name="Bot"
|
|
43
|
+
role="bot"
|
|
44
|
+
avatar={patternflyAvatar}
|
|
45
|
+
content="This message contains multiple action groups, both of which persist selections."
|
|
46
|
+
actions={[
|
|
47
|
+
{
|
|
48
|
+
// eslint-disable-next-line no-console
|
|
49
|
+
positive: { onClick: () => console.log('Good response') },
|
|
50
|
+
// eslint-disable-next-line no-console
|
|
51
|
+
negative: { onClick: () => console.log('Bad response') }
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
// eslint-disable-next-line no-console
|
|
55
|
+
listen: { onClick: () => console.log('Listen') }
|
|
56
|
+
}
|
|
57
|
+
]}
|
|
58
|
+
persistActionSelection={true}
|
|
59
|
+
/>
|
|
60
|
+
</>
|
|
61
|
+
);
|
package/patternfly-docs/content/extensions/chatbot/examples/Messages/MessageWithToolCall.tsx
CHANGED
|
@@ -31,7 +31,7 @@ export const MessageWithToolCallExample: FunctionComponent = () => {
|
|
|
31
31
|
name="Bot"
|
|
32
32
|
role="bot"
|
|
33
33
|
avatar={patternflyAvatar}
|
|
34
|
-
content="This example has an expandable tool call title, with an additional description
|
|
34
|
+
content="This example has an expandable tool call title, with an additional description:"
|
|
35
35
|
toolCall={{
|
|
36
36
|
titleText: "Calling 'awesome_tool_expansion'",
|
|
37
37
|
expandableContent: 'This is the expandable content for the tool call.',
|
|
@@ -39,6 +39,19 @@ export const MessageWithToolCallExample: FunctionComponent = () => {
|
|
|
39
39
|
loadingText: "Loading 'awesome_tool_expansion'"
|
|
40
40
|
}}
|
|
41
41
|
/>
|
|
42
|
+
<Message
|
|
43
|
+
name="Bot"
|
|
44
|
+
role="bot"
|
|
45
|
+
avatar={patternflyAvatar}
|
|
46
|
+
content="This example has an expandable tool call that is expanded by default:"
|
|
47
|
+
toolCall={{
|
|
48
|
+
isDefaultExpanded: true,
|
|
49
|
+
titleText: "Calling 'awesome_tool_expansion'",
|
|
50
|
+
expandableContent: 'This is the expandable content for the tool call.',
|
|
51
|
+
isLoading: toolCallsAreLoading,
|
|
52
|
+
loadingText: "Loading 'awesome_tool_expansion'"
|
|
53
|
+
}}
|
|
54
|
+
/>
|
|
42
55
|
</FlexItem>
|
|
43
56
|
</Flex>
|
|
44
57
|
);
|