PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 3.8.9
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v3.8.9
4.9.1 4.9.0 4.8.2 4.8.1 4.8.0 4.7.0 4.6.2 4.6.1 4.6.0 4.5.6 4.5.5 4.5.4 4.5.3 4.5.2 4.5.1 4.5.0 4.4.1 4.4.0 3.3.4 3.4.0 3.4.1 3.4.2 3.5.0 3.5.1 3.5.2 All 199 releases
betterdocs / includes / Core / WriteWithAI.php

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

1,119 lines 35.9 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() ) && $post_type == 'docs' ) {
29 add_action( 'admin_footer', [ $this, 'ai_autowrite_button' ] );
30 }
31 add_action( 'wp_ajax_generate_openai_content', [ $this, 'generate_openai_content_callback' ] );
32 }
33
34 public function isEnabledWriteWithAI() {
35 $isEnableAutoWrite = $this->settings->get( 'enable_write_with_ai', true );
36 return $isEnableAutoWrite;
37 }
38
39 public function isValidAPIKey( $apiKey ) {
40 if ( empty( $apiKey ) ) {
41 $api_response['valid'] = false;
42 $api_response['message'] = 'Please Insert your <a href="/admin.php?page=betterdocs-settings">OpenAI API Key</a> to use this Write with AI feature.';
43
44 return $api_response;
45 }
46
47 $ch = curl_init( 'https://api.openai.com/v1/engines' ); //phpcs:ignore WordPress.WP.AlternativeFunctions.curl_curl_init
48 curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true ); //phpcs:ignore WordPress.WP.AlternativeFunctions.curl_curl_setopt
49 curl_setopt(
50 $ch,
51 CURLOPT_HTTPHEADER,
52 [ //phpcs:ignore WordPress.WP.AlternativeFunctions.curl_curl_setopt
53 'Content-Type: application/json',
54 'Authorization: Bearer ' . $apiKey,
55 ]
56 );
57
58 $api_response = [];
59
60 $response = curl_exec( $ch ); //phpcs:ignore WordPress.WP.AlternativeFunctions.curl_curl_exec
61 $httpCode = curl_getinfo( $ch, CURLINFO_HTTP_CODE ); //phpcs:ignore WordPress.WP.AlternativeFunctions.curl_curl_getinfo
62
63 curl_close( $ch ); //phpcs:ignore WordPress.WP.AlternativeFunctions.curl_curl_close
64
65 if ( $httpCode == 200 ) {
66 $api_response['valid'] = true;
67 $api_response['message'] = 'Valid API Key';
68 } else {
69 $responseData = json_decode( $response, true );
70 // Access the message data (replace 'data' with the actual key used in the response)
71 $messageData = $responseData['error'] ? $responseData['error'] : '';
72 $api_response['valid'] = false;
73 $api_response['message'] = $messageData['message'] ? $messageData['message'] : 'Invalid API Key';
74 }
75
76 // print_r($response);
77
78 return $api_response;
79 }
80
81 public function get_api_key() {
82 $api_key = $this->settings->get( 'ai_autowrite_api_key', '' );
83 return $api_key;
84 }
85
86
87 public function generate_openai_response( $prompt, $keywords ) {
88 try {
89 $api_key = $this->settings->get( 'ai_autowrite_api_key', '' );
90 $max_tokens = $this->settings->get( 'ai_autowrite_max_token', 1500 );
91
92 $api_endpoint = 'https://api.openai.com/v1/chat/completions'; // Update the endpoint based on OpenAI API version
93
94 $request_options = array(
95 'headers' => array(
96 'Content-Type' => 'application/json',
97 'Authorization' => 'Bearer ' . $api_key,
98 ),
99 'body' => json_encode(
100 array( //phpcs:ignore WordPress.WP.AlternativeFunctions.json_encode_json_encode
101 'model' => 'gpt-4o-mini', // Add the model parameter here
102 'messages' => array(
103 array(
104 'role' => 'system',
105 'content' => 'You are a helpful assistant who writes documentation for users.'
106 ),
107 array(
108 'role' => 'user',
109 'content' => $prompt
110 ),
111 ),
112 'max_tokens' => $max_tokens,
113
114 )
115 ),
116 'timeout' => 50,
117 );
118
119 $response = wp_remote_post( $api_endpoint, $request_options );
120
121 if ( is_wp_error( $response ) ) {
122 return 'Error: ' . $response->get_error_message();
123 } else {
124 $body = wp_remote_retrieve_body( $response );
125
126 $data = json_decode( $body, true );
127
128 if ( ! empty( $data['error'] ) ) {
129 return $data['error']['message'];
130 }
131
132 return $data['choices'][0]['message']['content']; // Update this line to get the assistant's message
133 }
134 } catch ( Exception $error ) {
135 return 'Error: ' . $error->getMessage();
136 }
137 }
138
139
140
141 public function generate_openai_content_callback() {
142 // Verify the nonce
143 if (!isset($_POST['ai_nonce']) || !wp_verify_nonce($_POST['ai_nonce'], 'generate_openai_content_nonce')) { //phpcs:ignore
144 wp_send_json_error( 'Invalid nonce' );
145 wp_die();
146 }
147
148 $prompt = sanitize_text_field($_POST['prompt']); //phpcs:ignore
149 // $num_of_sections = sanitize_text_field($_POST['numOfSections']);
150 // $num_of_paragraphs = sanitize_text_field($_POST['numOfParagraphs']);
151 $keywords = sanitize_text_field($_POST['keywords']); //phpcs:ignore
152
153 $ai_instance = new WriteWithAI( $this->settings );
154
155 $generated_content = $ai_instance->generate_openai_response( $prompt, $keywords );
156
157 // Send the generated content as the AJAX response
158 wp_send_json_success( $generated_content );
159 wp_die();
160 }
161
162 public function ai_autowrite_button() {
163
164 ?>
165 <script>
166 var docsTitle;
167
168 function responseMessage(message) {
169 return `<div class="warning-message">
170 <span class="dashicons dashicons-warning"></span>
171 <div class="footer-message">
172 ${message}
173 </div>
174 </div>`;
175 }
176
177 function generateArticle(e) {
178
179
180 const title = document.getElementById('betterdocs-ai-title').value;
181 const keywords = document.getElementById('betterdocs-ai-keyword').value;
182 // const sectionOptions = document.getElementById('bd-autowrtite-content-section');
183 // const numOfSections = sectionOptions.options[sectionOptions.selectedIndex].value;
184
185 // const paragraphOptions = document.getElementById('bd-autowrtite-content-paragraph');
186 // const numOfParagraphs = paragraphOptions.options[paragraphOptions.selectedIndex].value;
187
188 const contentTextArea = document.getElementById('betterdocs-ai-content');
189 const docGenerateBtnTxt = document.querySelector('.generate-btn span');
190
191 // Add nonce value to the data being sent
192 const nonce = document.querySelector('input[name="ai_nonce"]').value;
193 const isOverwrite = document.getElementById('betterdocs-ai-checkbox').checked;
194 const generateDocLabel = "<?php echo esc_html__( 'Generate Doc', 'betterdocs' ); ?>";
195 const reGenerateDocLabel = "<?php echo esc_html__( 'Regenerate Doc', 'betterdocs' ); ?>";
196
197
198 // 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.`;
199
200
201 // Check if the title is not empty
202 if (title.trim() !== '' && keywords.trim() !== '') {
203 e.preventDefault();
204
205 if (!jQuery('#betterdocs-ai-error-message').parent().hasClass('hidden')) {
206 jQuery('#betterdocs-ai-error-message').parent().addClass('hidden');
207 }
208
209 docGenerateBtnTxt.innerHTML = "<?php echo esc_html__( 'Generating...', 'betterdocs' ); ?>";
210 jQuery('.generate-btn').prop('disabled', true);
211
212 jQuery.post({
213 url: ajaxurl, // Make sure ajaxurl is defined in your script
214 type: 'POST',
215 data: {
216 action: 'generate_openai_content',
217 prompt: document.querySelector('#betterdocs-ai-content').value,
218 // numOfSections: numOfSections,
219 // numOfParagraphs: numOfParagraphs,
220 keywords: keywords,
221 ai_nonce: nonce, // Pass the nonce value
222 },
223 success: function(response) {
224 // Update the content in the textarea
225
226 if (response.success && (typeof response.data === 'string')) {
227 docGenerateBtnTxt.innerHTML = "<?php echo esc_html__( 'Generated', 'betterdocs' ); ?>";
228 setTimeout(() => {
229 insertContentToEditor(title, response.data, isOverwrite, keywords);
230 docGenerateBtnTxt.innerHTML = reGenerateDocLabel;
231 jQuery('.generate-btn').removeAttr('disabled');
232
233 // console.log(jQuery('.betterdocs-ai-autowrite-form-container').data('new-doc-page'));
234
235 // if(jQuery('.betterdocs-ai-autowrite-form-container')?.data('new-doc-page')) {
236 // docGenerateBtnTxt.innerHTML = reGenerateDocLabel;
237 // }
238 }, 2000);
239 } else {
240 // alert(response.data.message);
241 jQuery('#betterdocs-ai-error-message').parent().removeClass('hidden');
242 jQuery('#betterdocs-ai-error-message').html(responseMessage(response.data.message));
243 docGenerateBtnTxt.innerHTML = generateDocLabel;
244 jQuery('.generate-btn').removeAttr('disabled');
245
246 }
247 },
248 error: function(error) {
249 console.error(error);
250 },
251 });
252
253 }
254 }
255
256 // Function to update the textarea
257 function updateTextarea() {
258 const title = document.getElementById('betterdocs-ai-title').value;
259 const keywords = document.getElementById('betterdocs-ai-keyword').value;
260 const sectionOptions = document.getElementById('bd-autowrtite-content-section');
261 let language = 'English';
262
263 if (language === '') {
264 language = 'English';
265 }
266
267 let keywordsText = keywords !== '' ? ` and incorporate the keywords: ${keywords}` : '';
268
269 const contentTextArea = document.getElementById('betterdocs-ai-content');
270
271 if (!jQuery('#betterdocs-ai-error-message').parent().hasClass('hidden')) {
272 jQuery('#betterdocs-ai-error-message').parent().addClass('hidden');
273 }
274
275 contentTextArea.value = `Generate a documentation using HTML heading tags for '${title}'. Include relevant details on ${keywords} in the documentation. Ensure all content is enclosed with <p> tags and apply a <span> tag with the class 'highlight' to headings and topic tags for emphasis.`;
276 }
277
278 function convertMarkdownToHTML(markdownContent) {
279 // Simple Markdown to HTML conversion (you may need to enhance this based on your needs)
280 const htmlContent = markdownContent
281 .replace(/^# (.+)$/gm, '<h1>$1</h1>')
282 .replace(/^## (.+)$/gm, '<h2>$1</h2>')
283 .replace(/^### (.+)$/gm, '<h3>$1</h3>')
284 .replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
285 .replace(/\*(.+?)\*/g, '<em>$1</em>');
286
287 return htmlContent;
288 }
289
290 function replaceHeadingTitle(content, title, overviewTitle) {
291 let regex = new RegExp(title, 'g');
292 let updateContent = content.replace(regex, overviewTitle);
293 return updateContent;
294 }
295
296 function removeFirstHeading(htmlString) {
297 var newDiv = document.createElement('div');
298 newDiv.innerHTML = htmlString;
299 var firstHeading = newDiv.querySelector('h1');
300 if (firstHeading) {
301 firstHeading.parentNode.removeChild(firstHeading);
302 }
303 return newDiv.innerHTML;
304 }
305
306
307 function wrapwithHeighlight(inputString, keywords) {
308 let modifiedString = inputString.replace(/<(h\d)(.*?)>(.*?)<\/\1>/g, '<$1$2><span class="highlight">$3</span></$1>');
309
310 const keywordArray = keywords.split(',').map(keyword => keyword.trim());
311
312 keywordArray.forEach(keyword => {
313 modifiedString = modifiedString.replace(new RegExp(`\\b(${keyword})\\b`, 'gi'), '<span class="highlight">$1</span>');
314 });
315
316 return modifiedString;
317 }
318
319 function getBodyContent(htmlString) {
320 var match = htmlString.match(/<body[^>]*>([\s\S]*?)<\/body>/i);
321
322 // Check if match is null before accessing match[1]
323 if (match) {
324 console.log(match[1].replace(/<p>\s+/g, '<p>'));
325 return match[1].replace(/<p>\s+/g, '<p>');
326 } else {
327 return '';
328 }
329 }
330
331
332
333 function insertContentToEditor(title, htmlContent, isOverwrite, keywords) {
334
335 const {
336 dispatch,
337 select
338 } = wp.data;
339
340 htmlContent = getBodyContent(htmlContent);
341 htmlContent = removeFirstHeading(htmlContent);
342 htmlContent = wrapwithHeighlight(htmlContent, keywords);
343 const isPostContent = `<?php echo isset($_GET['post']) ? esc_html(get_the_content($_GET['post'])) : ''; // phpcs:ignore ?>`;
344
345 const blocks = wp.blocks.rawHandler({
346 HTML: htmlContent
347 });
348
349 dispatch('core/editor').editPost({
350 title: title,
351 });
352
353 const selectedBlockClientId = select('core/block-editor').getSelectedBlockClientId();
354
355 const allBlocks = select('core/block-editor').getBlocks();
356
357 if (isOverwrite || isPostContent === '') {
358
359 allBlocks.forEach((block) => {
360 dispatch('core/block-editor').removeBlock(block.clientId);
361 });
362 dispatch('core/block-editor').insertBlocks(blocks);
363
364 } else if (selectedBlockClientId) {
365 const selectedIndex = select('core/block-editor').getBlockIndex(selectedBlockClientId);
366 dispatch('core/block-editor').insertBlocks(blocks, selectedIndex + 1);
367 } else {
368 dispatch('core/block-editor').insertBlocks(blocks);
369 }
370
371 closeWriteWithAIForm('afterInsertContent');
372 }
373
374 function closeWriteWithAIForm(after) {
375 jQuery('.betterdocs-ai-autowrite-form-container').addClass('hidden');
376 jQuery('#betterdocs-ai-autowrite-form-container-overlay').addClass('hidden');
377 jQuery('.betterdocs-ai-button').addClass('regenerate-btn');
378 }
379
380
381 document.addEventListener('DOMContentLoaded', function() {
382
383 // Subscribe to changes in the editor state
384 let previousDocsTitle = '';
385
386 wp.data.subscribe(() => {
387 // Get the updated post title
388 // const docsTitle = wp.data.select('core/editor').getEditedPostAttribute('title');
389
390 const docsTitle = wp.data.select('core/editor').getEditedPostAttribute('title');
391
392 // Check if the title has changed
393 if (docsTitle !== previousDocsTitle) {
394 let currentKeywords = jQuery('#betterdocs-ai-keyword').val();
395 if (!currentKeywords) {
396 currentKeywords = '{Documentation Keywords}';
397 }
398 const currentPrompt = `Generate documentation using HTML heading tags for '${docsTitle?docsTitle:'{Documentation title}'}'. Include relevant details on ${currentKeywords} in the documentation. Ensure all content is enclosed with <p> tags and apply a <span> tag with the class 'highlight' to headings and topic tags for emphasis.`;
399 jQuery('#betterdocs-ai-content').val(currentPrompt);
400
401 jQuery('#betterdocs-ai-title').val(docsTitle);
402
403 previousDocsTitle = docsTitle;
404 }
405
406
407 });
408 });
409
410
411 // This example assumes that this code is executed when your script is loaded.
412 // You may want to place it within the appropriate context in your application.
413
414
415 function writeWithAIForm() {
416
417 let title = `<?php echo isset($_GET['post']) ? esc_html(get_the_title($_GET['post'])) : ''; // phpcs:ignore ?>`;
418 if (docsTitle) {
419 title = docsTitle;
420 }
421
422 const promtTitle = `<?php echo isset($_GET['post']) ? esc_html(get_the_title($_GET['post'])) : '{Documentation Title}'; // phpcs:ignore ?>`;
423 const titlePlaceholder = "<?php echo esc_attr__( 'Enter a descriptive title for your documentation.', 'betterdocs' ); ?>";
424 const keywords = "<?php echo esc_attr( '{Documentation Keywords}' ); ?>";
425 const keywordsPlaceholder = "<?php echo esc_attr__( 'Add keywords to generate precise & relevant documentation (comma-separated).', 'betterdocs' ); ?>";
426 const language = "<?php echo esc_attr( 'English' ); ?>";
427 const titleLabel = "<?php echo esc_html__( 'Documentation Title:', 'betterdocs' ); ?>";
428 const keywordLabel = "<?php echo esc_html__( 'Keywords:', 'betterdocs' ); ?>";
429 const secLabel = "<?php echo esc_html__( '# of Sections:', 'betterdocs' ); ?>";
430 const paraLabel = "<?php echo esc_html__( '# of Paragraph Per Section: ', 'betterdocs' ); ?>";
431 const langLabel = "<?php echo esc_html__( 'Language:', 'betterdocs' ); ?>";
432 const promtLabel = "<?php echo esc_html__( 'Prompt:', 'betterdocs' ); ?>";
433 const promtBtnLabel = "<?php echo esc_html__( 'Generate Prompt', 'betterdocs' ); ?>";
434 let promtArticleLabel = "<?php echo esc_html__( 'Generate Doc', 'betterdocs' ); ?>";
435 const checkboxLabel = "<?php echo esc_html__( 'Overwrite your existing Doc:', 'betterdocs' ); ?>";
436 const nonce = "<?php echo esc_attr( wp_create_nonce( 'generate_openai_content_nonce' ) ); ?>";
437
438 <?php
439 $is_valid = $this->get_api_key();
440 $disable_field = 'disabled-input-field';
441 if ( $this->get_api_key() ) {
442 $disable_field = '';
443 }
444 ?>
445
446 let hiddenClass = 'hidden';
447 let newDocPageAtt = 'data-new-doc-page="true"';
448 const isPostContent = `<?php echo isset($_GET['post']) ? esc_html(get_the_content($_GET['post'])) : ''; // phpcs:ignore ?>`;
449
450 if (isPostContent !== '') {
451 hiddenClass = '';
452 newDocPageAtt = '';
453 }
454
455
456 // const prompt = `Make a knowledge base article with html heading tags about [${title}] in [${language}]. Here is some topic to describe the article: [${keywords}]. and wrap the content with p tag also add a span tag with a class named 'highlight' to all the heading and topic tags in the article.`;
457
458 // const prompt = `Create a details knowledge base article in [${language}] about [${title}] with html heading tags. Here is some topic to describe the article: [${keywords}]. And wrap the content with p tag also add a span tag with a class named 'highlight' to all the heading and topic tags in the article.`;
459
460 const prompt = `Generate a documentation using HTML heading tags for '${promtTitle}'. Include relevant details on ${keywords} in the documentation. Ensure all content is enclosed with <p> tags and apply a <span> tag with the class 'highlight' to headings and topic tags for emphasis.`;
461
462 const aiIcon = `<svg xmlns="http://www.w3.org/2000/svg" width="21" height="20" viewBox="0 0 21 20" fill="none">
463 <g clip-path="url(#clip0_2460_1729)">
464 <path d="M10.3956 18.8608L6.10991 19.2322L6.48134 14.9465L15.3956 6.08936C15.5287 5.95329 15.6876 5.84518 15.863 5.77137C16.0384 5.69756 16.2268 5.65954 16.4171 5.65954C16.6074 5.65954 16.7957 5.69756 16.9711 5.77137C17.1466 5.84518 17.3054 5.95329 17.4385 6.08936L19.2528 7.91793C19.3865 8.05083 19.4926 8.20886 19.565 8.38293C19.6375 8.55701 19.6747 8.74368 19.6747 8.93221C19.6747 9.12075 19.6375 9.30742 19.565 9.48149C19.4926 9.65557 19.3865 9.8136 19.2528 9.9465L10.3956 18.8608ZM1.7042 5.67507C1.20277 5.58793 1.20277 4.86793 1.7042 4.78079C2.59203 4.62626 3.41374 4.21085 4.06456 3.58751C4.71538 2.96417 5.16583 2.16113 5.35848 1.28079L5.38848 1.14221C5.49705 0.647929 6.20277 0.643643 6.31705 1.13936L6.35277 1.29936C6.55248 2.17579 7.0067 2.97367 7.65837 3.59281C8.31005 4.21195 9.13013 4.62475 10.0156 4.77936C10.5199 4.86507 10.5199 5.58936 10.0156 5.67793C9.13005 5.83218 8.3098 6.24465 7.65787 6.86354C7.00594 7.48243 6.5514 8.28014 6.35134 9.1565L6.3142 9.31793C6.20134 9.81221 5.49563 9.80936 5.38705 9.31364L5.35848 9.17507C5.16564 8.29433 4.71476 7.49102 4.06339 6.86764C3.41202 6.24426 2.58969 5.82908 1.70134 5.67507H1.7042Z" stroke="white" stroke-width="1.42857" stroke-linecap="round" stroke-linejoin="round"/>
465 </g>
466 <defs>
467 <clipPath id="clip0_2460_1729">
468 <rect width="20" height="20" fill="white" transform="translate(0.5)"/>
469 </clipPath>
470 </defs>
471 </svg>`;
472
473
474
475 return `
476 <div class="betterdocs-ai-autowrite-form-container hidden" ${newDocPageAtt}>
477 <div class="betterdocs-ai-autowrite-form-content">
478 <div class="betterdocs-ai-autowrite-top-part">
479 <div class="autowrite-heading">
480 <span class="autowrite-icon">
481 <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" fill="none">
482 <g clip-path="url(#clip0_2453_20882)">
483 <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"/>
484 </g>
485 <defs>
486 <clipPath id="clip0_2453_20882">
487 <rect width="32" height="32" fill="white"/>
488 </clipPath>
489 </defs>
490 </svg>
491 </span>
492 <h1><?php echo esc_html__( 'Write Documentation with BetterDocs AI', 'betterdocs' ); ?></h1>
493
494 </div>
495 <div class="autowrite-subheadding">
496 <p><?php echo esc_html__( 'Generate documentation effortlessly with BetterDocs AI. Simply input your doc title, keywords, prompt and let the system automatically generate comprehensive documentation tailored to your needs.', 'betterdocs' ); ?></p>
497 </div>
498
499 <?php if ( empty( $this->get_api_key() ) ) : ?>
500 <div id="betterdocs-ai-message">
501 <div class="warning-message">
502 <span class="dashicons dashicons-warning"></span>
503 <div>
504 <?php echo wp_kses_post( 'Please Insert your <a target="_blank" href="' . esc_url( admin_url( 'admin.php?page=betterdocs-settings&tab=tab-ai-autowrite' ) ) . '">OpenAI API Key</a> to use this Write with AI feature.', 'betterdocs' ); ?>
505 </div>
506 </div>
507
508 </div>
509 <?php endif; ?>
510
511 <div class="form-group hidden">
512 <div id="betterdocs-ai-error-message"></div>
513 </div>
514
515 </div>
516 <form id="betterdocs-ai-form" class="<?php echo esc_attr( $disable_field ); ?>">
517 <div class="form-inner-content">
518 <div class="form-group">
519 <label for="betterdocs-ai-title">${titleLabel}</label>
520 <input type="text" placeholder="${titlePlaceholder}" id="betterdocs-ai-title" name="betterdocs-ai-title" required value="${title}">
521 </div>
522
523 <div class="form-group">
524 <label for="betterdocs-ai-keyword">${keywordLabel}</label>
525 <input type="text" placeholder="${keywordsPlaceholder}" id="betterdocs-ai-keyword" name="betterdocs-ai-keyword" required>
526 </div>
527
528 <div class="form-group prompt-field">
529 <label for="betterdocs-ai-content">${promtLabel}</label>
530 <textarea id="betterdocs-ai-content" name="betterdocs-ai-content" rows="6" required>${prompt}</textarea>
531 <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>
532 </div>
533 <div class="betterdocs-ai-checkbox-field ${hiddenClass}">
534 <div class="form-group">
535 <div class="checkbox-field-label">
536 <label for="betterdocs-ai-checkbox">${checkboxLabel}</label>
537 <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>
538 </div>
539
540 <div class="checkbox-input-field">
541 <input type="checkbox" id="betterdocs-ai-checkbox" name="betterdocs-ai-checkbox" data-overwrite="false">
542 <label for="betterdocs-ai-checkbox" class="toggle-label"></label>
543 </div>
544 </div>
545 </div>
546
547 <input type="hidden" name="ai_nonce" value="${nonce}" />
548 </div>
549 <div class="generate-button-container">
550 <button type="submit" class="generate-btn" onclick="generateArticle(event)">
551 ${aiIcon} <span>${promtArticleLabel}</span>
552 </button>
553 </div>
554 </form>
555 </div>
556
557 <div class="bd-close-button" onclick="closeWriteWithAIForm('afterClosedClicked')">✕</div>
558 </div>
559
560 <div id="betterdocs-ai-autowrite-form-container-overlay" class="hidden"></div>
561 `;
562 }
563
564 jQuery(document).on('click', '.regenerate-btn', function() {
565 jQuery('.betterdocs-ai-autowrite-form-container').removeClass('hidden');
566 jQuery('#betterdocs-ai-autowrite-form-container-overlay').removeClass('hidden');
567 });
568
569 jQuery(document).ready(function($) {
570 // Check if we are in the Edit Post screen
571
572 if ($('.block-editor-page').length > 0) {
573
574 const bdAIButton = $(`<div class="betterdocs-ai-button-container">
575 <button class="betterdocs-ai-button">
576 <img src="<?php echo esc_url( BETTERDOCS_ABSURL . 'assets/admin/images/betterdocs-icon-white.png' ); ?>" alt="<?php echo esc_attr__( 'betterdocs icon', 'betterdocs' ); ?>" />
577 <span><?php echo esc_html__( 'Write with AI', 'betterdocs' ); ?></span>
578 </button>
579 </div>`);
580
581
582 const closeBtn = $('bd-close-button');
583
584 const formHtml = writeWithAIForm();
585 $(formHtml).addClass('hidden');
586
587 $(document).on('click', '.betterdocs-ai-button', function() {
588 $('.betterdocs-ai-autowrite-form-container').removeClass('hidden');
589 $('#betterdocs-ai-autowrite-form-container-overlay').removeClass('hidden');
590 });
591
592 // $(document).on('click', '.betterdocs-ai-button', function() {
593 if ($('.betterdocs-ai-autowrite-form-container').length < 1) {
594 $('body').append(formHtml);
595
596 // Add event listeners to input elements
597 document.getElementById('betterdocs-ai-title').addEventListener('input', updateTextarea);
598 document.getElementById('betterdocs-ai-keyword').addEventListener('input', updateTextarea);
599 // document.getElementById('bd-autowrtite-content-section').addEventListener('change', updateTextarea);
600 // document.getElementById('bd-autowrtite-content-paragraph').addEventListener('change', updateTextarea);
601 // document.getElementById('betterdocs-ai-language').addEventListener('input', updateTextarea);
602 }
603 // });
604 const btn = document.createElement('button');
605 wp.data.subscribe(function() {
606 setTimeout(() => {
607 if ($('.edit-post-header__settings').length > 0) {
608 $('.edit-post-header__settings').prepend(bdAIButton);
609 } else if ($('.editor-header__settings').length > 0) {
610 $('.editor-header__settings').prepend(bdAIButton);
611 }
612
613 }, 1);
614 });
615 }
616
617 $(document).on('click', '#betterdocs-ai-autowrite-form-container-overlay', function() {
618 closeWriteWithAIForm();
619 });
620 });
621 </script>
622
623 <style>
624 .hidden {
625 display: none !important;
626 }
627
628 .disabled-input-field input,
629 .disabled-input-field textarea,
630 .disabled-input-field button,
631 .disabled-input-field label {
632 pointer-events: none;
633 opacity: 0.6;
634 }
635
636 .disabled-input-field label {
637 opacity: 1;
638 }
639
640 .betterdocs-ai-button-container {
641 margin-left: 10px;
642 }
643
644 .autowrite-icon {
645 display: flex;
646 align-items: center;
647 width: 55px;
648 height: 55px;
649 background: #FFE4E8;
650 justify-content: center;
651 border-radius: 50px;
652 }
653
654 .autowrite-icon svg {
655 width: 28px;
656 height: 28px;
657 }
658
659 .autowrite-heading {
660 display: flex;
661 gap: 10px;
662 align-items: center;
663 }
664
665 .autowrite-heading h1 {
666 color: #1D2939;
667 font-family: 'IBM Plex Sans', sans-serif;
668 font-size: 24px;
669 font-style: normal;
670 font-weight: 500;
671 line-height: normal;
672 margin: 0;
673 }
674
675 .autowrite-heading p,
676 form#betterdocs-ai-form p {
677 margin: 0;
678 margin-bottom: 24px;
679 }
680
681 .autowrite-subheadding p {
682 font-family: 'IBM Plex Sans', sans-serif;
683 font-size: 14px;
684 }
685
686 .prompt-field .input-description {
687 margin: 0 !important;
688 }
689
690 .autowrite-heading p {
691 color: #475467;
692 font-family: 'IBM Plex Sans', sans-serif;
693 font-size: 16px;
694 font-style: normal;
695 font-weight: 400;
696 }
697
698 .betterdocs-ai-button {
699 background: #00B884;
700 border: 1px solid transparent;
701 color: #fff;
702 box-shadow: none;
703 font-size: 12px;
704 margin-right: 8px;
705 padding: 0px 15px;
706 cursor: pointer;
707 border-radius: 3px;
708 height: 38px;
709 display: flex;
710 align-items: center;
711 justify-content: space-between;
712 gap: 10px;
713 }
714
715 .betterdocs-ai-button img {
716 width: 20px;
717 height: 20px;
718 }
719
720 .betterdocs-ai-autowrite-form-container {
721 position: fixed;
722 top: 50%;
723 left: 50%;
724 transform: translate(-50%, -50%);
725 z-index: 100001;
726 width: 650px;
727 margin: 0 auto;
728 background-color: #fff;
729 border-radius: 5px;
730 font-family: 'IBM Plex Sans', sans-serif;
731 /* max-height: 600px; */
732 max-height: 750px;
733 max-width: 100%;
734 }
735
736 .betterdocs-ai-autowrite-form-content {
737 background-color: #fff;
738 padding: 20px 0px;
739 border-radius: 5px;
740 padding-bottom: 0;
741 overflow: auto;
742 /* max-height: 700px; */
743 height: 100%;
744
745 }
746
747 .betterdocs-ai-autowrite-top-part {
748 /* padding-bottom: 20px; */
749 border-bottom: 1px solid #ddd;
750 /* margin-bottom: 20px; */
751 padding: 0 40px;
752 }
753
754 #betterdocs-ai-form {
755 display: flex;
756 flex-direction: column;
757 /* height: 100%; */
758 overflow: hidden;
759 }
760
761 .form-inner-content {
762 overflow: auto;
763 height: 100%;
764 max-height: 435px;
765 /* max-height: 330px; */
766 padding: 0 40px;
767 }
768
769 .form-inner-content>.form-group {
770 margin-top: 20px;
771 }
772
773
774 #betterdocs-ai-autowrite-form-container-overlay {
775 position: fixed;
776 top: 0;
777 right: 0;
778 bottom: 0;
779 left: 0;
780 background-color: rgba(0, 0, 0, .7);
781 z-index: 100000;
782 }
783
784
785
786 #betterdocs-ai-form {
787 display: flex;
788 flex-direction: column;
789 }
790
791 #betterdocs-ai-form .form-group {
792 margin-bottom: 24px;
793 }
794
795 #betterdocs-ai-form .bd-aiform-flex {
796 display: flex;
797 justify-content: space-between;
798 }
799
800 #betterdocs-ai-form label {
801 font-weight: 600;
802 margin-bottom: 5px;
803 display: block;
804 font-size: 14px;
805 line-height: 20px;
806 }
807
808 select#bd-autowrtite-content-section,
809 #bd-autowrtite-content-paragraph,
810 #betterdocs-ai-language {
811 width: 150px !important;
812 height: 40px;
813 }
814
815 #betterdocs-ai-form input[type="text"],
816 #betterdocs-ai-form textarea {
817 width: 100%;
818 padding: 8px;
819 box-sizing: border-box;
820 border: 1px solid #D0D5DD;
821 border-radius: 4px;
822 color: #667085;
823 font-weight: 400;
824 }
825
826 /* Override autofill text color */
827 #betterdocs-ai-form input:-webkit-autofill {
828 -webkit-text-fill-color: #667085 !important;
829 }
830
831 #betterdocs-ai-form input::placeholder {
832 color: #acb2bf;
833 }
834
835 #betterdocs-ai-form textarea {
836 color: #667085;
837 }
838
839 #betterdocs-ai-form input:focus,
840 #betterdocs-ai-form textarea:focus {
841 box-shadow: none;
842 }
843
844 #betterdocs-ai-form textarea:focus {
845 color: inherit;
846 box-shadow: none;
847
848 }
849
850
851 .generate-button-container {
852 position: sticky;
853 bottom: 0px;
854 width: 100%;
855 margin-left: 0;
856 z-index: 999999 !important;
857 padding: 20px 0;
858 display: flex;
859 justify-content: end;
860 border-top: 1px solid #ddd;
861 background: white;
862 border-bottom-right-radius: 5px;
863 border-bottom-left-radius: 5px;
864 }
865
866 .generate-button-container button {
867 display: flex;
868 gap: 8px;
869 align-items: center;
870 justify-content: center;
871 opacity: 1 !important;
872 margin-right: 40px;
873 }
874
875 .input-description {
876 font-size: 14px;
877 color: #667085;
878 }
879
880 .betterdocs-ai-checkbox-field .form-group {
881 display: flex;
882 align-items: start;
883 /* gap: 200px; */
884 justify-content: space-between;
885 }
886
887 .betterdocs-ai-checkbox-field input {
888 width: 20px;
889 height: 20px;
890 }
891
892 /* Add this CSS to your existing stylesheet or create a new one */
893
894 .betterdocs-ai-checkbox-field {
895 position: relative;
896 font-size: 16px;
897 /* Adjust font size as needed */
898 }
899
900 .betterdocs-ai-checkbox-field input[type=checkbox]:checked::before {
901 padding: 2px;
902 }
903
904 .betterdocs-ai-checkbox-field input[type=checkbox] {
905 border: 1px solid #00b884;
906 }
907
908 .betterdocs-ai-checkbox-field input[type=checkbox]:checked::before {
909 content: '\2713';
910 color: #00b884;
911 }
912
913 /* Change the default checkbox color */
914 .betterdocs-ai-checkbox-field input[type="checkbox"]:hover {
915 -webkit-appearance: none;
916 /* WebKit/Blink Browsers */
917 -moz-appearance: none;
918 /* Firefox */
919 appearance: none;
920 border: 1px solid #00b884;
921 /* Set the height of the checkbox */
922 /* Optional: Round the corners */
923 outline: none;
924 /* Remove the default outline */
925 }
926
927 /* Style the checked state */
928 .betterdocs-ai-checkbox-field input[type="checkbox"]:checked {
929 /* Set the background color when checked */
930 border: 1px solid #00b884;
931 /* Set the border color when checked */
932 }
933
934 .checkbox-field-label {
935 width: calc(100% - 150px);
936 }
937
938 .checkbox-field-label p {
939 margin: 0 !important;
940 }
941
942 .checkbox-input-field {
943 width: 300px;
944 text-align: right;
945 }
946
947 .checkbox-input-field {
948 position: relative;
949 display: inline-block;
950 width: 60px;
951 height: 34px;
952 }
953
954 .checkbox-input-field input {
955 display: none;
956 }
957
958 .toggle-label {
959 position: absolute;
960 cursor: pointer;
961 top: 0;
962 left: 0;
963 right: 0;
964 bottom: 0;
965 background-color: #ccc;
966 border-radius: 34px;
967 transition: background-color 0.3s;
968 scale: .9;
969 width: 52px;
970 height: 26px;
971 }
972
973
974 .checkbox-input-field input:checked+.toggle-label {
975 background-color: #00b884;
976 }
977
978 .toggle-label:after {
979 content: "";
980 position: absolute;
981 height: 22px;
982 width: 22px;
983 left: 2px;
984 bottom: 2px;
985 background-color: white;
986 border-radius: 50%;
987 transition: transform 0.3s;
988 }
989
990 .checkbox-input-field input:checked+.toggle-label:after {
991 transform: translateX(26px);
992 }
993
994 .generate-btn,
995 .prompt-btn,
996 .keep-btn {
997 background-color: #00b884;
998 color: #fff;
999 border: none;
1000 padding: 10px 15px;
1001 text-align: center;
1002 text-decoration: none;
1003 display: inline-block;
1004 font-size: 16px;
1005 cursor: pointer;
1006 border-radius: 4px;
1007 }
1008
1009 .generate-btn[disabled] {
1010 cursor: unset;
1011 }
1012
1013 .bd-close-button {
1014 cursor: pointer;
1015 font-size: 18px;
1016 font-weight: bold;
1017 float: right;
1018 position: absolute;
1019 right: 1px;
1020 top: 1px;
1021 padding: 10px;
1022 background: #ffffff;
1023 border-radius: 25px;
1024 width: 20px;
1025 height: 20px;
1026 display: flex;
1027 align-items: center;
1028 justify-content: center;
1029 color: #281617;
1030 right: -60px;
1031 }
1032
1033 div#betterdocs-ai-error-message,
1034 #betterdocs-ai-message {
1035 font-size: 14px;
1036 font-family: 'IBM Plex Sans', sans-serif;
1037 color: #667085;
1038 margin-bottom: 20px;
1039 }
1040
1041 div#betterdocs-ai-error-message a,
1042 #betterdocs-ai-message a {
1043 text-decoration: none;
1044 font-weight: 600;
1045 }
1046
1047 div#betterdocs-ai-error-message a:focus,
1048 #betterdocs-ai-message a:focus {
1049 outline: none;
1050 box-shadow: none;
1051 color: #2271b1;
1052 }
1053
1054 #betterdocs-ai-message span {
1055 color: #d63638;
1056 }
1057
1058 .warning-message {
1059 display: flex;
1060 align-items: center;
1061 gap: 10px;
1062 background: #fbebed;
1063 padding: 12px;
1064 border-radius: 5px;
1065 font-size: 14px;
1066 line-height: 1.4em;
1067 border-left: 4px solid #d63638;
1068 }
1069
1070 .warning-message svg {
1071 width: 20px;
1072 height: 20px;
1073 }
1074
1075 .warning-message span {
1076 color: #d63638;
1077 }
1078
1079 .warning-message .footer-message {
1080 width: calc(100% - 20px);
1081 }
1082
1083 @media only screen and (max-width: 991px) {
1084 .betterdocs-ai-autowrite-form-container {
1085 left: 0;
1086 right: 0;
1087 transform: translate(0%, -50%);
1088 }
1089
1090 .betterdocs-ai-autowrite-form-content {
1091 overflow-x: auto;
1092 }
1093 }
1094
1095 @media only screen and (max-width: 740px) {
1096
1097 /* .bd-close-button {
1098 right: px;
1099 top: 1px;
1100 border-radius: 5px;
1101 width: 12px;
1102 height: 12px;
1103 color: #ffffff;
1104 background: #00b884;
1105 } */
1106 .bd-close-button {
1107 right: 0px;
1108 top: 0px;
1109 width: 12px;
1110 height: 12px;
1111 font-size: 14px;
1112 }
1113
1114 }
1115 </style>
1116 <?php
1117 }
1118 }
1119