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

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