diff --git a/includes/Classifai/Admin/BulkActions.php b/includes/Classifai/Admin/BulkActions.php index c6bc730b3..e01e75f9d 100644 --- a/includes/Classifai/Admin/BulkActions.php +++ b/includes/Classifai/Admin/BulkActions.php @@ -9,6 +9,7 @@ use Classifai\Providers\OpenAI\Whisper\Transcribe; use function Classifai\get_post_types_for_language_settings; use function Classifai\get_supported_post_types; +use function Classifai\get_tts_supported_post_types; /** * Handle bulk actions. @@ -68,15 +69,14 @@ public function register() { * Register bulk actions for language processing. */ public function register_language_processing_hooks() { - $this->chat_gpt = new ChatGPT( false ); - $this->embeddings = new Embeddings( false ); - $this->text_to_speech = new TextToSpeech( false ); + $this->chat_gpt = new ChatGPT( false ); + $this->embeddings = new Embeddings( false ); $user_roles = wp_get_current_user()->roles ?? []; $embedding_settings = $this->embeddings->get_settings(); $embeddings_post_types = []; $nlu_post_types = get_supported_post_types(); - $text_to_speech_post_types = $this->text_to_speech->get_supported_post_types(); + $text_to_speech_post_types = get_tts_supported_post_types(); $chat_gpt_post_types = []; $chat_gpt_settings = $this->chat_gpt->get_settings(); @@ -104,11 +104,6 @@ public function register_language_processing_hooks() { $this->embeddings = null; } - // Clear our TextToSpeech handler if no post types are set up. - if ( empty( $text_to_speech_post_types ) ) { - $this->text_to_speech = null; - } - // Merge our post types together and make them unique. $post_types = array_unique( array_merge( $chat_gpt_post_types, $embeddings_post_types, $nlu_post_types, $text_to_speech_post_types ) ); diff --git a/includes/Classifai/Helpers.php b/includes/Classifai/Helpers.php index 734bf1db6..47f47e3f1 100644 --- a/includes/Classifai/Helpers.php +++ b/includes/Classifai/Helpers.php @@ -3,6 +3,7 @@ namespace Classifai; use Classifai\Providers\Provider; +use Classifai\Providers\Azure; use Classifai\Services\Service; use Classifai\Services\ServicesManager; use WP_Error; @@ -275,6 +276,28 @@ function get_supported_post_types() { return $post_types; } +/** + * The list of post types that TTS supports. + * + * @return array Supported Post Types. + */ +function get_tts_supported_post_types() { + $classifai_settings = get_plugin_settings( 'language_processing', Azure\TextToSpeech::FEATURE_NAME ); + + if ( empty( $classifai_settings ) ) { + $post_types = []; + } else { + $post_types = []; + foreach ( $classifai_settings['post_types'] as $post_type => $enabled ) { + if ( ! empty( $enabled ) ) { + $post_types[] = $post_type; + } + } + } + + return $post_types; +} + /** * The list of post statuses that get the ClassifAI taxonomies. Defaults * to 'publish'. diff --git a/includes/Classifai/Providers/Azure/ComputerVision.php b/includes/Classifai/Providers/Azure/ComputerVision.php index 00ee86901..2a9555610 100644 --- a/includes/Classifai/Providers/Azure/ComputerVision.php +++ b/includes/Classifai/Providers/Azure/ComputerVision.php @@ -62,7 +62,7 @@ public function reset_settings() { * * @return array */ - private function get_default_settings() { + public function get_default_settings() { return [ 'valid' => false, 'url' => '', diff --git a/includes/Classifai/Providers/Azure/Personalizer.php b/includes/Classifai/Providers/Azure/Personalizer.php index e9600cef6..c094e1306 100644 --- a/includes/Classifai/Providers/Azure/Personalizer.php +++ b/includes/Classifai/Providers/Azure/Personalizer.php @@ -62,7 +62,7 @@ public function reset_settings() { * * @return array */ - private function get_default_settings() { + public function get_default_settings() { return [ 'authenticated' => false, 'url' => '', diff --git a/includes/Classifai/Providers/Azure/TextToSpeech.php b/includes/Classifai/Providers/Azure/TextToSpeech.php index 8ceeffba1..e7b156895 100644 --- a/includes/Classifai/Providers/Azure/TextToSpeech.php +++ b/includes/Classifai/Providers/Azure/TextToSpeech.php @@ -11,6 +11,7 @@ use WP_Http; use function Classifai\get_post_types_for_language_settings; +use function Classifai\get_tts_supported_post_types; use function Classifai\get_asset_info; class TextToSpeech extends Provider { @@ -30,12 +31,11 @@ class TextToSpeech extends Provider { const API_PATH = 'cognitiveservices/v1'; /** - * Meta key to store data indicating whether Text to Speech is enabled for - * the current post. + * Meta key to hide/unhide already generated audio file. * * @var string */ - const SYNTHESIZE_SPEECH_KEY = '_classifai_synthesize_speech'; + const DISPLAY_GENERATED_AUDIO = '_classifai_display_generated_audio'; /** * Meta key to get/set the ID of the speech audio file. @@ -87,21 +87,13 @@ public function __construct( $service ) { * Enqueue the editor scripts. */ public function enqueue_editor_assets() { - global $post; - - wp_enqueue_script( - 'classifai-editor', // Handle. - CLASSIFAI_PLUGIN_URL . 'dist/editor.js', - array( 'wp-blocks', 'wp-i18n', 'wp-element', 'wp-editor', 'wp-edit-post' ), - CLASSIFAI_PLUGIN_VERSION, - true - ); + $post = get_post(); if ( empty( $post ) ) { return; } - $supported_post_types = self::get_supported_post_types(); + $supported_post_types = get_tts_supported_post_types(); if ( ! in_array( $post->post_type, $supported_post_types, true ) ) { return; @@ -115,13 +107,13 @@ public function enqueue_editor_assets() { true ); - wp_localize_script( + wp_add_inline_script( 'classifai-gutenberg-plugin', - 'classifaiTextToSpeechData', - [ - 'supportedPostTypes' => self::get_supported_post_types(), - 'noPermissions' => ! is_user_logged_in() || ! current_user_can( 'edit_post', $post->ID ), - ] + sprintf( + 'var classifaiTTSEnabled = %d;', + true + ), + 'before' ); } @@ -133,6 +125,12 @@ public function register() { add_action( 'rest_api_init', [ $this, 'add_synthesize_speech_meta_to_rest_api' ] ); add_action( 'add_meta_boxes', [ $this, 'add_meta_box' ] ); add_action( 'save_post', [ $this, 'save_post_metadata' ], 5 ); + + $supported_post_type = get_tts_supported_post_types(); + foreach ( get_tts_supported_post_types() as $post_type ) { + add_action( 'rest_insert_' . $post_type, [ $this, 'rest_handle_audio' ], 10, 2 ); + } + add_filter( 'the_content', [ $this, 'render_post_audio_controls' ] ); } @@ -190,7 +188,7 @@ public function setup_fields_sections() { 'label_for' => 'post_types', 'option_index' => 'post_types', 'options' => $this->get_post_types_select_options(), - 'default_values' => $default_settings['post-types'], + 'default_values' => $default_settings['post_types'], ] ); @@ -240,6 +238,9 @@ public function sanitize_settings( $settings ) { if ( ! empty( $current_settings['voices'] ) ) { $current_settings['authenticated'] = true; + } else { + $current_settings['voices'] = []; + $current_settings['authenticated'] = false; } } } else { @@ -267,8 +268,6 @@ public function sanitize_settings( $settings ) { if ( isset( $settings['voice'] ) && ! empty( $settings['voice'] ) ) { $current_settings['voice'] = sanitize_text_field( $settings['voice'] ); - } else { - $current_settings['voice'] = ''; } return $current_settings; @@ -427,7 +426,7 @@ public function get_provider_debug_information( $settings = null, $configured = /** * Returns the default settings. */ - private function get_default_settings() { + public function get_default_settings() { return [ 'credentials' => array( 'url' => '', @@ -436,44 +435,108 @@ private function get_default_settings() { 'voices' => array(), 'voice' => '', 'authenticated' => false, - 'post-types' => array(), + 'post_types' => array(), ]; } /** - * Add `classifai_synthesize_speech` to rest API for view/edit. + * Initial audio generation state. + * + * Fetch the initial state of audio generation prior to the audio existing for the post. + * + * @param int|WP_Post|null $post Optional. Post ID or post object. `null`, `false`, `0` and other PHP falsey values + * return the current global post inside the loop. A numerically valid post ID that + * points to a non-existent post returns `null`. Defaults to global $post. + * @return bool The initial state of audio generation. Default true. + */ + public function get_audio_generation_initial_state( $post = null ) { + /** + * Initial state of the audio generation toggle when no audio already exists for the post. + * + * @since 2.3.0 + * @hook classifai_audio_generation_initial_state + * + * @param {bool} $state Initial state of audio generation toggle on a post. Default true. + * @param {WP_Post} $post The current Post object. + * + * @return {bool} Initial state the audio generation toggle should be set to when no audio exists. + */ + return apply_filters( 'classifai_audio_generation_initial_state', true, get_post( $post ) ); + } + + /** + * Subsequent audio generation state. + * + * Fetch the subsequent state of audio generation once audio is generated for the post. + * + * @param int|WP_Post|null $post Optional. Post ID or post object. `null`, `false`, `0` and other PHP falsey values + * return the current global post inside the loop. A numerically valid post ID that + * points to a non-existent post returns `null`. Defaults to global $post. + * @return bool The subsequent state of audio generation. Default false. + */ + public function get_audio_generation_subsequent_state( $post = null ) { + /** + * Subsequent state of the audio generation toggle when audio exists for the post. + * + * @since 2.3.0 + * @hook classifai_audio_generation_subsequent_state + * + * @param {bool} $state Subsequent state of audio generation toggle on a post. Default false. + * @param {WP_Post} $post The current Post object. + * + * @return {bool} Subsequent state the audio generation toggle should be set to when audio exists. + */ + return apply_filters( 'classifai_audio_generation_subsequent_state', false, get_post( $post ) ); + } + + /** + * Add audio related fields to rest API for view/edit. */ public function add_synthesize_speech_meta_to_rest_api() { - $settings = $this->get_settings(); - $supported_post_types = $settings['post_types'] ?? array(); - - $supported_post_types = array_keys( - array_filter( - $supported_post_types, - function( $post_type ) { - return ! is_null( $post_type ); - } + $supported_post_types = get_tts_supported_post_types(); + + register_rest_field( + $supported_post_types, + 'classifai_synthesize_speech', + array( + 'get_callback' => function( $object ) { + $audio_id = get_post_meta( $object['id'], self::AUDIO_ID_KEY, true ); + if ( + ( $this->get_audio_generation_initial_state( $object['id'] ) && ! $audio_id ) || + ( $this->get_audio_generation_subsequent_state( $object['id'] ) && $audio_id ) + ) { + return true; + } else { + return false; + } + }, + 'schema' => [ + 'type' => 'boolean', + 'context' => [ 'view', 'edit' ], + ], ) ); - if ( empty( $supported_post_types ) ) { - return; - } - register_rest_field( $supported_post_types, - 'classifai_synthesize_speech', + 'classifai_display_generated_audio', array( 'get_callback' => function( $object ) { - $process_content = get_post_meta( $object['id'], self::SYNTHESIZE_SPEECH_KEY, true ); - return ( 'no' === $process_content ) ? 'no' : 'yes'; + // Default to display the audio if available. + if ( metadata_exists( 'post', $object['id'], self::DISPLAY_GENERATED_AUDIO ) ) { + return (bool) get_post_meta( $object['id'], self::DISPLAY_GENERATED_AUDIO, true ); + } + return true; }, - 'update_callback' => function ( $value, $object ) { - $value = ( 'no' === $value ) ? 'no' : 'yes'; - return update_post_meta( $object->ID, self::SYNTHESIZE_SPEECH_KEY, $value ); + 'update_callback' => function( $value, $object ) { + if ( $value ) { + delete_post_meta( $object->ID, self::DISPLAY_GENERATED_AUDIO ); + } else { + update_post_meta( $object->ID, self::DISPLAY_GENERATED_AUDIO, false ); + } }, 'schema' => [ - 'type' => 'string', + 'type' => 'boolean', 'context' => [ 'view', 'edit' ], ], ) @@ -495,13 +558,42 @@ function( $post_type ) { ); } + /** + * Handles audio generation on rest updates / inserts. + * + * @param WP_Post $post Inserted or updated post object. + * @param WP_REST_Request $request Request object. + */ + public function rest_handle_audio( $post, $request ) { + + $audio_id = get_post_meta( $request->get_param( 'id' ), self::AUDIO_ID_KEY, true ); + + // Since we have dynamic generation option agnostic to meta saves we need a flag to differentiate audio generation accurately + $process_content = false; + if ( + ( $this->get_audio_generation_initial_state( $post ) && ! $audio_id ) || + ( $this->get_audio_generation_subsequent_state( $post ) && $audio_id ) + ) { + $process_content = true; + } + + // Add/Update audio if it was requested. + if ( + ( $process_content && null === $request->get_param( 'classifai_synthesize_speech' ) ) || + true === $request->get_param( 'classifai_synthesize_speech' ) + ) { + $save_post_handler = new SavePostHandler(); + $save_post_handler->synthesize_speech( $request->get_param( 'id' ) ); + } + } + /** * Add meta box to post types that support speech synthesis. * * @param string $post_type Post type. */ public function add_meta_box( $post_type ) { - if ( ! in_array( $post_type, $this->get_supported_post_types(), true ) ) { + if ( ! in_array( $post_type, get_tts_supported_post_types(), true ) ) { return; } @@ -511,7 +603,7 @@ public function add_meta_box( $post_type ) { [ $this, 'render_meta_box' ], null, 'side', - 'low', + 'high', array( '__back_compat_meta_box' => true ) ); } @@ -524,38 +616,65 @@ public function add_meta_box( $post_type ) { public function render_meta_box( $post ) { wp_nonce_field( 'classifai_text_to_speech_meta_action', 'classifai_text_to_speech_meta' ); - $process_content = get_post_meta( $post->ID, self::SYNTHESIZE_SPEECH_KEY, true ); - $process_content = ( 'no' === $process_content ) ? 'no' : 'yes'; + $source_url = false; + $audio_id = get_post_meta( $post->ID, self::AUDIO_ID_KEY, true ); + if ( $audio_id ) { + $source_url = wp_get_attachment_url( $audio_id ); + } + + $process_content = false; + if ( + ( $this->get_audio_generation_initial_state( $post ) && ! $audio_id ) || + ( $this->get_audio_generation_subsequent_state( $post ) && $audio_id ) + ) { + $process_content = true; + } + + $display_audio = true; + if ( metadata_exists( 'post', $post->ID, self::DISPLAY_GENERATED_AUDIO ) && + ! (bool) get_post_meta( $post->ID, self::DISPLAY_GENERATED_AUDIO, true ) ) { + $display_audio = false; + } - $post_type = get_post_type_object( get_post_type( $post ) ); $post_type_label = esc_html__( 'Post', 'classifai' ); + $post_type = get_post_type_object( get_post_type( $post ) ); if ( $post_type ) { $post_type_label = $post_type->labels->singular_name; } - $audio_id = get_post_meta( $post->ID, self::AUDIO_ID_KEY, true ); ?> -
+ +
+ +> + + +
time(), ], - wp_get_attachment_url( $audio_id ) + $source_url ); ?> @@ -574,26 +693,28 @@ public function render_meta_box( $post ) { * @param int $post_id Post ID. */ public function save_post_metadata( $post_id ) { - if ( ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) || ! current_user_can( 'edit_post', $post_id ) || 'revision' === get_post_type( $post_id ) ) { + + if ( ! in_array( get_post_type( $post_id ), get_tts_supported_post_types(), true ) ) { return; } - if ( empty( $_POST['classifai_text_to_speech_meta'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['classifai_text_to_speech_meta'] ) ), 'classifai_text_to_speech_meta_action' ) ) { + if ( ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) || ! current_user_can( 'edit_post', $post_id ) || 'revision' === get_post_type( $post_id ) ) { return; } - $supported_post_types = $this->get_supported_post_types(); - if ( ! in_array( get_post_type( $post_id ), $supported_post_types, true ) ) { + if ( empty( $_POST['classifai_text_to_speech_meta'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['classifai_text_to_speech_meta'] ) ), 'classifai_text_to_speech_meta_action' ) ) { return; } - if ( isset( $_POST['classifai_synthesize_speech'] ) && 'yes' === sanitize_text_field( wp_unslash( $_POST['classifai_synthesize_speech'] ) ) ) { - update_post_meta( $post_id, self::SYNTHESIZE_SPEECH_KEY, 'yes' ); + if ( ! isset( $_POST['classifai_display_generated_audio'] ) ) { + update_post_meta( $post_id, self::DISPLAY_GENERATED_AUDIO, false ); + } else { + delete_post_meta( $post_id, self::DISPLAY_GENERATED_AUDIO ); + } + if ( isset( $_POST['classifai_synthesize_speech'] ) ) { $save_post_handler = new SavePostHandler(); $save_post_handler->synthesize_speech( $post_id ); - } else { - update_post_meta( $post_id, self::SYNTHESIZE_SPEECH_KEY, 'no' ); } } @@ -611,7 +732,7 @@ public function render_post_audio_controls( $content ) { return $content; } - if ( ! in_array( $_post->post_type, self::get_supported_post_types(), true ) ) { + if ( ! in_array( $_post->post_type, get_tts_supported_post_types(), true ) ) { return $content; } @@ -630,8 +751,9 @@ public function render_post_audio_controls( $content ) { return $content; } - $is_audio_enabled = get_post_meta( $_post->ID, self::SYNTHESIZE_SPEECH_KEY, true ); - if ( 'no' === $is_audio_enabled ) { + // Respect the audio display settings of the post. + if ( metadata_exists( 'post', $_post->ID, self::DISPLAY_GENERATED_AUDIO ) && + ! (bool) get_post_meta( $_post->ID, self::DISPLAY_GENERATED_AUDIO, true ) ) { return $content; } @@ -752,24 +874,4 @@ protected function get_post_types_select_options() { return $options; } - /** - * Returns supported post types for Azure Text to Speech. - * - * @todo Move this to a more generic method during refactoring of the plugin. - * @return array - */ - public static function get_supported_post_types() { - $settings = \Classifai\get_plugin_settings( 'language_processing', self::FEATURE_NAME ); - $supported_post_types = isset( $settings['post_types'] ) ? $settings['post_types'] : array(); - - return array_keys( - array_filter( - $supported_post_types, - function( $post_type ) { - return '0' !== $post_type; - } - ) - ); - } - } diff --git a/includes/Classifai/Providers/OpenAI/ChatGPT.php b/includes/Classifai/Providers/OpenAI/ChatGPT.php index 3c92048c6..76707db4d 100644 --- a/includes/Classifai/Providers/OpenAI/ChatGPT.php +++ b/includes/Classifai/Providers/OpenAI/ChatGPT.php @@ -442,7 +442,10 @@ public function reset_settings() { * * @return array */ - private function get_default_settings() { + public function get_default_settings() { + if ( ! function_exists( 'get_editable_roles' ) ) { + require_once ABSPATH . 'wp-admin/includes/user.php'; + } $editable_roles = get_editable_roles() ?? []; return [ diff --git a/includes/Classifai/Providers/OpenAI/DallE.php b/includes/Classifai/Providers/OpenAI/DallE.php index 953b25b4c..0168df2fb 100644 --- a/includes/Classifai/Providers/OpenAI/DallE.php +++ b/includes/Classifai/Providers/OpenAI/DallE.php @@ -387,7 +387,7 @@ public function reset_settings() { * * @return array */ - private function get_default_settings() { + public function get_default_settings() { return [ 'authenticated' => false, 'api_key' => '', diff --git a/includes/Classifai/Providers/OpenAI/Embeddings.php b/includes/Classifai/Providers/OpenAI/Embeddings.php index cefe95b69..158bd2464 100644 --- a/includes/Classifai/Providers/OpenAI/Embeddings.php +++ b/includes/Classifai/Providers/OpenAI/Embeddings.php @@ -282,7 +282,7 @@ public function reset_settings() { * * @return array */ - private function get_default_settings() { + public function get_default_settings() { return [ 'authenticated' => false, 'api_key' => '', diff --git a/includes/Classifai/Providers/OpenAI/Whisper.php b/includes/Classifai/Providers/OpenAI/Whisper.php index 2beb0640f..6ffce1b91 100644 --- a/includes/Classifai/Providers/OpenAI/Whisper.php +++ b/includes/Classifai/Providers/OpenAI/Whisper.php @@ -300,7 +300,7 @@ public function reset_settings() { * * @return array */ - private function get_default_settings() { + public function get_default_settings() { return [ 'authenticated' => false, 'api_key' => '', diff --git a/includes/Classifai/Providers/Provider.php b/includes/Classifai/Providers/Provider.php index 6c2230883..35cfe03cc 100644 --- a/includes/Classifai/Providers/Provider.php +++ b/includes/Classifai/Providers/Provider.php @@ -153,7 +153,7 @@ public function register_settings() { * @return string|array|mixed */ public function get_settings( $index = false ) { - $defaults = []; + $defaults = $this->get_default_settings(); $settings = get_option( $this->get_option_name(), [] ); $settings = wp_parse_args( $settings, $defaults ); @@ -164,6 +164,15 @@ public function get_settings( $index = false ) { return $settings; } + /** + * Returns the default settings. + * + * @return array + */ + public function get_default_settings() { + return []; + } + /** * Generic text input field callback * diff --git a/includes/Classifai/Services/LanguageProcessing.php b/includes/Classifai/Services/LanguageProcessing.php index 034bde2be..d514dd805 100644 --- a/includes/Classifai/Services/LanguageProcessing.php +++ b/includes/Classifai/Services/LanguageProcessing.php @@ -6,7 +6,6 @@ namespace Classifai\Services; use Classifai\Admin\SavePostHandler; -use Classifai\Providers\Azure\TextToSpeech; use function Classifai\find_provider_class; use WP_REST_Server; use WP_REST_Request; @@ -390,7 +389,7 @@ public function speech_synthesis_permissions_check( WP_REST_Request $request ) { if ( ! empty( $post_id ) && current_user_can( 'edit_post', $post_id ) ) { $post_type = get_post_type( $post_id ); - $supported = TextToSpeech::get_supported_post_types(); + $supported = \Classifai\get_tts_supported_post_types(); // Check if processing allowed. if ( ! in_array( $post_type, $supported, true ) ) { diff --git a/src/js/gutenberg-plugin.js b/src/js/gutenberg-plugin.js index b0c614add..698457181 100644 --- a/src/js/gutenberg-plugin.js +++ b/src/js/gutenberg-plugin.js @@ -12,10 +12,10 @@ import { import { __, sprintf } from '@wordpress/i18n'; import { registerPlugin } from '@wordpress/plugins'; import { useState, useEffect, useRef } from '@wordpress/element'; -import { store as noticesStore } from '@wordpress/notices'; import { store as postAudioStore } from './store/register'; -const { classifaiEmbeddingData, classifaiPostData } = window; +const { classifaiEmbeddingData, classifaiPostData, classifaiTTSEnabled } = + window; /** * Create the ClassifAI icon @@ -191,93 +191,35 @@ const ClassifAIGenerateTagsButton = () => { ); }; -let errorCode = ''; - -/** - * Calls the server-side method that synthesizes speech for a post. - * - * @param {number} postId The Post ID - * @return {boolean} Returns true on success. - */ -const synthesizeSpeech = async ( postId ) => { - const { select, dispatch } = wp.data; - - // Endpoint URL. - const synthesizeSpeechUrl = `${ wpApiSettings.root }classifai/v1/synthesize-speech/${ postId }`; - - // Stores status of the synthensis process. - const isProcessing = select( postAudioStore ).getIsProcessing(); - - // Return early if already processing. - if ( isProcessing ) { - return; - } - - // Set state indicating the synthesis process has begun. - dispatch( postAudioStore ).setIsProcessing( true ); - - const response = await fetch( synthesizeSpeechUrl, { - headers: new Headers( { - 'X-WP-Nonce': wpApiSettings.nonce, - } ), - } ); - - // Return false if error. - if ( 200 !== response.status ) { - // Set state indicating the synthesis process has ended. - dispatch( postAudioStore ).setIsProcessing( false ); - return false; - } - - const result = await response.json(); - - if ( result.success ) { - // Set audio ID state after successful synthesis. - dispatch( postAudioStore ).setAudioId( result.audio_id ); - - // Set state indicating the synthesis process has ended. - dispatch( postAudioStore ).setIsProcessing( false ); - - if ( errorCode ) { - dispatch( noticesStore ).removeNotice( errorCode ); - errorCode = ''; - } - - return true; - } - errorCode = result.code; - dispatch( 'core/notices' ).createErrorNotice( result.message, { - id: errorCode, - } ); - dispatch( postAudioStore ).setIsProcessing( false ); -}; - /** * ClassifAI Text to Audio component. - * - * @param {Object} props Props object. */ -const ClassifAITSpeechSynthesisToggle = ( props ) => { +const ClassifAITTS = () => { // State of the audio being previewed in PluginDocumentSettingPanel. const [ isPreviewing, setIsPreviewing ] = useState( false ); const [ timestamp, setTimestamp ] = useState( new Date().getTime() ); - // Indicates whether speech synthesis is supported for the current post. - let isFeatureSupported = false; - // Indicates whether speech synthesis is enabled for the current post. - const isSynthesizeSpeech = - 'yes' === - useSelect( ( select ) => - select( 'core/editor' ).getEditedPostAttribute( - 'classifai_synthesize_speech' - ) - ); + const isSynthesizeSpeech = useSelect( ( select ) => + select( 'core/editor' ).getEditedPostAttribute( + 'classifai_synthesize_speech' + ) + ); - // Post type of the current post. - const postType = useSelect( ( select ) => - select( 'core/editor' ).getCurrentPostType() + // Indicates whether generated audio should be displayed on the frontend. + const displayGeneratedAudio = useSelect( ( select ) => + select( 'core/editor' ).getEditedPostAttribute( + 'classifai_display_generated_audio' + ) + ); + + // Post type label. + const postTypeLabel = useSelect( + ( select ) => + ( typeof select( 'core/editor' ).getPostTypeLabel !== 'undefined' && + select( 'core/editor' ).getPostTypeLabel() ) || + __( 'Post', 'classifai' ) ); // Says whether speech synthesis is in progress. @@ -285,14 +227,6 @@ const ClassifAITSpeechSynthesisToggle = ( props ) => { select( postAudioStore ).getIsProcessing() ); - // Figure out if speech synthesis is supported by the current post. - if ( - 'undefined' !== typeof classifaiTextToSpeechData && - classifaiTextToSpeechData.supportedPostTypes.includes( postType ) - ) { - isFeatureSupported = true; - } - // The audio ID saved in the DB for the current post. const defaultAudioId = useSelect( ( select ) => select( 'core/editor' ).getEditedPostAttribute( @@ -301,7 +235,9 @@ const ClassifAITSpeechSynthesisToggle = ( props ) => { ); // New audio ID in case it is regenerated manually or through publishing/updating the current post. - const audioId = props.audioId || defaultAudioId; + const audioId = + useSelect( ( select ) => select( postAudioStore ).getAudioId() ) || + defaultAudioId; // Get the attachment data by audio ID. const attachments = useSelect( ( select ) => @@ -315,6 +251,17 @@ const ClassifAITSpeechSynthesisToggle = ( props ) => { attachments && attachments.length > 0 && attachments[ 0 ].source_url; const isProcessingAudioProgress = useRef( false ); + const isPostSavingInProgress = useRef( false ); + const { isSavingPost } = useSelect( ( select ) => { + return { + isSavingPost: select( 'core/editor' ).isSavingPost(), + }; + } ); + const { isAutosavingPost } = useSelect( ( select ) => { + return { + isSavingPost: select( 'core/editor' ).isAutosavingPost(), + }; + } ); // Handles playing/pausing post audio during preview. useEffect( () => { @@ -342,6 +289,30 @@ const ClassifAITSpeechSynthesisToggle = ( props ) => { } }, [ isProcessingAudio ] ); + useEffect( () => { + // Code to run during post saving is in process. + if ( + isSavingPost && + ! isAutosavingPost && + ! isPostSavingInProgress.current + ) { + isPostSavingInProgress.current = true; + if ( isSynthesizeSpeech ) { + wp.data.dispatch( postAudioStore ).setIsProcessing( true ); + } + } + + if ( + ! isSavingPost && + ! isAutosavingPost && + isPostSavingInProgress.current + ) { + // Code to run after post is done saving. + isPostSavingInProgress.current = false; + wp.data.dispatch( postAudioStore ).setIsProcessing( false ); + } + }, [ isSavingPost, isAutosavingPost, isSynthesizeSpeech ] ); + // Fetches the latest audio file to avoid disk cache. const cacheBustingUrl = `${ sourceUrl }?ver=${ timestamp }`; @@ -357,25 +328,66 @@ const ClassifAITSpeechSynthesisToggle = ( props ) => { <>