/** * External dependencies */ import { Button, Flex } from '@wordpress/components'; import { useKeyboardShortcut } from '@wordpress/compose'; import { useImperativeHandle, useRef, useEffect, useCallback, useState } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import { Icon, closeSmall, check, arrowUp, trash, reusableBlock as regenerate, } from '@wordpress/icons'; import debugFactory from 'debug'; import { forwardRef } from 'react'; /** * Internal dependencies */ import { GuidelineMessage } from '../message/index.tsx'; import AIControl from './ai-control.tsx'; import './style.scss'; /** * Types */ import type { RequestingStateProp } from '../../types.ts'; import type { MutableRefObject, ReactElement } from 'react'; type BlockAIControlProps = { disabled?: boolean; value: string; placeholder?: string; showAccept?: boolean; acceptLabel?: string; showButtonLabels?: boolean; isTransparent?: boolean; state?: RequestingStateProp; showGuideLine?: boolean; customFooter?: ReactElement; onChange?: ( newValue: string ) => void; onSend?: ( currentValue: string ) => void; onStop?: () => void; onAccept?: () => void; onDiscard?: () => void; showRemove?: boolean; banner?: ReactElement; error?: ReactElement; lastAction?: string; }; const debug = debugFactory( 'jetpack-ai-client:block-ai-control' ); /** * BlockAIControl component. Used by the AI Assistant block, adding logic and components to the base AIControl component. * * @param {BlockAIControlProps} props - Component props * @param {MutableRefObject} ref - Ref to the component * @return {ReactElement} Rendered component */ export function BlockAIControl( { disabled = false, value = '', placeholder = '', showAccept = false, acceptLabel = __( 'Accept', 'jetpack-ai-client' ), showButtonLabels = true, isTransparent = false, state = 'init', showGuideLine = false, customFooter = null, onChange, onSend, onStop, onAccept, onDiscard, showRemove = false, banner = null, error = null, lastAction, }: BlockAIControlProps, ref: MutableRefObject< HTMLInputElement > ): ReactElement { const loading = state === 'requesting' || state === 'suggesting'; const [ editRequest, setEditRequest ] = useState( false ); const [ lastValue, setLastValue ] = useState( value || null ); const promptUserInputRef = useRef( null ); // Pass the ref to forwardRef. useImperativeHandle( ref, () => promptUserInputRef.current ); useEffect( () => { if ( editRequest ) { promptUserInputRef?.current?.focus(); } }, [ editRequest ] ); const sendHandler = useCallback( () => { setLastValue( value ); setEditRequest( false ); onSend?.( value ); }, [ value ] ); const changeHandler = useCallback( ( newValue: string ) => { onChange?.( newValue ); if ( state === 'init' ) { return; } if ( ! lastValue ) { // here we're coming from a one-click action setEditRequest( newValue.length > 0 ); } else { // here we're coming from an edit action setEditRequest( newValue !== lastValue ); } }, [ lastValue, state ] ); const discardHandler = useCallback( () => { onDiscard?.(); }, [] ); const cancelEdit = useCallback( () => { debug( 'cancelEdit, revert to last value', lastValue ); onChange?.( lastValue || '' ); setEditRequest( false ); }, [ lastValue ] ); useKeyboardShortcut( 'mod+enter', () => { if ( showAccept ) { onAccept?.(); } }, { target: promptUserInputRef, } ); useKeyboardShortcut( 'enter', e => { e.preventDefault(); sendHandler(); }, { target: promptUserInputRef, } ); const actions = ( <> { ( ! showAccept || editRequest ) && (
{ ! loading ? ( <> { editRequest && ( ) } { showRemove && ! editRequest && ! value?.length && onDiscard && ( ) } { value?.length > 0 && ( ) } ) : ( ) }
) } { showAccept && ! editRequest && (
{ ( value?.length > 0 || lastValue === null ) && ( ) }
) } ); const message = showGuideLine && ! loading && ! editRequest && ( customFooter || ( ) ); return ( ); } export default forwardRef( BlockAIControl );