database = new AIGH_Database(); $this->openai_client = new AIGH_OpenAI_Client(); $this->content_planner = new AIGH_Content_Planner($this->openai_client); $this->content_generator = new AIGH_Content_Generator($this->openai_client); $this->content_publisher = new AIGH_Content_Publisher(); $this->web_research = new AIGH_Web_Research($this->openai_client); // Initialize pipeline registry AIGH_Step_Registry::register_defaults(); register_activation_hook(__FILE__, array($this, 'activate_plugin')); add_action('admin_menu', array($this, 'add_admin_menu')); add_action('admin_init', array($this, 'register_settings')); add_action('admin_enqueue_scripts', array($this, 'enqueue_admin_scripts')); add_action('wp_ajax_aigh_create_plan', array($this, 'ajax_create_plan')); add_action('wp_ajax_aigh_create_plan_from_keywords', array($this, 'ajax_create_plan_from_keywords')); add_action('wp_ajax_aigh_get_plans', array($this, 'ajax_get_plans')); add_action('wp_ajax_aigh_get_plan_items', array($this, 'ajax_get_plan_items')); add_action('wp_ajax_aigh_update_plan_item', array($this, 'ajax_update_plan_item')); add_action('wp_ajax_aigh_delete_plan_item', array($this, 'ajax_delete_plan_item')); add_action('wp_ajax_aigh_update_plan_settings', array($this, 'ajax_update_plan_settings')); add_action('wp_ajax_aigh_apply_keywords', array($this, 'ajax_apply_keywords')); add_action('wp_ajax_aigh_apply_keywords_sequential', array($this, 'ajax_apply_keywords_sequential')); add_action('wp_ajax_aigh_generate_article', array($this, 'ajax_generate_article')); add_action('wp_ajax_aigh_generate_batch', array($this, 'ajax_generate_batch')); add_action('wp_ajax_aigh_generate', array($this, 'ajax_quick_generate')); add_action('wp_ajax_aigh_save_draft', array($this, 'ajax_save_draft')); add_action('wp_ajax_aigh_refine_article', array($this, 'ajax_refine_article')); add_action('wp_ajax_aigh_refine_article', array($this, 'ajax_refine_article')); add_action('wp_ajax_aigh_suggest_titles', array($this, 'ajax_suggest_titles')); // Workflow Builder AJAX handlers add_action('wp_ajax_aigh_get_workflows', array($this, 'ajax_get_workflows')); add_action('wp_ajax_aigh_get_workflow', array($this, 'ajax_get_workflow')); add_action('wp_ajax_aigh_save_workflow', array($this, 'ajax_save_workflow')); add_action('wp_ajax_aigh_delete_workflow', array($this, 'ajax_delete_workflow')); add_action('wp_ajax_aigh_get_available_steps', array($this, 'ajax_get_available_steps')); // Pipeline test endpoint (temporary - for development testing) add_action('wp_ajax_aigh_test_pipeline', array($this, 'ajax_test_pipeline')); // Style analysis endpoint add_action('wp_ajax_aigh_analyze_style', array($this, 'ajax_analyze_style')); } public function add_admin_menu() { add_menu_page( 'AI Ghostwriter', 'AI Ghostwriter', 'manage_options', 'ai-ghostwriter', array($this, 'render_dashboard_page'), 'dashicons-grid-view', 30 ); add_submenu_page( 'ai-ghostwriter', 'Dashboard', 'Dashboard', 'manage_options', 'ai-ghostwriter', array($this, 'render_dashboard_page') ); add_submenu_page( 'ai-ghostwriter', 'Content Planner', 'Content Planner', 'manage_options', 'ai-ghostwriter-planner', array($this, 'render_planner_page') ); add_submenu_page( 'ai-ghostwriter', 'Quick Generate', 'Quick Generate', 'manage_options', 'ai-ghostwriter-quick', array($this, 'render_quick_generate_page') ); add_submenu_page( 'ai-ghostwriter', 'Settings', 'Settings', 'manage_options', 'ai-ghostwriter-settings', array($this, 'render_settings_page') ); add_submenu_page( 'ai-ghostwriter', 'Workflow Builder', 'Workflow Builder', 'manage_options', 'ai-ghostwriter-workflows', array($this, 'render_workflow_builder_page') ); } public function register_settings() { register_setting('aigh_settings', 'aigh_api_key', array( 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field', 'default' => '' )); register_setting('aigh_settings', 'aigh_model', array( 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field', 'default' => 'gpt-4o-mini' )); register_setting('aigh_settings', 'aigh_default_category', array( 'type' => 'integer', 'sanitize_callback' => 'absint', 'default' => 1 )); register_setting('aigh_settings', 'aigh_default_status', array( 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field', 'default' => 'draft' )); register_setting('aigh_settings', 'aigh_serper_api_key', array( 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field', 'default' => '' )); register_setting('aigh_settings', 'aigh_enable_research', array( 'type' => 'boolean', 'sanitize_callback' => 'rest_sanitize_boolean', 'default' => false )); register_setting('aigh_settings', 'aigh_serper_api_key', array( 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field', 'default' => '' )); // Check if we need to seed new templates (version 2 adds research templates) $template_version = get_option('aigh_template_version', 1); if ($template_version < 2) { $this->seed_workflow_templates(); update_option('aigh_template_version', 2); } } public function enqueue_admin_scripts($hook) { if (strpos($hook, 'ai-ghostwriter') === false) { return; } wp_enqueue_style( 'ai-ghostwriter-admin', AIGH_PLUGIN_URL . 'assets/admin-style.css', array(), AIGH_VERSION ); wp_enqueue_script( 'ai-ghostwriter-admin', AIGH_PLUGIN_URL . 'assets/admin-script.js', array('jquery'), AIGH_VERSION, true ); // Load workflow builder JS on workflow page if (strpos($hook, 'ai-ghostwriter-workflows') !== false) { wp_enqueue_script( 'ai-ghostwriter-workflow-builder', AIGH_PLUGIN_URL . 'assets/workflow-builder.js', array('jquery', 'ai-ghostwriter-admin'), AIGH_VERSION, true ); } // Load settings page JS if (strpos($hook, 'ai-ghostwriter-settings') !== false) { wp_enqueue_script( 'aigh-settings-page', AIGH_PLUGIN_URL . 'assets/js/settings-page.js', array('jquery'), AIGH_VERSION, true ); } wp_localize_script('ai-ghostwriter-admin', 'aighAdmin', array( 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('aigh_nonce'), 'current_page' => $hook )); } public function render_dashboard_page() { if (!current_user_can('manage_options')) { return; } $api_key = get_option('aigh_api_key', ''); $stats = $this->database->get_statistics(); include AIGH_PLUGIN_DIR . 'includes/views/dashboard.php'; } public function render_planner_page() { if (!current_user_can('manage_options')) { return; } $api_key = get_option('aigh_api_key', ''); include AIGH_PLUGIN_DIR . 'includes/views/planner.php'; } public function render_quick_generate_page() { if (!current_user_can('manage_options')) { return; } $api_key = get_option('aigh_api_key', ''); include AIGH_PLUGIN_DIR . 'includes/views/quick-generate.php'; } public function render_settings_page() { if (!current_user_can('manage_options')) { return; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- This is a standard WP settings page check if (isset($_GET['settings-updated'])) { add_settings_error('aigh_messages', 'aigh_message', 'Settings Saved', 'updated'); } settings_errors('aigh_messages'); include AIGH_PLUGIN_DIR . 'includes/views/settings.php'; } public function ajax_create_plan() { check_ajax_referer('aigh_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error('Unauthorized'); return; } $topic = isset($_POST['topic']) ? sanitize_textarea_field(wp_unslash($_POST['topic'])) : ''; $persona = isset($_POST['persona']) ? sanitize_text_field(wp_unslash($_POST['persona'])) : ''; $num_articles = isset($_POST['num_articles']) ? absint($_POST['num_articles']) : 0; if (empty($topic) || $num_articles < 1 || $num_articles > 20) { wp_send_json_error('Invalid input'); return; } $result = $this->content_planner->create_plan($topic, $num_articles, $persona); if (is_wp_error($result)) { wp_send_json_error($result->get_error_message()); return; } wp_send_json_success($result); } /** * Create a content plan from a list of keywords * Each keyword becomes an article with that keyword as SEO target */ public function ajax_create_plan_from_keywords() { check_ajax_referer('aigh_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error('Unauthorized'); return; } $topic = isset($_POST['topic']) ? sanitize_textarea_field(wp_unslash($_POST['topic'])) : ''; $keywords_json = isset($_POST['keywords']) ? sanitize_text_field(wp_unslash($_POST['keywords'])) : ''; if (empty($topic)) { wp_send_json_error('Topic/theme is required'); return; } $keywords = json_decode($keywords_json, true); if (json_last_error() !== JSON_ERROR_NONE || !is_array($keywords) || empty($keywords)) { wp_send_json_error('Invalid or empty keywords data'); return; } // Create the plan $plan_id = $this->database->create_plan($topic, 'Plan created from ' . count($keywords) . ' keywords'); if (!$plan_id) { wp_send_json_error('Failed to create plan'); return; } // Generate article titles from keywords using AI $articles_data = $this->generate_titles_from_keywords($topic, $keywords); if (is_wp_error($articles_data)) { // Fallback: use keywords directly as titles $articles_data = array(); foreach ($keywords as $kw) { $primary = $kw['primary'] ?? ''; $secondary = $kw['secondary'] ?? array(); $all_keywords = array($primary); if (!empty($secondary)) { $all_keywords = array_merge($all_keywords, $secondary); } $articles_data[] = array( 'title' => ucwords($primary), 'outline' => 'Article about: ' . $primary, 'keywords' => implode(', ', $all_keywords) ); } } // Create plan items $created_count = 0; foreach ($articles_data as $article) { $item_id = $this->database->add_plan_item($plan_id, array( 'title' => $article['title'], 'outline' => $article['outline'] ?? '', 'keywords' => $article['keywords'], 'status' => 'planned' )); if ($item_id) { $created_count++; } } wp_send_json_success(array( 'plan_id' => $plan_id, 'article_count' => $created_count )); } /** * Generate article titles from keywords using AI */ private function generate_titles_from_keywords($theme, $keywords) { $keywords_list = array(); foreach ($keywords as $kw) { $primary = $kw['primary'] ?? ''; $secondary = $kw['secondary'] ?? array(); $all_keywords = array($primary); if (!empty($secondary)) { $all_keywords = array_merge($all_keywords, $secondary); } $keywords_list[] = array( 'primary' => $primary, 'all' => implode(', ', $all_keywords) ); } $system_prompt = 'You are an SEO expert and content strategist. Generate engaging, SEO-optimized article titles.'; $user_prompt = "Theme: {$theme}\n\n"; $user_prompt .= "For each keyword below, generate ONE article title that:\n"; $user_prompt .= "- Includes the primary keyword (preferably near the beginning)\n"; $user_prompt .= "- Is compelling and click-worthy\n"; $user_prompt .= "- Is under 60 characters\n"; $user_prompt .= "- Would rank well for that keyword\n\n"; $user_prompt .= "Also provide a brief 1-sentence outline for each.\n\n"; $user_prompt .= "Keywords:\n"; foreach ($keywords_list as $index => $kw) { $user_prompt .= ($index + 1) . ". Primary: \"{$kw['primary']}\""; if ($kw['all'] !== $kw['primary']) { $user_prompt .= " (also target: {$kw['all']})"; } $user_prompt .= "\n"; } $user_prompt .= "\nReturn as JSON array:\n"; $user_prompt .= '[{"title": "...", "outline": "...", "keywords": "primary, secondary1, secondary2"}, ...]'; $result = $this->openai_client->generate_json($system_prompt, $user_prompt, 0.7); if (is_wp_error($result)) { return $result; } // Validate we got the right number of results if (!is_array($result) || count($result) !== count($keywords)) { // Try to match what we can if (is_array($result)) { // Fill in missing with original keywords while (count($result) < count($keywords)) { $index = count($result); $kw = $keywords[$index]; $primary = $kw['primary'] ?? ''; $secondary = $kw['secondary'] ?? array(); $all = array_merge(array($primary), $secondary); $result[] = array( 'title' => ucwords($primary), 'outline' => 'Article about: ' . $primary, 'keywords' => implode(', ', $all) ); } } } // Ensure keywords are preserved foreach ($result as $index => &$article) { if (isset($keywords[$index])) { $kw = $keywords[$index]; $primary = $kw['primary'] ?? ''; $secondary = $kw['secondary'] ?? array(); $all = array_merge(array($primary), $secondary); $article['keywords'] = implode(', ', $all); } } return $result; } public function ajax_get_plans() { check_ajax_referer('aigh_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error('Unauthorized'); return; } $plans = $this->database->get_all_plans(); wp_send_json_success($plans); } public function ajax_get_plan_items() { check_ajax_referer('aigh_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error('Unauthorized'); return; } $plan_id = isset($_POST['plan_id']) ? absint($_POST['plan_id']) : 0; $items = $this->database->get_plan_items($plan_id); wp_send_json_success($items); } public function ajax_update_plan_item() { check_ajax_referer('aigh_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error('Unauthorized'); return; } $item_id = isset($_POST['item_id']) ? absint($_POST['item_id']) : 0; $data = array(); if (isset($_POST['title'])) { $data['title'] = sanitize_text_field(wp_unslash($_POST['title'])); } if (isset($_POST['outline'])) { $data['outline'] = sanitize_textarea_field(wp_unslash($_POST['outline'])); } if (isset($_POST['category'])) { $data['category'] = sanitize_text_field(wp_unslash($_POST['category'])); } if (isset($_POST['keywords'])) { $data['keywords'] = sanitize_text_field(wp_unslash($_POST['keywords'])); } if (isset($_POST['scheduled_date'])) { $data['scheduled_date'] = sanitize_text_field(wp_unslash($_POST['scheduled_date'])); } $result = $this->database->update_plan_item($item_id, $data); if ($result) { wp_send_json_success('Item updated'); } else { wp_send_json_error('Update failed'); } } public function ajax_delete_plan_item() { check_ajax_referer('aigh_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error('Unauthorized'); return; } $item_id = isset($_POST['item_id']) ? absint($_POST['item_id']) : 0; $result = $this->database->delete_plan_item($item_id); if ($result) { wp_send_json_success('Item deleted'); } else { wp_send_json_error('Delete failed'); } } /** * Update plan default settings (workflow, tone, length) */ public function ajax_update_plan_settings() { check_ajax_referer('aigh_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error('Unauthorized'); return; } $plan_id = isset($_POST['plan_id']) ? absint($_POST['plan_id']) : 0; if (!$plan_id) { wp_send_json_error('Plan ID is required'); return; } $settings = array(); if (isset($_POST['default_workflow_id'])) { $settings['default_workflow_id'] = absint($_POST['default_workflow_id']); } if (isset($_POST['default_tone'])) { $settings['default_tone'] = sanitize_text_field(wp_unslash($_POST['default_tone'])); } if (isset($_POST['default_length'])) { $settings['default_length'] = sanitize_text_field(wp_unslash($_POST['default_length'])); } $result = $this->database->update_plan_settings($plan_id, $settings); if ($result !== false) { wp_send_json_success('Settings saved'); } else { wp_send_json_error('Failed to save settings'); } } /** * Apply keywords from CSV to plan items */ public function ajax_apply_keywords() { check_ajax_referer('aigh_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error('Unauthorized'); return; } $plan_id = isset($_POST['plan_id']) ? absint($_POST['plan_id']) : 0; $keywords_json = isset($_POST['keywords']) ? sanitize_text_field(wp_unslash($_POST['keywords'])) : ''; aigh_log(' apply_keywords called for plan_id: ' . $plan_id); aigh_log(' keywords_json: ' . $keywords_json); if (empty($plan_id)) { wp_send_json_error('Plan ID is required'); return; } $keywords = json_decode($keywords_json, true); if (json_last_error() !== JSON_ERROR_NONE || !is_array($keywords)) { wp_send_json_error('Invalid keywords data: ' . json_last_error_msg()); return; } aigh_log(' Parsed ' . count($keywords) . ' keywords'); // Get all plan items $items = $this->database->get_plan_items($plan_id); if (empty($items)) { wp_send_json_error('No items found in this plan'); return; } aigh_log(' Found ' . count($items) . ' plan items'); $matched = 0; $assignments = array(); // Track which item got which keywords foreach ($keywords as $index => $keyword_data) { $primary = sanitize_text_field($keyword_data['primary'] ?? ''); $secondary = array_map('sanitize_text_field', $keyword_data['secondary'] ?? array()); $article_title = sanitize_text_field($keyword_data['article_title'] ?? ''); if (empty($primary)) { continue; } // Build keyword string (primary + secondary) $all_keywords = array($primary); if (!empty($secondary)) { $all_keywords = array_merge($all_keywords, $secondary); } $keywords_string = implode(', ', $all_keywords); $matched_item = null; $match_reason = ''; // Strategy 1: If article_title is specified, try to match if (!empty($article_title)) { $article_title_lower = strtolower($article_title); foreach ($items as $item) { $item_title_lower = strtolower($item->title); // Try multiple matching strategies if ($item_title_lower === $article_title_lower) { // Exact match $matched_item = $item; $match_reason = 'exact title match'; break; } elseif (strpos($item_title_lower, $article_title_lower) !== false) { // Item title contains CSV title $matched_item = $item; $match_reason = 'title contains'; break; } elseif (strpos($article_title_lower, $item_title_lower) !== false) { // CSV title contains item title $matched_item = $item; $match_reason = 'csv contains title'; break; } elseif (similar_text($item_title_lower, $article_title_lower) > strlen($article_title_lower) * 0.6) { // Fuzzy match - 60% similarity $matched_item = $item; $match_reason = 'fuzzy match'; break; } } } // Strategy 2: If no match yet and no article_title specified, assign by index if (!$matched_item && empty($article_title)) { if (isset($items[$index])) { $matched_item = $items[$index]; $match_reason = 'sequential assignment'; } } // Strategy 3: If still no match, try keyword-based matching if (!$matched_item) { $primary_words = explode(' ', strtolower($primary)); foreach ($items as $item) { $title_lower = strtolower($item->title); $outline_lower = strtolower($item->outline ?? ''); $combined = $title_lower . ' ' . $outline_lower; // Check if any significant word from keyword appears foreach ($primary_words as $word) { if (strlen($word) > 3 && strpos($combined, $word) !== false) { $matched_item = $item; $match_reason = "keyword word '$word' found"; break 2; } } } } if ($matched_item) { // Get current keywords and append (don't duplicate) $existing = $matched_item->keywords ?? ''; $existing_array = $existing ? array_map('trim', explode(',', $existing)) : array(); // Add new keywords that don't already exist foreach ($all_keywords as $kw) { $kw_lower = strtolower($kw); $found = false; foreach ($existing_array as $ex) { if (strtolower($ex) === $kw_lower) { $found = true; break; } } if (!$found) { $existing_array[] = $kw; } } $new_keywords = implode(', ', $existing_array); $update_result = $this->database->update_plan_item($matched_item->id, array( 'keywords' => $new_keywords )); aigh_log(" Updated item {$matched_item->id} ({$matched_item->title}) with keywords: {$new_keywords} - Result: " . ($update_result !== false ? 'OK' : 'FAILED')); // Update the item in our array so subsequent matches see the new keywords $matched_item->keywords = $new_keywords; $matched++; $assignments[] = array( 'keyword' => $primary, 'article' => $matched_item->title, 'reason' => $match_reason ); } else { aigh_log(" No match for keyword: {$primary}"); $assignments[] = array( 'keyword' => $primary, 'article' => null, 'reason' => 'no match found' ); } } $unassigned = count($keywords) - $matched; wp_send_json_success(array( 'matched' => $matched, 'unassigned' => $unassigned, 'total_keywords' => count($keywords), 'total_items' => count($items), 'assignments' => $assignments )); } /** * Apply keywords to plan items sequentially (keyword 1 -> article 1, etc.) * Used when creating a new plan with CSV keywords */ public function ajax_apply_keywords_sequential() { check_ajax_referer('aigh_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error('Unauthorized'); return; } $plan_id = isset($_POST['plan_id']) ? absint($_POST['plan_id']) : 0; $keywords_json = isset($_POST['keywords']) ? sanitize_text_field(wp_unslash($_POST['keywords'])) : ''; aigh_log(' ajax_apply_keywords_sequential called'); aigh_log(' Plan ID: ' . $plan_id); aigh_log(' Keywords JSON: ' . $keywords_json); if (empty($plan_id)) { wp_send_json_error('Plan ID is required'); return; } $keywords = json_decode($keywords_json, true); if (json_last_error() !== JSON_ERROR_NONE || !is_array($keywords)) { wp_send_json_error('Invalid keywords data'); return; } // Get plan info for context $plan = $this->database->get_plan($plan_id); $plan_topic = $plan ? $plan->topic : ''; // Get plan items $items = $this->database->get_plan_items($plan_id); if (empty($items)) { wp_send_json_error('No items found in plan'); return; } aigh_log(' Found ' . count($items) . ' items in plan'); foreach ($items as $idx => $itm) { aigh_log(" Item[$idx]: ID={$itm->id}, Title={$itm->title}, Current Keywords: " . ($itm->keywords ?? 'NONE')); } $applied = 0; // Assign keywords sequentially: keyword[0] -> item[0], keyword[1] -> item[1], etc. foreach ($keywords as $index => $keyword_data) { if (!isset($items[$index])) { break; // No more articles to assign to } $item = $items[$index]; $primary = sanitize_text_field($keyword_data['primary'] ?? ''); $secondary = array_map('sanitize_text_field', $keyword_data['secondary'] ?? array()); $provided_title = sanitize_text_field($keyword_data['article_title'] ?? ''); if (empty($primary)) { continue; } // Build keywords string $all_keywords = array($primary); if (!empty($secondary)) { $all_keywords = array_merge($all_keywords, $secondary); } $keywords_string = implode(', ', $all_keywords); // Determine the title to use // Priority: 1) CSV provided title, 2) Generate from keyword if (!empty($provided_title)) { $new_title = $provided_title; aigh_log(" Using provided title from CSV: {$new_title}"); } else { $new_title = $this->generate_keyword_title($primary, $plan_topic, $item->title); if (!empty($new_title) && !is_wp_error($new_title)) { aigh_log(" Generated new title: {$new_title}"); } } // Update the plan item with keywords AND title $update_data = array( 'keywords' => $keywords_string ); if (!empty($new_title) && !is_wp_error($new_title)) { $update_data['title'] = $new_title; } $result = $this->database->update_plan_item($item->id, $update_data); aigh_log(" Updated item {$item->id} with keywords: {$keywords_string}"); aigh_log(" Update result: " . ($result !== false ? 'SUCCESS' : 'FAILED')); $applied++; } wp_send_json_success(array( 'applied' => $applied, 'total_keywords' => count($keywords), 'total_items' => count($items) )); } /** * Generate an SEO-optimized title for a keyword */ private function generate_keyword_title($keyword, $topic, $original_title) { $system_prompt = "You are an SEO specialist. Generate ONE engaging, SEO-optimized blog post title."; $user_prompt = "Generate a single blog post title that:\n"; $user_prompt .= "- MUST include the exact keyword: \"{$keyword}\"\n"; $user_prompt .= "- Is related to the topic: {$topic}\n"; $user_prompt .= "- Is engaging and click-worthy\n"; $user_prompt .= "- Is 50-70 characters long\n\n"; $user_prompt .= "Original title for context: {$original_title}\n\n"; $user_prompt .= "Return ONLY the title, no quotes, no explanation:"; $result = $this->openai_client->generate_text($system_prompt, $user_prompt, 0.7, 100); if (is_wp_error($result)) { return $original_title; } // Clean up the result $title = trim($result); $title = trim($title, '"\''); return $title; } public function ajax_generate_article() { check_ajax_referer('aigh_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error('Unauthorized'); return; } $item_id = isset($_POST['item_id']) ? absint($_POST['item_id']) : 0; $workflow_id = isset($_POST['workflow_id']) ? absint($_POST['workflow_id']) : 0; $tone = isset($_POST['tone']) ? sanitize_text_field(wp_unslash($_POST['tone'])) : 'professional'; $length = isset($_POST['length']) ? sanitize_text_field(wp_unslash($_POST['length'])) : 'medium'; $custom_style = isset($_POST['custom_style']) ? sanitize_textarea_field(wp_unslash($_POST['custom_style'])) : ''; $item = $this->database->get_plan_item($item_id); if (!$item) { wp_send_json_error('Item not found'); return; } // If workflow is selected, use pipeline execution if ($workflow_id) { $content = $this->execute_workflow_for_plan_item($item, $workflow_id, $tone, $custom_style, $length); if (is_wp_error($content)) { wp_send_json_error($content->get_error_message()); return; } } else { // Use legacy content generator with tone, length, and custom style $content = $this->content_generator->generate_article($item, $tone, $custom_style, $length); if (is_wp_error($content)) { wp_send_json_error($content->get_error_message()); return; } } // Strip markdown code block wrappers if present $content = $this->strip_code_blocks($content); $post_id = $this->content_publisher->publish_article($item, $content); if (is_wp_error($post_id)) { wp_send_json_error($post_id->get_error_message()); return; } $this->database->update_plan_item($item_id, array( 'status' => 'published', 'post_id' => $post_id )); wp_send_json_success(array( 'post_id' => $post_id, 'edit_url' => get_edit_post_link($post_id, 'raw'), 'workflow_used' => $workflow_id > 0 )); } public function ajax_generate_batch() { check_ajax_referer('aigh_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error('Unauthorized'); return; } $plan_id = isset($_POST['plan_id']) ? absint($_POST['plan_id']) : 0; $workflow_id = isset($_POST['workflow_id']) ? absint($_POST['workflow_id']) : 0; $tone = isset($_POST['tone']) ? sanitize_text_field(wp_unslash($_POST['tone'])) : 'professional'; $custom_style = isset($_POST['custom_style']) ? sanitize_textarea_field(wp_unslash($_POST['custom_style'])) : ''; $items = $this->database->get_plan_items($plan_id, 'planned'); $results = array(); $errors = array(); $index = 0; foreach ($items as $item) { // If workflow is selected, use pipeline execution if ($workflow_id) { $content = $this->execute_workflow_for_plan_item($item, $workflow_id, $tone, $custom_style); } else { // Use legacy content generator with tone and custom style $content = $this->content_generator->generate_article($item, $tone, $custom_style); } if (is_wp_error($content)) { $errors[] = $item->title . ': ' . $content->get_error_message(); $index++; continue; } // Strip markdown code block wrappers if present $content = $this->strip_code_blocks($content); $post_id = $this->content_publisher->publish_article($item, $content); if (is_wp_error($post_id)) { $errors[] = $item->title . ': ' . $post_id->get_error_message(); $index++; continue; } $this->database->update_plan_item($item->id, array( 'status' => 'published', 'post_id' => $post_id )); $results[] = array( 'title' => $item->title, 'post_id' => $post_id ); $index++; } wp_send_json_success(array( 'generated' => count($results), 'errors' => $errors, 'results' => $results, 'workflow_used' => $workflow_id > 0 )); } public function ajax_quick_generate() { check_ajax_referer('aigh_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error('Unauthorized'); return; } $topic = isset($_POST['topic']) ? sanitize_textarea_field(wp_unslash($_POST['topic'])) : ''; $persona = isset($_POST['persona']) ? sanitize_text_field(wp_unslash($_POST['persona'])) : ''; $length = isset($_POST['length']) ? sanitize_text_field(wp_unslash($_POST['length'])) : 'medium'; $tone = isset($_POST['tone']) ? sanitize_text_field(wp_unslash($_POST['tone'])) : 'professional'; $custom_style = isset($_POST['custom_style']) ? sanitize_textarea_field(wp_unslash($_POST['custom_style'])) : ''; $research_urls = isset($_POST['research_urls']) ? sanitize_textarea_field(wp_unslash($_POST['research_urls'])) : ''; $use_web_search = isset($_POST['use_web_search']) && $_POST['use_web_search'] === 'true'; $workflow_id = isset($_POST['workflow_id']) ? absint($_POST['workflow_id']) : 0; $seo_keywords = isset($_POST['seo_keywords']) ? sanitize_text_field(wp_unslash($_POST['seo_keywords'])) : ''; if (empty($topic)) { wp_send_json_error('Please provide a topic'); return; } // If a workflow is selected, use the workflow execution path if ($workflow_id) { $this->execute_workflow_generation($workflow_id, $topic, $length, $tone, $research_urls, $seo_keywords, $custom_style, $persona); return; } // Otherwise, use the default generation (existing code) // Build pipeline steps $steps = array(); // Step 1: Input Topic - pass custom_style if available $input_config = array( 'topic' => $topic, 'persona' => $persona, 'length' => $length, 'tone' => $tone, 'seo_keywords' => $seo_keywords ); // If custom style is provided, use it instead of preset tone if (!empty($custom_style)) { $input_config['custom_style'] = $custom_style; } $input_step = AIGH_Step_Registry::create_step('input_topic', $input_config); if (is_wp_error($input_step)) { wp_send_json_error($input_step->get_error_message()); return; } $steps[] = $input_step; // Web Search (if enabled) $web_search_used = false; $web_search_sources = 0; $web_search_errors = array(); $web_research_brief = ''; if ($use_web_search) { aigh_log('AJAX: Web search enabled - calling full_web_research()'); $web_result = $this->web_research->full_web_research($topic); if (is_wp_error($web_result)) { aigh_log('AJAX: Web search FAILED: ' . $web_result->get_error_message()); $web_search_errors[] = $web_result->get_error_message(); } else { aigh_log('AJAX: Web search SUCCESS - sources: ' . $web_result['sources_count']); $web_search_used = true; $web_search_sources = $web_result['sources_count']; $web_search_errors = $web_result['errors'] ?? array(); $web_research_brief = $web_result['brief']; } } // Step 2: Research URLs (optional) if (!empty($research_urls)) { $research_step = AIGH_Step_Registry::create_step('research_urls', array( 'urls' => $research_urls, 'skip_on_empty' => true )); if (is_wp_error($research_step)) { wp_send_json_error($research_step->get_error_message()); return; } $steps[] = $research_step; } // Step 3: Generate Draft $generate_step = AIGH_Step_Registry::create_step('generate_draft'); if (is_wp_error($generate_step)) { wp_send_json_error($generate_step->get_error_message()); return; } $steps[] = $generate_step; // Execute pipeline (no save step - Quick Generate shows content first) $context = new AIGH_Pipeline_Context(); $engine = new AIGH_Pipeline_Engine(); // Inject web search brief into context if available if ($web_search_used && !empty($web_research_brief)) { $context->set('research_brief', $web_research_brief); $context->set('research_sources_count', $web_search_sources); } $result = $engine->execute($steps, $context); if (is_wp_error($result)) { wp_send_json_error($result->get_error_message()); return; } // Get content from context $content = $result->get('content', ''); if (empty($content)) { wp_send_json_error('No content was generated'); return; } // Build response $sources_count = $result->get('research_sources_count', 0); $research_errors = $result->get('research_errors', array()); if ($web_search_used) { $sources_count = max($sources_count, $web_search_sources); $research_errors = array_merge($research_errors, $web_search_errors); } wp_send_json_success(array( 'content' => $content, 'research_used' => $sources_count > 0 || $web_search_used, 'web_search_used' => $web_search_used, 'sources_count' => $sources_count, 'errors' => $research_errors, 'workflow_used' => false )); } /** * Execute a workflow for content generation * * @param int $workflow_id The workflow to execute * @param string $topic The content topic * @param string $length Content length preference * @param string $tone Content tone preference * @param string $research_urls Optional research URLs * @param string $seo_keywords Optional SEO keywords (comma-separated) */ private function execute_workflow_generation($workflow_id, $topic, $length, $tone, $research_urls = '', $seo_keywords = '', $custom_style = '', $persona = '') { // Load the workflow $pipeline_db = new AIGH_Pipeline_Database(); $workflow = $pipeline_db->get_pipeline($workflow_id); if (!$workflow) { wp_send_json_error('Workflow not found'); return; } $steps_config = $workflow->steps_config; if (empty($steps_config)) { wp_send_json_error('Workflow has no steps configured'); return; } // Create pipeline steps from config $steps = array(); foreach ($steps_config as $step_config) { $step_type = $step_config['step_type']; $config = $step_config['config'] ?? array(); // For research_urls step, inject user's URLs if provided if ($step_type === 'research_urls' && !empty($research_urls)) { $config['urls'] = $research_urls; } $step = AIGH_Step_Registry::create_step($step_type, $config); if (is_wp_error($step)) { wp_send_json_error('Error creating step ' . $step_type . ': ' . $step->get_error_message()); return; } $steps[] = $step; } // Create context with initial inputs (topic, length, tone, seo_keywords) $context = new AIGH_Pipeline_Context(); $context->set('topic', $topic); $context->set('length', $length); $context->set('tone', $tone); $context->set_metadata('workflow_id', $workflow_id); $context->set_metadata('workflow_name', $workflow->name); // Set persona in context if provided if (!empty($persona)) { $context->set('persona', $persona); } // Set SEO keywords in context if provided if (!empty($seo_keywords)) { $context->set('seo_keywords', $seo_keywords); } // Set custom style in context if provided if (!empty($custom_style)) { $context->set('custom_style', $custom_style); } // Also set research_urls in context if provided if (!empty($research_urls)) { $context->set('research_urls', $research_urls); } $engine = new AIGH_Pipeline_Engine(); $result = $engine->execute($steps, $context); if (is_wp_error($result)) { wp_send_json_error('Workflow execution failed: ' . $result->get_error_message()); return; } // Get content from context $content = $result->get('content', ''); if (empty($content)) { wp_send_json_error('Workflow completed but no content was generated'); return; } // Build response wp_send_json_success(array( 'content' => $content, 'workflow_used' => true, 'workflow_name' => $workflow->name, 'steps_executed' => count($steps), 'research_used' => $result->get('research_sources_count', 0) > 0, 'sources_count' => $result->get('research_sources_count', 0), 'refinements_applied' => $result->get('refinement_history', array()), 'errors' => $result->get_errors() )); } /** * Execute a workflow for a Content Planner item * * @param object $item The plan item object * @param int $workflow_id The workflow to execute * @param string $tone The writing tone/style * @return string|WP_Error The generated content or error */ private function execute_workflow_for_plan_item($item, $workflow_id, $tone = 'professional', $custom_style = '', $length = 'medium') { // Debug: Log what keywords we have aigh_log(' execute_workflow_for_plan_item called'); aigh_log(' Item ID: ' . $item->id); aigh_log(' Item title: ' . $item->title); aigh_log(' Item keywords from DB: ' . ($item->keywords ?? 'NULL/EMPTY')); aigh_log(' Custom style: ' . (!empty($custom_style) ? 'PROVIDED' : 'EMPTY')); aigh_log(' Length: ' . $length); // Load the workflow $pipeline_db = new AIGH_Pipeline_Database(); $workflow = $pipeline_db->get_pipeline($workflow_id); if (!$workflow) { return new WP_Error('workflow_not_found', 'Workflow not found'); } $steps_config = $workflow->steps_config; if (empty($steps_config)) { return new WP_Error('no_steps', 'Workflow has no steps configured'); } // Create pipeline steps from config $steps = array(); foreach ($steps_config as $step_config) { $step_type = $step_config['step_type']; $config = $step_config['config'] ?? array(); $step = AIGH_Step_Registry::create_step($step_type, $config); if (is_wp_error($step)) { return new WP_Error('step_creation_failed', 'Error creating step ' . $step_type . ': ' . $step->get_error_message()); } $steps[] = $step; } // Build a rich topic from the plan item $topic_parts = array(); $topic_parts[] = $item->title; if (!empty($item->outline)) { $topic_parts[] = "\n\nOutline:\n" . $item->outline; } if (!empty($item->keywords)) { $topic_parts[] = "\n\nKeywords to incorporate: " . $item->keywords; } $rich_topic = implode('', $topic_parts); // Create context with plan item data $context = new AIGH_Pipeline_Context(); $context->set('topic', $rich_topic); $context->set('title', $item->title); $context->set('outline', $item->outline ?? ''); $context->set('keywords', $item->keywords ?? ''); $context->set('seo_keywords', $item->keywords ?? ''); // For generate_draft step $context->set('category', $item->category ?? ''); $context->set('length', $length); $context->set('tone', $tone); // Set custom style if provided if (!empty($custom_style)) { $context->set('custom_style', $custom_style); } $context->set_metadata('workflow_id', $workflow_id); $context->set_metadata('workflow_name', $workflow->name); $context->set_metadata('plan_item_id', $item->id); $engine = new AIGH_Pipeline_Engine(); $result = $engine->execute($steps, $context); if (is_wp_error($result)) { return new WP_Error('workflow_failed', 'Workflow execution failed: ' . $result->get_error_message()); } // Get content from context $content = $result->get('content', ''); if (empty($content)) { return new WP_Error('no_content', 'Workflow completed but no content was generated'); } return $content; } public function ajax_save_draft() { check_ajax_referer('aigh_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error('Unauthorized'); return; } $title = isset($_POST['title']) ? sanitize_text_field(wp_unslash($_POST['title'])) : ''; $content = isset($_POST['content']) ? wp_kses_post(wp_unslash($_POST['content'])) : ''; $attachment_id = isset($_POST['attachment_id']) ? intval($_POST['attachment_id']) : 0; if (empty($title)) { wp_send_json_error('Title is required'); return; } $post_data = array( 'post_title' => $title, 'post_content' => $content, 'post_status' => 'draft', 'post_author' => get_current_user_id(), 'post_type' => 'post' ); $post_id = wp_insert_post($post_data, true); if (is_wp_error($post_id)) { wp_send_json_error($post_id->get_error_message()); return; } update_post_meta($post_id, '_aigh_generated', 1); // Set featured image if attachment provided if ($attachment_id > 0) { set_post_thumbnail($post_id, $attachment_id); } wp_send_json_success(array( 'post_id' => $post_id, 'edit_url' => get_edit_post_link($post_id, 'raw') )); } public function ajax_refine_article() { check_ajax_referer('aigh_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error('Unauthorized'); return; } $original_content = isset($_POST['content']) ? wp_kses_post(wp_unslash($_POST['content'])) : ''; $refinement_type = isset($_POST['refinement_type']) ? sanitize_text_field(wp_unslash($_POST['refinement_type'])) : ''; $custom_feedback = isset($_POST['custom_feedback']) ? sanitize_textarea_field(wp_unslash($_POST['custom_feedback'])) : ''; $use_research = isset($_POST['use_research']) && $_POST['use_research'] === 'true'; if (empty($original_content)) { wp_send_json_error('Original content is required'); return; } // Map old refinement types to new pipeline step types $step_type_map = array( 'add_detail' => 'refine_add_detail', 'add_examples' => 'refine_add_examples', 'add_statistics' => 'refine_add_statistics', 'improve_intro' => 'refine_improve_intro', 'improve_conclusion' => 'refine_improve_conclusion', 'improve_seo' => 'refine_improve_seo', 'make_longer' => 'refine_make_longer', 'make_shorter' => 'refine_make_shorter', 'custom' => 'refine_custom' ); $step_type = isset($step_type_map[$refinement_type]) ? $step_type_map[$refinement_type] : null; if (!$step_type) { wp_send_json_error('Invalid refinement type: ' . $refinement_type); return; } // Build step config $step_config = array( 'use_research' => $use_research ); // Add custom feedback if this is a custom refinement if ($refinement_type === 'custom') { if (empty($custom_feedback)) { wp_send_json_error('Custom feedback is required for custom refinement'); return; } $step_config['feedback'] = $custom_feedback; } // Create the refinement step $refinement_step = AIGH_Step_Registry::create_step($step_type, $step_config); if (is_wp_error($refinement_step)) { wp_send_json_error($refinement_step->get_error_message()); return; } // Create context with existing content $context = new AIGH_Pipeline_Context(array( 'content' => $original_content )); // Execute single refinement step via engine $engine = new AIGH_Pipeline_Engine(); $result = $engine->execute(array($refinement_step), $context); if (is_wp_error($result)) { wp_send_json_error($result->get_error_message()); return; } // Get refined content $refined_content = $result->get('content', ''); if (empty($refined_content)) { wp_send_json_error('Refinement produced no content'); return; } // Build response matching original format $response = array( 'content' => $refined_content ); // Add research info if used if ($use_research) { $response['research_used'] = true; $response['sources_analyzed'] = $result->get('research_sources_count', 0); } wp_send_json_success($response); } private function get_refinement_description($type) { $descriptions = array( 'add_detail' => 'Add more detail and depth to the article', 'add_examples' => 'Find relevant examples, case studies, and real-world scenarios', 'improve_intro' => 'Improve the introduction with current hooks and data', 'improve_conclusion' => 'Strengthen conclusion with current insights', 'improve_seo' => 'Find current SEO best practices and trending keywords', 'add_statistics' => 'Find current statistics and data points', ); return $descriptions[$type] ?? 'Improve the article'; } /** * Strip markdown code block wrappers from content */ private function strip_code_blocks($content) { if (empty($content)) { return $content; } // Remove ```html or ``` from start $content = preg_replace('/^```(?:html)?\s*/i', '', $content); // Remove ``` from end $content = preg_replace('/\s*```$/i', '', $content); return trim($content); } public function ajax_suggest_titles() { check_ajax_referer('aigh_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error('Unauthorized'); return; } $topic = isset($_POST['topic']) ? sanitize_textarea_field(wp_unslash($_POST['topic'])) : ''; $seo_keywords = isset($_POST['seo_keywords']) ? sanitize_text_field(wp_unslash($_POST['seo_keywords'])) : ''; if (empty($topic)) { wp_send_json_error('Topic is required'); return; } $titles = $this->content_generator->suggest_title($topic, $seo_keywords); if (is_wp_error($titles)) { wp_send_json_error($titles->get_error_message()); return; } if (!is_array($titles)) { wp_send_json_error('Invalid response format'); return; } wp_send_json_success(array('titles' => $titles)); } /** * Test pipeline execution (temporary development endpoint) * * Call via: admin-ajax.php?action=aigh_test_pipeline&topic=Your+Topic&nonce=XXX */ public function ajax_test_pipeline() { check_ajax_referer('aigh_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error('Unauthorized'); return; } $topic = isset($_POST['topic']) ? sanitize_textarea_field(wp_unslash($_POST['topic'])) : 'Test article about AI writing assistants'; $length = isset($_POST['length']) ? sanitize_text_field(wp_unslash($_POST['length'])) : 'short'; $tone = isset($_POST['tone']) ? sanitize_text_field(wp_unslash($_POST['tone'])) : 'professional'; $research_urls = isset($_POST['research_urls']) ? sanitize_textarea_field(wp_unslash($_POST['research_urls'])) : ''; $refinement_step = isset($_POST['refinement_step']) ? sanitize_text_field(wp_unslash($_POST['refinement_step'])) : ''; // Build pipeline steps dynamically $steps = array(); // Step 1: Input Topic (always) $steps[] = AIGH_Step_Registry::create_step('input_topic', array( 'topic' => $topic, 'length' => $length, 'tone' => $tone )); // Step 2: Research URLs (optional - only if URLs provided) if (!empty($research_urls)) { $steps[] = AIGH_Step_Registry::create_step('research_urls', array( 'urls' => $research_urls, 'skip_on_empty' => true )); } // Step 3: Generate Draft (always) $steps[] = AIGH_Step_Registry::create_step('generate_draft'); // Step 4: Refinement (optional - only if specified) if (!empty($refinement_step) && AIGH_Step_Registry::is_registered($refinement_step)) { $refinement_config = array( 'use_research' => !empty($research_urls) // Use research context if available ); $steps[] = AIGH_Step_Registry::create_step($refinement_step, $refinement_config); } // Step 5: Save Draft (always) $steps[] = AIGH_Step_Registry::create_step('save_draft', array( 'post_status' => 'draft' )); // Validate steps were created foreach ($steps as $index => $step) { if (is_wp_error($step)) { wp_send_json_error('Failed to create step ' . $index . ': ' . $step->get_error_message()); return; } } // Create context and engine $context = new AIGH_Pipeline_Context(); $engine = new AIGH_Pipeline_Engine(); // Execute pipeline $result = $engine->execute($steps, $context); if (is_wp_error($result)) { wp_send_json_error(array( 'message' => $result->get_error_message(), 'code' => $result->get_error_code() )); return; } // Return success with results wp_send_json_success(array( 'message' => 'Pipeline executed successfully!', 'post_id' => $result->get('post_id'), 'edit_url' => $result->get('edit_url'), 'post_title' => $result->get('post_title'), 'generated_at' => $result->get('generated_at'), 'research_used' => !empty($research_urls), 'research_sources_count' => $result->get('research_sources_count', 0), 'research_skipped' => $result->get('research_skipped', false), 'refinement_applied' => $result->get('refinement_applied', ''), 'refinement_history' => $result->get('refinement_history', array()), 'steps_executed' => count($steps), 'metadata' => $result->get_all_metadata(), 'errors' => $result->get_errors() )); } /** * Plugin activation - create tables and seed templates */ public function activate_plugin() { // Create main database tables $this->database->create_tables(); // Create pipeline database tables $pipeline_db = new AIGH_Pipeline_Database(); $pipeline_db->create_tables(); // Seed default workflow templates $this->seed_workflow_templates(); } /** * Seed default workflow templates */ private function seed_workflow_templates() { $pipeline_db = new AIGH_Pipeline_Database(); // Get existing templates to check by name $existing = $pipeline_db->get_all_pipelines(true, false); $existing_names = array(); if (!empty($existing)) { foreach ($existing as $template) { $existing_names[] = $template->name; } } // Helper function to check if template exists $template_exists = function($name) use ($existing_names) { return in_array($name, $existing_names); }; // Template 1: Quick Article if (!$template_exists('Quick Article')) { $pipeline_db->save_pipeline( 'Quick Article', array( array('step_type' => 'generate_draft', 'config' => array()) ), 'Fast article generation without research. Best for simple topics.', true // is_template ); } // Template 2: Research-Based (URLs) if (!$template_exists('Research-Based')) { $pipeline_db->save_pipeline( 'Research-Based', array( array('step_type' => 'research_urls', 'config' => array('skip_on_empty' => true)), array('step_type' => 'generate_draft', 'config' => array('use_research' => true)) ), 'Articles built from your provided source URLs.', true ); } // Template 3: Research Heavy - All 4 research steps if (!$template_exists('Research Heavy')) { $pipeline_db->save_pipeline( 'Research Heavy', array( array('step_type' => 'web_search', 'config' => array()), array('step_type' => 'find_statistics', 'config' => array()), array('step_type' => 'find_examples', 'config' => array()), array('step_type' => 'find_expert_quotes', 'config' => array()), array('step_type' => 'generate_draft', 'config' => array()) ), 'Maximum research: web search, statistics, examples, and expert quotes. Best for comprehensive, authoritative articles.', true ); } // Template 4: Quick with Stats if (!$template_exists('Quick with Stats')) { $pipeline_db->save_pipeline( 'Quick with Stats', array( array('step_type' => 'find_statistics', 'config' => array()), array('step_type' => 'generate_draft', 'config' => array()) ), 'Fast article generation backed by real statistics and data. Great for data-driven content.', true ); } // Template 5: Authority Article if (!$template_exists('Authority Article')) { $pipeline_db->save_pipeline( 'Authority Article', array( array('step_type' => 'find_expert_quotes', 'config' => array()), array('step_type' => 'find_examples', 'config' => array()), array('step_type' => 'generate_draft', 'config' => array()) ), 'Build credibility with expert quotes and real-world case studies. Perfect for thought leadership content.', true ); } } /** * Render Workflow Builder page */ public function render_workflow_builder_page() { if (!current_user_can('manage_options')) { return; } $api_key = get_option('aigh_api_key', ''); include AIGH_PLUGIN_DIR . 'includes/views/workflow-builder.php'; } /** * AJAX: Get workflows */ public function ajax_get_workflows() { check_ajax_referer('aigh_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error('Unauthorized'); return; } $templates_only = isset($_POST['templates_only']) && $_POST['templates_only'] === 'true'; $pipeline_db = new AIGH_Pipeline_Database(); $workflows = $pipeline_db->get_all_pipelines($templates_only, true); wp_send_json_success($workflows); } /** * AJAX: Get single workflow */ public function ajax_get_workflow() { check_ajax_referer('aigh_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error('Unauthorized'); return; } $workflow_id = isset($_POST['workflow_id']) ? absint($_POST['workflow_id']) : 0; if (!$workflow_id) { wp_send_json_error('Invalid workflow ID'); return; } $pipeline_db = new AIGH_Pipeline_Database(); $workflow = $pipeline_db->get_pipeline($workflow_id); if (!$workflow) { wp_send_json_error('Workflow not found'); return; } wp_send_json_success($workflow); } /** * AJAX: Save workflow */ public function ajax_save_workflow() { check_ajax_referer('aigh_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error('Unauthorized'); return; } $name = isset($_POST['name']) ? sanitize_text_field(wp_unslash($_POST['name'])) : ''; $description = isset($_POST['description']) ? sanitize_textarea_field(wp_unslash($_POST['description'])) : ''; $steps_config_json = isset($_POST['steps_config']) ? sanitize_text_field(wp_unslash($_POST['steps_config'])) : ''; $workflow_id = isset($_POST['workflow_id']) ? absint($_POST['workflow_id']) : 0; if (empty($name)) { wp_send_json_error('Workflow name is required'); return; } $steps_config = json_decode($steps_config_json, true); if (json_last_error() !== JSON_ERROR_NONE) { wp_send_json_error('Invalid steps configuration'); return; } if (empty($steps_config)) { wp_send_json_error('At least one step is required'); return; } // Validate steps $validation = AIGH_Step_Registry::validate_pipeline_config($steps_config); if (is_wp_error($validation)) { wp_send_json_error($validation->get_error_message()); return; } $pipeline_db = new AIGH_Pipeline_Database(); if ($workflow_id) { // Update existing workflow $result = $pipeline_db->update_pipeline($workflow_id, array( 'name' => $name, 'description' => $description, 'steps_config' => $steps_config )); if ($result) { wp_send_json_success(array('workflow_id' => $workflow_id)); } else { wp_send_json_error('Failed to update workflow'); } } else { // Create new workflow $new_id = $pipeline_db->save_pipeline($name, $steps_config, $description, false); if ($new_id) { wp_send_json_success(array('workflow_id' => $new_id)); } else { wp_send_json_error('Failed to create workflow'); } } } /** * AJAX: Delete workflow */ public function ajax_delete_workflow() { check_ajax_referer('aigh_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error('Unauthorized'); return; } $workflow_id = isset($_POST['workflow_id']) ? absint($_POST['workflow_id']) : 0; if (!$workflow_id) { wp_send_json_error('Invalid workflow ID'); return; } $pipeline_db = new AIGH_Pipeline_Database(); // Don't allow deleting templates $workflow = $pipeline_db->get_pipeline($workflow_id); if ($workflow && $workflow->is_template) { wp_send_json_error('Cannot delete template workflows'); return; } $result = $pipeline_db->delete_pipeline($workflow_id); if ($result) { wp_send_json_success('Workflow deleted'); } else { wp_send_json_error('Failed to delete workflow'); } } /** * AJAX: Get available steps from registry */ public function ajax_get_available_steps() { check_ajax_referer('aigh_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error('Unauthorized'); return; } $steps_by_category = AIGH_Step_Registry::get_steps_by_category(); wp_send_json_success($steps_by_category); } /** * AJAX: Analyze writing style samples */ public function ajax_analyze_style() { check_ajax_referer('aigh_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error('Unauthorized'); return; } $samples_json = isset($_POST['samples']) ? sanitize_text_field(wp_unslash($_POST['samples'])) : ''; $samples = json_decode($samples_json, true); if (empty($samples) || !is_array($samples)) { wp_send_json_error('No valid samples provided'); return; } // Extract text content from samples $combined_text = ''; foreach ($samples as $sample) { if ($sample['type'] === 'docx') { // For docx files, extract text using simple approach $binary = base64_decode($sample['content']); $text = $this->extract_docx_text($binary); $combined_text .= $text . "\n\n---\n\n"; } else { $combined_text .= $sample['content'] . "\n\n---\n\n"; } } // Limit text to prevent token overflow (roughly 8000 words) $words = explode(' ', $combined_text); if (count($words) > 8000) { $combined_text = implode(' ', array_slice($words, 0, 8000)); } // Use OpenAI to analyze the writing style $system_prompt = "You are an expert writing style analyst. Analyze writing samples and create concise style guides."; $user_prompt = "Analyze the following writing samples and create a concise style guide that captures the author's voice. Focus on: 1. Sentence structure (short/long, simple/complex) 2. Vocabulary level (casual/formal, technical/accessible) 3. Tone (serious/playful, authoritative/conversational) 4. Common phrases or patterns 5. Point of view (first person, second person, etc.) 6. Paragraph structure Provide your analysis as a practical writing instruction that an AI could follow to replicate this style. Keep it to 150-200 words maximum. Start directly with the instructions, no preamble. WRITING SAMPLES: {$combined_text}"; $result = $this->openai_client->generate_text($system_prompt, $user_prompt, 0.3, 500); if (is_wp_error($result)) { wp_send_json_error($result->get_error_message()); return; } wp_send_json_success(array( 'style_profile' => trim($result), 'samples_analyzed' => count($samples) )); } /** * Extract text from docx binary content */ private function extract_docx_text($binary) { // Create temp file $temp_file = wp_tempnam('docx'); file_put_contents($temp_file, $binary); $text = ''; // Try to extract using ZipArchive if (class_exists('ZipArchive')) { $zip = new ZipArchive(); if ($zip->open($temp_file) === true) { $xml = $zip->getFromName('word/document.xml'); if ($xml) { // Strip XML tags and get text $text = wp_strip_all_tags(str_replace('<', ' <', $xml)); $text = preg_replace('/\s+/', ' ', $text); } $zip->close(); } } // Clean up wp_delete_file($temp_file); return trim($text); } } AIGH_Core::get_instance();