@patternfly/chatbot 6.3.2 → 6.4.0-prerelease.11

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 (134) hide show
  1. package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.d.ts +2 -0
  2. package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.js +2 -2
  3. package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.test.js +6 -6
  4. package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.d.ts +27 -4
  5. package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.js +8 -14
  6. package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.test.js +53 -2
  7. package/dist/cjs/ChatbotHeader/ChatbotHeaderMenu.js +1 -1
  8. package/dist/cjs/ChatbotHeader/ChatbotHeaderMenu.test.js +1 -1
  9. package/dist/cjs/ChatbotHeader/ChatbotHeaderNewChatButton.d.ts +18 -0
  10. package/dist/cjs/ChatbotHeader/ChatbotHeaderNewChatButton.js +25 -0
  11. package/dist/cjs/ChatbotHeader/ChatbotHeaderNewChatButton.test.d.ts +1 -0
  12. package/dist/cjs/ChatbotHeader/ChatbotHeaderNewChatButton.test.js +22 -0
  13. package/dist/cjs/ChatbotHeader/index.d.ts +1 -0
  14. package/dist/cjs/ChatbotHeader/index.js +1 -0
  15. package/dist/cjs/FileDropZone/FileDropZone.d.ts +1 -2
  16. package/dist/cjs/Message/Message.d.ts +9 -2
  17. package/dist/cjs/Message/Message.js +40 -34
  18. package/dist/cjs/Message/Message.test.js +37 -0
  19. package/dist/cjs/Message/MessageInput.d.ts +3 -1
  20. package/dist/cjs/Message/MessageInput.js +2 -2
  21. package/dist/cjs/MessageBar/AttachButton.d.ts +2 -2
  22. package/dist/cjs/MessageBar/MessageBar.d.ts +2 -2
  23. package/dist/cjs/MessageBox/JumpButton.d.ts +5 -0
  24. package/dist/cjs/MessageBox/JumpButton.js +1 -1
  25. package/dist/cjs/MessageBox/JumpButton.test.js +4 -4
  26. package/dist/cjs/MessageBox/MessageBox.d.ts +9 -0
  27. package/dist/cjs/MessageBox/MessageBox.js +2 -2
  28. package/dist/cjs/MessageBox/MessageBox.test.js +2 -2
  29. package/dist/cjs/MessageDivider/MessageDivider.d.ts +9 -0
  30. package/dist/cjs/MessageDivider/MessageDivider.js +23 -0
  31. package/dist/cjs/MessageDivider/MessageDivider.test.d.ts +1 -0
  32. package/dist/cjs/MessageDivider/MessageDivider.test.js +29 -0
  33. package/dist/cjs/MessageDivider/index.d.ts +2 -0
  34. package/dist/cjs/MessageDivider/index.js +23 -0
  35. package/dist/cjs/ResponseActions/ResponseActions.d.ts +1 -0
  36. package/dist/cjs/ResponseActions/ResponseActions.js +4 -4
  37. package/dist/cjs/ResponseActions/ResponseActions.test.js +6 -1
  38. package/dist/cjs/index.d.ts +2 -0
  39. package/dist/cjs/index.js +4 -1
  40. package/dist/css/main.css +103 -81
  41. package/dist/css/main.css.map +1 -1
  42. package/dist/dynamic/MessageDivider/package.json +1 -0
  43. package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.d.ts +2 -0
  44. package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.js +2 -2
  45. package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.test.js +6 -6
  46. package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.d.ts +27 -4
  47. package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.js +10 -16
  48. package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.test.js +54 -3
  49. package/dist/esm/ChatbotHeader/ChatbotHeaderMenu.js +1 -1
  50. package/dist/esm/ChatbotHeader/ChatbotHeaderMenu.test.js +1 -1
  51. package/dist/esm/ChatbotHeader/ChatbotHeaderNewChatButton.d.ts +18 -0
  52. package/dist/esm/ChatbotHeader/ChatbotHeaderNewChatButton.js +22 -0
  53. package/dist/esm/ChatbotHeader/ChatbotHeaderNewChatButton.test.d.ts +1 -0
  54. package/dist/esm/ChatbotHeader/ChatbotHeaderNewChatButton.test.js +20 -0
  55. package/dist/esm/ChatbotHeader/index.d.ts +1 -0
  56. package/dist/esm/ChatbotHeader/index.js +1 -0
  57. package/dist/esm/FileDropZone/FileDropZone.d.ts +1 -2
  58. package/dist/esm/Message/Message.d.ts +9 -2
  59. package/dist/esm/Message/Message.js +40 -34
  60. package/dist/esm/Message/Message.test.js +37 -0
  61. package/dist/esm/Message/MessageInput.d.ts +3 -1
  62. package/dist/esm/Message/MessageInput.js +2 -2
  63. package/dist/esm/MessageBar/AttachButton.d.ts +2 -2
  64. package/dist/esm/MessageBar/MessageBar.d.ts +2 -2
  65. package/dist/esm/MessageBox/JumpButton.d.ts +5 -0
  66. package/dist/esm/MessageBox/JumpButton.js +1 -1
  67. package/dist/esm/MessageBox/JumpButton.test.js +4 -4
  68. package/dist/esm/MessageBox/MessageBox.d.ts +9 -0
  69. package/dist/esm/MessageBox/MessageBox.js +2 -2
  70. package/dist/esm/MessageBox/MessageBox.test.js +2 -2
  71. package/dist/esm/MessageDivider/MessageDivider.d.ts +9 -0
  72. package/dist/esm/MessageDivider/MessageDivider.js +21 -0
  73. package/dist/esm/MessageDivider/MessageDivider.test.d.ts +1 -0
  74. package/dist/esm/MessageDivider/MessageDivider.test.js +24 -0
  75. package/dist/esm/MessageDivider/index.d.ts +2 -0
  76. package/dist/esm/MessageDivider/index.js +2 -0
  77. package/dist/esm/ResponseActions/ResponseActions.d.ts +1 -0
  78. package/dist/esm/ResponseActions/ResponseActions.js +5 -5
  79. package/dist/esm/ResponseActions/ResponseActions.test.js +6 -1
  80. package/dist/esm/index.d.ts +2 -0
  81. package/dist/esm/index.js +2 -0
  82. package/dist/tsconfig.tsbuildinfo +1 -1
  83. package/package.json +9 -4
  84. package/patternfly-docs/content/extensions/chatbot/examples/Messages/MessageWithDividers.tsx +24 -0
  85. package/patternfly-docs/content/extensions/chatbot/examples/Messages/Messages.md +18 -1
  86. package/patternfly-docs/content/extensions/chatbot/examples/Messages/UserMessage.tsx +39 -7
  87. package/patternfly-docs/content/extensions/chatbot/examples/Messages/UserMessageWithExtraContent.tsx +401 -3
  88. package/patternfly-docs/content/extensions/chatbot/examples/UI/ChatbotConversationEditing.tsx +202 -0
  89. package/patternfly-docs/content/extensions/chatbot/examples/UI/ChatbotHeaderBasic.tsx +17 -3
  90. package/patternfly-docs/content/extensions/chatbot/examples/UI/ChatbotHeaderDrawer.tsx +45 -5
  91. package/patternfly-docs/content/extensions/chatbot/examples/UI/ChatbotHeaderDrawerWithPin.tsx +206 -0
  92. package/patternfly-docs/content/extensions/chatbot/examples/UI/UI.md +30 -4
  93. package/patternfly-docs/content/extensions/chatbot/examples/demos/Chatbot.md +33 -1
  94. package/patternfly-docs/content/extensions/chatbot/examples/demos/ChatbotDisplayMode.tsx +486 -0
  95. package/patternfly-docs/content/extensions/chatbot/examples/demos/ChatbotTranscripts.tsx +565 -0
  96. package/src/Chatbot/Chatbot.scss +1 -1
  97. package/src/ChatbotContent/ChatbotContent.scss +1 -1
  98. package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.test.tsx +6 -6
  99. package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.tsx +5 -2
  100. package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.scss +70 -32
  101. package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.test.tsx +176 -3
  102. package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.tsx +110 -60
  103. package/src/ChatbotFooter/ChatbotFooter.scss +1 -1
  104. package/src/ChatbotHeader/ChatbotHeader.scss +3 -3
  105. package/src/ChatbotHeader/ChatbotHeaderMenu.test.tsx +1 -1
  106. package/src/ChatbotHeader/ChatbotHeaderMenu.tsx +2 -2
  107. package/src/ChatbotHeader/ChatbotHeaderNewChatButton.test.tsx +25 -0
  108. package/src/ChatbotHeader/ChatbotHeaderNewChatButton.tsx +64 -0
  109. package/src/ChatbotHeader/index.ts +1 -0
  110. package/src/ChatbotModal/ChatbotModal.scss +1 -1
  111. package/src/ChatbotToggle/ChatbotToggle.scss +2 -2
  112. package/src/FileDetails/__snapshots__/FileDetails.test.tsx.snap +6 -9
  113. package/src/FileDetailsLabel/__snapshots__/FileDetailsLabel.test.tsx.snap +6 -9
  114. package/src/FileDropZone/FileDropZone.tsx +2 -2
  115. package/src/Message/Message.scss +9 -7
  116. package/src/Message/Message.test.tsx +54 -0
  117. package/src/Message/Message.tsx +70 -50
  118. package/src/Message/MessageInput.tsx +5 -1
  119. package/src/MessageBar/AttachButton.tsx +2 -2
  120. package/src/MessageBar/MessageBar.tsx +2 -2
  121. package/src/MessageBar/SendButton.scss +3 -3
  122. package/src/MessageBox/JumpButton.scss +1 -1
  123. package/src/MessageBox/JumpButton.test.tsx +4 -4
  124. package/src/MessageBox/JumpButton.tsx +20 -4
  125. package/src/MessageBox/MessageBox.test.tsx +2 -2
  126. package/src/MessageBox/MessageBox.tsx +23 -2
  127. package/src/MessageDivider/MessageDivider.scss +45 -0
  128. package/src/MessageDivider/MessageDivider.test.tsx +24 -0
  129. package/src/MessageDivider/MessageDivider.tsx +35 -0
  130. package/src/MessageDivider/index.ts +3 -0
  131. package/src/ResponseActions/ResponseActions.test.tsx +6 -1
  132. package/src/ResponseActions/ResponseActions.tsx +24 -3
  133. package/src/index.ts +3 -0
  134. package/src/main.scss +1 -52
