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/Core/WriteWithAI.php +162 -391 4.5.54.2.6 View file →
@@ -1,319 +1,172 @@
1 1 <?php
2 2
3 - namespace WPDeveloper\BetterDocs\Core;
3 +namespace WPDeveloper\BetterDocs\Core;
4 4
5 -if ( ! defined( 'ABSPATH' ) ) {
6 - exit;
7 -}
5 +use WPDeveloper\BetterDocs\Utils\Base;
6 +use WPDeveloper\BetterDocs\Core\Settings;
7 +use WPDeveloper\BetterDocs\Core\PostType;
8 8
9 +use WPDeveloper\BetterDocs\Utils\Helper;
9 10
10 - use WPDeveloper\BetterDocs\Utils\Base;
11 - use WPDeveloper\BetterDocs\Core\Settings;
12 - use WPDeveloper\BetterDocs\Core\PostType;
11 +class WriteWithAI extends Base {
13 12
14 - use WPDeveloper\BetterDocs\Utils\Helper;
15 - use WPDeveloper\BetterDocs\Utils\AIHelper;
13 + public $settings;
16 14
17 - class WriteWithAI extends Base {
15 + public function __construct( Settings $settings ) {
16 + $this->settings = $settings;
17 + // Get the post ID from the URL
18 + $post_id = isset($_GET['post']) ? intval($_GET['post']) : 0; // phpcs:ignore
18 19
19 - public $settings;
20 + if (!empty($_GET['post_type'])) { // phpcs:ignore
21 + $post_type = $_GET['post_type']; // phpcs:ignore
22 + } elseif ( $post_id > 0 ) {
23 + $post_type = get_post_type( $post_id );
24 + } else {
25 + $post_type = '';
26 + }
20 27
21 - public function __construct( Settings $settings ) {
22 - $this->settings = $settings;
23 - // Get the post ID from the URL
24 - $post_id = isset( $_GET[ 'post' ] ) ? intval( $_GET[ 'post' ] ) : 0; // phpcs:ignore
28 + if ( ! empty( $this->isEnabledWriteWithAI() ) && $post_type == 'docs' ) {
29 + add_action( 'admin_footer', [ $this, 'ai_autowrite_button' ] );
30 + }
31 + add_action( 'wp_ajax_generate_openai_content', [ $this, 'generate_openai_content_callback' ] );
25 32
26 - if ( ! empty( $_GET[ 'post_type' ] ) ) { // phpcs:ignore
27 - $post_type = $_GET[ 'post_type' ]; // phpcs:ignore
28 - } elseif ( $post_id > 0 ) {
29 - $post_type = get_post_type( $post_id );
30 - } else {
31 - $post_type = '';
32 - }
33 + }
33 34
34 - if ( ! empty( $this->isEnabledWriteWithAI() ) && 'docs' == $post_type ) {
35 - add_action( 'admin_footer', array( $this, 'ai_autowrite_button' ) );
36 - add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_ai_edit_assets' ) );
37 - }
38 - add_action( 'wp_ajax_generate_openai_content', array( $this, 'generate_openai_content_callback' ) );
39 - }
35 + public function isEnabledWriteWithAI() {
36 + $isEnableAutoWrite = $this->settings->get( 'enable_write_with_ai', true );
37 + return $isEnableAutoWrite;
38 + }
40 39
41 - public function enqueue_ai_edit_assets( $hook ) {
42 - if ( 'post.php' !== $hook && 'post-new.php' !== $hook ) {
43 - return;
44 - }
40 + public function isValidAPIKey( $apiKey ) {
41 + if ( empty( $apiKey ) ) {
42 + $api_response['valid'] = false;
43 + $api_response['message'] = 'Please Insert your <a href="/admin.php?page=betterdocs-settings">OpenAI API Key</a> to use this Write with AI feature.';
45 44
46 - global $post_type;
47 - if ( 'docs' !== $post_type ) {
48 - return;
49 - }
45 + return $api_response;
46 + }
50 47
51 - if ( empty( $this->get_api_key() ) ) {
52 - return;
53 - }
48 + $ch = curl_init( 'https://api.openai.com/v1/engines' ); //phpcs:ignore WordPress.WP.AlternativeFunctions.curl_curl_init
49 + curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true ); //phpcs:ignore WordPress.WP.AlternativeFunctions.curl_curl_setopt
50 + curl_setopt(
51 + $ch,
52 + CURLOPT_HTTPHEADER,
53 + [ //phpcs:ignore WordPress.WP.AlternativeFunctions.curl_curl_setopt
54 + 'Content-Type: application/json',
55 + 'Authorization: Bearer ' . $apiKey,
56 + ]
57 + );
54 58
55 - betterdocs()->assets->enqueue( 'betterdocs-ai-edit', 'blocks/ai-edit.js' );
56 - betterdocs()->assets->enqueue( 'betterdocs-ai-edit-style', 'blocks/ai-edit-style.css' );
59 + $api_response = [];
57 60
58 - wp_localize_script(
59 - 'betterdocs-ai-edit',
60 - 'betterdocsAIEdit',
61 - array(
62 - 'rest_url' => esc_url_raw( rest_url( 'betterdocs/v1/ai-edit' ) ),
63 - 'rest_nonce' => wp_create_nonce( 'wp_rest' ),
64 - 'post_id' => get_the_ID()
65 - )
66 - );
67 - }
61 + $response = curl_exec( $ch ); //phpcs:ignore WordPress.WP.AlternativeFunctions.curl_curl_exec
62 + $httpCode = curl_getinfo( $ch, CURLINFO_HTTP_CODE ); //phpcs:ignore WordPress.WP.AlternativeFunctions.curl_curl_getinfo
68 63
69 - public function isEnabledWriteWithAI() {
70 - $isEnableAutoWrite = $this->settings->get( 'enable_write_with_ai', true );
71 - return $isEnableAutoWrite;
72 - }
64 + curl_close( $ch ); //phpcs:ignore WordPress.WP.AlternativeFunctions.curl_curl_close
73 65
74 - public function isValidAPIKey( $apiKey ) {
75 - if ( empty( $apiKey ) ) {
76 - $api_response[ 'valid' ] = false;
77 - $api_response[ 'message' ] = 'Please Insert your <a href="/admin.php?page=betterdocs-settings#betterdocs-ai">OpenAI API Key</a> to use this Write with AI feature.';
66 + if ( $httpCode == 200 ) {
67 + $api_response['valid'] = true;
68 + $api_response['message'] = 'Valid API Key';
69 + } else {
70 + $responseData = json_decode( $response, true );
71 + // Access the message data (replace 'data' with the actual key used in the response)
72 + $messageData = $responseData['error'] ? $responseData['error'] : '';
73 + $api_response['valid'] = false;
74 + $api_response['message'] = $messageData['message'] ? $messageData['message'] : 'Invalid API Key';
75 + }
78 76
79 - return $api_response;
80 - }
77 + // print_r($response);
81 78
82 - $api_response = array();
79 + return $api_response;
80 + }
83 81
84 - $response = wp_safe_remote_get(
85 - 'https://api.openai.com/v1/engines',
86 - array(
87 - 'headers' => array(
88 - 'Content-Type' => 'application/json',
89 - 'Authorization' => 'Bearer ' . $apiKey,
90 - ),
91 - 'timeout' => 15,
92 - )
93 - );
82 + public function get_api_key() {
83 + $api_key = $this->settings->get( 'ai_autowrite_api_key', '' );
84 + return $api_key;
85 + }
94 86
95 - if ( is_wp_error( $response ) ) {
96 - $api_response[ 'valid' ] = false;
97 - $api_response[ 'message' ] = $response->get_error_message();
98 - return $api_response;
99 - }
100 87
101 - $httpCode = (int) wp_remote_retrieve_response_code( $response );
102 - $body = wp_remote_retrieve_body( $response );
88 + public function generate_openai_response( $prompt, $keywords ) {
89 + try {
90 + $api_key = $this->settings->get( 'ai_autowrite_api_key', '' );
91 + $max_tokens = $this->settings->get( 'ai_autowrite_max_token', 1500 );
92 + $model = $this->settings->get( 'write_with_ai_model', 'gpt-4o-mini' );
103 93
104 - if ( 200 === $httpCode ) {
105 - $api_response[ 'valid' ] = true;
106 - $api_response[ 'message' ] = 'Valid API Key';
107 - } else {
108 - $responseData = json_decode( $body, true );
109 - $messageData = ! empty( $responseData[ 'error' ] ) ? $responseData[ 'error' ] : array();
110 - $api_response[ 'valid' ] = false;
111 - $api_response[ 'message' ] = ! empty( $messageData[ 'message' ] ) ? $messageData[ 'message' ] : 'Invalid API Key';
112 - }
94 + $api_endpoint = 'https://api.openai.com/v1/chat/completions'; // Update the endpoint based on OpenAI API version
113 95
114 - // print_r($response);
96 + $request_options = array(
97 + 'headers' => array(
98 + 'Content-Type' => 'application/json',
99 + 'Authorization' => 'Bearer ' . $api_key,
100 + ),
101 + 'body' => json_encode(
102 + array( //phpcs:ignore WordPress.WP.AlternativeFunctions.json_encode_json_encode
103 + 'model' => $model, // Add the model parameter here
104 + 'messages' => array(
105 + array(
106 + 'role' => 'system',
107 + 'content' => 'You are a helpful assistant who writes documentation for users.'
108 + ),
109 + array(
110 + 'role' => 'user',
111 + 'content' => $prompt
112 + ),
113 + ),
114 + 'max_tokens' => $max_tokens,
115 115
116 - return $api_response;
117 - }
116 + )
117 + ),
118 + 'timeout' => 50,
119 + );
118 120
119 - public function get_api_key() {
120 - $api_key = $this->settings->get( 'ai_autowrite_api_key', '' );
121 - return $api_key;
122 - }
121 + $response = wp_remote_post( $api_endpoint, $request_options );
123 122
124 - public function get_system_prompt() {
125 - $prompt = <<<'PROMPT'
126 -You are a Senior Technical Writer specializing in comprehensive, high-quality documentation. Your goal is to produce documentation that scores 100/100 on clarity, completeness, and structure.
123 + if ( is_wp_error( $response ) ) {
124 + return 'Error: ' . $response->get_error_message();
125 + } else {
126 + $body = wp_remote_retrieve_body( $response );
127 127
128 -## Output format
128 + $data = json_decode( $body, true );
129 129
130 -Return only HTML body content. Never wrap output in `<!doctype>`, `<html>`, `<head>`, `<body>`, or markdown code fences (no ```html ... ```).
130 + if ( ! empty( $data['error'] ) ) {
131 + return $data['error']['message'];
132 + }
131 133
132 -Use semantic, Gutenberg-friendly tags only:
133 -- Headings: `<h2>`, `<h3>`, `<h4>` (do not emit `<h1>` — it is reserved for the document title)
134 -- Paragraphs: `<p>` for prose
135 -- Lists: `<ul>`/`<ol>` with `<li>` for any list of items, steps, or bullet points — never fake a list with paragraphs or `<br>`
136 -- Links: `<a href="https://...">link text</a>` with absolute URLs
137 -- Inline emphasis: `<strong>`, `<em>`, `<code>`
138 -- Code blocks: `<pre><code>...</code></pre>` for multi-line code or commands
139 -- Quotes: `<blockquote>`
140 -- Tables: `<table>` with `<thead>`, `<tbody>`, `<tr>`, `<th>`, `<td>`
141 -- Images: `<img src="..." alt="...">`
134 + return $data['choices'][0]['message']['content']; // Update this line to get the assistant's message
135 + }
136 + } catch ( Exception $error ) {
137 + return 'Error: ' . $error->getMessage();
138 + }
139 + }
142 140
143 -Apply `<span class="highlight">key term</span>` to important topic terms inside headings and to the first occurrence of each keyword in body text. Use it sparingly — never wrap whole sentences or wrap text inside `href`, `src`, `alt`, or other attributes.
144 141
145 -Do not emit `<style>`, `<script>`, `<iframe>`, `<form>`, or inline `style=""` / `class=""` attributes (other than the `highlight` class above).
146 142
147 -If — and only if — the user's prompt explicitly asks for raw/custom HTML, embed code, or a non-standard structure, follow that request instead of these defaults.
148 -PROMPT;
143 + public function generate_openai_content_callback() {
144 + // Verify the nonce
145 + if (!isset($_POST['ai_nonce']) || !wp_verify_nonce($_POST['ai_nonce'], 'generate_openai_content_nonce')) { //phpcs:ignore
146 + wp_send_json_error( 'Invalid nonce' );
147 + wp_die();
148 + }
149 149
150 - return apply_filters( 'betterdocs_write_with_ai_system_prompt', $prompt );
151 - }
150 + $prompt = sanitize_text_field($_POST['prompt']); //phpcs:ignore
151 + // $num_of_sections = sanitize_text_field($_POST['numOfSections']);
152 + // $num_of_paragraphs = sanitize_text_field($_POST['numOfParagraphs']);
153 + $keywords = sanitize_text_field($_POST['keywords']); //phpcs:ignore
152 154
153 - public function generate_openai_response( $prompt, $keywords ) {
154 - try {
155 - $api_key = $this->settings->get( 'ai_autowrite_api_key', '' );
156 - $max_tokens = $this->settings->get( 'ai_autowrite_max_token', 2500 );
157 - $model = $this->settings->get( 'write_with_ai_model', 'gpt-4o-mini' );
155 + $ai_instance = new WriteWithAI( $this->settings );
158 156
159 - $api_endpoint = 'https://api.openai.com/v1/chat/completions'; // Update the endpoint based on OpenAI API version
157 + $generated_content = $ai_instance->generate_openai_response( $prompt, $keywords );
160 158
161 - $request_body = AIHelper::build_openai_payload(
162 - $model,
163 - array(
164 - array(
165 - 'role' => 'system',
166 - 'content' => $this->get_system_prompt()
167 - ),
168 - array(
169 - 'role' => 'user',
170 - 'content' => $prompt
171 - )
172 - ),
173 - $max_tokens,
174 - null,
175 - 'write_with_ai'
176 - );
159 + // Send the generated content as the AJAX response
160 + wp_send_json_success( $generated_content );
161 + wp_die();
162 + }
177 163
178 - $request_options = array(
179 - 'headers' => array(
180 - 'Content-Type' => 'application/json',
181 - 'Authorization' => 'Bearer ' . $api_key
182 - ),
183 - 'body' => json_encode( $request_body ),
184 - 'timeout' => 300
185 - );
186 164
187 - // GPT-5.5 reasoning can run well past the default limits; give PHP and
188 - // the HTTP call room to finish (still subject to server php-fpm/nginx limits).
189 - if ( function_exists( 'set_time_limit' ) ) {
190 - set_time_limit( 300 ); // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Discouraged -- long-running AI generation needs an extended limit; still bounded by server fpm/nginx timeouts.
191 - }
192 165
193 - $response = wp_remote_post( $api_endpoint, $request_options );
166 + public function ai_autowrite_button() {
194 167
195 - if ( is_wp_error( $response ) ) {
196 - return 'Error: ' . $response->get_error_message();
197 - } else {
198 - $body = wp_remote_retrieve_body( $response );
199 -
200 - $data = json_decode( $body, true );
201 -
202 - if ( ! empty( $data[ 'error' ] ) ) {
203 - return $data[ 'error' ][ 'message' ];
204 - }
205 -
206 - return $data[ 'choices' ][ 0 ][ 'message' ][ 'content' ]; // Update this line to get the assistant's message
207 - }
208 - } catch ( Exception $error ) {
209 - return 'Error: ' . $error->getMessage();
210 - }
211 - }
212 -
213 - public function generate_openai_response_ai_edit( $prompt ) {
214 - $api_key = $this->settings->get( 'ai_autowrite_api_key', '' );
215 - $max_tokens = $this->settings->get( 'ai_autowrite_max_token', 2500 );
216 - $model = $this->settings->get( 'write_with_ai_model', 'gpt-4o-mini' );
217 -
218 - $api_endpoint = 'https://api.openai.com/v1/chat/completions';
219 -
220 - $payload = AIHelper::build_openai_payload(
221 - $model,
222 - array(
223 - array(
224 - 'role' => 'system',
225 - 'content' => $this->get_system_prompt()
226 - ),
227 - array(
228 - 'role' => 'user',
229 - 'content' => $prompt
230 - )
231 - ),
232 - $max_tokens,
233 - null,
234 - 'write_with_ai'
235 - );
236 -
237 - $request_options = array(
238 - 'headers' => array(
239 - 'Content-Type' => 'application/json',
240 - 'Authorization' => 'Bearer ' . $api_key
241 - ),
242 - 'body' => wp_json_encode( $payload ),
243 - 'timeout' => 300
244 - );
245 -
246 - // GPT-5.5 reasoning can run well past the default limits; give PHP and
247 - // the HTTP call room to finish (still subject to server php-fpm/nginx limits).
248 - if ( function_exists( 'set_time_limit' ) ) {
249 - set_time_limit( 300 ); // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Discouraged -- long-running AI generation needs an extended limit; still bounded by server fpm/nginx timeouts.
250 - }
251 -
252 - $response = wp_remote_post( $api_endpoint, $request_options );
253 -
254 - if ( is_wp_error( $response ) ) {
255 - return array(
256 - 'success' => false,
257 - 'error' => $response->get_error_message(),
258 - 'model' => $model
259 - );
260 - }
261 -
262 - $body = wp_remote_retrieve_body( $response );
263 - $data = json_decode( $body, true );
264 -
265 - if ( ! empty( $data[ 'error' ] ) ) {
266 - return array(
267 - 'success' => false,
268 - 'error' => isset( $data[ 'error' ][ 'message' ] ) ? $data[ 'error' ][ 'message' ] : 'OpenAI error',
269 - 'model' => $model,
270 - 'raw' => $data
271 - );
272 - }
273 -
274 - $content = isset( $data[ 'choices' ][ 0 ][ 'message' ][ 'content' ] ) ? $data[ 'choices' ][ 0 ][ 'message' ][ 'content' ] : '';
275 - $usage = isset( $data[ 'usage' ] ) && is_array( $data[ 'usage' ] ) ? $data[ 'usage' ] : array();
276 -
277 - return array(
278 - 'success' => true,
279 - 'content' => $content,
280 - 'model' => $model,
281 - 'prompt_tokens' => isset( $usage[ 'prompt_tokens' ] ) ? (int) $usage[ 'prompt_tokens' ] : null,
282 - 'completion_tokens' => isset( $usage[ 'completion_tokens' ] ) ? (int) $usage[ 'completion_tokens' ] : null,
283 - 'total_tokens' => isset( $usage[ 'total_tokens' ] ) ? (int) $usage[ 'total_tokens' ] : null,
284 - 'finish_reason' => isset( $data[ 'choices' ][ 0 ][ 'finish_reason' ] ) ? $data[ 'choices' ][ 0 ][ 'finish_reason' ] : null
285 - );
286 - }
287 -
288 - public function generate_openai_content_callback() {
289 - // Verify the nonce
290 - $ai_nonce = isset( $_POST['ai_nonce'] ) ? sanitize_text_field( wp_unslash( $_POST['ai_nonce'] ) ) : '';
291 - if ( ! wp_verify_nonce( $ai_nonce, 'generate_openai_content_nonce' ) ) {
292 - wp_send_json_error( 'Invalid nonce' );
293 - wp_die();
294 - }
295 -
296 - if ( ! current_user_can( 'edit_posts' ) ) {
297 - wp_send_json_error( 'Insufficient permissions' );
298 - wp_die();
299 - }
300 -
301 - $prompt = isset( $_POST['prompt'] ) ? sanitize_text_field( wp_unslash( $_POST['prompt'] ) ) : '';
302 - $keywords = isset( $_POST['keywords'] ) ? sanitize_text_field( wp_unslash( $_POST['keywords'] ) ) : '';
303 -
304 - $ai_instance = new WriteWithAI( $this->settings );
305 -
306 - $generated_content = $ai_instance->generate_openai_response( $prompt, $keywords );
307 -
308 - // Send the generated content as the AJAX response
309 - wp_send_json_success( $generated_content );
310 - wp_die();
311 - }
312 -
313 - public function ai_autowrite_button() {
314 -
315 - ?>
168 + ?>
316 169 <script>
317 170 var docsTitle;
318 171
319 172 function responseMessage(message) {
@@ -379,8 +232,14 @@
379 232 setTimeout(() => {
380 233 insertContentToEditor(title, response.data, isOverwrite, keywords);
381 234 docGenerateBtnTxt.innerHTML = reGenerateDocLabel;
382 235 jQuery('.generate-btn').removeAttr('disabled');
236 +
237 + // console.log(jQuery('.betterdocs-ai-autowrite-form-container').data('new-doc-page'));
238 +
239 + // if(jQuery('.betterdocs-ai-autowrite-form-container')?.data('new-doc-page')) {
240 + // docGenerateBtnTxt.innerHTML = reGenerateDocLabel;
241 + // }
383 242 }, 2000);
384 243 } else {
385 244 // alert(response.data.message);
386 245 jQuery('#betterdocs-ai-error-message').parent().removeClass('hidden');
@@ -389,21 +248,10 @@
389 248 jQuery('.generate-btn').removeAttr('disabled');
390 249
391 250 }
392 251 },
393 - error: function(jqXHR, textStatus, errorThrown) {
394 - console.error(jqXHR, textStatus, errorThrown);
395 -
396 - // Reset the UI so a slow/failed request never leaves a permanent "Generating...".
397 - var timedOut = ( textStatus === 'timeout' ) || ( jqXHR && jqXHR.status === 504 ) || ( jqXHR && jqXHR.status === 0 );
398 - var errMessage = timedOut
399 - ? "<?php echo esc_html__( 'The request timed out before a response came back. The AI may still have generated content on the server — try again, or pick a faster model / lower the token limit.', 'betterdocs' ); ?>"
400 - : "<?php echo esc_html__( 'Something went wrong while generating. Please try again.', 'betterdocs' ); ?>";
401 -
402 - jQuery('#betterdocs-ai-error-message').parent().removeClass('hidden');
403 - jQuery('#betterdocs-ai-error-message').html(responseMessage(errMessage));
404 - docGenerateBtnTxt.innerHTML = generateDocLabel;
405 - jQuery('.generate-btn').removeAttr('disabled');
252 + error: function(error) {
253 + console.error(error);
406 254 },
407 255 });
408 256
409 257 }
@@ -427,9 +275,9 @@
427 275 if (!jQuery('#betterdocs-ai-error-message').parent().hasClass('hidden')) {
428 276 jQuery('#betterdocs-ai-error-message').parent().addClass('hidden');
429 277 }
430 278
431 - contentTextArea.value = `Write documentation for '${title}'. Cover these topics in depth: ${keywords}. Use headings, paragraphs, lists, links, code blocks, and tables wherever they help the reader. Follow the output-format rules in the system instructions.`;
279 + contentTextArea.value = `Generate a documentation using HTML heading tags for '${title}'. Include relevant details on ${keywords} in the documentation. Ensure all content is enclosed with <p> tags and apply a <span> tag with the class 'highlight' to headings and topic tags for emphasis.`;
432 280 }
433 281
434 282 function convertMarkdownToHTML(markdownContent) {
435 283 // Simple Markdown to HTML conversion (you may need to enhance this based on your needs)
@@ -459,107 +307,30 @@
459 307 return newDiv.innerHTML;
460 308 }
461 309
462 310
463 - function escapeHtml(str) {
464 - return String(str).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
465 - }
466 -
467 - function escapeRegex(str) {
468 - return String(str).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
469 - }
470 -
471 - function stripCodeFences(htmlString) {
472 - if (!htmlString) {
473 - return '';
474 - }
475 - return htmlString
476 - .replace(/^\s*```(?:html|HTML)?\s*\n?/, '')
477 - .replace(/\n?\s*```\s*$/, '');
478 - }
479 -
480 311 function wrapwithHeighlight(inputString, keywords) {
481 - if (!inputString) {
482 - return '';
483 - }
312 + let modifiedString = inputString.replace(/<(h\d)(.*?)>(.*?)<\/\1>/g, '<$1$2><span class="highlight">$3</span></$1>');
484 313
485 - const container = document.createElement('div');
486 - container.innerHTML = inputString;
314 + const keywordArray = keywords.split(',').map(keyword => keyword.trim());
487 315
488 - // Wrap each heading's visible text with the highlight span (skip if already present)
489 - container.querySelectorAll('h1, h2, h3, h4, h5, h6').forEach(function (heading) {
490 - if (heading.querySelector('.highlight')) {
491 - return;
492 - }
493 - const text = heading.textContent;
494 - if (!text || !text.trim()) {
495 - return;
496 - }
497 - heading.innerHTML = '<span class="highlight">' + escapeHtml(text) + '</span>';
316 + keywordArray.forEach(keyword => {
317 + modifiedString = modifiedString.replace(new RegExp(`\\b(${keyword})\\b`, 'gi'), '<span class="highlight">$1</span>');
498 318 });
499 319
500 - // Wrap keywords only inside text nodes — never inside attributes, code, or existing highlights
501 - const keywordList = (keywords || '')
502 - .split(',')
503 - .map(function (k) { return k.trim(); })
504 - .filter(Boolean);
505 -
506 - if (keywordList.length) {
507 - const pattern = new RegExp('\\b(' + keywordList.map(escapeRegex).join('|') + ')\\b', 'gi');
508 - const walker = document.createTreeWalker(container, NodeFilter.SHOW_TEXT, {
509 - acceptNode: function (node) {
510 - let parent = node.parentNode;
511 - while (parent && parent !== container) {
512 - if (parent.nodeType === 1) {
513 - const tag = parent.tagName.toLowerCase();
514 - if (tag === 'code' || tag === 'pre') {
515 - return NodeFilter.FILTER_REJECT;
516 - }
517 - if (parent.classList && parent.classList.contains('highlight')) {
518 - return NodeFilter.FILTER_REJECT;
519 - }
520 - }
521 - parent = parent.parentNode;
522 - }
523 - return NodeFilter.FILTER_ACCEPT;
524 - }
525 - });
526 -
527 - const textNodes = [];
528 - let current;
529 - while ((current = walker.nextNode())) {
530 - textNodes.push(current);
531 - }
532 -
533 - textNodes.forEach(function (textNode) {
534 - pattern.lastIndex = 0;
535 - if (!pattern.test(textNode.nodeValue)) {
536 - return;
537 - }
538 - pattern.lastIndex = 0;
539 - const fragmentHost = document.createElement('span');
540 - fragmentHost.innerHTML = escapeHtml(textNode.nodeValue).replace(pattern, '<span class="highlight">$1</span>');
541 - const parent = textNode.parentNode;
542 - while (fragmentHost.firstChild) {
543 - parent.insertBefore(fragmentHost.firstChild, textNode);
544 - }
545 - parent.removeChild(textNode);
546 - });
547 - }
548 -
549 - return container.innerHTML;
320 + return modifiedString;
550 321 }
551 322
552 323 function getBodyContent(htmlString) {
553 - if (!htmlString) {
554 - return '';
555 - }
556 - const cleaned = stripCodeFences(htmlString);
557 - const match = cleaned.match(/<body[^>]*>([\s\S]*?)<\/body>/i);
324 + var match = htmlString.match(/<body[^>]*>([\s\S]*?)<\/body>/i);
325 +
326 + // Check if match is null before accessing match[1]
558 327 if (match) {
328 + console.log(match[1].replace(/<p>\s+/g, '<p>'));
559 329 return match[1].replace(/<p>\s+/g, '<p>');
330 + } else {
331 + return '';
560 332 }
561 - return cleaned;
562 333 }
563 334
564 335
565 336
@@ -572,9 +343,9 @@
572 343
573 344 htmlContent = getBodyContent(htmlContent);
574 345 htmlContent = removeFirstHeading(htmlContent);
575 346 htmlContent = wrapwithHeighlight(htmlContent, keywords);
576 - const isPostContent = `<?php echo isset( $_GET['post'] ) ? esc_html( get_the_content( absint( wp_unslash( $_GET['post'] ) ) ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only post id from URL. ?>`;
347 + const isPostContent = `<?php echo isset($_GET['post']) ? esc_html(get_the_content($_GET['post'])) : ''; // phpcs:ignore ?>`;
577 348
578 349 const blocks = wp.blocks.rawHandler({
579 350 HTML: htmlContent
580 351 });
@@ -627,9 +398,9 @@
627 398 let currentKeywords = jQuery('#betterdocs-ai-keyword').val();
628 399 if (!currentKeywords) {
629 400 currentKeywords = '{Documentation Keywords}';
630 401 }
631 - const currentPrompt = `Write documentation for '${docsTitle?docsTitle:'{Documentation title}'}'. Cover these topics in depth: ${currentKeywords}. Use headings, paragraphs, lists, links, code blocks, and tables wherever they help the reader. Follow the output-format rules in the system instructions.`;
402 + const currentPrompt = `Generate documentation using HTML heading tags for '${docsTitle?docsTitle:'{Documentation title}'}'. Include relevant details on ${currentKeywords} in the documentation. Ensure all content is enclosed with <p> tags and apply a <span> tag with the class 'highlight' to headings and topic tags for emphasis.`;
632 403 jQuery('#betterdocs-ai-content').val(currentPrompt);
633 404
634 405 jQuery('#betterdocs-ai-title').val(docsTitle);
635 406
@@ -646,14 +417,14 @@
646 417
647 418
648 419 function writeWithAIForm() {
649 420
650 - let title = `<?php echo isset( $_GET['post'] ) ? esc_html( get_the_title( absint( wp_unslash( $_GET['post'] ) ) ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only post id from URL. ?>`;
421 + let title = `<?php echo isset($_GET['post']) ? esc_html(get_the_title($_GET['post'])) : ''; // phpcs:ignore ?>`;
651 422 if (docsTitle) {
652 423 title = docsTitle;
653 424 }
654 425
655 - const promtTitle = `<?php echo isset( $_GET['post'] ) ? esc_html( get_the_title( absint( wp_unslash( $_GET['post'] ) ) ) ) : '{Documentation Title}'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only post id from URL. ?>`;
426 + const promtTitle = `<?php echo isset($_GET['post']) ? esc_html(get_the_title($_GET['post'])) : '{Documentation Title}'; // phpcs:ignore ?>`;
656 427 const titlePlaceholder = "<?php echo esc_attr__( 'Enter a descriptive title for your documentation.', 'betterdocs' ); ?>";
657 428 const keywords = "<?php echo esc_attr( '{Documentation Keywords}' ); ?>";
658 429 const keywordsPlaceholder = "<?php echo esc_attr__( 'Add keywords to generate precise & relevant documentation (comma-separated).', 'betterdocs' ); ?>";
659 430 const language = "<?php echo esc_attr( 'English' ); ?>";
@@ -668,18 +439,18 @@
668 439 const checkboxLabel = "<?php echo esc_html__( 'Overwrite your existing Doc:', 'betterdocs' ); ?>";
669 440 const nonce = "<?php echo esc_attr( wp_create_nonce( 'generate_openai_content_nonce' ) ); ?>";
670 441
671 442 <?php
672 - $is_valid = $this->get_api_key();
673 - $disable_field = 'disabled-input-field';
674 - if ( $this->get_api_key() ) {
675 - $disable_field = '';
676 - }
677 - ?>
443 + $is_valid = $this->get_api_key();
444 + $disable_field = 'disabled-input-field';
445 + if ( $this->get_api_key() ) {
446 + $disable_field = '';
447 + }
448 + ?>
678 449
679 450 let hiddenClass = 'hidden';
680 451 let newDocPageAtt = 'data-new-doc-page="true"';
681 - const isPostContent = `<?php echo isset( $_GET['post'] ) ? esc_html( get_the_content( absint( wp_unslash( $_GET['post'] ) ) ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only post id from URL. ?>`;
452 + const isPostContent = `<?php echo isset($_GET['post']) ? esc_html(get_the_content($_GET['post'])) : ''; // phpcs:ignore ?>`;
682 453
683 454 if (isPostContent !== '') {
684 455 hiddenClass = '';
685 456 newDocPageAtt = '';
@@ -689,9 +460,9 @@
689 460 // const prompt = `Make a knowledge base article with html heading tags about [${title}] in [${language}]. Here is some topic to describe the article: [${keywords}]. and wrap the content with p tag also add a span tag with a class named 'highlight' to all the heading and topic tags in the article.`;
690 461
691 462 // const prompt = `Create a details knowledge base article in [${language}] about [${title}] with html heading tags. Here is some topic to describe the article: [${keywords}]. And wrap the content with p tag also add a span tag with a class named 'highlight' to all the heading and topic tags in the article.`;
692 463
693 - const prompt = `Write documentation for '${promtTitle}'. Cover these topics in depth: ${keywords}. Use headings, paragraphs, lists, links, code blocks, and tables wherever they help the reader. Follow the output-format rules in the system instructions.`;
464 + const prompt = `Generate a documentation using HTML heading tags for '${promtTitle}'. Include relevant details on ${keywords} in the documentation. Ensure all content is enclosed with <p> tags and apply a <span> tag with the class 'highlight' to headings and topic tags for emphasis.`;
694 465
695 466 const aiIcon = `<svg xmlns="http://www.w3.org/2000/svg" width="21" height="20" viewBox="0 0 21 20" fill="none">
696 467 <g clip-path="url(#clip0_2460_1729)">
697 468 <path d="M10.3956 18.8608L6.10991 19.2322L6.48134 14.9465L15.3956 6.08936C15.5287 5.95329 15.6876 5.84518 15.863 5.77137C16.0384 5.69756 16.2268 5.65954 16.4171 5.65954C16.6074 5.65954 16.7957 5.69756 16.9711 5.77137C17.1466 5.84518 17.3054 5.95329 17.4385 6.08936L19.2528 7.91793C19.3865 8.05083 19.4926 8.20886 19.565 8.38293C19.6375 8.55701 19.6747 8.74368 19.6747 8.93221C19.6747 9.12075 19.6375 9.30742 19.565 9.48149C19.4926 9.65557 19.3865 9.8136 19.2528 9.9465L10.3956 18.8608ZM1.7042 5.67507C1.20277 5.58793 1.20277 4.86793 1.7042 4.78079C2.59203 4.62626 3.41374 4.21085 4.06456 3.58751C4.71538 2.96417 5.16583 2.16113 5.35848 1.28079L5.38848 1.14221C5.49705 0.647929 6.20277 0.643643 6.31705 1.13936L6.35277 1.29936C6.55248 2.17579 7.0067 2.97367 7.65837 3.59281C8.31005 4.21195 9.13013 4.62475 10.0156 4.77936C10.5199 4.86507 10.5199 5.58936 10.0156 5.67793C9.13005 5.83218 8.3098 6.24465 7.65787 6.86354C7.00594 7.48243 6.5514 8.28014 6.35134 9.1565L6.3142 9.31793C6.20134 9.81221 5.49563 9.80936 5.38705 9.31364L5.35848 9.17507C5.16564 8.29433 4.71476 7.49102 4.06339 6.86764C3.41202 6.24426 2.58969 5.82908 1.70134 5.67507H1.7042Z" stroke="white" stroke-width="1.42857" stroke-linecap="round" stroke-linejoin="round"/>
@@ -728,14 +499,14 @@
728 499 <div class="autowrite-subheadding">
729 500 <p><?php echo esc_html__( 'Generate documentation effortlessly with BetterDocs AI. Simply input your doc title, keywords, prompt and let the system automatically generate comprehensive documentation tailored to your needs.', 'betterdocs' ); ?></p>
730 501 </div>
731 502
732 - <?php if ( empty( $this->get_api_key() ) ): ?>
503 + <?php if ( empty( $this->get_api_key() ) ) : ?>
733 504 <div id="betterdocs-ai-message">
734 505 <div class="warning-message">
735 506 <span class="dashicons dashicons-warning"></span>
736 507 <div>
737 - <?php echo wp_kses_post( 'Please Insert your <a target="_blank" href="' . esc_url( admin_url( 'admin.php?page=betterdocs-settings#betterdocs-ai' ) ) . '">OpenAI API Key</a> to use this Write with AI feature.', 'betterdocs' ); ?>
508 + <?php echo wp_kses_post( 'Please Insert your <a target="_blank" href="' . esc_url( admin_url( 'admin.php?page=betterdocs-settings&tab=tab-ai-autowrite' ) ) . '">OpenAI API Key</a> to use this Write with AI feature.', 'betterdocs' ); ?>
738 509 </div>
739 510 </div>
740 511
741 512 </div>
@@ -1347,6 +1118,6 @@
1347 1118
1348 1119 }
1349 1120 </style>
1350 1121 <?php
1351 - }
1352 - }
1122 + }
1123 +}