From aea4f3594af9a02c5ecd6a911ad5094887d07dcd Mon Sep 17 00:00:00 2001 From: Christian Gastrell Date: Mon, 21 Oct 2024 18:53:25 +0200 Subject: [PATCH 1/5] decouple prompt input from prompt component, export it to make it available for other modals --- .../src/logo-generator/components/prompt.tsx | 140 +++++++++++------- .../ai-client/src/logo-generator/index.ts | 1 + 2 files changed, 85 insertions(+), 56 deletions(-) diff --git a/projects/js-packages/ai-client/src/logo-generator/components/prompt.tsx b/projects/js-packages/ai-client/src/logo-generator/components/prompt.tsx index 643bd0e3eb9fa..f3ac034fe8a01 100644 --- a/projects/js-packages/ai-client/src/logo-generator/components/prompt.tsx +++ b/projects/js-packages/ai-client/src/logo-generator/components/prompt.tsx @@ -7,6 +7,7 @@ import { __, sprintf } from '@wordpress/i18n'; import { Icon, info } from '@wordpress/icons'; import debugFactory from 'debug'; import { useCallback, useEffect, useState, useRef } from 'react'; +import { Dispatch, SetStateAction } from 'react'; /** * Internal dependencies */ @@ -37,6 +38,79 @@ type PromptProps = { initialPrompt?: string; }; +export const AiModalPromptInput = ( { + prompt = '', + setPrompt = () => {}, + disabled = false, + generateHandler = () => {}, + placeholder = '', +}: { + prompt: string; + setPrompt: Dispatch< SetStateAction< string > >; + disabled: boolean; + generateHandler: () => void; + placeholder?: string; +} ) => { + const inputRef = useRef< HTMLDivElement | null >( null ); + const hasPrompt = prompt?.length >= MINIMUM_PROMPT_LENGTH; + + const onPromptInput = ( event: React.ChangeEvent< HTMLInputElement > ) => { + setPrompt( event.target.textContent || '' ); + }; + + const onPromptPaste = ( event: React.ClipboardEvent< HTMLInputElement > ) => { + event.preventDefault(); + + const selection = event.currentTarget.ownerDocument.getSelection(); + if ( ! selection || ! selection.rangeCount ) { + return; + } + + // Paste plain text only + const text = event.clipboardData.getData( 'text/plain' ); + + selection.deleteFromDocument(); + const range = selection.getRangeAt( 0 ); + range.insertNode( document.createTextNode( text ) ); + selection.collapseToEnd(); + + setPrompt( inputRef.current?.textContent || '' ); + }; + + const onKeyDown = ( event: React.KeyboardEvent ) => { + if ( event.key === 'Enter' ) { + event.preventDefault(); + generateHandler(); + } + }; + + return ( +
+
+ +
+ ); +}; + export const Prompt = ( { initialPrompt = '' }: PromptProps ) => { const { tracks } = useAnalytics(); const { recordEvent: recordTracksEvent } = tracks; @@ -143,29 +217,6 @@ export const Prompt = ( { initialPrompt = '' }: PromptProps ) => { } }, [ context, generateLogo, prompt, style ] ); - const onPromptInput = ( event: React.ChangeEvent< HTMLInputElement > ) => { - setPrompt( event.target.textContent || '' ); - }; - - const onPromptPaste = ( event: React.ClipboardEvent< HTMLInputElement > ) => { - event.preventDefault(); - - const selection = event.currentTarget.ownerDocument.getSelection(); - if ( ! selection || ! selection.rangeCount ) { - return; - } - - // Paste plain text only - const text = event.clipboardData.getData( 'text/plain' ); - - selection.deleteFromDocument(); - const range = selection.getRangeAt( 0 ); - range.insertNode( document.createTextNode( text ) ); - selection.collapseToEnd(); - - setPrompt( inputRef.current?.textContent || '' ); - }; - const onUpgradeClick = () => { recordTracksEvent( EVENT_UPGRADE, { context, placement: EVENT_PLACEMENT_INPUT_FOOTER } ); }; @@ -179,13 +230,6 @@ export const Prompt = ( { initialPrompt = '' }: PromptProps ) => { [ context, setStyle, recordTracksEvent ] ); - const onKeyDown = ( event: React.KeyboardEvent ) => { - if ( event.key === 'Enter' ) { - event.preventDefault(); - onGenerate(); - } - }; - return (
@@ -212,32 +256,16 @@ export const Prompt = ( { initialPrompt = '' }: PromptProps ) => { /> ) }
-
-
- -
+
{ ! isUnlimited && ! requireUpgrade && (
diff --git a/projects/js-packages/ai-client/src/logo-generator/index.ts b/projects/js-packages/ai-client/src/logo-generator/index.ts index e6a9b9dec67ce..fab7b12e9442f 100644 --- a/projects/js-packages/ai-client/src/logo-generator/index.ts +++ b/projects/js-packages/ai-client/src/logo-generator/index.ts @@ -1 +1,2 @@ export * from './components/generator-modal.js'; +export { AiModalPromptInput } from './components/prompt.js'; From 4f0d3d0afc23dc7319b139bfe58ff4de22dd3d77 Mon Sep 17 00:00:00 2001 From: Christian Gastrell Date: Tue, 22 Oct 2024 18:12:56 +0200 Subject: [PATCH 2/5] allow passing down CTA button label as prop --- .../ai-client/src/logo-generator/components/prompt.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/projects/js-packages/ai-client/src/logo-generator/components/prompt.tsx b/projects/js-packages/ai-client/src/logo-generator/components/prompt.tsx index f3ac034fe8a01..13a83c5a14140 100644 --- a/projects/js-packages/ai-client/src/logo-generator/components/prompt.tsx +++ b/projects/js-packages/ai-client/src/logo-generator/components/prompt.tsx @@ -44,12 +44,14 @@ export const AiModalPromptInput = ( { disabled = false, generateHandler = () => {}, placeholder = '', + buttonLabel = '', }: { prompt: string; setPrompt: Dispatch< SetStateAction< string > >; disabled: boolean; generateHandler: () => void; placeholder?: string; + buttonLabel?: string; } ) => { const inputRef = useRef< HTMLDivElement | null >( null ); const hasPrompt = prompt?.length >= MINIMUM_PROMPT_LENGTH; @@ -105,7 +107,7 @@ export const AiModalPromptInput = ( { onClick={ generateHandler } disabled={ disabled || ! hasPrompt } > - { __( 'Generate', 'jetpack-ai-client' ) } + { buttonLabel || __( 'Generate', 'jetpack-ai-client' ) }
); From 3bae854d8b23ba5b0c4107a7682df3fb623fc372 Mon Sep 17 00:00:00 2001 From: Christian Gastrell Date: Tue, 22 Oct 2024 18:24:38 +0200 Subject: [PATCH 3/5] implement AiModalPromptInput on GP image generation modal --- .../ai-image/components/ai-image-modal.tsx | 77 ++++--------------- 1 file changed, 13 insertions(+), 64 deletions(-) diff --git a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/ai-image/components/ai-image-modal.tsx b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/ai-image/components/ai-image-modal.tsx index 12572e7df2284..ec2868aaabdd3 100644 --- a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/ai-image/components/ai-image-modal.tsx +++ b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/ai-image/components/ai-image-modal.tsx @@ -1,9 +1,10 @@ /** * External dependencies */ -import { Button, Tooltip, KeyboardShortcuts } from '@wordpress/components'; +import { AiModalPromptInput } from '@automattic/jetpack-ai-client'; +import { Button } from '@wordpress/components'; import { useCallback, useRef, useState, useEffect } from '@wordpress/element'; -import { __, sprintf } from '@wordpress/i18n'; +import { __ } from '@wordpress/i18n'; import { Icon, external } from '@wordpress/icons'; /** * Internal dependencies @@ -34,13 +35,11 @@ export default function AiImageModal( { isUnlimited = false, upgradeDescription = null, hasError = false, - postContent = null, handlePreviousImage = () => {}, handleNextImage = () => {}, acceptButton = null, autoStart = false, autoStartAction = null, - generateButtonLabel = null, instructionsPlaceholder = null, }: { title: string; @@ -72,13 +71,6 @@ export default function AiImageModal( { const [ userPrompt, setUserPrompt ] = useState( '' ); const triggeredAutoGeneration = useRef( false ); - const handleUserPromptChange = useCallback( - ( e: React.ChangeEvent< HTMLTextAreaElement > ) => { - setUserPrompt( e.target.value.trim() ); - }, - [ setUserPrompt ] - ); - const handleTryAgain = useCallback( () => { onTryAgain?.( { userPrompt } ); }, [ onTryAgain, userPrompt ] ); @@ -87,37 +79,13 @@ export default function AiImageModal( { onGenerate?.( { userPrompt } ); }, [ onGenerate, userPrompt ] ); - const costTooltipTextSingular = __( '1 request per image', 'jetpack' ); - - const costTooltipTextPlural = sprintf( - // Translators: %d is the cost of generating one image. - __( '%d requests per image', 'jetpack' ), - cost - ); - - const costTooltipText = cost === 1 ? costTooltipTextSingular : costTooltipTextPlural; - // Controllers const instructionsDisabled = notEnoughRequests || generating || requireUpgrade; const upgradePromptVisible = ( requireUpgrade || notEnoughRequests ) && ! generating; const counterVisible = Boolean( ! isUnlimited && cost && currentLimit ); - const tryAgainButtonDisabled = ! userPrompt && ! postContent; - const generateButtonDisabled = - notEnoughRequests || generating || ( ! userPrompt && ! postContent ); - const tryAgainButton = ( - - ); - - const generateButton = ( - - - - ); + const generateLabel = __( 'Generate', 'jetpack' ); + const tryAgainLabel = __( 'Try again', 'jetpack' ); /** * Trigger image generation automatically. @@ -136,28 +104,14 @@ export default function AiImageModal( { { open && (
-
-
- { - if ( ! generateButtonDisabled ) { - handleGenerate(); - } - }, - } } - > - - -
-
+ { upgradePromptVisible && ( ) }
-
-
- { hasError ? tryAgainButton : generateButton } -
-
Date: Tue, 22 Oct 2024 18:25:56 +0200 Subject: [PATCH 4/5] changelog --- .../changelog/change-jetpack-ai-modal-prompt-input-reuse | 4 ++++ .../changelog/change-jetpack-ai-modal-prompt-input-reuse | 4 ++++ 2 files changed, 8 insertions(+) create mode 100644 projects/js-packages/ai-client/changelog/change-jetpack-ai-modal-prompt-input-reuse create mode 100644 projects/plugins/jetpack/changelog/change-jetpack-ai-modal-prompt-input-reuse diff --git a/projects/js-packages/ai-client/changelog/change-jetpack-ai-modal-prompt-input-reuse b/projects/js-packages/ai-client/changelog/change-jetpack-ai-modal-prompt-input-reuse new file mode 100644 index 0000000000000..c4a38f6eb8440 --- /dev/null +++ b/projects/js-packages/ai-client/changelog/change-jetpack-ai-modal-prompt-input-reuse @@ -0,0 +1,4 @@ +Significance: minor +Type: changed + +AI Client: decouple prompt input as component and export it for reusability diff --git a/projects/plugins/jetpack/changelog/change-jetpack-ai-modal-prompt-input-reuse b/projects/plugins/jetpack/changelog/change-jetpack-ai-modal-prompt-input-reuse new file mode 100644 index 0000000000000..958a637ead149 --- /dev/null +++ b/projects/plugins/jetpack/changelog/change-jetpack-ai-modal-prompt-input-reuse @@ -0,0 +1,4 @@ +Significance: patch +Type: other + +Jetpack AI: use new exported component for AI generation modal on GP image generation From faa7142d896d61dcf5223f4cef5eb20535f21f0d Mon Sep 17 00:00:00 2001 From: Christian Gastrell Date: Tue, 22 Oct 2024 20:24:12 +0200 Subject: [PATCH 5/5] fix wee css issue --- .../components/ai-image/components/ai-image-modal.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/ai-image/components/ai-image-modal.scss b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/ai-image/components/ai-image-modal.scss index 0f914277191bc..c47eb5e218245 100644 --- a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/ai-image/components/ai-image-modal.scss +++ b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/ai-image/components/ai-image-modal.scss @@ -15,7 +15,6 @@ &__actions { width: 100%; display: flex; - justify-content: center; } &__user-prompt {