Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(StopButton): Add StopButton for MessageBar #272

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,16 @@ Attachments can also be added to the chatbot via [drag and drop.](/patternfly-ai

```

### Message bar with stop button

If you are using streaming, you may wish to allow users to stop a response from a chatbot.

Setting `hasStopButton` to true and passing in a `handleStopButton` callback function will enable the stop button. You can use this callback to trigger an [AbortController](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) configured as part of your API call.

```js file="./ChatbotMessageBarStop.tsx"

```

### Footer with message bar and footnote

A simple footer with a message bar and footnote would have this code structure:
Expand All @@ -74,3 +84,7 @@ A simple footer with a message bar and footnote would have this code structure:
```js file="./ChatbotFooter.tsx"

```

```

```
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import React from 'react';
import { MessageBar } from '@patternfly/virtual-assistant/dist/dynamic/MessageBar';

export const ChatbotMessageBarStop: React.FunctionComponent = () => {
const handleSend = (message) => alert(message);

const handleStopButton = () => alert('Stop button clicked');

return <MessageBar handleStopButton={handleStopButton} hasStopButton onSendMessage={handleSend} />;
};
1 change: 1 addition & 0 deletions packages/module/src/MessageBar/MessageBar.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
@import './AttachButton';
@import './MicrophoneButton';
@import './SendButton';
@import './StopButton';

// ============================================================================
// Chatbot Footer - Message Bar
Expand Down
48 changes: 32 additions & 16 deletions packages/module/src/MessageBar/MessageBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import MicrophoneButton from './MicrophoneButton';
import { AttachButton } from './AttachButton';
import AttachMenu from '../AttachMenu';
import StopButton from './StopButton';

export interface MessageBarWithAttachMenuProps {
/** Flag to enable whether attach menu is open */
Expand Down Expand Up @@ -40,6 +41,10 @@
hasAttachButton?: boolean;
/** Flag to enable the Microphone button */
hasMicrophoneButton?: boolean;
/** Flag to enable the Stop button, used for streaming content */
hasStopButton?: boolean;
/** Callback function for when stop button is clicked */
handleStopButton?: () => void;
/** Callback function for when attach button is used to upload a file */
handleAttach?: (data: File[], event: DropEvent) => void;
/** Props to enable a menu that opens when the Attach button is clicked, instead of the attachment window */
Expand All @@ -57,6 +62,8 @@
handleAttach,
attachMenuProps,
isSendButtonDisabled,
handleStopButton,
hasStopButton,
...props
}: MessageBarProps) => {
// Text Input
Expand All @@ -83,12 +90,12 @@
(event) => {
if (event.key === 'Enter' && !event.shiftKey) {
event.preventDefault();
if (!isSendButtonDisabled) {
if (!isSendButtonDisabled && !hasStopButton) {
handleSend();
}
}
},
[handleSend]

Check warning on line 98 in packages/module/src/MessageBar/MessageBar.tsx

View workflow job for this annotation

GitHub Actions / call-build-lint-test-workflow / lint

React Hook React.useCallback has missing dependencies: 'hasStopButton' and 'isSendButtonDisabled'. Either include them or remove the dependency array
);

const handleAttachMenuToggle = () => {
Expand All @@ -96,21 +103,12 @@
attachMenuProps?.onAttachMenuToggleClick();
};

const messageBarContents = (
<>
<div className="pf-chatbot__message-bar-input">
<AutoTextArea
ref={textareaRef}
className="pf-chatbot__message-textarea"
value={message as any} // Added any to make the third part TextArea component types happy. Remove when replced with PF TextArea
onChange={handleChange as any} // Added any to make the third part TextArea component types happy. Remove when replced with PF TextArea
onKeyDown={handleKeyDown}
placeholder={isListeningMessage ? 'Listening' : 'Send a message...'}
aria-label={isListeningMessage ? 'Listening' : 'Send a message...'}
{...props}
/>
</div>
<div className="pf-chatbot__message-bar-actions">
const renderButtons = () => {
if (hasStopButton && handleStopButton) {
return <StopButton onClick={handleStopButton} />;
}
return (
<>
{attachMenuProps && (
<AttachButton ref={attachButtonRef} onClick={handleAttachMenuToggle} isDisabled={isListeningMessage} />
)}
Expand All @@ -127,7 +125,25 @@
{(alwayShowSendButton || message) && (
<SendButton value={message} onClick={handleSend} isDisabled={isSendButtonDisabled} />
)}
</>
);
};

const messageBarContents = (
<>
<div className="pf-chatbot__message-bar-input">
<AutoTextArea
ref={textareaRef}
className="pf-chatbot__message-textarea"
value={message as any} // Added any to make the third part TextArea component types happy. Remove when replced with PF TextArea

Check warning on line 138 in packages/module/src/MessageBar/MessageBar.tsx

View workflow job for this annotation

GitHub Actions / call-build-lint-test-workflow / lint

Unexpected any. Specify a different type
onChange={handleChange as any} // Added any to make the third part TextArea component types happy. Remove when replced with PF TextArea

Check warning on line 139 in packages/module/src/MessageBar/MessageBar.tsx

View workflow job for this annotation

GitHub Actions / call-build-lint-test-workflow / lint

Unexpected any. Specify a different type
onKeyDown={handleKeyDown}
placeholder={isListeningMessage ? 'Listening' : 'Send a message...'}
aria-label={isListeningMessage ? 'Listening' : 'Send a message...'}
{...props}
/>
</div>
<div className="pf-chatbot__message-bar-actions">{renderButtons()}</div>
</>
);

Expand Down
22 changes: 22 additions & 0 deletions packages/module/src/MessageBar/StopButton.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// ============================================================================
// Chatbot Footer - Message Bar - Stop
// ============================================================================
.pf-v6-c-button.pf-chatbot__button--stop {
background-color: var(--pf-t--global--color--brand--default);
border-radius: var(--pf-t--global--border--radius--pill);
padding: var(--pf-t--global--spacer--md);
width: 3rem;
height: 3rem;
display: flex;
justify-content: center;
align-items: center;

.pf-v6-c-button__icon {
color: var(--pf-t--global--icon--color--inverse);
}

&:hover,
&:focus {
background-color: var(--pf-t--global--color--brand--hover);
}
}
57 changes: 57 additions & 0 deletions packages/module/src/MessageBar/StopButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// ============================================================================
// Chatbot Footer - Message Bar - Send
// ============================================================================
import React from 'react';

// Import PatternFly components
import { Button, ButtonProps, Tooltip, TooltipProps, Icon } from '@patternfly/react-core';

export interface StopButtonProps extends ButtonProps {
/** Callback for when button is clicked */
onClick: () => void;
/** Class name for the stop button */
className?: string;
/** Props to control the PF Tooltip component */
tooltipProps?: TooltipProps;
/** English text "Stop" used in the tooltip */
tooltipContent?: string;
}

export const StopButton: React.FunctionComponent<StopButtonProps> = ({
className,
onClick,
tooltipProps,
tooltipContent = 'Send',
...props
}: StopButtonProps) => (
<Tooltip
id="pf-chatbot__tooltip--send"
content={tooltipContent}
position={tooltipProps?.position || 'top'}
entryDelay={tooltipProps?.entryDelay || 0}
exitDelay={tooltipProps?.exitDelay || 0}
distance={tooltipProps?.distance || 8}
animationDuration={tooltipProps?.animationDuration || 0}
{...tooltipProps}
>
<Button
className={`pf-chatbot__button--stop ${className ?? ''}`}
variant="link"
aria-label={props['aria-label'] || 'Stop button'}
onClick={onClick}
icon={
<Icon iconSize="xl" isInline>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M0.5 3C0.5 1.62109 1.62109 0.5 3 0.5H13C14.3789 0.5 15.5 1.62109 15.5 3V13C15.5 14.3789 14.3789 15.5 13 15.5H3C1.62109 15.5 0.5 14.3789 0.5 13V3Z"
fill="currentColor"
/>
</svg>
</Icon>
}
{...props}
/>
</Tooltip>
);

export default StopButton;
Loading