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

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