Add AI writing assistant plugin with local backend, brave search, and image generation support

- Implement local backend AI provider with Ollama integration
- Add Brave Search API integration for real-time search suggestions
- Add image generation manager with multiple AI providers
- Create hybrid provider system with local/cloud fallback
- Add comprehensive settings UI with provider management
- Implement Gutenberg sidebar with writing assistance controls
- Add SEO schema generation for AI-generated content
- Multiple provider support: OpenRouter, local backend, Codex
This commit is contained in:
Dwindi Ramadhana
2026-05-17 10:48:05 +07:00
parent 97426d5ab1
commit d2c10756ab
61 changed files with 18725 additions and 806 deletions

View File

@@ -23,9 +23,10 @@ class WP_Agentic_Writer_Markdown_Parser {
*
* @since 0.1.0
* @param string $markdown Markdown content.
* @param array $image_placeholders Optional. Array of image placeholder data with agent_image_id.
* @return array Array of Gutenberg blocks.
*/
public static function parse( $markdown ) {
public static function parse( $markdown, $image_placeholders = array() ) {
$markdown = self::normalize_markdown( $markdown );
$blocks = array();
$lines = explode( "\n", $markdown );
@@ -39,6 +40,7 @@ class WP_Agentic_Writer_Markdown_Parser {
$in_auto_code_block = false;
$auto_code_lines = array();
$auto_code_language = 'text';
$image_index = 0;
$is_code_like_line = function( $trimmed ) {
if ( '' === $trimmed ) {
return false;
@@ -92,8 +94,15 @@ class WP_Agentic_Writer_Markdown_Parser {
$in_list = false;
}
// Create image placeholder block.
$blocks[] = self::create_image_placeholder_block( $matches[1] );
// Get agent_image_id from placeholders array if available
$agent_image_id = null;
if ( ! empty( $image_placeholders[ $image_index ] ) ) {
$agent_image_id = $image_placeholders[ $image_index ]['agent_image_id'] ?? null;
}
// Create image placeholder block with agent_image_id
$blocks[] = self::create_image_placeholder_block( $matches[1], $agent_image_id );
$image_index++;
continue;
}
@@ -258,6 +267,23 @@ class WP_Agentic_Writer_Markdown_Parser {
continue;
}
// Handle numbered items with bold title (treat as paragraph, not list).
if ( preg_match( '/^(\d+)\.\s+\*\*(.+?)\*\*\s*$/', $trimmed, $matches ) ) {
if ( ! empty( $current_paragraph ) ) {
$blocks[] = self::create_paragraph_block( $current_paragraph );
$current_paragraph = '';
}
if ( $in_list ) {
$blocks[] = self::create_list_block( $list_type, $list_items );
$list_items = array();
$in_list = false;
}
// Create paragraph with manual numbering and bold title.
$content = $matches[1] . '. <strong>' . self::parse_inline_markdown( $matches[2] ) . '</strong>';
$blocks[] = self::create_paragraph_block( $content );
continue;
}
// Handle ordered lists.
if ( preg_match( '/^\d+\.\s+(.+)$/', $trimmed, $matches ) ) {
if ( ! empty( $current_paragraph ) ) {
@@ -639,20 +665,35 @@ class WP_Agentic_Writer_Markdown_Parser {
*
* @since 0.1.0
* @param string $description Image description/alt text.
* @param string $agent_image_id Optional. Agent-assigned image ID for tracking.
* @return array Gutenberg block.
*/
private static function create_image_placeholder_block( $description ) {
private static function create_image_placeholder_block( $description, $agent_image_id = null ) {
$alt = trim( $description );
// Build className with agent image ID (WordPress preserves className reliably)
$class_name = '';
if ( ! empty( $agent_image_id ) ) {
$class_name = 'wpaw-agent-img-' . esc_attr( $agent_image_id );
}
$attrs = array(
'id' => 0,
'url' => '',
'alt' => $alt,
'alt' => '[Image: ' . $alt . ']', // Mark as placeholder
'caption' => '',
'sizeSlug' => 'large',
'linkDestination' => 'none',
);
$html = '<figure class="wp-block-image size-large"><img alt="' . esc_attr( $alt ) . '" /></figure>';
// Add className and data attribute if agent_image_id provided
if ( ! empty( $agent_image_id ) ) {
$attrs['className'] = $class_name;
$attrs['data-agent-image-id'] = $agent_image_id;
}
$figure_class = 'wp-block-image size-large' . ( $class_name ? ' ' . $class_name : '' );
$html = '<figure class="' . esc_attr( $figure_class ) . '"><img alt="[Image: ' . esc_attr( $alt ) . ']" data-agent-image-id="' . esc_attr( $agent_image_id ) . '" /></figure>';
return array(
'blockName' => 'core/image',