true, ); } /** * {@inheritDoc} * * @since 1.5.0 */ protected function input_schema(): array { return array( 'type' => 'object', 'required' => array( 'post_id', 'content' ), 'properties' => array( 'post_id' => array( 'type' => 'integer', 'sanitize_callback' => 'absint', 'description' => esc_html__( 'The ID of the post that contains the block to generate a label for.', 'ai' ), ), 'content' => array( 'type' => 'string', 'description' => esc_html__( 'The content of the block to generate a label for.', 'ai' ), ), 'document_context' => array( 'type' => 'string', 'description' => esc_html__( 'The outline or contextual overview of the entire document to provide structural context.', 'ai' ), ), ), ); } /** * {@inheritDoc} * * @since 1.5.0 */ protected function output_schema(): array { return array( 'type' => 'string', 'description' => esc_html__( 'The generated label for the block.', 'ai' ), ); } /** * {@inheritDoc} * * @since 1.5.0 */ protected function permission_callback( $args ) { if ( empty( $args['post_id'] ) ) { return new WP_Error( 'missing_post_id', esc_html__( 'A post ID is required to generate a block container label.', 'ai' ) ); } $post_id = absint( $args['post_id'] ); $post = get_post( $post_id ); if ( ! $post ) { return new WP_Error( 'post_not_found', sprintf( /* translators: %d: post ID */ esc_html__( 'Post with ID %d not found.', 'ai' ), $post_id ) ); } if ( ! current_user_can( 'edit_post', $post_id ) ) { return new WP_Error( 'insufficient_permissions', esc_html__( 'You do not have permission to edit this post.', 'ai' ) ); } // Ensure the post type is allowed in REST endpoints. $post_type = get_post_type_object( $post->post_type ); return $post_type && ! empty( $post_type->show_in_rest ); } /** * Returns a prompt builder for Block Container Label Generation. * * @since 1.5.0 * * @param string $prompt The prompt to build. * @return \WP_AI_Client_Prompt_Builder|\WP_Error The prompt builder, or a WP_Error if there isn't a model that supports text generation. */ private function get_prompt_builder( string $prompt ) { $prompt_builder = wp_ai_client_prompt( $prompt ) ->using_system_instruction( $this->get_system_instruction( 'system-instruction.php' ) ); $prompt_builder = $this->filter_prompt_builder( $prompt_builder, Block_Container_Label_Generation_Experiment::class, array(), $prompt ); return $this->ensure_text_generation_supported( $prompt_builder, esc_html__( 'Label generation failed. Please ensure you have a connected provider that supports text generation.', 'ai' ) ); } /** * {@inheritDoc} * * @since 1.5.0 */ protected function execute_callback( $input ) { $args = wp_parse_args( $input, array( 'post_id' => null, 'content' => null, 'document_context' => null, ) ); $content = normalize_content( $args['content'] ?? '' ); if ( empty( $content ) ) { return new WP_Error( 'content_not_provided', esc_html__( 'No content provided to generate a label.', 'ai' ) ); } $min_content_length = get_min_content_length( 'block-container-label-generation', self::DEFAULT_MIN_CONTENT_LENGTH ); if ( count_characters_excluding_spaces( $content ) < $min_content_length ) { return new WP_Error( 'content_too_short', sprintf( /* translators: %d: Minimum content length in characters. */ esc_html__( 'A minimum of %d characters is required to generate a label.', 'ai' ), $min_content_length ) ); } $document_context = ! empty( $args['document_context'] ) ? trim( (string) $args['document_context'] ) : ''; // Escape angle brackets so content cannot break out of the tags. $escaped_content = str_replace( array( '<', '>' ), array( '<', '>' ), $content ); if ( ! empty( $document_context ) ) { $escaped_context = str_replace( array( '<', '>' ), array( '<', '>' ), $document_context ); $prompt = sprintf( "\n%s\n\n%s", $escaped_context, $escaped_content ); } else { $prompt = sprintf( '%s', $escaped_content ); } $result = $this->generate_label( $prompt ); if ( is_wp_error( $result ) ) { return $result; } if ( empty( $result ) ) { return new WP_Error( 'no_results', esc_html__( 'No label was generated.', 'ai' ) ); } return sanitize_text_field( trim( $result, " \t\n\r\0\x0B\"'" ) ); } /** * Generates a label for the given content using the AI client. * * @since 1.5.0 * * @param string $prompt The prompt to use for the block container label generation. * @return string|\WP_Error The generated label, or a WP_Error if there was an error. */ protected function generate_label( string $prompt ) { $prompt = $this->filter_prompt( $prompt ); $builder = $this->get_prompt_builder( $prompt ); if ( is_wp_error( $builder ) ) { return $builder; } return $builder->generate_text(); } }