serper_api_key = get_option('aigh_serper_api_key', ''); } protected function get_default_config() { return array( 'num_results' => 5, 'num_sources_to_fetch' => 3, 'query_patterns' => array( '{topic} case study', '{topic} example success story', '{topic} real world implementation' ) ); } public function execute($context) { $this->log_start($context); // Check for Serper API key if (empty($this->serper_api_key)) { $this->log_end($context, false); return $this->create_error('no_api_key', 'Serper API key not configured. Please add it in Settings.'); } // Get topic from context $topic = $this->get_from_context($context, 'topic', ''); if (empty($topic)) { $this->log_end($context, false); return $this->create_error('no_topic', 'No topic found in context'); } // Build search queries $queries = $this->build_examples_queries($topic); // Execute searches $all_results = array(); $num_results = $this->get_config_value('num_results', 5); foreach ($queries as $query) { $results = $this->search_web($query, $num_results); if (is_wp_error($results)) { continue; } // Merge results, avoiding duplicates foreach ($results as $key => $result) { if (!is_numeric($key)) { // Keep answer_box and knowledge_graph if (!isset($all_results[$key])) { $all_results[$key] = $result; } continue; } // Check for duplicate URLs $url = $result['url'] ?? ''; $is_duplicate = false; foreach ($all_results as $existing) { if (is_array($existing) && isset($existing['url']) && $existing['url'] === $url) { $is_duplicate = true; break; } } if (!$is_duplicate) { $all_results[] = $result; } } } if (empty($all_results)) { $this->set_in_context($context, 'examples_results', array()); $this->set_in_context($context, 'examples_brief', ''); $this->set_in_context($context, 'examples_skipped', true); $this->log_end($context, true); return $context; } // Store raw search results $this->set_in_context($context, 'examples_results', $all_results); // Fetch content from top results $num_sources = $this->get_config_value('num_sources_to_fetch', 3); $fetched_content = $this->fetch_top_results($all_results, $num_sources); // Create examples brief $brief = $this->create_examples_brief($fetched_content, $all_results, $topic); $this->set_in_context($context, 'examples_brief', $brief); // Append to existing research_brief if present $existing_brief = $this->get_from_context($context, 'research_brief', ''); if (!empty($existing_brief)) { $combined = $existing_brief . "\n\n" . $brief; $this->set_in_context($context, 'research_brief', $combined); } else { $this->set_in_context($context, 'research_brief', $brief); } $this->set_in_context($context, 'examples_skipped', false); $this->log_end($context, true); return $context; } /** * Build examples-focused search queries */ private function build_examples_queries($topic) { $queries = array(); $patterns = $this->get_config_value('query_patterns', array( '{topic} case study', '{topic} example success story', '{topic} real world implementation' )); // Extract core topic (shorter version for better search) $core_topic = $this->extract_core_topic($topic); foreach ($patterns as $pattern) { $query = str_replace('{topic}', $core_topic, $pattern); $queries[] = $query; } return $queries; } /** * Extract core topic for search queries */ private function extract_core_topic($topic) { // If topic is short enough, use as-is if (strlen($topic) <= 60) { return $topic; } // Extract key phrases for longer topics $words = str_word_count($topic, 1); $keywords = array_slice($words, 0, 6); return implode(' ', $keywords); } /** * Search the web using Serper API */ private function search_web($query, $num_results = 5) { $response = wp_remote_post('https://google.serper.dev/search', array( 'headers' => array( 'X-API-KEY' => $this->serper_api_key, 'Content-Type' => 'application/json' ), 'body' => json_encode(array( 'q' => $query, 'num' => min($num_results * 2, 10) )), 'timeout' => 30 )); if (is_wp_error($response)) { return $response; } $status_code = wp_remote_retrieve_response_code($response); if ($status_code !== 200) { return new WP_Error('serper_error', 'Serper API error: HTTP ' . $status_code); } $body = wp_remote_retrieve_body($response); $data = json_decode($body, true); if (json_last_error() !== JSON_ERROR_NONE) { return new WP_Error('json_error', 'Failed to parse Serper response'); } // Extract and filter results - prioritize case study sources $results = array(); // Domains to skip $skip_domains = array( 'pinterest.com', 'youtube.com', 'facebook.com', 'twitter.com', 'instagram.com', 'tiktok.com', 'reddit.com', 'quora.com' ); // Priority domains for examples and case studies $priority_domains = array( 'hbr.org', // Harvard Business Review 'mckinsey.com', // McKinsey & Company 'forbes.com', // Forbes 'businessinsider.com', // Business Insider 'medium.com', // Medium (often has case studies) 'hubspot.com', // HubSpot case studies 'salesforce.com', // Salesforce case studies 'techcrunch.com', // Tech news with examples 'wired.com', // Wired 'fastcompany.com' // Fast Company ); $priority_results = array(); $other_results = array(); if (!empty($data['organic'])) { foreach ($data['organic'] as $item) { $url = $item['link'] ?? ''; if (empty($url)) continue; // Parse domain $parsed = wp_parse_url($url); $domain = $parsed['host'] ?? ''; $domain = preg_replace('/^www\./', '', $domain); // Skip unwanted domains $skip = false; foreach ($skip_domains as $skip_domain) { if (strpos($domain, $skip_domain) !== false) { $skip = true; break; } } if ($skip) continue; $result = array( 'title' => $item['title'] ?? '', 'url' => $url, 'snippet' => $item['snippet'] ?? '', 'position' => $item['position'] ?? 0 ); // Check if priority domain $is_priority = false; foreach ($priority_domains as $priority) { if (strpos($domain, $priority) !== false) { $is_priority = true; break; } } if ($is_priority) { $priority_results[] = $result; } else { $other_results[] = $result; } } } // Combine priority first, then others $results = array_merge($priority_results, $other_results); $results = array_slice($results, 0, $num_results); // Re-index $indexed_results = array_values($results); // Include answer box if present if (!empty($data['answerBox'])) { $indexed_results['answer_box'] = array( 'title' => $data['answerBox']['title'] ?? '', 'answer' => $data['answerBox']['answer'] ?? $data['answerBox']['snippet'] ?? '' ); } // Include knowledge graph if present if (!empty($data['knowledgeGraph'])) { $indexed_results['knowledge_graph'] = array( 'title' => $data['knowledgeGraph']['title'] ?? '', 'description' => $data['knowledgeGraph']['description'] ?? '' ); } return $indexed_results; } /** * Fetch content from top search results */ private function fetch_top_results($search_results, $num_sources) { $fetched = array( 'sources' => array(), 'content' => array(), 'errors' => array() ); $fetched_count = 0; foreach ($search_results as $key => $result) { if (!is_numeric($key)) continue; if ($fetched_count >= $num_sources) break; if (empty($result['url'])) continue; $content = $this->fetch_url_content($result['url']); if (is_wp_error($content)) { $fetched['errors'][] = $result['url'] . ': ' . $content->get_error_message(); continue; } $fetched['sources'][] = array( 'title' => $result['title'], 'url' => $result['url'] ); $fetched['content'][] = array( 'source' => $result['title'], 'url' => $result['url'], 'content' => $content ); $fetched_count++; } return $fetched; } /** * Fetch and extract content from a URL */ private function fetch_url_content($url) { $response = wp_remote_get($url, array( 'timeout' => 15, 'user-agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' )); if (is_wp_error($response)) { return $response; } $status_code = wp_remote_retrieve_response_code($response); if ($status_code !== 200) { return new WP_Error('fetch_error', 'HTTP ' . $status_code); } $html = wp_remote_retrieve_body($response); if (empty($html)) { return new WP_Error('empty_response', 'Empty response'); } // Extract text content $text = $this->extract_text_from_html($html); if (strlen($text) < 100) { return new WP_Error('insufficient_content', 'Content too short'); } // Truncate if too long $max_length = 8000; if (strlen($text) > $max_length) { $text = substr($text, 0, $max_length); $last_period = strrpos($text, '. '); if ($last_period !== false && $last_period > ($max_length * 0.8)) { $text = substr($text, 0, $last_period + 1); } $text .= "\n\n[Content truncated...]"; } return $text; } /** * Extract readable text from HTML */ private function extract_text_from_html($html) { // Remove unwanted elements $html = preg_replace('/]*>(.*?)<\/script>/is', '', $html); $html = preg_replace('/]*>(.*?)<\/style>/is', '', $html); $html = preg_replace('/]*>(.*?)<\/nav>/is', '', $html); $html = preg_replace('/]*>(.*?)<\/footer>/is', '', $html); $html = preg_replace('/]*>(.*?)<\/header>/is', '', $html); $html = preg_replace('/]*>(.*?)<\/aside>/is', '', $html); $html = preg_replace('//s', '', $html); // Try to find main content $extracted = ''; if (preg_match('/]*>(.*?)<\/article>/is', $html, $matches)) { $extracted = $matches[1]; } elseif (preg_match('/]*>(.*?)<\/main>/is', $html, $matches)) { $extracted = $matches[1]; } elseif (preg_match_all('/]*>(.{50,}?)<\/p>/is', $html, $matches)) { $extracted = implode("\n\n", $matches[0]); } else { $extracted = $html; } // Convert to text $extracted = preg_replace('/]*>(.*?)<\/h[1-6]>/is', "\n\n### $1 ###\n\n", $extracted); $extracted = preg_replace('/]*>(.*?)<\/li>/is', "• $1\n", $extracted); $extracted = preg_replace('//i', "\n", $extracted); $extracted = preg_replace('/<\/p>/i', "\n\n", $extracted); $text = wp_strip_all_tags($extracted); $text = html_entity_decode($text, ENT_QUOTES | ENT_HTML5, 'UTF-8'); $text = preg_replace('/[ \t]+/', ' ', $text); $text = preg_replace('/\n\s*\n\s*\n+/', "\n\n", $text); return trim($text); } /** * Create an examples brief from search results */ private function create_examples_brief($fetched_content, $search_results, $topic) { $brief = "=== EXAMPLES & CASE STUDIES RESEARCH ===\n"; $brief .= "Topic: {$topic}\n\n"; // Add answer box if present if (!empty($search_results['answer_box'])) { $brief .= "### Key Example ###\n"; $brief .= $search_results['answer_box']['answer'] . "\n\n"; } // Add search snippets focusing on examples $brief .= "### Examples Found ###\n"; foreach ($search_results as $key => $result) { if (!is_numeric($key)) continue; $snippet = $result['snippet'] ?? ''; $title = $result['title'] ?? ''; // Look for case study indicators in title or snippet $case_study_keywords = array('case study', 'example', 'success', 'story', 'how', 'implementation', 'achieved', 'transformed'); $is_example = false; foreach ($case_study_keywords as $keyword) { if (stripos($title, $keyword) !== false || stripos($snippet, $keyword) !== false) { $is_example = true; break; } } if ($is_example || !empty($snippet)) { $brief .= "• {$title}: {$snippet}\n"; } } $brief .= "\n"; // Add fetched content if (!empty($fetched_content['content'])) { $brief .= "### Detailed Case Studies ###\n\n"; foreach ($fetched_content['content'] as $idx => $item) { $num = $idx + 1; $brief .= "--- Example {$num}: {$item['source']} ---\n"; $brief .= "URL: {$item['url']}\n\n"; $brief .= $item['content'] . "\n\n"; } } // Add sources list if (!empty($fetched_content['sources'])) { $brief .= "### Source URLs ###\n"; foreach ($fetched_content['sources'] as $source) { $brief .= "• {$source['title']}: {$source['url']}\n"; } $brief .= "\n"; } $brief .= "=== END EXAMPLES RESEARCH ==="; return $brief; } public function get_config_schema() { return array( 'num_results' => array( 'type' => 'number', 'label' => 'Results per Query', 'description' => 'How many search results to retrieve per query', 'min' => 1, 'max' => 10, 'default' => 5 ), 'num_sources_to_fetch' => array( 'type' => 'number', 'label' => 'Sources to Fetch', 'description' => 'How many pages to fetch full content from', 'min' => 1, 'max' => 5, 'default' => 3 ) ); } }