/*
* External dependencies
*/
import { aiAssistantIcon } from '@automattic/jetpack-ai-client';
import { useAnalytics } from '@automattic/jetpack-shared-extension-utils';
import { ToolbarButton, Dropdown } from '@wordpress/components';
import { useSelect, useDispatch } from '@wordpress/data';
import { useState, useEffect } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import debugFactory from 'debug';
import React from 'react';
/**
* Internal dependencies
*/
import { getStoreBlockId } from '../../extensions/ai-assistant/with-ai-assistant';
import { getBlocksContent, getRawTextFromHTML } from '../../lib/utils/block-content';
import { transformToAIAssistantBlock } from '../../transforms';
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 { ReactElement } from 'react';
const debug = debugFactory( 'jetpack-ai-assistant:dropdown' );
type AiAssistantBlockToolbarDropdownContentProps = {
onClose: () => void;
blockType: ExtendedBlockProp;
};
/**
* The dropdown component with logic for the AI Assistant block.
* @param {AiAssistantBlockToolbarDropdownContentProps} props - The props.
* @returns {ReactElement} The React content of the dropdown.
*/
function AiAssistantBlockToolbarDropdownContent( {
onClose,
blockType,
}: AiAssistantBlockToolbarDropdownContentProps ) {
// Set the state for the no content info.
const [ noContent, setNoContent ] = useState( false );
/*
* Let's disable the eslint rule for this line.
* @todo: fix by using StoreDescriptor, or something similar
*/
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const { getSelectedBlockClientIds, getBlocksByClientId } = useSelect( 'core/block-editor' );
const { removeBlocks, replaceBlock } = useDispatch( 'core/block-editor' );
// Store the current content in a local state
useEffect( () => {
const clientIds = getSelectedBlockClientIds();
const blocks = getBlocksByClientId( clientIds );
const content = getBlocksContent( blocks );
const rawContent = getRawTextFromHTML( content );
// Set no content condition to show the Notice info message.
return setNoContent( ! rawContent.length );
}, [ getBlocksByClientId, getSelectedBlockClientIds ] );
const { tracks } = useAnalytics();
const requestSuggestion = (
promptType: PromptTypeProp,
options: AiAssistantDropdownOnChangeOptionsArgProps = {}
) => {
const clientIds = getSelectedBlockClientIds();
const blocks = getBlocksByClientId( clientIds );
const content = getBlocksContent( blocks );
onClose();
debug( 'requestSuggestion', promptType, options );
tracks.recordEvent( 'jetpack_editor_ai_assistant_extension_toolbar_button_click', {
suggestion: promptType,
block_type: blockType,
} );
const [ firstBlock ] = blocks;
const [ firstClientId, ...otherBlocksIds ] = clientIds;
const extendedBlockAttributes = {
...( firstBlock?.attributes || {} ), // firstBlock.attributes should never be undefined, but still add a fallback
content,
};
const newAIAssistantBlock = transformToAIAssistantBlock( blockType, extendedBlockAttributes );
/*
* Store in the local storage the client id
* of the block that need to auto-trigger the AI Assistant request.
* @todo: find a better way to update the content,
* probably using a new store triggering an action.
*/
// Storage client Id, prompt type, and options.
const storeObject = {
clientId: firstClientId,
type: promptType,
options: { ...options, contentType: 'generated', fromExtension: true }, // When converted, the original content must be treated as generated
};
localStorage.setItem(
getStoreBlockId( newAIAssistantBlock.clientId ),
JSON.stringify( storeObject )
);
/*
* Replace the first block with the new AI Assistant block instance.
* This block contains the original content,
* even for multiple blocks selection.
*/
replaceBlock( firstClientId, newAIAssistantBlock );
// It removes the rest of the blocks in case there are more than one.
removeBlocks( otherBlocksIds );
};
const replaceWithAiAssistantBlock = () => {
const clientIds = getSelectedBlockClientIds();
const blocks = getBlocksByClientId( clientIds );
const content = getBlocksContent( blocks );
const [ firstClientId, ...otherBlocksIds ] = clientIds;
const [ firstBlock ] = blocks;
const extendedBlockAttributes = {
...( firstBlock?.attributes || {} ), // firstBlock.attributes should never be undefined, but still add a fallback
content,
};
replaceBlock(
firstClientId,
transformToAIAssistantBlock( blockType, extendedBlockAttributes )
);
removeBlocks( otherBlocksIds );
tracks.recordEvent( 'jetpack_ai_assistant_prompt_show', { block_type: blockType } );
};
return (
);
}
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 => {
if ( isOpen ) {
tracks.recordEvent( 'jetpack_ai_assistant_extension_toolbar_menu_show', {
block_type: blockType,
} );
}
};
return (
{
return (
);
} }
onToggle={ toggleHandler }
renderContent={ ( { onClose: onClose } ) => (
) }
/>
);
}