PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 3.8.9
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v3.8.9
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 3.5.2 All 199 releases
← All changes | includes/Core/WriteWithAI.php +168 -402 4.5.53.8.9 View file →
@@ -1,319 +1,168 @@
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' ] );
32 + }
25 33
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 - }
34 + public function isEnabledWriteWithAI() {
35 + $isEnableAutoWrite = $this->settings->get( 'enable_write_with_ai', true );
36 + return $isEnableAutoWrite;
37 + }
33 38
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 - }
39 + public function isValidAPIKey( $apiKey ) {
40 + if ( empty( $apiKey ) ) {
41 + $api_response['valid'] = false;
42 + $api_response['message'] = 'Please Insert your <a href="/admin.php?page=betterdocs-settings">OpenAI API Key</a> to use this Write with AI feature.';
40 43
41 - public function enqueue_ai_edit_assets( $hook ) {
42 - if ( 'post.php' !== $hook && 'post-new.php' !== $hook ) {
43 - return;
44 - }
44 + return $api_response;
45 + }
45 46
46 - global $post_type;
47 - if ( 'docs' !== $post_type ) {
48 - return;
49 - }
47 + $ch = curl_init( 'https://api.openai.com/v1/engines' ); //phpcs:ignore WordPress.WP.AlternativeFunctions.curl_curl_init
48 + curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true ); //phpcs:ignore WordPress.WP.AlternativeFunctions.curl_curl_setopt
49 + curl_setopt(
50 + $ch,
51 + CURLOPT_HTTPHEADER,
52 + [ //phpcs:ignore WordPress.WP.AlternativeFunctions.curl_curl_setopt
53 + 'Content-Type: application/json',
54 + 'Authorization: Bearer ' . $apiKey,
55 + ]
56 + );
50 57
51 - if ( empty( $this->get_api_key() ) ) {
52 - return;
53 - }
58 + $api_response = [];
54 59
55 - betterdocs()->assets->enqueue( 'betterdocs-ai-edit', 'blocks/ai-edit.js' );
56 - betterdocs()->assets->enqueue( 'betterdocs-ai-edit-style', 'blocks/ai-edit-style.css' );
60 + $response = curl_exec( $ch ); //phpcs:ignore WordPress.WP.AlternativeFunctions.curl_curl_exec
61 + $httpCode = curl_getinfo( $ch, CURLINFO_HTTP_CODE ); //phpcs:ignore WordPress.WP.AlternativeFunctions.curl_curl_getinfo
57 62
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 - }
63 + curl_close( $ch ); //phpcs:ignore WordPress.WP.AlternativeFunctions.curl_curl_close
68 64
69 - public function isEnabledWriteWithAI() {
70 - $isEnableAutoWrite = $this->settings->get( 'enable_write_with_ai', true );
71 - return $isEnableAutoWrite;
72 - }
65 + if ( $httpCode == 200 ) {
66 + $api_response['valid'] = true;
67 + $api_response['message'] = 'Valid API Key';
68 + } else {
69 + $responseData = json_decode( $response, true );
70 + // Access the message data (replace 'data' with the actual key used in the response)
71 + $messageData = $responseData['error'] ? $responseData['error'] : '';
72 + $api_response['valid'] = false;
73 + $api_response['message'] = $messageData['message'] ? $messageData['message'] : 'Invalid API Key';
74 + }
73 75
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.';
76 + // print_r($response);
78 77
79 - return $api_response;
80 - }
78 + return $api_response;
79 + }
81 80
82 - $api_response = array();
81 + public function get_api_key() {
82 + $api_key = $this->settings->get( 'ai_autowrite_api_key', '' );
83 + return $api_key;
84 + }
83 85
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 - );
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 - }
87 + public function generate_openai_response( $prompt, $keywords ) {
88 + try {
89 + $api_key = $this->settings->get( 'ai_autowrite_api_key', '' );
90 + $max_tokens = $this->settings->get( 'ai_autowrite_max_token', 1500 );
100 91
101 - $httpCode = (int) wp_remote_retrieve_response_code( $response );
102 - $body = wp_remote_retrieve_body( $response );
92 + $api_endpoint = 'https://api.openai.com/v1/chat/completions'; // Update the endpoint based on OpenAI API version
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 + $request_options = array(
95 + 'headers' => array(
96 + 'Content-Type' => 'application/json',
97 + 'Authorization' => 'Bearer ' . $api_key,
98 + ),
99 + 'body' => json_encode(
100 + array( //phpcs:ignore WordPress.WP.AlternativeFunctions.json_encode_json_encode
101 + 'model' => 'gpt-4o-mini', // Add the model parameter here
102 + 'messages' => array(
103 + array(
104 + 'role' => 'system',
105 + 'content' => 'You are a helpful assistant who writes documentation for users.'
106 + ),
107 + array(
108 + 'role' => 'user',
109 + 'content' => $prompt
110 + ),
111 + ),
112 + 'max_tokens' => $max_tokens,
113 113
114 - // print_r($response);
114 + )
115 + ),
116 + 'timeout' => 50,
117 + );
115 118
116 - return $api_response;
117 - }
119 + $response = wp_remote_post( $api_endpoint, $request_options );
118 120
119 - public function get_api_key() {
120 - $api_key = $this->settings->get( 'ai_autowrite_api_key', '' );
121 - return $api_key;
122 - }
121 + if ( is_wp_error( $response ) ) {
122 + return 'Error: ' . $response->get_error_message();
123 + } else {
124 + $body = wp_remote_retrieve_body( $response );
123 125
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.
126 + $data = json_decode( $body, true );
127 127
128 -## Output format
128 + if ( ! empty( $data['error'] ) ) {
129 + return $data['error']['message'];
130 + }
129 131
130 -Return only HTML body content. Never wrap output in `<!doctype>`, `<html>`, `<head>`, `<body>`, or markdown code fences (no ```html ... ```).
132 + return $data['choices'][0]['message']['content']; // Update this line to get the assistant's message
133 + }
134 + } catch ( Exception $error ) {
135 + return 'Error: ' . $error->getMessage();
136 + }
137 + }
131 138
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="...">`
142 139
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 140
145 -Do not emit `<style>`, `<script>`, `<iframe>`, `<form>`, or inline `style=""` / `class=""` attributes (other than the `highlight` class above).
141 + public function generate_openai_content_callback() {
142 + // Verify the nonce
143 + if (!isset($_POST['ai_nonce']) || !wp_verify_nonce($_POST['ai_nonce'], 'generate_openai_content_nonce')) { //phpcs:ignore
144 + wp_send_json_error( 'Invalid nonce' );
145 + wp_die();
146 + }
146 147
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;
148 + $prompt = sanitize_text_field($_POST['prompt']); //phpcs:ignore
149 + // $num_of_sections = sanitize_text_field($_POST['numOfSections']);
150 + // $num_of_paragraphs = sanitize_text_field($_POST['numOfParagraphs']);
151 + $keywords = sanitize_text_field($_POST['keywords']); //phpcs:ignore
149 152
150 - return apply_filters( 'betterdocs_write_with_ai_system_prompt', $prompt );
151 - }
153 + $ai_instance = new WriteWithAI( $this->settings );
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 + $generated_content = $ai_instance->generate_openai_response( $prompt, $keywords );
158 156
159 - $api_endpoint = 'https://api.openai.com/v1/chat/completions'; // Update the endpoint based on OpenAI API version
157 + // Send the generated content as the AJAX response
158 + wp_send_json_success( $generated_content );
159 + wp_die();
160 + }
160 161
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 - );
162 + public function ai_autowrite_button() {
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 -
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 -
193 - $response = wp_remote_post( $api_endpoint, $request_options );
194 -
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 - ?>
164 + ?>
316 165 <script>
317 166 var docsTitle;
318 167
319 168 function responseMessage(message) {
@@ -379,8 +228,14 @@
379 228 setTimeout(() => {
380 229 insertContentToEditor(title, response.data, isOverwrite, keywords);
381 230 docGenerateBtnTxt.innerHTML = reGenerateDocLabel;
382 231 jQuery('.generate-btn').removeAttr('disabled');
232 +
233 + // console.log(jQuery('.betterdocs-ai-autowrite-form-container').data('new-doc-page'));
234 +
235 + // if(jQuery('.betterdocs-ai-autowrite-form-container')?.data('new-doc-page')) {
236 + // docGenerateBtnTxt.innerHTML = reGenerateDocLabel;
237 + // }
383 238 }, 2000);
384 239 } else {
385 240 // alert(response.data.message);
386 241 jQuery('#betterdocs-ai-error-message').parent().removeClass('hidden');
@@ -389,21 +244,10 @@
389 244 jQuery('.generate-btn').removeAttr('disabled');
390 245
391 246 }
392 247 },
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');
248 + error: function(error) {
249 + console.error(error);
406 250 },
407 251 });
408 252
409 253 }
@@ -427,9 +271,9 @@
427 271 if (!jQuery('#betterdocs-ai-error-message').parent().hasClass('hidden')) {
428 272 jQuery('#betterdocs-ai-error-message').parent().addClass('hidden');
429 273 }
430 274
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.`;
275 + 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 276 }
433 277
434 278 function convertMarkdownToHTML(markdownContent) {
435 279 // Simple Markdown to HTML conversion (you may need to enhance this based on your needs)
@@ -459,107 +303,30 @@
459 303 return newDiv.innerHTML;
460 304 }
461 305
462 306
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 307 function wrapwithHeighlight(inputString, keywords) {
481 - if (!inputString) {
482 - return '';
483 - }
308 + let modifiedString = inputString.replace(/<(h\d)(.*?)>(.*?)<\/\1>/g, '<$1$2><span class="highlight">$3</span></$1>');
484 309
485 - const container = document.createElement('div');
486 - container.innerHTML = inputString;
310 + const keywordArray = keywords.split(',').map(keyword => keyword.trim());
487 311
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>';
312 + keywordArray.forEach(keyword => {
313 + modifiedString = modifiedString.replace(new RegExp(`\\b(${keyword})\\b`, 'gi'), '<span class="highlight">$1</span>');
498 314 });
499 315
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;
316 + return modifiedString;
550 317 }
551 318
552 319 function getBodyContent(htmlString) {
553 - if (!htmlString) {
554 - return '';
555 - }
556 - const cleaned = stripCodeFences(htmlString);
557 - const match = cleaned.match(/<body[^>]*>([\s\S]*?)<\/body>/i);
320 + var match = htmlString.match(/<body[^>]*>([\s\S]*?)<\/body>/i);
321 +
322 + // Check if match is null before accessing match[1]
558 323 if (match) {
324 + console.log(match[1].replace(/<p>\s+/g, '<p>'));
559 325 return match[1].replace(/<p>\s+/g, '<p>');
326 + } else {
327 + return '';
560 328 }
561 - return cleaned;
562 329 }
563 330
564 331
565 332
@@ -572,9 +339,9 @@
572 339
573 340 htmlContent = getBodyContent(htmlContent);
574 341 htmlContent = removeFirstHeading(htmlContent);
575 342 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. ?>`;
343 + const isPostContent = `<?php echo isset($_GET['post']) ? esc_html(get_the_content($_GET['post'])) : ''; // phpcs:ignore ?>`;
577 344
578 345 const blocks = wp.blocks.rawHandler({
579 346 HTML: htmlContent
580 347 });
@@ -627,9 +394,9 @@
627 394 let currentKeywords = jQuery('#betterdocs-ai-keyword').val();
628 395 if (!currentKeywords) {
629 396 currentKeywords = '{Documentation Keywords}';
630 397 }
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.`;
398 + 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 399 jQuery('#betterdocs-ai-content').val(currentPrompt);
633 400
634 401 jQuery('#betterdocs-ai-title').val(docsTitle);
635 402
@@ -646,14 +413,14 @@
646 413
647 414
648 415 function writeWithAIForm() {
649 416
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. ?>`;
417 + let title = `<?php echo isset($_GET['post']) ? esc_html(get_the_title($_GET['post'])) : ''; // phpcs:ignore ?>`;
651 418 if (docsTitle) {
652 419 title = docsTitle;
653 420 }
654 421
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. ?>`;
422 + const promtTitle = `<?php echo isset($_GET['post']) ? esc_html(get_the_title($_GET['post'])) : '{Documentation Title}'; // phpcs:ignore ?>`;
656 423 const titlePlaceholder = "<?php echo esc_attr__( 'Enter a descriptive title for your documentation.', 'betterdocs' ); ?>";
657 424 const keywords = "<?php echo esc_attr( '{Documentation Keywords}' ); ?>";
658 425 const keywordsPlaceholder = "<?php echo esc_attr__( 'Add keywords to generate precise & relevant documentation (comma-separated).', 'betterdocs' ); ?>";
659 426 const language = "<?php echo esc_attr( 'English' ); ?>";
@@ -668,18 +435,18 @@
668 435 const checkboxLabel = "<?php echo esc_html__( 'Overwrite your existing Doc:', 'betterdocs' ); ?>";
669 436 const nonce = "<?php echo esc_attr( wp_create_nonce( 'generate_openai_content_nonce' ) ); ?>";
670 437
671 438 <?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 - ?>
439 + $is_valid = $this->get_api_key();
440 + $disable_field = 'disabled-input-field';
441 + if ( $this->get_api_key() ) {
442 + $disable_field = '';
443 + }
444 + ?>
678 445
679 446 let hiddenClass = 'hidden';
680 447 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. ?>`;
448 + const isPostContent = `<?php echo isset($_GET['post']) ? esc_html(get_the_content($_GET['post'])) : ''; // phpcs:ignore ?>`;
682 449
683 450 if (isPostContent !== '') {
684 451 hiddenClass = '';
685 452 newDocPageAtt = '';
@@ -689,9 +456,9 @@
689 456 // 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 457
691 458 // 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 459
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.`;
460 + 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 461
695 462 const aiIcon = `<svg xmlns="http://www.w3.org/2000/svg" width="21" height="20" viewBox="0 0 21 20" fill="none">
696 463 <g clip-path="url(#clip0_2460_1729)">
697 464 <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 +495,14 @@
728 495 <div class="autowrite-subheadding">
729 496 <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 497 </div>
731 498
732 - <?php if ( empty( $this->get_api_key() ) ): ?>
499 + <?php if ( empty( $this->get_api_key() ) ) : ?>
733 500 <div id="betterdocs-ai-message">
734 501 <div class="warning-message">
735 502 <span class="dashicons dashicons-warning"></span>
736 503 <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' ); ?>
504 + <?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 505 </div>
739 506 </div>
740 507
741 508 </div>
@@ -871,10 +638,8 @@
871 638 }
872 639
873 640 .betterdocs-ai-button-container {
874 641 margin-left: 10px;
875 - white-space: nowrap;
876 - max-width: 145px;
877 642 }
878 643
879 644 .autowrite-icon {
880 645 display: flex;
@@ -944,9 +709,8 @@
944 709 display: flex;
945 710 align-items: center;
946 711 justify-content: space-between;
947 712 gap: 10px;
948 - width: 135px;
949 713 }
950 714
951 715 .betterdocs-ai-button img {
952 716 width: 20px;
@@ -1251,18 +1015,20 @@
1251 1015 font-size: 18px;
1252 1016 font-weight: bold;
1253 1017 float: right;
1254 1018 position: absolute;
1255 - top: -16px;
1256 - border-radius: 50%;
1019 + right: 1px;
1020 + top: 1px;
1021 + padding: 10px;
1257 1022 background: #ffffff;
1258 - width: 40px;
1259 - height: 40px;
1023 + border-radius: 25px;
1024 + width: 20px;
1025 + height: 20px;
1260 1026 display: flex;
1261 1027 align-items: center;
1262 1028 justify-content: center;
1263 1029 color: #281617;
1264 - right: -42px;
1030 + right: -60px;
1265 1031 }
1266 1032
1267 1033 div#betterdocs-ai-error-message,
1268 1034 #betterdocs-ai-message {
@@ -1347,6 +1113,6 @@
1347 1113
1348 1114 }
1349 1115 </style>
1350 1116 <?php
1351 - }
1352 - }
1117 + }
1118 +}