@@ -1,8 +1,43 @@
1
- import { Fragment, FunctionComponent } from 'react';
2
-
1
+ import { Fragment, FunctionComponent, useState, useEffect } from 'react';
3
2
  import Message from '@patternfly/chatbot/dist/dynamic/Message';
4
3
  import userAvatar from './user_avatar.svg';
5
- import { Alert, Badge, Button, Card, CardBody, CardFooter, CardTitle } from '@patternfly/react-core';
4
+ import {
5
+ Accordion,
6
+ AccordionContent,
7
+ AccordionItem,
8
+ AccordionToggle,
9
+ Alert,
10
+ Badge,
11
+ Button,
12
+ ButtonVariant,
13
+ Card,
14
+ CardBody,
15
+ CardExpandableContent,
16
+ CardFooter,
17
+ CardHeader,
18
+ CardTitle,
19
+ CodeBlock,
20
+ CodeBlockCode,
21
+ Content,
22
+ ContentVariants,
23
+ DescriptionList,
24
+ DescriptionListDescription,
25
+ DescriptionListGroup,
26
+ DescriptionListTerm,
27
+ Flex,
28
+ FlexItem,
29
+ HelperText,
30
+ HelperTextItem,
31
+ Icon,
32
+ Progress,
33
+ ProgressMeasureLocation,
34
+ Spinner
35
+ } from '@patternfly/react-core';
36
+ import ArrowCircleDownIcon from '@patternfly/react-icons/dist/esm/icons/arrow-circle-down-icon';
37
+ import CheckCircleIcon from '@patternfly/react-icons/dist/esm/icons/check-circle-icon';
38
+ import ArrowRightIcon from '@patternfly/react-icons/dist/esm/icons/arrow-right-icon';
39
+ import patternflyAvatar from '../Messages/patternfly_avatar.jpg';
40
+ import React from 'react';
6
41
 
