diff --git a/projects/plugins/jetpack/changelog/update-jetpack-ai-separate-toolbar-dropdown-ui-from-logic b/projects/plugins/jetpack/changelog/update-jetpack-ai-separate-toolbar-dropdown-ui-from-logic new file mode 100644 index 0000000000000..404f2ca4270de --- /dev/null +++ b/projects/plugins/jetpack/changelog/update-jetpack-ai-separate-toolbar-dropdown-ui-from-logic @@ -0,0 +1,4 @@ +Significance: patch +Type: other + +AI Assistant: Separate toolbar dropdown logic and UI diff --git a/projects/plugins/jetpack/extensions/blocks/ai-assistant/components/ai-assistant-toolbar-dropdown/dropdown-content.tsx b/projects/plugins/jetpack/extensions/blocks/ai-assistant/components/ai-assistant-toolbar-dropdown/dropdown-content.tsx new file mode 100644 index 0000000000000..6273fb35e4049 --- /dev/null +++ b/projects/plugins/jetpack/extensions/blocks/ai-assistant/components/ai-assistant-toolbar-dropdown/dropdown-content.tsx @@ -0,0 +1,179 @@ +/* + * External dependencies + */ +import { aiAssistantIcon } from '@automattic/jetpack-ai-client'; +import { MenuItem, MenuGroup, Notice } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import { post, postContent, postExcerpt, termDescription, blockTable } from '@wordpress/icons'; +import React from 'react'; +/** + * Internal dependencies + */ +import { + PROMPT_TYPE_CHANGE_TONE, + PROMPT_TYPE_CORRECT_SPELLING, + PROMPT_TYPE_MAKE_LONGER, + PROMPT_TYPE_SIMPLIFY, + PROMPT_TYPE_SUMMARIZE, + PROMPT_TYPE_CHANGE_LANGUAGE, + PROMPT_TYPE_USER_PROMPT, +} from '../../lib/prompt'; +import { I18nMenuDropdown } from '../i18n-dropdown-control'; +import { ToneDropdownMenu } from '../tone-dropdown-control'; +import './style.scss'; +/** + * Types and constants + */ +import type { ExtendedBlockProp } from '../../extensions/ai-assistant'; +import type { PromptTypeProp } from '../../lib/prompt'; +import type { ToneProp } from '../tone-dropdown-control'; +import type { ReactElement } from 'react'; + +// Quick edits option: "Correct spelling and grammar" +export const QUICK_EDIT_KEY_CORRECT_SPELLING = 'correct-spelling' as const; + +// Quick edits option: "Simplify" +export const QUICK_EDIT_KEY_SIMPLIFY = 'simplify' as const; + +// Quick edits option: "Summarize" +export const QUICK_EDIT_KEY_SUMMARIZE = 'summarize' as const; + +// Quick edits option: "Make longer" +export const QUICK_EDIT_KEY_MAKE_LONGER = 'make-longer' as const; + +// Ask AI Assistant option +export const KEY_ASK_AI_ASSISTANT = 'ask-ai-assistant' as const; + +const quickActionsList = { + default: [ + { + name: __( 'Correct spelling and grammar', 'jetpack' ), + key: QUICK_EDIT_KEY_CORRECT_SPELLING, + aiSuggestion: PROMPT_TYPE_CORRECT_SPELLING, + icon: termDescription, + }, + ], + 'core/paragraph': [ + { + name: __( 'Simplify', 'jetpack' ), + key: QUICK_EDIT_KEY_SIMPLIFY, + aiSuggestion: PROMPT_TYPE_SIMPLIFY, + icon: post, + }, + { + name: __( 'Summarize', 'jetpack' ), + key: QUICK_EDIT_KEY_SUMMARIZE, + aiSuggestion: PROMPT_TYPE_SUMMARIZE, + icon: postExcerpt, + }, + { + name: __( 'Expand', 'jetpack' ), + key: QUICK_EDIT_KEY_MAKE_LONGER, + aiSuggestion: PROMPT_TYPE_MAKE_LONGER, + icon: postContent, + }, + ], + 'core/list': [ + { + name: __( 'Turn list into a table', 'jetpack' ), + key: 'turn-into-table', + aiSuggestion: PROMPT_TYPE_USER_PROMPT, + icon: blockTable, + options: { + userPrompt: 'make a table from this list, do not enclose the response in a code block', + }, + }, + { + name: __( 'Write a post from this list', 'jetpack' ), + key: 'write-post-from-list', + aiSuggestion: PROMPT_TYPE_USER_PROMPT, + icon: post, + options: { + userPrompt: + 'Write a post based on the list items. Include a title as first order heading and try to use secondary headings for each entry', + }, + }, + ], +}; + +export type AiAssistantDropdownOnChangeOptionsArgProps = { + tone?: ToneProp; + language?: string; + userPrompt?: string; +}; + +type AiAssistantToolbarDropdownContentProps = { + blockType: ExtendedBlockProp; + disabled?: boolean; + onAskAiAssistant: () => void; + onRequestSuggestion: ( + promptType: PromptTypeProp, + options?: AiAssistantDropdownOnChangeOptionsArgProps + ) => void; +}; + +/** + * The React UI content of the dropdown. + * @param {AiAssistantToolbarDropdownContentProps} props - The props. + * @returns {ReactElement} The React content of the dropdown. + */ +export default function AiAssistantToolbarDropdownContent( { + blockType, + disabled = false, + onAskAiAssistant, + onRequestSuggestion, +}: AiAssistantToolbarDropdownContentProps ): ReactElement { + const blockQuickActions = quickActionsList[ blockType ] ?? []; + + return ( + <> + { disabled && ( + + { __( 'Add content to activate the tools below', 'jetpack' ) } + + ) } + + + + + { __( 'Ask AI Assistant', 'jetpack' ) } + + + + { [ ...quickActionsList.default, ...blockQuickActions ].map( quickAction => ( + { + onRequestSuggestion( quickAction.aiSuggestion, { ...( quickAction.options ?? {} ) } ); + } } + disabled={ disabled } + > + { quickAction.name } + + ) ) } + + { + onRequestSuggestion( PROMPT_TYPE_CHANGE_TONE, { tone } ); + } } + disabled={ disabled } + /> + + { + onRequestSuggestion( PROMPT_TYPE_CHANGE_LANGUAGE, { language } ); + } } + disabled={ disabled } + /> + + > + ); +} diff --git a/projects/plugins/jetpack/extensions/blocks/ai-assistant/components/ai-assistant-controls/index.tsx b/projects/plugins/jetpack/extensions/blocks/ai-assistant/components/ai-assistant-toolbar-dropdown/index.tsx similarity index 51% rename from projects/plugins/jetpack/extensions/blocks/ai-assistant/components/ai-assistant-controls/index.tsx rename to projects/plugins/jetpack/extensions/blocks/ai-assistant/components/ai-assistant-toolbar-dropdown/index.tsx index 921600abc9e56..913884be98d20 100644 --- a/projects/plugins/jetpack/extensions/blocks/ai-assistant/components/ai-assistant-controls/index.tsx +++ b/projects/plugins/jetpack/extensions/blocks/ai-assistant/components/ai-assistant-toolbar-dropdown/index.tsx @@ -3,147 +3,44 @@ */ import { aiAssistantIcon } from '@automattic/jetpack-ai-client'; import { useAnalytics } from '@automattic/jetpack-shared-extension-utils'; -import { getBlockContent } from '@wordpress/blocks'; -import { MenuItem, MenuGroup, ToolbarButton, Dropdown, Notice } from '@wordpress/components'; +import { ToolbarButton, Dropdown } from '@wordpress/components'; import { useSelect, useDispatch } from '@wordpress/data'; import { useState, useEffect } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; -import { post, postContent, postExcerpt, termDescription, blockTable } from '@wordpress/icons'; import debugFactory from 'debug'; +import React from 'react'; /** * Internal dependencies */ import { getStoreBlockId } from '../../extensions/ai-assistant/with-ai-assistant'; -import { - PROMPT_TYPE_CHANGE_TONE, - PROMPT_TYPE_CORRECT_SPELLING, - PROMPT_TYPE_MAKE_LONGER, - PROMPT_TYPE_SIMPLIFY, - PROMPT_TYPE_SUMMARIZE, - PROMPT_TYPE_CHANGE_LANGUAGE, - PROMPT_TYPE_USER_PROMPT, -} from '../../lib/prompt'; -import { getRawTextFromHTML } from '../../lib/utils/block-content'; +import { getBlocksContent, getRawTextFromHTML } from '../../lib/utils/block-content'; import { transformToAIAssistantBlock } from '../../transforms'; -import { I18nMenuDropdown } from '../i18n-dropdown-control'; -import { ToneDropdownMenu } from '../tone-dropdown-control'; +import AiAssistantToolbarDropdownContent from './dropdown-content'; import './style.scss'; /** * Types and constants */ +import type { AiAssistantDropdownOnChangeOptionsArgProps } from './dropdown-content'; import type { ExtendedBlockProp } from '../../extensions/ai-assistant'; import type { PromptTypeProp } from '../../lib/prompt'; -import type { ToneProp } from '../tone-dropdown-control'; import type { ReactElement } from 'react'; const debug = debugFactory( 'jetpack-ai-assistant:dropdown' ); -// Quick edits option: "Correct spelling and grammar" -const QUICK_EDIT_KEY_CORRECT_SPELLING = 'correct-spelling' as const; - -// Quick edits option: "Simplify" -const QUICK_EDIT_KEY_SIMPLIFY = 'simplify' as const; - -// Quick edits option: "Summarize" -const QUICK_EDIT_KEY_SUMMARIZE = 'summarize' as const; - -// Quick edits option: "Make longer" -const QUICK_EDIT_KEY_MAKE_LONGER = 'make-longer' as const; - -// Ask AI Assistant option -export const KEY_ASK_AI_ASSISTANT = 'ask-ai-assistant' as const; - -const quickActionsList = { - default: [ - { - name: __( 'Correct spelling and grammar', 'jetpack' ), - key: QUICK_EDIT_KEY_CORRECT_SPELLING, - aiSuggestion: PROMPT_TYPE_CORRECT_SPELLING, - icon: termDescription, - }, - ], - 'core/paragraph': [ - { - name: __( 'Simplify', 'jetpack' ), - key: QUICK_EDIT_KEY_SIMPLIFY, - aiSuggestion: PROMPT_TYPE_SIMPLIFY, - icon: post, - }, - { - name: __( 'Summarize', 'jetpack' ), - key: QUICK_EDIT_KEY_SUMMARIZE, - aiSuggestion: PROMPT_TYPE_SUMMARIZE, - icon: postExcerpt, - }, - { - name: __( 'Expand', 'jetpack' ), - key: QUICK_EDIT_KEY_MAKE_LONGER, - aiSuggestion: PROMPT_TYPE_MAKE_LONGER, - icon: postContent, - }, - ], - 'core/list': [ - { - name: __( 'Turn list into a table', 'jetpack' ), - key: 'turn-into-table', - aiSuggestion: PROMPT_TYPE_USER_PROMPT, - icon: blockTable, - options: { - userPrompt: 'make a table from this list, do not enclose the response in a code block', - }, - }, - { - name: __( 'Write a post from this list', 'jetpack' ), - key: 'write-post-from-list', - aiSuggestion: PROMPT_TYPE_USER_PROMPT, - icon: post, - options: { - userPrompt: - 'Write a post based on the list items. Include a title as first order heading and try to use secondary headings for each entry', - }, - }, - ], -}; - -export type AiAssistantDropdownOnChangeOptionsArgProps = { - tone?: ToneProp; - language?: string; - userPrompt?: string; -}; - -type AiAssistantControlComponentProps = { - /* - * The block type. Required. - */ - blockType: ExtendedBlockProp; -}; - -/** - * Given a list of blocks, it returns their content as a string. - * @param {Array} blocks - The list of blocks. - * @returns {string} The content of the blocks as a string. - */ -export function getBlocksContent( blocks ) { - return blocks - .filter( block => block != null ) // Safeguard against null or undefined blocks - .map( block => getBlockContent( block ) ) - .join( '\n\n' ); -} - -type AiAssistantDropdownContentProps = { +type AiAssistantBlockToolbarDropdownContentProps = { onClose: () => void; blockType: ExtendedBlockProp; }; /** - * The React content of the dropdown. - * @param {AiAssistantDropdownContentProps} props - The props. + * The dropdown component with logic for the AI Assistant block. + * @param {AiAssistantBlockToolbarDropdownContentProps} props - The props. * @returns {ReactElement} The React content of the dropdown. */ -function AiAssistantDropdownContent( { +function AiAssistantBlockToolbarDropdownContent( { onClose, blockType, -}: AiAssistantDropdownContentProps ): ReactElement { +}: AiAssistantBlockToolbarDropdownContentProps ) { // Set the state for the no content info. const [ noContent, setNoContent ] = useState( false ); @@ -247,62 +144,30 @@ function AiAssistantDropdownContent( { tracks.recordEvent( 'jetpack_ai_assistant_prompt_show', { block_type: blockType } ); }; - const blockQuickActions = quickActionsList[ blockType ] ?? []; - return ( - <> - { noContent && ( - - { __( 'Add content to activate the tools below', 'jetpack' ) } - - ) } - - - - - { __( 'Ask AI Assistant', 'jetpack' ) } - - - - { [ ...quickActionsList.default, ...blockQuickActions ].map( quickAction => ( - { - requestSuggestion( quickAction.aiSuggestion, { ...( quickAction.options ?? {} ) } ); - } } - disabled={ noContent } - > - { quickAction.name } - - ) ) } - - { - requestSuggestion( PROMPT_TYPE_CHANGE_TONE, { tone } ); - } } - disabled={ noContent } - /> - - { - requestSuggestion( PROMPT_TYPE_CHANGE_LANGUAGE, { language } ); - } } - disabled={ noContent } - /> - - > + ); } -export default function AiAssistantDropdown( { blockType }: AiAssistantControlComponentProps ) { +type AiAssistantBlockToolbarDropdownProps = { + blockType: ExtendedBlockProp; + label?: string; +}; + +/** + * The AI Assistant dropdown component. + * @param {AiAssistantBlockToolbarDropdownProps} props - The props. + * @returns {ReactElement} The AI Assistant dropdown component. + */ +export default function AiAssistantBlockToolbarDropdown( { + blockType, + label = __( 'AI Assistant', 'jetpack' ), +}: AiAssistantBlockToolbarDropdownProps ) { const { tracks } = useAnalytics(); const toggleHandler = isOpen => { @@ -312,6 +177,7 @@ export default function AiAssistantDropdown( { blockType }: AiAssistantControlCo } ); } }; + return ( ); } } onToggle={ toggleHandler } renderContent={ ( { onClose: onClose } ) => ( - + ) } /> ); diff --git a/projects/plugins/jetpack/extensions/blocks/ai-assistant/components/ai-assistant-controls/style.scss b/projects/plugins/jetpack/extensions/blocks/ai-assistant/components/ai-assistant-toolbar-dropdown/style.scss similarity index 100% rename from projects/plugins/jetpack/extensions/blocks/ai-assistant/components/ai-assistant-controls/style.scss rename to projects/plugins/jetpack/extensions/blocks/ai-assistant/components/ai-assistant-toolbar-dropdown/style.scss diff --git a/projects/plugins/jetpack/extensions/blocks/ai-assistant/extensions/ai-assistant/with-ai-assistant.tsx b/projects/plugins/jetpack/extensions/blocks/ai-assistant/extensions/ai-assistant/with-ai-assistant.tsx index d1fd6aa19f98d..8ae77eaa2db51 100644 --- a/projects/plugins/jetpack/extensions/blocks/ai-assistant/extensions/ai-assistant/with-ai-assistant.tsx +++ b/projects/plugins/jetpack/extensions/blocks/ai-assistant/extensions/ai-assistant/with-ai-assistant.tsx @@ -3,10 +3,11 @@ */ import { BlockControls } from '@wordpress/block-editor'; import { createHigherOrderComponent } from '@wordpress/compose'; +import React from 'react'; /** * Internal dependencies */ -import AiAssistantDropdown from '../../components/ai-assistant-controls'; +import AiAssistantBlockToolbarDropdown from '../../components/ai-assistant-toolbar-dropdown'; export function getStoreBlockId( clientId ) { return `ai-assistant-block-${ clientId }`; @@ -34,7 +35,7 @@ export const withAIAssistant = createHigherOrderComponent( - + > ); diff --git a/projects/plugins/jetpack/extensions/blocks/ai-assistant/lib/utils/block-content.ts b/projects/plugins/jetpack/extensions/blocks/ai-assistant/lib/utils/block-content.ts index a47b444124c31..7985c5adfb6bf 100644 --- a/projects/plugins/jetpack/extensions/blocks/ai-assistant/lib/utils/block-content.ts +++ b/projects/plugins/jetpack/extensions/blocks/ai-assistant/lib/utils/block-content.ts @@ -49,16 +49,31 @@ export function getContentFromBlocks(): string { return renderMarkdownFromHTML( { content: serialize( blocks ) } ); } +/** + * Given a list of blocks, it returns their content as a string. + * @param {Array} blocks - The list of blocks. + * @returns {string} The content of the blocks as a string. + */ +export function getBlocksContent( blocks ) { + return blocks + .filter( block => block != null ) // Safeguard against null or undefined blocks + .map( block => getBlockContent( block ) ) + .join( '\n\n' ); +} + +/** + * Returns the text content of the inner blocks of a block. + * + * @param {string} clientId - The block clientId. + * @returns {string} The text content. + */ export function getTextContentFromInnerBlocks( clientId: string ) { const block = select( 'core/block-editor' ).getBlock( clientId ); if ( ! block?.innerBlocks?.length ) { return ''; } - return block.innerBlocks - .filter( blq => blq != null ) // Safeguard against null or undefined blocks - .map( blq => getBlockContent( blq.clientId ) ) - .join( '\n\n' ); + return getBlocksContent( block.innerBlocks ); } /**