build_prompt( $news_item, $related_links ); $body = array( 'model' => $model, 'temperature' => (float) Settings::get( 'ai_temperature', 0.7 ), 'response_format' => array( 'type' => 'json_object' ), 'messages' => array( array( 'role' => 'system', 'content' => $this->build_system_prompt( $author_id ), ), array( 'role' => 'user', 'content' => $prompt, ), ), ); $response = wp_remote_post( 'https://openrouter.ai/api/v1/chat/completions', array( 'timeout' => 120, 'headers' => array( 'Content-Type' => 'application/json', 'Authorization' => 'Bearer ' . $api_key, 'HTTP-Referer' => home_url(), 'X-Title' => 'AI Content Autopilot', ), 'body' => wp_json_encode( $body ), ) ); if ( is_wp_error( $response ) ) { Logger::error( __( 'OpenRouter API error: ', 'ai-content-autopilot' ) . $response->get_error_message() ); return null; } $status_code = wp_remote_retrieve_response_code( $response ); $response_body = wp_remote_retrieve_body( $response ); $data = json_decode( $response_body, true ); if ( $status_code !== 200 ) { $error_msg = $data['error']['message'] ?? __( 'Unknown error', 'ai-content-autopilot' ); /* translators: 1: HTTP status code, 2: error message */ Logger::error( sprintf( __( 'OpenRouter API returned %1$d: %2$s', 'ai-content-autopilot' ), $status_code, $error_msg ) ); return null; } $content = $data['choices'][0]['message']['content'] ?? null; if ( empty( $content ) ) { Logger::error( __( 'Empty response from OpenRouter API.', 'ai-content-autopilot' ) ); return null; } $article = json_decode( $content, true ); if ( ! $article || empty( $article['title'] ) || empty( $article['content'] ) ) { Logger::error( __( 'Could not parse AI response as JSON.', 'ai-content-autopilot' ), $content ); return null; } // Ensure all expected fields exist. $article = wp_parse_args( $article, array( 'title' => '', 'content' => '', 'excerpt' => '', 'category_hint' => '', 'image_prompt' => '', 'image_alt' => '', 'image_caption' => '', ) ); // Clean up content: remove literal \n and normalize whitespace between HTML tags. $article['content'] = self::clean_content( $article['content'] ); // Attach usage data and model for cost tracking. $article['_model'] = $model; $article['_usage'] = $data['usage'] ?? array(); $article['_response_data'] = $data; /* translators: %s: article title */ Logger::info( sprintf( __( 'Article generated: "%s"', 'ai-content-autopilot' ), $article['title'] ) ); return $article; } /** * Build system prompt for the AI. * * @param int|null $author_id Optional author ID for per-author writing style. */ private function build_system_prompt( $author_id = null ) { $language = Settings::get( 'ai_language', 'norsk' ); $niche = Settings::get( 'ai_niche', '' ); $site_identity = Settings::get( 'site_identity', '' ); $min_words = (int) Settings::get( 'min_words', 600 ); $max_words = (int) Settings::get( 'max_words', 1200 ); $style = Settings::get( 'ai_style', 'informativ og engasjerende' ); $prompt = "Du er en profesjonell journalist og innholdsskaper. "; $prompt .= "Skriv alltid på {$language}. "; if ( ! empty( $site_identity ) ) { $prompt .= "\n\nOm nettstedet du skriver for:\n{$site_identity}\n\n"; } if ( ! empty( $niche ) ) { $prompt .= "Du spesialiserer deg på {$niche}. "; } if ( strlen( $style ) > 100 ) { $prompt .= "\n\nSkrivestil du skal etterligne:\n{$style}\n\n"; } else { $prompt .= "Skrivestilen din er {$style}. "; } $prompt .= "Artikkelen skal være mellom {$min_words} og {$max_words} ord. "; $prompt .= "Bruk HTML-formatering (

,

,

, , ,