PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 4.2.6
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v4.2.6
4.9.2 4.9.1 4.9.0 4.8.2 4.8.1 4.8.0 4.7.0 4.6.2 4.6.1 4.6.0 4.5.6 4.5.5 4.5.4 4.5.3 4.5.2 4.5.1 4.5.0 4.4.1 4.4.0 3.3.4 3.4.0 3.4.1 3.4.2 3.5.0 3.5.1 All 200 releases
← All changes | includes/Utils/AIHelper.php +220 -449 4.9.24.2.6 View file →
@@ -2,493 +2,264 @@
2 2
3 3 namespace WPDeveloper\BetterDocs\Utils;
4 4
5 5 use WPDeveloper\BetterDocs\Core\Settings;
6 -use WPDeveloper\BetterDocs\AI\ProviderFactory;
7 6
8 7 class AIHelper {
9 8
10 - /**
11 - * Settings instance
12 - *
13 - * @var Settings
14 - */
15 - private $settings;
9 + /**
10 + * Settings instance
11 + *
12 + * @var Settings
13 + */
14 + private $settings;
16 15
17 - public function __construct( Settings $settings ) {
18 - $this->settings = $settings;
19 - }
16 + public function __construct( Settings $settings ) {
17 + $this->settings = $settings;
18 + }
20 19
21 - /**
22 - * Build a provider factory bound to the current settings.
23 - *
24 - * @return ProviderFactory
25 - */
26 - private function factory() {
27 - return new ProviderFactory( $this->settings );
28 - }
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 + }
29 28
30 - /**
31 - * Get the API key for the active AI platform.
32 - *
33 - * @return string
34 - */
35 - public function get_api_key() {
36 - $factory = $this->factory();
37 - return $factory->api_key_for( $factory->active_platform() );
38 - }
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 + }
39 38
40 - /**
41 - * Check if OpenAI API key is configured
42 - *
43 - * @return bool
44 - */
45 - public function has_api_key() {
46 - $api_key = $this->get_api_key();
47 - return ! empty( $api_key );
48 - }
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 49
50 - /**
51 - * Validate OpenAI API key
52 - *
53 - * @param string $api_key Optional API key to validate, uses stored key if not provided
54 - * @return array Array with 'valid' boolean and 'message' string
55 - */
56 - public function validate_api_key( $api_key = '' ) {
57 - if ( empty( $api_key ) ) {
58 - $api_key = $this->get_api_key();
59 - }
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 + }
60 56
61 - if ( empty( $api_key ) ) {
62 - return array(
63 - 'valid' => false,
64 - 'message' => 'Please Insert your <a href="/admin.php?page=betterdocs-settings#betterdocs-ai">API Key</a> to use AI features.'
65 - );
66 - }
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 67
68 - $factory = $this->factory();
69 - return $factory->validate( $factory->active_platform(), $api_key );
70 - }
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 71
72 - /**
73 - * Minimum token policy by (feature context, model family). Used as the
74 - * single source of truth for:
75 - * - server-side save validation (Core/Settings.php)
76 - * - field UI props sent to the React notice (Core/Settings.php)
77 - * - runtime payload floor in the provider layer (AI\Providers\BaseProvider::floor_tokens)
78 - *
79 - * Override the whole map (or any cell) via the `betterdocs_ai_min_tokens`
80 - * filter. Returns 0 when no minimum applies (unknown context or model).
81 - *
82 - * @param string $context Feature key, e.g. 'write_with_ai' or 'article_summary'.
83 - * @param string $model OpenAI model identifier.
84 - * @return int Minimum recommended max_tokens for that pair.
85 - */
86 - public static function get_min_tokens( $context, $model ) {
87 - $family = self::token_family( $model );
88 - $map = apply_filters( 'betterdocs_ai_min_tokens', array(
89 - 'write_with_ai' => array( 'gpt-4' => 2500, 'gpt-5' => 4500, 'gpt-5.5' => 10000 ),
90 - 'article_summary' => array( 'gpt-4' => 1500, 'gpt-5' => 2500, 'gpt-5.5' => 10000 ),
91 - ) );
92 - return isset( $map[ $context ][ $family ] ) ? (int) $map[ $context ][ $family ] : 0;
93 - }
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 + }
94 86
95 - /**
96 - * Map a model id to its token-floor family key.
97 - *
98 - * gpt-5.x point releases (gpt-5.5, gpt-5.1, ...) generate much larger, slower
99 - * responses and need a heavier floor than the base gpt-5 family, so they get
100 - * their own 'gpt-5.5' key. Plain gpt-5* stays 'gpt-5'; everything else 'gpt-4'.
101 - *
102 - * @param string $model OpenAI model identifier.
103 - * @return string Family key used in the min-token map.
104 - */
105 - private static function token_family( $model ) {
106 - if ( self::is_gpt5_point_release( $model ) ) {
107 - return 'gpt-5.5';
108 - }
109 - return ( 0 === strpos( (string) $model, 'gpt-5' ) ) ? 'gpt-5' : 'gpt-4';
110 - }
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' );
111 98
112 - /**
113 - * Whether a model is a gpt-5.x point release (gpt-5.5, gpt-5.1, ...), which
114 - * use the newer reasoning_effort vocabulary and need a heavier token floor.
115 - *
116 - * @param string $model OpenAI model identifier.
117 - * @return bool
118 - */
119 - public static function is_gpt5_point_release( $model ) {
120 - return (bool) preg_match( '/^gpt-5\.\d/', (string) $model );
121 - }
99 + if ( empty( $api_key ) ) {
100 + return new \WP_Error( 'no_api_key', 'OpenAI API key is not configured.' );
101 + }
122 102
123 - /**
124 - * Return the threshold map for one context, in {family => min} shape, so
125 - * Settings.php can serialize it onto a field for the React notice to read.
126 - *
127 - * @param string $context Feature key.
128 - * @return array<string,int>
129 - */
130 - public static function get_min_tokens_map( $context ) {
131 - return array(
132 - 'gpt-4' => self::get_min_tokens( $context, 'gpt-4o' ),
133 - 'gpt-5' => self::get_min_tokens( $context, 'gpt-5' ),
134 - 'gpt-5.5' => self::get_min_tokens( $context, 'gpt-5.5' ),
135 - );
136 - }
103 + // Default options
104 + $defaults = [
105 + 'model' => $model,
106 + 'max_tokens' => $max_tokens,
107 + 'temperature' => 0.7,
108 + 'timeout' => 50
109 + ];
137 110
138 - /**
139 - * Make a chat-completion request to the active AI platform.
140 - *
141 - * Provider-agnostic: the platform, model, key, payload shape and parsing are
142 - * resolved by ProviderFactory. The model is the global `ai_model`; callers
143 - * may still override per request via $options['model'].
144 - *
145 - * @param array $messages Array of messages for the chat completion.
146 - * @param array $options Optional parameters (model, max_tokens, temperature, timeout).
147 - * @return string|\WP_Error API response content or error.
148 - */
149 - public function make_openai_request( $messages, $options = array() ) {
150 - $defaults = array(
151 - 'max_tokens' => (int) $this->settings->get( 'article_summary_max_token', 1500 ),
152 - 'temperature' => 0.7,
153 - 'timeout' => 50,
154 - 'context' => 'article_summary',
155 - );
111 + $options = wp_parse_args( $options, $defaults );
156 112
157 - $options = wp_parse_args( $options, $defaults );
113 + $api_endpoint = 'https://api.openai.com/v1/chat/completions';
158 114
159 - $result = $this->factory()->make()->chat( $messages, $options );
115 + $request_body = [
116 + 'model' => $options['model'],
117 + 'messages' => $messages,
118 + 'max_tokens' => $options['max_tokens'],
119 + 'temperature' => $options['temperature']
120 + ];
160 121
161 - if ( is_wp_error( $result ) ) {
162 - return $result;
163 - }
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 + ];
164 130
165 - return $result['content'];
166 - }
131 + $response = wp_remote_post( $api_endpoint, $request_options );
167 132
168 - /**
169 - * Analyze article quality using OpenAI
170 - *
171 - * @param string $content Article content to analyze
172 - * @param string $title Article title (optional)
173 - * @return array|\WP_Error Analysis result with score and feedback
174 - */
175 - public function analyze_article_quality( $content, $title = '' ) {
176 - if ( empty( $content ) ) {
177 - return new \WP_Error( 'empty_content', 'Article content cannot be empty.' );
178 - }
133 + if ( is_wp_error( $response ) ) {
134 + return new \WP_Error( 'api_error', 'Failed to connect to OpenAI API: ' . $response->get_error_message() );
135 + }
179 136
180 - // Create analysis prompt
181 - $prompt = $this->build_quality_analysis_prompt( $content, $title );
137 + $body = wp_remote_retrieve_body( $response );
138 + $data = json_decode( $body, true );
182 139
183 - $messages = array(
184 - array(
185 - 'role' => 'system',
186 - 'content' => 'You are an expert content analyst specializing in documentation quality assessment. Provide detailed, actionable feedback to help improve article quality.'
187 - ),
188 - array(
189 - 'role' => 'user',
190 - 'content' => $prompt
191 - )
192 - );
140 + if ( ! empty( $data['error'] ) ) {
141 + return new \WP_Error( 'openai_error', $data['error']['message'] );
142 + }
193 143
194 - $options = array(
195 - 'max_tokens' => 2000,
196 - 'temperature' => 0.3 // Lower temperature for more consistent analysis
197 - );
144 + if ( empty( $data['choices'][0]['message']['content'] ) ) {
145 + return new \WP_Error( 'no_content', 'No content received from OpenAI.' );
146 + }
198 147
199 - $response = $this->make_openai_request( $messages, $options );
148 + return $data['choices'][0]['message']['content'];
149 + }
200 150
201 - if ( is_wp_error( $response ) ) {
202 - return $response;
203 - }
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 + }
204 163
205 - // Parse the AI response into structured data
206 - return $this->parse_quality_analysis_response( $response );
207 - }
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 + }
208 176
209 - /**
210 - * Build the prompt for article quality analysis
211 - *
212 - * @param string $content Article content
213 - * @param string $title Article title
214 - * @return string Formatted prompt
215 - */
216 - private function build_quality_analysis_prompt( $content, $title = '' ) {
217 - $title_section = ! empty( $title ) ? "Title: {$title}\n\n" : '';
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 + );
218 188
219 - $prompt = "Please analyze the following documentation article for quality and provide a comprehensive assessment:\n\n";
220 - $prompt .= $title_section;
221 - $prompt .= "Content:\n{$content}\n\n";
222 - $prompt .= "Please evaluate the article based on these criteria and provide your response in the following JSON format:\n\n";
223 - $prompt .= "{\n";
224 - $prompt .= ' "overall_score": 91,';
225 - $prompt .= ' "scores": {';
226 - $prompt .= ' "clarity": 90,';
227 - $prompt .= ' "completeness": 92,';
228 - $prompt .= ' "relevance": 95,';
229 - $prompt .= ' "structure": 87,';
230 - $prompt .= ' "readability": 88';
231 - $prompt .= ' },';
232 - $prompt .= ' "feedback": {';
233 - $prompt .= ' "strengths": ["Clear headings", "Good use of examples"],';
234 - $prompt .= ' "improvements": ["Add more detailed explanations in section 2", "Include troubleshooting steps"],';
235 - $prompt .= ' "suggestions": ["Consider adding screenshots", "Break down complex paragraphs"]';
236 - $prompt .= ' },';
237 - $prompt .= ' "priority": "medium"';
238 - $prompt .= "}\n\n";
239 - $prompt .= "Scoring criteria (0-100):\n";
240 - $prompt .= "- Clarity: How clear and understandable is the content?\n";
241 - $prompt .= "- Completeness: Does it cover the topic thoroughly?\n";
242 - $prompt .= "- Relevance: Is the content relevant to the stated purpose?\n";
243 - $prompt .= "- Structure: Is the content well-organized with proper headings?\n";
244 - $prompt .= "- Readability: Is it easy to read and follow?\n\n";
245 - $prompt .= "Priority levels: low (80+), medium (60-79), high (below 60)\n";
246 - $prompt .= "Provide specific, actionable feedback that content creators can implement.";
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}";
247 190
248 - return $prompt;
249 - }
191 + $user_message = $this->create_user_message( $user_prompt );
250 192
251 - /**
252 - * Parse AI response into structured quality analysis data
253 - *
254 - * @param string $response Raw AI response
255 - * @return array|\WP_Error Parsed analysis data
256 - */
257 - private function parse_quality_analysis_response( $response ) {
258 - // Try to extract JSON from the response
259 - $json_start = strpos( $response, '{' );
260 - $json_end = strrpos( $response, '}' );
193 + return [ $system_message, $user_message ];
194 + }
261 195
262 - if ( false === $json_start || false === $json_end ) {
263 - return new \WP_Error( 'parse_error', 'Could not find valid JSON in AI response.' );
264 - }
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 + );
265 207
266 - $json_string = substr( $response, $json_start, $json_end - $json_start + 1 );
267 - $data = json_decode( $json_string, true );
208 + $user_message = $this->create_user_message( $prompt );
268 209
269 - if ( json_last_error() !== JSON_ERROR_NONE ) {
270 - return new \WP_Error( 'json_error', 'Invalid JSON in AI response: ' . json_last_error_msg() );
271 - }
210 + return [ $system_message, $user_message ];
211 + }
272 212
273 - // Validate required fields
274 - $required_fields = array( 'overall_score', 'scores', 'feedback' );
275 - foreach ( $required_fields as $field ) {
276 - if ( ! isset( $data[ $field ] ) ) {
277 - return new \WP_Error( 'missing_field', "Missing required field: {$field}" );
278 - }
279 - }
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' );
280 224
281 - // Ensure scores are within valid range
282 - $data[ 'overall_score' ] = max( 0, min( 100, intval( $data[ 'overall_score' ] ) ) );
225 + // Remove extra whitespace
226 + $content = preg_replace( '/\s+/', ' ', $content );
227 + $content = trim( $content );
283 228
284 - if ( isset( $data[ 'scores' ] ) && is_array( $data[ 'scores' ] ) ) {
285 - foreach ( $data[ 'scores' ] as $key => $score ) {
286 - $data[ 'scores' ][ $key ] = max( 0, min( 100, intval( $score ) ) );
287 - }
288 - }
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 + }
289 239
290 - // Set default priority if not provided
291 - if ( ! isset( $data[ 'priority' ] ) ) {
292 - $overall_score = $data[ 'overall_score' ];
293 - if ( $overall_score >= 80 ) {
294 - $data[ 'priority' ] = 'low';
295 - } elseif ( $overall_score >= 60 ) {
296 - $data[ 'priority' ] = 'medium';
297 - } else {
298 - $data[ 'priority' ] = 'high';
299 - }
300 - }
240 + return $content;
241 + }
301 242
302 - return $data;
303 - }
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 + }
304 251
305 - /**
306 - * Save article quality score as post meta
307 - *
308 - * @param int $post_id Post ID
309 - * @param array $quality_data Quality analysis data
310 - * @return bool Success status
311 - */
312 - public function save_article_quality_score( $post_id, $quality_data ) {
313 - if ( empty( $post_id ) || ! is_array( $quality_data ) ) {
314 - return false;
315 - }
316 -
317 - // Save the complete analysis data
318 - $saved = update_post_meta( $post_id, '_betterdocs_article_quality_analysis', $quality_data );
319 -
320 - // Save just the overall score for easy querying
321 - update_post_meta( $post_id, '_betterdocs_article_quality_score', $quality_data[ 'overall_score' ] );
322 -
323 - // Save analysis timestamp
324 - update_post_meta( $post_id, '_betterdocs_article_quality_analyzed_at', current_time( 'mysql' ) );
325 -
326 - return false !== $saved;
327 - }
328 -
329 - /**
330 - * Get article quality score from post meta
331 - *
332 - * @param int $post_id Post ID
333 - * @return array|false Quality analysis data or false if not found
334 - */
335 - public function get_article_quality_score( $post_id ) {
336 - if ( empty( $post_id ) ) {
337 - return false;
338 - }
339 -
340 - $quality_data = get_post_meta( $post_id, '_betterdocs_article_quality_analysis', true );
341 -
342 - if ( empty( $quality_data ) ) {
343 - return false;
344 - }
345 -
346 - // Add timestamp if available
347 - $analyzed_at = get_post_meta( $post_id, '_betterdocs_article_quality_analyzed_at', true );
348 - if ( $analyzed_at ) {
349 - $quality_data[ 'analyzed_at' ] = $analyzed_at;
350 - }
351 -
352 - return $quality_data;
353 - }
354 -
355 - /**
356 - * Check if article needs re-analysis based on last modified date
357 - *
358 - * @param int $post_id Post ID
359 - * @return bool True if re-analysis is needed
360 - */
361 - public function needs_reanalysis( $post_id ) {
362 - $analyzed_at = get_post_meta( $post_id, '_betterdocs_article_quality_analyzed_at', true );
363 -
364 - if ( empty( $analyzed_at ) ) {
365 - return true; // Never analyzed
366 - }
367 -
368 - $post = get_post( $post_id );
369 - if ( ! $post ) {
370 - return false;
371 - }
372 -
373 - // Check if post was modified after last analysis
374 - $post_modified = strtotime( $post->post_modified );
375 - $analyzed_timestamp = strtotime( $analyzed_at );
376 -
377 - return $post_modified > $analyzed_timestamp;
378 - }
379 -
380 - /**
381 - * Create a system message for OpenAI
382 - *
383 - * @param string $content System message content
384 - * @return array Message array
385 - */
386 - public function create_system_message( $content ) {
387 - return array(
388 - 'role' => 'system',
389 - 'content' => $content
390 - );
391 - }
392 -
393 - /**
394 - * Create a user message for OpenAI
395 - *
396 - * @param string $content User message content
397 - * @return array Message array
398 - */
399 - public function create_user_message( $content ) {
400 - return array(
401 - 'role' => 'user',
402 - 'content' => $content
403 - );
404 - }
405 -
406 - /**
407 - * Create messages array for article summarization
408 - *
409 - * @param string $title Article title
410 - * @param string $content Article content
411 - * @return array Messages array
412 - */
413 - public function create_summary_messages( $title, $content ) {
414 - $system_message = $this->create_system_message(
415 - '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.'
416 - );
417 -
418 - $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}";
419 -
420 - $user_message = $this->create_user_message( $user_prompt );
421 -
422 - return array( $system_message, $user_message );
423 - }
424 -
425 - /**
426 - * Create messages array for content generation
427 - *
428 - * @param string $prompt User prompt
429 - * @param string $keywords Optional keywords
430 - * @return array Messages array
431 - */
432 - public function create_content_messages( $prompt, $keywords = '' ) {
433 - $system_message = $this->create_system_message(
434 - 'You are a helpful assistant who writes documentation for users.'
435 - );
436 -
437 - $user_message = $this->create_user_message( $prompt );
438 -
439 - return array( $system_message, $user_message );
440 - }
441 -
442 - /**
443 - * Sanitize and prepare content for AI processing
444 - *
445 - * @param string $content Raw content
446 - * @param int $max_length Maximum length to keep
447 - * @return string Sanitized content
448 - */
449 - public function prepare_content_for_ai( $content, $max_length = 4000 ) {
450 - // Strip HTML tags and decode entities
451 - $content = wp_strip_all_tags( $content );
452 - $content = html_entity_decode( $content, ENT_QUOTES, 'UTF-8' );
453 -
454 - // Remove extra whitespace
455 - $content = preg_replace( '/\s+/', ' ', $content );
456 - $content = trim( $content );
457 -
458 - // Limit length
459 - if ( strlen( $content ) > $max_length ) {
460 - $content = substr( $content, 0, $max_length );
461 - // Try to cut at a word boundary
462 - $last_space = strrpos( $content, ' ' );
463 - if ( false !== $last_space && $last_space > $max_length * 0.8 ) {
464 - $content = substr( $content, 0, $last_space );
465 - }
466 - $content .= '...';
467 - }
468 -
469 - return $content;
470 - }
471 -
472 - /**
473 - * Check if AI features are enabled
474 - *
475 - * @return bool
476 - */
477 - public function is_ai_enabled() {
478 - return $this->settings->get( 'enable_write_with_ai', true ) && $this->has_api_key();
479 - }
480 -
481 - /**
482 - * Get AI usage statistics (placeholder for future implementation)
483 - *
484 - * @return array Usage statistics
485 - */
486 - public function get_usage_stats() {
487 - // This could be implemented to track API usage, costs, etc.
488 - return array(
489 - 'requests_today' => 0,
490 - 'tokens_used' => 0,
491 - 'cost_estimate' => 0
492 - );
493 - }
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 + }
494 265 }