ai_service = $ai_service; } public function name(): string { return 'add_blocks_to_page'; } public function description(): string { return 'Convert HTML or WordPress block markup into WordPress blocks and insert them into the current post/page. ' . 'Use this when the user wants to add a new section, hero, card, component, blog post list, navigation menu, or any other block to the page. ' . 'Provide either raw html/block markup or a description and the content will be generated for you. ' . "Set block_type to 'native' when generating dynamic or native WordPress content such as blog posts, navigation, post content, archives, or search forms. " . "Use position 'start' to add a header/section at the very top of the page, 'end' (default) to append at the bottom, " . "'before_selection' or 'after_selection' to insert relative to the block the user currently has selected in the editor " . "(e.g. 'add a section after this', 'put a CTA above the selected block'). For before_selection/after_selection the user must have a block selected."; } public function input_schema(): array { return array( 'type' => 'object', 'properties' => array( 'html' => array( 'type' => 'string', 'description' => 'Tailwind CSS HTML or serialized WordPress block markup to convert and insert into the page.', ), 'description' => array( 'type' => 'string', 'description' => 'Description of the section to generate and insert. Use this if you do not have ready HTML/block markup.', ), 'block_type' => array( 'type' => 'string', 'enum' => array( 'tailwind', 'native' ), 'default' => 'tailwind', 'description' => "Use 'native' for dynamic or native WordPress blocks (blog posts, navigation, post content, archives, search). Use 'tailwind' (default) for static sections, hero areas, cards, buttons, and custom layouts.", ), 'position' => array( 'type' => 'string', 'enum' => array( 'end', 'start', 'before_selection', 'after_selection' ), 'default' => 'end', 'description' => "Where to insert the blocks. 'start' = very top of the page (e.g. add a header). 'end' (default) = bottom of the page. 'before_selection' / 'after_selection' = insert immediately before/after the block the user currently has selected in the editor (requires a selection; use this when the user asks to insert 'in the middle', 'after this section', 'above the selected block', etc.).", ), ), ); } public function execute( array $input, array $context = array() ): array { $post_id = intval( $context['post_id'] ?? 0 ); if ( ! $post_id ) { return array( 'type' => 'error', 'message' => __( 'No current post ID available.', 'advanced-animation' ), ); } $content = $this->prepare_content( $input, $context ); if ( is_wp_error( $content ) ) { return array( 'type' => 'error', 'message' => $content->get_error_message(), ); } $position = $input['position'] ?? 'end'; $blocks = HtmlToBlocks::convert( $content ); if ( empty( $blocks ) ) { return array( 'type' => 'error', 'message' => sprintf( /* translators: %s: received HTML snippet */ __( 'Could not convert HTML to blocks. Received: %s', 'advanced-animation' ), mb_strimwidth( $content, 0, 200, '...' ) ), ); } $stage_result = 'chat' !== ( $context['request_source'] ?? '' ); $result = Block_Updater::insert_blocks( $post_id, $blocks, $position, $context['page_content'] ?? null, $stage_result, $context['selection'] ?? null ); if ( is_wp_error( $result ) ) { return array( 'type' => 'error', 'message' => $result->get_error_message(), ); } return array_merge( array( 'type' => 'blocks_updated', 'action' => 'add', ), $result ); } /** * Resolve the content to insert, either from input or by generating it. * * @return string|WP_Error */ private function prepare_content( array $input, array $context ) { $html = $input['html'] ?? ''; if ( ! empty( $html ) ) { return $this->clean_content( $html ); } $description = $input['description'] ?? ''; if ( empty( $description ) ) { return new WP_Error( 'missing_input', __( 'No html or description provided.', 'advanced-animation' ) ); } $block_type = $input['block_type'] ?? 'tailwind'; $preference = $context['block_type_preference'] ?? 'auto'; if ( in_array( $preference, array( 'tailwind', 'native' ), true ) ) { $block_type = $preference; } $post_id = intval( $context['post_id'] ?? 0 ) ?: null; if ( 'native' === $block_type ) { $generated = $this->ai_service->generate_native_blocks( $description, $context['selection'] ?? null, $post_id ); } else { $generated = $this->ai_service->generate_html( $description, $context['selection'] ?? null, $post_id ); } if ( is_wp_error( $generated ) ) { return $generated; } return $this->clean_content( $generated ); } /** * Strip markdown fences and other wrapping that breaks conversion. */ private function clean_content( string $content ): string { $content = trim( $content ); // Strip markdown fences. if ( preg_match( '/^```[^\r\n]*\R([\s\S]*?)```$/', $content, $matches ) ) { $content = trim( $matches[1] ); } // Extract serialized native block markup from explanatory model text. $native_start = stripos( $content, '' ); if ( false !== $native_end ) { return trim( substr( $content, 0, $native_end + 3 ) ); } } // Extract body contents if a full document is returned. if ( preg_match( '/]*>(.*?)<\/body>/s', $content, $matches ) ) { $content = trim( $matches[1] ); } return $content; } }