| @@ -1,410 +1,172 @@ | ||
| 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; | |
| 11 | +class WriteWithAI extends Base { | |
| 13 | 12 | |
| 14 | - use WPDeveloper\BetterDocs\Utils\Helper; | |
| 15 | - use WPDeveloper\BetterDocs\Utils\AIHelper; | |
| 16 | - use WPDeveloper\BetterDocs\Utils\AIUsage; | |
| 13 | + public $settings; | |
| 17 | 14 | |
| 18 | - class WriteWithAI extends Base { | |
| 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 | 19 | |
| 20 | - public $settings; | |
| 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 | + } | |
| 21 | 27 | |
| 22 | - public function __construct( Settings $settings ) { | |
| 23 | - $this->settings = $settings; | |
| 24 | - // Get the post ID from the URL | |
| 25 | - $post_id = isset( $_GET[ 'post' ] ) ? intval( $_GET[ 'post' ] ) : 0; // phpcs:ignore | |
| 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' ] ); | |
| 26 | 32 | |
| 27 | - if ( ! empty( $_GET[ 'post_type' ] ) ) { // phpcs:ignore | |
| 28 | - $post_type = $_GET[ 'post_type' ]; // phpcs:ignore | |
| 29 | - } elseif ( $post_id > 0 ) { | |
| 30 | - $post_type = get_post_type( $post_id ); | |
| 31 | - } else { | |
| 32 | - $post_type = ''; | |
| 33 | - } | |
| 33 | + } | |
| 34 | 34 | |
| 35 | - if ( ! empty( $this->isEnabledWriteWithAI() ) && 'docs' == $post_type ) { | |
| 36 | - add_action( 'admin_footer', array( $this, 'ai_autowrite_button' ) ); | |
| 37 | - add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_ai_edit_assets' ) ); | |
| 38 | - } | |
| 39 | - add_action( 'wp_ajax_generate_openai_content', array( $this, 'generate_openai_content_callback' ) ); | |
| 40 | - } | |
| 35 | + public function isEnabledWriteWithAI() { | |
| 36 | + $isEnableAutoWrite = $this->settings->get( 'enable_write_with_ai', true ); | |
| 37 | + return $isEnableAutoWrite; | |
| 38 | + } | |
| 41 | 39 | |
| 42 | - public function enqueue_ai_edit_assets( $hook ) { | |
| 43 | - if ( 'post.php' !== $hook && 'post-new.php' !== $hook ) { | |
| 44 | - return; | |
| 45 | - } | |
| 40 | + public function isValidAPIKey( $apiKey ) { | |
| 41 | + if ( empty( $apiKey ) ) { | |
| 42 | + $api_response['valid'] = false; | |
| 43 | + $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 | 44 | |
| 47 | - global $post_type; | |
| 48 | - if ( 'docs' !== $post_type ) { | |
| 49 | - return; | |
| 50 | - } | |
| 45 | + return $api_response; | |
| 46 | + } | |
| 51 | 47 | |
| 52 | - if ( empty( $this->get_api_key() ) ) { | |
| 53 | - return; | |
| 54 | - } | |
| 48 | + $ch = curl_init( 'https://api.openai.com/v1/engines' ); //phpcs:ignore WordPress.WP.AlternativeFunctions.curl_curl_init | |
| 49 | + curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true ); //phpcs:ignore WordPress.WP.AlternativeFunctions.curl_curl_setopt | |
| 50 | + curl_setopt( | |
| 51 | + $ch, | |
| 52 | + CURLOPT_HTTPHEADER, | |
| 53 | + [ //phpcs:ignore WordPress.WP.AlternativeFunctions.curl_curl_setopt | |
| 54 | + 'Content-Type: application/json', | |
| 55 | + 'Authorization: Bearer ' . $apiKey, | |
| 56 | + ] | |
| 57 | + ); | |
| 55 | 58 | |
| 56 | - betterdocs()->assets->enqueue( 'betterdocs-ai-edit', 'blocks/ai-edit.js' ); | |
| 57 | - betterdocs()->assets->enqueue( 'betterdocs-ai-edit-style', 'blocks/ai-edit-style.css' ); | |
| 59 | + $api_response = []; | |
| 58 | 60 | |
| 59 | - wp_localize_script( | |
| 60 | - 'betterdocs-ai-edit', | |
| 61 | - 'betterdocsAIEdit', | |
| 62 | - array( | |
| 63 | - 'rest_url' => esc_url_raw( rest_url( 'betterdocs/v1/ai-edit' ) ), | |
| 64 | - 'rest_nonce' => wp_create_nonce( 'wp_rest' ), | |
| 65 | - 'post_id' => get_the_ID() | |
| 66 | - ) | |
| 67 | - ); | |
| 68 | - } | |
| 61 | + $response = curl_exec( $ch ); //phpcs:ignore WordPress.WP.AlternativeFunctions.curl_curl_exec | |
| 62 | + $httpCode = curl_getinfo( $ch, CURLINFO_HTTP_CODE ); //phpcs:ignore WordPress.WP.AlternativeFunctions.curl_curl_getinfo | |
| 69 | 63 | |
| 70 | - public function isEnabledWriteWithAI() { | |
| 71 | - $isEnableAutoWrite = $this->settings->get( 'enable_write_with_ai', true ); | |
| 72 | - return $isEnableAutoWrite; | |
| 73 | - } | |
| 64 | + curl_close( $ch ); //phpcs:ignore WordPress.WP.AlternativeFunctions.curl_curl_close | |
| 74 | 65 | |
| 75 | - public function isValidAPIKey( $apiKey ) { | |
| 76 | - if ( empty( $apiKey ) ) { | |
| 77 | - $api_response[ 'valid' ] = false; | |
| 78 | - $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.'; | |
| 66 | + if ( $httpCode == 200 ) { | |
| 67 | + $api_response['valid'] = true; | |
| 68 | + $api_response['message'] = 'Valid API Key'; | |
| 69 | + } else { | |
| 70 | + $responseData = json_decode( $response, true ); | |
| 71 | + // Access the message data (replace 'data' with the actual key used in the response) | |
| 72 | + $messageData = $responseData['error'] ? $responseData['error'] : ''; | |
| 73 | + $api_response['valid'] = false; | |
| 74 | + $api_response['message'] = $messageData['message'] ? $messageData['message'] : 'Invalid API Key'; | |
| 75 | + } | |
| 79 | 76 | |
| 80 | - return $api_response; | |
| 81 | - } | |
| 77 | + // print_r($response); | |
| 82 | 78 | |
| 83 | - $api_response = array(); | |
| 79 | + return $api_response; | |
| 80 | + } | |
| 84 | 81 | |
| 85 | - $response = wp_safe_remote_get( | |
| 86 | - 'https://api.openai.com/v1/engines', | |
| 87 | - array( | |
| 88 | - 'headers' => array( | |
| 89 | - 'Content-Type' => 'application/json', | |
| 90 | - 'Authorization' => 'Bearer ' . $apiKey, | |
| 91 | - ), | |
| 92 | - 'timeout' => 15, | |
| 93 | - ) | |
| 94 | - ); | |
| 82 | + public function get_api_key() { | |
| 83 | + $api_key = $this->settings->get( 'ai_autowrite_api_key', '' ); | |
| 84 | + return $api_key; | |
| 85 | + } | |
| 95 | 86 | |
| 96 | - if ( is_wp_error( $response ) ) { | |
| 97 | - $api_response[ 'valid' ] = false; | |
| 98 | - $api_response[ 'message' ] = $response->get_error_message(); | |
| 99 | - return $api_response; | |
| 100 | - } | |
| 101 | 87 | |
| 102 | - $httpCode = (int) wp_remote_retrieve_response_code( $response ); | |
| 103 | - $body = wp_remote_retrieve_body( $response ); | |
| 88 | + public function generate_openai_response( $prompt, $keywords ) { | |
| 89 | + try { | |
| 90 | + $api_key = $this->settings->get( 'ai_autowrite_api_key', '' ); | |
| 91 | + $max_tokens = $this->settings->get( 'ai_autowrite_max_token', 1500 ); | |
| 92 | + $model = $this->settings->get( 'write_with_ai_model', 'gpt-4o-mini' ); | |
| 104 | 93 | |
| 105 | - if ( 200 === $httpCode ) { | |
| 106 | - $api_response[ 'valid' ] = true; | |
| 107 | - $api_response[ 'message' ] = 'Valid API Key'; | |
| 108 | - } else { | |
| 109 | - $responseData = json_decode( $body, true ); | |
| 110 | - $messageData = ! empty( $responseData[ 'error' ] ) ? $responseData[ 'error' ] : array(); | |
| 111 | - $api_response[ 'valid' ] = false; | |
| 112 | - $api_response[ 'message' ] = ! empty( $messageData[ 'message' ] ) ? $messageData[ 'message' ] : 'Invalid API Key'; | |
| 113 | - } | |
| 94 | + $api_endpoint = 'https://api.openai.com/v1/chat/completions'; // Update the endpoint based on OpenAI API version | |
| 114 | 95 | |
| 115 | - // print_r($response); | |
| 96 | + $request_options = array( | |
| 97 | + 'headers' => array( | |
| 98 | + 'Content-Type' => 'application/json', | |
| 99 | + 'Authorization' => 'Bearer ' . $api_key, | |
| 100 | + ), | |
| 101 | + 'body' => json_encode( | |
| 102 | + array( //phpcs:ignore WordPress.WP.AlternativeFunctions.json_encode_json_encode | |
| 103 | + 'model' => $model, // Add the model parameter here | |
| 104 | + 'messages' => array( | |
| 105 | + array( | |
| 106 | + 'role' => 'system', | |
| 107 | + 'content' => 'You are a helpful assistant who writes documentation for users.' | |
| 108 | + ), | |
| 109 | + array( | |
| 110 | + 'role' => 'user', | |
| 111 | + 'content' => $prompt | |
| 112 | + ), | |
| 113 | + ), | |
| 114 | + 'max_tokens' => $max_tokens, | |
| 116 | 115 | |
| 117 | - return $api_response; | |
| 118 | - } | |
| 116 | + ) | |
| 117 | + ), | |
| 118 | + 'timeout' => 50, | |
| 119 | + ); | |
| 119 | 120 | |
| 120 | - public function get_api_key() { | |
| 121 | - $api_key = $this->settings->get( 'ai_autowrite_api_key', '' ); | |
| 122 | - return $api_key; | |
| 123 | - } | |
| 121 | + $response = wp_remote_post( $api_endpoint, $request_options ); | |
| 124 | 122 | |
| 125 | - public function get_system_prompt() { | |
| 126 | - $prompt = <<<'PROMPT' | |
| 127 | -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. | |
| 123 | + if ( is_wp_error( $response ) ) { | |
| 124 | + return 'Error: ' . $response->get_error_message(); | |
| 125 | + } else { | |
| 126 | + $body = wp_remote_retrieve_body( $response ); | |
| 128 | 127 | |
| 129 | -## Output format | |
| 128 | + $data = json_decode( $body, true ); | |
| 130 | 129 | |
| 131 | -Return only HTML body content. Never wrap output in `<!doctype>`, `<html>`, `<head>`, `<body>`, or markdown code fences (no ```html ... ```). | |
| 130 | + if ( ! empty( $data['error'] ) ) { | |
| 131 | + return $data['error']['message']; | |
| 132 | + } | |
| 132 | 133 | |
| 133 | -Use semantic, Gutenberg-friendly tags only: | |
| 134 | -- Headings: `<h2>`, `<h3>`, `<h4>` (do not emit `<h1>` — it is reserved for the document title) | |
| 135 | -- Paragraphs: `<p>` for prose | |
| 136 | -- Lists: `<ul>`/`<ol>` with `<li>` for any list of items, steps, or bullet points — never fake a list with paragraphs or `<br>` | |
| 137 | -- Links: `<a href="https://...">link text</a>` with absolute URLs | |
| 138 | -- Inline emphasis: `<strong>`, `<em>`, `<code>` | |
| 139 | -- Code blocks: `<pre><code>...</code></pre>` for multi-line code or commands | |
| 140 | -- Quotes: `<blockquote>` | |
| 141 | -- Tables: `<table>` with `<thead>`, `<tbody>`, `<tr>`, `<th>`, `<td>` | |
| 142 | -- Images: `<img src="..." alt="...">` | |
| 134 | + return $data['choices'][0]['message']['content']; // Update this line to get the assistant's message | |
| 135 | + } | |
| 136 | + } catch ( Exception $error ) { | |
| 137 | + return 'Error: ' . $error->getMessage(); | |
| 138 | + } | |
| 139 | + } | |
| 143 | 140 | |
| 144 | -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. | |
| 145 | 141 | |
| 146 | -Do not emit `<style>`, `<script>`, `<iframe>`, `<form>`, or inline `style=""` / `class=""` attributes (other than the `highlight` class above). | |
| 147 | 142 | |
| 148 | -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. | |
| 149 | -PROMPT; | |
| 143 | + public function generate_openai_content_callback() { | |
| 144 | + // Verify the nonce | |
| 145 | + if (!isset($_POST['ai_nonce']) || !wp_verify_nonce($_POST['ai_nonce'], 'generate_openai_content_nonce')) { //phpcs:ignore | |
| 146 | + wp_send_json_error( 'Invalid nonce' ); | |
| 147 | + wp_die(); | |
| 148 | + } | |
| 150 | 149 | |
| 151 | - return apply_filters( 'betterdocs_write_with_ai_system_prompt', $prompt ); | |
| 152 | - } | |
| 150 | + $prompt = sanitize_text_field($_POST['prompt']); //phpcs:ignore | |
| 151 | + // $num_of_sections = sanitize_text_field($_POST['numOfSections']); | |
| 152 | + // $num_of_paragraphs = sanitize_text_field($_POST['numOfParagraphs']); | |
| 153 | + $keywords = sanitize_text_field($_POST['keywords']); //phpcs:ignore | |
| 153 | 154 | |
| 154 | - public function generate_openai_response( $prompt, $keywords ) { | |
| 155 | - try { | |
| 156 | - $api_key = $this->settings->get( 'ai_autowrite_api_key', '' ); | |
| 157 | - $max_tokens = $this->settings->get( 'ai_autowrite_max_token', 2500 ); | |
| 158 | - $model = $this->settings->get( 'write_with_ai_model', 'gpt-4o-mini' ); | |
| 155 | + $ai_instance = new WriteWithAI( $this->settings ); | |
| 159 | 156 | |
| 160 | - $api_endpoint = 'https://api.openai.com/v1/chat/completions'; // Update the endpoint based on OpenAI API version | |
| 157 | + $generated_content = $ai_instance->generate_openai_response( $prompt, $keywords ); | |
| 161 | 158 | |
| 162 | - $request_body = AIHelper::build_openai_payload( | |
| 163 | - $model, | |
| 164 | - array( | |
| 165 | - array( | |
| 166 | - 'role' => 'system', | |
| 167 | - 'content' => $this->get_system_prompt() | |
| 168 | - ), | |
| 169 | - array( | |
| 170 | - 'role' => 'user', | |
| 171 | - 'content' => $prompt | |
| 172 | - ) | |
| 173 | - ), | |
| 174 | - $max_tokens, | |
| 175 | - null, | |
| 176 | - 'write_with_ai' | |
| 177 | - ); | |
| 159 | + // Send the generated content as the AJAX response | |
| 160 | + wp_send_json_success( $generated_content ); | |
| 161 | + wp_die(); | |
| 162 | + } | |
| 178 | 163 | |
| 179 | - $request_options = array( | |
| 180 | - 'headers' => array( | |
| 181 | - 'Content-Type' => 'application/json', | |
| 182 | - 'Authorization' => 'Bearer ' . $api_key | |
| 183 | - ), | |
| 184 | - 'body' => json_encode( $request_body ), | |
| 185 | - 'timeout' => 300 | |
| 186 | - ); | |
| 187 | 164 | |
| 188 | - // GPT-5.5 reasoning can run well past the default limits; give PHP and | |
| 189 | - // the HTTP call room to finish (still subject to server php-fpm/nginx limits). | |
| 190 | - if ( function_exists( 'set_time_limit' ) ) { | |
| 191 | - 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. | |
| 192 | - } | |
| 193 | 165 | |
| 194 | - $response = wp_remote_post( $api_endpoint, $request_options ); | |
| 166 | + public function ai_autowrite_button() { | |
| 195 | 167 | |
| 196 | - if ( is_wp_error( $response ) ) { | |
| 197 | - return 'Error: ' . $response->get_error_message(); | |
| 198 | - } else { | |
| 199 | - $body = wp_remote_retrieve_body( $response ); | |
| 200 | - | |
| 201 | - $data = json_decode( $body, true ); | |
| 202 | - | |
| 203 | - if ( ! empty( $data[ 'error' ] ) ) { | |
| 204 | - return $data[ 'error' ][ 'message' ]; | |
| 205 | - } | |
| 206 | - | |
| 207 | - return $data[ 'choices' ][ 0 ][ 'message' ][ 'content' ]; // Update this line to get the assistant's message | |
| 208 | - } | |
| 209 | - } catch ( Exception $error ) { | |
| 210 | - return 'Error: ' . $error->getMessage(); | |
| 211 | - } | |
| 212 | - } | |
| 213 | - | |
| 214 | - public function generate_openai_response_ai_edit( $prompt ) { | |
| 215 | - $api_key = $this->settings->get( 'ai_autowrite_api_key', '' ); | |
| 216 | - $max_tokens = $this->settings->get( 'ai_autowrite_max_token', 2500 ); | |
| 217 | - $model = $this->settings->get( 'write_with_ai_model', 'gpt-4o-mini' ); | |
| 218 | - | |
| 219 | - $api_endpoint = 'https://api.openai.com/v1/chat/completions'; | |
| 220 | - | |
| 221 | - $payload = AIHelper::build_openai_payload( | |
| 222 | - $model, | |
| 223 | - array( | |
| 224 | - array( | |
| 225 | - 'role' => 'system', | |
| 226 | - 'content' => $this->get_system_prompt() | |
| 227 | - ), | |
| 228 | - array( | |
| 229 | - 'role' => 'user', | |
| 230 | - 'content' => $prompt | |
| 231 | - ) | |
| 232 | - ), | |
| 233 | - $max_tokens, | |
| 234 | - null, | |
| 235 | - 'write_with_ai' | |
| 236 | - ); | |
| 237 | - | |
| 238 | - $request_options = array( | |
| 239 | - 'headers' => array( | |
| 240 | - 'Content-Type' => 'application/json', | |
| 241 | - 'Authorization' => 'Bearer ' . $api_key | |
| 242 | - ), | |
| 243 | - 'body' => wp_json_encode( $payload ), | |
| 244 | - 'timeout' => 300 | |
| 245 | - ); | |
| 246 | - | |
| 247 | - // GPT-5.5 reasoning can run well past the default limits; give PHP and | |
| 248 | - // the HTTP call room to finish (still subject to server php-fpm/nginx limits). | |
| 249 | - if ( function_exists( 'set_time_limit' ) ) { | |
| 250 | - 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. | |
| 251 | - } | |
| 252 | - | |
| 253 | - $response = wp_remote_post( $api_endpoint, $request_options ); | |
| 254 | - | |
| 255 | - if ( is_wp_error( $response ) ) { | |
| 256 | - return array( | |
| 257 | - 'success' => false, | |
| 258 | - 'error' => $response->get_error_message(), | |
| 259 | - 'model' => $model | |
| 260 | - ); | |
| 261 | - } | |
| 262 | - | |
| 263 | - $body = wp_remote_retrieve_body( $response ); | |
| 264 | - $data = json_decode( $body, true ); | |
| 265 | - | |
| 266 | - if ( ! empty( $data[ 'error' ] ) ) { | |
| 267 | - return array( | |
| 268 | - 'success' => false, | |
| 269 | - 'error' => isset( $data[ 'error' ][ 'message' ] ) ? $data[ 'error' ][ 'message' ] : 'OpenAI error', | |
| 270 | - 'model' => $model, | |
| 271 | - 'raw' => $data | |
| 272 | - ); | |
| 273 | - } | |
| 274 | - | |
| 275 | - $content = isset( $data[ 'choices' ][ 0 ][ 'message' ][ 'content' ] ) ? $data[ 'choices' ][ 0 ][ 'message' ][ 'content' ] : ''; | |
| 276 | - $usage = isset( $data[ 'usage' ] ) && is_array( $data[ 'usage' ] ) ? $data[ 'usage' ] : array(); | |
| 277 | - | |
| 278 | - return array( | |
| 279 | - 'success' => true, | |
| 280 | - 'content' => $content, | |
| 281 | - 'model' => $model, | |
| 282 | - 'prompt_tokens' => isset( $usage[ 'prompt_tokens' ] ) ? (int) $usage[ 'prompt_tokens' ] : null, | |
| 283 | - 'completion_tokens' => isset( $usage[ 'completion_tokens' ] ) ? (int) $usage[ 'completion_tokens' ] : null, | |
| 284 | - 'total_tokens' => isset( $usage[ 'total_tokens' ] ) ? (int) $usage[ 'total_tokens' ] : null, | |
| 285 | - 'finish_reason' => isset( $data[ 'choices' ][ 0 ][ 'finish_reason' ] ) ? $data[ 'choices' ][ 0 ][ 'finish_reason' ] : null | |
| 286 | - ); | |
| 287 | - } | |
| 288 | - | |
| 289 | - /** | |
| 290 | - * Generic chat completion using the Write-with-AI model/token/key settings. | |
| 291 | - * | |
| 292 | - * Shared by lightweight, plain-text generators (glossary definitions, FAQ answers) | |
| 293 | - * that need the same dynamic model as Write with AI / AI Edit but a caller-supplied | |
| 294 | - * system prompt instead of the doc-authoring HTML prompt. Returns the same structured | |
| 295 | - * array shape as {@see self::generate_openai_response_ai_edit()}. | |
| 296 | - * | |
| 297 | - * @param string $user_prompt The user message. | |
| 298 | - * @param string $system_prompt Optional system message (omitted when empty). | |
| 299 | - * @return array { success:bool, content?:string, error?:string, model:string, *_tokens?:int } | |
| 300 | - */ | |
| 301 | - public function generate_text( $user_prompt, $system_prompt = '' ) { | |
| 302 | - $api_key = $this->settings->get( 'ai_autowrite_api_key', '' ); | |
| 303 | - $max_tokens = $this->settings->get( 'ai_autowrite_max_token', 2500 ); | |
| 304 | - $model = $this->settings->get( 'write_with_ai_model', 'gpt-4o-mini' ); | |
| 305 | - | |
| 306 | - $messages = array(); | |
| 307 | - if ( $system_prompt !== '' ) { | |
| 308 | - $messages[] = array( | |
| 309 | - 'role' => 'system', | |
| 310 | - 'content' => $system_prompt | |
| 311 | - ); | |
| 312 | - } | |
| 313 | - $messages[] = array( | |
| 314 | - 'role' => 'user', | |
| 315 | - 'content' => $user_prompt | |
| 316 | - ); | |
| 317 | - | |
| 318 | - $api_endpoint = 'https://api.openai.com/v1/chat/completions'; | |
| 319 | - | |
| 320 | - $payload = AIHelper::build_openai_payload( $model, $messages, $max_tokens, null, 'write_with_ai' ); | |
| 321 | - | |
| 322 | - $request_options = array( | |
| 323 | - 'headers' => array( | |
| 324 | - 'Content-Type' => 'application/json', | |
| 325 | - 'Authorization' => 'Bearer ' . $api_key | |
| 326 | - ), | |
| 327 | - 'body' => wp_json_encode( $payload ), | |
| 328 | - 'timeout' => 300 | |
| 329 | - ); | |
| 330 | - | |
| 331 | - // GPT-5.5 reasoning can run well past the default limits; give PHP and | |
| 332 | - // the HTTP call room to finish (still subject to server php-fpm/nginx limits). | |
| 333 | - if ( function_exists( 'set_time_limit' ) ) { | |
| 334 | - set_time_limit( 300 ); | |
| 335 | - } | |
| 336 | - | |
| 337 | - $response = wp_remote_post( $api_endpoint, $request_options ); | |
| 338 | - | |
| 339 | - if ( is_wp_error( $response ) ) { | |
| 340 | - return array( | |
| 341 | - 'success' => false, | |
| 342 | - 'error' => $response->get_error_message(), | |
| 343 | - 'model' => $model | |
| 344 | - ); | |
| 345 | - } | |
| 346 | - | |
| 347 | - $body = wp_remote_retrieve_body( $response ); | |
| 348 | - $data = json_decode( $body, true ); | |
| 349 | - | |
| 350 | - if ( ! empty( $data[ 'error' ] ) ) { | |
| 351 | - return array( | |
| 352 | - 'success' => false, | |
| 353 | - 'error' => isset( $data[ 'error' ][ 'message' ] ) ? $data[ 'error' ][ 'message' ] : 'OpenAI error', | |
| 354 | - 'model' => $model, | |
| 355 | - 'raw' => $data | |
| 356 | - ); | |
| 357 | - } | |
| 358 | - | |
| 359 | - $content = isset( $data[ 'choices' ][ 0 ][ 'message' ][ 'content' ] ) ? $data[ 'choices' ][ 0 ][ 'message' ][ 'content' ] : ''; | |
| 360 | - $usage = isset( $data[ 'usage' ] ) && is_array( $data[ 'usage' ] ) ? $data[ 'usage' ] : array(); | |
| 361 | - | |
| 362 | - return array( | |
| 363 | - 'success' => true, | |
| 364 | - 'content' => $content, | |
| 365 | - 'model' => $model, | |
| 366 | - 'prompt_tokens' => isset( $usage[ 'prompt_tokens' ] ) ? (int) $usage[ 'prompt_tokens' ] : null, | |
| 367 | - 'completion_tokens' => isset( $usage[ 'completion_tokens' ] ) ? (int) $usage[ 'completion_tokens' ] : null, | |
| 368 | - 'total_tokens' => isset( $usage[ 'total_tokens' ] ) ? (int) $usage[ 'total_tokens' ] : null, | |
| 369 | - 'finish_reason' => isset( $data[ 'choices' ][ 0 ][ 'finish_reason' ] ) ? $data[ 'choices' ][ 0 ][ 'finish_reason' ] : null | |
| 370 | - ); | |
| 371 | - } | |
| 372 | - | |
| 373 | - public function generate_openai_content_callback() { | |
| 374 | - // Verify the nonce | |
| 375 | - $ai_nonce = isset( $_POST['ai_nonce'] ) ? sanitize_text_field( wp_unslash( $_POST['ai_nonce'] ) ) : ''; | |
| 376 | - if ( ! wp_verify_nonce( $ai_nonce, 'generate_openai_content_nonce' ) ) { | |
| 377 | - wp_send_json_error( 'Invalid nonce' ); | |
| 378 | - wp_die(); | |
| 379 | - } | |
| 380 | - | |
| 381 | - if ( ! current_user_can( 'edit_posts' ) ) { | |
| 382 | - wp_send_json_error( 'Insufficient permissions' ); | |
| 383 | - wp_die(); | |
| 384 | - } | |
| 385 | - | |
| 386 | - $prompt = isset( $_POST['prompt'] ) ? sanitize_text_field( wp_unslash( $_POST['prompt'] ) ) : ''; | |
| 387 | - $keywords = isset( $_POST['keywords'] ) ? sanitize_text_field( wp_unslash( $_POST['keywords'] ) ) : ''; | |
| 388 | - | |
| 389 | - $ai_instance = new WriteWithAI( $this->settings ); | |
| 390 | - | |
| 391 | - $generated_content = $ai_instance->generate_openai_response( $prompt, $keywords ); | |
| 392 | - | |
| 393 | - // Count a successful generation (skip obvious upstream errors). | |
| 394 | - if ( is_string( $generated_content ) && $generated_content !== '' && strpos( $generated_content, 'Error:' ) !== 0 ) { | |
| 395 | - $post_id = isset( $_POST[ 'post_id' ] ) ? intval( $_POST[ 'post_id' ] ) : 0; //phpcs:ignore | |
| 396 | - AIUsage::record( 'write_with_ai', $post_id ); | |
| 397 | - } | |
| 398 | - | |
| 399 | - // Send the generated content as the AJAX response | |
| 400 | - wp_send_json_success( $generated_content ); | |
| 401 | - wp_die(); | |
| 402 | - } | |
| 403 | - | |
| 404 | - public function ai_autowrite_button() { | |
| 405 | - | |
| 406 | - ?> | |
| 168 | + ?> | |
| 407 | 169 | <script> |
| 408 | 170 | var docsTitle; |
| 409 | 171 | |
| 410 | 172 | function responseMessage(message) { |
| @@ -459,9 +221,8 @@ | ||
| 459 | 221 | prompt: document.querySelector('#betterdocs-ai-content').value, |
| 460 | 222 | // numOfSections: numOfSections, |
| 461 | 223 | // numOfParagraphs: numOfParagraphs, |
| 462 | 224 | keywords: keywords, |
| 463 | - post_id: (document.getElementById('post_ID') ? document.getElementById('post_ID').value : 0), | |
| 464 | 225 | ai_nonce: nonce, // Pass the nonce value |
| 465 | 226 | }, |
| 466 | 227 | success: function(response) { |
| 467 | 228 | // Update the content in the textarea |
| @@ -471,8 +232,14 @@ | ||
| 471 | 232 | setTimeout(() => { |
| 472 | 233 | insertContentToEditor(title, response.data, isOverwrite, keywords); |
| 473 | 234 | docGenerateBtnTxt.innerHTML = reGenerateDocLabel; |
| 474 | 235 | jQuery('.generate-btn').removeAttr('disabled'); |
| 236 | + | |
| 237 | + // console.log(jQuery('.betterdocs-ai-autowrite-form-container').data('new-doc-page')); | |
| 238 | + | |
| 239 | + // if(jQuery('.betterdocs-ai-autowrite-form-container')?.data('new-doc-page')) { | |
| 240 | + // docGenerateBtnTxt.innerHTML = reGenerateDocLabel; | |
| 241 | + // } | |
| 475 | 242 | }, 2000); |
| 476 | 243 | } else { |
| 477 | 244 | // alert(response.data.message); |
| 478 | 245 | jQuery('#betterdocs-ai-error-message').parent().removeClass('hidden'); |
| @@ -481,21 +248,10 @@ | ||
| 481 | 248 | jQuery('.generate-btn').removeAttr('disabled'); |
| 482 | 249 | |
| 483 | 250 | } |
| 484 | 251 | }, |
| 485 | - error: function(jqXHR, textStatus, errorThrown) { | |
| 486 | - console.error(jqXHR, textStatus, errorThrown); | |
| 487 | - | |
| 488 | - // Reset the UI so a slow/failed request never leaves a permanent "Generating...". | |
| 489 | - var timedOut = ( textStatus === 'timeout' ) || ( jqXHR && jqXHR.status === 504 ) || ( jqXHR && jqXHR.status === 0 ); | |
| 490 | - var errMessage = timedOut | |
| 491 | - ? "<?php echo esc_html__( 'The request timed out before a response came back. The AI may still have generated content on the server — try again, or pick a faster model / lower the token limit.', 'betterdocs' ); ?>" | |
| 492 | - : "<?php echo esc_html__( 'Something went wrong while generating. Please try again.', 'betterdocs' ); ?>"; | |
| 493 | - | |
| 494 | - jQuery('#betterdocs-ai-error-message').parent().removeClass('hidden'); | |
| 495 | - jQuery('#betterdocs-ai-error-message').html(responseMessage(errMessage)); | |
| 496 | - docGenerateBtnTxt.innerHTML = generateDocLabel; | |
| 497 | - jQuery('.generate-btn').removeAttr('disabled'); | |
| 252 | + error: function(error) { | |
| 253 | + console.error(error); | |
| 498 | 254 | }, |
| 499 | 255 | }); |
| 500 | 256 | |
| 501 | 257 | } |
| @@ -519,9 +275,9 @@ | ||
| 519 | 275 | if (!jQuery('#betterdocs-ai-error-message').parent().hasClass('hidden')) { |
| 520 | 276 | jQuery('#betterdocs-ai-error-message').parent().addClass('hidden'); |
| 521 | 277 | } |
| 522 | 278 | |
| 523 | - contentTextArea.value = `Write documentation for '${title}'. Cover these topics in depth: ${keywords}. Use headings, paragraphs, lists, links, code blocks, and tables wherever they help the reader. Follow the output-format rules in the system instructions.`; | |
| 279 | + 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.`; | |
| 524 | 280 | } |
| 525 | 281 | |
| 526 | 282 | function convertMarkdownToHTML(markdownContent) { |
| 527 | 283 | // Simple Markdown to HTML conversion (you may need to enhance this based on your needs) |
| @@ -551,107 +307,30 @@ | ||
| 551 | 307 | return newDiv.innerHTML; |
| 552 | 308 | } |
| 553 | 309 | |
| 554 | 310 | |
| 555 | - function escapeHtml(str) { | |
| 556 | - return String(str).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>'); | |
| 557 | - } | |
| 558 | - | |
| 559 | - function escapeRegex(str) { | |
| 560 | - return String(str).replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); | |
| 561 | - } | |
| 562 | - | |
| 563 | - function stripCodeFences(htmlString) { | |
| 564 | - if (!htmlString) { | |
| 565 | - return ''; | |
| 566 | - } | |
| 567 | - return htmlString | |
| 568 | - .replace(/^\s*```(?:html|HTML)?\s*\n?/, '') | |
| 569 | - .replace(/\n?\s*```\s*$/, ''); | |
| 570 | - } | |
| 571 | - | |
| 572 | 311 | function wrapwithHeighlight(inputString, keywords) { |
| 573 | - if (!inputString) { | |
| 574 | - return ''; | |
| 575 | - } | |
| 312 | + let modifiedString = inputString.replace(/<(h\d)(.*?)>(.*?)<\/\1>/g, '<$1$2><span class="highlight">$3</span></$1>'); | |
| 576 | 313 | |
| 577 | - const container = document.createElement('div'); | |
| 578 | - container.innerHTML = inputString; | |
| 314 | + const keywordArray = keywords.split(',').map(keyword => keyword.trim()); | |
| 579 | 315 | |
| 580 | - // Wrap each heading's visible text with the highlight span (skip if already present) | |
| 581 | - container.querySelectorAll('h1, h2, h3, h4, h5, h6').forEach(function (heading) { | |
| 582 | - if (heading.querySelector('.highlight')) { | |
| 583 | - return; | |
| 584 | - } | |
| 585 | - const text = heading.textContent; | |
| 586 | - if (!text || !text.trim()) { | |
| 587 | - return; | |
| 588 | - } | |
| 589 | - heading.innerHTML = '<span class="highlight">' + escapeHtml(text) + '</span>'; | |
| 316 | + keywordArray.forEach(keyword => { | |
| 317 | + modifiedString = modifiedString.replace(new RegExp(`\\b(${keyword})\\b`, 'gi'), '<span class="highlight">$1</span>'); | |
| 590 | 318 | }); |
| 591 | 319 | |
| 592 | - // Wrap keywords only inside text nodes — never inside attributes, code, or existing highlights | |
| 593 | - const keywordList = (keywords || '') | |
| 594 | - .split(',') | |
| 595 | - .map(function (k) { return k.trim(); }) | |
| 596 | - .filter(Boolean); | |
| 597 | - | |
| 598 | - if (keywordList.length) { | |
| 599 | - const pattern = new RegExp('\\b(' + keywordList.map(escapeRegex).join('|') + ')\\b', 'gi'); | |
| 600 | - const walker = document.createTreeWalker(container, NodeFilter.SHOW_TEXT, { | |
| 601 | - acceptNode: function (node) { | |
| 602 | - let parent = node.parentNode; | |
| 603 | - while (parent && parent !== container) { | |
| 604 | - if (parent.nodeType === 1) { | |
| 605 | - const tag = parent.tagName.toLowerCase(); | |
| 606 | - if (tag === 'code' || tag === 'pre') { | |
| 607 | - return NodeFilter.FILTER_REJECT; | |
| 608 | - } | |
| 609 | - if (parent.classList && parent.classList.contains('highlight')) { | |
| 610 | - return NodeFilter.FILTER_REJECT; | |
| 611 | - } | |
| 612 | - } | |
| 613 | - parent = parent.parentNode; | |
| 614 | - } | |
| 615 | - return NodeFilter.FILTER_ACCEPT; | |
| 616 | - } | |
| 617 | - }); | |
| 618 | - | |
| 619 | - const textNodes = []; | |
| 620 | - let current; | |
| 621 | - while ((current = walker.nextNode())) { | |
| 622 | - textNodes.push(current); | |
| 623 | - } | |
| 624 | - | |
| 625 | - textNodes.forEach(function (textNode) { | |
| 626 | - pattern.lastIndex = 0; | |
| 627 | - if (!pattern.test(textNode.nodeValue)) { | |
| 628 | - return; | |
| 629 | - } | |
| 630 | - pattern.lastIndex = 0; | |
| 631 | - const fragmentHost = document.createElement('span'); | |
| 632 | - fragmentHost.innerHTML = escapeHtml(textNode.nodeValue).replace(pattern, '<span class="highlight">$1</span>'); | |
| 633 | - const parent = textNode.parentNode; | |
| 634 | - while (fragmentHost.firstChild) { | |
| 635 | - parent.insertBefore(fragmentHost.firstChild, textNode); | |
| 636 | - } | |
| 637 | - parent.removeChild(textNode); | |
| 638 | - }); | |
| 639 | - } | |
| 640 | - | |
| 641 | - return container.innerHTML; | |
| 320 | + return modifiedString; | |
| 642 | 321 | } |
| 643 | 322 | |
| 644 | 323 | function getBodyContent(htmlString) { |
| 645 | - if (!htmlString) { | |
| 646 | - return ''; | |
| 647 | - } | |
| 648 | - const cleaned = stripCodeFences(htmlString); | |
| 649 | - const match = cleaned.match(/<body[^>]*>([\s\S]*?)<\/body>/i); | |
| 324 | + var match = htmlString.match(/<body[^>]*>([\s\S]*?)<\/body>/i); | |
| 325 | + | |
| 326 | + // Check if match is null before accessing match[1] | |
| 650 | 327 | if (match) { |
| 328 | + console.log(match[1].replace(/<p>\s+/g, '<p>')); | |
| 651 | 329 | return match[1].replace(/<p>\s+/g, '<p>'); |
| 330 | + } else { | |
| 331 | + return ''; | |
| 652 | 332 | } |
| 653 | - return cleaned; | |
| 654 | 333 | } |
| 655 | 334 | |
| 656 | 335 | |
| 657 | 336 | |
| @@ -664,9 +343,9 @@ | ||
| 664 | 343 | |
| 665 | 344 | htmlContent = getBodyContent(htmlContent); |
| 666 | 345 | htmlContent = removeFirstHeading(htmlContent); |
| 667 | 346 | htmlContent = wrapwithHeighlight(htmlContent, keywords); |
| 668 | - const isPostContent = `<?php echo isset( $_GET['post'] ) ? esc_html( get_the_content( absint( wp_unslash( $_GET['post'] ) ) ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only post id from URL. ?>`; | |
| 347 | + const isPostContent = `<?php echo isset($_GET['post']) ? esc_html(get_the_content($_GET['post'])) : ''; // phpcs:ignore ?>`; | |
| 669 | 348 | |
| 670 | 349 | const blocks = wp.blocks.rawHandler({ |
| 671 | 350 | HTML: htmlContent |
| 672 | 351 | }); |
| @@ -719,9 +398,9 @@ | ||
| 719 | 398 | let currentKeywords = jQuery('#betterdocs-ai-keyword').val(); |
| 720 | 399 | if (!currentKeywords) { |
| 721 | 400 | currentKeywords = '{Documentation Keywords}'; |
| 722 | 401 | } |
| 723 | - const currentPrompt = `Write documentation for '${docsTitle?docsTitle:'{Documentation title}'}'. Cover these topics in depth: ${currentKeywords}. Use headings, paragraphs, lists, links, code blocks, and tables wherever they help the reader. Follow the output-format rules in the system instructions.`; | |
| 402 | + 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.`; | |
| 724 | 403 | jQuery('#betterdocs-ai-content').val(currentPrompt); |
| 725 | 404 | |
| 726 | 405 | jQuery('#betterdocs-ai-title').val(docsTitle); |
| 727 | 406 | |
| @@ -738,14 +417,14 @@ | ||
| 738 | 417 | |
| 739 | 418 | |
| 740 | 419 | function writeWithAIForm() { |
| 741 | 420 | |
| 742 | - let title = `<?php echo isset( $_GET['post'] ) ? esc_html( get_the_title( absint( wp_unslash( $_GET['post'] ) ) ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only post id from URL. ?>`; | |
| 421 | + let title = `<?php echo isset($_GET['post']) ? esc_html(get_the_title($_GET['post'])) : ''; // phpcs:ignore ?>`; | |
| 743 | 422 | if (docsTitle) { |
| 744 | 423 | title = docsTitle; |
| 745 | 424 | } |
| 746 | 425 | |
| 747 | - const promtTitle = `<?php echo isset( $_GET['post'] ) ? esc_html( get_the_title( absint( wp_unslash( $_GET['post'] ) ) ) ) : '{Documentation Title}'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only post id from URL. ?>`; | |
| 426 | + const promtTitle = `<?php echo isset($_GET['post']) ? esc_html(get_the_title($_GET['post'])) : '{Documentation Title}'; // phpcs:ignore ?>`; | |
| 748 | 427 | const titlePlaceholder = "<?php echo esc_attr__( 'Enter a descriptive title for your documentation.', 'betterdocs' ); ?>"; |
| 749 | 428 | const keywords = "<?php echo esc_attr( '{Documentation Keywords}' ); ?>"; |
| 750 | 429 | const keywordsPlaceholder = "<?php echo esc_attr__( 'Add keywords to generate precise & relevant documentation (comma-separated).', 'betterdocs' ); ?>"; |
| 751 | 430 | const language = "<?php echo esc_attr( 'English' ); ?>"; |
| @@ -760,18 +439,18 @@ | ||
| 760 | 439 | const checkboxLabel = "<?php echo esc_html__( 'Overwrite your existing Doc:', 'betterdocs' ); ?>"; |
| 761 | 440 | const nonce = "<?php echo esc_attr( wp_create_nonce( 'generate_openai_content_nonce' ) ); ?>"; |
| 762 | 441 | |
| 763 | 442 | <?php |
| 764 | - $is_valid = $this->get_api_key(); | |
| 765 | - $disable_field = 'disabled-input-field'; | |
| 766 | - if ( $this->get_api_key() ) { | |
| 767 | - $disable_field = ''; | |
| 768 | - } | |
| 769 | - ?> | |
| 443 | + $is_valid = $this->get_api_key(); | |
| 444 | + $disable_field = 'disabled-input-field'; | |
| 445 | + if ( $this->get_api_key() ) { | |
| 446 | + $disable_field = ''; | |
| 447 | + } | |
| 448 | + ?> | |
| 770 | 449 | |
| 771 | 450 | let hiddenClass = 'hidden'; |
| 772 | 451 | let newDocPageAtt = 'data-new-doc-page="true"'; |
| 773 | - const isPostContent = `<?php echo isset( $_GET['post'] ) ? esc_html( get_the_content( absint( wp_unslash( $_GET['post'] ) ) ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only post id from URL. ?>`; | |
| 452 | + const isPostContent = `<?php echo isset($_GET['post']) ? esc_html(get_the_content($_GET['post'])) : ''; // phpcs:ignore ?>`; | |
| 774 | 453 | |
| 775 | 454 | if (isPostContent !== '') { |
| 776 | 455 | hiddenClass = ''; |
| 777 | 456 | newDocPageAtt = ''; |
| @@ -781,11 +460,20 @@ | ||
| 781 | 460 | // 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.`; |
| 782 | 461 | |
| 783 | 462 | // 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.`; |
| 784 | 463 | |
| 785 | - const prompt = `Write documentation for '${promtTitle}'. Cover these topics in depth: ${keywords}. Use headings, paragraphs, lists, links, code blocks, and tables wherever they help the reader. Follow the output-format rules in the system instructions.`; | |
| 464 | + 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.`; | |
| 786 | 465 | |
| 787 | - const aiIcon = `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="-1.5 -1.5 23 23" fill="none"><path d="m9.895 18.861-4.286.371.371-4.286 8.915-8.857a1.429 1.429 0 0 1 2.043 0l1.814 1.829a1.431 1.431 0 0 1 0 2.029l-8.857 8.914ZM1.204 5.674c-.501-.088-.501-.807 0-.894a4.537 4.537 0 0 0 3.654-3.5l.03-.139c.109-.494.814-.499.929-.003l.036.16a4.561 4.561 0 0 0 3.663 3.48c.504.086.504.81 0 .899a4.561 4.561 0 0 0-3.664 3.479l-.037.161c-.112.494-.818.491-.926-.004l-.029-.139a4.537 4.537 0 0 0-3.658-3.5h.003Z" stroke="#fff" stroke-width="1.429" stroke-linecap="round" stroke-linejoin="round"/></svg>`; | |
| 466 | + const aiIcon = `<svg xmlns="http://www.w3.org/2000/svg" width="21" height="20" viewBox="0 0 21 20" fill="none"> | |
| 467 | + <g clip-path="url(#clip0_2460_1729)"> | |
| 468 | + <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"/> | |
| 469 | + </g> | |
| 470 | + <defs> | |
| 471 | + <clipPath id="clip0_2460_1729"> | |
| 472 | + <rect width="20" height="20" fill="white" transform="translate(0.5)"/> | |
| 473 | + </clipPath> | |
| 474 | + </defs> | |
| 475 | + </svg>`; | |
| 788 | 476 | |
| 789 | 477 | |
| 790 | 478 | |
| 791 | 479 | return ` |
| @@ -792,24 +480,33 @@ | ||
| 792 | 480 | <div class="betterdocs-ai-autowrite-form-container hidden" ${newDocPageAtt}> |
| 793 | 481 | <div class="betterdocs-ai-autowrite-form-content"> |
| 794 | 482 | <div class="betterdocs-ai-autowrite-top-part"> |
| 795 | 483 | <div class="autowrite-heading"> |
| 796 | - <span class="autowrite-icon" style="--bd-icon-tint: rgb(127, 86, 217); --bd-icon-tint-rgb: 127, 86, 217; display:inline-flex; align-items:center; justify-content:center; width:34px; height:34px; flex-shrink:0; border-radius:9px; color:rgb(127, 86, 217); background:rgba(127, 86, 217, 0.2);"> | |
| 797 | - <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="none" viewBox="-1.5 -1.5 23 23"><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.429" d="m9.895 18.861-4.286.371.371-4.286 8.915-8.857a1.43 1.43 0 0 1 2.043 0l1.814 1.829a1.43 1.43 0 0 1 0 2.029zM1.204 5.674c-.501-.088-.501-.807 0-.894a4.54 4.54 0 0 0 3.654-3.5l.03-.139c.109-.494.814-.499.929-.003l.036.16a4.56 4.56 0 0 0 3.663 3.48c.504.086.504.81 0 .899a4.56 4.56 0 0 0-3.664 3.479l-.037.161c-.112.494-.818.491-.926-.004l-.029-.139a4.54 4.54 0 0 0-3.658-3.5h.003Z"></path></svg> | |
| 484 | + <span class="autowrite-icon"> | |
| 485 | + <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" fill="none"> | |
| 486 | + <g clip-path="url(#clip0_2453_20882)"> | |
| 487 | + <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"/> | |
| 488 | + </g> | |
| 489 | + <defs> | |
| 490 | + <clipPath id="clip0_2453_20882"> | |
| 491 | + <rect width="32" height="32" fill="white"/> | |
| 492 | + </clipPath> | |
| 493 | + </defs> | |
| 494 | + </svg> | |
| 798 | 495 | </span> |
| 799 | - <h1 style="margin:0; font-family:'IBM Plex Sans',sans-serif; font-size:18px; font-weight:600; line-height:1.3; color:#101828;"><?php echo esc_html__( 'Write Documentation with BetterDocs AI', 'betterdocs' ); ?></h1> | |
| 496 | + <h1><?php echo esc_html__( 'Write Documentation with BetterDocs AI', 'betterdocs' ); ?></h1> | |
| 800 | 497 | |
| 801 | 498 | </div> |
| 802 | 499 | <div class="autowrite-subheadding"> |
| 803 | - <p style="margin:8px 0 0; font-family:'IBM Plex Sans',sans-serif; font-size:14px; font-weight:400; line-height:1.4; color:#667085;"><?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> | |
| 500 | + <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> | |
| 804 | 501 | </div> |
| 805 | 502 | |
| 806 | - <?php if ( empty( $this->get_api_key() ) ): ?> | |
| 503 | + <?php if ( empty( $this->get_api_key() ) ) : ?> | |
| 807 | 504 | <div id="betterdocs-ai-message"> |
| 808 | 505 | <div class="warning-message"> |
| 809 | 506 | <span class="dashicons dashicons-warning"></span> |
| 810 | 507 | <div> |
| 811 | - <?php echo wp_kses_post( 'Please Insert your <a target="_blank" href="' . esc_url( admin_url( 'admin.php?page=betterdocs-settings#betterdocs-ai' ) ) . '">OpenAI API Key</a> to use this Write with AI feature.', 'betterdocs' ); ?> | |
| 508 | + <?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' ); ?> | |
| 812 | 509 | </div> |
| 813 | 510 | </div> |
| 814 | 511 | |
| 815 | 512 | </div> |
| @@ -860,9 +557,9 @@ | ||
| 860 | 557 | </div> |
| 861 | 558 | </form> |
| 862 | 559 | </div> |
| 863 | 560 | |
| 864 | - <div class="bd-close-button" onclick="closeWriteWithAIForm('afterClosedClicked')"><svg width="15" height="14" viewBox="0 0 15 14" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M1.69687 14L0.296875 12.6L5.89687 7L0.296875 1.4L1.69687 0L7.29688 5.6L12.8969 0L14.2969 1.4L8.69688 7L14.2969 12.6L12.8969 14L7.29688 8.4L1.69687 14Z" fill="currentColor"/></svg></div> | |
| 561 | + <div class="bd-close-button" onclick="closeWriteWithAIForm('afterClosedClicked')">✕</div> | |
| 865 | 562 | </div> |
| 866 | 563 | |
| 867 | 564 | <div id="betterdocs-ai-autowrite-form-container-overlay" class="hidden"></div> |
| 868 | 565 | `; |
| @@ -952,21 +649,18 @@ | ||
| 952 | 649 | |
| 953 | 650 | .autowrite-icon { |
| 954 | 651 | display: flex; |
| 955 | 652 | align-items: center; |
| 956 | - width: 32px; | |
| 957 | - height: 32px; | |
| 958 | - flex-shrink: 0; | |
| 959 | - /* Mint chip + green sparkle — matches the FAQ/Glossary modal | |
| 960 | - header icons (rgba(#00B884, 0.12)). */ | |
| 961 | - background: rgba(0, 184, 132, 0.12); | |
| 653 | + width: 55px; | |
| 654 | + height: 55px; | |
| 655 | + background: #FFE4E8; | |
| 962 | 656 | justify-content: center; |
| 963 | - border-radius: 9px; | |
| 657 | + border-radius: 50px; | |
| 964 | 658 | } |
| 965 | 659 | |
| 966 | 660 | .autowrite-icon svg { |
| 967 | - width: 17px; | |
| 968 | - height: 17px; | |
| 661 | + width: 28px; | |
| 662 | + height: 28px; | |
| 969 | 663 | } |
| 970 | 664 | |
| 971 | 665 | .autowrite-heading { |
| 972 | 666 | display: flex; |
| @@ -971,19 +665,17 @@ | ||
| 971 | 665 | .autowrite-heading { |
| 972 | 666 | display: flex; |
| 973 | 667 | gap: 10px; |
| 974 | 668 | align-items: center; |
| 975 | - /* Keep the title clear of the in-header close button. */ | |
| 976 | - padding-right: 64px; | |
| 977 | 669 | } |
| 978 | 670 | |
| 979 | 671 | .autowrite-heading h1 { |
| 980 | - color: #101828; | |
| 672 | + color: #1D2939; | |
| 981 | 673 | font-family: 'IBM Plex Sans', sans-serif; |
| 982 | - font-size: 18px; | |
| 674 | + font-size: 24px; | |
| 983 | 675 | font-style: normal; |
| 984 | - font-weight: 600; | |
| 985 | - line-height: 1.3; | |
| 676 | + font-weight: 500; | |
| 677 | + line-height: normal; | |
| 986 | 678 | margin: 0; |
| 987 | 679 | } |
| 988 | 680 | |
| 989 | 681 | .autowrite-heading p, |
| @@ -993,10 +685,9 @@ | ||
| 993 | 685 | } |
| 994 | 686 | |
| 995 | 687 | .autowrite-subheadding p { |
| 996 | 688 | font-family: 'IBM Plex Sans', sans-serif; |
| 997 | - font-size: 13px; | |
| 998 | - color: #667085; | |
| 689 | + font-size: 14px; | |
| 999 | 690 | } |
| 1000 | 691 | |
| 1001 | 692 | .prompt-field .input-description { |
| 1002 | 693 | margin: 0 !important; |
| @@ -1041,9 +732,9 @@ | ||
| 1041 | 732 | z-index: 100001; |
| 1042 | 733 | width: 650px; |
| 1043 | 734 | margin: 0 auto; |
| 1044 | 735 | background-color: #fff; |
| 1045 | - border-radius: 20px; | |
| 736 | + border-radius: 5px; | |
| 1046 | 737 | font-family: 'IBM Plex Sans', sans-serif; |
| 1047 | 738 | /* max-height: 600px; */ |
| 1048 | 739 | max-height: 750px; |
| 1049 | 740 | max-width: 100%; |
| @@ -1050,10 +741,11 @@ | ||
| 1050 | 741 | } |
| 1051 | 742 | |
| 1052 | 743 | .betterdocs-ai-autowrite-form-content { |
| 1053 | 744 | background-color: #fff; |
| 1054 | - padding: 0; | |
| 1055 | - border-radius: 20px; | |
| 745 | + padding: 20px 0px; | |
| 746 | + border-radius: 5px; | |
| 747 | + padding-bottom: 0; | |
| 1056 | 748 | overflow: auto; |
| 1057 | 749 | /* max-height: 700px; */ |
| 1058 | 750 | height: 100%; |
| 1059 | 751 | |
| @@ -1059,13 +751,12 @@ | ||
| 1059 | 751 | |
| 1060 | 752 | } |
| 1061 | 753 | |
| 1062 | 754 | .betterdocs-ai-autowrite-top-part { |
| 1063 | - /* Soft-gray header bar — matches the FAQ/Glossary/Edit-with-AI modals. */ | |
| 1064 | - background: #f9fafb; | |
| 1065 | - border-bottom: 1px solid #f2f4f7; | |
| 1066 | - border-radius: 20px 20px 0 0; | |
| 1067 | - padding: 24px 40px 20px; | |
| 755 | + /* padding-bottom: 20px; */ | |
| 756 | + border-bottom: 1px solid #ddd; | |
| 757 | + /* margin-bottom: 20px; */ | |
| 758 | + padding: 0 40px; | |
| 1068 | 759 | } |
| 1069 | 760 | |
| 1070 | 761 | #betterdocs-ai-form { |
| 1071 | 762 | display: flex; |
| @@ -1113,13 +804,12 @@ | ||
| 1113 | 804 | justify-content: space-between; |
| 1114 | 805 | } |
| 1115 | 806 | |
| 1116 | 807 | #betterdocs-ai-form label { |
| 1117 | - font-size: 14px; | |
| 1118 | - font-weight: 500; | |
| 1119 | - color: #667085; | |
| 808 | + font-weight: 600; | |
| 1120 | 809 | margin-bottom: 5px; |
| 1121 | 810 | display: block; |
| 811 | + font-size: 14px; | |
| 1122 | 812 | line-height: 20px; |
| 1123 | 813 | } |
| 1124 | 814 | |
| 1125 | 815 | select#bd-autowrtite-content-section, |
| @@ -1126,76 +816,59 @@ | ||
| 1126 | 816 | #bd-autowrtite-content-paragraph, |
| 1127 | 817 | #betterdocs-ai-language { |
| 1128 | 818 | width: 150px !important; |
| 1129 | 819 | height: 40px; |
| 1130 | - border: 1px solid #f2f4f7; | |
| 1131 | - background: #f2f4f7; | |
| 1132 | - color: #101828; | |
| 1133 | - font-size: 14px; | |
| 1134 | - border-radius: 5px; | |
| 1135 | - padding: 5px 12px; | |
| 1136 | - outline: none; | |
| 1137 | - box-shadow: none; | |
| 1138 | 820 | } |
| 1139 | 821 | |
| 1140 | 822 | #betterdocs-ai-form input[type="text"], |
| 1141 | 823 | #betterdocs-ai-form textarea { |
| 1142 | 824 | width: 100%; |
| 825 | + padding: 8px; | |
| 1143 | 826 | box-sizing: border-box; |
| 1144 | - border: 1px solid #f2f4f7; | |
| 1145 | - background: #fff; | |
| 1146 | - border-radius: 5px; | |
| 1147 | - color: #101828; | |
| 1148 | - font-size: 14px; | |
| 827 | + border: 1px solid #D0D5DD; | |
| 828 | + border-radius: 4px; | |
| 829 | + color: #667085; | |
| 1149 | 830 | font-weight: 400; |
| 1150 | - outline: none; | |
| 1151 | - box-shadow: none; | |
| 1152 | 831 | } |
| 1153 | 832 | |
| 1154 | - #betterdocs-ai-form input[type="text"] { | |
| 1155 | - height: 40px; | |
| 1156 | - padding: 5px 15px; | |
| 833 | + /* Override autofill text color */ | |
| 834 | + #betterdocs-ai-form input:-webkit-autofill { | |
| 835 | + -webkit-text-fill-color: #667085 !important; | |
| 1157 | 836 | } |
| 1158 | 837 | |
| 1159 | - #betterdocs-ai-form textarea { | |
| 1160 | - padding: 10px 15px; | |
| 1161 | - min-height: 100px; | |
| 838 | + #betterdocs-ai-form input::placeholder { | |
| 839 | + color: #acb2bf; | |
| 1162 | 840 | } |
| 1163 | 841 | |
| 1164 | - /* Override autofill text color */ | |
| 1165 | - #betterdocs-ai-form input:-webkit-autofill { | |
| 1166 | - -webkit-text-fill-color: #101828 !important; | |
| 842 | + #betterdocs-ai-form textarea { | |
| 843 | + color: #667085; | |
| 1167 | 844 | } |
| 1168 | 845 | |
| 1169 | - #betterdocs-ai-form input::placeholder, | |
| 1170 | - #betterdocs-ai-form textarea::placeholder { | |
| 1171 | - color: #667085; | |
| 846 | + #betterdocs-ai-form input:focus, | |
| 847 | + #betterdocs-ai-form textarea:focus { | |
| 848 | + box-shadow: none; | |
| 1172 | 849 | } |
| 1173 | 850 | |
| 1174 | - #betterdocs-ai-form input:focus, | |
| 1175 | 851 | #betterdocs-ai-form textarea:focus { |
| 1176 | - border-color: #00b884; | |
| 1177 | - box-shadow: 0 0 0 3px rgba(0, 184, 132, 0.15); | |
| 1178 | - outline: none; | |
| 852 | + color: inherit; | |
| 853 | + box-shadow: none; | |
| 854 | + | |
| 1179 | 855 | } |
| 1180 | 856 | |
| 1181 | 857 | |
| 1182 | - /* Footer bar — soft gray surface; Cancel pinned left, Generate right. */ | |
| 1183 | 858 | .generate-button-container { |
| 1184 | 859 | position: sticky; |
| 1185 | 860 | bottom: 0px; |
| 1186 | 861 | width: 100%; |
| 1187 | - box-sizing: border-box; | |
| 1188 | 862 | margin-left: 0; |
| 1189 | 863 | z-index: 999999 !important; |
| 1190 | - padding: 16px 22px; | |
| 864 | + padding: 20px 0; | |
| 1191 | 865 | display: flex; |
| 1192 | - align-items: center; | |
| 1193 | - justify-content: flex-end; | |
| 1194 | - border-top: 1px solid #f2f4f7; | |
| 1195 | - background: #f9fafb; | |
| 1196 | - border-bottom-right-radius: 20px; | |
| 1197 | - border-bottom-left-radius: 20px; | |
| 866 | + justify-content: end; | |
| 867 | + border-top: 1px solid #ddd; | |
| 868 | + background: white; | |
| 869 | + border-bottom-right-radius: 5px; | |
| 870 | + border-bottom-left-radius: 5px; | |
| 1198 | 871 | } |
| 1199 | 872 | |
| 1200 | 873 | .generate-button-container button { |
| 1201 | 874 | display: flex; |
| @@ -1202,35 +875,11 @@ | ||
| 1202 | 875 | gap: 8px; |
| 1203 | 876 | align-items: center; |
| 1204 | 877 | justify-content: center; |
| 1205 | 878 | opacity: 1 !important; |
| 879 | + margin-right: 40px; | |
| 1206 | 880 | } |
| 1207 | 881 | |
| 1208 | - /* Cancel — white pill with neutral border. Stays clickable even when | |
| 1209 | - the form is disabled (missing API key). */ | |
| 1210 | - .generate-button-container .bd-ai-cancel-btn { | |
| 1211 | - pointer-events: auto; | |
| 1212 | - display: inline-flex; | |
| 1213 | - min-height: 36px; | |
| 1214 | - padding: 7px 16px; | |
| 1215 | - justify-content: center; | |
| 1216 | - align-items: center; | |
| 1217 | - background-color: #ffffff; | |
| 1218 | - color: #344054; | |
| 1219 | - border: 1px solid #d0d5dd; | |
| 1220 | - border-radius: 9px; | |
| 1221 | - font-size: 16px; | |
| 1222 | - cursor: pointer; | |
| 1223 | - text-decoration: none; | |
| 1224 | - transition: all 0.3s ease 0s; | |
| 1225 | - box-sizing: border-box; | |
| 1226 | - } | |
| 1227 | - | |
| 1228 | - .generate-button-container .bd-ai-cancel-btn:hover { | |
| 1229 | - background-color: #f2f4f7; | |
| 1230 | - color: #101828; | |
| 1231 | - } | |
| 1232 | - | |
| 1233 | 882 | .input-description { |
| 1234 | 883 | font-size: 14px; |
| 1235 | 884 | color: #667085; |
| 1236 | 885 | } |
| @@ -1351,67 +1000,42 @@ | ||
| 1351 | 1000 | |
| 1352 | 1001 | .generate-btn, |
| 1353 | 1002 | .prompt-btn, |
| 1354 | 1003 | .keep-btn { |
| 1355 | - display: inline-flex; | |
| 1356 | - min-height: 0; | |
| 1357 | - padding: 9px 20px; | |
| 1358 | - gap: 8px; | |
| 1359 | - justify-content: center; | |
| 1360 | - align-items: center; | |
| 1361 | 1004 | background-color: #00b884; |
| 1362 | 1005 | color: #fff; |
| 1363 | - border: 1px solid transparent; | |
| 1364 | - border-radius: 10px; | |
| 1365 | - font-size: 13px; | |
| 1366 | - font-weight: 600; | |
| 1006 | + border: none; | |
| 1007 | + padding: 10px 15px; | |
| 1008 | + text-align: center; | |
| 1009 | + text-decoration: none; | |
| 1010 | + display: inline-block; | |
| 1011 | + font-size: 16px; | |
| 1367 | 1012 | cursor: pointer; |
| 1368 | - text-decoration: none; | |
| 1369 | - box-shadow: none; | |
| 1370 | - transition: background 0.15s, transform 0.05s; | |
| 1371 | - box-sizing: border-box; | |
| 1013 | + border-radius: 4px; | |
| 1372 | 1014 | } |
| 1373 | 1015 | |
| 1374 | - .generate-btn:hover, | |
| 1375 | - .prompt-btn:hover, | |
| 1376 | - .keep-btn:hover { | |
| 1377 | - background-color: #00956b; | |
| 1378 | - } | |
| 1379 | - | |
| 1380 | 1016 | .generate-btn[disabled] { |
| 1381 | 1017 | cursor: unset; |
| 1382 | 1018 | } |
| 1383 | 1019 | |
| 1384 | - /* Close — 44px gray rounded square INSIDE the modal, top-right of | |
| 1385 | - the header, aligned with the header icon. */ | |
| 1386 | 1020 | .bd-close-button { |
| 1387 | 1021 | cursor: pointer; |
| 1022 | + font-size: 18px; | |
| 1023 | + font-weight: bold; | |
| 1024 | + float: right; | |
| 1388 | 1025 | position: absolute; |
| 1389 | - top: 24px; | |
| 1390 | - right: 24px; | |
| 1391 | - z-index: 2; | |
| 1392 | - width: 32px; | |
| 1393 | - height: 32px; | |
| 1026 | + top: -16px; | |
| 1027 | + border-radius: 50%; | |
| 1028 | + background: #ffffff; | |
| 1029 | + width: 40px; | |
| 1030 | + height: 40px; | |
| 1394 | 1031 | display: flex; |
| 1395 | 1032 | align-items: center; |
| 1396 | 1033 | justify-content: center; |
| 1397 | - border-radius: 9px; | |
| 1398 | - background: #f2f4f7; | |
| 1399 | - box-shadow: none; | |
| 1400 | - color: #667085; | |
| 1401 | - transition: all 0.2s ease-in-out; | |
| 1034 | + color: #281617; | |
| 1035 | + right: -42px; | |
| 1402 | 1036 | } |
| 1403 | 1037 | |
| 1404 | - .bd-close-button svg { | |
| 1405 | - width: 16px; | |
| 1406 | - height: 16px; | |
| 1407 | - } | |
| 1408 | - | |
| 1409 | - .bd-close-button:hover { | |
| 1410 | - background: #eaecf0; | |
| 1411 | - color: #101828; | |
| 1412 | - } | |
| 1413 | - | |
| 1414 | 1038 | div#betterdocs-ai-error-message, |
| 1415 | 1039 | #betterdocs-ai-message { |
| 1416 | 1040 | font-size: 14px; |
| 1417 | 1041 | font-family: 'IBM Plex Sans', sans-serif; |
| @@ -1494,6 +1118,6 @@ | ||
| 1494 | 1118 | |
| 1495 | 1119 | } |
| 1496 | 1120 | </style> |
| 1497 | 1121 | <?php |
| 1498 | - } | |
| 1499 | - } | |
| 1122 | + } | |
| 1123 | +} | |