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.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 +162 -384 4.5.44.2.6 View file →
@@ -1,312 +1,172 @@
1 1 <?php
2 2
3 - namespace WPDeveloper\BetterDocs\Core;
3 +namespace WPDeveloper\BetterDocs\Core;
4 4
5 - use WPDeveloper\BetterDocs\Utils\Base;
6 - use WPDeveloper\BetterDocs\Core\Settings;
7 - use WPDeveloper\BetterDocs\Core\PostType;
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;
10 - use WPDeveloper\BetterDocs\Utils\AIHelper;
9 +use WPDeveloper\BetterDocs\Utils\Helper;
11 10
12 - class WriteWithAI extends Base {
11 +class WriteWithAI extends Base {
13 12
14 - public $settings;
13 + public $settings;
15 14
16 - public function __construct( Settings $settings ) {
17 - $this->settings = $settings;
18 - // Get the post ID from the URL
19 - $post_id = isset( $_GET[ 'post' ] ) ? intval( $_GET[ 'post' ] ) : 0; // phpcs:ignore
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
20 19
21 - if ( ! empty( $_GET[ 'post_type' ] ) ) { // phpcs:ignore
22 - $post_type = $_GET[ 'post_type' ]; // phpcs:ignore
23 - } elseif ( $post_id > 0 ) {
24 - $post_type = get_post_type( $post_id );
25 - } else {
26 - $post_type = '';
27 - }
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 + }
28 27
29 - if ( ! empty( $this->isEnabledWriteWithAI() ) && 'docs' == $post_type ) {
30 - add_action( 'admin_footer', array( $this, 'ai_autowrite_button' ) );
31 - add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_ai_edit_assets' ) );
32 - }
33 - add_action( 'wp_ajax_generate_openai_content', array( $this, 'generate_openai_content_callback' ) );
34 - }
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' ] );
35 32
36 - public function enqueue_ai_edit_assets( $hook ) {
37 - if ( 'post.php' !== $hook && 'post-new.php' !== $hook ) {
38 - return;
39 - }
33 + }
40 34
41 - global $post_type;
42 - if ( 'docs' !== $post_type ) {
43 - return;
44 - }
35 + public function isEnabledWriteWithAI() {
36 + $isEnableAutoWrite = $this->settings->get( 'enable_write_with_ai', true );
37 + return $isEnableAutoWrite;
38 + }
45 39
46 - if ( empty( $this->get_api_key() ) ) {
47 - return;
48 - }
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.';
49 44
50 - betterdocs()->assets->enqueue( 'betterdocs-ai-edit', 'blocks/ai-edit.js' );
51 - betterdocs()->assets->enqueue( 'betterdocs-ai-edit-style', 'blocks/ai-edit-style.css' );
45 + return $api_response;
46 + }
52 47
53 - wp_localize_script(
54 - 'betterdocs-ai-edit',
55 - 'betterdocsAIEdit',
56 - array(
57 - 'rest_url' => esc_url_raw( rest_url( 'betterdocs/v1/ai-edit' ) ),
58 - 'rest_nonce' => wp_create_nonce( 'wp_rest' ),
59 - 'post_id' => get_the_ID()
60 - )
61 - );
62 - }
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 + );
63 58
64 - public function isEnabledWriteWithAI() {
65 - $isEnableAutoWrite = $this->settings->get( 'enable_write_with_ai', true );
66 - return $isEnableAutoWrite;
67 - }
59 + $api_response = [];
68 60
69 - public function isValidAPIKey( $apiKey ) {
70 - if ( empty( $apiKey ) ) {
71 - $api_response[ 'valid' ] = false;
72 - $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.';
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
73 63
74 - return $api_response;
75 - }
64 + curl_close( $ch ); //phpcs:ignore WordPress.WP.AlternativeFunctions.curl_curl_close
76 65
77 - $ch = curl_init( 'https://api.openai.com/v1/engines' ); //phpcs:ignore WordPress.WP.AlternativeFunctions.curl_curl_init
78 - curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true ); //phpcs:ignore WordPress.WP.AlternativeFunctions.curl_curl_setopt
79 - curl_setopt(
80 - $ch,
81 - CURLOPT_HTTPHEADER,
82 - array( //phpcs:ignore WordPress.WP.AlternativeFunctions.curl_curl_setopt
83 - 'Content-Type: application/json',
84 - 'Authorization: Bearer ' . $apiKey
85 - )
86 - );
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 + }
87 76
88 - $api_response = array();
77 + // print_r($response);
89 78
90 - $response = curl_exec( $ch ); //phpcs:ignore WordPress.WP.AlternativeFunctions.curl_curl_exec
91 - $httpCode = curl_getinfo( $ch, CURLINFO_HTTP_CODE ); //phpcs:ignore WordPress.WP.AlternativeFunctions.curl_curl_getinfo
79 + return $api_response;
80 + }
92 81
93 - curl_close( $ch ); //phpcs:ignore WordPress.WP.AlternativeFunctions.curl_curl_close
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 ( 200 == $httpCode ) {
96 - $api_response[ 'valid' ] = true;
97 - $api_response[ 'message' ] = 'Valid API Key';
98 - } else {
99 - $responseData = json_decode( $response, true );
100 - // Access the message data (replace 'data' with the actual key used in the response)
101 - $messageData = $responseData[ 'error' ] ? $responseData[ 'error' ] : '';
102 - $api_response[ 'valid' ] = false;
103 - $api_response[ 'message' ] = $messageData[ 'message' ] ? $messageData[ 'message' ] : 'Invalid API Key';
104 - }
105 87
106 - // print_r($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' );
107 93
108 - return $api_response;
109 - }
94 + $api_endpoint = 'https://api.openai.com/v1/chat/completions'; // Update the endpoint based on OpenAI API version
110 95
111 - public function get_api_key() {
112 - $api_key = $this->settings->get( 'ai_autowrite_api_key', '' );
113 - return $api_key;
114 - }
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 - public function get_system_prompt() {
117 - $prompt = <<<'PROMPT'
118 -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.
116 + )
117 + ),
118 + 'timeout' => 50,
119 + );
119 120
120 -## Output format
121 + $response = wp_remote_post( $api_endpoint, $request_options );
121 122
122 -Return only HTML body content. Never wrap output in `<!doctype>`, `<html>`, `<head>`, `<body>`, or markdown code fences (no ```html ... ```).
123 + if ( is_wp_error( $response ) ) {
124 + return 'Error: ' . $response->get_error_message();
125 + } else {
126 + $body = wp_remote_retrieve_body( $response );
123 127
124 -Use semantic, Gutenberg-friendly tags only:
125 -- Headings: `<h2>`, `<h3>`, `<h4>` (do not emit `<h1>` — it is reserved for the document title)
126 -- Paragraphs: `<p>` for prose
127 -- Lists: `<ul>`/`<ol>` with `<li>` for any list of items, steps, or bullet points — never fake a list with paragraphs or `<br>`
128 -- Links: `<a href="https://...">link text</a>` with absolute URLs
129 -- Inline emphasis: `<strong>`, `<em>`, `<code>`
130 -- Code blocks: `<pre><code>...</code></pre>` for multi-line code or commands
131 -- Quotes: `<blockquote>`
132 -- Tables: `<table>` with `<thead>`, `<tbody>`, `<tr>`, `<th>`, `<td>`
133 -- Images: `<img src="..." alt="...">`
128 + $data = json_decode( $body, true );
134 129
135 -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.
130 + if ( ! empty( $data['error'] ) ) {
131 + return $data['error']['message'];
132 + }
136 133
137 -Do not emit `<style>`, `<script>`, `<iframe>`, `<form>`, or inline `style=""` / `class=""` attributes (other than the `highlight` class above).
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 + }
138 140
139 -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.
140 -PROMPT;
141 141
142 - return apply_filters( 'betterdocs_write_with_ai_system_prompt', $prompt );
143 - }
144 142
145 - public function generate_openai_response( $prompt, $keywords ) {
146 - try {
147 - $api_key = $this->settings->get( 'ai_autowrite_api_key', '' );
148 - $max_tokens = $this->settings->get( 'ai_autowrite_max_token', 2500 );
149 - $model = $this->settings->get( 'write_with_ai_model', 'gpt-4o-mini' );
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 + }
150 149
151 - $api_endpoint = 'https://api.openai.com/v1/chat/completions'; // Update the endpoint based on OpenAI API version
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 - $request_body = AIHelper::build_openai_payload(
154 - $model,
155 - array(
156 - array(
157 - 'role' => 'system',
158 - 'content' => $this->get_system_prompt()
159 - ),
160 - array(
161 - 'role' => 'user',
162 - 'content' => $prompt
163 - )
164 - ),
165 - $max_tokens,
166 - null,
167 - 'write_with_ai'
168 - );
155 + $ai_instance = new WriteWithAI( $this->settings );
169 156
170 - $request_options = array(
171 - 'headers' => array(
172 - 'Content-Type' => 'application/json',
173 - 'Authorization' => 'Bearer ' . $api_key
174 - ),
175 - 'body' => json_encode( $request_body ),
176 - 'timeout' => 300
177 - );
157 + $generated_content = $ai_instance->generate_openai_response( $prompt, $keywords );
178 158
179 - // GPT-5.5 reasoning can run well past the default limits; give PHP and
180 - // the HTTP call room to finish (still subject to server php-fpm/nginx limits).
181 - if ( function_exists( 'set_time_limit' ) ) {
182 - set_time_limit( 300 );
183 - }
159 + // Send the generated content as the AJAX response
160 + wp_send_json_success( $generated_content );
161 + wp_die();
162 + }
184 163
185 - $response = wp_remote_post( $api_endpoint, $request_options );
186 164
187 - if ( is_wp_error( $response ) ) {
188 - return 'Error: ' . $response->get_error_message();
189 - } else {
190 - $body = wp_remote_retrieve_body( $response );
191 165
192 - $data = json_decode( $body, true );
166 + public function ai_autowrite_button() {
193 167
194 - if ( ! empty( $data[ 'error' ] ) ) {
195 - return $data[ 'error' ][ 'message' ];
196 - }
197 -
198 - return $data[ 'choices' ][ 0 ][ 'message' ][ 'content' ]; // Update this line to get the assistant's message
199 - }
200 - } catch ( Exception $error ) {
201 - return 'Error: ' . $error->getMessage();
202 - }
203 - }
204 -
205 - public function generate_openai_response_ai_edit( $prompt ) {
206 - $api_key = $this->settings->get( 'ai_autowrite_api_key', '' );
207 - $max_tokens = $this->settings->get( 'ai_autowrite_max_token', 2500 );
208 - $model = $this->settings->get( 'write_with_ai_model', 'gpt-4o-mini' );
209 -
210 - $api_endpoint = 'https://api.openai.com/v1/chat/completions';
211 -
212 - $payload = AIHelper::build_openai_payload(
213 - $model,
214 - array(
215 - array(
216 - 'role' => 'system',
217 - 'content' => $this->get_system_prompt()
218 - ),
219 - array(
220 - 'role' => 'user',
221 - 'content' => $prompt
222 - )
223 - ),
224 - $max_tokens,
225 - null,
226 - 'write_with_ai'
227 - );
228 -
229 - $request_options = array(
230 - 'headers' => array(
231 - 'Content-Type' => 'application/json',
232 - 'Authorization' => 'Bearer ' . $api_key
233 - ),
234 - 'body' => wp_json_encode( $payload ),
235 - 'timeout' => 300
236 - );
237 -
238 - // GPT-5.5 reasoning can run well past the default limits; give PHP and
239 - // the HTTP call room to finish (still subject to server php-fpm/nginx limits).
240 - if ( function_exists( 'set_time_limit' ) ) {
241 - set_time_limit( 300 );
242 - }
243 -
244 - $response = wp_remote_post( $api_endpoint, $request_options );
245 -
246 - if ( is_wp_error( $response ) ) {
247 - return array(
248 - 'success' => false,
249 - 'error' => $response->get_error_message(),
250 - 'model' => $model
251 - );
252 - }
253 -
254 - $body = wp_remote_retrieve_body( $response );
255 - $data = json_decode( $body, true );
256 -
257 - if ( ! empty( $data[ 'error' ] ) ) {
258 - return array(
259 - 'success' => false,
260 - 'error' => isset( $data[ 'error' ][ 'message' ] ) ? $data[ 'error' ][ 'message' ] : 'OpenAI error',
261 - 'model' => $model,
262 - 'raw' => $data
263 - );
264 - }
265 -
266 - $content = isset( $data[ 'choices' ][ 0 ][ 'message' ][ 'content' ] ) ? $data[ 'choices' ][ 0 ][ 'message' ][ 'content' ] : '';
267 - $usage = isset( $data[ 'usage' ] ) && is_array( $data[ 'usage' ] ) ? $data[ 'usage' ] : array();
268 -
269 - return array(
270 - 'success' => true,
271 - 'content' => $content,
272 - 'model' => $model,
273 - 'prompt_tokens' => isset( $usage[ 'prompt_tokens' ] ) ? (int) $usage[ 'prompt_tokens' ] : null,
274 - 'completion_tokens' => isset( $usage[ 'completion_tokens' ] ) ? (int) $usage[ 'completion_tokens' ] : null,
275 - 'total_tokens' => isset( $usage[ 'total_tokens' ] ) ? (int) $usage[ 'total_tokens' ] : null,
276 - 'finish_reason' => isset( $data[ 'choices' ][ 0 ][ 'finish_reason' ] ) ? $data[ 'choices' ][ 0 ][ 'finish_reason' ] : null
277 - );
278 - }
279 -
280 - public function generate_openai_content_callback() {
281 - // Verify the nonce
282 - if ( ! isset( $_POST[ 'ai_nonce' ] ) || ! wp_verify_nonce( $_POST[ 'ai_nonce' ], 'generate_openai_content_nonce' ) ) { //phpcs:ignore
283 - wp_send_json_error( 'Invalid nonce' );
284 - wp_die();
285 - }
286 -
287 - if ( ! current_user_can( 'edit_posts' ) ) {
288 - wp_send_json_error( 'Insufficient permissions' );
289 - wp_die();
290 - }
291 -
292 - $prompt = sanitize_text_field( $_POST[ 'prompt' ] ); //phpcs:ignore
293 - // $num_of_sections = sanitize_text_field($_POST['numOfSections']);
294 - // $num_of_paragraphs = sanitize_text_field($_POST['numOfParagraphs']);
295 - $keywords = sanitize_text_field( $_POST[ 'keywords' ] ); //phpcs:ignore
296 -
297 - $ai_instance = new WriteWithAI( $this->settings );
298 -
299 - $generated_content = $ai_instance->generate_openai_response( $prompt, $keywords );
300 -
301 - // Send the generated content as the AJAX response
302 - wp_send_json_success( $generated_content );
303 - wp_die();
304 - }
305 -
306 - public function ai_autowrite_button() {
307 -
308 - ?>
168 + ?>
309 169 <script>
310 170 var docsTitle;
311 171
312 172 function responseMessage(message) {
@@ -372,8 +232,14 @@
372 232 setTimeout(() => {
373 233 insertContentToEditor(title, response.data, isOverwrite, keywords);
374 234 docGenerateBtnTxt.innerHTML = reGenerateDocLabel;
375 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 + // }
376 242 }, 2000);
377 243 } else {
378 244 // alert(response.data.message);
379 245 jQuery('#betterdocs-ai-error-message').parent().removeClass('hidden');
@@ -382,21 +248,10 @@
382 248 jQuery('.generate-btn').removeAttr('disabled');
383 249
384 250 }
385 251 },
386 - error: function(jqXHR, textStatus, errorThrown) {
387 - console.error(jqXHR, textStatus, errorThrown);
388 -
389 - // Reset the UI so a slow/failed request never leaves a permanent "Generating...".
390 - var timedOut = ( textStatus === 'timeout' ) || ( jqXHR && jqXHR.status === 504 ) || ( jqXHR && jqXHR.status === 0 );
391 - var errMessage = timedOut
392 - ? "<?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' ); ?>"
393 - : "<?php echo esc_html__( 'Something went wrong while generating. Please try again.', 'betterdocs' ); ?>";
394 -
395 - jQuery('#betterdocs-ai-error-message').parent().removeClass('hidden');
396 - jQuery('#betterdocs-ai-error-message').html(responseMessage(errMessage));
397 - docGenerateBtnTxt.innerHTML = generateDocLabel;
398 - jQuery('.generate-btn').removeAttr('disabled');
252 + error: function(error) {
253 + console.error(error);
399 254 },
400 255 });
401 256
402 257 }
@@ -420,9 +275,9 @@
420 275 if (!jQuery('#betterdocs-ai-error-message').parent().hasClass('hidden')) {
421 276 jQuery('#betterdocs-ai-error-message').parent().addClass('hidden');
422 277 }
423 278
424 - 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.`;
425 280 }
426 281
427 282 function convertMarkdownToHTML(markdownContent) {
428 283 // Simple Markdown to HTML conversion (you may need to enhance this based on your needs)
@@ -452,107 +307,30 @@
452 307 return newDiv.innerHTML;
453 308 }
454 309
455 310
456 - function escapeHtml(str) {
457 - return String(str).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
458 - }
459 -
460 - function escapeRegex(str) {
461 - return String(str).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
462 - }
463 -
464 - function stripCodeFences(htmlString) {
465 - if (!htmlString) {
466 - return '';
467 - }
468 - return htmlString
469 - .replace(/^\s*```(?:html|HTML)?\s*\n?/, '')
470 - .replace(/\n?\s*```\s*$/, '');
471 - }
472 -
473 311 function wrapwithHeighlight(inputString, keywords) {
474 - if (!inputString) {
475 - return '';
476 - }
312 + let modifiedString = inputString.replace(/<(h\d)(.*?)>(.*?)<\/\1>/g, '<$1$2><span class="highlight">$3</span></$1>');
477 313
478 - const container = document.createElement('div');
479 - container.innerHTML = inputString;
314 + const keywordArray = keywords.split(',').map(keyword => keyword.trim());
480 315
481 - // Wrap each heading's visible text with the highlight span (skip if already present)
482 - container.querySelectorAll('h1, h2, h3, h4, h5, h6').forEach(function (heading) {
483 - if (heading.querySelector('.highlight')) {
484 - return;
485 - }
486 - const text = heading.textContent;
487 - if (!text || !text.trim()) {
488 - return;
489 - }
490 - 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>');
491 318 });
492 319
493 - // Wrap keywords only inside text nodes — never inside attributes, code, or existing highlights
494 - const keywordList = (keywords || '')
495 - .split(',')
496 - .map(function (k) { return k.trim(); })
497 - .filter(Boolean);
498 -
499 - if (keywordList.length) {
500 - const pattern = new RegExp('\\b(' + keywordList.map(escapeRegex).join('|') + ')\\b', 'gi');
501 - const walker = document.createTreeWalker(container, NodeFilter.SHOW_TEXT, {
502 - acceptNode: function (node) {
503 - let parent = node.parentNode;
504 - while (parent && parent !== container) {
505 - if (parent.nodeType === 1) {
506 - const tag = parent.tagName.toLowerCase();
507 - if (tag === 'code' || tag === 'pre') {
508 - return NodeFilter.FILTER_REJECT;
509 - }
510 - if (parent.classList && parent.classList.contains('highlight')) {
511 - return NodeFilter.FILTER_REJECT;
512 - }
513 - }
514 - parent = parent.parentNode;
515 - }
516 - return NodeFilter.FILTER_ACCEPT;
517 - }
518 - });
519 -
520 - const textNodes = [];
521 - let current;
522 - while ((current = walker.nextNode())) {
523 - textNodes.push(current);
524 - }
525 -
526 - textNodes.forEach(function (textNode) {
527 - pattern.lastIndex = 0;
528 - if (!pattern.test(textNode.nodeValue)) {
529 - return;
530 - }
531 - pattern.lastIndex = 0;
532 - const fragmentHost = document.createElement('span');
533 - fragmentHost.innerHTML = escapeHtml(textNode.nodeValue).replace(pattern, '<span class="highlight">$1</span>');
534 - const parent = textNode.parentNode;
535 - while (fragmentHost.firstChild) {
536 - parent.insertBefore(fragmentHost.firstChild, textNode);
537 - }
538 - parent.removeChild(textNode);
539 - });
540 - }
541 -
542 - return container.innerHTML;
320 + return modifiedString;
543 321 }
544 322
545 323 function getBodyContent(htmlString) {
546 - if (!htmlString) {
547 - return '';
548 - }
549 - const cleaned = stripCodeFences(htmlString);
550 - 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]
551 327 if (match) {
328 + console.log(match[1].replace(/<p>\s+/g, '<p>'));
552 329 return match[1].replace(/<p>\s+/g, '<p>');
330 + } else {
331 + return '';
553 332 }
554 - return cleaned;
555 333 }
556 334
557 335
558 336
@@ -565,9 +343,9 @@
565 343
566 344 htmlContent = getBodyContent(htmlContent);
567 345 htmlContent = removeFirstHeading(htmlContent);
568 346 htmlContent = wrapwithHeighlight(htmlContent, keywords);
569 - const isPostContent = `<?php echo isset( $_GET[ 'post' ] ) ? esc_html( get_the_content( $_GET[ 'post' ] ) ) : ''; // phpcs:ignore ?>`;
347 + const isPostContent = `<?php echo isset($_GET['post']) ? esc_html(get_the_content($_GET['post'])) : ''; // phpcs:ignore ?>`;
570 348
571 349 const blocks = wp.blocks.rawHandler({
572 350 HTML: htmlContent
573 351 });
@@ -620,9 +398,9 @@
620 398 let currentKeywords = jQuery('#betterdocs-ai-keyword').val();
621 399 if (!currentKeywords) {
622 400 currentKeywords = '{Documentation Keywords}';
623 401 }
624 - 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.`;
625 403 jQuery('#betterdocs-ai-content').val(currentPrompt);
626 404
627 405 jQuery('#betterdocs-ai-title').val(docsTitle);
628 406
@@ -639,14 +417,14 @@
639 417
640 418
641 419 function writeWithAIForm() {
642 420
643 - let title = `<?php echo isset( $_GET[ 'post' ] ) ? esc_html( get_the_title( $_GET[ 'post' ] ) ) : ''; // phpcs:ignore ?>`;
421 + let title = `<?php echo isset($_GET['post']) ? esc_html(get_the_title($_GET['post'])) : ''; // phpcs:ignore ?>`;
644 422 if (docsTitle) {
645 423 title = docsTitle;
646 424 }
647 425
648 - const promtTitle = `<?php echo isset( $_GET[ 'post' ] ) ? esc_html( get_the_title( $_GET[ 'post' ] ) ) : '{Documentation Title}'; // phpcs:ignore ?>`;
426 + const promtTitle = `<?php echo isset($_GET['post']) ? esc_html(get_the_title($_GET['post'])) : '{Documentation Title}'; // phpcs:ignore ?>`;
649 427 const titlePlaceholder = "<?php echo esc_attr__( 'Enter a descriptive title for your documentation.', 'betterdocs' ); ?>";
650 428 const keywords = "<?php echo esc_attr( '{Documentation Keywords}' ); ?>";
651 429 const keywordsPlaceholder = "<?php echo esc_attr__( 'Add keywords to generate precise & relevant documentation (comma-separated).', 'betterdocs' ); ?>";
652 430 const language = "<?php echo esc_attr( 'English' ); ?>";
@@ -661,18 +439,18 @@
661 439 const checkboxLabel = "<?php echo esc_html__( 'Overwrite your existing Doc:', 'betterdocs' ); ?>";
662 440 const nonce = "<?php echo esc_attr( wp_create_nonce( 'generate_openai_content_nonce' ) ); ?>";
663 441
664 442 <?php
665 - $is_valid = $this->get_api_key();
666 - $disable_field = 'disabled-input-field';
667 - if ( $this->get_api_key() ) {
668 - $disable_field = '';
669 - }
670 - ?>
443 + $is_valid = $this->get_api_key();
444 + $disable_field = 'disabled-input-field';
445 + if ( $this->get_api_key() ) {
446 + $disable_field = '';
447 + }
448 + ?>
671 449
672 450 let hiddenClass = 'hidden';
673 451 let newDocPageAtt = 'data-new-doc-page="true"';
674 - const isPostContent = `<?php echo isset( $_GET[ 'post' ] ) ? esc_html( get_the_content( $_GET[ 'post' ] ) ) : ''; // phpcs:ignore ?>`;
452 + const isPostContent = `<?php echo isset($_GET['post']) ? esc_html(get_the_content($_GET['post'])) : ''; // phpcs:ignore ?>`;
675 453
676 454 if (isPostContent !== '') {
677 455 hiddenClass = '';
678 456 newDocPageAtt = '';
@@ -682,9 +460,9 @@
682 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.`;
683 461
684 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.`;
685 463
686 - 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.`;
687 465
688 466 const aiIcon = `<svg xmlns="http://www.w3.org/2000/svg" width="21" height="20" viewBox="0 0 21 20" fill="none">
689 467 <g clip-path="url(#clip0_2460_1729)">
690 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"/>
@@ -721,14 +499,14 @@
721 499 <div class="autowrite-subheadding">
722 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>
723 501 </div>
724 502
725 - <?php if ( empty( $this->get_api_key() ) ): ?>
503 + <?php if ( empty( $this->get_api_key() ) ) : ?>
726 504 <div id="betterdocs-ai-message">
727 505 <div class="warning-message">
728 506 <span class="dashicons dashicons-warning"></span>
729 507 <div>
730 - <?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' ); ?>
731 509 </div>
732 510 </div>
733 511
734 512 </div>
@@ -1340,6 +1118,6 @@
1340 1118
1341 1119 }
1342 1120 </style>
1343 1121 <?php
1344 - }
1345 - }
1122 + }
1123 +}