options = $options; $this->logger = $logger; } /** * Updates the internal options array. Typically called after settings are saved. * * @param array $options The new plugin options array. */ public function set_options( $options ) { $this->options = $options; } // --- Public Methods (AI Task Selectors) --- /** * Rewrites content using the currently selected main AI provider (Gemini or Groq). * * @param string $original_text The original text to rewrite. * @return string|WP_Error The rewritten text on success, or a WP_Error object on failure. */ public function rewrite_content( $original_text ) { if ( isset( $this->options['main_ia_provider'] ) && 'groq' === $this->options['main_ia_provider'] ) { return $this->rewrite_with_groq( $original_text ); } // Default to Gemini if not Groq or if provider setting is missing/invalid. return $this->rewrite_with_gemini( $original_text ); } /** * Rewrites a title using the selected main AI provider. * * @param string $original_title The original title. * @param string $rewritten_content The rewritten content (used for context by the AI). * @return string|WP_Error The newly generated title on success, or a WP_Error object on failure. */ public function rewrite_title( $original_title, $rewritten_content ) { if ( isset( $this->options['main_ia_provider'] ) && 'groq' === $this->options['main_ia_provider'] ) { return $this->rewrite_title_with_groq( $original_title, $rewritten_content ); } return $this->rewrite_title_with_gemini( $original_title, $rewritten_content ); } /** * Categorizes content using the selected main AI provider. * * @param string $rewritten_content The content to categorize. * @return string|WP_Error The suggested category name on success, or a WP_Error object on failure. */ public function categorize_content( $rewritten_content ) { if ( isset( $this->options['main_ia_provider'] ) && 'groq' === $this->options['main_ia_provider'] ) { return $this->categorize_with_groq( $rewritten_content ); } return $this->categorize_with_gemini( $rewritten_content ); } /** * Generates tags for content using the selected main AI provider. * * @param string $rewritten_content The content to generate tags for. * @return array|WP_Error An array of suggested tag strings on success, or a WP_Error object on failure. */ public function generate_tags( $rewritten_content ) { if ( isset( $this->options['main_ia_provider'] ) && 'groq' === $this->options['main_ia_provider'] ) { return $this->generate_tags_with_groq( $rewritten_content ); } return $this->generate_tags_with_gemini( $rewritten_content ); } // --- Model Lists --- /** * Retrieves a list of all available Gemini models supported by the plugin. * * @return array An associative array where keys are model IDs and values contain label and API version. */ public function get_all_gemini_models() { return array( 'gemini-1.5-flash-001' => array( /* translators: Model name in dropdown */ 'label' => __( 'Gemini 1.5 Flash (Recommended)', 'ainp-ai-native-publisher' ), 'version' => 'v1beta', ), 'gemini-1.5-pro-001' => array( /* translators: Model name in dropdown */ 'label' => __( 'Gemini 1.5 Pro (Advanced)', 'ainp-ai-native-publisher' ), 'version' => 'v1beta', ), 'gemini-pro' => array( /* translators: Model name in dropdown */ 'label' => __( 'Gemini Pro (Legacy Stable)', 'ainp-ai-native-publisher' ), 'version' => 'v1beta', ), 'gemini-1.0-pro' => array( /* translators: Model name in dropdown */ 'label' => __( 'Gemini 1.0 Pro (Legacy)', 'ainp-ai-native-publisher' ), 'version' => 'v1beta', ), ); } /** * Retrieves a list of all available Groq models supported by the plugin. * * @return array An associative array where keys are model IDs and values are display labels. */ public function get_all_groq_models() { return array( /* translators: Model name in dropdown */ 'llama-3.1-8b-instant' => __( 'Llama 3.1 8b (Fast & Efficient)', 'ainp-ai-native-publisher' ), /* translators: Model name in dropdown */ 'llama-3.1-70b-versatile' => __( 'Llama 3.1 70b (Advanced)', 'ainp-ai-native-publisher' ), /* translators: Model name in dropdown */ 'gemma2-9b-it' => __( 'Gemma 2 9b (from Google)', 'ainp-ai-native-publisher' ), ); } // --- Connection Test Methods --- /** * Tests the connection to all configured Gemini models using the provided API key. */ public function test_gemini_connections() { $results = array(); if ( empty( $this->options['gemini_api_key'] ) ) { return array( 'error' => __( 'No Gemini API key has been entered.', 'ainp-ai-native-publisher' ) ); } $payload = array( 'contents' => array( array( 'parts' => array( array( 'text' => 'Respond only with the word "OK"' ) ) ) ) ); $all_models = $this->get_all_gemini_models(); foreach ( $all_models as $model_name => $model_info ) { $response = $this->call_gemini_api( $model_name, $model_info['version'], 'generateContent', $payload, true ); if ( is_wp_error( $response ) ) { $results[ $model_name ] = array( 'status' => 'error', 'message' => $response->get_error_message(), ); } elseif ( isset( $response['candidates'][0]['content']['parts'][0]['text'] ) && false !== stripos( $response['candidates'][0]['content']['parts'][0]['text'], 'OK' ) ) { $results[ $model_name ] = array( 'status' => 'success', 'message' => __( 'Connection successful.', 'ainp-ai-native-publisher' ), ); } else { $results[ $model_name ] = array( 'status' => 'error', 'message' => __( 'Unexpected response format.', 'ainp-ai-native-publisher' ), ); } } return $results; } /** * Tests the connection to the Groq API using the selected model and API key. */ public function test_groq_connection() { if ( empty( $this->options['groq_api_key'] ) ) { return array( 'error' => __( 'No Groq API key has been entered.', 'ainp-ai-native-publisher' ) ); } $selected_model = $this->options['groq_model_selection'] ?? 'llama-3.1-8b-instant'; $payload = array( 'messages' => array( array( 'role' => 'user', 'content' => 'Respond only with the word "OK"' ) ), 'model' => $selected_model, ); $response = $this->call_groq_api( $payload, true ); if ( is_wp_error( $response ) ) { return array( 'status' => 'error', 'message' => $response->get_error_message(), ); } elseif ( isset( $response['choices'][0]['message']['content'] ) && false !== stripos( $response['choices'][0]['message']['content'], 'OK' ) ) { return array( 'status' => 'success', 'message' => __( 'Connection successful.', 'ainp-ai-native-publisher' ), ); } else { return array( 'status' => 'error', 'message' => __( 'Unexpected response from the Groq API.', 'ainp-ai-native-publisher' ), ); } } /** * Tests the connection to the Unsplash API. */ public function test_unsplash_connection() { if ( empty( $this->options['unsplash_access_key'] ) ) { return array( 'error' => __( 'No Unsplash Access Key has been entered.', 'ainp-ai-native-publisher' ) ); } $response = $this->call_unsplash_api( 'nature', true ); if ( is_wp_error( $response ) ) { return array( 'status' => 'error', 'message' => $response->get_error_message(), ); } elseif ( isset( $response['results'] ) ) { return array( 'status' => 'success', 'message' => __( 'Connection successful.', 'ainp-ai-native-publisher' ), ); } else { return array( 'status' => 'error', 'message' => __( 'Unexpected response structure from Unsplash.', 'ainp-ai-native-publisher' ), ); } } // --- Helper: Language Instruction --- private function get_language_instruction() { $target_language = $this->options['target_language'] ?? 'Portuguese (Brazil)'; return "\n\nIMPORTANT: Write the final output strictly in " . $target_language . "."; } // --- Helper: Category List Expansion --- private function get_expanded_category_list() { // Get actual categories from WP $site_categories = get_categories( array( 'hide_empty' => 0, 'fields' => 'names' ) ); // Fallback list of standard topics to ensure AI has options even on empty sites $default_topics = array( 'News', 'Politics', 'Economy', 'Technology', 'Science', 'Health', 'Sports', 'Entertainment', 'World', 'Business' ); // Merge and unique $all_cats = array_unique( array_merge( $site_categories, $default_topics ) ); // Limit to 50 to prevent token overflow return implode( ', ', array_slice( $all_cats, 0, 50 ) ); } // --- Private Methods (AI-Specific Logic) --- private function rewrite_with_gemini( $original_text ) { $originality_prefix = ! empty( $this->options['prompt_originality_prefix'] ) ? trim( $this->options['prompt_originality_prefix'] ) . "\n\n---\n\n" : ''; $base_prompt = str_replace( '{original_text}', wp_strip_all_tags( $original_text ), $this->options['prompt_rewrite'] ); $prompt = $originality_prefix . $base_prompt . $this->get_language_instruction(); $payload = array( 'contents' => array( array( 'parts' => array( array( 'text' => $prompt ) ) ) ) ); $all_models = $this->get_all_gemini_models(); $model_name = $this->options['gemini_model_selection'] ?? 'gemini-1.5-flash-001'; $version = $all_models[ $model_name ]['version'] ?? 'v1beta'; $response = $this->call_gemini_api( $model_name, $version, 'generateContent', $payload ); if ( is_wp_error( $response ) ) { return $response; } if ( isset( $response['candidates'][0]['content']['parts'][0]['text'] ) ) { // Security: Sanitize HTML content before returning return wp_kses_post( $this->format_ia_response_content( $response['candidates'][0]['content']['parts'][0]['text'] ) ); } return new WP_Error( 'gemini_rewrite_error', __( 'Unexpected response from Gemini API for content rewriting (missing text).', 'ainp-ai-native-publisher' ), $response ); } private function rewrite_with_groq( $original_text ) { $originality_prefix = ! empty( $this->options['prompt_originality_prefix'] ) ? trim( $this->options['prompt_originality_prefix'] ) . "\n\n---\n\n" : ''; $base_prompt = str_replace( '{original_text}', wp_strip_all_tags( $original_text ), $this->options['prompt_rewrite'] ); $prompt = $originality_prefix . $base_prompt . $this->get_language_instruction(); $payload = array( 'messages' => array( array( 'role' => 'user', 'content' => $prompt ) ), 'model' => $this->options['groq_model_selection'] ?? 'llama-3.1-8b-instant', 'temperature' => 0.7, ); $response = $this->call_groq_api( $payload ); if ( is_wp_error( $response ) ) { return $response; } if ( isset( $response['choices'][0]['message']['content'] ) ) { // Security: Sanitize HTML content before returning return wp_kses_post( $this->format_ia_response_content( $response['choices'][0]['message']['content'] ) ); } return new WP_Error( 'groq_rewrite_error', __( 'Unexpected response from Groq API for content rewriting (missing text).', 'ainp-ai-native-publisher' ), $response ); } private function rewrite_title_with_gemini( $original_title, $rewritten_content ) { $prompt_context_content = mb_substr( wp_strip_all_tags( $rewritten_content ), 0, 500 ) . '...'; $base_prompt = str_replace( array( '{original_title}', '{rewritten_content}' ), array( wp_strip_all_tags( $original_title ), $prompt_context_content ), $this->options['prompt_title'] ); $prompt = $base_prompt . $this->get_language_instruction(); $payload = array( 'contents' => array( array( 'parts' => array( array( 'text' => $prompt ) ) ) ), 'generationConfig' => array( 'maxOutputTokens' => 50, 'temperature' => 0.7, ), ); $all_models = $this->get_all_gemini_models(); $model_name = $this->options['gemini_model_selection'] ?? 'gemini-1.5-flash-001'; $version = $all_models[ $model_name ]['version'] ?? 'v1beta'; $response = $this->call_gemini_api( $model_name, $version, 'generateContent', $payload ); if ( is_wp_error( $response ) ) { return $response; } if ( isset( $response['candidates'][0]['content']['parts'][0]['text'] ) ) { return $this->format_ia_response_title( $response['candidates'][0]['content']['parts'][0]['text'], $original_title ); } /* translators: %s: Original post title */ $error_message = sprintf( __( 'Unexpected response from Gemini API for title of "%1$s".', 'ainp-ai-native-publisher' ), esc_html( $original_title ) ); return new WP_Error( 'gemini_title_unexpected_response', $error_message, $response ); } private function rewrite_title_with_groq( $original_title, $rewritten_content ) { $prompt_context_content = mb_substr( wp_strip_all_tags( $rewritten_content ), 0, 500 ) . '...'; $base_prompt = str_replace( array( '{original_title}', '{rewritten_content}' ), array( wp_strip_all_tags( $original_title ), $prompt_context_content ), $this->options['prompt_title'] ); $prompt = $base_prompt . $this->get_language_instruction(); $payload = array( 'messages' => array( array( 'role' => 'user', 'content' => $prompt ) ), 'model' => $this->options['groq_model_selection'] ?? 'llama-3.1-8b-instant', 'max_tokens' => 50, 'temperature' => 0.7, ); $response = $this->call_groq_api( $payload ); if ( is_wp_error( $response ) ) { return $response; } if ( isset( $response['choices'][0]['message']['content'] ) ) { return $this->format_ia_response_title( $response['choices'][0]['message']['content'], $original_title ); } /* translators: %s: Original post title */ $error_message = sprintf( __( 'Unexpected response from Groq API for title of "%1$s".', 'ainp-ai-native-publisher' ), esc_html( $original_title ) ); return new WP_Error( 'groq_title_unexpected_response', $error_message, $response ); } private function categorize_with_gemini( $rewritten_content ) { // Use expanded list to avoid "Loterias" trap $category_list = $this->get_expanded_category_list(); $prompt_context_content = mb_substr( wp_strip_all_tags( $rewritten_content ), 0, 1500 ) . '...'; $base_prompt = str_replace( array( '{rewritten_content}', '{current_categories}' ), array( $prompt_context_content, $category_list ), $this->options['prompt_category'] ); // Translate prompt to target language for the category name $prompt = $base_prompt . $this->get_language_instruction() . " Output ONLY the category name."; $payload = array( 'contents' => array( array( 'parts' => array( array( 'text' => $prompt ) ) ) ), 'generationConfig' => array( 'responseMimeType' => 'text/plain', 'maxOutputTokens' => 20, ), ); $all_models = $this->get_all_gemini_models(); $model_name = $this->options['gemini_model_selection'] ?? 'gemini-1.5-flash-001'; $version = $all_models[ $model_name ]['version'] ?? 'v1beta'; $response = $this->call_gemini_api( $model_name, $version, 'generateContent', $payload ); if ( is_wp_error( $response ) ) { return $response; } if ( isset( $response['candidates'][0]['content']['parts'][0]['text'] ) ) { return sanitize_text_field( trim( $response['candidates'][0]['content']['parts'][0]['text'], '"\'., ' ) ); } return new WP_Error( 'gemini_category_error', __( 'Unexpected response from Gemini API for categorization.', 'ainp-ai-native-publisher' ), $response ); } private function categorize_with_groq( $rewritten_content ) { // Use expanded list to avoid "Loterias" trap $category_list = $this->get_expanded_category_list(); $prompt_context_content = mb_substr( wp_strip_all_tags( $rewritten_content ), 0, 1500 ) . '...'; $base_prompt = str_replace( array( '{rewritten_content}', '{current_categories}' ), array( $prompt_context_content, $category_list ), $this->options['prompt_category'] ); $prompt = $base_prompt . $this->get_language_instruction(); $payload = array( // Changed system prompt to force classification even with minimal data and use expanded list 'messages' => array( array( 'role' => 'system', 'content' => 'You are a content classifier. Analyze the text and choose the single best category from the user provided list. If no exact match exists, choose the closest generic topic. Do not refuse to answer. Output ONLY the category name.' ), array( 'role' => 'user', 'content' => $prompt ) ), 'model' => $this->options['groq_model_selection'] ?? 'llama-3.1-8b-instant', 'max_tokens' => 20, ); $response = $this->call_groq_api( $payload ); if ( is_wp_error( $response ) ) { return $response; } if ( isset( $response['choices'][0]['message']['content'] ) ) { return sanitize_text_field( trim( $response['choices'][0]['message']['content'], '"\'., ' ) ); } return new WP_Error( 'groq_category_error', __( 'Unexpected response from Groq API for categorization.', 'ainp-ai-native-publisher' ), $response ); } private function generate_tags_with_gemini( $rewritten_content ) { $prompt_context_content = mb_substr( wp_strip_all_tags( $rewritten_content ), 0, 1500 ) . '...'; $base_prompt = str_replace( '{rewritten_content}', $prompt_context_content, $this->options['prompt_tags'] ); $prompt = $base_prompt . " " . $this->get_language_instruction() . " Ensure the output is a VALID JSON array of strings."; $json_schema = array( 'type' => 'OBJECT', 'properties' => array( 'tags' => array( 'type' => 'ARRAY', 'items' => array( 'type' => 'STRING' ), ), ), 'required' => array( 'tags' ), ); $payload = array( 'contents' => array( array( 'parts' => array( array( 'text' => $prompt ) ) ) ), 'generationConfig' => array( 'responseMimeType' => 'application/json', 'responseSchema' => $json_schema, 'maxOutputTokens' => 100, ), ); $all_models = $this->get_all_gemini_models(); $model_name = $this->options['gemini_model_selection'] ?? 'gemini-1.5-flash-001'; $version = $all_models[ $model_name ]['version'] ?? 'v1beta'; $response = $this->call_gemini_api( $model_name, $version, 'generateContent', $payload ); if ( is_wp_error( $response ) ) { return $response; } if ( isset( $response['candidates'][0]['content']['parts'][0]['text'] ) ) { return $this->format_ia_response_tags( $response['candidates'][0]['content']['parts'][0]['text'] ); } return new WP_Error( 'gemini_tags_error', __( 'Unexpected JSON response structure from Gemini API for tags.', 'ainp-ai-native-publisher' ), $response ); } private function generate_tags_with_groq( $rewritten_content ) { // Increased context to 1500 $prompt_context_content = mb_substr( wp_strip_all_tags( $rewritten_content ), 0, 1500 ) . '...'; $base_prompt = str_replace( '{rewritten_content}', $prompt_context_content, $this->options['prompt_tags'] ); // Refined instructions to reduce API Error 400 and Force JSON $prompt = $base_prompt . " " . $this->get_language_instruction() . " Output MUST be a JSON object with a single key 'tags' containing an array of strings."; $payload = array( 'messages' => array( array( 'role' => 'user', 'content' => $prompt ) ), 'model' => $this->options['groq_model_selection'] ?? 'llama-3.1-8b-instant', 'response_format' => array( 'type' => 'json_object' ), 'max_tokens' => 150, // Increased slightly to prevent cutoff ); $response = $this->call_groq_api( $payload ); if ( is_wp_error( $response ) ) { return $response; } if ( isset( $response['choices'][0]['message']['content'] ) ) { return $this->format_ia_response_tags( $response['choices'][0]['message']['content'] ); } return new WP_Error( 'groq_tags_error', __( 'Unexpected JSON response structure from Groq API for tags.', 'ainp-ai-native-publisher' ), $response ); } // --- Private Helper Functions (Formatting & API Calls) --- private function format_ia_response_content( $text ) { $text = str_replace( array( "\r\n", "\r" ), "\n", $text ); $text = trim( $text ); $text = preg_replace( '/^##\s*(.+?)\s*$/m', "\n\n