7
42
  const UserActionEndContent = () => {
8
43
  // eslint-disable-next-line no-console
@@ -36,6 +71,350 @@ const BeforeMainContent = () => (
36
71
  </div>
37
72
  );
38
73
 
74
+ const downloadCard = (
75
+ <Card>
76
+ <CardHeader isToggleRightAligned>
77
+ <CardTitle>
78
+ <Flex spaceItems={{ default: 'spaceItemsSm' }}>
79
+ <FlexItem>
80
+ <Icon size="lg" status="success">
81
+ <CheckCircleIcon />
82
+ </Icon>
83
+ </FlexItem>
84
+ <FlexItem>Your discovery ISO is ready</FlexItem>
85
+ </Flex>
86
+ </CardTitle>
87
+ </CardHeader>
88
+
89
+ <CardBody>
90
+ <Flex direction={{ default: 'column' }} spaceItems={{ default: 'spaceItemsLg' }}>
91
+ <FlexItem>
92
+ <Content component={ContentVariants.p}>
93
+ To begin adding hosts to your bare metal cluster, you first need to boot them with the generated Discovery
94
+ ISO. This allows the installation program to see and manage your hardware.
95
+ </Content>
96
+ </FlexItem>
97
+
98
+ <Flex direction={{ default: 'column' }} spaceItems={{ default: 'spaceItemsSm' }}>
99
+ <FlexItem>
100
+ <Button variant={ButtonVariant.primary} icon={<ArrowCircleDownIcon />} isBlock>
101
+ Download Discovery ISO
102
+ </Button>
103
+ </FlexItem>
104
+
105
+ <FlexItem alignSelf={{ default: 'alignSelfCenter' }}>
106
+ <Content component={ContentVariants.small}>1.2 GB • Expires in 24 hours</Content>
107
+ </FlexItem>
108
+ </Flex>
109
+ </Flex>
110
+ </CardBody>
111
+
112
+ <CardFooter>
113
+ <Content component={ContentVariants.small}>
114
+ <strong>Next step:</strong> After downloading, boot your bare metal hosts from this ISO image.
115
+ </Content>
116
+ </CardFooter>
117
+ </Card>
118
+ );
119
+ interface Stage {
120
+ id: string;
121
+ name: string;
122
+ startProgress: number;
123
+ endProgress: number;
124
+ }
125
+
126
+ const LiveProgressSummaryCard = () => {
127
+ const [isCardExpanded, setIsCardExpanded] = useState(false);
128
+ const [isAccordionExpanded, setIsAccordionExpanded] = useState('installing-toggle');
129
+ const [progress, setProgress] = useState(15);
130
+ const [isSimulationRunning, setIsSimulationRunning] = useState(false);
131
+ const [userManuallyExpandedAccordion, setUserManuallyExpandedAccordion] = useState(false);
132
+
133
+ const stages: Stage[] = [
134
+ {
135
+ id: 'installing-toggle',
136
+ name: 'Installing cluster bootstrap',
137
+ startProgress: 0,
138
+ endProgress: 25
139
+ },
140
+ {
141
+ id: 'setup-toggle',
142
+ name: 'Control plane setup',
143
+ startProgress: 25,
144
+ endProgress: 45
145
+ },
146
+ {
147
+ id: 'deploying-toggle',
148
+ name: 'Deploying cluster operators',
149
+ startProgress: 45,
150
+ endProgress: 85
151
+ },
152
+ {
153
+ id: 'finalizing-toggle',
154
+ name: 'Finalizing installation',
155
+ startProgress: 85,
156
+ endProgress: 100
157
+ }
158
+ ];
159
+
160
+ const getCurrentStage = () =>
161
+ stages.find((stage) => progress >= stage.startProgress && progress < stage.endProgress) ||
162
+ stages[stages.length - 1];
163
+
164
+ const getTimeRemaining = () => {
165
+ const remainingProgress = 100 - progress;
166
+ const estimatedMinutes = Math.max(1, Math.round((remainingProgress / 100) * 30)); // 30 minutes total simulation
167
+ return `About ${estimatedMinutes} minute${estimatedMinutes !== 1 ? 's' : ''} remaining`;
168
+ };
169
+
170
+ const getCurrentStageName = () => {
171
+ const currentStage = getCurrentStage();
172
+ return currentStage.name;
173
+ };
174
+
175
+ const getStageStatus = (stage: Stage) => {
176
+ if (progress >= stage.endProgress) {
177
+ return 'completed';
178
+ }
179
+ if (progress >= stage.startProgress) {
180
+ return 'in-progress';
181
+ }
182
+ return 'pending';
183
+ };
184
+
185
+ const renderStageIcon = (stage: Stage) => {
186
+ const status = getStageStatus(stage);
187
+
188
+ if (status === 'completed') {
189
+ return <CheckCircleIcon color="green" aria-label="Complete" />;
190
+ } else if (status === 'in-progress') {
191
+ return <Spinner size="sm" aria-valuetext="In progress" />;
192
+ } else {
193
+ return <div style={{ width: 'var(--pf-t--global--spacer--md)' }}>{/* Empty space for pending stages */}</div>;
194
+ }
195
+ };
196
+
197
+ // Auto-increment progress when simulation is running
198
+ useEffect(() => {
199
+ let interval;
200
+
201
+ if (isSimulationRunning && progress < 100) {
202
+ interval = setInterval(() => {
203
+ setProgress((prev) => {
204
+ const increment = Math.random() * 2 + 0.5; // Random increment between 0.5-2.5%
205
+ const newProgress = Math.min(prev + increment, 100);
206
+
207
+ // Stop simulation when complete
208
+ if (newProgress >= 100) {
209
+ setIsSimulationRunning(false);
210
+ setUserManuallyExpandedAccordion(false); // Reset manual override when simulation completes
211
+ }
212
+
213
+ return newProgress;
214
+ });
215
+ }, 800);
216
+ }
217
+
218
+ return () => {
219
+ if (interval) {
220
+ clearInterval(interval);
221
+ }
222
+ };
223
+ }, [isSimulationRunning, progress]);
224
+
225
+ // Auto-expand accordion to show current stage (only if user hasn't manually overridden)
226
+ useEffect(() => {
227
+ if (isSimulationRunning && !userManuallyExpandedAccordion) {
228
+ setIsAccordionExpanded(getCurrentStage().id);
229
+ }
230
+ }, [progress, isSimulationRunning, userManuallyExpandedAccordion]);
231
+
232
+ const onExpandCard = (_event: React.MouseEvent) => {
233
+ setIsCardExpanded(!isCardExpanded);
234
+ };
235
+
236
+ const onExpandAccordion = (id: string) => {
237
+ setUserManuallyExpandedAccordion(true);
238
+
239
+ if (id === isAccordionExpanded) {
240
+ setIsAccordionExpanded('');
241
+ } else {
242
+ setIsAccordionExpanded(id);
243
+ }
244
+ };
245
+
246
+ const getStageContent = (stage: Stage) => {
247
+ const status = getStageStatus(stage);
248
+
249
+ if (status === 'in-progress') {
250
+ switch (stage.id) {
251
+ case 'installing-toggle':
252
+ return `Installing bootstrap node...
253
+ Installing etcd cluster...
254
+ Installing control plane...`;
255
+ case 'setup-toggle':
256
+ return `Configuring cluster networking...
257
+ Setting up OpenShift API server...
258
+ Configuring authentication...
259
+ Setting up cluster operators...`;
260
+ case 'deploying-toggle':
261
+ return `Deploying openshift-apiserver operator...
262
+ Deploying openshift-sdn operator...
263
+ Deploying openshift-ingress operator...
264
+ `;
265
+ case 'finalizing-toggle':
266
+ return `Finalizing cluster configuration...
267
+ Running post-installation tasks...
268
+ Setting up cluster console...`;
269
+ }
270
+ }
271
+ if (status === 'pending') {
272
+ return 'Processing...';
273
+ }
274
+ return 'Complete!';
275
+ };
276
+
277
+ const renderCodeBlock = (stage: Stage) => (
278
+ <CodeBlock
279
+ style={
280
+ {
281
+ '--pf-v6-c-code-block--BackgroundColor': 'var(--pf-t--color--gray--95)',
282
+ '--pf-v6-c-code-block--BorderRadius': 'var(--pf-t--global--border--radius--small)'
283
+ } as React.CSSProperties
284
+ }
285
+ >
286
+ <CodeBlockCode>{getStageContent(stage)}</CodeBlockCode>
287
+ </CodeBlock>
288
+ );
289
+
290
+ return (
291
+ <>
292
+ <Flex className="pf-v6-u-mt-lg" spaceItems={{ default: 'spaceItemsSm' }}>
293
+ <FlexItem>
294
+ <Button
295
+ variant="primary"
296
+ size="sm"
297
+ onClick={() => {
298
+ setProgress(15);
299
+ setIsSimulationRunning(true);
300
+ setUserManuallyExpandedAccordion(false);
301
+ }}
302
+ isDisabled={isSimulationRunning}
303
+ >
304
+ {isSimulationRunning ? 'Simulation running...' : 'Start simulation of cluster installation progress'}
305
+ </Button>
306
+ </FlexItem>
307
+ <FlexItem>
308
+ <Button
309
+ variant="secondary"
310
+ size="sm"
311
+ onClick={() => {
312
+ setIsSimulationRunning(false);
313
+ setProgress(15);
314
+ setIsAccordionExpanded('installing-toggle');
315
+ setUserManuallyExpandedAccordion(false);
316
+ }}
317
+ >
318
+ Reset
319
+ </Button>
320
+ </FlexItem>
321
+ </Flex>
322
+ <Card ouiaId="BasicCard" isExpanded={isCardExpanded}>
323
+ <CardHeader
324
+ onExpand={onExpandCard}
325
+ isToggleRightAligned
326
+ toggleButtonProps={{
327
+ id: 'toggle-button1',
328
+ 'aria-label': 'Details',
329
+ 'aria-labelledby': 'expandable-card-title toggle-button1',
330
+ 'aria-expanded': isCardExpanded
331
+ }}
332
+ >
333
+ <DescriptionList>
334
+ <DescriptionListGroup
335
+ style={
336
+ {
337
+ '--pf-v6-c-description-list__group--RowGap': 'var(--pf-t--global--spacer--md)'
338
+ } as React.CSSProperties
339
+ }
340
+ >
341
+ <DescriptionListTerm id="title-outside-progress-example-label">
342
+ OpenShift cluster installation
343
+ </DescriptionListTerm>
344
+ <DescriptionListDescription>
345
+ <Progress
346
+ aria-labelledby="title-outside-progress-example-label"
347
+ value={progress}
348
+ measureLocation={ProgressMeasureLocation.outside}
349
+ style={{ '--pf-v6-c-progress--GridGap': 'var(--pf-t--global--spacer--sm' } as React.CSSProperties}
350
+ helperText={
351
+ <Flex justifyContent={{ default: 'justifyContentSpaceBetween' }}>
352
+ <FlexItem>
353
+ <HelperText>
354
+ <HelperTextItem>
355
+ {progress >= 100 ? 'Installation complete!' : getCurrentStageName()}
356
+ </HelperTextItem>
357
+ </HelperText>
358
+ </FlexItem>
359
+ <FlexItem>
360
+ <HelperText>
361
+ {/* Progress was getting announced on VoiceOver constantly - this helps avoid that */}
362
+ <HelperTextItem aria-live="off">
363
+ {progress >= 100 ? 'Completed' : getTimeRemaining()}
364
+ </HelperTextItem>
365
+ </HelperText>
366
+ </FlexItem>
367
+ </Flex>
368
+ }
369
+ />
370
+ </DescriptionListDescription>
371
+ </DescriptionListGroup>
372
+ </DescriptionList>
373
+ </CardHeader>
374
+ <CardExpandableContent>
375
+ <CardBody>
376
+ <hr className="pf-v6-u-mb-md" />
377
+ <Accordion
378
+ style={
379
+ {
380
+ '--pf-v6-c-accordion__expandable-content-body--PaddingBlockStart': 'var(--pf-t--global--spacer--md)',
381
+ '--pf-v6-c-accordion__expandable-content-body--PaddingBlockEnd': '0',
382
+ '--pf-v6-c-accordion__expandable-content-body--PaddingInlineStart': '0',
383
+ '--pf-v6-c-accordion__expandable-content-body--PaddingInlineEnd': '0',
384
+ '--pf-v6-c-accordion__expandable-content--BackgroundColor': 'initial',
385
+ '--pf-v6-c-accordion__expandable-content--Color': '#fff'
386
+ } as React.CSSProperties
387
+ }
388
+ >
389
+ {stages.map((stage) => (
390
+ <AccordionItem key={stage.id} isExpanded={isAccordionExpanded === stage.id}>
391
+ <AccordionToggle
392
+ onClick={() => {
393
+ onExpandAccordion(stage.id);
394
+ }}
395
+ id={stage.id}
396
+ >
397
+ <Flex spaceItems={{ default: 'spaceItemsSm' }} alignItems={{ default: 'alignItemsCenter' }}>
398
+ <FlexItem>{renderStageIcon(stage)}</FlexItem>
399
+ <FlexItem>{stage.name}</FlexItem>
400
+ </Flex>
401
+ </AccordionToggle>
402
+ <AccordionContent id={stage.id.replace('-toggle', '')}>{renderCodeBlock(stage)}</AccordionContent>
403
+ </AccordionItem>
404
+ ))}
405
+ </Accordion>
406
+ </CardBody>
407
+ </CardExpandableContent>
408
+ <CardFooter>
409
+ <Button isBlock variant="tertiary" icon={<ArrowRightIcon />} iconPosition="end">
410
+ Open in console
411
+ </Button>
412
+ </CardFooter>
413
+ </Card>
414
+ </>
415
+ );
416
+ };
417
+
39
418
  export const UserMessageWithExtraContent: FunctionComponent = () => (
40
419
  <>
41
420
  <Message
@@ -50,5 +429,24 @@ export const UserMessageWithExtraContent: FunctionComponent = () => (
50
429
  endContent: <UserActionEndContent />
51
430
  }}
52
431
  />
432
+ <Message
433
+ avatar={userAvatar}
434
+ name="User"
435
+ role="user"
436
+ content="This is a main message."
437
+ timestamp="1 hour ago"
438
+ extraContent={{
439
+ afterMainContent: <LiveProgressSummaryCard />
440
+ }}
441
+ />
442
+ <Message
443
+ avatar={patternflyAvatar}
444
+ name="Bot"
445
+ role="bot"
446
+ content="All set! I've finished building the Discovery ISO. The next step is to download it and boot your hosts, which you can do using the summary card I've prepared for you:"
447
+ extraContent={{
448
+ endContent: downloadCard
449
+ }}
450
+ />
53
451
  </>
54
452
  );
