| 1 |
<?php |
| 2 |
|
| 3 |
namespace WPDeveloper\BetterDocs\Utils; |
| 4 |
|
| 5 |
use WPDeveloper\BetterDocs\Core\Settings; |
| 6 |
|
| 7 |
class AIHelper { |
| 8 |
|
| 9 |
/** |
| 10 |
* Settings instance |
| 11 |
* |
| 12 |
* @var Settings |
| 13 |
*/ |
| 14 |
private $settings; |
| 15 |
|
| 16 |
public function __construct( Settings $settings ) { |
| 17 |
$this->settings = $settings; |
| 18 |
} |
| 19 |
|
| 20 |
/** |
| 21 |
* Get OpenAI API key from settings |
| 22 |
* |
| 23 |
* @return string |
| 24 |
*/ |
| 25 |
public function get_api_key() { |
| 26 |
return $this->settings->get( 'ai_autowrite_api_key', '' ); |
| 27 |
} |
| 28 |
|
| 29 |
/** |
| 30 |
* Check if OpenAI API key is configured |
| 31 |
* |
| 32 |
* @return bool |
| 33 |
*/ |
| 34 |
public function has_api_key() { |
| 35 |
$api_key = $this->get_api_key(); |
| 36 |
return ! empty( $api_key ); |
| 37 |
} |
| 38 |
|
| 39 |
/** |
| 40 |
* Validate OpenAI API key |
| 41 |
* |
| 42 |
* @param string $api_key Optional API key to validate, uses stored key if not provided |
| 43 |
* @return array Array with 'valid' boolean and 'message' string |
| 44 |
*/ |
| 45 |
public function validate_api_key( $api_key = '' ) { |
| 46 |
if ( empty( $api_key ) ) { |
| 47 |
$api_key = $this->get_api_key(); |
| 48 |
} |
| 49 |
|
| 50 |
if ( empty( $api_key ) ) { |
| 51 |
return [ |
| 52 |
'valid' => false, |
| 53 |
'message' => 'Please Insert your <a href="/admin.php?page=betterdocs-settings">OpenAI API Key</a> to use AI features.' |
| 54 |
]; |
| 55 |
} |
| 56 |
|
| 57 |
$ch = curl_init( 'https://api.openai.com/v1/models' ); //phpcs:ignore WordPress.WP.AlternativeFunctions.curl_curl_init |
| 58 |
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true ); //phpcs:ignore WordPress.WP.AlternativeFunctions.curl_curl_setopt |
| 59 |
curl_setopt( //phpcs:ignore WordPress.WP.AlternativeFunctions.curl_curl_setopt |
| 60 |
$ch, |
| 61 |
CURLOPT_HTTPHEADER, |
| 62 |
[ |
| 63 |
'Content-Type: application/json', |
| 64 |
'Authorization: Bearer ' . $api_key, |
| 65 |
] |
| 66 |
); |
| 67 |
|
| 68 |
$response = curl_exec( $ch ); //phpcs:ignore WordPress.WP.AlternativeFunctions.curl_curl_exec |
| 69 |
$httpCode = curl_getinfo( $ch, CURLINFO_HTTP_CODE ); //phpcs:ignore WordPress.WP.AlternativeFunctions.curl_curl_getinfo |
| 70 |
curl_close( $ch ); //phpcs:ignore WordPress.WP.AlternativeFunctions.curl_curl_close |
| 71 |
|
| 72 |
if ( $httpCode == 200 ) { |
| 73 |
return [ |
| 74 |
'valid' => true, |
| 75 |
'message' => 'Valid API Key' |
| 76 |
]; |
| 77 |
} else { |
| 78 |
$responseData = json_decode( $response, true ); |
| 79 |
$messageData = $responseData['error'] ?? ''; |
| 80 |
return [ |
| 81 |
'valid' => false, |
| 82 |
'message' => $messageData['message'] ?? 'Invalid API Key' |
| 83 |
]; |
| 84 |
} |
| 85 |
} |
| 86 |
|
| 87 |
/** |
| 88 |
* Make a request to OpenAI API |
| 89 |
* |
| 90 |
* @param array $messages Array of messages for the chat completion |
| 91 |
* @param array $options Optional parameters (model, max_tokens, temperature, etc.) |
| 92 |
* @return string|\WP_Error API response content or error |
| 93 |
*/ |
| 94 |
public function make_openai_request( $messages, $options = [] ) { |
| 95 |
$api_key = $this->get_api_key(); |
| 96 |
$max_tokens = $this->settings->get( 'article_summary_max_token', 1500 ); |
| 97 |
$model = $this->settings->get( 'article_summary_model', 'gpt-4o-mini' ); |
| 98 |
|
| 99 |
if ( empty( $api_key ) ) { |
| 100 |
return new \WP_Error( 'no_api_key', 'OpenAI API key is not configured.' ); |
| 101 |
} |
| 102 |
|
| 103 |
// Default options |
| 104 |
$defaults = [ |
| 105 |
'model' => $model, |
| 106 |
'max_tokens' => $max_tokens, |
| 107 |
'temperature' => 0.7, |
| 108 |
'timeout' => 50 |
| 109 |
]; |
| 110 |
|
| 111 |
$options = wp_parse_args( $options, $defaults ); |
| 112 |
|
| 113 |
$api_endpoint = 'https://api.openai.com/v1/chat/completions'; |
| 114 |
|
| 115 |
$request_body = [ |
| 116 |
'model' => $options['model'], |
| 117 |
'messages' => $messages, |
| 118 |
'max_tokens' => $options['max_tokens'], |
| 119 |
'temperature' => $options['temperature'] |
| 120 |
]; |
| 121 |
|
| 122 |
$request_options = [ |
| 123 |
'headers' => [ |
| 124 |
'Content-Type' => 'application/json', |
| 125 |
'Authorization' => 'Bearer ' . $api_key, |
| 126 |
], |
| 127 |
'body' => json_encode( $request_body ), //phpcs:ignore WordPress.WP.AlternativeFunctions.json_encode_json_encode |
| 128 |
'timeout' => $options['timeout'], |
| 129 |
]; |
| 130 |
|
| 131 |
$response = wp_remote_post( $api_endpoint, $request_options ); |
| 132 |
|
| 133 |
if ( is_wp_error( $response ) ) { |
| 134 |
return new \WP_Error( 'api_error', 'Failed to connect to OpenAI API: ' . $response->get_error_message() ); |
| 135 |
} |
| 136 |
|
| 137 |
$body = wp_remote_retrieve_body( $response ); |
| 138 |
$data = json_decode( $body, true ); |
| 139 |
|
| 140 |
if ( ! empty( $data['error'] ) ) { |
| 141 |
return new \WP_Error( 'openai_error', $data['error']['message'] ); |
| 142 |
} |
| 143 |
|
| 144 |
if ( empty( $data['choices'][0]['message']['content'] ) ) { |
| 145 |
return new \WP_Error( 'no_content', 'No content received from OpenAI.' ); |
| 146 |
} |
| 147 |
|
| 148 |
return $data['choices'][0]['message']['content']; |
| 149 |
} |
| 150 |
|
| 151 |
/** |
| 152 |
* Create a system message for OpenAI |
| 153 |
* |
| 154 |
* @param string $content System message content |
| 155 |
* @return array Message array |
| 156 |
*/ |
| 157 |
public function create_system_message( $content ) { |
| 158 |
return [ |
| 159 |
'role' => 'system', |
| 160 |
'content' => $content |
| 161 |
]; |
| 162 |
} |
| 163 |
|
| 164 |
/** |
| 165 |
* Create a user message for OpenAI |
| 166 |
* |
| 167 |
* @param string $content User message content |
| 168 |
* @return array Message array |
| 169 |
*/ |
| 170 |
public function create_user_message( $content ) { |
| 171 |
return [ |
| 172 |
'role' => 'user', |
| 173 |
'content' => $content |
| 174 |
]; |
| 175 |
} |
| 176 |
|
| 177 |
/** |
| 178 |
* Create messages array for article summarization |
| 179 |
* |
| 180 |
* @param string $title Article title |
| 181 |
* @param string $content Article content |
| 182 |
* @return array Messages array |
| 183 |
*/ |
| 184 |
public function create_summary_messages( $title, $content ) { |
| 185 |
$system_message = $this->create_system_message( |
| 186 |
'You are a helpful assistant that creates concise, informative summaries of documentation articles. Always format your response in clean HTML with paragraph tags. Do not use markdown formatting, code blocks, or backticks. Return only the HTML content without any wrapper formatting.' |
| 187 |
); |
| 188 |
|
| 189 |
$user_prompt = "Please provide a concise summary of the following article titled '{$title}'. The summary should be 2-3 paragraphs long, highlighting the main points and key takeaways. Format the response in HTML with proper paragraph tags. Do not wrap the response in markdown code blocks or use any markdown formatting.\n\nArticle content:\n{$content}"; |
| 190 |
|
| 191 |
$user_message = $this->create_user_message( $user_prompt ); |
| 192 |
|
| 193 |
return [ $system_message, $user_message ]; |
| 194 |
} |
| 195 |
|
| 196 |
/** |
| 197 |
* Create messages array for content generation |
| 198 |
* |
| 199 |
* @param string $prompt User prompt |
| 200 |
* @param string $keywords Optional keywords |
| 201 |
* @return array Messages array |
| 202 |
*/ |
| 203 |
public function create_content_messages( $prompt, $keywords = '' ) { |
| 204 |
$system_message = $this->create_system_message( |
| 205 |
'You are a helpful assistant who writes documentation for users.' |
| 206 |
); |
| 207 |
|
| 208 |
$user_message = $this->create_user_message( $prompt ); |
| 209 |
|
| 210 |
return [ $system_message, $user_message ]; |
| 211 |
} |
| 212 |
|
| 213 |
/** |
| 214 |
* Sanitize and prepare content for AI processing |
| 215 |
* |
| 216 |
* @param string $content Raw content |
| 217 |
* @param int $max_length Maximum length to keep |
| 218 |
* @return string Sanitized content |
| 219 |
*/ |
| 220 |
public function prepare_content_for_ai( $content, $max_length = 4000 ) { |
| 221 |
// Strip HTML tags and decode entities |
| 222 |
$content = wp_strip_all_tags( $content ); |
| 223 |
$content = html_entity_decode( $content, ENT_QUOTES, 'UTF-8' ); |
| 224 |
|
| 225 |
// Remove extra whitespace |
| 226 |
$content = preg_replace( '/\s+/', ' ', $content ); |
| 227 |
$content = trim( $content ); |
| 228 |
|
| 229 |
// Limit length |
| 230 |
if ( strlen( $content ) > $max_length ) { |
| 231 |
$content = substr( $content, 0, $max_length ); |
| 232 |
// Try to cut at a word boundary |
| 233 |
$last_space = strrpos( $content, ' ' ); |
| 234 |
if ( $last_space !== false && $last_space > $max_length * 0.8 ) { |
| 235 |
$content = substr( $content, 0, $last_space ); |
| 236 |
} |
| 237 |
$content .= '...'; |
| 238 |
} |
| 239 |
|
| 240 |
return $content; |
| 241 |
} |
| 242 |
|
| 243 |
/** |
| 244 |
* Check if AI features are enabled |
| 245 |
* |
| 246 |
* @return bool |
| 247 |
*/ |
| 248 |
public function is_ai_enabled() { |
| 249 |
return $this->settings->get( 'enable_write_with_ai', true ) && $this->has_api_key(); |
| 250 |
} |
| 251 |
|
| 252 |
/** |
| 253 |
* Get AI usage statistics (placeholder for future implementation) |
| 254 |
* |
| 255 |
* @return array Usage statistics |
| 256 |
*/ |
| 257 |
public function get_usage_stats() { |
| 258 |
// This could be implemented to track API usage, costs, etc. |
| 259 |
return [ |
| 260 |
'requests_today' => 0, |
| 261 |
'tokens_used' => 0, |
| 262 |
'cost_estimate' => 0 |
| 263 |
]; |
| 264 |
} |
| 265 |
} |
| 266 |
|