@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.
Files changed (41) hide show
  1. package/dist/cjs/DeepThinking/DeepThinking.d.ts +2 -0
  2. package/dist/cjs/DeepThinking/DeepThinking.js +2 -2
  3. package/dist/cjs/DeepThinking/DeepThinking.test.js +41 -0
  4. package/dist/cjs/Message/Message.d.ts +15 -3
  5. package/dist/cjs/Message/Message.js +1 -1
  6. package/dist/cjs/Message/Message.test.js +125 -2
  7. package/dist/cjs/ToolCall/ToolCall.d.ts +2 -0
  8. package/dist/cjs/ToolCall/ToolCall.js +7 -2
  9. package/dist/cjs/ToolCall/ToolCall.test.js +26 -0
  10. package/dist/cjs/ToolResponse/ToolResponse.d.ts +2 -0
  11. package/dist/cjs/ToolResponse/ToolResponse.js +2 -2
  12. package/dist/cjs/ToolResponse/ToolResponse.test.js +40 -0
  13. package/dist/css/main.css +10 -0
  14. package/dist/css/main.css.map +1 -1
  15. package/dist/esm/DeepThinking/DeepThinking.d.ts +2 -0
  16. package/dist/esm/DeepThinking/DeepThinking.js +2 -2
  17. package/dist/esm/DeepThinking/DeepThinking.test.js +41 -0
  18. package/dist/esm/Message/Message.d.ts +15 -3
  19. package/dist/esm/Message/Message.js +1 -1
  20. package/dist/esm/Message/Message.test.js +125 -2
  21. package/dist/esm/ToolCall/ToolCall.d.ts +2 -0
  22. package/dist/esm/ToolCall/ToolCall.js +7 -2
  23. package/dist/esm/ToolCall/ToolCall.test.js +26 -0
  24. package/dist/esm/ToolResponse/ToolResponse.d.ts +2 -0
  25. package/dist/esm/ToolResponse/ToolResponse.js +2 -2
  26. package/dist/esm/ToolResponse/ToolResponse.test.js +40 -0
  27. package/package.json +1 -1
  28. package/patternfly-docs/content/extensions/chatbot/examples/Messages/MessageWithDeepThinking.tsx +25 -11
  29. package/patternfly-docs/content/extensions/chatbot/examples/Messages/MessageWithMultipleActionGroups.tsx +61 -0
  30. package/patternfly-docs/content/extensions/chatbot/examples/Messages/MessageWithToolCall.tsx +14 -1
  31. package/patternfly-docs/content/extensions/chatbot/examples/Messages/MessageWithToolResponse.tsx +222 -105
  32. package/patternfly-docs/content/extensions/chatbot/examples/Messages/Messages.md +18 -0
  33. package/src/DeepThinking/DeepThinking.test.tsx +61 -0
  34. package/src/DeepThinking/DeepThinking.tsx +4 -1
  35. package/src/Message/Message.test.tsx +198 -2
  36. package/src/Message/Message.tsx +35 -6
  37. package/src/ResponseActions/ResponseActions.scss +11 -0
  38. package/src/ToolCall/ToolCall.test.tsx +51 -0
  39. package/src/ToolCall/ToolCall.tsx +12 -1
  40. package/src/ToolResponse/ToolResponse.test.tsx +44 -0
  41. 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('should be able to show actions', () => __awaiter(void 0, void 0, void 0, function* () {
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 })).toBeTruthy();
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(true);
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.20",
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",
@@ -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
- <Message
7
- name="Bot"
8
- role="bot"
9
- avatar={patternflyAvatar}
10
- content="This example has a body description that's within the recommended limit of 2 lines."
11
- deepThinking={{
12
- toggleContent: 'Show thinking',
13
- subheading: 'Thought for 3 seconds',
14
- body: "Here's why I said this."
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
+ );
@@ -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
  );