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 -395 4.5.43.8.9 View file →
@@ -1,312 +1,168 @@
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' ] );
32 + }
35 33
36 - public function enqueue_ai_edit_assets( $hook ) {
37 - if ( 'post.php' !== $hook && 'post-new.php' !== $hook ) {
38 - return;
39 - }
34 + public function isEnabledWriteWithAI() {
35 + $isEnableAutoWrite = $this->settings->get( 'enable_write_with_ai', true );
36 + return $isEnableAutoWrite;
37 + }
40 38
41 - global $post_type;
42 - if ( 'docs' !== $post_type ) {
43 - return;
44 - }
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.';
45 43
46 - if ( empty( $this->get_api_key() ) ) {
47 - return;
48 - }
44 + return $api_response;
45 + }
49 46
50 - betterdocs()->assets->enqueue( 'betterdocs-ai-edit', 'blocks/ai-edit.js' );
51 - betterdocs()->assets->enqueue( 'betterdocs-ai-edit-style', 'blocks/ai-edit-style.css' );
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 + );
52 57
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 - }
58 + $api_response = [];
63 59
64 - public function isEnabledWriteWithAI() {
65 - $isEnableAutoWrite = $this->settings->get( 'enable_write_with_ai', true );
66 - return $isEnableAutoWrite;
67 - }
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
68 62
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.';
63 + curl_close( $ch ); //phpcs:ignore WordPress.WP.AlternativeFunctions.curl_curl_close
73 64
74 - return $api_response;
75 - }
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 + }
76 75
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 - );
76 + // print_r($response);
87 77
88 - $api_response = array();
78 + return $api_response;
79 + }
89 80
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
81 + public function get_api_key() {
82 + $api_key = $this->settings->get( 'ai_autowrite_api_key', '' );
83 + return $api_key;
84 + }
92 85
93 - curl_close( $ch ); //phpcs:ignore WordPress.WP.AlternativeFunctions.curl_curl_close
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 - }
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 );
105 91
106 - // print_r($response);
92 + $api_endpoint = 'https://api.openai.com/v1/chat/completions'; // Update the endpoint based on OpenAI API version
107 93
108 - return $api_response;
109 - }
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,
110 113
111 - public function get_api_key() {
112 - $api_key = $this->settings->get( 'ai_autowrite_api_key', '' );
113 - return $api_key;
114 - }
114 + )
115 + ),
116 + 'timeout' => 50,
117 + );
115 118
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.
119 + $response = wp_remote_post( $api_endpoint, $request_options );
119 120
120 -## Output format
121 + if ( is_wp_error( $response ) ) {
122 + return 'Error: ' . $response->get_error_message();
123 + } else {
124 + $body = wp_remote_retrieve_body( $response );
121 125
122 -Return only HTML body content. Never wrap output in `<!doctype>`, `<html>`, `<head>`, `<body>`, or markdown code fences (no ```html ... ```).
126 + $data = json_decode( $body, true );
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 + if ( ! empty( $data['error'] ) ) {
129 + return $data['error']['message'];
130 + }
134 131
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.
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 + }
136 138
137 -Do not emit `<style>`, `<script>`, `<iframe>`, `<form>`, or inline `style=""` / `class=""` attributes (other than the `highlight` class above).
138 139
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 140
142 - return apply_filters( 'betterdocs_write_with_ai_system_prompt', $prompt );
143 - }
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 + }
144 147
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' );
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
150 152
151 - $api_endpoint = 'https://api.openai.com/v1/chat/completions'; // Update the endpoint based on OpenAI API version
153 + $ai_instance = new WriteWithAI( $this->settings );
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 + $generated_content = $ai_instance->generate_openai_response( $prompt, $keywords );
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 + // Send the generated content as the AJAX response
158 + wp_send_json_success( $generated_content );
159 + wp_die();
160 + }
178 161
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 - }
162 + public function ai_autowrite_button() {
184 163
185 - $response = wp_remote_post( $api_endpoint, $request_options );
186 -
187 - if ( is_wp_error( $response ) ) {
188 - return 'Error: ' . $response->get_error_message();
189 - } else {
190 - $body = wp_remote_retrieve_body( $response );
191 -
192 - $data = json_decode( $body, true );
193 -
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 - ?>
164 + ?>
309 165 <script>
310 166 var docsTitle;
311 167
312 168 function responseMessage(message) {
@@ -372,8 +228,14 @@
372 228 setTimeout(() => {
373 229 insertContentToEditor(title, response.data, isOverwrite, keywords);
374 230 docGenerateBtnTxt.innerHTML = reGenerateDocLabel;
375 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 + // }
376 238 }, 2000);
377 239 } else {
378 240 // alert(response.data.message);
379 241 jQuery('#betterdocs-ai-error-message').parent().removeClass('hidden');
@@ -382,21 +244,10 @@
382 244 jQuery('.generate-btn').removeAttr('disabled');
383 245
384 246 }
385 247 },
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');
248 + error: function(error) {
249 + console.error(error);
399 250 },
400 251 });
401 252
402 253 }
@@ -420,9 +271,9 @@
420 271 if (!jQuery('#betterdocs-ai-error-message').parent().hasClass('hidden')) {
421 272 jQuery('#betterdocs-ai-error-message').parent().addClass('hidden');
422 273 }
423 274
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.`;
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.`;
425 276 }
426 277
427 278 function convertMarkdownToHTML(markdownContent) {
428 279 // Simple Markdown to HTML conversion (you may need to enhance this based on your needs)
@@ -452,107 +303,30 @@
452 303 return newDiv.innerHTML;
453 304 }
454 305
455 306
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 307 function wrapwithHeighlight(inputString, keywords) {
474 - if (!inputString) {
475 - return '';
476 - }
308 + let modifiedString = inputString.replace(/<(h\d)(.*?)>(.*?)<\/\1>/g, '<$1$2><span class="highlight">$3</span></$1>');
477 309
478 - const container = document.createElement('div');
479 - container.innerHTML = inputString;
310 + const keywordArray = keywords.split(',').map(keyword => keyword.trim());
480 311
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>';
312 + keywordArray.forEach(keyword => {
313 + modifiedString = modifiedString.replace(new RegExp(`\\b(${keyword})\\b`, 'gi'), '<span class="highlight">$1</span>');
491 314 });
492 315
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;
316 + return modifiedString;
543 317 }
544 318
545 319 function getBodyContent(htmlString) {
546 - if (!htmlString) {
547 - return '';
548 - }
549 - const cleaned = stripCodeFences(htmlString);
550 - 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]
551 323 if (match) {
324 + console.log(match[1].replace(/<p>\s+/g, '<p>'));
552 325 return match[1].replace(/<p>\s+/g, '<p>');
326 + } else {
327 + return '';
553 328 }
554 - return cleaned;
555 329 }
556 330
557 331
558 332
@@ -565,9 +339,9 @@
565 339
566 340 htmlContent = getBodyContent(htmlContent);
567 341 htmlContent = removeFirstHeading(htmlContent);
568 342 htmlContent = wrapwithHeighlight(htmlContent, keywords);
569 - const isPostContent = `<?php echo isset( $_GET[ 'post' ] ) ? esc_html( get_the_content( $_GET[ 'post' ] ) ) : ''; // phpcs:ignore ?>`;
343 + const isPostContent = `<?php echo isset($_GET['post']) ? esc_html(get_the_content($_GET['post'])) : ''; // phpcs:ignore ?>`;
570 344
571 345 const blocks = wp.blocks.rawHandler({
572 346 HTML: htmlContent
573 347 });
@@ -620,9 +394,9 @@
620 394 let currentKeywords = jQuery('#betterdocs-ai-keyword').val();
621 395 if (!currentKeywords) {
622 396 currentKeywords = '{Documentation Keywords}';
623 397 }
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.`;
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.`;
625 399 jQuery('#betterdocs-ai-content').val(currentPrompt);
626 400
627 401 jQuery('#betterdocs-ai-title').val(docsTitle);
628 402
@@ -639,14 +413,14 @@
639 413
640 414
641 415 function writeWithAIForm() {
642 416
643 - let title = `<?php echo isset( $_GET[ 'post' ] ) ? esc_html( get_the_title( $_GET[ 'post' ] ) ) : ''; // phpcs:ignore ?>`;
417 + let title = `<?php echo isset($_GET['post']) ? esc_html(get_the_title($_GET['post'])) : ''; // phpcs:ignore ?>`;
644 418 if (docsTitle) {
645 419 title = docsTitle;
646 420 }
647 421
648 - const promtTitle = `<?php echo isset( $_GET[ 'post' ] ) ? esc_html( get_the_title( $_GET[ 'post' ] ) ) : '{Documentation Title}'; // phpcs:ignore ?>`;
422 + const promtTitle = `<?php echo isset($_GET['post']) ? esc_html(get_the_title($_GET['post'])) : '{Documentation Title}'; // phpcs:ignore ?>`;
649 423 const titlePlaceholder = "<?php echo esc_attr__( 'Enter a descriptive title for your documentation.', 'betterdocs' ); ?>";
650 424 const keywords = "<?php echo esc_attr( '{Documentation Keywords}' ); ?>";
651 425 const keywordsPlaceholder = "<?php echo esc_attr__( 'Add keywords to generate precise & relevant documentation (comma-separated).', 'betterdocs' ); ?>";
652 426 const language = "<?php echo esc_attr( 'English' ); ?>";
@@ -661,18 +435,18 @@
661 435 const checkboxLabel = "<?php echo esc_html__( 'Overwrite your existing Doc:', 'betterdocs' ); ?>";
662 436 const nonce = "<?php echo esc_attr( wp_create_nonce( 'generate_openai_content_nonce' ) ); ?>";
663 437
664 438 <?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 - ?>
439 + $is_valid = $this->get_api_key();
440 + $disable_field = 'disabled-input-field';
441 + if ( $this->get_api_key() ) {
442 + $disable_field = '';
443 + }
444 + ?>
671 445
672 446 let hiddenClass = 'hidden';
673 447 let newDocPageAtt = 'data-new-doc-page="true"';
674 - const isPostContent = `<?php echo isset( $_GET[ 'post' ] ) ? esc_html( get_the_content( $_GET[ 'post' ] ) ) : ''; // phpcs:ignore ?>`;
448 + const isPostContent = `<?php echo isset($_GET['post']) ? esc_html(get_the_content($_GET['post'])) : ''; // phpcs:ignore ?>`;
675 449
676 450 if (isPostContent !== '') {
677 451 hiddenClass = '';
678 452 newDocPageAtt = '';
@@ -682,9 +456,9 @@
682 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.`;
683 457
684 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.`;
685 459
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.`;
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.`;
687 461
688 462 const aiIcon = `<svg xmlns="http://www.w3.org/2000/svg" width="21" height="20" viewBox="0 0 21 20" fill="none">
689 463 <g clip-path="url(#clip0_2460_1729)">
690 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"/>
@@ -721,14 +495,14 @@
721 495 <div class="autowrite-subheadding">
722 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>
723 497 </div>
724 498
725 - <?php if ( empty( $this->get_api_key() ) ): ?>
499 + <?php if ( empty( $this->get_api_key() ) ) : ?>
726 500 <div id="betterdocs-ai-message">
727 501 <div class="warning-message">
728 502 <span class="dashicons dashicons-warning"></span>
729 503 <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' ); ?>
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' ); ?>
731 505 </div>
732 506 </div>
733 507
734 508 </div>
@@ -864,10 +638,8 @@
864 638 }
865 639
866 640 .betterdocs-ai-button-container {
867 641 margin-left: 10px;
868 - white-space: nowrap;
869 - max-width: 145px;
870 642 }
871 643
872 644 .autowrite-icon {
873 645 display: flex;
@@ -937,9 +709,8 @@
937 709 display: flex;
938 710 align-items: center;
939 711 justify-content: space-between;
940 712 gap: 10px;
941 - width: 135px;
942 713 }
943 714
944 715 .betterdocs-ai-button img {
945 716 width: 20px;
@@ -1244,18 +1015,20 @@
1244 1015 font-size: 18px;
1245 1016 font-weight: bold;
1246 1017 float: right;
1247 1018 position: absolute;
1248 - top: -16px;
1249 - border-radius: 50%;
1019 + right: 1px;
1020 + top: 1px;
1021 + padding: 10px;
1250 1022 background: #ffffff;
1251 - width: 40px;
1252 - height: 40px;
1023 + border-radius: 25px;
1024 + width: 20px;
1025 + height: 20px;
1253 1026 display: flex;
1254 1027 align-items: center;
1255 1028 justify-content: center;
1256 1029 color: #281617;
1257 - right: -42px;
1030 + right: -60px;
1258 1031 }
1259 1032
1260 1033 div#betterdocs-ai-error-message,
1261 1034 #betterdocs-ai-message {
@@ -1340,6 +1113,6 @@
1340 1113
1341 1114 }
1342 1115 </style>
1343 1116 <?php
1344 - }
1345 - }
1117 + }
1118 +}