@patternfly/chatbot 6.3.0 → 6.4.0-prerelease.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.d.ts +5 -1
- package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.js +3 -3
- package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.test.js +17 -0
- package/dist/cjs/ChatbotHeader/ChatbotHeaderNewChatButton.d.ts +18 -0
- package/dist/cjs/ChatbotHeader/ChatbotHeaderNewChatButton.js +25 -0
- package/dist/cjs/ChatbotHeader/ChatbotHeaderNewChatButton.test.d.ts +1 -0
- package/dist/cjs/ChatbotHeader/ChatbotHeaderNewChatButton.test.js +22 -0
- package/dist/cjs/ChatbotHeader/index.d.ts +1 -0
- package/dist/cjs/ChatbotHeader/index.js +1 -0
- package/dist/cjs/FileDropZone/FileDropZone.d.ts +1 -2
- package/dist/cjs/Message/CodeBlockMessage/CodeBlockMessage.js +1 -10
- package/dist/cjs/Message/Message.d.ts +4 -2
- package/dist/cjs/Message/Message.js +4 -4
- package/dist/cjs/Message/Message.test.js +26 -0
- package/dist/cjs/Message/MessageInput.d.ts +3 -1
- package/dist/cjs/Message/MessageInput.js +2 -2
- package/dist/cjs/MessageBar/AttachButton.d.ts +2 -2
- package/dist/cjs/MessageBar/MessageBar.d.ts +2 -2
- package/dist/cjs/MessageBox/MessageBox.js +1 -1
- package/dist/cjs/MessageDivider/MessageDivider.d.ts +9 -0
- package/dist/cjs/MessageDivider/MessageDivider.js +23 -0
- package/dist/cjs/MessageDivider/MessageDivider.test.d.ts +1 -0
- package/dist/cjs/MessageDivider/MessageDivider.test.js +29 -0
- package/dist/cjs/MessageDivider/index.d.ts +2 -0
- package/dist/cjs/MessageDivider/index.js +23 -0
- package/dist/cjs/ResponseActions/ResponseActions.d.ts +1 -0
- package/dist/cjs/ResponseActions/ResponseActions.js +4 -4
- package/dist/cjs/ResponseActions/ResponseActions.test.js +6 -1
- package/dist/cjs/index.d.ts +2 -0
- package/dist/cjs/index.js +4 -1
- package/dist/css/main.css +56 -55
- package/dist/css/main.css.map +1 -1
- package/dist/dynamic/MessageDivider/package.json +1 -0
- package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.d.ts +5 -1
- package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.js +5 -5
- package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.test.js +17 -0
- package/dist/esm/ChatbotHeader/ChatbotHeaderNewChatButton.d.ts +18 -0
- package/dist/esm/ChatbotHeader/ChatbotHeaderNewChatButton.js +22 -0
- package/dist/esm/ChatbotHeader/ChatbotHeaderNewChatButton.test.d.ts +1 -0
- package/dist/esm/ChatbotHeader/ChatbotHeaderNewChatButton.test.js +20 -0
- package/dist/esm/ChatbotHeader/index.d.ts +1 -0
- package/dist/esm/ChatbotHeader/index.js +1 -0
- package/dist/esm/FileDropZone/FileDropZone.d.ts +1 -2
- package/dist/esm/Message/CodeBlockMessage/CodeBlockMessage.js +1 -7
- package/dist/esm/Message/Message.d.ts +4 -2
- package/dist/esm/Message/Message.js +4 -4
- package/dist/esm/Message/Message.test.js +26 -0
- package/dist/esm/Message/MessageInput.d.ts +3 -1
- package/dist/esm/Message/MessageInput.js +2 -2
- package/dist/esm/MessageBar/AttachButton.d.ts +2 -2
- package/dist/esm/MessageBar/MessageBar.d.ts +2 -2
- package/dist/esm/MessageBox/MessageBox.js +1 -1
- package/dist/esm/MessageDivider/MessageDivider.d.ts +9 -0
- package/dist/esm/MessageDivider/MessageDivider.js +21 -0
- package/dist/esm/MessageDivider/MessageDivider.test.d.ts +1 -0
- package/dist/esm/MessageDivider/MessageDivider.test.js +24 -0
- package/dist/esm/MessageDivider/index.d.ts +2 -0
- package/dist/esm/MessageDivider/index.js +2 -0
- package/dist/esm/ResponseActions/ResponseActions.d.ts +1 -0
- package/dist/esm/ResponseActions/ResponseActions.js +5 -5
- package/dist/esm/ResponseActions/ResponseActions.test.js +6 -1
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/index.js +2 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +6 -4
- package/patternfly-docs/content/extensions/chatbot/examples/Messages/MessageWithDividers.tsx +24 -0
- package/patternfly-docs/content/extensions/chatbot/examples/Messages/Messages.md +15 -1
- package/patternfly-docs/content/extensions/chatbot/examples/Messages/UserMessage.tsx +39 -7
- package/patternfly-docs/content/extensions/chatbot/examples/UI/ChatbotHeaderBasic.tsx +17 -3
- package/patternfly-docs/content/extensions/chatbot/examples/UI/ChatbotHeaderDrawer.tsx +9 -0
- package/patternfly-docs/content/extensions/chatbot/examples/UI/ChatbotHeaderDrawerWithPin.tsx +196 -0
- package/patternfly-docs/content/extensions/chatbot/examples/UI/UI.md +16 -3
- package/patternfly-docs/content/extensions/chatbot/examples/demos/Chatbot.md +33 -1
- package/patternfly-docs/content/extensions/chatbot/examples/demos/ChatbotDisplayMode.tsx +486 -0
- package/patternfly-docs/content/extensions/chatbot/examples/demos/ChatbotTranscripts.tsx +565 -0
- package/src/Chatbot/Chatbot.scss +1 -1
- package/src/ChatbotContent/ChatbotContent.scss +1 -1
- package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.scss +14 -2
- package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.test.tsx +58 -0
- package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.tsx +34 -12
- package/src/ChatbotFooter/ChatbotFooter.scss +1 -1
- package/src/ChatbotHeader/ChatbotHeader.scss +3 -3
- package/src/ChatbotHeader/ChatbotHeaderNewChatButton.test.tsx +25 -0
- package/src/ChatbotHeader/ChatbotHeaderNewChatButton.tsx +64 -0
- package/src/ChatbotHeader/index.ts +1 -0
- package/src/ChatbotToggle/ChatbotToggle.scss +2 -2
- package/src/FileDetails/__snapshots__/FileDetails.test.tsx.snap +6 -9
- package/src/FileDetailsLabel/__snapshots__/FileDetailsLabel.test.tsx.snap +6 -9
- package/src/FileDropZone/FileDropZone.tsx +2 -2
- package/src/Message/CodeBlockMessage/CodeBlockMessage.tsx +3 -27
- package/src/Message/Message.scss +9 -7
- package/src/Message/Message.test.tsx +35 -0
- package/src/Message/Message.tsx +8 -4
- package/src/Message/MessageInput.tsx +5 -1
- package/src/MessageBar/AttachButton.tsx +2 -2
- package/src/MessageBar/MessageBar.tsx +2 -2
- package/src/MessageBar/SendButton.scss +3 -3
- package/src/MessageBox/JumpButton.scss +1 -1
- package/src/MessageBox/MessageBox.tsx +1 -1
- package/src/MessageDivider/MessageDivider.scss +45 -0
- package/src/MessageDivider/MessageDivider.test.tsx +24 -0
- package/src/MessageDivider/MessageDivider.tsx +35 -0
- package/src/MessageDivider/index.ts +3 -0
- package/src/ResponseActions/ResponseActions.test.tsx +6 -1
- package/src/ResponseActions/ResponseActions.tsx +24 -3
- package/src/index.ts +3 -0
- package/src/main.scss +1 -52
- package/dist/cjs/Message/CodeBlockMessage/ExpandableSectionForSyntaxHighlighter.d.ts +0 -62
- package/dist/cjs/Message/CodeBlockMessage/ExpandableSectionForSyntaxHighlighter.js +0 -139
- package/dist/esm/Message/CodeBlockMessage/ExpandableSectionForSyntaxHighlighter.d.ts +0 -62
- package/dist/esm/Message/CodeBlockMessage/ExpandableSectionForSyntaxHighlighter.js +0 -133
- package/src/Message/CodeBlockMessage/ExpandableSectionForSyntaxHighlighter.tsx +0 -223
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Fragment, useState, CSSProperties, FunctionComponent, MouseEvent } from 'react';
|
|
1
|
+
import { Fragment, useState, useRef, useEffect, CSSProperties, FunctionComponent, MouseEvent, Ref } from 'react';
|
|
2
2
|
import Message from '@patternfly/chatbot/dist/dynamic/Message';
|
|
3
3
|
import userAvatar from './user_avatar.svg';
|
|
4
4
|
import {
|
|
@@ -12,12 +12,32 @@ import {
|
|
|
12
12
|
import { rehypeCodeBlockToggle } from '@patternfly/chatbot/dist/esm/Message/Plugins/rehypeCodeBlockToggle';
|
|
13
13
|
|
|
14
14
|
export const UserMessageExample: FunctionComponent = () => {
|
|
15
|
-
const
|
|
16
|
-
const
|
|
15
|
+
const messageInputRef = useRef<HTMLTextAreaElement>(null);
|
|
16
|
+
const editButtonRef = useRef<HTMLButtonElement>(null);
|
|
17
|
+
const [variant, setVariant] = useState<string | number | undefined>('Code');
|
|
17
18
|
const [isOpen, setIsOpen] = useState<boolean>(false);
|
|
18
19
|
const [selected, setSelected] = useState<string>('Message content type');
|
|
19
20
|
const [isExpandable, setIsExpanded] = useState(false);
|
|
20
21
|
|
|
22
|
+
const [isEditable, setIsEditable] = useState<boolean>(false);
|
|
23
|
+
const prevIsEditable = useRef<boolean>(false);
|
|
24
|
+
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
if (isEditable && messageInputRef?.current) {
|
|
27
|
+
messageInputRef.current.focus();
|
|
28
|
+
const messageLength = messageInputRef.current.value.length;
|
|
29
|
+
// Mimic the behavior of the textarea when the user clicks on a label to place the cursor at the end of the input value
|
|
30
|
+
messageInputRef.current.setSelectionRange(messageLength, messageLength);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// We only want to re-focus the edit action button if the user has previously clicked on it,
|
|
34
|
+
// and prevent it from receiving focus on page load
|
|
35
|
+
if (prevIsEditable.current && !isEditable && editButtonRef?.current) {
|
|
36
|
+
editButtonRef.current.focus();
|
|
37
|
+
prevIsEditable.current = false;
|
|
38
|
+
}
|
|
39
|
+
}, [isEditable]);
|
|
40
|
+
|
|
21
41
|
/* eslint-disable indent */
|
|
22
42
|
const renderContent = () => {
|
|
23
43
|
switch (variant) {
|
|
@@ -180,6 +200,11 @@ _Italic text, formatted with single underscores_
|
|
|
180
200
|
setIsOpen(!isOpen);
|
|
181
201
|
};
|
|
182
202
|
|
|
203
|
+
const onUpdateOrCancelEdit = () => {
|
|
204
|
+
prevIsEditable.current = isEditable;
|
|
205
|
+
setIsEditable(false);
|
|
206
|
+
};
|
|
207
|
+
|
|
183
208
|
const toggle = (toggleRef: Ref<MenuToggleElement>) => (
|
|
184
209
|
<MenuToggle
|
|
185
210
|
className="pf-v6-u-mb-md"
|
|
@@ -212,6 +237,17 @@ _Italic text, formatted with single underscores_
|
|
|
212
237
|
avatar={userAvatar}
|
|
213
238
|
avatarProps={{ isBordered: true }}
|
|
214
239
|
/>
|
|
240
|
+
<Message
|
|
241
|
+
name="User"
|
|
242
|
+
role="user"
|
|
243
|
+
isEditable={isEditable}
|
|
244
|
+
onEditUpdate={onUpdateOrCancelEdit}
|
|
245
|
+
onEditCancel={onUpdateOrCancelEdit}
|
|
246
|
+
actions={{ edit: { onClick: () => setIsEditable(true), innerRef: editButtonRef } }}
|
|
247
|
+
content="This is a user message with an edit action."
|
|
248
|
+
avatar={userAvatar}
|
|
249
|
+
inputRef={messageInputRef}
|
|
250
|
+
/>
|
|
215
251
|
<Select
|
|
216
252
|
id="single-select"
|
|
217
253
|
isOpen={isOpen}
|
|
@@ -235,7 +271,6 @@ _Italic text, formatted with single underscores_
|
|
|
235
271
|
<SelectOption value="Table">Table</SelectOption>
|
|
236
272
|
<SelectOption value="Image">Image</SelectOption>
|
|
237
273
|
<SelectOption value="Error">Error</SelectOption>
|
|
238
|
-
<SelectOption value="Editable">Editable</SelectOption>
|
|
239
274
|
</SelectList>
|
|
240
275
|
</Select>
|
|
241
276
|
<Message
|
|
@@ -246,10 +281,7 @@ _Italic text, formatted with single underscores_
|
|
|
246
281
|
tableProps={
|
|
247
282
|
variant === 'Table' ? { 'aria-label': 'App information and user roles for user messages' } : undefined
|
|
248
283
|
}
|
|
249
|
-
isEditable={variant === 'Editable' ? isEditable : false}
|
|
250
284
|
error={variant === 'Error' ? error : undefined}
|
|
251
|
-
onEditUpdate={() => setIsEditable(false)}
|
|
252
|
-
onEditCancel={() => setIsEditable(false)}
|
|
253
285
|
codeBlockProps={{ isExpandable, expandableSectionProps: { truncateMaxLines: isExpandable ? 1 : undefined } }}
|
|
254
286
|
// In this example, custom plugin will override any custom expandedText or collapsedText attributes provided
|
|
255
287
|
// The purpose of this plugin is to provide unique link names for the code blocks
|
|
@@ -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
|
)}
|
|
@@ -62,6 +62,7 @@ const EMPTY_STATE = {
|
|
|
62
62
|
export const ChatbotHeaderTitleDemo: FunctionComponent = () => {
|
|
63
63
|
const [isOpen, setIsOpen] = useState(true);
|
|
64
64
|
const [isButtonOrderReversed, setIsButtonOrderReversed] = useState(false);
|
|
65
|
+
const [isNewChatButtonDisabled, setIsNewChatButtonDisabled] = useState(false);
|
|
65
66
|
const [isCompact, setIsCompact] = useState(false);
|
|
66
67
|
const [conversations, setConversations] = useState<Conversation[] | { [key: string]: Conversation[] }>(
|
|
67
68
|
initialConversations
|
|
@@ -109,6 +110,13 @@ export const ChatbotHeaderTitleDemo: FunctionComponent = () => {
|
|
|
109
110
|
id="drawer-actions-visible"
|
|
110
111
|
name="drawer-actions-visible"
|
|
111
112
|
></Checkbox>
|
|
113
|
+
<Checkbox
|
|
114
|
+
label="Disable new chat button"
|
|
115
|
+
isChecked={isNewChatButtonDisabled}
|
|
116
|
+
onChange={() => setIsNewChatButtonDisabled(!isNewChatButtonDisabled)}
|
|
117
|
+
id="drawer-actions-disabled"
|
|
118
|
+
name="drawer-actions-disabled"
|
|
119
|
+
></Checkbox>
|
|
112
120
|
<Checkbox
|
|
113
121
|
label="Show loading state"
|
|
114
122
|
isChecked={isLoading}
|
|
@@ -152,6 +160,7 @@ export const ChatbotHeaderTitleDemo: FunctionComponent = () => {
|
|
|
152
160
|
// eslint-disable-next-line no-console
|
|
153
161
|
onSelectActiveItem={(e, selectedItem) => console.log(`Selected history item with id ${selectedItem}`)}
|
|
154
162
|
conversations={conversations}
|
|
163
|
+
newChatButtonProps={{ isDisabled: isNewChatButtonDisabled }}
|
|
155
164
|
onNewChat={() => {
|
|
156
165
|
setIsOpen(!isOpen);
|
|
157
166
|
}}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
// Assisted by: Cursor
|
|
2
|
+
import React, { FunctionComponent, useState } 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 { Checkbox, DropdownItem, DropdownList } from '@patternfly/react-core';
|
|
8
|
+
import { ThumbtackIcon } from '@patternfly/react-icons';
|
|
9
|
+
|
|
10
|
+
// Sample conversations
|
|
11
|
+
const initialConversations: { [key: string]: Conversation[] } = {
|
|
12
|
+
Today: [
|
|
13
|
+
{
|
|
14
|
+
id: '1',
|
|
15
|
+
text: 'Red Hat products and services',
|
|
16
|
+
label: 'Conversation options for "Red Hat products and services"'
|
|
17
|
+
}
|
|
18
|
+
],
|
|
19
|
+
'This month': [
|
|
20
|
+
{
|
|
21
|
+
id: '2',
|
|
22
|
+
text: 'Enterprise Linux installation and setup',
|
|
23
|
+
label: 'Conversation options for "Enterprise Linux installation and setup"'
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
id: '3',
|
|
27
|
+
text: 'Troubleshoot system crash',
|
|
28
|
+
label: 'Conversation options for "Troubleshoot system crash"'
|
|
29
|
+
}
|
|
30
|
+
],
|
|
31
|
+
March: [
|
|
32
|
+
{
|
|
33
|
+
id: '4',
|
|
34
|
+
text: 'Ansible security and updates',
|
|
35
|
+
label: 'Conversation options for "Ansible security and updates"'
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
id: '5',
|
|
39
|
+
text: 'Red Hat certification',
|
|
40
|
+
label: 'Conversation options for "Red Hat certification"'
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
id: '6',
|
|
44
|
+
text: 'Lightspeed user documentation',
|
|
45
|
+
label: 'Conversation options for "Lightspeed user documentation"'
|
|
46
|
+
}
|
|
47
|
+
],
|
|
48
|
+
February: [
|
|
49
|
+
{
|
|
50
|
+
id: '7',
|
|
51
|
+
text: 'Crashing pod assistance',
|
|
52
|
+
label: 'Conversation options for "Crashing pod assistance"'
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
id: '8',
|
|
56
|
+
text: 'OpenShift AI pipelines',
|
|
57
|
+
label: 'Conversation options for "OpenShift AI pipelines"'
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
id: '9',
|
|
61
|
+
text: 'Updating subscription plan',
|
|
62
|
+
label: 'Conversation options for "Updating subscription plan"'
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
id: '10',
|
|
66
|
+
text: 'Red Hat licensing options',
|
|
67
|
+
label: 'Conversation options for "Red Hat licensing options"'
|
|
68
|
+
}
|
|
69
|
+
],
|
|
70
|
+
January: [
|
|
71
|
+
{
|
|
72
|
+
id: '11',
|
|
73
|
+
text: 'RHEL system performance',
|
|
74
|
+
label: 'Conversation options for "RHEL system performance"'
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
id: '12',
|
|
78
|
+
text: 'Manage user accounts',
|
|
79
|
+
label: 'Conversation options for "Manage user accounts"'
|
|
80
|
+
}
|
|
81
|
+
]
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export const ChatbotHeaderPinDemo: FunctionComponent = () => {
|
|
85
|
+
const [isDrawerOpen, setIsDrawerOpen] = useState(true);
|
|
86
|
+
const [isCompact, setIsCompact] = useState(false);
|
|
87
|
+
const [pinnedConversations, setPinnedConversations] = useState<Set<string>>(new Set());
|
|
88
|
+
const displayMode = ChatbotDisplayMode.embedded;
|
|
89
|
+
|
|
90
|
+
const handlePinToggle = (conversationId: string) => {
|
|
91
|
+
setPinnedConversations((prev) => {
|
|
92
|
+
const newPinned = new Set(prev);
|
|
93
|
+
if (newPinned.has(conversationId)) {
|
|
94
|
+
newPinned.delete(conversationId);
|
|
95
|
+
} else {
|
|
96
|
+
newPinned.add(conversationId);
|
|
97
|
+
}
|
|
98
|
+
return newPinned;
|
|
99
|
+
});
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const createMenuItems = (conversationId: string) => {
|
|
103
|
+
const isPinned = pinnedConversations.has(conversationId);
|
|
104
|
+
|
|
105
|
+
return [
|
|
106
|
+
<DropdownList key={`${conversationId}-menu`} aria-label="Conversation options">
|
|
107
|
+
<DropdownItem
|
|
108
|
+
value={isPinned ? 'Unpin' : 'Pin'}
|
|
109
|
+
id={isPinned ? 'Unpin' : 'Pin'}
|
|
110
|
+
onClick={() => handlePinToggle(conversationId)}
|
|
111
|
+
>
|
|
112
|
+
{isPinned ? 'Unpin' : 'Pin'}
|
|
113
|
+
</DropdownItem>
|
|
114
|
+
<DropdownItem value="Delete" id="Delete">
|
|
115
|
+
Delete
|
|
116
|
+
</DropdownItem>
|
|
117
|
+
<DropdownItem value="Rename" id="Rename">
|
|
118
|
+
Rename
|
|
119
|
+
</DropdownItem>
|
|
120
|
+
<DropdownItem value="Archive" id="Archive">
|
|
121
|
+
Archive
|
|
122
|
+
</DropdownItem>
|
|
123
|
+
</DropdownList>
|
|
124
|
+
];
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
// Reorganize conversations to show pinned ones at the top
|
|
128
|
+
const organizeConversations = () => {
|
|
129
|
+
const organized: { [key: string]: Conversation[] } = {};
|
|
130
|
+
const pinnedItems: Conversation[] = [];
|
|
131
|
+
|
|
132
|
+
// Collect all pinned conversations first
|
|
133
|
+
Object.entries(initialConversations).forEach(([_period, conversations]) => {
|
|
134
|
+
conversations.forEach((conv) => {
|
|
135
|
+
if (pinnedConversations.has(conv.id)) {
|
|
136
|
+
pinnedItems.push({
|
|
137
|
+
...conv,
|
|
138
|
+
menuItems: createMenuItems(conv.id),
|
|
139
|
+
icon: <ThumbtackIcon />
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// Add pinned section if there are pinned items
|
|
146
|
+
if (pinnedItems.length > 0) {
|
|
147
|
+
organized.Pinned = pinnedItems;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Add unpinned conversations
|
|
151
|
+
Object.entries(initialConversations).forEach(([period, conversations]) => {
|
|
152
|
+
const unpinnedConversations = conversations
|
|
153
|
+
.filter((conv) => !pinnedConversations.has(conv.id))
|
|
154
|
+
.map((conv) => ({
|
|
155
|
+
...conv,
|
|
156
|
+
menuItems: createMenuItems(conv.id)
|
|
157
|
+
}));
|
|
158
|
+
|
|
159
|
+
if (unpinnedConversations.length > 0) {
|
|
160
|
+
organized[period] = unpinnedConversations;
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
return organized;
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const conversations = organizeConversations();
|
|
168
|
+
|
|
169
|
+
return (
|
|
170
|
+
<>
|
|
171
|
+
<Checkbox
|
|
172
|
+
label="Display drawer"
|
|
173
|
+
isChecked={isDrawerOpen}
|
|
174
|
+
onChange={() => setIsDrawerOpen(!isDrawerOpen)}
|
|
175
|
+
id="drawer-pin-visible"
|
|
176
|
+
name="drawer-pin-visible"
|
|
177
|
+
></Checkbox>
|
|
178
|
+
<Checkbox
|
|
179
|
+
label="Show compact version"
|
|
180
|
+
isChecked={isCompact}
|
|
181
|
+
onChange={() => setIsCompact(!isCompact)}
|
|
182
|
+
id="drawer-pin-compact"
|
|
183
|
+
name="drawer-pin-compact"
|
|
184
|
+
></Checkbox>
|
|
185
|
+
<ChatbotConversationHistoryNav
|
|
186
|
+
displayMode={displayMode}
|
|
187
|
+
onDrawerToggle={() => setIsDrawerOpen(!isDrawerOpen)}
|
|
188
|
+
isDrawerOpen={isDrawerOpen}
|
|
189
|
+
setIsDrawerOpen={setIsDrawerOpen}
|
|
190
|
+
conversations={conversations}
|
|
191
|
+
drawerContent={<div>Drawer content</div>}
|
|
192
|
+
isCompact={isCompact}
|
|
193
|
+
/>
|
|
194
|
+
</>
|
|
195
|
+
);
|
|
196
|
+
};
|
|
@@ -61,14 +61,15 @@ ChatbotHeaderMenu,
|
|
|
61
61
|
ChatbotHeaderActions,
|
|
62
62
|
ChatbotHeaderTitle,
|
|
63
63
|
ChatbotHeaderOptionsDropdown,
|
|
64
|
-
ChatbotHeaderSelectorDropdown
|
|
64
|
+
ChatbotHeaderSelectorDropdown,
|
|
65
|
+
ChatbotHeaderNewChatButton
|
|
65
66
|
} from '@patternfly/chatbot/dist/dynamic/ChatbotHeader';
|
|
66
67
|
import { ChatbotFooter, ChatbotFootnote } from '@patternfly/chatbot/dist/dynamic/ChatbotFooter';
|
|
67
68
|
import { MessageBar } from '@patternfly/chatbot/dist/dynamic/MessageBar';
|
|
68
69
|
import SourceDetailsMenuItem from '@patternfly/chatbot/dist/dynamic/SourceDetailsMenuItem';
|
|
69
70
|
import { ChatbotModal } from '@patternfly/chatbot/dist/dynamic/ChatbotModal';
|
|
70
71
|
import SettingsForm from '@patternfly/chatbot/dist/dynamic/Settings';
|
|
71
|
-
import { BellIcon, CalendarAltIcon, ClipboardIcon, CodeIcon, UploadIcon } from '@patternfly/react-icons';
|
|
72
|
+
import { BellIcon, CalendarAltIcon, ClipboardIcon, CodeIcon, ThumbtackIcon, UploadIcon } from '@patternfly/react-icons';
|
|
72
73
|
import { useDropzone } from 'react-dropzone';
|
|
73
74
|
|
|
74
75
|
import ChatbotConversationHistoryNav from '@patternfly/chatbot/dist/dynamic/ChatbotConversationHistoryNav';
|
|
@@ -78,6 +79,7 @@ import OutlinedWindowRestoreIcon from '@patternfly/react-icons/dist/esm/icons/ou
|
|
|
78
79
|
import ExpandIcon from '@patternfly/react-icons/dist/esm/icons/expand-icon';
|
|
79
80
|
import OpenDrawerRightIcon from '@patternfly/react-icons/dist/esm/icons/open-drawer-right-icon';
|
|
80
81
|
import CogIcon from '@patternfly/react-icons/dist/esm/icons/cog-icon';
|
|
82
|
+
import PenToSquareIcon from '@patternfly/react-icons/dist/esm/icons/pen-to-square-icon';
|
|
81
83
|
import PFHorizontalLogoColor from './PF-HorizontalLogo-Color.svg';
|
|
82
84
|
import PFHorizontalLogoReverse from './PF-HorizontalLogo-Reverse.svg';
|
|
83
85
|
import userAvatar from '../Messages/user_avatar.svg';
|
|
@@ -184,9 +186,10 @@ The ChatBot header is persistent, and contains the title for the ChatBot window,
|
|
|
184
186
|
|
|
185
187
|
The `<ChatbotHeader>` has 2 sections:
|
|
186
188
|
|
|
187
|
-
- `<ChatbotHeaderMain>` contains the title and an optional menu toggle:
|
|
189
|
+
- `<ChatbotHeaderMain>` contains the title and an optional menu toggle or new chat button:
|
|
188
190
|
- `<ChatbotHeaderTitle>` handles the layout and display of a title or image at different responsive sizes.
|
|
189
191
|
- `<ChatbotHeaderMenu>` (optional) is placed on the left side of the header and used to toggle a chat history menu.
|
|
192
|
+
- `<ChatbotHeaderNewChatButton>` (optional) is placed on the left side of the header and used to initiate a new chat.
|
|
190
193
|
- `<ChatbotHeaderActions>` contains any additional controls:
|
|
191
194
|
- The `<ChatbotHeaderSelectorDropdown>` component is a standard PatternFly dropdown that matches the ChatBot styles.
|
|
192
195
|
- The `<ChatbotHeaderOptionsDropdown>` component is a dropdown with a menu toggle that is intended to be used to update ChatBot settings (like the display mode).
|
|
@@ -197,6 +200,7 @@ Your `<ChatbotHeader>` code structure should look like this:
|
|
|
197
200
|
<ChatbotHeader>
|
|
198
201
|
<ChatbotHeaderMain>
|
|
199
202
|
<ChatbotHeaderMenu ... />
|
|
203
|
+
<ChatbotHeaderNewChatButton ... />
|
|
200
204
|
<ChatbotHeaderTitle ... />
|
|
201
205
|
</ChatbotHeaderMain>
|
|
202
206
|
<ChatbotHeaderActions>
|
|
@@ -221,6 +225,7 @@ There are a variety of options and customizations you can make to the header, to
|
|
|
221
225
|
In this example, select the respective checkbox to toggle these features:
|
|
222
226
|
|
|
223
227
|
- **Menu:** Users can select the menu toggle to open a menu of additional options or actions.
|
|
228
|
+
- **New chat button:** Used to start a new chat session. The header button can be used in addition to or in place of a new chat button within the [conversation history drawer](/patternfly-ai/chatbot/ui/#drawer-with-search-and-new-chat-button).
|
|
224
229
|
- **Left-aligned logo**
|
|
225
230
|
- **Centered logo**
|
|
226
231
|
- **Selector dropdown:** Users can choose from preselected options in a dropdown menu. For example, they can toggle between AI models.
|
|
@@ -361,6 +366,14 @@ Actions can be added to conversations with `menuItems`. Optionally, you can also
|
|
|
361
366
|
|
|
362
367
|
```
|
|
363
368
|
|
|
369
|
+
### Pinning conversations
|
|
370
|
+
|
|
371
|
+
To help users track important conversations, add a "pin" option to the conversation action menus. This action moves a conversation to a dedicated "pinned" section at the top of the history drawer for quick access. Pinned items should contain an "unpin" option, so that users can remove pinned conversations as needed.
|
|
372
|
+
|
|
373
|
+
```js file="./ChatbotHeaderDrawerWithPin.tsx"
|
|
374
|
+
|
|
375
|
+
```
|
|
376
|
+
|
|
364
377
|
### Drawer with active conversation
|
|
365
378
|
|
|
366
379
|
If you're showing a conversation that is already active, you can set the `activeItemId` prop on your `<ChatbotConversationHistoryNav>` to apply an active visual state.
|
|
@@ -44,7 +44,8 @@ ChatbotHeaderMenu,
|
|
|
44
44
|
ChatbotHeaderTitle,
|
|
45
45
|
ChatbotHeaderActions,
|
|
46
46
|
ChatbotHeaderSelectorDropdown,
|
|
47
|
-
ChatbotHeaderOptionsDropdown
|
|
47
|
+
ChatbotHeaderOptionsDropdown,
|
|
48
|
+
ChatbotHeaderCloseButton,
|
|
48
49
|
} from '@patternfly/chatbot/dist/dynamic/ChatbotHeader';
|
|
49
50
|
|
|
50
51
|
import ExpandIcon from '@patternfly/react-icons/dist/esm/icons/expand-icon';
|
|
@@ -59,6 +60,7 @@ import userAvatar from '../Messages/user_avatar.svg';
|
|
|
59
60
|
import patternflyAvatar from '../Messages/patternfly_avatar.jpg';
|
|
60
61
|
import { getTrackingProviders } from "@patternfly/chatbot/dist/dynamic/tracking";
|
|
61
62
|
import { useEffect,useCallback, useRef, useState, FunctionComponent, MouseEvent } from 'react';
|
|
63
|
+
import saveAs from 'file-saver';
|
|
62
64
|
|
|
63
65
|
### Basic ChatBot
|
|
64
66
|
|
|
@@ -129,6 +131,22 @@ This demo displays a ChatBot in a static, inline drawer. This demo includes:
|
|
|
129
131
|
|
|
130
132
|
```
|
|
131
133
|
|
|
134
|
+
### Display mode switcher
|
|
135
|
+
|
|
136
|
+
This demo showcases how the ChatBot can be rendered in different display modes to suit various application layouts. It demonstrates how to dynamically change the page structure in response to the user's selection. This demo includes:
|
|
137
|
+
|
|
138
|
+
1. The ability to switch between overlay, drawer, and fullscreen modes using the [`<ChatbotHeaderOptionsDropdown>`](/patternfly-ai/chatbot/ui#header-options) in the header.
|
|
139
|
+
2. A conditional page layout that renders the ChatBot for each display mode option:
|
|
140
|
+
- **Overlay:** As a floating window on top of the page content.
|
|
141
|
+
- **Drawer:** Inside an inline PatternFly `<Drawer>` as a side panel.
|
|
142
|
+
- **Fullscreen:** As a top-level component that covers the entire screen for an embedded experience.
|
|
143
|
+
3. Logic to show or hide the `<ChatbotToggle>` button, which is only present in the default overlay mode.
|
|
144
|
+
4. A [basic ChatBot](#basic-chatbot) with a header, welcome prompt, and message bar to populate the different layouts.
|
|
145
|
+
|
|
146
|
+
```js file="./ChatbotDisplayMode.tsx" isFullscreen
|
|
147
|
+
|
|
148
|
+
```
|
|
149
|
+
|
|
132
150
|
### Comparing ChatBots
|
|
133
151
|
|
|
134
152
|
To let users compare how different ChatBots respond to the same prompt, you can add multiple ChatBots within the same window. The following demo illustrates a comparison view pattern that allows users to toggle between different conversations in a single ChatBot window.
|
|
@@ -149,3 +167,17 @@ Your code structure should look like this:
|
|
|
149
167
|
```js file="./EmbeddedComparisonChatbot.tsx" isFullscreen
|
|
150
168
|
|
|
151
169
|
```
|
|
170
|
+
|
|
171
|
+
### Chat transcripts
|
|
172
|
+
|
|
173
|
+
This demo illustrates how you could add downloadable transcripts to your ChatBot, which outline conversation details in a Markdown file. This approach allows users to easily share information from a conversation with others.
|
|
174
|
+
|
|
175
|
+
A message transcript includes details from a single chat message. To download a sample message transcript in this demo, click the "Download" action under a bot message.
|
|
176
|
+
|
|
177
|
+
A conversation transcript includes details from the entirety of a ChatBot conversation. To download a sample conversation transcript in this demo, open the chat history menu and click "Download" in the options menu for the conversation.
|
|
178
|
+
|
|
179
|
+
In this example, file download is implemented with [file-saver](https://www.npmjs.com/package/file-saver).
|
|
180
|
+
|
|
181
|
+
```js file="./ChatbotTranscripts.tsx" isFullscreen
|
|
182
|
+
|
|
183
|
+
```
|