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:
@@ -136,6 +136,39 @@ class WP_Agentic_Writer_Gutenberg_Sidebar {
|
||||
true
|
||||
);
|
||||
|
||||
// Enqueue image block toolbar script.
|
||||
$block_image_script_path = WP_AGENTIC_WRITER_DIR . 'assets/js/block-image-generate.js';
|
||||
wp_enqueue_script(
|
||||
'wp-agentic-writer-block-image-generate',
|
||||
WP_AGENTIC_WRITER_URL . 'assets/js/block-image-generate.js',
|
||||
array(
|
||||
'wp-block-editor',
|
||||
'wp-components',
|
||||
'wp-compose',
|
||||
'wp-data',
|
||||
'wp-element',
|
||||
'wp-hooks',
|
||||
'wp-i18n',
|
||||
),
|
||||
file_exists( $block_image_script_path ) ? filemtime( $block_image_script_path ) : WP_AGENTIC_WRITER_VERSION,
|
||||
true
|
||||
);
|
||||
|
||||
// Enqueue image modal script.
|
||||
$image_modal_script_path = WP_AGENTIC_WRITER_DIR . 'assets/js/image-modal.js';
|
||||
wp_enqueue_script(
|
||||
'wp-agentic-writer-image-modal',
|
||||
WP_AGENTIC_WRITER_URL . 'assets/js/image-modal.js',
|
||||
array(
|
||||
'wp-components',
|
||||
'wp-element',
|
||||
'wp-data',
|
||||
'wp-block-editor',
|
||||
),
|
||||
file_exists( $image_modal_script_path ) ? filemtime( $image_modal_script_path ) : WP_AGENTIC_WRITER_VERSION,
|
||||
true
|
||||
);
|
||||
|
||||
// Enqueue sidebar styles.
|
||||
$style_path = WP_AGENTIC_WRITER_DIR . 'assets/css/sidebar.css';
|
||||
wp_enqueue_style(
|
||||
@@ -464,6 +497,37 @@ class WP_Agentic_Writer_Gutenberg_Sidebar {
|
||||
'permission_callback' => array( $this, 'check_permissions' ),
|
||||
)
|
||||
);
|
||||
|
||||
// Image generation endpoints.
|
||||
register_rest_route(
|
||||
'wp-agentic-writer/v1',
|
||||
'/image-recommendations/(?P<post_id>\d+)',
|
||||
array(
|
||||
'methods' => 'GET',
|
||||
'callback' => array( $this, 'handle_get_image_recommendations' ),
|
||||
'permission_callback' => array( $this, 'check_permissions' ),
|
||||
)
|
||||
);
|
||||
|
||||
register_rest_route(
|
||||
'wp-agentic-writer/v1',
|
||||
'/generate-image',
|
||||
array(
|
||||
'methods' => 'POST',
|
||||
'callback' => array( $this, 'handle_generate_image' ),
|
||||
'permission_callback' => array( $this, 'check_permissions' ),
|
||||
)
|
||||
);
|
||||
|
||||
register_rest_route(
|
||||
'wp-agentic-writer/v1',
|
||||
'/commit-image',
|
||||
array(
|
||||
'methods' => 'POST',
|
||||
'callback' => array( $this, 'handle_commit_image' ),
|
||||
'permission_callback' => array( $this, 'check_permissions' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -498,17 +562,39 @@ class WP_Agentic_Writer_Gutenberg_Sidebar {
|
||||
$stored_language = get_post_meta( $post_id, '_wpaw_detected_language', true );
|
||||
$effective_language = $this->resolve_language_preference( $post_config, $detected_from_message ?: $stored_language );
|
||||
|
||||
// Extract focus keyword for context anchoring
|
||||
$focus_keyword = '';
|
||||
if ( ! empty( $post_config['focus_keyword'] ) ) {
|
||||
$focus_keyword = sanitize_text_field( $post_config['focus_keyword'] );
|
||||
} elseif ( ! empty( $post_config['seo_focus_keyword'] ) ) {
|
||||
$focus_keyword = sanitize_text_field( $post_config['seo_focus_keyword'] );
|
||||
} elseif ( $post_id > 0 ) {
|
||||
$focus_keyword = get_post_meta( $post_id, '_wpaw_focus_keyword', true );
|
||||
}
|
||||
|
||||
// Build focus keyword instruction for chat
|
||||
$focus_keyword_instruction = '';
|
||||
if ( ! empty( $focus_keyword ) ) {
|
||||
$focus_keyword_instruction = "
|
||||
CONTEXT ANCHOR: The user is working on an article about \"{$focus_keyword}\".
|
||||
Keep your responses relevant to this primary topic. If the conversation drifts, gently guide it back to \"{$focus_keyword}\".
|
||||
|
||||
At the END of your response, if you identify a good focus keyword from the discussion, suggest it in this format:
|
||||
**Focus Keyword Suggestion:** [your suggested keyword]
|
||||
";
|
||||
}
|
||||
|
||||
$language_instruction = $this->build_language_instruction( $effective_language, 'chat responses' );
|
||||
$system_prompt = "You are a helpful writing assistant. Answer clearly, with concise structure and practical suggestions.
|
||||
|
||||
{$focus_keyword_instruction}
|
||||
CRITICAL LANGUAGE REQUIREMENT:
|
||||
{$language_instruction}
|
||||
{$post_config_context}";
|
||||
|
||||
$messages = $this->prepend_system_prompt( $messages, $system_prompt );
|
||||
|
||||
// Get OpenRouter provider.
|
||||
$provider = WP_Agentic_Writer_OpenRouter_Provider::get_instance();
|
||||
// Get provider for this task type.
|
||||
$provider = WP_Agentic_Writer_Provider_Manager::get_provider_for_task( $type );
|
||||
|
||||
if ( $stream ) {
|
||||
$web_search_options = $this->get_web_search_options( $post_config );
|
||||
@@ -567,33 +653,55 @@ CRITICAL LANGUAGE REQUIREMENT:
|
||||
header( 'Cache-Control: no-cache' );
|
||||
header( 'X-Accel-Buffering: no' );
|
||||
|
||||
if ( ob_get_level() > 0 ) {
|
||||
// Aggressively disable ALL output buffering layers (WordPress nests multiple)
|
||||
@ini_set( 'output_buffering', 'Off' );
|
||||
@ini_set( 'zlib.output_compression', false );
|
||||
while ( ob_get_level() > 0 ) {
|
||||
ob_end_flush();
|
||||
}
|
||||
flush();
|
||||
|
||||
$provider = WP_Agentic_Writer_OpenRouter_Provider::get_instance();
|
||||
$provider = WP_Agentic_Writer_Provider_Manager::get_provider_for_task( $type );
|
||||
$accumulated_content = '';
|
||||
$total_cost = 0;
|
||||
$chunks_emitted = 0;
|
||||
|
||||
$this->maybe_inject_brave_search( $messages, $provider, $web_search_options );
|
||||
|
||||
$response = $provider->chat_stream(
|
||||
$messages,
|
||||
$web_search_options,
|
||||
$type,
|
||||
function( $chunk, $is_complete, $full_content ) use ( &$accumulated_content ) {
|
||||
function( $chunk, $is_complete, $full_content ) use ( &$accumulated_content, &$chunks_emitted ) {
|
||||
$accumulated_content = $full_content;
|
||||
if ( '' !== $chunk ) {
|
||||
$chunks_emitted++;
|
||||
echo "data: " . wp_json_encode(
|
||||
array(
|
||||
'type' => 'conversational_stream',
|
||||
'content' => $accumulated_content,
|
||||
)
|
||||
) . "\n\n";
|
||||
if ( ob_get_level() > 0 ) {
|
||||
ob_end_flush();
|
||||
}
|
||||
flush();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Fallback: if streaming produced no chunks but we have accumulated content, emit it now
|
||||
if ( 0 === $chunks_emitted && ! is_wp_error( $response ) && ! empty( $response['content'] ) ) {
|
||||
$accumulated_content = $response['content'];
|
||||
echo "data: " . wp_json_encode(
|
||||
array(
|
||||
'type' => 'conversational_stream',
|
||||
'content' => $accumulated_content,
|
||||
)
|
||||
) . "\n\n";
|
||||
flush();
|
||||
}
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
echo "data: " . wp_json_encode(
|
||||
array(
|
||||
@@ -1020,11 +1128,12 @@ CRITICAL LANGUAGE REQUIREMENT:
|
||||
}
|
||||
|
||||
// If fallback is provided and not empty, use it
|
||||
if ( ! empty( $fallback ) && 'auto' !== $fallback ) {
|
||||
return $fallback;
|
||||
if ( ! empty( $fallback ) && 'auto' !== strtolower( $fallback ) ) {
|
||||
return strtolower( $fallback );
|
||||
}
|
||||
|
||||
return 'english';
|
||||
// Default to 'auto' instead of 'english' to let AI detect from context
|
||||
return 'auto';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1040,7 +1149,7 @@ CRITICAL LANGUAGE REQUIREMENT:
|
||||
|
||||
// If auto or empty, let AI detect from context
|
||||
if ( empty( $language ) || 'auto' === strtolower( $language ) ) {
|
||||
return "Write the {$context} in the most appropriate language based on the topic and context.";
|
||||
return "CRITICAL: Detect the language from the conversation history and topic. Write ALL {$context} in the SAME language as the user's input. If the user wrote in Indonesian, write in Indonesian. If English, write in English. Match the user's language exactly.";
|
||||
}
|
||||
|
||||
// Pass any language name directly to AI - AI models understand all languages
|
||||
@@ -1072,6 +1181,63 @@ CRITICAL LANGUAGE REQUIREMENT:
|
||||
return $messages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Physically scrapes the web and injects the results as a system prompt if applicable.
|
||||
*
|
||||
* @since 0.1.0
|
||||
* @param array &$messages Chat messages (passed by reference).
|
||||
* @param object $provider AI Provider instance.
|
||||
* @param array $web_search_options Web search options.
|
||||
* @return void
|
||||
*/
|
||||
private function maybe_inject_brave_search( &$messages, $provider, $web_search_options ) {
|
||||
if ( empty( $web_search_options['web_search_enabled'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only inject if the provider doesn't natively support OpenRouter's web search routing plugins
|
||||
if ( $provider instanceof WP_Agentic_Writer_OpenRouter_Provider ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$last_query = '';
|
||||
foreach ( array_reverse( $messages ) as $msg ) {
|
||||
if ( 'user' === $msg['role'] ) {
|
||||
$last_query = (string) $msg['content'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( empty( $last_query ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$brave_search = WP_Agentic_Writer_Brave_Search_API::get_instance();
|
||||
$results = $brave_search->search( $last_query, 3 );
|
||||
|
||||
if ( ! is_wp_error( $results ) && ! empty( $results ) ) {
|
||||
$context_markdown = $brave_search->format_results_for_llm( $results, $last_query );
|
||||
|
||||
$injection_message = array(
|
||||
'role' => 'system',
|
||||
'content' => $context_markdown
|
||||
);
|
||||
|
||||
$injected = false;
|
||||
for( $i = count( $messages ) - 1; $i >= 0; $i-- ) {
|
||||
if ( 'user' === $messages[ $i ]['role'] ) {
|
||||
array_splice( $messages, $i, 0, array( $injection_message ) );
|
||||
$injected = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! $injected ) {
|
||||
array_unshift( $messages, $injection_message );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build web search option overrides.
|
||||
*
|
||||
@@ -1206,12 +1372,22 @@ CRITICAL LANGUAGE REQUIREMENT:
|
||||
return $this->stream_generate_plan( $topic, $context, $post_id, $auto_execute, $article_length, $clarification_answers, $effective_language, $post_config, $chat_history );
|
||||
}
|
||||
|
||||
// Get OpenRouter provider.
|
||||
$provider = WP_Agentic_Writer_OpenRouter_Provider::get_instance();
|
||||
// Get provider for planning task.
|
||||
$provider = WP_Agentic_Writer_Provider_Manager::get_provider_for_task( 'planning' );
|
||||
|
||||
// Build prompt for plan generation.
|
||||
$plan_language_instruction = $this->build_language_instruction( $effective_language, 'article plan (title, section headings, descriptions)' );
|
||||
$system_prompt = "You are an expert content strategist and technical writer. Your task is to create a detailed article plan/outline based on the user's topic and context.
|
||||
$system_prompt = "You are an Information Architect and SEO/GEO Strategist. Your task is to outline a high-information-density article based on the user's topic and context.
|
||||
|
||||
ANTI-ROBOT RULES:
|
||||
- Never use generic intros or 'throat-clearing' fluff.
|
||||
- Avoid academic, pompous, or 'expert' posturing.
|
||||
- Headings must provide direct value or ask specific questions the article will answer.
|
||||
|
||||
GEO/SEO STRATEGY:
|
||||
- Design the outline for Generative Engine Optimization (GEO): sections must flow logically to answer the user's core intent comprehensively.
|
||||
- Suggest strategic use of tables, bullet points, and Q&A formats where they maximize information density.
|
||||
- Incorporate secondary entities and related concepts naturally to show topical depth.
|
||||
|
||||
CRITICAL LANGUAGE REQUIREMENT:
|
||||
{$plan_language_instruction}
|
||||
@@ -1256,6 +1432,7 @@ Keep sections focused and actionable. Include H2 headings only. For technical ar
|
||||
);
|
||||
|
||||
// Generate plan.
|
||||
$this->maybe_inject_brave_search( $messages, $provider, $web_search_options );
|
||||
$response = $provider->chat( $messages, array_merge( array( 'temperature' => 0.7 ), $web_search_options ), 'planning' );
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
@@ -1350,7 +1527,7 @@ Keep sections focused and actionable. Include H2 headings only. For technical ar
|
||||
);
|
||||
}
|
||||
|
||||
$provider = WP_Agentic_Writer_OpenRouter_Provider::get_instance();
|
||||
$provider = WP_Agentic_Writer_Provider_Manager::get_provider_for_task( 'planning' );
|
||||
$memory_context = $this->get_post_memory_context( $post_id );
|
||||
|
||||
$system_prompt = "You are an expert content strategist. Revise the provided outline based on the user's instruction.
|
||||
@@ -1402,6 +1579,8 @@ Rules:
|
||||
),
|
||||
);
|
||||
|
||||
// Generate revised plan.
|
||||
$this->maybe_inject_brave_search( $messages, $provider, $web_search_options );
|
||||
$response = $provider->chat( $messages, array_merge( array( 'temperature' => 0.6 ), $web_search_options ), 'planning' );
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
@@ -1576,13 +1755,26 @@ Rules:
|
||||
}
|
||||
flush();
|
||||
|
||||
$provider = WP_Agentic_Writer_OpenRouter_Provider::get_instance();
|
||||
$provider = WP_Agentic_Writer_Provider_Manager::get_provider_for_task( 'writing' );
|
||||
$total_cost = 0;
|
||||
$post_config = $this->sanitize_post_config( wp_parse_args( $post_config, $this->get_default_post_config() ) );
|
||||
$post_config_context = $this->build_post_config_context( $post_config );
|
||||
$web_search_options = $this->get_web_search_options( $post_config );
|
||||
$effective_language = $this->resolve_language_preference( $post_config, $detected_language );
|
||||
|
||||
// Extract focus keyword for context anchoring
|
||||
$focus_keyword = '';
|
||||
if ( ! empty( $post_config['focus_keyword'] ) ) {
|
||||
$focus_keyword = sanitize_text_field( $post_config['focus_keyword'] );
|
||||
} elseif ( ! empty( $post_config['seo_focus_keyword'] ) ) {
|
||||
$focus_keyword = sanitize_text_field( $post_config['seo_focus_keyword'] );
|
||||
}
|
||||
|
||||
// Save focus keyword to post meta for persistence
|
||||
if ( $post_id > 0 && ! empty( $focus_keyword ) ) {
|
||||
update_post_meta( $post_id, '_wpaw_focus_keyword', $focus_keyword );
|
||||
}
|
||||
|
||||
try {
|
||||
// Note: Clarity check should be done BEFORE calling this streaming endpoint
|
||||
// The frontend is responsible for checking clarity first via /check-clarity
|
||||
@@ -1656,8 +1848,22 @@ Rules:
|
||||
// Determine language instruction for plan generation
|
||||
$plan_language_instruction = $this->build_language_instruction( $effective_language, 'article plan (title, section headings, descriptions)' );
|
||||
|
||||
$system_prompt = "You are an expert content strategist and technical writer. Your task is to create a detailed article plan/outline based on the user's topic and context.
|
||||
// Build focus keyword anchor instruction
|
||||
$focus_keyword_instruction = '';
|
||||
if ( ! empty( $focus_keyword ) ) {
|
||||
$focus_keyword_instruction = "
|
||||
PRIMARY TOPIC ANCHOR: \"{$focus_keyword}\"
|
||||
|
||||
CRITICAL: This article MUST be about \"{$focus_keyword}\".
|
||||
- The title MUST include or clearly relate to \"{$focus_keyword}\"
|
||||
- All sections MUST support this primary topic
|
||||
- Recent conversation refinements are meant to ENHANCE this topic, not REPLACE it
|
||||
- If user discussed sub-topics, treat them as ASPECTS of the primary topic \"{$focus_keyword}\"
|
||||
";
|
||||
}
|
||||
|
||||
$system_prompt = "You are an expert content strategist and technical writer. Your task is to create a detailed article plan/outline based on the user's topic and context.
|
||||
{$focus_keyword_instruction}
|
||||
CRITICAL LANGUAGE REQUIREMENT:
|
||||
{$plan_language_instruction}
|
||||
|
||||
@@ -1706,6 +1912,7 @@ Keep sections focused and actionable. Include H2 headings only. For technical ar
|
||||
error_log( 'WP Agentic Writer: Calling OpenRouter API for planning. Topic: ' . substr( $topic, 0, 100 ) );
|
||||
error_log( 'WP Agentic Writer: Detected language: ' . $detected_language );
|
||||
|
||||
$this->maybe_inject_brave_search( $messages, $provider, $web_search_options );
|
||||
$response = $provider->chat( $messages, array_merge( array( 'temperature' => 0.7 ), $web_search_options ), 'planning' );
|
||||
|
||||
error_log( 'WP Agentic Writer: OpenRouter API response received' );
|
||||
@@ -2105,7 +2312,31 @@ Remember: You MUST include the ~~~ARTICLE~~~ divider to separate your conversati
|
||||
|
||||
// NOW parse the complete markdown content and send blocks
|
||||
if ( ! empty( $markdown_content ) ) {
|
||||
$markdown_blocks = WP_Agentic_Writer_Markdown_Parser::parse( $markdown_content );
|
||||
// Extract image placeholders and generate IDs
|
||||
$image_placeholders = array();
|
||||
if ( preg_match_all( '/\[IMAGE:\s*(.+?)\]/i', $markdown_content, $matches ) ) {
|
||||
$image_manager = WP_Agentic_Writer_Image_Manager::get_instance();
|
||||
|
||||
foreach ( $matches[1] as $index => $description ) {
|
||||
$agent_image_id = 'img_' . $post_id . '_' . time() . '_' . ( $index + 1 );
|
||||
$image_placeholders[] = array(
|
||||
'agent_image_id' => $agent_image_id,
|
||||
'description' => trim( $description ),
|
||||
);
|
||||
|
||||
// Save to database
|
||||
$image_manager->save_image_recommendation(
|
||||
$post_id,
|
||||
$agent_image_id,
|
||||
'section_' . $section_id,
|
||||
$heading,
|
||||
trim( $description ),
|
||||
trim( $description )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$markdown_blocks = WP_Agentic_Writer_Markdown_Parser::parse( $markdown_content, $image_placeholders );
|
||||
|
||||
foreach ( $markdown_blocks as $block ) {
|
||||
echo "data: " . wp_json_encode(
|
||||
@@ -2221,8 +2452,8 @@ Remember: You MUST include the ~~~ARTICLE~~~ divider to separate your conversati
|
||||
}
|
||||
}
|
||||
|
||||
// Get OpenRouter provider.
|
||||
$provider = WP_Agentic_Writer_OpenRouter_Provider::get_instance();
|
||||
// Get provider for writing task.
|
||||
$provider = WP_Agentic_Writer_Provider_Manager::get_provider_for_task( 'writing' );
|
||||
|
||||
$image_instruction = "IMAGE SUGGESTIONS:
|
||||
- Suggest where images would enhance understanding
|
||||
@@ -2282,7 +2513,18 @@ Remember: You MUST include the ~~~ARTICLE~~~ divider to separate your conversati
|
||||
}
|
||||
|
||||
// Build system prompt for article generation.
|
||||
$system_prompt = "You are an expert technical writer and blogger. Your task is to write engaging, clear, and well-structured content based on the provided article plan.
|
||||
$system_prompt = "You are an industry practitioner sharing insights with a colleague. Write engaging, high-information-density content based on the provided article plan.
|
||||
|
||||
ANTI-ROBOT RULES:
|
||||
- BANNED WORDS: delve, furthermore, moreover, crucial, paramount, landscape, testament, in today's digital world, in conclusion.
|
||||
- BANNED PATTERNS: Do not use transition words to start paragraphs. Do not summarize what you are about to say. Do not summarize what you just said.
|
||||
- BURSTINESS: Mix very short, punchy sentences (3-5 words) with longer, descriptive ones. Avoid uniform sentence length.
|
||||
- TONE: Conversational, direct, pragmatic. Do not sound like an academic 'expert' or textbook.
|
||||
|
||||
GEO/SEO STRATEGY:
|
||||
- Answer the implicit user intent directly and immediately in the first paragraph.
|
||||
- Maximize information density: high ratio of facts/insights to total word count. Remove filler adjectives.
|
||||
- Use bullet points or numbered lists where they make data easier to scan.
|
||||
|
||||
CRITICAL LANGUAGE REQUIREMENT:
|
||||
{$language_instruction}
|
||||
@@ -2291,22 +2533,17 @@ CRITICAL LANGUAGE REQUIREMENT:
|
||||
|
||||
Follow these guidelines:
|
||||
- Use the tone specified in POST CONFIG if provided; otherwise be conversational but professional
|
||||
- Use clear examples and analogies
|
||||
- For code blocks, use proper syntax highlighting
|
||||
- Keep paragraphs concise (2-3 sentences)
|
||||
- Use transitions between sections
|
||||
- Write for the specified difficulty level
|
||||
- Code formatting: Any code/config snippets MUST be in fenced code blocks with a language tag (e.g., ```php). Never place code inline in paragraphs.
|
||||
- Embed secondary keywords naturally as concepts, without forcing exact matches
|
||||
- For code blocks, use proper syntax highlighting (e.g., ```php)
|
||||
- Code typography: Use plain ASCII quotes inside code. Do NOT use smart quotes.
|
||||
- Write for the specified difficulty level
|
||||
{$seo_instruction}
|
||||
{$internal_links_instruction}
|
||||
|
||||
IMAGE SUGGESTIONS:
|
||||
- Suggest where images would enhance understanding
|
||||
- Place image suggestions on their own line using this format: [IMAGE: descriptive alt text]
|
||||
- Be strategic: only suggest images where they add real value (diagrams, screenshots, visual examples)
|
||||
- Good places for images: after introductions, before complex explanations, to show examples
|
||||
- Maximum 1-2 image suggestions per section
|
||||
- Suggest where images would enhance understanding (diagrams, screenshots)
|
||||
- Place image suggestions on their own line: [IMAGE: descriptive alt text]
|
||||
- Maximum 1 image per section
|
||||
{$image_instruction}";
|
||||
|
||||
// Generate content for each section.
|
||||
@@ -2435,7 +2672,7 @@ IMAGE SUGGESTIONS:
|
||||
}
|
||||
flush();
|
||||
|
||||
$provider = WP_Agentic_Writer_OpenRouter_Provider::get_instance();
|
||||
$provider = WP_Agentic_Writer_Provider_Manager::get_provider_for_task( 'writing' );
|
||||
$total_cost = 0;
|
||||
$post_config = $this->sanitize_post_config( wp_parse_args( $post_config, $this->get_default_post_config() ) );
|
||||
$post_config_context = $this->build_post_config_context( $post_config );
|
||||
@@ -2527,7 +2764,18 @@ IMAGE SUGGESTIONS:
|
||||
}
|
||||
}
|
||||
|
||||
$system_prompt = "You are an expert technical writer and blogger. Your task is to write engaging, clear, and well-structured content based on the provided article plan.
|
||||
$system_prompt = "You are an industry practitioner sharing insights with a colleague. Write engaging, high-information-density content based on the provided article plan.
|
||||
|
||||
ANTI-ROBOT RULES:
|
||||
- BANNED WORDS: delve, furthermore, moreover, crucial, paramount, landscape, testament, in today's digital world, in conclusion.
|
||||
- BANNED PATTERNS: Do not use transition words to start paragraphs. Do not summarize what you are about to say. Do not summarize what you just said.
|
||||
- BURSTINESS: Mix very short, punchy sentences (3-5 words) with longer, descriptive ones. Avoid uniform sentence length.
|
||||
- TONE: Conversational, direct, pragmatic. Do not sound like an academic 'expert' or textbook.
|
||||
|
||||
GEO/SEO STRATEGY:
|
||||
- Answer the implicit user intent directly and immediately in the first paragraph.
|
||||
- Maximize information density: high ratio of facts/insights to total word count. Remove filler adjectives.
|
||||
- Use bullet points or numbered lists where they make data easier to scan.
|
||||
|
||||
CRITICAL LANGUAGE REQUIREMENT:
|
||||
{$language_instruction}
|
||||
@@ -2535,22 +2783,17 @@ CRITICAL LANGUAGE REQUIREMENT:
|
||||
|
||||
Follow these guidelines:
|
||||
- Use the tone specified in POST CONFIG if provided; otherwise be conversational but professional
|
||||
- Use clear examples and analogies
|
||||
- For code blocks, use proper syntax highlighting
|
||||
- Keep paragraphs concise (2-3 sentences)
|
||||
- Use transitions between sections
|
||||
- Write for the specified difficulty level
|
||||
- Code formatting: Any code/config snippets MUST be in fenced code blocks with a language tag (e.g., ```php). Never place code inline in paragraphs.
|
||||
- Embed secondary keywords naturally as concepts, without forcing exact matches
|
||||
- For code blocks, use proper syntax highlighting (e.g., ```php)
|
||||
- Code typography: Use plain ASCII quotes inside code. Do NOT use smart quotes.
|
||||
- Write for the specified difficulty level
|
||||
{$seo_instruction}
|
||||
{$internal_links_instruction}
|
||||
|
||||
IMAGE SUGGESTIONS:
|
||||
- Suggest where images would enhance understanding
|
||||
- Place image suggestions on their own line using this format: [IMAGE: descriptive alt text]
|
||||
- Be strategic: only suggest images where they add real value (diagrams, screenshots, visual examples)
|
||||
- Good places for images: after introductions, before complex explanations, to show examples
|
||||
- Maximum 1-2 image suggestions per section
|
||||
- Suggest where images would enhance understanding (diagrams, screenshots)
|
||||
- Place image suggestions on their own line: [IMAGE: descriptive alt text]
|
||||
- Maximum 1 image per section
|
||||
{$image_instruction}";
|
||||
|
||||
$section_index = 0;
|
||||
@@ -2795,8 +3038,8 @@ IMAGE SUGGESTIONS:
|
||||
);
|
||||
}
|
||||
|
||||
// Get OpenRouter provider.
|
||||
$provider = WP_Agentic_Writer_OpenRouter_Provider::get_instance();
|
||||
// Get provider for writing task.
|
||||
$provider = WP_Agentic_Writer_Provider_Manager::get_provider_for_task( 'writing' );
|
||||
|
||||
$messages = array(
|
||||
array(
|
||||
@@ -2944,7 +3187,7 @@ IMAGE SUGGESTIONS:
|
||||
);
|
||||
}
|
||||
|
||||
$provider = WP_Agentic_Writer_OpenRouter_Provider::get_instance();
|
||||
$provider = WP_Agentic_Writer_Provider_Manager::get_provider_for_task( 'writing' );
|
||||
|
||||
// Get settings.
|
||||
$settings = get_option( 'wp_agentic_writer_settings', array() );
|
||||
@@ -2960,15 +3203,21 @@ IMAGE SUGGESTIONS:
|
||||
'pov',
|
||||
);
|
||||
|
||||
// If quiz is disabled, always return clear.
|
||||
// If quiz is disabled, skip AI questions but still add MANDATORY config questions
|
||||
if ( ! $enabled ) {
|
||||
$result = array(
|
||||
'is_clear' => true,
|
||||
'confidence' => 1.0,
|
||||
'questions' => array(),
|
||||
);
|
||||
// MANDATORY: Always add config questions (language, focus keyword)
|
||||
$result['questions'] = $this->append_config_questions( $result['questions'], $post_config );
|
||||
if ( ! empty( $result['questions'] ) ) {
|
||||
$result['is_clear'] = false; // Force quiz for config questions
|
||||
}
|
||||
return new WP_REST_Response(
|
||||
array(
|
||||
'result' => array(
|
||||
'is_clear' => true,
|
||||
'confidence' => 1.0,
|
||||
'questions' => array(),
|
||||
),
|
||||
'result' => $result,
|
||||
'cost' => 0,
|
||||
),
|
||||
200
|
||||
@@ -3079,9 +3328,11 @@ QUESTION TYPES:
|
||||
|
||||
CONFIDENCE CALCULATION:
|
||||
- Start at 100% (1.0)
|
||||
- Subtract 20% for each missing HIGH-PRIORITY category (topic_scope, target_platform, specific_focus)
|
||||
- Subtract 15% for each missing HIGH-PRIORITY category (topic_scope, target_platform, specific_focus)
|
||||
- Subtract 10% for each missing SECONDARY category (target_outcome, target_audience, content_depth)
|
||||
- CRITICAL: If chat history exists with detailed discussion, ADD 20% confidence bonus (user already provided context)
|
||||
- If confidence < {$threshold}, generate questions starting with HIGH-PRIORITY
|
||||
- MANDATORY: Even if confidence >= threshold, ALWAYS ask at least 1-2 system config questions (language, SEO settings)
|
||||
|
||||
QUESTION GENERATION STRATEGY:
|
||||
1. Always detect user language first and match it
|
||||
@@ -3118,6 +3369,11 @@ No markdown, no explanation - just JSON.";
|
||||
// Log error and use default questions instead of failing.
|
||||
error_log( 'WP Agentic Writer: Clarity check API error - ' . $response->get_error_message() );
|
||||
$result = $this->get_default_clarification_questions( $topic );
|
||||
// MANDATORY: Always add config questions
|
||||
$result['questions'] = $this->append_config_questions( $result['questions'] ?? array(), $post_config );
|
||||
if ( ! empty( $result['questions'] ) ) {
|
||||
$result['is_clear'] = false;
|
||||
}
|
||||
return new WP_REST_Response(
|
||||
array(
|
||||
'result' => $result,
|
||||
@@ -3135,6 +3391,11 @@ No markdown, no explanation - just JSON.";
|
||||
// Log parse error and use default questions instead of failing.
|
||||
error_log( 'WP Agentic Writer: Failed to parse clarity check JSON' );
|
||||
$result = $this->get_default_clarification_questions( $topic );
|
||||
// MANDATORY: Always add config questions
|
||||
$result['questions'] = $this->append_config_questions( $result['questions'] ?? array(), $post_config );
|
||||
if ( ! empty( $result['questions'] ) ) {
|
||||
$result['is_clear'] = false;
|
||||
}
|
||||
return new WP_REST_Response(
|
||||
array(
|
||||
'result' => $result,
|
||||
@@ -3156,15 +3417,15 @@ No markdown, no explanation - just JSON.";
|
||||
$response['cost'] ?? 0
|
||||
);
|
||||
|
||||
// Always add configuration questions, even if no clarity questions
|
||||
// MANDATORY: Always add configuration questions
|
||||
if ( ! isset( $result['questions'] ) || ! is_array( $result['questions'] ) ) {
|
||||
$result['questions'] = array();
|
||||
}
|
||||
$result['questions'] = $this->append_config_questions( $result['questions'], $post_config );
|
||||
|
||||
// If only config questions exist and clarity is clear, still show config
|
||||
if ( empty( $result['questions'] ) === false && $result['is_clear'] === true ) {
|
||||
$result['is_clear'] = false; // Force quiz to show for config questions
|
||||
// CRITICAL: Always show quiz if config questions exist (system questions are MANDATORY)
|
||||
if ( ! empty( $result['questions'] ) ) {
|
||||
$result['is_clear'] = false; // Force quiz to show - config questions are mandatory
|
||||
}
|
||||
|
||||
return new WP_REST_Response(
|
||||
@@ -3312,7 +3573,7 @@ No markdown, no explanation - just JSON.";
|
||||
return $this->stream_block_refine( $block_id, $block_type, $block_content, $refinement_request, $article_context, $post_id, $post_config );
|
||||
}
|
||||
|
||||
$provider = WP_Agentic_Writer_OpenRouter_Provider::get_instance();
|
||||
$provider = WP_Agentic_Writer_Provider_Manager::get_provider_for_task( 'refinement' );
|
||||
|
||||
// Build context from article structure.
|
||||
$context_str = "\n\nArticle Context:\n";
|
||||
@@ -3450,7 +3711,7 @@ Keep the same block type (paragraph, heading, list, etc.).";
|
||||
}
|
||||
flush();
|
||||
|
||||
$provider = WP_Agentic_Writer_OpenRouter_Provider::get_instance();
|
||||
$provider = WP_Agentic_Writer_Provider_Manager::get_provider_for_task( 'refinement' );
|
||||
$post_config = $this->sanitize_post_config( wp_parse_args( $post_config, $this->get_default_post_config() ) );
|
||||
$post_config_context = $this->build_post_config_context( $post_config );
|
||||
$stored_language = get_post_meta( $post_id, '_wpaw_detected_language', true );
|
||||
@@ -3473,10 +3734,16 @@ Keep the same block type (paragraph, heading, list, etc.).";
|
||||
$context_str .= "Next section: " . $article_context['nextBlock']['heading'] . "\n";
|
||||
}
|
||||
|
||||
$system_prompt = "You are an expert content refiner. Your task is to rewrite and improve content based on the user's request.
|
||||
$system_prompt = "You are a precise content editor. Your task is to refine the provided content based strictly on the user's request.
|
||||
|
||||
ANTI-ROBOT RULES:
|
||||
- BANNED WORDS: delve, furthermore, moreover, crucial, paramount, landscape, testament.
|
||||
- Do not add introductory throat-clearing sentences or summarizing conclusions unless explicitly requested.
|
||||
- Increase specificity and information density. Do not just increase word count with conversational filler.
|
||||
|
||||
CRITICAL LANGUAGE REQUIREMENT:
|
||||
{$language_instruction}
|
||||
|
||||
{$post_config_context}
|
||||
|
||||
{$context_str}
|
||||
@@ -4057,7 +4324,7 @@ No markdown, no explanation - just JSON.";
|
||||
);
|
||||
}
|
||||
|
||||
$provider = WP_Agentic_Writer_OpenRouter_Provider::get_instance();
|
||||
$provider = WP_Agentic_Writer_Provider_Manager::get_provider_for_task( 'writing' );
|
||||
$post_config = $this->sanitize_post_config( wp_parse_args( $post_config, $this->get_default_post_config() ) );
|
||||
$post_config_context = $this->build_post_config_context( $post_config );
|
||||
$stored_language = get_post_meta( $post_id, '_wpaw_detected_language', true );
|
||||
@@ -4225,7 +4492,12 @@ Blocks:
|
||||
$context_str .= "Next section: " . $article_context['nextBlock']['heading'] . "\n";
|
||||
}
|
||||
|
||||
$system_prompt = "You are an expert content refiner. Your task is to rewrite and improve content based on the user's request.
|
||||
$system_prompt = "You are a precise content editor. Your task is to refine the provided content based strictly on the user's request.
|
||||
|
||||
ANTI-ROBOT RULES:
|
||||
- BANNED WORDS: delve, furthermore, moreover, crucial, paramount, landscape, testament.
|
||||
- Do not add introductory throat-clearing sentences or summarizing conclusions unless explicitly requested.
|
||||
- Increase specificity and information density. Do not just increase word count with conversational filler.
|
||||
|
||||
CRITICAL LANGUAGE REQUIREMENT:
|
||||
{$language_instruction}
|
||||
@@ -5026,7 +5298,7 @@ Output format:
|
||||
}
|
||||
|
||||
$language_instruction = $this->build_language_instruction( $effective_language, 'meta description' );
|
||||
$provider = WP_Agentic_Writer_OpenRouter_Provider::get_instance();
|
||||
$provider = WP_Agentic_Writer_Provider_Manager::get_provider_for_task( 'clarity' );
|
||||
|
||||
$prompt = "Generate a compelling meta description for SEO. Requirements:\n";
|
||||
$prompt .= "- Length: MAXIMUM 155 characters (STRICT - count every character including spaces)\n";
|
||||
@@ -5138,7 +5410,7 @@ Output format:
|
||||
}
|
||||
}
|
||||
|
||||
$provider = WP_Agentic_Writer_OpenRouter_Provider::get_instance();
|
||||
$provider = WP_Agentic_Writer_Provider_Manager::get_provider_for_task( 'clarity' );
|
||||
|
||||
$prompt = "Generate a compelling meta description for SEO. Requirements:\n";
|
||||
$prompt .= "- Length: MAXIMUM 155 characters (STRICT - count every character including spaces)\n";
|
||||
@@ -5303,8 +5575,8 @@ PREFERENCES: [any specific requirements]
|
||||
Conversation:
|
||||
{$history_text}";
|
||||
|
||||
// Call AI with cheap model
|
||||
$provider = WP_Agentic_Writer_OpenRouter_Provider::get_instance();
|
||||
// Call AI with clarity model for language detection
|
||||
$provider = WP_Agentic_Writer_Provider_Manager::get_provider_for_task( 'clarity' );
|
||||
$messages = array(
|
||||
array(
|
||||
'role' => 'user',
|
||||
@@ -5385,8 +5657,8 @@ User's message: \"{$last_message}\"
|
||||
|
||||
Respond with ONLY the intent code (e.g., \"create_outline\"). No explanation.";
|
||||
|
||||
// Call AI with cheap model
|
||||
$provider = WP_Agentic_Writer_OpenRouter_Provider::get_instance();
|
||||
// Call AI with clarity model for intent detection
|
||||
$provider = WP_Agentic_Writer_Provider_Manager::get_provider_for_task( 'clarity' );
|
||||
$messages = array(
|
||||
array(
|
||||
'role' => 'user',
|
||||
@@ -5429,4 +5701,79 @@ Respond with ONLY the intent code (e.g., \"create_outline\"). No explanation.";
|
||||
200
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle get image recommendations request.
|
||||
*
|
||||
* @param WP_REST_Request $request REST request.
|
||||
* @return WP_REST_Response|WP_Error Response.
|
||||
*/
|
||||
public function handle_get_image_recommendations( $request ) {
|
||||
$post_id = $request->get_param( 'post_id' );
|
||||
|
||||
$image_manager = WP_Agentic_Writer_Image_Manager::get_instance();
|
||||
$images = $image_manager->get_image_recommendations( $post_id );
|
||||
|
||||
return new WP_REST_Response(
|
||||
array( 'images' => $images ),
|
||||
200
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle generate image request.
|
||||
*
|
||||
* @param WP_REST_Request $request REST request.
|
||||
* @return WP_REST_Response|WP_Error Response.
|
||||
*/
|
||||
public function handle_generate_image( $request ) {
|
||||
$post_id = $request->get_param( 'post_id' );
|
||||
$agent_image_id = $request->get_param( 'agent_image_id' );
|
||||
$prompt = $request->get_param( 'prompt' );
|
||||
$variant_count = $request->get_param( 'variant_count' ) ?? 2;
|
||||
|
||||
$image_manager = WP_Agentic_Writer_Image_Manager::get_instance();
|
||||
$variants = $image_manager->generate_image_variants(
|
||||
$post_id,
|
||||
$agent_image_id,
|
||||
$prompt,
|
||||
$variant_count
|
||||
);
|
||||
|
||||
if ( is_wp_error( $variants ) ) {
|
||||
return $variants;
|
||||
}
|
||||
|
||||
return new WP_REST_Response(
|
||||
array( 'variants' => $variants ),
|
||||
200
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle commit image request.
|
||||
*
|
||||
* @param WP_REST_Request $request REST request.
|
||||
* @return WP_REST_Response|WP_Error Response.
|
||||
*/
|
||||
public function handle_commit_image( $request ) {
|
||||
$post_id = $request->get_param( 'post_id' );
|
||||
$agent_image_id = $request->get_param( 'agent_image_id' );
|
||||
$variant_id = $request->get_param( 'variant_id' );
|
||||
$alt_text = $request->get_param( 'alt' );
|
||||
|
||||
$image_manager = WP_Agentic_Writer_Image_Manager::get_instance();
|
||||
$result = $image_manager->commit_image_variant(
|
||||
$post_id,
|
||||
$agent_image_id,
|
||||
$variant_id,
|
||||
$alt_text
|
||||
);
|
||||
|
||||
if ( is_wp_error( $result ) ) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
return new WP_REST_Response( $result, 200 );
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user