PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 3.5.3
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v3.5.3
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
← All changes | includes/Core/WriteWithAI.php +950 -663 4.7.03.5.3 View file →
@@ -1,813 +1,1100 @@
1 1 <?php
2 2
3 - namespace WPDeveloper\BetterDocs\Core;
3 +namespace WPDeveloper\BetterDocs\Core;
4 4
5 -if ( ! defined( 'ABSPATH' ) ) {
6 - exit;
7 -}
5 +use WPDeveloper\BetterDocs\Utils\Base;
6 +use WPDeveloper\BetterDocs\Core\Settings;
7 +use WPDeveloper\BetterDocs\Core\PostType;
8 8
9 +use WPDeveloper\BetterDocs\Utils\Helper;
9 10
10 - use WPDeveloper\BetterDocs\Utils\Base;
11 - use WPDeveloper\BetterDocs\Core\Settings;
12 - use WPDeveloper\BetterDocs\Core\PostType;
13 -
14 - use WPDeveloper\BetterDocs\Utils\Helper;
15 - use WPDeveloper\BetterDocs\Utils\AIHelper;
16 - use WPDeveloper\BetterDocs\Utils\AIUsage;
17 - use WPDeveloper\BetterDocs\REST\AIEdit;
18 -
19 - class WriteWithAI extends Base {
20 -
11 +class WriteWithAI extends Base
12 +{
21 13 public $settings;
22 14
23 - public function __construct( Settings $settings ) {
15 + public function __construct(Settings $settings)
16 + {
24 17 $this->settings = $settings;
25 18 // Get the post ID from the URL
26 - $post_id = isset( $_GET[ 'post' ] ) ? intval( $_GET[ 'post' ] ) : 0; // phpcs:ignore
19 + $post_id = isset($_GET['post']) ? intval($_GET['post']) : 0;
27 20
28 - if ( ! empty( $_GET[ 'post_type' ] ) ) { // phpcs:ignore
29 - $post_type = $_GET[ 'post_type' ]; // phpcs:ignore
30 - } elseif ( $post_id > 0 ) {
31 - $post_type = get_post_type( $post_id );
21 + if (!empty($_GET['post_type'])) {
22 + $post_type = $_GET['post_type'];
23 + } else if ($post_id > 0) {
24 + $post_type = get_post_type($post_id);
32 25 } else {
33 26 $post_type = '';
34 27 }
35 28
36 - if ( ! empty( $this->isEnabledWriteWithAI() ) && 'docs' == $post_type ) {
37 - add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_ai_edit_assets' ) );
29 + if (!empty($this->isEnabledWriteWithAI()) && $post_type == 'docs') {
30 + add_action('admin_footer', [$this, 'ai_autowrite_button']);
38 31 }
39 - // Legacy AJAX handler kept for back-compat; the redesigned modal uses the REST route.
40 - add_action( 'wp_ajax_generate_openai_content', array( $this, 'generate_openai_content_callback' ) );
32 + add_action('wp_ajax_generate_openai_content', [$this, 'generate_openai_content_callback']);
41 33 }
42 34
43 - public function enqueue_ai_edit_assets( $hook ) {
44 - if ( 'post.php' !== $hook && 'post-new.php' !== $hook ) {
45 - return;
35 + public function isEnabledWriteWithAI()
36 + {
37 + $isEnableAutoWrite = $this->settings->get('enable_write_with_ai', true);
38 + return $isEnableAutoWrite;
39 + }
40 +
41 + public function isValidAPIKey($apiKey)
42 + {
43 + if (empty($apiKey)) {
44 + $api_response['valid'] = false;
45 + $api_response['message'] = 'Please Insert your <a href="/admin.php?page=betterdocs-settings">OpenAI API Key</a> to use this Write with AI feature.';
46 +
47 + return $api_response;
46 48 }
47 49
48 - global $post_type;
49 - if ( 'docs' !== $post_type ) {
50 - return;
51 - }
50 + $ch = curl_init('https://api.openai.com/v1/engines');
51 + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
52 + curl_setopt($ch, CURLOPT_HTTPHEADER, [
53 + 'Content-Type: application/json',
54 + 'Authorization: Bearer ' . $apiKey,
55 + ]);
52 56
53 - $api_key = $this->get_api_key();
54 - $has_key = ! empty( $api_key );
57 + $api_response = [];
55 58
56 - // Write with AI — loads even without a key so the modal can show its
57 - // "add an API key" banner (matches the legacy inline form behavior).
58 - betterdocs()->assets->enqueue( 'betterdocs-write-with-ai', 'blocks/write-with-ai.js' );
59 - betterdocs()->assets->enqueue( 'betterdocs-write-with-ai-style', 'blocks/write-with-ai-style.css' );
59 + $response = curl_exec($ch);
60 + $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
60 61
61 - wp_localize_script(
62 - 'betterdocs-write-with-ai',
63 - 'betterdocsWriteWithAI',
64 - array(
65 - 'rest_url' => esc_url_raw( rest_url( 'betterdocs/v1/write-with-ai' ) ),
66 - 'rest_nonce' => wp_create_nonce( 'wp_rest' ),
67 - 'post_id' => get_the_ID(),
68 - 'has_key' => $has_key,
69 - // Term-suggestion (Docs AI Suite) wiring reused by the Write-with-AI
70 - // preview step. Endpoint + gate mirror REST\DocsAISuite / Core\DocsAISuite.
71 - 'rest_suggest_url' => esc_url_raw( rest_url( 'betterdocs/v1/ai-suggest-terms' ) ),
72 - 'suggest_terms_enabled' => (bool) $this->settings->get( 'enable_docs_ai_suite', true ),
73 - 'model' => $this->settings->get( 'write_with_ai_model', 'gpt-4o-mini' ),
74 - 'max_token' => (int) $this->settings->get( 'ai_autowrite_max_token', 2500 ),
75 - 'settings_url' => esc_url( admin_url( 'admin.php?page=betterdocs-settings#betterdocs-ai' ) ),
76 - 'woo_active' => class_exists( 'WooCommerce' ),
77 - 'is_multilingual_active' => Helper::is_multilingual_active(),
78 - 'language_options' => Helper::get_active_languages(),
79 - 'instructions' => $this->get_instruction_choices(),
80 - // From Git is a Pro feature. Pro is detected here; Git enabled/auth
81 - // status is filled in by Pro via the filter below (default: off).
82 - 'is_pro_active' => betterdocs()->is_pro_active(),
83 - 'git' => apply_filters(
84 - 'betterdocs_write_with_ai_git',
85 - array(
86 - 'enabled' => false,
87 - 'connected' => false,
88 - 'settings_url' => admin_url( 'admin.php?page=betterdocs-settings#git-sync' ),
89 - )
90 - ),
91 - )
92 - );
62 + curl_close($ch);
93 63
94 - // AI Edit — only meaningful with a key configured.
95 - if ( ! $has_key ) {
96 - return;
64 + if ($httpCode == 200) {
65 + $api_response['valid'] = true;
66 + $api_response['message'] = 'Valid API Key';
67 + } else {
68 + $responseData = json_decode($response, true);
69 + // Access the message data (replace 'data' with the actual key used in the response)
70 + $messageData = $responseData['error'] ? $responseData['error'] : '';
71 + $api_response['valid'] = false;
72 + $api_response['message'] = $messageData['message'] ? $messageData['message'] : 'Invalid API Key';
97 73 }
98 74
99 - betterdocs()->assets->enqueue( 'betterdocs-ai-edit', 'blocks/ai-edit.js' );
100 - betterdocs()->assets->enqueue( 'betterdocs-ai-edit-style', 'blocks/ai-edit-style.css' );
75 + // print_r($response);
101 76
102 - wp_localize_script(
103 - 'betterdocs-ai-edit',
104 - 'betterdocsAIEdit',
105 - array(
106 - 'rest_url' => esc_url_raw( rest_url( 'betterdocs/v1/ai-edit' ) ),
107 - 'rest_nonce' => wp_create_nonce( 'wp_rest' ),
108 - 'post_id' => get_the_ID(),
109 - 'instructions' => $this->get_instruction_choices(),
110 - 'actions' => AIEdit::get_localized_actions()
111 - )
112 - );
77 + return $api_response;
113 78 }
114 79
115 - public function isEnabledWriteWithAI() {
116 - $isEnableAutoWrite = $this->settings->get( 'enable_write_with_ai', true );
117 - return $isEnableAutoWrite;
80 + public function get_api_key()
81 + {
82 + $api_key = $this->settings->get('ai_autowrite_api_key', '');
83 + return $api_key;
118 84 }
119 85
120 - public function isValidAPIKey( $apiKey ) {
121 - if ( empty( $apiKey ) ) {
122 - $api_response[ 'valid' ] = false;
123 - $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.';
124 86
125 - return $api_response;
126 - }
87 + public function generate_openai_response($prompt, $keywords)
88 + {
89 + try {
90 + $api_key = $this->settings->get('ai_autowrite_api_key', '');
91 + $max_tokens = $this->settings->get('ai_autowrite_max_token', 1500);
127 92
128 - $api_response = array();
93 + $api_endpoint = 'https://api.openai.com/v1/chat/completions'; // Update the endpoint based on OpenAI API version
129 94
130 - $response = wp_safe_remote_get(
131 - 'https://api.openai.com/v1/engines',
132 - array(
95 + $request_options = array(
133 96 'headers' => array(
134 - 'Content-Type' => 'application/json',
135 - 'Authorization' => 'Bearer ' . $apiKey,
97 + 'Content-Type' => 'application/json',
98 + 'Authorization' => 'Bearer ' . $api_key,
136 99 ),
137 - 'timeout' => 15,
138 - )
139 - );
100 + 'body' => json_encode(array(
101 + 'model' => 'gpt-3.5-turbo', // Add the model parameter here
102 + 'messages' => array(
103 + array('role' => 'system', 'content' => 'You are a helpful assistant who writes documentation for users.'),
104 + array('role' => 'user', 'content' => $prompt),
105 + ),
106 + 'max_tokens' => $max_tokens,
140 107
141 - if ( is_wp_error( $response ) ) {
142 - $api_response[ 'valid' ] = false;
143 - $api_response[ 'message' ] = $response->get_error_message();
144 - return $api_response;
108 + )),
109 + 'timeout' => 50,
110 + );
111 +
112 + $response = wp_remote_post($api_endpoint, $request_options);
113 +
114 + if (is_wp_error($response)) {
115 + return 'Error: ' . $response->get_error_message();
116 + } else {
117 + $body = wp_remote_retrieve_body($response);
118 +
119 + $data = json_decode($body, true);
120 +
121 + if (!empty($data['error'])) {
122 + return $data['error']['message'];
123 + }
124 +
125 + return $data['choices'][0]['message']['content']; // Update this line to get the assistant's message
126 + }
127 + } catch (Exception $error) {
128 + return 'Error: ' . $error->getMessage();
145 129 }
130 + }
146 131
147 - $httpCode = (int) wp_remote_retrieve_response_code( $response );
148 - $body = wp_remote_retrieve_body( $response );
149 132
150 - if ( 200 === $httpCode ) {
151 - $api_response[ 'valid' ] = true;
152 - $api_response[ 'message' ] = 'Valid API Key';
153 - } else {
154 - $responseData = json_decode( $body, true );
155 - $messageData = ! empty( $responseData[ 'error' ] ) ? $responseData[ 'error' ] : array();
156 - $api_response[ 'valid' ] = false;
157 - $api_response[ 'message' ] = ! empty( $messageData[ 'message' ] ) ? $messageData[ 'message' ] : 'Invalid API Key';
133 +
134 + public function generate_openai_content_callback()
135 + {
136 + // Verify the nonce
137 + if (!isset($_POST['ai_nonce']) || !wp_verify_nonce($_POST['ai_nonce'], 'generate_openai_content_nonce')) {
138 + wp_send_json_error('Invalid nonce');
139 + wp_die();
158 140 }
159 141
160 - // print_r($response);
142 + $prompt = sanitize_text_field($_POST['prompt']);
143 + // $num_of_sections = sanitize_text_field($_POST['numOfSections']);
144 + // $num_of_paragraphs = sanitize_text_field($_POST['numOfParagraphs']);
145 + $keywords = sanitize_text_field($_POST['keywords']);
161 146
162 - return $api_response;
147 + $ai_instance = new WriteWithAI($this->settings);
148 +
149 + $generated_content = $ai_instance->generate_openai_response($prompt, $keywords);
150 +
151 + // Send the generated content as the AJAX response
152 + wp_send_json_success($generated_content);
153 + wp_die();
163 154 }
164 155
165 - public function get_api_key() {
166 - $api_key = $this->settings->get( 'ai_autowrite_api_key', '' );
167 - return $api_key;
168 - }
156 + public function ai_autowrite_button()
157 + {
169 158
170 - /**
171 - * The built-in "Default" instruction body.
172 - *
173 - * Single source of truth shared by {@see Settings::get_default()} (which seeds
174 - * the editable "Default" instruction set) and {@see self::get_system_prompt()}
175 - * (the fallback when no saved Default content exists).
176 - *
177 - * @return string
178 - */
179 - public static function default_instruction_content() {
180 - return <<<'PROMPT'
181 -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.
159 + ?>
160 + <script>
161 + var docsTitle;
182 162
183 -## Output format
163 + function responseMessage(message) {
164 + return `<div class="warning-message">
165 + <span class="dashicons dashicons-warning"></span>
166 + <div class="footer-message">
167 + ${message}
168 + </div>
169 + </div>`;
170 + }
184 171
185 -Return only HTML body content. Never wrap output in `<!doctype>`, `<html>`, `<head>`, `<body>`, or markdown code fences (no ```html ... ```).
172 + function generateArticle(e) {
186 173
187 -Use semantic, Gutenberg-friendly tags only:
188 -- Headings: `<h2>`, `<h3>`, `<h4>` (do not emit `<h1>` — it is reserved for the document title)
189 -- Paragraphs: `<p>` for prose
190 -- Lists: `<ul>`/`<ol>` with `<li>` for any list of items, steps, or bullet points — never fake a list with paragraphs or `<br>`
191 -- Links: `<a href="https://...">link text</a>` with absolute URLs
192 -- Inline emphasis: `<strong>`, `<em>`, `<code>`
193 -- Code blocks: `<pre><code>...</code></pre>` for multi-line code or commands
194 -- Quotes: `<blockquote>`
195 -- Tables: `<table>` with `<thead>`, `<tbody>`, `<tr>`, `<th>`, `<td>`
196 -- Images: `<img src="..." alt="...">`
197 174
198 -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.
175 + const title = document.getElementById('betterdocs-ai-title').value;
176 + const keywords = document.getElementById('betterdocs-ai-keyword').value;
177 + // const sectionOptions = document.getElementById('bd-autowrtite-content-section');
178 + // const numOfSections = sectionOptions.options[sectionOptions.selectedIndex].value;
199 179
200 -Do not emit `<style>`, `<script>`, `<iframe>`, `<form>`, or inline `style=""` / `class=""` attributes (other than the `highlight` class above).
180 + // const paragraphOptions = document.getElementById('bd-autowrtite-content-paragraph');
181 + // const numOfParagraphs = paragraphOptions.options[paragraphOptions.selectedIndex].value;
201 182
202 -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.
203 -PROMPT;
204 - }
183 + const contentTextArea = document.getElementById('betterdocs-ai-content');
184 + const docGenerateBtnTxt = document.querySelector('.generate-btn span');
205 185
206 - /**
207 - * The saved instruction sets, normalized to a list of { id, title, content }.
208 - *
209 - * Stored in the `write_with_ai_instructions` setting. Always returns at least
210 - * the built-in "Default" set so callers never have to special-case an empty
211 - * option (e.g. a site whose stored value predates this feature).
212 - *
213 - * @return array<int,array{id:string,title:string,content:string}>
214 - */
215 - public function get_instructions() {
216 - $stored = $this->settings->get( 'write_with_ai_instructions', array() );
186 + // Add nonce value to the data being sent
187 + const nonce = document.querySelector('input[name="ai_nonce"]').value;
188 + const isOverwrite = document.getElementById('betterdocs-ai-checkbox').checked;
189 + const generateDocLabel = "<?php echo esc_html__('Generate Doc', 'betterdocs'); ?>";
190 + const reGenerateDocLabel = "<?php echo esc_html__('Regenerate Doc', 'betterdocs'); ?>";
217 191
218 - $instructions = array();
219 - if ( is_array( $stored ) ) {
220 - foreach ( $stored as $item ) {
221 - if ( ! is_array( $item ) || empty( $item['id'] ) ) {
222 - continue;
192 +
193 + // 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.`;
194 +
195 +
196 + // Check if the title is not empty
197 + if (title.trim() !== '' && keywords.trim() !== '') {
198 + e.preventDefault();
199 +
200 + if (!jQuery('#betterdocs-ai-error-message').parent().hasClass('hidden')) {
201 + jQuery('#betterdocs-ai-error-message').parent().addClass('hidden');
202 + }
203 +
204 + docGenerateBtnTxt.innerHTML = "<?php echo esc_html__('Generating...', 'betterdocs'); ?>";
205 + jQuery('.generate-btn').prop('disabled', true);
206 +
207 + jQuery.post({
208 + url: ajaxurl, // Make sure ajaxurl is defined in your script
209 + type: 'POST',
210 + data: {
211 + action: 'generate_openai_content',
212 + prompt: document.querySelector('#betterdocs-ai-content').value,
213 + // numOfSections: numOfSections,
214 + // numOfParagraphs: numOfParagraphs,
215 + keywords: keywords,
216 + ai_nonce: nonce, // Pass the nonce value
217 + },
218 + success: function(response) {
219 + // Update the content in the textarea
220 +
221 + if (response.success && (typeof response.data === 'string')) {
222 + docGenerateBtnTxt.innerHTML = "<?php echo esc_html__('Generated', 'betterdocs'); ?>";
223 + setTimeout(() => {
224 + insertContentToEditor(title, response.data, isOverwrite, keywords);
225 + docGenerateBtnTxt.innerHTML = reGenerateDocLabel;
226 + jQuery('.generate-btn').removeAttr('disabled');
227 +
228 + // console.log(jQuery('.betterdocs-ai-autowrite-form-container').data('new-doc-page'));
229 +
230 + // if(jQuery('.betterdocs-ai-autowrite-form-container')?.data('new-doc-page')) {
231 + // docGenerateBtnTxt.innerHTML = reGenerateDocLabel;
232 + // }
233 + }, 2000);
234 + } else {
235 + // alert(response.data.message);
236 + jQuery('#betterdocs-ai-error-message').parent().removeClass('hidden');
237 + jQuery('#betterdocs-ai-error-message').html(responseMessage(response.data.message));
238 + docGenerateBtnTxt.innerHTML = generateDocLabel;
239 + jQuery('.generate-btn').removeAttr('disabled');
240 +
241 + }
242 + },
243 + error: function(error) {
244 + console.error(error);
245 + },
246 + });
247 +
223 248 }
224 - $instructions[] = array(
225 - 'id' => sanitize_key( (string) $item['id'] ),
226 - 'title' => isset( $item['title'] ) ? (string) $item['title'] : '',
227 - 'content' => isset( $item['content'] ) ? (string) $item['content'] : '',
228 - );
229 249 }
230 - }
231 250
232 - // Guarantee a Default set is always present.
233 - $has_default = false;
234 - foreach ( $instructions as $item ) {
235 - if ( 'default' === $item['id'] ) {
236 - $has_default = true;
237 - break;
251 + // Function to update the textarea
252 + function updateTextarea() {
253 + const title = document.getElementById('betterdocs-ai-title').value;
254 + const keywords = document.getElementById('betterdocs-ai-keyword').value;
255 + const sectionOptions = document.getElementById('bd-autowrtite-content-section');
256 + let language = 'English';
257 +
258 + if (language === '') {
259 + language = 'English';
260 + }
261 +
262 + let keywordsText = keywords !== '' ? ` and incorporate the keywords: ${keywords}` : '';
263 +
264 + const contentTextArea = document.getElementById('betterdocs-ai-content');
265 +
266 + if (!jQuery('#betterdocs-ai-error-message').parent().hasClass('hidden')) {
267 + jQuery('#betterdocs-ai-error-message').parent().addClass('hidden');
268 + }
269 +
270 + 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.`;
238 271 }
239 - }
240 - if ( ! $has_default ) {
241 - array_unshift(
242 - $instructions,
243 - array(
244 - 'id' => 'default',
245 - 'title' => __( 'Default/Core', 'betterdocs' ),
246 - 'content' => self::default_instruction_content(),
247 - )
248 - );
249 - }
250 272
251 - return $instructions;
252 - }
273 + function convertMarkdownToHTML(markdownContent) {
274 + // Simple Markdown to HTML conversion (you may need to enhance this based on your needs)
275 + const htmlContent = markdownContent
276 + .replace(/^# (.+)$/gm, '<h1>$1</h1>')
277 + .replace(/^## (.+)$/gm, '<h2>$1</h2>')
278 + .replace(/^### (.+)$/gm, '<h3>$1</h3>')
279 + .replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
280 + .replace(/\*(.+?)\*/g, '<em>$1</em>');
253 281
254 - /**
255 - * The selectable (non-default) instruction sets exposed to the editor UI.
256 - *
257 - * Only id + title are sent to the browser; the prompt body stays server-side
258 - * and is resolved at request time by {@see self::get_instruction_messages()}.
259 - *
260 - * @return array<int,array{id:string,title:string}>
261 - */
262 - public function get_instruction_choices() {
263 - $choices = array();
264 - foreach ( $this->get_instructions() as $item ) {
265 - if ( 'default' === $item['id'] ) {
266 - continue;
282 + return htmlContent;
267 283 }
268 - if ( '' === trim( $item['content'] ) ) {
269 - continue; // skip empty sets — they'd inject nothing.
284 +
285 + function replaceHeadingTitle(content, title, overviewTitle) {
286 + let regex = new RegExp(title, 'g');
287 + let updateContent = content.replace(regex, overviewTitle);
288 + return updateContent;
270 289 }
271 - $choices[] = array(
272 - 'id' => $item['id'],
273 - 'title' => '' !== trim( $item['title'] ) ? $item['title'] : $item['id'],
274 - );
275 - }
276 - return $choices;
277 - }
278 290
279 - /**
280 - * Resolve selected instruction ids into extra `system` messages.
281 - *
282 - * The "Default" set is intentionally excluded — it is always sent as the base
283 - * system prompt by {@see self::get_system_prompt()}. Unknown or empty ids are
284 - * silently ignored. Output order follows the requested $ids order.
285 - *
286 - * @param array $ids
287 - * @return array<int,array{role:string,content:string}>
288 - */
289 - public function get_instruction_messages( $ids ) {
290 - if ( empty( $ids ) || ! is_array( $ids ) ) {
291 - return array();
292 - }
291 + function removeFirstHeading(htmlString) {
292 + var newDiv = document.createElement('div');
293 + newDiv.innerHTML = htmlString;
294 + var firstHeading = newDiv.querySelector('h1');
295 + if (firstHeading) {
296 + firstHeading.parentNode.removeChild(firstHeading);
297 + }
298 + return newDiv.innerHTML;
299 + }
293 300
294 - $by_id = array();
295 - foreach ( $this->get_instructions() as $item ) {
296 - $by_id[ $item['id'] ] = $item;
297 - }
298 301
299 - $messages = array();
300 - $seen = array();
301 - foreach ( $ids as $id ) {
302 - $id = sanitize_key( (string) $id );
303 - if ( 'default' === $id || isset( $seen[ $id ] ) || ! isset( $by_id[ $id ] ) ) {
304 - continue;
302 + function wrapwithHeighlight(inputString, keywords) {
303 + let modifiedString = inputString.replace(/<(h\d)(.*?)>(.*?)<\/\1>/g, '<$1$2><span class="highlight">$3</span></$1>');
304 +
305 + const keywordArray = keywords.split(',').map(keyword => keyword.trim());
306 +
307 + keywordArray.forEach(keyword => {
308 + modifiedString = modifiedString.replace(new RegExp(`\\b(${keyword})\\b`, 'gi'), '<span class="highlight">$1</span>');
309 + });
310 +
311 + return modifiedString;
305 312 }
306 - $content = trim( $by_id[ $id ]['content'] );
307 - if ( '' === $content ) {
308 - continue;
313 +
314 + function getBodyContent(htmlString) {
315 + var match = htmlString.match(/<body[^>]*>([\s\S]*?)<\/body>/i);
316 +
317 + console.log(match[1].replace(/<p>\s+/g, '<p>'));
318 +
319 + return match ? match[1].replace(/<p>\s+/g, '<p>') : '';
309 320 }
310 - $seen[ $id ] = true;
311 - $messages[] = array(
312 - 'role' => 'system',
313 - 'content' => $content,
314 - );
315 - }
316 321
317 - return $messages;
318 - }
319 322
320 - /**
321 - * Coerce a list of extra messages into well-formed `system` chat messages.
322 - *
323 - * Accepts either pre-built [ 'role'=>'system', 'content'=>… ] entries (as
324 - * returned by {@see self::get_instruction_messages()}) or bare strings, and
325 - * drops anything empty. Keeps the OpenAI payload builders tolerant of either
326 - * shape so callers can pass instruction ids or ready-made messages.
327 - *
328 - * @param array $extra_system
329 - * @return array<int,array{role:string,content:string}>
330 - */
331 - protected function normalize_extra_system( $extra_system ) {
332 - if ( empty( $extra_system ) || ! is_array( $extra_system ) ) {
333 - return array();
334 - }
323 + function insertContentToEditor(title, htmlContent, isOverwrite, keywords) {
335 324
336 - $messages = array();
337 - foreach ( $extra_system as $entry ) {
338 - if ( is_string( $entry ) ) {
339 - $content = trim( $entry );
340 - $role = 'system';
341 - } elseif ( is_array( $entry ) && isset( $entry['content'] ) ) {
342 - $content = trim( (string) $entry['content'] );
343 - $role = isset( $entry['role'] ) ? (string) $entry['role'] : 'system';
344 - } else {
345 - continue;
325 + const {
326 + dispatch,
327 + select
328 + } = wp.data;
329 +
330 + htmlContent = getBodyContent(htmlContent);
331 + htmlContent = removeFirstHeading(htmlContent);
332 + htmlContent = wrapwithHeighlight(htmlContent, keywords);
333 + const isPostContent = `<?php echo isset($_GET['post']) ? get_the_content($_GET['post']) : ''; ?>`;
334 +
335 + const blocks = wp.blocks.rawHandler({
336 + HTML: htmlContent
337 + });
338 +
339 + dispatch('core/editor').editPost({
340 + title: title,
341 + });
342 +
343 + const selectedBlockClientId = select('core/block-editor').getSelectedBlockClientId();
344 +
345 + const allBlocks = select('core/block-editor').getBlocks();
346 +
347 + if (isOverwrite || isPostContent === '') {
348 +
349 + allBlocks.forEach((block) => {
350 + dispatch('core/block-editor').removeBlock(block.clientId);
351 + });
352 + dispatch('core/block-editor').insertBlocks(blocks);
353 +
354 + } else if (selectedBlockClientId) {
355 + const selectedIndex = select('core/block-editor').getBlockIndex(selectedBlockClientId);
356 + dispatch('core/block-editor').insertBlocks(blocks, selectedIndex + 1);
357 + } else {
358 + dispatch('core/block-editor').insertBlocks(blocks);
359 + }
360 +
361 + closeWriteWithAIForm('afterInsertContent');
346 362 }
347 363
348 - if ( '' === $content ) {
349 - continue;
364 + function closeWriteWithAIForm(after) {
365 + jQuery('.betterdocs-ai-autowrite-form-container').addClass('hidden');
366 + jQuery('#betterdocs-ai-autowrite-form-container-overlay').addClass('hidden');
367 + jQuery('.betterdocs-ai-button').addClass('regenerate-btn');
350 368 }
351 369
352 - $messages[] = array(
353 - 'role' => $role,
354 - 'content' => $content,
355 - );
356 - }
357 370
358 - return $messages;
359 - }
371 + document.addEventListener('DOMContentLoaded', function() {
360 372
361 - public function get_system_prompt() {
362 - // Prefer the saved (editable) "Default" instruction set; fall back to the
363 - // built-in body when it's missing or has been cleared.
364 - $prompt = self::default_instruction_content();
365 - foreach ( $this->get_instructions() as $item ) {
366 - if ( 'default' === $item['id'] && '' !== trim( $item['content'] ) ) {
367 - $prompt = $item['content'];
368 - break;
373 + // Subscribe to changes in the editor state
374 + let previousDocsTitle = '';
375 +
376 + wp.data.subscribe(() => {
377 + // Get the updated post title
378 + // const docsTitle = wp.data.select('core/editor').getEditedPostAttribute('title');
379 +
380 + const docsTitle = wp.data.select('core/editor').getEditedPostAttribute('title');
381 +
382 + // Check if the title has changed
383 + if (docsTitle !== previousDocsTitle) {
384 + let currentKeywords = jQuery('#betterdocs-ai-keyword').val();
385 + if (!currentKeywords) {
386 + currentKeywords = '{Documentation Keywords}';
387 + }
388 + 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.`;
389 + jQuery('#betterdocs-ai-content').val(currentPrompt);
390 +
391 + jQuery('#betterdocs-ai-title').val(docsTitle);
392 +
393 + previousDocsTitle = docsTitle;
394 + }
395 +
396 +
397 + });
398 + });
399 +
400 +
401 + // This example assumes that this code is executed when your script is loaded.
402 + // You may want to place it within the appropriate context in your application.
403 +
404 +
405 + function writeWithAIForm() {
406 +
407 + let title = `<?php echo isset($_GET['post']) ? get_the_title($_GET['post']) : ''; ?>`;
408 + if (docsTitle) {
409 + title = docsTitle;
410 + }
411 +
412 + const promtTitle = `<?php echo isset($_GET['post']) ? get_the_title($_GET['post']) : '{Documentation Title}'; ?>`;
413 + const titlePlaceholder = "<?php echo esc_attr__('Enter a descriptive title for your documentation.', 'betterdocs'); ?>";
414 + const keywords = "<?php echo esc_attr('{Documentation Keywords}'); ?>";
415 + const keywordsPlaceholder = "<?php echo esc_attr__('Add keywords to generate precise & relevant documentation (comma-separated).', 'betterdocs'); ?>";
416 + const language = "<?php echo esc_attr('English'); ?>";
417 + const titleLabel = "<?php echo esc_html__('Documentation Title:', 'betterdocs'); ?>";
418 + const keywordLabel = "<?php echo esc_html__('Keywords:', 'betterdocs'); ?>";
419 + const secLabel = "<?php echo esc_html__('# of Sections:', 'betterdocs'); ?>";
420 + const paraLabel = "<?php echo esc_html__('# of Paragraph Per Section: ', 'betterdocs'); ?>";
421 + const langLabel = "<?php echo esc_html__('Language:', 'betterdocs'); ?>";
422 + const promtLabel = "<?php echo esc_html__('Prompt:', 'betterdocs'); ?>";
423 + const promtBtnLabel = "<?php echo esc_html__('Generate Prompt', 'betterdocs'); ?>";
424 + let promtArticleLabel = "<?php echo esc_html__('Generate Doc', 'betterdocs'); ?>";
425 + const checkboxLabel = "<?php echo esc_html__('Overwrite your existing Doc:', 'betterdocs'); ?>";
426 + const nonce = "<?php echo esc_attr(wp_create_nonce('generate_openai_content_nonce')); ?>";
427 +
428 + <?php $is_valid = $this->get_api_key();
429 + $disable_field = 'disabled-input-field';
430 + if ($this->get_api_key()) {
431 + $disable_field = '';
432 + }
433 + ?>
434 +
435 + let hiddenClass = 'hidden';
436 + let newDocPageAtt = 'data-new-doc-page="true"';
437 + const isPostContent = `<?php echo isset($_GET['post']) ? get_the_content($_GET['post']) : ''; ?>`;
438 +
439 + if (isPostContent !== '') {
440 + hiddenClass = '';
441 + newDocPageAtt = '';
442 + }
443 +
444 +
445 + // 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.`;
446 +
447 + // 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.`;
448 +
449 + 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.`;
450 +
451 + const aiIcon = `<svg xmlns="http://www.w3.org/2000/svg" width="21" height="20" viewBox="0 0 21 20" fill="none">
452 + <g clip-path="url(#clip0_2460_1729)">
453 + <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"/>
454 + </g>
455 + <defs>
456 + <clipPath id="clip0_2460_1729">
457 + <rect width="20" height="20" fill="white" transform="translate(0.5)"/>
458 + </clipPath>
459 + </defs>
460 + </svg>`;
461 +
462 +
463 +
464 + return `
465 + <div class="betterdocs-ai-autowrite-form-container hidden" ${newDocPageAtt}>
466 + <div class="betterdocs-ai-autowrite-form-content">
467 + <div class="betterdocs-ai-autowrite-top-part">
468 + <div class="autowrite-heading">
469 + <span class="autowrite-icon">
470 + <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" fill="none">
471 + <g clip-path="url(#clip0_2453_20882)">
472 + <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"/>
473 + </g>
474 + <defs>
475 + <clipPath id="clip0_2453_20882">
476 + <rect width="32" height="32" fill="white"/>
477 + </clipPath>
478 + </defs>
479 + </svg>
480 + </span>
481 + <h1><?php echo esc_html__('Write Documentation with BetterDocs AI', 'betterdocs'); ?></h1>
482 +
483 + </div>
484 + <div class="autowrite-subheadding">
485 + <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>
486 + </div>
487 +
488 + <?php if (empty($this->get_api_key())) : ?>
489 + <div id="betterdocs-ai-message">
490 + <div class="warning-message">
491 + <span class="dashicons dashicons-warning"></span>
492 + <div>
493 + <?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'); ?>
494 + </div>
495 + </div>
496 +
497 + </div>
498 + <?php endif; ?>
499 +
500 + <div class="form-group hidden">
501 + <div id="betterdocs-ai-error-message"></div>
502 + </div>
503 +
504 + </div>
505 + <form id="betterdocs-ai-form" class="<?php echo esc_attr($disable_field); ?>">
506 + <div class="form-inner-content">
507 + <div class="form-group">
508 + <label for="betterdocs-ai-title">${titleLabel}</label>
509 + <input type="text" placeholder="${titlePlaceholder}" id="betterdocs-ai-title" name="betterdocs-ai-title" required value="${title}">
510 + </div>
511 +
512 + <div class="form-group">
513 + <label for="betterdocs-ai-keyword">${keywordLabel}</label>
514 + <input type="text" placeholder="${keywordsPlaceholder}" id="betterdocs-ai-keyword" name="betterdocs-ai-keyword" required>
515 + </div>
516 +
517 + <div class="form-group prompt-field">
518 + <label for="betterdocs-ai-content">${promtLabel}</label>
519 + <textarea id="betterdocs-ai-content" name="betterdocs-ai-content" rows="6" required>${prompt}</textarea>
520 + <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>
521 + </div>
522 + <div class="betterdocs-ai-checkbox-field ${hiddenClass}">
523 + <div class="form-group">
524 + <div class="checkbox-field-label">
525 + <label for="betterdocs-ai-checkbox">${checkboxLabel}</label>
526 + <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>
527 + </div>
528 +
529 + <div class="checkbox-input-field">
530 + <input type="checkbox" id="betterdocs-ai-checkbox" name="betterdocs-ai-checkbox" data-overwrite="false">
531 + <label for="betterdocs-ai-checkbox" class="toggle-label"></label>
532 + </div>
533 + </div>
534 + </div>
535 +
536 + <input type="hidden" name="ai_nonce" value="${nonce}" />
537 + </div>
538 + <div class="generate-button-container">
539 + <button type="submit" class="generate-btn" onclick="generateArticle(event)">
540 + ${aiIcon} <span>${promtArticleLabel}</span>
541 + </button>
542 + </div>
543 + </form>
544 + </div>
545 +
546 + <div class="bd-close-button" onclick="closeWriteWithAIForm('afterClosedClicked')">✕</div>
547 + </div>
548 +
549 + <div id="betterdocs-ai-autowrite-form-container-overlay" class="hidden"></div>
550 + `;
369 551 }
370 - }
371 552
372 - return apply_filters( 'betterdocs_write_with_ai_system_prompt', $prompt );
373 - }
553 + jQuery(document).on('click', '.regenerate-btn', function() {
554 + jQuery('.betterdocs-ai-autowrite-form-container').removeClass('hidden');
555 + jQuery('#betterdocs-ai-autowrite-form-container-overlay').removeClass('hidden');
556 + });
374 557
375 - public function get_outline_system_prompt() {
376 - $prompt = <<<'PROMPT'
377 -You are a Senior Technical Writer. Produce a documentation OUTLINE only — not the full article.
558 + jQuery(document).ready(function($) {
559 + // Check if we are in the Edit Post screen
560 + if ($('.block-editor-page').length > 0) {
561 + const bdAIButton = $(`<div class="betterdocs-ai-button-container">
562 + <button class="betterdocs-ai-button">
563 + <img src="<?php echo esc_url(BETTERDOCS_ABSURL . 'assets/admin/images/betterdocs-icon-white.png'); ?>" alt="<?php echo esc_attr__('betterdocs icon', 'embedpress'); ?>" />
564 + <span><?php echo esc_html__('Write with AI', 'embedpress'); ?></span>
565 + </button>
566 + </div>`);
567 + const closeBtn = $('bd-close-button');
378 568
379 -Return ONLY a JSON array (no prose, no markdown, no code fences). Each element is an object:
380 - { "level": "h2" | "h3", "text": "Section heading" }
569 + const formHtml = writeWithAIForm();
570 + $(formHtml).addClass('hidden');
381 571
382 -Rules:
383 -- 5 to 9 top-level "h2" sections that comprehensively cover the topic.
384 -- Add "h3" sub-sections only where they genuinely help; keep each one directly after its parent h2.
385 -- Headings are concise, descriptive, and free of numbering.
386 -- Do not include an h1/title and do not include any text outside the JSON array.
387 -PROMPT;
572 + $(document).on('click', '.betterdocs-ai-button', function() {
573 + $('.betterdocs-ai-autowrite-form-container').removeClass('hidden');
574 + $('#betterdocs-ai-autowrite-form-container-overlay').removeClass('hidden');
575 + });
388 576
389 - return apply_filters( 'betterdocs_write_with_ai_outline_system_prompt', $prompt );
390 - }
577 + // $(document).on('click', '.betterdocs-ai-button', function() {
578 + if ($('.betterdocs-ai-autowrite-form-container').length < 1) {
579 + $('body').append(formHtml);
391 580
392 - /**
393 - * Generate a documentation outline as a structured array of sections.
394 - *
395 - * @param string $user_prompt The composed prompt (title/keywords/instructions).
396 - * @return array { success:bool, outline?:array<int,array{level:string,text:string}>, error?:string }
397 - */
398 - public function generate_outline_response( $user_prompt, $extra_system = array() ) {
399 - $result = $this->generate_text( $user_prompt, $this->get_outline_system_prompt(), $extra_system );
581 + // Add event listeners to input elements
582 + document.getElementById('betterdocs-ai-title').addEventListener('input', updateTextarea);
583 + document.getElementById('betterdocs-ai-keyword').addEventListener('input', updateTextarea);
584 + // document.getElementById('bd-autowrtite-content-section').addEventListener('change', updateTextarea);
585 + // document.getElementById('bd-autowrtite-content-paragraph').addEventListener('change', updateTextarea);
586 + // document.getElementById('betterdocs-ai-language').addEventListener('input', updateTextarea);
587 + }
588 + // });
589 + const btn = document.createElement('button');
590 + wp.data.subscribe(function() {
591 + setTimeout(() => {
592 + if ($('.edit-post-header__settings').length > 0) {
593 + $('.edit-post-header__settings').prepend(bdAIButton);
594 + }
595 + }, 1);
596 + });
597 + }
400 598
401 - if ( empty( $result['success'] ) ) {
402 - return array(
403 - 'success' => false,
404 - 'error' => isset( $result['error'] ) ? $result['error'] : 'OpenAI error',
405 - );
406 - }
599 + $(document).on('click', '#betterdocs-ai-autowrite-form-container-overlay', function() {
600 + closeWriteWithAIForm();
601 + });
602 + });
603 + </script>
407 604
408 - $outline = $this->parse_outline( (string) $result['content'] );
605 + <style>
606 + .hidden {
607 + display: none !important;
608 + }
409 609
410 - if ( empty( $outline ) ) {
411 - return array(
412 - 'success' => false,
413 - 'error' => 'The AI returned an outline we could not read. Please try again.',
414 - );
415 - }
610 + .disabled-input-field input,
611 + .disabled-input-field textarea,
612 + .disabled-input-field button,
613 + .disabled-input-field label {
614 + pointer-events: none;
615 + opacity: 0.6;
616 + }
416 617
417 - return array(
418 - 'success' => true,
419 - 'outline' => $outline,
420 - );
421 - }
618 + .disabled-input-field label {
619 + opacity: 1;
620 + }
422 621
423 - /**
424 - * Parse a model outline response (ideally a JSON array) into a clean list of
425 - * { level, text } sections. Tolerates code fences and stray prose.
426 - *
427 - * @param string $content
428 - * @return array<int,array{level:string,text:string}>
429 - */
430 - protected function parse_outline( $content ) {
431 - $content = trim( $content );
432 - $content = preg_replace( '/^```(?:json)?\s*/i', '', $content );
433 - $content = preg_replace( '/```\s*$/', '', $content );
622 + .betterdocs-ai-button-container {
623 + margin-left: 10px;
624 + }
434 625
435 - // Grab the first JSON array if the model wrapped it in prose.
436 - if ( preg_match( '/\[[\s\S]*\]/', $content, $m ) ) {
437 - $content = $m[0];
438 - }
626 + .autowrite-icon {
627 + display: flex;
628 + align-items: center;
629 + width: 55px;
630 + height: 55px;
631 + background: #FFE4E8;
632 + justify-content: center;
633 + border-radius: 50px;
634 + }
439 635
440 - $decoded = json_decode( $content, true );
441 - if ( ! is_array( $decoded ) ) {
442 - return array();
443 - }
636 + .autowrite-icon svg {
637 + width: 28px;
638 + height: 28px;
639 + }
444 640
445 - $outline = array();
446 - foreach ( $decoded as $item ) {
447 - if ( ! is_array( $item ) || empty( $item['text'] ) ) {
448 - continue;
641 + .autowrite-heading {
642 + display: flex;
643 + gap: 10px;
644 + align-items: center;
449 645 }
450 - $level = isset( $item['level'] ) && 'h3' === strtolower( (string) $item['level'] ) ? 'h3' : 'h2';
451 - $text = sanitize_text_field( (string) $item['text'] );
452 - if ( '' === $text ) {
453 - continue;
646 +
647 + .autowrite-heading h1 {
648 + color: #1D2939;
649 + font-family: 'IBM Plex Sans', sans-serif;
650 + font-size: 24px;
651 + font-style: normal;
652 + font-weight: 500;
653 + line-height: normal;
654 + margin: 0;
454 655 }
455 - $outline[] = array( 'level' => $level, 'text' => $text );
456 - }
457 656
458 - return $outline;
459 - }
657 + .autowrite-heading p,
658 + form#betterdocs-ai-form p {
659 + margin: 0;
660 + margin-bottom: 24px;
661 + }
460 662
461 - public function generate_openai_response( $prompt, $keywords, $max_tokens = null, $extra_system = array() ) {
462 - try {
463 - $api_key = $this->settings->get( 'ai_autowrite_api_key', '' );
464 - // Caller may raise the cap (e.g. a "large" doc) above the saved default.
465 - $max_tokens = null !== $max_tokens ? (int) $max_tokens : $this->settings->get( 'ai_autowrite_max_token', 2500 );
466 - $model = $this->settings->get( 'write_with_ai_model', 'gpt-4o-mini' );
663 + .autowrite-subheadding p {
664 + font-family: 'IBM Plex Sans', sans-serif;
665 + font-size: 14px;
666 + }
467 667
468 - $api_endpoint = 'https://api.openai.com/v1/chat/completions'; // Update the endpoint based on OpenAI API version
668 + .prompt-field .input-description {
669 + margin: 0 !important;
670 + }
469 671
470 - $messages = array_merge(
471 - array(
472 - array(
473 - 'role' => 'system',
474 - 'content' => $this->get_system_prompt()
475 - )
476 - ),
477 - $this->normalize_extra_system( $extra_system ),
478 - array(
479 - array(
480 - 'role' => 'user',
481 - 'content' => $prompt
482 - )
483 - )
484 - );
672 + .autowrite-heading p {
673 + color: #475467;
674 + font-family: 'IBM Plex Sans', sans-serif;
675 + font-size: 16px;
676 + font-style: normal;
677 + font-weight: 400;
678 + }
485 679
486 - $request_body = AIHelper::build_openai_payload(
487 - $model,
488 - $messages,
489 - $max_tokens,
490 - null,
491 - 'write_with_ai'
492 - );
680 + .betterdocs-ai-button {
681 + background: #00B884;
682 + border: 1px solid transparent;
683 + color: #fff;
684 + box-shadow: none;
685 + font-size: 12px;
686 + margin-right: 8px;
687 + padding: 0px 15px;
688 + cursor: pointer;
689 + border-radius: 3px;
690 + height: 38px;
691 + display: flex;
692 + align-items: center;
693 + justify-content: space-between;
694 + gap: 10px;
695 + }
493 696
494 - $request_options = array(
495 - 'headers' => array(
496 - 'Content-Type' => 'application/json',
497 - 'Authorization' => 'Bearer ' . $api_key
498 - ),
499 - 'body' => json_encode( $request_body ),
500 - 'timeout' => 300
501 - );
697 + .betterdocs-ai-button img {
698 + width: 20px;
699 + height: 20px;
700 + }
502 701
503 - // GPT-5.5 reasoning can run well past the default limits; give PHP and
504 - // the HTTP call room to finish (still subject to server php-fpm/nginx limits).
505 - if ( function_exists( 'set_time_limit' ) ) {
506 - set_time_limit( 300 ); // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Discouraged -- long-running AI generation needs an extended limit; still bounded by server fpm/nginx timeouts.
702 + .betterdocs-ai-autowrite-form-container {
703 + position: fixed;
704 + top: 50%;
705 + left: 50%;
706 + transform: translate(-50%, -50%);
707 + z-index: 100001;
708 + width: 650px;
709 + margin: 0 auto;
710 + background-color: #fff;
711 + border-radius: 5px;
712 + font-family: 'IBM Plex Sans', sans-serif;
713 + /* max-height: 600px; */
714 + max-height: 750px;
715 + max-width: 100%;
507 716 }
508 717
509 - $response = wp_remote_post( $api_endpoint, $request_options );
718 + .betterdocs-ai-autowrite-form-content {
719 + background-color: #fff;
720 + padding: 20px 0px;
721 + border-radius: 5px;
722 + padding-bottom: 0;
723 + overflow: auto;
724 + /* max-height: 700px; */
725 + height: 100%;
510 726
511 - if ( is_wp_error( $response ) ) {
512 - return 'Error: ' . $response->get_error_message();
513 - } else {
514 - $body = wp_remote_retrieve_body( $response );
727 + }
515 728
516 - $data = json_decode( $body, true );
729 + .betterdocs-ai-autowrite-top-part {
730 + /* padding-bottom: 20px; */
731 + border-bottom: 1px solid #ddd;
732 + /* margin-bottom: 20px; */
733 + padding: 0 40px;
734 + }
517 735
518 - if ( ! empty( $data[ 'error' ] ) ) {
519 - return $data[ 'error' ][ 'message' ];
520 - }
736 + #betterdocs-ai-form {
737 + display: flex;
738 + flex-direction: column;
739 + /* height: 100%; */
740 + overflow: hidden;
741 + }
521 742
522 - return $data[ 'choices' ][ 0 ][ 'message' ][ 'content' ]; // Update this line to get the assistant's message
743 + .form-inner-content {
744 + overflow: auto;
745 + height: 100%;
746 + max-height: 435px;
747 + /* max-height: 330px; */
748 + padding: 0 40px;
523 749 }
524 - } catch ( Exception $error ) {
525 - return 'Error: ' . $error->getMessage();
526 - }
527 - }
528 750
529 - public function generate_openai_response_ai_edit( $prompt, $extra_system = array() ) {
530 - $api_key = $this->settings->get( 'ai_autowrite_api_key', '' );
531 - $max_tokens = $this->settings->get( 'ai_autowrite_max_token', 2500 );
532 - $model = $this->settings->get( 'write_with_ai_model', 'gpt-4o-mini' );
751 + .form-inner-content>.form-group {
752 + margin-top: 20px;
753 + }
533 754
534 - $api_endpoint = 'https://api.openai.com/v1/chat/completions';
535 755
536 - $messages = array_merge(
537 - array(
538 - array(
539 - 'role' => 'system',
540 - 'content' => $this->get_system_prompt()
541 - )
542 - ),
543 - $this->normalize_extra_system( $extra_system ),
544 - array(
545 - array(
546 - 'role' => 'user',
547 - 'content' => $prompt
548 - )
549 - )
550 - );
756 + #betterdocs-ai-autowrite-form-container-overlay {
757 + position: fixed;
758 + top: 0;
759 + right: 0;
760 + bottom: 0;
761 + left: 0;
762 + background-color: rgba(0, 0, 0, .7);
763 + z-index: 100000;
764 + }
551 765
552 - $payload = AIHelper::build_openai_payload(
553 - $model,
554 - $messages,
555 - $max_tokens,
556 - null,
557 - 'write_with_ai'
558 - );
559 766
560 - $request_options = array(
561 - 'headers' => array(
562 - 'Content-Type' => 'application/json',
563 - 'Authorization' => 'Bearer ' . $api_key
564 - ),
565 - 'body' => wp_json_encode( $payload ),
566 - 'timeout' => 300
567 - );
568 767
569 - // GPT-5.5 reasoning can run well past the default limits; give PHP and
570 - // the HTTP call room to finish (still subject to server php-fpm/nginx limits).
571 - if ( function_exists( 'set_time_limit' ) ) {
572 - set_time_limit( 300 ); // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Discouraged -- long-running AI generation needs an extended limit; still bounded by server fpm/nginx timeouts.
573 - }
768 + #betterdocs-ai-form {
769 + display: flex;
770 + flex-direction: column;
771 + }
574 772
575 - $response = wp_remote_post( $api_endpoint, $request_options );
773 + #betterdocs-ai-form .form-group {
774 + margin-bottom: 24px;
775 + }
576 776
577 - if ( is_wp_error( $response ) ) {
578 - return array(
579 - 'success' => false,
580 - 'error' => $response->get_error_message(),
581 - 'model' => $model
582 - );
583 - }
777 + #betterdocs-ai-form .bd-aiform-flex {
778 + display: flex;
779 + justify-content: space-between;
780 + }
584 781
585 - $body = wp_remote_retrieve_body( $response );
586 - $data = json_decode( $body, true );
782 + #betterdocs-ai-form label {
783 + font-weight: 600;
784 + margin-bottom: 5px;
785 + display: block;
786 + font-size: 14px;
787 + line-height: 20px;
788 + }
587 789
588 - if ( ! empty( $data[ 'error' ] ) ) {
589 - return array(
590 - 'success' => false,
591 - 'error' => isset( $data[ 'error' ][ 'message' ] ) ? $data[ 'error' ][ 'message' ] : 'OpenAI error',
592 - 'model' => $model,
593 - 'raw' => $data
594 - );
595 - }
790 + select#bd-autowrtite-content-section,
791 + #bd-autowrtite-content-paragraph,
792 + #betterdocs-ai-language {
793 + width: 150px !important;
794 + height: 40px;
795 + }
596 796
597 - $content = isset( $data[ 'choices' ][ 0 ][ 'message' ][ 'content' ] ) ? $data[ 'choices' ][ 0 ][ 'message' ][ 'content' ] : '';
598 - $usage = isset( $data[ 'usage' ] ) && is_array( $data[ 'usage' ] ) ? $data[ 'usage' ] : array();
797 + #betterdocs-ai-form input[type="text"],
798 + #betterdocs-ai-form textarea {
799 + width: 100%;
800 + padding: 8px;
801 + box-sizing: border-box;
802 + border: 1px solid #D0D5DD;
803 + border-radius: 4px;
804 + color: #667085;
805 + font-weight: 400;
806 + }
599 807
600 - return array(
601 - 'success' => true,
602 - 'content' => $content,
603 - 'model' => $model,
604 - 'prompt_tokens' => isset( $usage[ 'prompt_tokens' ] ) ? (int) $usage[ 'prompt_tokens' ] : null,
605 - 'completion_tokens' => isset( $usage[ 'completion_tokens' ] ) ? (int) $usage[ 'completion_tokens' ] : null,
606 - 'total_tokens' => isset( $usage[ 'total_tokens' ] ) ? (int) $usage[ 'total_tokens' ] : null,
607 - 'finish_reason' => isset( $data[ 'choices' ][ 0 ][ 'finish_reason' ] ) ? $data[ 'choices' ][ 0 ][ 'finish_reason' ] : null
608 - );
609 - }
808 + /* Override autofill text color */
809 + #betterdocs-ai-form input:-webkit-autofill {
810 + -webkit-text-fill-color: #667085 !important;
811 + }
610 812
611 - /**
612 - * Generic chat completion using the Write-with-AI model/token/key settings.
613 - *
614 - * Shared by lightweight, plain-text generators (glossary definitions, FAQ answers)
615 - * that need the same dynamic model as Write with AI / AI Edit but a caller-supplied
616 - * system prompt instead of the doc-authoring HTML prompt. Returns the same structured
617 - * array shape as {@see self::generate_openai_response_ai_edit()}.
618 - *
619 - * @param string $user_prompt The user message.
620 - * @param string $system_prompt Optional system message (omitted when empty).
621 - * @return array { success:bool, content?:string, error?:string, model:string, *_tokens?:int }
622 - */
623 - public function generate_text( $user_prompt, $system_prompt = '', $extra_system = array() ) {
624 - $api_key = $this->settings->get( 'ai_autowrite_api_key', '' );
625 - $max_tokens = $this->settings->get( 'ai_autowrite_max_token', 2500 );
626 - $model = $this->settings->get( 'write_with_ai_model', 'gpt-4o-mini' );
813 + #betterdocs-ai-form input::placeholder {
814 + color: #acb2bf;
815 + }
627 816
628 - $messages = array();
629 - if ( $system_prompt !== '' ) {
630 - $messages[] = array(
631 - 'role' => 'system',
632 - 'content' => $system_prompt
633 - );
634 - }
635 - foreach ( $this->normalize_extra_system( $extra_system ) as $extra ) {
636 - $messages[] = $extra;
637 - }
638 - $messages[] = array(
639 - 'role' => 'user',
640 - 'content' => $user_prompt
641 - );
817 + #betterdocs-ai-form textarea {
818 + color: #667085;
819 + }
642 820
643 - $api_endpoint = 'https://api.openai.com/v1/chat/completions';
821 + #betterdocs-ai-form input:focus,
822 + #betterdocs-ai-form textarea:focus {
823 + box-shadow: none;
824 + }
644 825
645 - $payload = AIHelper::build_openai_payload( $model, $messages, $max_tokens, null, 'write_with_ai' );
826 + #betterdocs-ai-form textarea:focus {
827 + color: inherit;
828 + box-shadow: none;
646 829
647 - $request_options = array(
648 - 'headers' => array(
649 - 'Content-Type' => 'application/json',
650 - 'Authorization' => 'Bearer ' . $api_key
651 - ),
652 - 'body' => wp_json_encode( $payload ),
653 - 'timeout' => 300
654 - );
830 + }
655 831
656 - // GPT-5.5 reasoning can run well past the default limits; give PHP and
657 - // the HTTP call room to finish (still subject to server php-fpm/nginx limits).
658 - if ( function_exists( 'set_time_limit' ) ) {
659 - set_time_limit( 300 );
660 - }
661 832
662 - $response = wp_remote_post( $api_endpoint, $request_options );
833 + .generate-button-container {
834 + position: sticky;
835 + bottom: 0px;
836 + width: 100%;
837 + margin-left: 0;
838 + z-index: 999999 !important;
839 + padding: 20px 0;
840 + display: flex;
841 + justify-content: end;
842 + border-top: 1px solid #ddd;
843 + background: white;
844 + border-bottom-right-radius: 5px;
845 + border-bottom-left-radius: 5px;
846 + }
663 847
664 - if ( is_wp_error( $response ) ) {
665 - return array(
666 - 'success' => false,
667 - 'error' => $response->get_error_message(),
668 - 'model' => $model
669 - );
670 - }
848 + .generate-button-container button {
849 + display: flex;
850 + gap: 8px;
851 + align-items: center;
852 + justify-content: center;
853 + opacity: 1 !important;
854 + margin-right: 40px;
855 + }
671 856
672 - $body = wp_remote_retrieve_body( $response );
673 - $data = json_decode( $body, true );
857 + .input-description {
858 + font-size: 14px;
859 + color: #667085;
860 + }
674 861
675 - if ( ! empty( $data[ 'error' ] ) ) {
676 - return array(
677 - 'success' => false,
678 - 'error' => isset( $data[ 'error' ][ 'message' ] ) ? $data[ 'error' ][ 'message' ] : 'OpenAI error',
679 - 'model' => $model,
680 - 'raw' => $data
681 - );
682 - }
862 + .betterdocs-ai-checkbox-field .form-group {
863 + display: flex;
864 + align-items: start;
865 + /* gap: 200px; */
866 + justify-content: space-between;
867 + }
683 868
684 - $content = isset( $data[ 'choices' ][ 0 ][ 'message' ][ 'content' ] ) ? $data[ 'choices' ][ 0 ][ 'message' ][ 'content' ] : '';
685 - $usage = isset( $data[ 'usage' ] ) && is_array( $data[ 'usage' ] ) ? $data[ 'usage' ] : array();
869 + .betterdocs-ai-checkbox-field input {
870 + width: 20px;
871 + height: 20px;
872 + }
686 873
687 - return array(
688 - 'success' => true,
689 - 'content' => $content,
690 - 'model' => $model,
691 - 'prompt_tokens' => isset( $usage[ 'prompt_tokens' ] ) ? (int) $usage[ 'prompt_tokens' ] : null,
692 - 'completion_tokens' => isset( $usage[ 'completion_tokens' ] ) ? (int) $usage[ 'completion_tokens' ] : null,
693 - 'total_tokens' => isset( $usage[ 'total_tokens' ] ) ? (int) $usage[ 'total_tokens' ] : null,
694 - 'finish_reason' => isset( $data[ 'choices' ][ 0 ][ 'finish_reason' ] ) ? $data[ 'choices' ][ 0 ][ 'finish_reason' ] : null
695 - );
696 - }
874 + /* Add this CSS to your existing stylesheet or create a new one */
697 875
698 - /**
699 - * Generate an OpenAI chat completion with a caller-supplied system + user
700 - * prompt. Mirrors generate_openai_response_ai_edit() (same key/model/token
701 - * floor/timeout handling and return shape) but does NOT force the
702 - * documentation-writer system prompt, so callers such as the Docs AI Suite
703 - * (taxonomy suggestions, excerpts) can supply task-appropriate instructions.
704 - *
705 - * @param string $system_prompt System instruction for the model.
706 - * @param string $user_prompt User message / content payload.
707 - * @param float|null $temperature Optional sampling temperature (ignored for gpt-5*).
708 - * @return array{success:bool,content?:string,error?:string,model:string,...}
709 - */
710 - public function generate_openai_response_raw( $system_prompt, $user_prompt, $temperature = null ) {
711 - $api_key = $this->settings->get( 'ai_autowrite_api_key', '' );
712 - $max_tokens = $this->settings->get( 'ai_autowrite_max_token', 2500 );
713 - $model = $this->settings->get( 'write_with_ai_model', 'gpt-4o-mini' );
876 + .betterdocs-ai-checkbox-field {
877 + position: relative;
878 + font-size: 16px;
879 + /* Adjust font size as needed */
880 + }
714 881
715 - $api_endpoint = 'https://api.openai.com/v1/chat/completions';
882 + .betterdocs-ai-checkbox-field input[type=checkbox]:checked::before {
883 + padding: 2px;
884 + }
716 885
717 - $payload = AIHelper::build_openai_payload(
718 - $model,
719 - array(
720 - array(
721 - 'role' => 'system',
722 - 'content' => $system_prompt
723 - ),
724 - array(
725 - 'role' => 'user',
726 - 'content' => $user_prompt
727 - )
728 - ),
729 - $max_tokens,
730 - $temperature,
731 - 'write_with_ai'
732 - );
886 + .betterdocs-ai-checkbox-field input[type=checkbox] {
887 + border: 1px solid #00b884;
888 + }
733 889
734 - $request_options = array(
735 - 'headers' => array(
736 - 'Content-Type' => 'application/json',
737 - 'Authorization' => 'Bearer ' . $api_key
738 - ),
739 - 'body' => wp_json_encode( $payload ),
740 - 'timeout' => 300
741 - );
890 + .betterdocs-ai-checkbox-field input[type=checkbox]:checked::before {
891 + content: '\2713';
892 + color: #00b884;
893 + }
742 894
743 - if ( function_exists( 'set_time_limit' ) ) {
744 - set_time_limit( 300 );
745 - }
895 + /* Change the default checkbox color */
896 + .betterdocs-ai-checkbox-field input[type="checkbox"]:hover {
897 + -webkit-appearance: none;
898 + /* WebKit/Blink Browsers */
899 + -moz-appearance: none;
900 + /* Firefox */
901 + appearance: none;
902 + border: 1px solid #00b884;
903 + /* Set the height of the checkbox */
904 + /* Optional: Round the corners */
905 + outline: none;
906 + /* Remove the default outline */
907 + }
746 908
747 - $response = wp_remote_post( $api_endpoint, $request_options );
909 + /* Style the checked state */
910 + .betterdocs-ai-checkbox-field input[type="checkbox"]:checked {
911 + /* Set the background color when checked */
912 + border: 1px solid #00b884;
913 + /* Set the border color when checked */
914 + }
748 915
749 - if ( is_wp_error( $response ) ) {
750 - return array(
751 - 'success' => false,
752 - 'error' => $response->get_error_message(),
753 - 'model' => $model
754 - );
755 - }
916 + .checkbox-field-label {
917 + width: calc(100% - 150px);
918 + }
756 919
757 - $body = wp_remote_retrieve_body( $response );
758 - $data = json_decode( $body, true );
920 + .checkbox-field-label p {
921 + margin: 0 !important;
922 + }
759 923
760 - if ( ! empty( $data[ 'error' ] ) ) {
761 - return array(
762 - 'success' => false,
763 - 'error' => isset( $data[ 'error' ][ 'message' ] ) ? $data[ 'error' ][ 'message' ] : 'OpenAI error',
764 - 'model' => $model,
765 - 'raw' => $data
766 - );
767 - }
924 + .checkbox-input-field {
925 + width: 300px;
926 + text-align: right;
927 + }
768 928
769 - $content = isset( $data[ 'choices' ][ 0 ][ 'message' ][ 'content' ] ) ? $data[ 'choices' ][ 0 ][ 'message' ][ 'content' ] : '';
770 - $usage = isset( $data[ 'usage' ] ) && is_array( $data[ 'usage' ] ) ? $data[ 'usage' ] : array();
929 + .checkbox-input-field {
930 + position: relative;
931 + display: inline-block;
932 + width: 60px;
933 + height: 34px;
934 + }
771 935
772 - return array(
773 - 'success' => true,
774 - 'content' => $content,
775 - 'model' => $model,
776 - 'prompt_tokens' => isset( $usage[ 'prompt_tokens' ] ) ? (int) $usage[ 'prompt_tokens' ] : null,
777 - 'completion_tokens' => isset( $usage[ 'completion_tokens' ] ) ? (int) $usage[ 'completion_tokens' ] : null,
778 - 'total_tokens' => isset( $usage[ 'total_tokens' ] ) ? (int) $usage[ 'total_tokens' ] : null,
779 - 'finish_reason' => isset( $data[ 'choices' ][ 0 ][ 'finish_reason' ] ) ? $data[ 'choices' ][ 0 ][ 'finish_reason' ] : null
780 - );
781 - }
936 + .checkbox-input-field input {
937 + display: none;
938 + }
782 939
783 - public function generate_openai_content_callback() {
784 - // Verify the nonce
785 - $ai_nonce = isset( $_POST['ai_nonce'] ) ? sanitize_text_field( wp_unslash( $_POST['ai_nonce'] ) ) : '';
786 - if ( ! wp_verify_nonce( $ai_nonce, 'generate_openai_content_nonce' ) ) {
787 - wp_send_json_error( 'Invalid nonce' );
788 - wp_die();
789 - }
940 + .toggle-label {
941 + position: absolute;
942 + cursor: pointer;
943 + top: 0;
944 + left: 0;
945 + right: 0;
946 + bottom: 0;
947 + background-color: #ccc;
948 + border-radius: 34px;
949 + transition: background-color 0.3s;
950 + scale: .9;
951 + width: 52px;
952 + height: 26px;
953 + }
790 954
791 - if ( ! current_user_can( 'edit_posts' ) ) {
792 - wp_send_json_error( 'Insufficient permissions' );
793 - wp_die();
794 - }
795 955
796 - $prompt = isset( $_POST['prompt'] ) ? sanitize_text_field( wp_unslash( $_POST['prompt'] ) ) : '';
797 - $keywords = isset( $_POST['keywords'] ) ? sanitize_text_field( wp_unslash( $_POST['keywords'] ) ) : '';
956 + .checkbox-input-field input:checked+.toggle-label {
957 + background-color: #00b884;
958 + }
798 959
799 - $ai_instance = new WriteWithAI( $this->settings );
960 + .toggle-label:after {
961 + content: "";
962 + position: absolute;
963 + height: 22px;
964 + width: 22px;
965 + left: 2px;
966 + bottom: 2px;
967 + background-color: white;
968 + border-radius: 50%;
969 + transition: transform 0.3s;
970 + }
800 971
801 - $generated_content = $ai_instance->generate_openai_response( $prompt, $keywords );
972 + .checkbox-input-field input:checked+.toggle-label:after {
973 + transform: translateX(26px);
974 + }
802 975
803 - // Count a successful generation (skip obvious upstream errors).
804 - if ( is_string( $generated_content ) && $generated_content !== '' && strpos( $generated_content, 'Error:' ) !== 0 ) {
805 - $post_id = isset( $_POST[ 'post_id' ] ) ? intval( $_POST[ 'post_id' ] ) : 0; //phpcs:ignore
806 - AIUsage::record( 'write_with_ai', $post_id );
807 - }
976 + .generate-btn,
977 + .prompt-btn,
978 + .keep-btn {
979 + background-color: #00b884;
980 + color: #fff;
981 + border: none;
982 + padding: 10px 15px;
983 + text-align: center;
984 + text-decoration: none;
985 + display: inline-block;
986 + font-size: 16px;
987 + cursor: pointer;
988 + border-radius: 4px;
989 + }
808 990
809 - // Send the generated content as the AJAX response
810 - wp_send_json_success( $generated_content );
811 - wp_die();
991 + .generate-btn[disabled] {
992 + cursor: unset;
993 + }
994 +
995 + .bd-close-button {
996 + cursor: pointer;
997 + font-size: 18px;
998 + font-weight: bold;
999 + float: right;
1000 + position: absolute;
1001 + right: 1px;
1002 + top: 1px;
1003 + padding: 10px;
1004 + background: #ffffff;
1005 + border-radius: 25px;
1006 + width: 20px;
1007 + height: 20px;
1008 + display: flex;
1009 + align-items: center;
1010 + justify-content: center;
1011 + color: #281617;
1012 + right: -60px;
1013 + }
1014 +
1015 + div#betterdocs-ai-error-message,
1016 + #betterdocs-ai-message {
1017 + font-size: 14px;
1018 + font-family: 'IBM Plex Sans', sans-serif;
1019 + color: #667085;
1020 + margin-bottom: 20px;
1021 + }
1022 +
1023 + div#betterdocs-ai-error-message a,
1024 + #betterdocs-ai-message a {
1025 + text-decoration: none;
1026 + font-weight: 600;
1027 + }
1028 +
1029 + div#betterdocs-ai-error-message a:focus,
1030 + #betterdocs-ai-message a:focus {
1031 + outline: none;
1032 + box-shadow: none;
1033 + color: #2271b1;
1034 + }
1035 +
1036 + #betterdocs-ai-message span {
1037 + color: #d63638;
1038 + }
1039 +
1040 + .warning-message {
1041 + display: flex;
1042 + align-items: center;
1043 + gap: 10px;
1044 + background: #fbebed;
1045 + padding: 12px;
1046 + border-radius: 5px;
1047 + font-size: 14px;
1048 + line-height: 1.4em;
1049 + border-left: 4px solid #d63638;
1050 + }
1051 +
1052 + .warning-message svg {
1053 + width: 20px;
1054 + height: 20px;
1055 + }
1056 +
1057 + .warning-message span {
1058 + color: #d63638;
1059 + }
1060 +
1061 + .warning-message .footer-message {
1062 + width: calc(100% - 20px);
1063 + }
1064 +
1065 + @media only screen and (max-width: 991px) {
1066 + .betterdocs-ai-autowrite-form-container {
1067 + left: 0;
1068 + right: 0;
1069 + transform: translate(0%, -50%);
1070 + }
1071 +
1072 + .betterdocs-ai-autowrite-form-content {
1073 + overflow-x: auto;
1074 + }
1075 + }
1076 +
1077 + @media only screen and (max-width: 740px) {
1078 +
1079 + /* .bd-close-button {
1080 + right: px;
1081 + top: 1px;
1082 + border-radius: 5px;
1083 + width: 12px;
1084 + height: 12px;
1085 + color: #ffffff;
1086 + background: #00b884;
1087 + } */
1088 + .bd-close-button {
1089 + right: 0px;
1090 + top: 0px;
1091 + width: 12px;
1092 + height: 12px;
1093 + font-size: 14px;
1094 + }
1095 +
1096 + }
1097 + </style>
1098 +<?php
812 1099 }
813 1100 }