From 05aa446dd552fb3953c6592a0d14842a910a6571 Mon Sep 17 00:00:00 2001 From: Douglas Henri Date: Tue, 26 Sep 2023 17:39:47 -0300 Subject: [PATCH] AI Extension: Add keyboard shortcut for stop action on forms and update text (#33271) * add stop shortcut to AIControl component * add tooltip to Stop action * update component story * remove tooltip in favor of permanent text * focus on prompt after stop for forms * changelog * remove esc shortcut from AIControl component * move shortcuts to KeyboardShortcuts wrapper component * move focus logic on Form extension to AIAssistantBar component * bump version --- .../changelog/update-ai-control-stop-text | 4 + projects/js-packages/ai-client/package.json | 2 +- .../src/components/ai-control/index.tsx | 4 +- .../ai-control/stories/index.stories.tsx | 5 +- .../changelog/update-ai-control-stop-text | 4 + .../extensions/blocks/ai-assistant/edit.js | 366 +++++++++--------- .../components/ai-assistant-bar/index.tsx | 83 ++-- 7 files changed, 251 insertions(+), 217 deletions(-) create mode 100644 projects/js-packages/ai-client/changelog/update-ai-control-stop-text create mode 100644 projects/plugins/jetpack/changelog/update-ai-control-stop-text diff --git a/projects/js-packages/ai-client/changelog/update-ai-control-stop-text b/projects/js-packages/ai-client/changelog/update-ai-control-stop-text new file mode 100644 index 0000000000000..d40cd60aca055 --- /dev/null +++ b/projects/js-packages/ai-client/changelog/update-ai-control-stop-text @@ -0,0 +1,4 @@ +Significance: patch +Type: added + +AI Client: Add keyboard shortcut text next to Stop action diff --git a/projects/js-packages/ai-client/package.json b/projects/js-packages/ai-client/package.json index e747c57ced3f8..b2564f8968e93 100644 --- a/projects/js-packages/ai-client/package.json +++ b/projects/js-packages/ai-client/package.json @@ -1,7 +1,7 @@ { "private": false, "name": "@automattic/jetpack-ai-client", - "version": "0.1.9", + "version": "0.1.10-alpha", "description": "A JS client for consuming Jetpack AI services", "homepage": "https://github.com/Automattic/jetpack/tree/HEAD/projects/js-packages/ai-client/#readme", "bugs": { diff --git a/projects/js-packages/ai-client/src/components/ai-control/index.tsx b/projects/js-packages/ai-client/src/components/ai-control/index.tsx index a289919f539db..7541b2d4dfed7 100644 --- a/projects/js-packages/ai-client/src/components/ai-control/index.tsx +++ b/projects/js-packages/ai-client/src/components/ai-control/index.tsx @@ -143,7 +143,7 @@ export function AIControl( { ! loading ? ( ) } diff --git a/projects/js-packages/ai-client/src/components/ai-control/stories/index.stories.tsx b/projects/js-packages/ai-client/src/components/ai-control/stories/index.stories.tsx index c2648a6e76c49..4490579337df3 100644 --- a/projects/js-packages/ai-client/src/components/ai-control/stories/index.stories.tsx +++ b/projects/js-packages/ai-client/src/components/ai-control/stories/index.stories.tsx @@ -25,7 +25,7 @@ export default { ), ], argTypes: { - requestingState: { + state: { control: { type: 'select', }, @@ -51,10 +51,9 @@ const Template = args => { }; const DefaultArgs = { - loading: false, isTransparent: false, placeholder: '', - requestingState: 'init', + state: 'init', showButtonLabels: true, showAccept: false, acceptLabel: 'Accept', diff --git a/projects/plugins/jetpack/changelog/update-ai-control-stop-text b/projects/plugins/jetpack/changelog/update-ai-control-stop-text new file mode 100644 index 0000000000000..5c1e1c1fdaf5e --- /dev/null +++ b/projects/plugins/jetpack/changelog/update-ai-control-stop-text @@ -0,0 +1,4 @@ +Significance: patch +Type: enhancement + +AI Extension: Add keyboard shortcut for stop action on forms diff --git a/projects/plugins/jetpack/extensions/blocks/ai-assistant/edit.js b/projects/plugins/jetpack/extensions/blocks/ai-assistant/edit.js index 8b053339fc59b..942892bdcb65d 100644 --- a/projects/plugins/jetpack/extensions/blocks/ai-assistant/edit.js +++ b/projects/plugins/jetpack/extensions/blocks/ai-assistant/edit.js @@ -15,8 +15,9 @@ import { ToggleControl, TextareaControl, Button, + KeyboardShortcuts, } from '@wordpress/components'; -import { useKeyboardShortcut, useViewportMatch } from '@wordpress/compose'; +import { useViewportMatch } from '@wordpress/compose'; import { useSelect, useDispatch } from '@wordpress/data'; import { RawHTML, useState } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; @@ -371,6 +372,7 @@ export default function AIAssistantEdit( { attributes, setAttributes, clientId } const handleStopSuggestion = () => { stopSuggestion(); + focusOnPrompt(); }; const handleImageRequest = () => { @@ -391,18 +393,6 @@ export default function AIAssistantEdit( { attributes, setAttributes, clientId } } ); }; - useKeyboardShortcut( - 'esc', - e => { - e.stopImmediatePropagation(); - handleStopSuggestion(); - focusOnPrompt(); - }, - { - target: blockRef, - } - ); - /* * Custom prompt modal */ @@ -419,175 +409,189 @@ export default function AIAssistantEdit( { attributes, setAttributes, clientId } const innerBlocks = useInnerBlocksProps( blockProps ); return ( -
- { errorData?.message && ! errorDismissed && errorData?.code !== 'error_quota_exceeded' && ( - - { errorData.message } - - ) } - - { contentIsLoaded && ! useGutenbergSyntax && ( -
- { markdownConverter.render( attributes.content ) } -
- ) } - - { contentIsLoaded && useGutenbergSyntax && ( -
- ) } - - { isPlaygroundVisible && ( - - - - setAttributes( { useGutenbergSyntax: check } ) } - checked={ attributes.useGutenbergSyntax } - /> - - - setAttributes( { useGpt4: check } ) } - checked={ attributes.useGpt4 } - /> - - - { isCustomPrompModalVisible && ( - - setAttributes( { customSystemPrompt: value } ) } - className="jetpack-ai-assistant__custom-prompt" - value={ - attributes.customSystemPrompt || - getInitialSystemPrompt( { - useGutenbergSyntax: attributes.useGutenbergSyntax, - useGpt4: attributes.useGpt4, - } )?.content - } - /> -
- - - -
-
- ) } - -
-
-
- ) } - - { requireUpgrade && } - { ! connected && } - { ! isWaitingState && connected && ! requireUpgrade && ( - { - if ( ! aiControlRef?.current ) { - return; - } - - const userPromptInput = aiControlRef.current; - - // Focus the text area - userPromptInput.focus(); - - // Add a typing effect in the text area - for ( let i = 0; i < prompt.length; i++ ) { - setTimeout( () => { - setUserPrompt( prompt.slice( 0, i + 1 ) ); - }, 25 * i ); - } - } } - recordEvent={ tracks.recordEvent } - isGeneratingTitle={ isGeneratingTitle } - /> - ) } - - - { ! loadingImages && resultImages.length > 0 && ( - - { + if ( [ 'requesting', 'suggesting' ].includes( requestingState ) ) { + handleStopSuggestion(); + } + }, + } } + > +
+ { errorData?.message && ! errorDismissed && errorData?.code !== 'error_quota_exceeded' && ( + - { attributes.requestedPrompt } - - - { __( 'Please choose your image', 'jetpack' ) } - - - { resultImages.map( image => ( - - ) ) } - - - ) } - - { ! loadingImages && imageModal && ( - setImageModal( null ) }> - + ) } + + { contentIsLoaded && ! useGutenbergSyntax && ( +
+ { markdownConverter.render( attributes.content ) } +
+ ) } + + { contentIsLoaded && useGutenbergSyntax && ( +
- - ) } -
+ ) } + + { isPlaygroundVisible && ( + + + + setAttributes( { useGutenbergSyntax: check } ) } + checked={ attributes.useGutenbergSyntax } + /> + + + setAttributes( { useGpt4: check } ) } + checked={ attributes.useGpt4 } + /> + + + { isCustomPrompModalVisible && ( + + setAttributes( { customSystemPrompt: value } ) } + className="jetpack-ai-assistant__custom-prompt" + value={ + attributes.customSystemPrompt || + getInitialSystemPrompt( { + useGutenbergSyntax: attributes.useGutenbergSyntax, + useGpt4: attributes.useGpt4, + } )?.content + } + /> +
+ + + +
+
+ ) } + +
+
+
+ ) } + + { requireUpgrade && } + { ! connected && } + { ! isWaitingState && connected && ! requireUpgrade && ( + { + if ( ! aiControlRef?.current ) { + return; + } + + const userPromptInput = aiControlRef.current; + + // Focus the text area + userPromptInput.focus(); + + // Add a typing effect in the text area + for ( let i = 0; i < prompt.length; i++ ) { + setTimeout( () => { + setUserPrompt( prompt.slice( 0, i + 1 ) ); + }, 25 * i ); + } + } } + recordEvent={ tracks.recordEvent } + isGeneratingTitle={ isGeneratingTitle } + /> + ) } + + + { ! loadingImages && resultImages.length > 0 && ( + + + { attributes.requestedPrompt } + + + { __( 'Please choose your image', 'jetpack' ) } + + + { resultImages.map( image => ( + + ) ) } + + + ) } + + { ! loadingImages && imageModal && ( + setImageModal( null ) }> + + + ) } +
+ ); } diff --git a/projects/plugins/jetpack/extensions/blocks/ai-assistant/extensions/jetpack-contact-form/components/ai-assistant-bar/index.tsx b/projects/plugins/jetpack/extensions/blocks/ai-assistant/extensions/jetpack-contact-form/components/ai-assistant-bar/index.tsx index a005abc4824c7..1d1be990813ef 100644 --- a/projects/plugins/jetpack/extensions/blocks/ai-assistant/extensions/jetpack-contact-form/components/ai-assistant-bar/index.tsx +++ b/projects/plugins/jetpack/extensions/blocks/ai-assistant/extensions/jetpack-contact-form/components/ai-assistant-bar/index.tsx @@ -3,6 +3,7 @@ */ import { useAiContext, AIControl, ERROR_QUOTA_EXCEEDED } from '@automattic/jetpack-ai-client'; import { serialize } from '@wordpress/blocks'; +import { KeyboardShortcuts } from '@wordpress/components'; import { useViewportMatch } from '@wordpress/compose'; import { select } from '@wordpress/data'; import { useDispatch } from '@wordpress/data'; @@ -16,10 +17,11 @@ import { } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import { store as noticesStore } from '@wordpress/notices'; +import classNames from 'classnames'; +import React from 'react'; /** * Internal dependencies */ -import classNames from 'classnames'; import ConnectPrompt from '../../../../components/connect-prompt'; import UpgradePrompt from '../../../../components/upgrade-prompt'; import useAIFeature from '../../../../hooks/use-ai-feature'; @@ -83,12 +85,15 @@ export default function AiAssistantBar( { const { inputValue, setInputValue, isVisible, assistantAnchor } = useContext( AiAssistantUiContext ); + const focusOnPrompt = () => { + // Small delay to avoid focus crash + setTimeout( () => { + inputRef.current?.focus?.(); + }, 100 ); + }; + const { requestSuggestion, requestingState, stopSuggestion, requestingError } = useAiContext( { - onDone: () => { - setTimeout( () => { - inputRef.current?.focus?.(); - }, 10 ); - }, + onDone: focusOnPrompt, } ); const { requireUpgrade } = useAIFeature(); @@ -105,7 +110,7 @@ export default function AiAssistantBar( { const { removeNotice } = useDispatch( noticesStore ); - const onSend = useCallback( () => { + const handleSend = useCallback( () => { // Do not send the request if the input value is empty. if ( ! inputValue?.length ) { return; @@ -120,8 +125,14 @@ export default function AiAssistantBar( { } ); requestSuggestion( prompt, { feature: 'jetpack-form-ai-extension' } ); + wrapperRef?.current?.focus(); }, [ clientId, inputValue, removeNotice, requestSuggestion ] ); + const handleStopSuggestion = useCallback( () => { + stopSuggestion(); + focusOnPrompt(); + }, [ stopSuggestion ] ); + /* * Fix the assistant bar when the viewport is mobile, * and the Assistant anchor exists. @@ -204,30 +215,42 @@ export default function AiAssistantBar( { // Assistant bar component. const AiAssistantBarComponent = ( -
{ + if ( [ 'requesting', 'suggesting' ].includes( requestingState ) ) { + handleStopSuggestion(); + } + }, + } } > - { siteRequireUpgrade && } - { ! connected && } - -
+
+ { siteRequireUpgrade && } + { ! connected && } + +
+ ); // Check if the Assistant bar should be rendered in the Assistant anchor (fixed mode)