@@ -0,0 +1,202 @@
1
+ // From Cursor, with aid
2
+ import React, { FunctionComponent, useState, useRef, useEffect } from 'react';
3
+ import { ChatbotDisplayMode } from '@patternfly/chatbot/dist/dynamic/Chatbot';
4
+ import ChatbotConversationHistoryNav, {
5
+ Conversation
6
+ } from '@patternfly/chatbot/dist/dynamic/ChatbotConversationHistoryNav';
7
+ import { ChatbotModal } from '@patternfly/chatbot/dist/dynamic/ChatbotModal';
8
+ import {
9
+ Checkbox,
10
+ DropdownItem,
11
+ DropdownList,
12
+ Button,
13
+ TextInput,
14
+ Form,
15
+ FormGroup,
16
+ ModalHeader,
17
+ ModalBody,
18
+ ModalFooter
19
+ } from '@patternfly/react-core';
20
+
21
+ export const ChatbotHeaderTitleDemo: FunctionComponent = () => {
22
+ const [isDrawerOpen, setIsDrawerOpen] = useState(true);
23
+ const displayMode = ChatbotDisplayMode.embedded;
24
+
25
+ // Modal state
26
+ const [isModalOpen, setIsModalOpen] = useState(false);
27
+ const [editingConversationId, setEditingConversationId] = useState<string | number | null>(null);
28
+ const [editingText, setEditingText] = useState('');
29
+
30
+ // Ref for the text input
31
+ const textInputRef = useRef<HTMLInputElement>(null);
32
+
33
+ // Focus the text input when modal opens
34
+ useEffect(() => {
35
+ if (isModalOpen && textInputRef.current) {
36
+ textInputRef.current.focus();
37
+ // Move cursor to the end of the text
38
+ const length = textInputRef.current.value.length;
39
+ textInputRef.current.setSelectionRange(length, length);
40
+ }
41
+ }, [isModalOpen]);
42
+
43
+ const findConversationAndGroup = (conversations: { [key: string]: Conversation[] }, itemId: string | number) => {
44
+ for (const [groupKey, conversationList] of Object.entries(conversations)) {
45
+ const conversationIndex = conversationList.findIndex((conv) => conv.id === itemId);
46
+ if (conversationIndex !== -1) {
47
+ return { groupKey, conversationIndex, conversation: conversationList[conversationIndex] };
48
+ }
49
+ }
50
+ return null;
51
+ };
52
+
53
+ const onRenameClick = (itemId: string | number) => {
54
+ const result = findConversationAndGroup(conversations, itemId);
55
+ if (result) {
56
+ setEditingConversationId(itemId);
57
+ setEditingText(result.conversation.text);
58
+ setIsModalOpen(true);
59
+ }
60
+ };
61
+
62
+ const handleModalSave = () => {
63
+ if (editingConversationId) {
64
+ setConversations((prevConversations) => {
65
+ const result = findConversationAndGroup(prevConversations, editingConversationId);
66
+ if (!result) {
67
+ return prevConversations;
68
+ }
69
+
70
+ const { groupKey, conversationIndex } = result;
71
+ const newConversations = { ...prevConversations };
72
+ const newGroup = [...newConversations[groupKey]];
73
+
74
+ newGroup[conversationIndex] = { ...newGroup[conversationIndex], text: editingText };
75
+ newConversations[groupKey] = newGroup;
76
+
77
+ return newConversations;
78
+ });
79
+ }
80
+ handleModalClose();
81
+ };
82
+
83
+ const handleModalCancel = () => {
84
+ handleModalClose();
85
+ };
86
+
87
+ const handleModalClose = () => {
88
+ setIsModalOpen(false);
89
+ setEditingConversationId(null);
90
+ setEditingText('');
91
+ };
92
+
93
+ const handleTextInputKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
94
+ if (event.key === 'Enter') {
95
+ event.preventDefault();
96
+ handleModalSave();
97
+ }
98
+ };
99
+
100
+ const renderMenuItems = (itemId: string | number) => [
101
+ <DropdownList key={`list-${itemId}`}>
102
+ <DropdownItem value="Download" id="Download">
103
+ Download
104
+ </DropdownItem>
105
+ <DropdownItem value="Rename" id="Rename" onClick={() => onRenameClick(itemId)}>
106
+ Rename
107
+ </DropdownItem>
108
+ <DropdownItem value="Archive" id="Archive">
109
+ Archive
110
+ </DropdownItem>
111
+ <DropdownItem value="Delete" id="Delete">
112
+ Delete
113
+ </DropdownItem>
114
+ </DropdownList>
115
+ ];
116
+
117
+ const initialConversations: { [key: string]: Conversation[] } = {
118
+ Today: [{ id: '1', text: 'Red Hat products and services' }],
119
+ 'This month': [
120
+ {
121
+ id: '2',
122
+ text: 'Enterprise Linux installation and setup'
123
+ },
124
+ { id: '3', text: 'Troubleshoot system crash' }
125
+ ],
126
+ March: [
127
+ { id: '4', text: 'Ansible security and updates' },
128
+ { id: '5', text: 'Red Hat certification' },
129
+ { id: '6', text: 'Lightspeed user documentation' }
130
+ ],
131
+ February: [
132
+ { id: '7', text: 'Crashing pod assistance' },
133
+ { id: '8', text: 'OpenShift AI pipelines' },
134
+ { id: '9', text: 'Updating subscription plan' },
135
+ { id: '10', text: 'Red Hat licensing options' }
136
+ ],
137
+ January: [
138
+ { id: '11', text: 'RHEL system performance' },
139
+ { id: '12', text: 'Manage user accounts' }
140
+ ]
141
+ };
142
+
143
+ const [conversations, setConversations] = useState(initialConversations);
144
+
145
+ // Create conversations with menu items dynamically
146
+ const conversationsWithMenuItems = () => {
147
+ const newConversations = { ...conversations };
148
+ Object.keys(newConversations).forEach((groupKey) => {
149
+ newConversations[groupKey] = newConversations[groupKey].map((conv) => ({
150
+ ...conv,
151
+ menuItems: renderMenuItems(conv.id)
152
+ }));
153
+ });
154
+ return newConversations;
155
+ };
156
+
157
+ return (
158
+ <>
159
+ <Checkbox
160
+ label="Display drawer"
161
+ isChecked={isDrawerOpen}
162
+ onChange={() => setIsDrawerOpen(!isDrawerOpen)}
163
+ id="drawer-actions-visible"
164
+ name="drawer-actions-visible"
165
+ ></Checkbox>
166
+ <ChatbotConversationHistoryNav
167
+ displayMode={displayMode}
168
+ onDrawerToggle={() => setIsDrawerOpen(!isDrawerOpen)}
169
+ isDrawerOpen={isDrawerOpen}
170
+ setIsDrawerOpen={setIsDrawerOpen}
171
+ conversations={conversationsWithMenuItems()}
172
+ drawerContent={<div>Drawer content</div>}
173
+ />
174
+
175
+ <ChatbotModal displayMode={displayMode} isOpen={isModalOpen} onClose={handleModalClose}>
176
+ <ModalHeader title="Rename Conversation" />
177
+ <ModalBody>
178
+ <Form>
179
+ <FormGroup label="Conversation Name" fieldId="conversation-name" isRequired>
180
+ <TextInput
181
+ isRequired
182
+ ref={textInputRef}
183
+ value={editingText}
184
+ onChange={(_, value) => setEditingText(value)}
185
+ onKeyDown={handleTextInputKeyDown}
186
+ id="conversation-name"
187
+ />
188
+ </FormGroup>
189
+ </Form>
190
+ </ModalBody>
191
+ <ModalFooter>
192
+ <Button key="save" variant="primary" onClick={handleModalSave}>
193
+ Save
194
+ </Button>
195
+ <Button key="cancel" variant="link" onClick={handleModalCancel}>
196
+ Cancel
197
+ </Button>
198
+ </ModalFooter>
199
+ </ChatbotModal>
200
+ </>
201
+ );
202
+ };
@@ -17,7 +17,8 @@ import {
17
17
  ChatbotHeaderActions,
18
18
  ChatbotHeaderTitle,
19
19
  ChatbotHeaderOptionsDropdown,
20
- ChatbotHeaderSelectorDropdown
20
+ ChatbotHeaderSelectorDropdown,
21
+ ChatbotHeaderNewChatButton
21
22
  } from '@patternfly/chatbot/dist/dynamic/ChatbotHeader';
