Skip to content

Commit

Permalink
AI Assistant: add suggestions actions (#34399)
Browse files Browse the repository at this point in the history
* add suggestion action buttons

* add editRequest state to handle back and forth editing 'mode'

* add discard callback on AI control, pass on tryAgain handler

* add changelog entries

* adjust behavior upon observations
  • Loading branch information
CGastrell committed Dec 15, 2023
1 parent b3f1e36 commit 725451f
Show file tree
Hide file tree
Showing 5 changed files with 133 additions and 21 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: added

AI Client: add internal state management to toggle editing last prompt. Add discard handler prop
124 changes: 106 additions & 18 deletions projects/js-packages/ai-client/src/components/ai-control/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,23 @@
import { PlainText } from '@wordpress/block-editor';
import { Button } from '@wordpress/components';
import { useKeyboardShortcut } from '@wordpress/compose';
import { forwardRef, useImperativeHandle, useRef } from '@wordpress/element';
import {
forwardRef,
useImperativeHandle,
useRef,
useEffect,
useCallback,
} from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import { Icon, closeSmall, check, arrowUp } from '@wordpress/icons';
import {
Icon,
closeSmall,
check,
arrowUp,
arrowLeft,
trash,
reusableBlock,
} from '@wordpress/icons';
import classNames from 'classnames';
import React from 'react';
/**
Expand Down Expand Up @@ -60,6 +74,7 @@ export function AIControl(
onSend = noop,
onStop = noop,
onAccept = noop,
onDiscard = noop,
}: {
disabled?: boolean;
value: string;
Expand All @@ -75,11 +90,43 @@ export function AIControl(
onSend?: ( currentValue: string ) => void;
onStop?: () => void;
onAccept?: () => void;
onDiscard?: () => void;
},
ref: React.MutableRefObject< null > // eslint-disable-line @typescript-eslint/ban-types
): React.ReactElement {
const promptUserInputRef = useRef( null );
const loading = state === 'requesting' || state === 'suggesting';
const [ editRequest, setEditRequest ] = React.useState( false );
const [ lastValue, setLastValue ] = React.useState( '' );

useEffect( () => {
if ( editRequest ) {
promptUserInputRef?.current?.focus();
}

if ( ! editRequest && lastValue && value !== lastValue ) {
onChange?.( lastValue );
}
}, [ editRequest, lastValue ] );

const sendRequest = useCallback( () => {
setLastValue( value );
setEditRequest( false );
onSend?.( value );
}, [ value ] );

const changeHandler = useCallback(
( newValue: string ) => {
onChange?.( newValue );
setEditRequest( state !== 'init' && lastValue && lastValue !== newValue );
},
[ lastValue, state ]
);

const discardHandler = useCallback( () => {
onDiscard?.();
onAccept?.();
}, [] );

// Pass the ref to forwardRef.
useImperativeHandle( ref, () => promptUserInputRef.current );
Expand All @@ -100,7 +147,7 @@ export function AIControl(
'enter',
e => {
e.preventDefault();
onSend?.( value );
sendRequest();
},
{
target: promptUserInputRef,
Expand All @@ -119,30 +166,47 @@ export function AIControl(
<div className="jetpack-components-ai-control__input-wrapper">
<PlainText
value={ value }
onChange={ onChange }
onChange={ changeHandler }
placeholder={ placeholder }
className="jetpack-components-ai-control__input"
disabled={ loading || disabled }
ref={ promptUserInputRef }
/>
</div>

{ ! showAccept && value?.length > 0 && (
{ ( ! showAccept || editRequest ) && value?.length > 0 && (
<div className="jetpack-components-ai-control__controls-prompt_button_wrapper">
{ ! loading ? (
<Button
className="jetpack-components-ai-control__controls-prompt_button"
onClick={ () => onSend?.( value ) }
variant="primary"
disabled={ ! value?.length || disabled }
label={ __( 'Send request', 'jetpack-ai-client' ) }
>
{ showButtonLabels ? (
__( 'Generate', 'jetpack-ai-client' )
) : (
<Icon icon={ arrowUp } />
<>
{ editRequest && (
<Button
className="jetpack-components-ai-control__controls-prompt_button"
onClick={ () => setEditRequest( false ) }
variant="tertiary"
label={ __( 'Cancel', 'jetpack-ai-client' ) }
>
{ showButtonLabels ? (
__( 'Cancel', 'jetpack-ai-client' )
) : (
<Icon icon={ closeSmall } />
) }
</Button>
) }
</Button>

<Button
className="jetpack-components-ai-control__controls-prompt_button"
onClick={ sendRequest }
variant="primary"
disabled={ ! value?.length || disabled }
label={ __( 'Send request', 'jetpack-ai-client' ) }
>
{ showButtonLabels ? (
__( 'Generate', 'jetpack-ai-client' )
) : (
<Icon icon={ arrowUp } />
) }
</Button>
</>
) : (
<Button
className="jetpack-components-ai-control__controls-prompt_button"
Expand All @@ -160,8 +224,32 @@ export function AIControl(
</div>
) }

{ showAccept && (
{ showAccept && ! editRequest && value?.length > 0 && (
<div className="jetpack-components-ai-control__controls-prompt_button_wrapper">
<Button
className="jetpack-components-ai-control__controls-prompt_button"
label={ __( 'Back to edit', 'jetpack-ai-client' ) }
onClick={ () => setEditRequest( true ) }
tooltipPosition="top"
>
<Icon icon={ arrowLeft } />
</Button>
<Button
className="jetpack-components-ai-control__controls-prompt_button"
label={ __( 'Discard', 'jetpack-ai-client' ) }
onClick={ discardHandler }
tooltipPosition="top"
>
<Icon icon={ trash } />
</Button>
<Button
className="jetpack-components-ai-control__controls-prompt_button"
label={ __( 'Regenerate', 'jetpack-ai-client' ) }
onClick={ () => onSend?.( value ) }
tooltipPosition="top"
>
<Icon icon={ reusableBlock } />
</Button>
<Button
className="jetpack-components-ai-control__controls-prompt_button"
onClick={ onAccept }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,21 @@
}
}

.jetpack-components-ai-control__controls-prompt_button_wrapper {
text-transform: uppercase;
font-size: 11px;
font-weight: 600;
line-height: 16px;
user-select: none;
white-space: nowrap;
display: flex;
align-items: center;

.components-button.is-small:not(.has-label) {
padding: 0;
}
}

.jetpack-components-ai-control__controls-prompt_button {
&:disabled {
opacity: 0.6;
Expand Down
4 changes: 4 additions & 0 deletions projects/plugins/jetpack/changelog/add-ai-suggestions-actions
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: patch
Type: other

Pass down tryAgain handler to AI Control
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@ export default function AIAssistantEdit( { attributes, setAttributes, clientId,
* - Create blocks from HTML code
*/
const HTML = markdownConverter
.render( attributes.content )
.render( attributes.content || '' )
// Fix list indentation
.replace( /<li>\s+<p>/g, '<li>' )
.replace( /<\/p>\s+<\/li>/g, '</li>' );
Expand All @@ -362,7 +362,7 @@ export default function AIAssistantEdit( { attributes, setAttributes, clientId,

const handleAcceptTitle = () => {
if ( isInBlockEditor ) {
editPost( { title: attributes.content.trim() } );
editPost( { title: attributes.content ? attributes.content.trim() : '' } );
removeBlock( clientId );
} else {
handleAcceptContent();
Expand Down Expand Up @@ -568,10 +568,11 @@ export default function AIAssistantEdit( { attributes, setAttributes, clientId,
onSend={ handleSend }
onStop={ handleStopSuggestion }
onAccept={ handleAccept }
onDiscard={ handleTryAgain }
state={ requestingState }
isTransparent={ requireUpgrade || ! connected }
showButtonLabels={ ! isMobileViewport }
showAccept={ contentIsLoaded && ! isWaitingState }
showAccept={ requestingState !== 'init' && contentIsLoaded && ! isWaitingState }
acceptLabel={ acceptLabel }
showGuideLine={ contentIsLoaded }
/>
Expand Down

0 comments on commit 725451f

Please sign in to comment.