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

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