@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
@@ -23,113 +23,230 @@ export const MessageWithToolResponseExample: FunctionComponent = () => {
23
23
  };
24
24
 
25
25
  return (
26
- <Message
27
- name="Bot"
28
- role="bot"
29
- avatar={patternflyAvatar}
30
- content="This example has a body description that's within the recommended limit of 2 lines:"
31
- toolResponse={{
32
- toggleContent: 'Tool response: toolName',
33
- subheading: 'Thought for 3 seconds',
34
- body: "Here's the summary for your toolName response:",
35
- cardTitle: (
36
- <Flex alignItems={{ default: 'alignItemsCenter' }} justifyContent={{ default: 'justifyContentSpaceBetween' }}>
37
- <FlexItem>
38
- <Flex direction={{ default: 'column' }} gap={{ default: 'gapXs' }}>
39
- <FlexItem grow={{ default: 'grow' }}>
40
- <Flex gap={{ default: 'gapXs' }}>
41
- <FlexItem>
42
- <WrenchIcon style={{ color: 'var(--pf-t--global--icon--color--brand--default' }} />
43
- </FlexItem>
44
- <FlexItem>toolName</FlexItem>
45
- </Flex>
46
- </FlexItem>
47
- <FlexItem>
48
- <Flex gap={{ default: 'gapSm' }} style={{ fontSize: '12px', fontWeight: '400' }}>
49
- <FlexItem>Execution time:</FlexItem>
50
- <FlexItem>0.12 seconds</FlexItem>
51
- </Flex>
52
- </FlexItem>
53
- </Flex>
54
- </FlexItem>
55
- <FlexItem>
56
- <Button
57
- variant="plain"
58
- aria-label="Copy tool response to clipboard"
59
- icon={<CopyIcon style={{ color: 'var(--pf-t--global--icon--color--subtle)' }} />}
60
- ></Button>
61
- </FlexItem>
62
- </Flex>
63
- ),
64
- cardBody: (
65
- <>
66
- <DescriptionList
67
- style={{ '--pf-v6-c-description-list--RowGap': 'var(--pf-t--global--spacer--md)' } as any}
68
- aria-label="Tool response"
26
+ <>
27
+ <Message
28
+ name="Bot"
29
+ role="bot"
30
+ avatar={patternflyAvatar}
31
+ content="This message has a body description that's within the recommended limit of 2 lines:"
32
+ toolResponse={{
33
+ toggleContent: 'Tool response: toolName',
34
+ subheading: 'Thought for 3 seconds',
35
+ body: "Here's the summary for your toolName response:",
36
+ cardTitle: (
37
+ <Flex
38
+ alignItems={{ default: 'alignItemsCenter' }}
39
+ justifyContent={{ default: 'justifyContentSpaceBetween' }}
69
40
  >
70
- <DescriptionListGroup
71
- style={{ '--pf-v6-c-description-list__group--RowGap': 'var(--pf-t--global--spacer--xs)' } as any}
41
+ <FlexItem>
42
+ <Flex direction={{ default: 'column' }} gap={{ default: 'gapXs' }}>
43
+ <FlexItem grow={{ default: 'grow' }}>
44
+ <Flex gap={{ default: 'gapXs' }}>
45
+ <FlexItem>
46
+ <WrenchIcon style={{ color: 'var(--pf-t--global--icon--color--brand--default' }} />
47
+ </FlexItem>
48
+ <FlexItem>toolName</FlexItem>
49
+ </Flex>
50
+ </FlexItem>
51
+ <FlexItem>
52
+ <Flex gap={{ default: 'gapSm' }} style={{ fontSize: '12px', fontWeight: '400' }}>
53
+ <FlexItem>Execution time:</FlexItem>
54
+ <FlexItem>0.12 seconds</FlexItem>
55
+ </Flex>
56
+ </FlexItem>
57
+ </Flex>
58
+ </FlexItem>
59
+ <FlexItem>
60
+ <Button
61
+ variant="plain"
62
+ aria-label="Copy tool response to clipboard"
63
+ icon={<CopyIcon style={{ color: 'var(--pf-t--global--icon--color--subtle)' }} />}
64
+ ></Button>
65
+ </FlexItem>
66
+ </Flex>
67
+ ),
68
+ cardBody: (
69
+ <>
70
+ <DescriptionList
71
+ style={{ '--pf-v6-c-description-list--RowGap': 'var(--pf-t--global--spacer--md)' } as any}
72
+ aria-label="Tool response"
72
73
  >
73
- <DescriptionListTerm>Parameters</DescriptionListTerm>
74
- <DescriptionListDescription>
75
- <Flex direction={{ default: 'column' }}>
76
- <FlexItem>Optional description text for parameters.</FlexItem>
77
- <FlexItem>
78
- <Flex gap={{ default: 'gapSm' }}>
79
- <FlexItem>
80
- <Label variant="outline" color="blue">
81
- type
82
- </Label>
83
- </FlexItem>
84
- <FlexItem>
85
- <Label variant="outline" color="blue">
86
- properties
87
- </Label>
88
- </FlexItem>
89
- <FlexItem>
90
- <Label variant="outline" color="blue">
91
- label
92
- </Label>
93
- </FlexItem>
94
- <FlexItem>
95
- <Label variant="outline" color="blue">
96
- label
97
- </Label>
98
- </FlexItem>
99
- </Flex>
100
- </FlexItem>
101
- </Flex>
102
- </DescriptionListDescription>
103
- </DescriptionListGroup>
104
- <DescriptionListGroup
105
- style={{ '--pf-v6-c-description-list__group--RowGap': 'var(--pf-t--global--spacer--xs)' } as any}
74
+ <DescriptionListGroup
75
+ style={{ '--pf-v6-c-description-list__group--RowGap': 'var(--pf-t--global--spacer--xs)' } as any}
76
+ >
77
+ <DescriptionListTerm>Parameters</DescriptionListTerm>
78
+ <DescriptionListDescription>
79
+ <Flex direction={{ default: 'column' }}>
80
+ <FlexItem>Optional description text for parameters.</FlexItem>
81
+ <FlexItem>
82
+ <Flex gap={{ default: 'gapSm' }}>
83
+ <FlexItem>
84
+ <Label variant="outline" color="blue">
85
+ type
86
+ </Label>
87
+ </FlexItem>
88
+ <FlexItem>
89
+ <Label variant="outline" color="blue">
90
+ properties
91
+ </Label>
92
+ </FlexItem>
93
+ <FlexItem>
94
+ <Label variant="outline" color="blue">
95
+ label
96
+ </Label>
97
+ </FlexItem>
98
+ <FlexItem>
99
+ <Label variant="outline" color="blue">
100
+ label
101
+ </Label>
102
+ </FlexItem>
103
+ </Flex>
104
+ </FlexItem>
105
+ </Flex>
106
+ </DescriptionListDescription>
107
+ </DescriptionListGroup>
108
+ <DescriptionListGroup
109
+ style={{ '--pf-v6-c-description-list__group--RowGap': 'var(--pf-t--global--spacer--xs)' } as any}
110
+ >
111
+ <DescriptionListTerm>Response</DescriptionListTerm>
112
+ <DescriptionListDescription>
113
+ <ExpandableSection
114
+ variant={ExpandableSectionVariant.truncate}
115
+ toggleTextExpanded="show less of response"
116
+ toggleTextCollapsed="show more of response"
117
+ onToggle={onToggle}
118
+ isExpanded={isExpanded}
119
+ style={
120
+ {
121
+ '--pf-v6-c-expandable-section__content--Opacity': '1',
122
+ '--pf-v6-c-expandable-section__content--PaddingInlineStart': 0,
123
+ '--pf-v6-c-expandable-section__content--TranslateY': 0,
124
+ '--pf-v6-c-expandable-section--m-expand-top__content--TranslateY': 0
125
+ } as any
126
+ }
127
+ >
128
+ Descriptive text about the tool response, including completion status, details on the data that
129
+ was processed, or anything else relevant to the use case.
130
+ </ExpandableSection>
131
+ </DescriptionListDescription>
132
+ </DescriptionListGroup>
133
+ </DescriptionList>
134
+ </>
135
+ )
136
+ }}
137
+ />
138
+ <Message
139
+ name="Bot"
140
+ role="bot"
141
+ avatar={patternflyAvatar}
142
+ content="This message has a tool response that is collapsed by default:"
143
+ toolResponse={{
144
+ isDefaultExpanded: false,
145
+ toggleContent: 'Tool response: toolName',
146
+ subheading: 'Thought for 3 seconds',
147
+ body: "Here's the summary for your toolName response:",
148
+ cardTitle: (
149
+ <Flex
150
+ alignItems={{ default: 'alignItemsCenter' }}
151
+ justifyContent={{ default: 'justifyContentSpaceBetween' }}
152
+ >
153
+ <FlexItem>
154
+ <Flex direction={{ default: 'column' }} gap={{ default: 'gapXs' }}>
155
+ <FlexItem grow={{ default: 'grow' }}>
156
+ <Flex gap={{ default: 'gapXs' }}>
157
+ <FlexItem>
158
+ <WrenchIcon style={{ color: 'var(--pf-t--global--icon--color--brand--default' }} />
159
+ </FlexItem>
160
+ <FlexItem>toolName</FlexItem>
161
+ </Flex>
162
+ </FlexItem>
163
+ <FlexItem>
164
+ <Flex gap={{ default: 'gapSm' }} style={{ fontSize: '12px', fontWeight: '400' }}>
165
+ <FlexItem>Execution time:</FlexItem>
166
+ <FlexItem>0.12 seconds</FlexItem>
167
+ </Flex>
168
+ </FlexItem>
169
+ </Flex>
170
+ </FlexItem>
171
+ <FlexItem>
172
+ <Button
173
+ variant="plain"
174
+ aria-label="Copy tool response to clipboard"
175
+ icon={<CopyIcon style={{ color: 'var(--pf-t--global--icon--color--subtle)' }} />}
176
+ ></Button>
177
+ </FlexItem>
178
+ </Flex>
179
+ ),
180
+ cardBody: (
181
+ <>
182
+ <DescriptionList
183
+ style={{ '--pf-v6-c-description-list--RowGap': 'var(--pf-t--global--spacer--md)' } as any}
184
+ aria-label="Tool response"
106
185
  >
107
- <DescriptionListTerm>Response</DescriptionListTerm>
108
- <DescriptionListDescription>
109
- <ExpandableSection
110
- variant={ExpandableSectionVariant.truncate}
111
- toggleTextExpanded="show less of response"
112
- toggleTextCollapsed="show more of response"
113
- onToggle={onToggle}
114
- isExpanded={isExpanded}
115
- style={
116
- {
117
- '--pf-v6-c-expandable-section__content--Opacity': '1',
118
- '--pf-v6-c-expandable-section__content--PaddingInlineStart': 0,
119
- '--pf-v6-c-expandable-section__content--TranslateY': 0,
120
- '--pf-v6-c-expandable-section--m-expand-top__content--TranslateY': 0
121
- } as any
122
- }
123
- >
124
- Descriptive text about the tool response, including completion status, details on the data that was
125
- processed, or anything else relevant to the use case.
126
- </ExpandableSection>
127
- </DescriptionListDescription>
128
- </DescriptionListGroup>
129
- </DescriptionList>
130
- </>
131
- )
132
- }}
133
- />
186
+ <DescriptionListGroup
187
+ style={{ '--pf-v6-c-description-list__group--RowGap': 'var(--pf-t--global--spacer--xs)' } as any}
188
+ >
189
+ <DescriptionListTerm>Parameters</DescriptionListTerm>
190
+ <DescriptionListDescription>
191
+ <Flex direction={{ default: 'column' }}>
192
+ <FlexItem>Optional description text for parameters.</FlexItem>
193
+ <FlexItem>
194
+ <Flex gap={{ default: 'gapSm' }}>
195
+ <FlexItem>
196
+ <Label variant="outline" color="blue">
197
+ type
198
+ </Label>
199
+ </FlexItem>
200
+ <FlexItem>
201
+ <Label variant="outline" color="blue">
202
+ properties
203
+ </Label>
204
+ </FlexItem>
205
+ <FlexItem>
206
+ <Label variant="outline" color="blue">
207
+ label
208
+ </Label>
209
+ </FlexItem>
210
+ <FlexItem>
211
+ <Label variant="outline" color="blue">
212
+ label
213
+ </Label>
214
+ </FlexItem>
215
+ </Flex>
216
+ </FlexItem>
217
+ </Flex>
218
+ </DescriptionListDescription>
219
+ </DescriptionListGroup>
220
+ <DescriptionListGroup
221
+ style={{ '--pf-v6-c-description-list__group--RowGap': 'var(--pf-t--global--spacer--xs)' } as any}
222
+ >
223
+ <DescriptionListTerm>Response</DescriptionListTerm>
224
+ <DescriptionListDescription>
225
+ <ExpandableSection
226
+ variant={ExpandableSectionVariant.truncate}
227
+ toggleTextExpanded="show less of response"
228
+ toggleTextCollapsed="show more of response"
229
+ onToggle={onToggle}
230
+ isExpanded={isExpanded}
231
+ style={
232
+ {
233
+ '--pf-v6-c-expandable-section__content--Opacity': '1',
234
+ '--pf-v6-c-expandable-section__content--PaddingInlineStart': 0,
235
+ '--pf-v6-c-expandable-section__content--TranslateY': 0,
236
+ '--pf-v6-c-expandable-section--m-expand-top__content--TranslateY': 0
237
+ } as any
238
+ }
239
+ >
240
+ Descriptive text about the tool response, including completion status, details on the data that
241
+ was processed, or anything else relevant to the use case.
242
+ </ExpandableSection>
243
+ </DescriptionListDescription>
244
+ </DescriptionListGroup>
245
+ </DescriptionList>
246
+ </>
247
+ )
248
+ }}
249
+ />
250
+ </>
134
251
  );
