ai_service = $ai_service; } public function name(): string { return 'generate_blocks'; } public function description(): string { return 'Generate a Tailwind CSS v4 HTML section or native WordPress blocks and convert it into WordPress block data. ' . 'Use this for layouts, sections, pages, hero areas, cards, buttons, forms, blog post lists, navigation menus, etc. ' . "Set block_type to 'native' when generating dynamic or native WordPress content such as blog posts, navigation, post content, archives, or search forms."; } public function input_schema(): array { return array( 'type' => 'object', 'properties' => array( 'description' => array( 'type' => 'string', 'description' => 'Detailed description of the section or page to generate.', ), '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.", ), 'selection' => array( 'type' => 'object', 'description' => 'Optional selected block context (html, blocks, indices).', 'properties' => array( 'html' => array( 'type' => 'string' ), 'blocks' => array( 'type' => 'array' ), 'indices' => array( 'type' => 'array' ), ), ), ), 'required' => array( 'description' ), ); } public function execute( array $input, array $context = array() ): array { $description = $input['description'] ?? ''; $selection = $input['selection'] ?? $context['selection'] ?? null; $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 ) { $content = $this->ai_service->generate_native_blocks( $description, $selection, $post_id ); } else { $content = $this->ai_service->generate_html( $description, $selection, $post_id ); } if ( is_wp_error( $content ) ) { return array( 'type' => 'error', 'message' => $content->get_error_message(), ); } $content = $this->clean_content( $content ); $blocks = HtmlToBlocks::convert( $content ); $replacement_html = serialize_blocks( BlocksToHtml::normalize_blocks( $blocks ) ); $post_id = intval( $context['post_id'] ?? 0 ); $stage_result = 'chat' !== ( $context['request_source'] ?? '' ); if ( $post_id && $stage_result ) { SelectionBridge::store_result( array( 'action' => 'insert', 'editor' => 'gutenberg', 'post_id' => $post_id, 'blocks' => $blocks, 'html' => $content, 'replacement_html' => $replacement_html, ) ); } return array( 'action' => 'insert', 'editor' => 'gutenberg', 'type' => 'blocks', 'html' => $content, 'blocks' => $blocks, 'replacement_html' => $replacement_html, ); } /** * 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; } }