fix(sidepanel): Fixes some issues while conversation is streaming (#2828)
* fix(sidepanel): Stop navigation during streaming * fix(sidepanel): Fix scroll jumping while conversation is streaming * fix(sidepanel): Keep input enabled but disable sending while streaming --------- Co-authored-by: Dylan Tientcheu <dylan.tientcheu@algolia.com>
This commit is contained in:
parent
8c750e5102
commit
8ad60d9bab
6 changed files with 43 additions and 10 deletions
|
|
@ -281,6 +281,19 @@ html[data-theme='dark'] {
|
|||
display: none;
|
||||
}
|
||||
|
||||
.DocSearch-Sidepanel-Action-menu[aria-disabled='true'],
|
||||
.DocSearch-Sidepanel-Action-back[aria-disabled='true'] {
|
||||
user-select: none;
|
||||
pointer-events: none;
|
||||
color: var(--docsearch-sidepanel-accent-muted);
|
||||
}
|
||||
|
||||
.DocSearch-Sidepanel-Action-menu[aria-disabled='true']:hover,
|
||||
.DocSearch-Sidepanel-Action-back[aria-disabled='true']:hover {
|
||||
color: var(--docsearch-sidepanel-accent-muted);
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 769px) {
|
||||
.DocSearch-Sidepanel-Action-menu,
|
||||
.DocSearch-Sidepanel-Action-expand {
|
||||
|
|
|
|||
|
|
@ -37,15 +37,22 @@ export function Menu({ children }: PropsWithChildren): JSX.Element {
|
|||
|
||||
type MenuTriggerProps = ButtonHTMLAttributes<HTMLButtonElement>;
|
||||
|
||||
function MenuTrigger({ children, className = '' }: PropsWithChildren<MenuTriggerProps>): JSX.Element {
|
||||
function MenuTrigger({ children, className = '', disabled }: PropsWithChildren<MenuTriggerProps>): JSX.Element {
|
||||
const { open, setOpen } = React.useContext(MenuContext);
|
||||
|
||||
function toggleOpen(): void {
|
||||
if (disabled) return;
|
||||
|
||||
setOpen(!open);
|
||||
}
|
||||
|
||||
return (
|
||||
<button type="button" className={`DocSearch-Menu-trigger ${className}`} onClick={() => toggleOpen()}>
|
||||
<button
|
||||
type="button"
|
||||
className={`DocSearch-Menu-trigger ${className}${disabled ? ' disabled' : ''}`}
|
||||
aria-disabled={disabled}
|
||||
onClick={() => toggleOpen()}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -260,15 +260,17 @@ export const ConversationScreen = memo(
|
|||
translations;
|
||||
|
||||
const mostRecentExchangeRef = React.useRef<HTMLDivElement>(null);
|
||||
const totalExchanges = exchanges.length;
|
||||
|
||||
// Only scroll the most recent exchange into view when needed
|
||||
React.useEffect(() => {
|
||||
if (mostRecentExchangeRef.current) {
|
||||
mostRecentExchangeRef.current.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'nearest',
|
||||
block: 'start',
|
||||
});
|
||||
}
|
||||
}, [exchanges, props.status]);
|
||||
}, [totalExchanges]);
|
||||
|
||||
return (
|
||||
<div className="DocSearch-Sidepanel-ConversationScreen">
|
||||
|
|
|
|||
|
|
@ -74,6 +74,8 @@ export const PromptForm = React.forwardRef<HTMLTextAreaElement, Props>(
|
|||
};
|
||||
|
||||
const handleSend = (): void => {
|
||||
if (isStreaming) return;
|
||||
|
||||
const prompt = userPrompt.trim();
|
||||
|
||||
if (prompt === '') return;
|
||||
|
|
@ -90,6 +92,9 @@ export const PromptForm = React.forwardRef<HTMLTextAreaElement, Props>(
|
|||
};
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>): void => {
|
||||
// Allow Enter to work normally (new line) when streaming
|
||||
if (isStreaming) return;
|
||||
|
||||
if (e.key === 'Enter' && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
|
||||
|
|
@ -116,6 +121,8 @@ export const PromptForm = React.forwardRef<HTMLTextAreaElement, Props>(
|
|||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (isStreaming) return;
|
||||
|
||||
handleSend();
|
||||
}}
|
||||
>
|
||||
|
|
@ -123,7 +130,6 @@ export const PromptForm = React.forwardRef<HTMLTextAreaElement, Props>(
|
|||
ref={promptRef}
|
||||
placeholder={promptPlaceholder}
|
||||
className="DocSearch-Sidepanel-Prompt--textarea"
|
||||
aria-disabled={isStreaming}
|
||||
value={userPrompt}
|
||||
aria-label={promptAriaLabelText}
|
||||
aria-labelledby="prompt-label"
|
||||
|
|
|
|||
|
|
@ -363,6 +363,7 @@ function SidepanelInner(
|
|||
exchanges={exchanges}
|
||||
setSidepanelState={setSidepanelState}
|
||||
hasConversations={conversations.getAll().length > 0}
|
||||
isStreaming={isStreaming}
|
||||
onNewConversation={handleStartNewConversation}
|
||||
onToggleExpanded={toggleIsExpanded}
|
||||
onClose={onClose}
|
||||
|
|
|
|||
|
|
@ -21,15 +21,17 @@ import type { SidepanelState } from './types';
|
|||
type BackButtonProps = {
|
||||
mobile?: boolean;
|
||||
onBack: () => void;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
const BackButton = ({ onBack, mobile = false }: BackButtonProps): JSX.Element => {
|
||||
const BackButton = ({ onBack, mobile = false, disabled = false }: BackButtonProps): JSX.Element => {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className={`DocSearch-Action DocSearch-Sidepanel-Action-back${mobile ? ' mobile' : ''}`}
|
||||
title="Go back to previous screen"
|
||||
onClick={onBack}
|
||||
aria-disabled={disabled}
|
||||
onClick={disabled ? undefined : onBack}
|
||||
>
|
||||
<BackIcon />
|
||||
</button>
|
||||
|
|
@ -64,6 +66,7 @@ type SidepanelHeaderProps = {
|
|||
onClose: () => void;
|
||||
translations?: HeaderTranslations;
|
||||
hasConversations: boolean;
|
||||
isStreaming: boolean;
|
||||
};
|
||||
|
||||
export const SidepanelHeader = React.memo(
|
||||
|
|
@ -76,6 +79,7 @@ export const SidepanelHeader = React.memo(
|
|||
onClose,
|
||||
translations = {},
|
||||
hasConversations,
|
||||
isStreaming,
|
||||
}: SidepanelHeaderProps): JSX.Element => {
|
||||
const {
|
||||
title = 'Ask AI',
|
||||
|
|
@ -113,7 +117,7 @@ export const SidepanelHeader = React.memo(
|
|||
return (
|
||||
<header className="DocSearch-Sidepanel-Header">
|
||||
<div className="DocSearch-Sidepanel-Header--left">
|
||||
<BackButton mobile={true} onBack={onBack} />
|
||||
<BackButton mobile={true} disabled={isStreaming} onBack={onBack} />
|
||||
|
||||
{sidepanelState === 'conversation-history' && <div className="DocSearch-Divider" />}
|
||||
|
||||
|
|
@ -133,14 +137,14 @@ export const SidepanelHeader = React.memo(
|
|||
</button>
|
||||
</div>
|
||||
<div className="DocSearch-Sidepanel-Header--center">
|
||||
<BackButton onBack={onBack} />
|
||||
<BackButton disabled={isStreaming} onBack={onBack} />
|
||||
<SparklesIcon className="DocSearch-Sidepanel-Header-TitleIcon" />
|
||||
<h2 className="DocSearch-Sidepanel-Title">{header}</h2>
|
||||
</div>
|
||||
<div className="DocSearch-Sidepanel-Header--right">
|
||||
{sidepanelState !== 'conversation-history' && (!newConversationDisabled || hasConversations) && (
|
||||
<Menu>
|
||||
<Menu.Trigger className="DocSearch-Action DocSearch-Sidepanel-Action-menu">
|
||||
<Menu.Trigger className="DocSearch-Action DocSearch-Sidepanel-Action-menu" disabled={isStreaming}>
|
||||
<MoreVerticalIcon />
|
||||
</Menu.Trigger>
|
||||
<Menu.Content>
|
||||
|
|
|
|||
Loading…
Reference in a new issue