PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 4.5.6
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v4.5.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
betterdocs / includes / Core / WriteWithAI.php

WriteWithAI.php in BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot 4.5.6, at includes/Core/WriteWithAI.php

1,445 lines 50.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace WPDeveloper\BetterDocs\Core;
4
5 if ( ! defined( 'ABSPATH' ) ) {
6 exit;
7 }
8
9
10 use WPDeveloper\BetterDocs\Utils\Base;
11 use WPDeveloper\BetterDocs\Core\Settings;
12 use WPDeveloper\BetterDocs\Core\PostType;
13
14 use WPDeveloper\BetterDocs\Utils\Helper;
15 use WPDeveloper\BetterDocs\Utils\AIHelper;
16 use WPDeveloper\BetterDocs\Utils\AIUsage;
17
18 class WriteWithAI extends Base {
19
20 public $settings;
21
22 public function __construct( Settings $settings ) {
23 $this->settings = $settings;
24 // Get the post ID from the URL
25 $post_id = isset( $_GET[ 'post' ] ) ? intval( $_GET[ 'post' ] ) : 0; // phpcs:ignore
26
27 if ( ! empty( $_GET[ 'post_type' ] ) ) { // phpcs:ignore
28 $post_type = $_GET[ 'post_type' ]; // phpcs:ignore
29 } elseif ( $post_id > 0 ) {
30 $post_type = get_post_type( $post_id );
31 } else {
32 $post_type = '';
33 }
34
35 if ( ! empty( $this->isEnabledWriteWithAI() ) && 'docs' == $post_type ) {
36 add_action( 'admin_footer', array( $this, 'ai_autowrite_button' ) );
37 add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_ai_edit_assets' ) );
38 }
39 add_action( 'wp_ajax_generate_openai_content', array( $this, 'generate_openai_content_callback' ) );
40 }
41
42 public function enqueue_ai_edit_assets( $hook ) {
43 if ( 'post.php' !== $hook && 'post-new.php' !== $hook ) {
44 return;
45 }
46
47 global $post_type;
48 if ( 'docs' !== $post_type ) {
49 return;
50 }
51
52 if ( empty( $this->get_api_key() ) ) {
53 return;
54 }
55
56 betterdocs()->assets->enqueue( 'betterdocs-ai-edit', 'blocks/ai-edit.js' );
57 betterdocs()->assets->enqueue( 'betterdocs-ai-edit-style', 'blocks/ai-edit-style.css' );
58
59 wp_localize_script(
60 'betterdocs-ai-edit',
61 'betterdocsAIEdit',
62 array(
63 'rest_url' => esc_url_raw( rest_url( 'betterdocs/v1/ai-edit' ) ),
64 'rest_nonce' => wp_create_nonce( 'wp_rest' ),
65 'post_id' => get_the_ID()
66 )
67 );
68 }
69
70 public function isEnabledWriteWithAI() {
71 $isEnableAutoWrite = $this->settings->get( 'enable_write_with_ai', true );
72 return $isEnableAutoWrite;
73 }
74
75 public function isValidAPIKey( $apiKey ) {
76 if ( empty( $apiKey ) ) {
77 $api_response[ 'valid' ] = false;
78 $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.';
79
80 return $api_response;
81 }
82
83 $api_response = array();
84
85 $response = wp_safe_remote_get(
86 'https://api.openai.com/v1/engines',
87 array(
88 'headers' => array(
89 'Content-Type' => 'application/json',
90 'Authorization' => 'Bearer ' . $apiKey,
91 ),
92 'timeout' => 15,
93 )
94 );
95
96 if ( is_wp_error( $response ) ) {
97 $api_response[ 'valid' ] = false;
98 $api_response[ 'message' ] = $response->get_error_message();
99 return $api_response;
100 }
101
102 $httpCode = (int) wp_remote_retrieve_response_code( $response );
103 $body = wp_remote_retrieve_body( $response );
104
105 if ( 200 === $httpCode ) {
106 $api_response[ 'valid' ] = true;
107 $api_response[ 'message' ] = 'Valid API Key';
108 } else {
109 $responseData = json_decode( $body, true );
110 $messageData = ! empty( $responseData[ 'error' ] ) ? $responseData[ 'error' ] : array();
111 $api_response[ 'valid' ] = false;
112 $api_response[ 'message' ] = ! empty( $messageData[ 'message' ] ) ? $messageData[ 'message' ] : 'Invalid API Key';
113 }
114
115 // print_r($response);
116
117 return $api_response;
118 }
119
120 public function get_api_key() {
121 $api_key = $this->settings->get( 'ai_autowrite_api_key', '' );
122 return $api_key;
123 }
124
125 public function get_system_prompt() {
126 $prompt = <<<'PROMPT'
127 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.
128
129 ## Output format
130
131 Return only HTML body content. Never wrap output in `<!doctype>`, `<html>`, `<head>`, `<body>`, or markdown code fences (no ```html ... ```).
132
133 Use semantic, Gutenberg-friendly tags only:
134 - Headings: `<h2>`, `<h3>`, `<h4>` (do not emit `<h1>` — it is reserved for the document title)
135 - Paragraphs: `<p>` for prose
136 - Lists: `<ul>`/`<ol>` with `<li>` for any list of items, steps, or bullet points — never fake a list with paragraphs or `<br>`
137 - Links: `<a href="https://...">link text</a>` with absolute URLs
138 - Inline emphasis: `<strong>`, `<em>`, `<code>`
139 - Code blocks: `<pre><code>...</code></pre>` for multi-line code or commands
140 - Quotes: `<blockquote>`
141 - Tables: `<table>` with `<thead>`, `<tbody>`, `<tr>`, `<th>`, `<td>`
142 - Images: `<img src="..." alt="...">`
143
144 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.
145
146 Do not emit `<style>`, `<script>`, `<iframe>`, `<form>`, or inline `style=""` / `class=""` attributes (other than the `highlight` class above).
147
148 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.
149 PROMPT;
150
151 return apply_filters( 'betterdocs_write_with_ai_system_prompt', $prompt );
152 }
153
154 public function generate_openai_response( $prompt, $keywords ) {
155 try {
156 $api_key = $this->settings->get( 'ai_autowrite_api_key', '' );
157 $max_tokens = $this->settings->get( 'ai_autowrite_max_token', 2500 );
158 $model = $this->settings->get( 'write_with_ai_model', 'gpt-4o-mini' );
159
160 $api_endpoint = 'https://api.openai.com/v1/chat/completions'; // Update the endpoint based on OpenAI API version
161
162 $request_body = AIHelper::build_openai_payload(
163 $model,
164 array(
165 array(
166 'role' => 'system',
167 'content' => $this->get_system_prompt()
168 ),
169 array(
170 'role' => 'user',
171 'content' => $prompt
172 )
173 ),
174 $max_tokens,
175 null,
176 'write_with_ai'
177 );
178
179 $request_options = array(
180 'headers' => array(
181 'Content-Type' => 'application/json',
182 'Authorization' => 'Bearer ' . $api_key
183 ),
184 'body' => json_encode( $request_body ),
185 'timeout' => 300
186 );
187
188 // GPT-5.5 reasoning can run well past the default limits; give PHP and
189 // the HTTP call room to finish (still subject to server php-fpm/nginx limits).
190 if ( function_exists( 'set_time_limit' ) ) {
191 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.
192 }
193
194 $response = wp_remote_post( $api_endpoint, $request_options );
195
196 if ( is_wp_error( $response ) ) {
197 return 'Error: ' . $response->get_error_message();
198 } else {
199 $body = wp_remote_retrieve_body( $response );
200
201 $data = json_decode( $body, true );
202
203 if ( ! empty( $data[ 'error' ] ) ) {
204 return $data[ 'error' ][ 'message' ];
205 }
206
207 return $data[ 'choices' ][ 0 ][ 'message' ][ 'content' ]; // Update this line to get the assistant's message
208 }
209 } catch ( Exception $error ) {
210 return 'Error: ' . $error->getMessage();
211 }
212 }
213
214 public function generate_openai_response_ai_edit( $prompt ) {
215 $api_key = $this->settings->get( 'ai_autowrite_api_key', '' );
216 $max_tokens = $this->settings->get( 'ai_autowrite_max_token', 2500 );
217 $model = $this->settings->get( 'write_with_ai_model', 'gpt-4o-mini' );
218
219 $api_endpoint = 'https://api.openai.com/v1/chat/completions';
220
221 $payload = AIHelper::build_openai_payload(
222 $model,
223 array(
224 array(
225 'role' => 'system',
226 'content' => $this->get_system_prompt()
227 ),
228 array(
229 'role' => 'user',
230 'content' => $prompt
231 )
232 ),
233 $max_tokens,
234 null,
235 'write_with_ai'
236 );
237
238 $request_options = array(
239 'headers' => array(
240 'Content-Type' => 'application/json',
241 'Authorization' => 'Bearer ' . $api_key
242 ),
243 'body' => wp_json_encode( $payload ),
244 'timeout' => 300
245 );
246
247 // GPT-5.5 reasoning can run well past the default limits; give PHP and
248 // the HTTP call room to finish (still subject to server php-fpm/nginx limits).
249 if ( function_exists( 'set_time_limit' ) ) {
250 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.
251 }
252
253 $response = wp_remote_post( $api_endpoint, $request_options );
254
255 if ( is_wp_error( $response ) ) {
256 return array(
257 'success' => false,
258 'error' => $response->get_error_message(),
259 'model' => $model
260 );
261 }
262
263 $body = wp_remote_retrieve_body( $response );
264 $data = json_decode( $body, true );
265
266 if ( ! empty( $data[ 'error' ] ) ) {
267 return array(
268 'success' => false,
269 'error' => isset( $data[ 'error' ][ 'message' ] ) ? $data[ 'error' ][ 'message' ] : 'OpenAI error',
270 'model' => $model,
271 'raw' => $data
272 );
273 }
274
275 $content = isset( $data[ 'choices' ][ 0 ][ 'message' ][ 'content' ] ) ? $data[ 'choices' ][ 0 ][ 'message' ][ 'content' ] : '';
276 $usage = isset( $data[ 'usage' ] ) && is_array( $data[ 'usage' ] ) ? $data[ 'usage' ] : array();
277
278 return array(
279 'success' => true,
280 'content' => $content,
281 'model' => $model,
282 'prompt_tokens' => isset( $usage[ 'prompt_tokens' ] ) ? (int) $usage[ 'prompt_tokens' ] : null,
283 'completion_tokens' => isset( $usage[ 'completion_tokens' ] ) ? (int) $usage[ 'completion_tokens' ] : null,
284 'total_tokens' => isset( $usage[ 'total_tokens' ] ) ? (int) $usage[ 'total_tokens' ] : null,
285 'finish_reason' => isset( $data[ 'choices' ][ 0 ][ 'finish_reason' ] ) ? $data[ 'choices' ][ 0 ][ 'finish_reason' ] : null
286 );
287 }
288
289 /**
290 * Generic chat completion using the Write-with-AI model/token/key settings.
291 *
292 * Shared by lightweight, plain-text generators (glossary definitions, FAQ answers)
293 * that need the same dynamic model as Write with AI / AI Edit but a caller-supplied
294 * system prompt instead of the doc-authoring HTML prompt. Returns the same structured
295 * array shape as {@see self::generate_openai_response_ai_edit()}.
296 *
297 * @param string $user_prompt The user message.
298 * @param string $system_prompt Optional system message (omitted when empty).
299 * @return array { success:bool, content?:string, error?:string, model:string, *_tokens?:int }
300 */
301 public function generate_text( $user_prompt, $system_prompt = '' ) {
302 $api_key = $this->settings->get( 'ai_autowrite_api_key', '' );
303 $max_tokens = $this->settings->get( 'ai_autowrite_max_token', 2500 );
304 $model = $this->settings->get( 'write_with_ai_model', 'gpt-4o-mini' );
305
306 $messages = array();
307 if ( $system_prompt !== '' ) {
308 $messages[] = array(
309 'role' => 'system',
310 'content' => $system_prompt
311 );
312 }
313 $messages[] = array(
314 'role' => 'user',
315 'content' => $user_prompt
316 );
317
318 $api_endpoint = 'https://api.openai.com/v1/chat/completions';
319
320 $payload = AIHelper::build_openai_payload( $model, $messages, $max_tokens, null, 'write_with_ai' );
321
322 $request_options = array(
323 'headers' => array(
324 'Content-Type' => 'application/json',
325 'Authorization' => 'Bearer ' . $api_key
326 ),
327 'body' => wp_json_encode( $payload ),
328 'timeout' => 300
329 );
330
331 // GPT-5.5 reasoning can run well past the default limits; give PHP and
332 // the HTTP call room to finish (still subject to server php-fpm/nginx limits).
333 if ( function_exists( 'set_time_limit' ) ) {
334 set_time_limit( 300 );
335 }
336
337 $response = wp_remote_post( $api_endpoint, $request_options );
338
339 if ( is_wp_error( $response ) ) {
340 return array(
341 'success' => false,
342 'error' => $response->get_error_message(),
343 'model' => $model
344 );
345 }
346
347 $body = wp_remote_retrieve_body( $response );
348 $data = json_decode( $body, true );
349
350 if ( ! empty( $data[ 'error' ] ) ) {
351 return array(
352 'success' => false,
353 'error' => isset( $data[ 'error' ][ 'message' ] ) ? $data[ 'error' ][ 'message' ] : 'OpenAI error',
354 'model' => $model,
355 'raw' => $data
356 );
357 }
358
359 $content = isset( $data[ 'choices' ][ 0 ][ 'message' ][ 'content' ] ) ? $data[ 'choices' ][ 0 ][ 'message' ][ 'content' ] : '';
360 $usage = isset( $data[ 'usage' ] ) && is_array( $data[ 'usage' ] ) ? $data[ 'usage' ] : array();
361
362 return array(
363 'success' => true,
364 'content' => $content,
365 'model' => $model,
366 'prompt_tokens' => isset( $usage[ 'prompt_tokens' ] ) ? (int) $usage[ 'prompt_tokens' ] : null,
367 'completion_tokens' => isset( $usage[ 'completion_tokens' ] ) ? (int) $usage[ 'completion_tokens' ] : null,
368 'total_tokens' => isset( $usage[ 'total_tokens' ] ) ? (int) $usage[ 'total_tokens' ] : null,
369 'finish_reason' => isset( $data[ 'choices' ][ 0 ][ 'finish_reason' ] ) ? $data[ 'choices' ][ 0 ][ 'finish_reason' ] : null
370 );
371 }
372
373 public function generate_openai_content_callback() {
374 // Verify the nonce
375 $ai_nonce = isset( $_POST['ai_nonce'] ) ? sanitize_text_field( wp_unslash( $_POST['ai_nonce'] ) ) : '';
376 if ( ! wp_verify_nonce( $ai_nonce, 'generate_openai_content_nonce' ) ) {
377 wp_send_json_error( 'Invalid nonce' );
378 wp_die();
379 }
380
381 if ( ! current_user_can( 'edit_posts' ) ) {
382 wp_send_json_error( 'Insufficient permissions' );
383 wp_die();
384 }
385
386 $prompt = isset( $_POST['prompt'] ) ? sanitize_text_field( wp_unslash( $_POST['prompt'] ) ) : '';
387 $keywords = isset( $_POST['keywords'] ) ? sanitize_text_field( wp_unslash( $_POST['keywords'] ) ) : '';
388
389 $ai_instance = new WriteWithAI( $this->settings );
390
391 $generated_content = $ai_instance->generate_openai_response( $prompt, $keywords );
392
393 // Count a successful generation (skip obvious upstream errors).
394 if ( is_string( $generated_content ) && $generated_content !== '' && strpos( $generated_content, 'Error:' ) !== 0 ) {
395 $post_id = isset( $_POST[ 'post_id' ] ) ? intval( $_POST[ 'post_id' ] ) : 0; //phpcs:ignore
396 AIUsage::record( 'write_with_ai', $post_id );
397 }
398
399 // Send the generated content as the AJAX response
400 wp_send_json_success( $generated_content );
401 wp_die();
402 }
403
404 public function ai_autowrite_button() {
405
406 ?>
407 <script>
408 var docsTitle;
409
410 function responseMessage(message) {
411 return `<div class="warning-message">
412 <span class="dashicons dashicons-warning"></span>
413 <div class="footer-message">
414 ${message}
415 </div>
416 </div>`;
417 }
418
419 function generateArticle(e) {
420
421
422 const title = document.getElementById('betterdocs-ai-title').value;
423 const keywords = document.getElementById('betterdocs-ai-keyword').value;
424 // const sectionOptions = document.getElementById('bd-autowrtite-content-section');
425 // const numOfSections = sectionOptions.options[sectionOptions.selectedIndex].value;
426
427 // const paragraphOptions = document.getElementById('bd-autowrtite-content-paragraph');
428 // const numOfParagraphs = paragraphOptions.options[paragraphOptions.selectedIndex].value;
429
430 const contentTextArea = document.getElementById('betterdocs-ai-content');
431 const docGenerateBtnTxt = document.querySelector('.generate-btn span');
432
433 // Add nonce value to the data being sent
434 const nonce = document.querySelector('input[name="ai_nonce"]').value;
435 const isOverwrite = document.getElementById('betterdocs-ai-checkbox').checked;
436 const generateDocLabel = "<?php echo esc_html__( 'Generate Doc', 'betterdocs' ); ?>";
437 const reGenerateDocLabel = "<?php echo esc_html__( 'Regenerate Doc', 'betterdocs' ); ?>";
438
439
440 // contentTextArea.value = `Write an article about ${title} in English. The article is organized by the following exact ${numOfSections} headings. Write exact ${numOfParagraphs} number of paragraphs per heading.${keywords} Use Markdown for formatting. Add an introduction and a conclusion. Style: creative. Tone: cheerful.`;
441
442
443 // Check if the title is not empty
444 if (title.trim() !== '' && keywords.trim() !== '') {
445 e.preventDefault();
446
447 if (!jQuery('#betterdocs-ai-error-message').parent().hasClass('hidden')) {
448 jQuery('#betterdocs-ai-error-message').parent().addClass('hidden');
449 }
450
451 docGenerateBtnTxt.innerHTML = "<?php echo esc_html__( 'Generating...', 'betterdocs' ); ?>";
452 jQuery('.generate-btn').prop('disabled', true);
453
454 jQuery.post({
455 url: ajaxurl, // Make sure ajaxurl is defined in your script
456 type: 'POST',
457 data: {
458 action: 'generate_openai_content',
459 prompt: document.querySelector('#betterdocs-ai-content').value,
460 // numOfSections: numOfSections,
461 // numOfParagraphs: numOfParagraphs,
462 keywords: keywords,
463 post_id: (document.getElementById('post_ID') ? document.getElementById('post_ID').value : 0),
464 ai_nonce: nonce, // Pass the nonce value
465 },
466 success: function(response) {
467 // Update the content in the textarea
468
469 if (response.success && (typeof response.data === 'string')) {
470 docGenerateBtnTxt.innerHTML = "<?php echo esc_html__( 'Generated', 'betterdocs' ); ?>";
471 setTimeout(() => {
472 insertContentToEditor(title, response.data, isOverwrite, keywords);
473 docGenerateBtnTxt.innerHTML = reGenerateDocLabel;
474 jQuery('.generate-btn').removeAttr('disabled');
475 }, 2000);
476 } else {
477 // alert(response.data.message);
478 jQuery('#betterdocs-ai-error-message').parent().removeClass('hidden');
479 jQuery('#betterdocs-ai-error-message').html(responseMessage(response.data.message));
480 docGenerateBtnTxt.innerHTML = generateDocLabel;
481 jQuery('.generate-btn').removeAttr('disabled');
482
483 }
484 },
485 error: function(jqXHR, textStatus, errorThrown) {
486 console.error(jqXHR, textStatus, errorThrown);
487
488 // Reset the UI so a slow/failed request never leaves a permanent "Generating...".
489 var timedOut = ( textStatus === 'timeout' ) || ( jqXHR && jqXHR.status === 504 ) || ( jqXHR && jqXHR.status === 0 );
490 var errMessage = timedOut
491 ? "<?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' ); ?>"
492 : "<?php echo esc_html__( 'Something went wrong while generating. Please try again.', 'betterdocs' ); ?>";
493
494 jQuery('#betterdocs-ai-error-message').parent().removeClass('hidden');
495 jQuery('#betterdocs-ai-error-message').html(responseMessage(errMessage));
496 docGenerateBtnTxt.innerHTML = generateDocLabel;
497 jQuery('.generate-btn').removeAttr('disabled');
498 },
499 });
500
501 }
502 }
503
504 // Function to update the textarea
505 function updateTextarea() {
506 const title = document.getElementById('betterdocs-ai-title').value;
507 const keywords = document.getElementById('betterdocs-ai-keyword').value;
508 const sectionOptions = document.getElementById('bd-autowrtite-content-section');
509 let language = 'English';
510
511 if (language === '') {
512 language = 'English';
513 }
514
515 let keywordsText = keywords !== '' ? ` and incorporate the keywords: ${keywords}` : '';
516
517 const contentTextArea = document.getElementById('betterdocs-ai-content');
518
519 if (!jQuery('#betterdocs-ai-error-message').parent().hasClass('hidden')) {
520 jQuery('#betterdocs-ai-error-message').parent().addClass('hidden');
521 }
522
523 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.`;
524 }
525
526 function convertMarkdownToHTML(markdownContent) {
527 // Simple Markdown to HTML conversion (you may need to enhance this based on your needs)
528 const htmlContent = markdownContent
529 .replace(/^# (.+)$/gm, '<h1>$1</h1>')
530 .replace(/^## (.+)$/gm, '<h2>$1</h2>')
531 .replace(/^### (.+)$/gm, '<h3>$1</h3>')
532 .replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
533 .replace(/\*(.+?)\*/g, '<em>$1</em>');
534
535 return htmlContent;
536 }
537
538 function replaceHeadingTitle(content, title, overviewTitle) {
539 let regex = new RegExp(title, 'g');
540 let updateContent = content.replace(regex, overviewTitle);
541 return updateContent;
542 }
543
544 function removeFirstHeading(htmlString) {
545 var newDiv = document.createElement('div');
546 newDiv.innerHTML = htmlString;
547 var firstHeading = newDiv.querySelector('h1');
548 if (firstHeading) {
549 firstHeading.parentNode.removeChild(firstHeading);
550 }
551 return newDiv.innerHTML;
552 }
553
554
555 function escapeHtml(str) {
556 return String(str).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
557 }
558
559 function escapeRegex(str) {
560 return String(str).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
561 }
562
563 function stripCodeFences(htmlString) {
564 if (!htmlString) {
565 return '';
566 }
567 return htmlString
568 .replace(/^\s*```(?:html|HTML)?\s*\n?/, '')
569 .replace(/\n?\s*```\s*$/, '');
570 }
571
572 function wrapwithHeighlight(inputString, keywords) {
573 if (!inputString) {
574 return '';
575 }
576
577 const container = document.createElement('div');
578 container.innerHTML = inputString;
579
580 // Wrap each heading's visible text with the highlight span (skip if already present)
581 container.querySelectorAll('h1, h2, h3, h4, h5, h6').forEach(function (heading) {
582 if (heading.querySelector('.highlight')) {
583 return;
584 }
585 const text = heading.textContent;
586 if (!text || !text.trim()) {
587 return;
588 }
589 heading.innerHTML = '<span class="highlight">' + escapeHtml(text) + '</span>';
590 });
591
592 // Wrap keywords only inside text nodes — never inside attributes, code, or existing highlights
593 const keywordList = (keywords || '')
594 .split(',')
595 .map(function (k) { return k.trim(); })
596 .filter(Boolean);
597
598 if (keywordList.length) {
599 const pattern = new RegExp('\\b(' + keywordList.map(escapeRegex).join('|') + ')\\b', 'gi');
600 const walker = document.createTreeWalker(container, NodeFilter.SHOW_TEXT, {
601 acceptNode: function (node) {
602 let parent = node.parentNode;
603 while (parent && parent !== container) {
604 if (parent.nodeType === 1) {
605 const tag = parent.tagName.toLowerCase();
606 if (tag === 'code' || tag === 'pre') {
607 return NodeFilter.FILTER_REJECT;
608 }
609 if (parent.classList && parent.classList.contains('highlight')) {
610 return NodeFilter.FILTER_REJECT;
611 }
612 }
613 parent = parent.parentNode;
614 }
615 return NodeFilter.FILTER_ACCEPT;
616 }
617 });
618
619 const textNodes = [];
620 let current;
621 while ((current = walker.nextNode())) {
622 textNodes.push(current);
623 }
624
625 textNodes.forEach(function (textNode) {
626 pattern.lastIndex = 0;
627 if (!pattern.test(textNode.nodeValue)) {
628 return;
629 }
630 pattern.lastIndex = 0;
631 const fragmentHost = document.createElement('span');
632 fragmentHost.innerHTML = escapeHtml(textNode.nodeValue).replace(pattern, '<span class="highlight">$1</span>');
633 const parent = textNode.parentNode;
634 while (fragmentHost.firstChild) {
635 parent.insertBefore(fragmentHost.firstChild, textNode);
636 }
637 parent.removeChild(textNode);
638 });
639 }
640
641 return container.innerHTML;
642 }
643
644 function getBodyContent(htmlString) {
645 if (!htmlString) {
646 return '';
647 }
648 const cleaned = stripCodeFences(htmlString);
649 const match = cleaned.match(/<body[^>]*>([\s\S]*?)<\/body>/i);
650 if (match) {
651 return match[1].replace(/<p>\s+/g, '<p>');
652 }
653 return cleaned;
654 }
655
656
657
658 function insertContentToEditor(title, htmlContent, isOverwrite, keywords) {
659
660 const {
661 dispatch,
662 select
663 } = wp.data;
664
665 htmlContent = getBodyContent(htmlContent);
666 htmlContent = removeFirstHeading(htmlContent);
667 htmlContent = wrapwithHeighlight(htmlContent, keywords);
668 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. ?>`;
669
670 const blocks = wp.blocks.rawHandler({
671 HTML: htmlContent
672 });
673
674 dispatch('core/editor').editPost({
675 title: title,
676 });
677
678 const selectedBlockClientId = select('core/block-editor').getSelectedBlockClientId();
679
680 const allBlocks = select('core/block-editor').getBlocks();
681
682 if (isOverwrite || isPostContent === '') {
683
684 allBlocks.forEach((block) => {
685 dispatch('core/block-editor').removeBlock(block.clientId);
686 });
687 dispatch('core/block-editor').insertBlocks(blocks);
688
689 } else if (selectedBlockClientId) {
690 const selectedIndex = select('core/block-editor').getBlockIndex(selectedBlockClientId);
691 dispatch('core/block-editor').insertBlocks(blocks, selectedIndex + 1);
692 } else {
693 dispatch('core/block-editor').insertBlocks(blocks);
694 }
695
696 closeWriteWithAIForm('afterInsertContent');
697 }
698
699 function closeWriteWithAIForm(after) {
700 jQuery('.betterdocs-ai-autowrite-form-container').addClass('hidden');
701 jQuery('#betterdocs-ai-autowrite-form-container-overlay').addClass('hidden');
702 jQuery('.betterdocs-ai-button').addClass('regenerate-btn');
703 }
704
705
706 document.addEventListener('DOMContentLoaded', function() {
707
708 // Subscribe to changes in the editor state
709 let previousDocsTitle = '';
710
711 wp.data.subscribe(() => {
712 // Get the updated post title
713 // const docsTitle = wp.data.select('core/editor').getEditedPostAttribute('title');
714
715 const docsTitle = wp.data.select('core/editor').getEditedPostAttribute('title');
716
717 // Check if the title has changed
718 if (docsTitle !== previousDocsTitle) {
719 let currentKeywords = jQuery('#betterdocs-ai-keyword').val();
720 if (!currentKeywords) {
721 currentKeywords = '{Documentation Keywords}';
722 }
723 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.`;
724 jQuery('#betterdocs-ai-content').val(currentPrompt);
725
726 jQuery('#betterdocs-ai-title').val(docsTitle);
727
728 previousDocsTitle = docsTitle;
729 }
730
731
732 });
733 });
734
735
736 // This example assumes that this code is executed when your script is loaded.
737 // You may want to place it within the appropriate context in your application.
738
739
740 function writeWithAIForm() {
741
742 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. ?>`;
743 if (docsTitle) {
744 title = docsTitle;
745 }
746
747 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. ?>`;
748 const titlePlaceholder = "<?php echo esc_attr__( 'Enter a descriptive title for your documentation.', 'betterdocs' ); ?>";
749 const keywords = "<?php echo esc_attr( '{Documentation Keywords}' ); ?>";
750 const keywordsPlaceholder = "<?php echo esc_attr__( 'Add keywords to generate precise & relevant documentation (comma-separated).', 'betterdocs' ); ?>";
751 const language = "<?php echo esc_attr( 'English' ); ?>";
752 const titleLabel = "<?php echo esc_html__( 'Documentation Title:', 'betterdocs' ); ?>";
753 const keywordLabel = "<?php echo esc_html__( 'Keywords:', 'betterdocs' ); ?>";
754 const secLabel = "<?php echo esc_html__( '# of Sections:', 'betterdocs' ); ?>";
755 const paraLabel = "<?php echo esc_html__( '# of Paragraph Per Section: ', 'betterdocs' ); ?>";
756 const langLabel = "<?php echo esc_html__( 'Language:', 'betterdocs' ); ?>";
757 const promtLabel = "<?php echo esc_html__( 'Prompt:', 'betterdocs' ); ?>";
758 const promtBtnLabel = "<?php echo esc_html__( 'Generate Prompt', 'betterdocs' ); ?>";
759 let promtArticleLabel = "<?php echo esc_html__( 'Generate Doc', 'betterdocs' ); ?>";
760 const checkboxLabel = "<?php echo esc_html__( 'Overwrite your existing Doc:', 'betterdocs' ); ?>";
761 const nonce = "<?php echo esc_attr( wp_create_nonce( 'generate_openai_content_nonce' ) ); ?>";
762
763 <?php
764 $is_valid = $this->get_api_key();
765 $disable_field = 'disabled-input-field';
766 if ( $this->get_api_key() ) {
767 $disable_field = '';
768 }
769 ?>
770
771 let hiddenClass = 'hidden';
772 let newDocPageAtt = 'data-new-doc-page="true"';
773 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. ?>`;
774
775 if (isPostContent !== '') {
776 hiddenClass = '';
777 newDocPageAtt = '';
778 }
779
780
781 // 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.`;
782
783 // 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.`;
784
785 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.`;
786
787 const aiIcon = `<svg xmlns="http://www.w3.org/2000/svg" width="21" height="20" viewBox="0 0 21 20" fill="none">
788 <g clip-path="url(#clip0_2460_1729)">
789 <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"/>
790 </g>
791 <defs>
792 <clipPath id="clip0_2460_1729">
793 <rect width="20" height="20" fill="white" transform="translate(0.5)"/>
794 </clipPath>
795 </defs>
796 </svg>`;
797
798
799
800 return `
801 <div class="betterdocs-ai-autowrite-form-container hidden" ${newDocPageAtt}>
802 <div class="betterdocs-ai-autowrite-form-content">
803 <div class="betterdocs-ai-autowrite-top-part">
804 <div class="autowrite-heading">
805 <span class="autowrite-icon">
806 <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" fill="none">
807 <g clip-path="url(#clip0_2453_20882)">
808 <path d="M15.8322 30.1765L8.97508 30.7708L9.56936 23.9136L23.8322 9.74219C24.0451 9.52448 24.2993 9.3515 24.58 9.23341C24.8606 9.11531 25.162 9.05447 25.4665 9.05447C25.771 9.05447 26.0724 9.11531 26.3531 9.23341C26.6337 9.3515 26.8879 9.52448 27.1008 9.74219L30.0037 12.6679C30.2176 12.8805 30.3874 13.1334 30.5033 13.4119C30.6192 13.6904 30.6788 13.9891 30.6788 14.2908C30.6788 14.5924 30.6192 14.8911 30.5033 15.1696C30.3874 15.4481 30.2176 15.701 30.0037 15.9136L15.8322 30.1765ZM1.92593 9.07933C1.12365 8.9399 1.12365 7.7879 1.92593 7.64848C3.34647 7.40124 4.6612 6.73658 5.70251 5.73923C6.74382 4.74188 7.46455 3.45703 7.77279 2.04848L7.82079 1.82676C7.99451 1.03591 9.12365 1.02905 9.30651 1.82219L9.36365 2.07819C9.68319 3.48048 10.4099 4.75709 11.4526 5.74772C12.4953 6.73834 13.8074 7.39881 15.2242 7.64619C16.0311 7.78333 16.0311 8.94219 15.2242 9.0839C13.8073 9.33071 12.4949 9.99066 11.4518 10.9809C10.4087 11.9711 9.68146 13.2474 9.36136 14.6496L9.30193 14.9079C9.12136 15.6988 7.99222 15.6942 7.81851 14.901L7.77279 14.6793C7.46424 13.2702 6.74284 11.9849 5.70064 10.9874C4.65845 9.99003 3.34273 9.32574 1.92136 9.07933H1.92593Z" stroke="#E31B54" stroke-width="2.28571" stroke-linecap="round" stroke-linejoin="round"/>
809 </g>
810 <defs>
811 <clipPath id="clip0_2453_20882">
812 <rect width="32" height="32" fill="white"/>
813 </clipPath>
814 </defs>
815 </svg>
816 </span>
817 <h1><?php echo esc_html__( 'Write Documentation with BetterDocs AI', 'betterdocs' ); ?></h1>
818
819 </div>
820 <div class="autowrite-subheadding">
821 <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>
822 </div>
823
824 <?php if ( empty( $this->get_api_key() ) ): ?>
825 <div id="betterdocs-ai-message">
826 <div class="warning-message">
827 <span class="dashicons dashicons-warning"></span>
828 <div>
829 <?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' ); ?>
830 </div>
831 </div>
832
833 </div>
834 <?php endif; ?>
835
836 <div class="form-group hidden">
837 <div id="betterdocs-ai-error-message"></div>
838 </div>
839
840 </div>
841 <form id="betterdocs-ai-form" class="<?php echo esc_attr( $disable_field ); ?>">
842 <div class="form-inner-content">
843 <div class="form-group">
844 <label for="betterdocs-ai-title">${titleLabel}</label>
845 <input type="text" placeholder="${titlePlaceholder}" id="betterdocs-ai-title" name="betterdocs-ai-title" required value="${title}">
846 </div>
847
848 <div class="form-group">
849 <label for="betterdocs-ai-keyword">${keywordLabel}</label>
850 <input type="text" placeholder="${keywordsPlaceholder}" id="betterdocs-ai-keyword" name="betterdocs-ai-keyword" required>
851 </div>
852
853 <div class="form-group prompt-field">
854 <label for="betterdocs-ai-content">${promtLabel}</label>
855 <textarea id="betterdocs-ai-content" name="betterdocs-ai-content" rows="6" required>${prompt}</textarea>
856 <p class="input-description"><?php echo esc_html__( 'Ensure you include a clear and detailed prompt to receive the desired output. Follow the guidelines provided for the best results.', 'betterdocs' ); ?></p>
857 </div>
858 <div class="betterdocs-ai-checkbox-field ${hiddenClass}">
859 <div class="form-group">
860 <div class="checkbox-field-label">
861 <label for="betterdocs-ai-checkbox">${checkboxLabel}</label>
862 <p class="input-description"><?php echo esc_html__( 'Overwrite the existing doc with the AI Generated Content. If Disabled, new AI Generated content will be added in the section you are currently editing.', 'betterdocs' ); ?></p>
863 </div>
864
865 <div class="checkbox-input-field">
866 <input type="checkbox" id="betterdocs-ai-checkbox" name="betterdocs-ai-checkbox" data-overwrite="false">
867 <label for="betterdocs-ai-checkbox" class="toggle-label"></label>
868 </div>
869 </div>
870 </div>
871
872 <input type="hidden" name="ai_nonce" value="${nonce}" />
873 </div>
874 <div class="generate-button-container">
875 <button type="submit" class="generate-btn" onclick="generateArticle(event)">
876 ${aiIcon} <span>${promtArticleLabel}</span>
877 </button>
878 </div>
879 </form>
880 </div>
881
882 <div class="bd-close-button" onclick="closeWriteWithAIForm('afterClosedClicked')">✕</div>
883 </div>
884
885 <div id="betterdocs-ai-autowrite-form-container-overlay" class="hidden"></div>
886 `;
887 }
888
889 jQuery(document).on('click', '.regenerate-btn', function() {
890 jQuery('.betterdocs-ai-autowrite-form-container').removeClass('hidden');
891 jQuery('#betterdocs-ai-autowrite-form-container-overlay').removeClass('hidden');
892 });
893
894 jQuery(document).ready(function($) {
895 // Check if we are in the Edit Post screen
896
897 if ($('.block-editor-page').length > 0) {
898
899 const bdAIButton = $(`<div class="betterdocs-ai-button-container">
900 <button class="betterdocs-ai-button">
901 <img src="<?php echo esc_url( BETTERDOCS_ABSURL . 'assets/admin/images/betterdocs-icon-white.png' ); ?>" alt="<?php echo esc_attr__( 'betterdocs icon', 'betterdocs' ); ?>" />
902 <span><?php echo esc_html__( 'Write with AI', 'betterdocs' ); ?></span>
903 </button>
904 </div>`);
905
906
907 const closeBtn = $('bd-close-button');
908
909 const formHtml = writeWithAIForm();
910 $(formHtml).addClass('hidden');
911
912 $(document).on('click', '.betterdocs-ai-button', function() {
913 $('.betterdocs-ai-autowrite-form-container').removeClass('hidden');
914 $('#betterdocs-ai-autowrite-form-container-overlay').removeClass('hidden');
915 });
916
917 // $(document).on('click', '.betterdocs-ai-button', function() {
918 if ($('.betterdocs-ai-autowrite-form-container').length < 1) {
919 $('body').append(formHtml);
920
921 // Add event listeners to input elements
922 document.getElementById('betterdocs-ai-title').addEventListener('input', updateTextarea);
923 document.getElementById('betterdocs-ai-keyword').addEventListener('input', updateTextarea);
924 // document.getElementById('bd-autowrtite-content-section').addEventListener('change', updateTextarea);
925 // document.getElementById('bd-autowrtite-content-paragraph').addEventListener('change', updateTextarea);
926 // document.getElementById('betterdocs-ai-language').addEventListener('input', updateTextarea);
927 }
928 // });
929 const btn = document.createElement('button');
930 wp.data.subscribe(function() {
931 setTimeout(() => {
932 if ($('.edit-post-header__settings').length > 0) {
933 $('.edit-post-header__settings').prepend(bdAIButton);
934 } else if ($('.editor-header__settings').length > 0) {
935 $('.editor-header__settings').prepend(bdAIButton);
936 }
937
938 }, 1);
939 });
940 }
941
942 $(document).on('click', '#betterdocs-ai-autowrite-form-container-overlay', function() {
943 closeWriteWithAIForm();
944 });
945 });
946 </script>
947
948 <style>
949 .hidden {
950 display: none !important;
951 }
952
953 .disabled-input-field input,
954 .disabled-input-field textarea,
955 .disabled-input-field button,
956 .disabled-input-field label {
957 pointer-events: none;
958 opacity: 0.6;
959 }
960
961 .disabled-input-field label {
962 opacity: 1;
963 }
964
965 .betterdocs-ai-button-container {
966 margin-left: 10px;
967 white-space: nowrap;
968 max-width: 145px;
969 }
970
971 .autowrite-icon {
972 display: flex;
973 align-items: center;
974 width: 55px;
975 height: 55px;
976 background: #FFE4E8;
977 justify-content: center;
978 border-radius: 50px;
979 }
980
981 .autowrite-icon svg {
982 width: 28px;
983 height: 28px;
984 }
985
986 .autowrite-heading {
987 display: flex;
988 gap: 10px;
989 align-items: center;
990 }
991
992 .autowrite-heading h1 {
993 color: #1D2939;
994 font-family: 'IBM Plex Sans', sans-serif;
995 font-size: 24px;
996 font-style: normal;
997 font-weight: 500;
998 line-height: normal;
999 margin: 0;
1000 }
1001
1002 .autowrite-heading p,
1003 form#betterdocs-ai-form p {
1004 margin: 0;
1005 margin-bottom: 24px;
1006 }
1007
1008 .autowrite-subheadding p {
1009 font-family: 'IBM Plex Sans', sans-serif;
1010 font-size: 14px;
1011 }
1012
1013 .prompt-field .input-description {
1014 margin: 0 !important;
1015 }
1016
1017 .autowrite-heading p {
1018 color: #475467;
1019 font-family: 'IBM Plex Sans', sans-serif;
1020 font-size: 16px;
1021 font-style: normal;
1022 font-weight: 400;
1023 }
1024
1025 .betterdocs-ai-button {
1026 background: #00B884;
1027 border: 1px solid transparent;
1028 color: #fff;
1029 box-shadow: none;
1030 font-size: 12px;
1031 margin-right: 8px;
1032 padding: 0px 15px;
1033 cursor: pointer;
1034 border-radius: 3px;
1035 height: 38px;
1036 display: flex;
1037 align-items: center;
1038 justify-content: space-between;
1039 gap: 10px;
1040 width: 135px;
1041 }
1042
1043 .betterdocs-ai-button img {
1044 width: 20px;
1045 height: 20px;
1046 }
1047
1048 .betterdocs-ai-autowrite-form-container {
1049 position: fixed;
1050 top: 50%;
1051 left: 50%;
1052 transform: translate(-50%, -50%);
1053 z-index: 100001;
1054 width: 650px;
1055 margin: 0 auto;
1056 background-color: #fff;
1057 border-radius: 5px;
1058 font-family: 'IBM Plex Sans', sans-serif;
1059 /* max-height: 600px; */
1060 max-height: 750px;
1061 max-width: 100%;
1062 }
1063
1064 .betterdocs-ai-autowrite-form-content {
1065 background-color: #fff;
1066 padding: 20px 0px;
1067 border-radius: 5px;
1068 padding-bottom: 0;
1069 overflow: auto;
1070 /* max-height: 700px; */
1071 height: 100%;
1072
1073 }
1074
1075 .betterdocs-ai-autowrite-top-part {
1076 /* padding-bottom: 20px; */
1077 border-bottom: 1px solid #ddd;
1078 /* margin-bottom: 20px; */
1079 padding: 0 40px;
1080 }
1081
1082 #betterdocs-ai-form {
1083 display: flex;
1084 flex-direction: column;
1085 /* height: 100%; */
1086 overflow: hidden;
1087 }
1088
1089 .form-inner-content {
1090 overflow: auto;
1091 height: 100%;
1092 max-height: 435px;
1093 /* max-height: 330px; */
1094 padding: 0 40px;
1095 }
1096
1097 .form-inner-content>.form-group {
1098 margin-top: 20px;
1099 }
1100
1101
1102 #betterdocs-ai-autowrite-form-container-overlay {
1103 position: fixed;
1104 top: 0;
1105 right: 0;
1106 bottom: 0;
1107 left: 0;
1108 background-color: rgba(0, 0, 0, .7);
1109 z-index: 100000;
1110 }
1111
1112
1113
1114 #betterdocs-ai-form {
1115 display: flex;
1116 flex-direction: column;
1117 }
1118
1119 #betterdocs-ai-form .form-group {
1120 margin-bottom: 24px;
1121 }
1122
1123 #betterdocs-ai-form .bd-aiform-flex {
1124 display: flex;
1125 justify-content: space-between;
1126 }
1127
1128 #betterdocs-ai-form label {
1129 font-weight: 600;
1130 margin-bottom: 5px;
1131 display: block;
1132 font-size: 14px;
1133 line-height: 20px;
1134 }
1135
1136 select#bd-autowrtite-content-section,
1137 #bd-autowrtite-content-paragraph,
1138 #betterdocs-ai-language {
1139 width: 150px !important;
1140 height: 40px;
1141 }
1142
1143 #betterdocs-ai-form input[type="text"],
1144 #betterdocs-ai-form textarea {
1145 width: 100%;
1146 padding: 8px;
1147 box-sizing: border-box;
1148 border: 1px solid #D0D5DD;
1149 border-radius: 4px;
1150 color: #667085;
1151 font-weight: 400;
1152 }
1153
1154 /* Override autofill text color */
1155 #betterdocs-ai-form input:-webkit-autofill {
1156 -webkit-text-fill-color: #667085 !important;
1157 }
1158
1159 #betterdocs-ai-form input::placeholder {
1160 color: #acb2bf;
1161 }
1162
1163 #betterdocs-ai-form textarea {
1164 color: #667085;
1165 }
1166
1167 #betterdocs-ai-form input:focus,
1168 #betterdocs-ai-form textarea:focus {
1169 box-shadow: none;
1170 }
1171
1172 #betterdocs-ai-form textarea:focus {
1173 color: inherit;
1174 box-shadow: none;
1175
1176 }
1177
1178
1179 .generate-button-container {
1180 position: sticky;
1181 bottom: 0px;
1182 width: 100%;
1183 margin-left: 0;
1184 z-index: 999999 !important;
1185 padding: 20px 0;
1186 display: flex;
1187 justify-content: end;
1188 border-top: 1px solid #ddd;
1189 background: white;
1190 border-bottom-right-radius: 5px;
1191 border-bottom-left-radius: 5px;
1192 }
1193
1194 .generate-button-container button {
1195 display: flex;
1196 gap: 8px;
1197 align-items: center;
1198 justify-content: center;
1199 opacity: 1 !important;
1200 margin-right: 40px;
1201 }
1202
1203 .input-description {
1204 font-size: 14px;
1205 color: #667085;
1206 }
1207
1208 .betterdocs-ai-checkbox-field .form-group {
1209 display: flex;
1210 align-items: start;
1211 /* gap: 200px; */
1212 justify-content: space-between;
1213 }
1214
1215 .betterdocs-ai-checkbox-field input {
1216 width: 20px;
1217 height: 20px;
1218 }
1219
1220 /* Add this CSS to your existing stylesheet or create a new one */
1221
1222 .betterdocs-ai-checkbox-field {
1223 position: relative;
1224 font-size: 16px;
1225 /* Adjust font size as needed */
1226 }
1227
1228 .betterdocs-ai-checkbox-field input[type=checkbox]:checked::before {
1229 padding: 2px;
1230 }
1231
1232 .betterdocs-ai-checkbox-field input[type=checkbox] {
1233 border: 1px solid #00b884;
1234 }
1235
1236 .betterdocs-ai-checkbox-field input[type=checkbox]:checked::before {
1237 content: '\2713';
1238 color: #00b884;
1239 }
1240
1241 /* Change the default checkbox color */
1242 .betterdocs-ai-checkbox-field input[type="checkbox"]:hover {
1243 -webkit-appearance: none;
1244 /* WebKit/Blink Browsers */
1245 -moz-appearance: none;
1246 /* Firefox */
1247 appearance: none;
1248 border: 1px solid #00b884;
1249 /* Set the height of the checkbox */
1250 /* Optional: Round the corners */
1251 outline: none;
1252 /* Remove the default outline */
1253 }
1254
1255 /* Style the checked state */
1256 .betterdocs-ai-checkbox-field input[type="checkbox"]:checked {
1257 /* Set the background color when checked */
1258 border: 1px solid #00b884;
1259 /* Set the border color when checked */
1260 }
1261
1262 .checkbox-field-label {
1263 width: calc(100% - 150px);
1264 }
1265
1266 .checkbox-field-label p {
1267 margin: 0 !important;
1268 }
1269
1270 .checkbox-input-field {
1271 width: 300px;
1272 text-align: right;
1273 }
1274
1275 .checkbox-input-field {
1276 position: relative;
1277 display: inline-block;
1278 width: 60px;
1279 height: 34px;
1280 }
1281
1282 .checkbox-input-field input {
1283 display: none;
1284 }
1285
1286 .toggle-label {
1287 position: absolute;
1288 cursor: pointer;
1289 top: 0;
1290 left: 0;
1291 right: 0;
1292 bottom: 0;
1293 background-color: #ccc;
1294 border-radius: 34px;
1295 transition: background-color 0.3s;
1296 scale: .9;
1297 width: 52px;
1298 height: 26px;
1299 }
1300
1301
1302 .checkbox-input-field input:checked+.toggle-label {
1303 background-color: #00b884;
1304 }
1305
1306 .toggle-label:after {
1307 content: "";
1308 position: absolute;
1309 height: 22px;
1310 width: 22px;
1311 left: 2px;
1312 bottom: 2px;
1313 background-color: white;
1314 border-radius: 50%;
1315 transition: transform 0.3s;
1316 }
1317
1318 .checkbox-input-field input:checked+.toggle-label:after {
1319 transform: translateX(26px);
1320 }
1321
1322 .generate-btn,
1323 .prompt-btn,
1324 .keep-btn {
1325 background-color: #00b884;
1326 color: #fff;
1327 border: none;
1328 padding: 10px 15px;
1329 text-align: center;
1330 text-decoration: none;
1331 display: inline-block;
1332 font-size: 16px;
1333 cursor: pointer;
1334 border-radius: 4px;
1335 }
1336
1337 .generate-btn[disabled] {
1338 cursor: unset;
1339 }
1340
1341 .bd-close-button {
1342 cursor: pointer;
1343 font-size: 18px;
1344 font-weight: bold;
1345 float: right;
1346 position: absolute;
1347 top: -16px;
1348 border-radius: 50%;
1349 background: #ffffff;
1350 width: 40px;
1351 height: 40px;
1352 display: flex;
1353 align-items: center;
1354 justify-content: center;
1355 color: #281617;
1356 right: -42px;
1357 }
1358
1359 div#betterdocs-ai-error-message,
1360 #betterdocs-ai-message {
1361 font-size: 14px;
1362 font-family: 'IBM Plex Sans', sans-serif;
1363 color: #667085;
1364 margin-bottom: 20px;
1365 }
1366
1367 div#betterdocs-ai-error-message a,
1368 #betterdocs-ai-message a {
1369 text-decoration: none;
1370 font-weight: 600;
1371 }
1372
1373 div#betterdocs-ai-error-message a:focus,
1374 #betterdocs-ai-message a:focus {
1375 outline: none;
1376 box-shadow: none;
1377 color: #2271b1;
1378 }
1379
1380 #betterdocs-ai-message span {
1381 color: #d63638;
1382 }
1383
1384 .warning-message {
1385 display: flex;
1386 align-items: center;
1387 gap: 10px;
1388 background: #fbebed;
1389 padding: 12px;
1390 border-radius: 5px;
1391 font-size: 14px;
1392 line-height: 1.4em;
1393 border-left: 4px solid #d63638;
1394 }
1395
1396 .warning-message svg {
1397 width: 20px;
1398 height: 20px;
1399 }
1400
1401 .warning-message span {
1402 color: #d63638;
1403 }
1404
1405 .warning-message .footer-message {
1406 width: calc(100% - 20px);
1407 }
1408
1409 @media only screen and (max-width: 991px) {
1410 .betterdocs-ai-autowrite-form-container {
1411 left: 0;
1412 right: 0;
1413 transform: translate(0%, -50%);
1414 }
1415
1416 .betterdocs-ai-autowrite-form-content {
1417 overflow-x: auto;
1418 }
1419 }
1420
1421 @media only screen and (max-width: 740px) {
1422
1423 /* .bd-close-button {
1424 right: px;
1425 top: 1px;
1426 border-radius: 5px;
1427 width: 12px;
1428 height: 12px;
1429 color: #ffffff;
1430 background: #00b884;
1431 } */
1432 .bd-close-button {
1433 right: 0px;
1434 top: 0px;
1435 width: 12px;
1436 height: 12px;
1437 font-size: 14px;
1438 }
1439
1440 }
1441 </style>
1442 <?php
1443 }
1444 }
1445