135
252
  };
@@ -122,6 +122,24 @@ When `persistActionSelection` is `true`:
122
122
 
123
123
  ```
124
124
 
125
+ ### Multiple messsage action groups
126
+
127
+ To maintain finer control over message action selection behavior, you can create groups of actions by passing an array of objects to the `actions` prop. This allows you to separate actions into conceptually or functionally different groups and implement different behavior for each group as needed. For example, you could separate feedback actions (thumbs up/down) form utility actions (copy and download), and have different selection behaviors for each group.
128
+
129
+ To provide flexibility for your use case, there are 2 approaches you can take to pass an array of objects to `actions`:
130
+
131
+ 1. Pass an array of objects, where each object contains:
132
+
133
+ - `actions`: An `action` object containing the actions for that group (the same format as a single `action` object)
134
+
135
+ - `persistActionSelection` (optional): A boolean to control whether selections persists for this specific group
136
+
137
+ 2. Pass an array of `action` objects (the same format as a single `action` object) and (optionally) a value for the `persistActionSelection` property that will apply to all groups.
138
+
139
+ ```js file="./MessageWithMultipleActionGroups.tsx"
140
+
141
+ ```
142
+
125
143
  ### Custom message actions
126
144
 
127
145
  Beyond the standard message actions (good response, bad response, copy, share, or listen), you can add custom actions to a bot message by passing an `actions` object to the `<Message>` component. This object can contain the following customizations:
@@ -1,4 +1,5 @@
1
1
  import { render, screen } from '@testing-library/react';
2
+ import userEvent from '@testing-library/user-event';
2
3
  import '@testing-library/jest-dom';
3
4
  import DeepThinking from './DeepThinking';
4
5
 
@@ -58,4 +59,64 @@ describe('DeepThinking', () => {
58
59
  const subheadingContainer = container.querySelector('.pf-chatbot__tool-response-subheading');
59
60
  expect(subheadingContainer).toBeFalsy();
60
61
  });
62
+
63
+ it('should pass through cardBodyProps', () => {
64
+ render(
65
+ <DeepThinking {...defaultProps} body="Thinking content" cardBodyProps={{ className: 'custom-card-body-class' }} />
66
+ );
67
+
68
+ const cardBody = screen.getByText('Thinking content').closest('.pf-v6-c-card__body');
69
+ expect(cardBody).toHaveClass('custom-card-body-class');
70
+ });
71
+
72
+ it('Renders expanded by default', () => {
73
+ render(<DeepThinking {...defaultProps} body="Thinking content" />);
74
+
75
+ expect(screen.getByRole('button', { name: defaultProps.toggleContent })).toHaveAttribute('aria-expanded', 'true');
76
+ expect(screen.getByText('Thinking content')).toBeVisible();
77
+ });
78
+
79
+ it('Renders collapsed when isDefaultExpanded is false', () => {
80
+ render(<DeepThinking isDefaultExpanded={false} {...defaultProps} body="Thinking content" />);
81
+
82
+ expect(screen.getByRole('button', { name: defaultProps.toggleContent })).toHaveAttribute('aria-expanded', 'false');
83
+ expect(screen.getByText('Thinking content')).not.toBeVisible();
84
+ });
85
+
86
+ it('expandableSectionProps.isExpanded overrides isDefaultExpanded', () => {
87
+ render(
88
+ <DeepThinking
89
+ {...defaultProps}
90
+ isDefaultExpanded={false}
91
+ body="Thinking content"
92
+ expandableSectionProps={{ isExpanded: true }}
93
+ />
94
+ );
95
+
96
+ expect(screen.getByRole('button', { name: defaultProps.toggleContent })).toHaveAttribute('aria-expanded', 'true');
97
+ expect(screen.getByText('Thinking content')).toBeVisible();
98
+ });
99
+
100
+ it('expandableSectionProps.onToggle overrides internal onToggle behavior', async () => {
101
+ const user = userEvent.setup();
102
+ const customOnToggle = jest.fn();
103
+
104
+ render(
105
+ <DeepThinking
106
+ {...defaultProps}
107
+ isDefaultExpanded={false}
108
+ body="Thinking content"
109
+ expandableSectionProps={{ onToggle: customOnToggle }}
110
+ />
111
+ );
112
+
113
+ const toggleButton = screen.getByRole('button', { name: defaultProps.toggleContent });
114
+ expect(toggleButton).toHaveAttribute('aria-expanded', 'false');
115
+
116
+ await user.click(toggleButton);
117
+
118
+ expect(customOnToggle).toHaveBeenCalled();
119
+ expect(toggleButton).toHaveAttribute('aria-expanded', 'false');
120
+ expect(screen.getByText('Thinking content')).not.toBeVisible();
121
+ });
61
122
  });
@@ -14,6 +14,8 @@ import { useState, type FunctionComponent } from 'react';
14
14
  export interface DeepThinkingProps {
15
15
  /** Toggle content shown for expandable section */
16
16
  toggleContent: React.ReactNode;
17
+ /** Flag indicating whether the expandable content is expanded by default. */
18
+ isDefaultExpanded?: boolean;
17
19
  /** Additional props passed to expandable section */
18
20
  expandableSectionProps?: Omit<ExpandableSectionProps, 'ref'>;
19
21
  /** Subheading rendered inside expandable section */
@@ -32,9 +34,10 @@ export const DeepThinking: FunctionComponent<DeepThinkingProps> = ({
32
34
  expandableSectionProps,
33
35
  subheading,
34
36
  toggleContent,
37
+ isDefaultExpanded = true,
35
38
  cardBodyProps
36
39
  }: DeepThinkingProps) => {
37
- const [isExpanded, setIsExpanded] = useState(true);
40
+ const [isExpanded, setIsExpanded] = useState(isDefaultExpanded);
38
41
 
39
42
  const onToggle = (_event: React.MouseEvent, isExpanded: boolean) => {
40
43
  setIsExpanded(isExpanded);