22
23
  import { ChatbotDisplayMode } from '@patternfly/chatbot/dist/dynamic/Chatbot';
23
24
  import OutlinedWindowRestoreIcon from '@patternfly/react-icons/dist/esm/icons/outlined-window-restore-icon';
@@ -31,6 +32,7 @@ export const BasicDemo: FunctionComponent = () => {
31
32
  const [selectedModel, setSelectedModel] = useState('Granite Code 7B');
32
33
  const [showAll, setShowAll] = useState<boolean>(true);
33
34
  const [showMenu, setShowMenu] = useState<boolean>(true);
35
+ const [showNewChatButton, setShowNewChatButton] = useState<boolean>(true);
34
36
  const [showLogo, setShowLogo] = useState<boolean>(false);
35
37
  const [showCenteredLogo, setShowCenteredLogo] = useState<boolean>(true);
36
38
  const [showSelectorDropdown, setShowSelectorDropdown] = useState<boolean>(true);
@@ -58,9 +60,10 @@ export const BasicDemo: FunctionComponent = () => {
58
60
  <Stack hasGutter>
59
61
  <FormGroup role="radiogroup" isInline fieldId="header-variant-form-radio-group" label="Variant">
60
62
  <Checkbox
61
- isChecked={showMenu && showCenteredLogo && showSelectorDropdown && showOptionsDropdown}
63
+ isChecked={showMenu && showNewChatButton && showCenteredLogo && showSelectorDropdown && showOptionsDropdown}
62
64
  onChange={() => {
63
65
  setShowMenu(true);
66
+ setShowNewChatButton(true);
64
67
  setShowCenteredLogo(true);
65
68
  setShowSelectorDropdown(true);
66
69
  setShowOptionsDropdown(true);
@@ -80,6 +83,16 @@ export const BasicDemo: FunctionComponent = () => {
80
83
  label="With menu"
81
84
  id="menu"
82
85
  />
86
+ <Checkbox
87
+ isChecked={showNewChatButton}
88
+ onChange={() => {
89
+ setShowNewChatButton(!showNewChatButton);
90
+ showAll && setShowAll(!showAll);
91
+ }}
92
+ name="basic-inline-radio"
93
+ label="With new chat button"
94
+ id="new-chat-button"
95
+ />
83
96
  <Checkbox
84
97
  isChecked={showLogo}
85
98
  onChange={() => {
@@ -124,9 +137,10 @@ export const BasicDemo: FunctionComponent = () => {
124
137
  </FormGroup>
125
138
 
126
139
  <ChatbotHeader>
127
- {(showMenu || showLogo || showCenteredLogo) && (
140
+ {(showMenu || showNewChatButton || showLogo || showCenteredLogo) && (
128
141
  <ChatbotHeaderMain>
129
142
  {showMenu && <ChatbotHeaderMenu onMenuToggle={() => alert('Menu toggle clicked')} />}
143
+ {showNewChatButton && <ChatbotHeaderNewChatButton onClick={() => alert('New chat button clicked')} />}
130
144
  {(showLogo || showCenteredLogo) && (
131
145
  <ChatbotHeaderTitle>{showCenteredLogo ? <Bullseye>{title}</Bullseye> : title}</ChatbotHeaderTitle>
132
146
  )}