PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 2.2.0
MxChat – AI Chatbot & Content Generation for WordPress v2.2.0
3.2.21 3.2.20 3.2.19 3.2.18 3.2.17 3.2.16 3.2.15 3.2.14 3.2.12 3.2.13 3.2.11 3.2.10 3.2.9 3.2.8 3.2.7 3.2.6 3.2.5 3.2.4 3.2.3 3.2.2 3.2.1 2.0.3 2.0.4 2.0.5 2.0.6 All 152 releases
mxchat-basic / includes / class-mxchat-admin.php

class-mxchat-admin.php in MxChat – AI Chatbot & Content Generation for WordPress 2.2.0, at includes/class-mxchat-admin.php

8,659 lines 372.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 if (!defined('ABSPATH')) {
3 exit; // Exit if accessed directly
4 }
5
6 class MxChat_Admin {
7 private $options;
8 private $chat_count;
9 private $is_activated;
10
11 public function __construct() {
12 $this->options = get_option('mxchat_options');
13 $this->chat_count = get_option('mxchat_chat_count', 0);
14 $this->is_activated = $this->is_license_active();
15
16 // Initialize default options if they are not set
17 if (!$this->options) {
18 $this->initialize_default_options();
19 }
20
21 // Add admin menu and initialize settings
22 add_action('admin_menu', array($this, 'mxchat_add_plugin_page'));
23 add_action('admin_init', array($this, 'mxchat_page_init'));
24 add_action('admin_init', array($this, 'mxchat_prompts_page_init'));
25 add_action('admin_enqueue_scripts', array($this, 'mxchat_enqueue_admin_assets'));
26 add_action('wp_ajax_mxchat_delete_chat_history', array($this, 'mxchat_delete_chat_history'));
27 add_action('admin_post_mxchat_submit_content', array($this, 'mxchat_handle_content_submission'));
28 add_action('admin_post_mxchat_delete_prompt', array($this, 'mxchat_handle_delete_prompt'));
29 add_action('wp_ajax_mxchat_fetch_chat_history', array($this, 'mxchat_fetch_chat_history'));
30 add_action('wp_ajax_nopriv_mxchat_fetch_chat_history', array($this, 'mxchat_fetch_chat_history'));
31 add_action('admin_post_mxchat_submit_sitemap', array($this, 'mxchat_handle_sitemap_submission'));
32 add_action('wp_footer', array($this, 'mxchat_append_chatbot_to_body'));
33 add_action('admin_head-mxchat-prompts', array($this, 'mxchat_enqueue_admin_assets'));
34 add_action('admin_head-toplevel_page_mxchat-max', array($this, 'mxchat_enqueue_admin_assets'));
35 add_action('wp_ajax_mxchat_activate_license', array($this, 'mxchat_handle_activate_license'));
36 add_action('admin_notices', array($this, 'mxchat_display_admin_notice'));
37 add_action('wp_ajax_mxchat_save_inline_prompt', array($this, 'mxchat_save_inline_prompt'));
38 add_action('admin_post_mxchat_delete_all_prompts', array($this, 'mxchat_handle_delete_all_prompts'));
39 add_action('admin_post_mxchat_add_intent', array($this, 'mxchat_handle_add_intent'));
40 add_action('admin_post_mxchat_delete_intent', array($this, 'mxchat_handle_delete_intent'));
41 add_action('wp_ajax_mxchat_toggle_action', array($this, 'mxchat_toggle_action'));
42 add_action('wp_ajax_mxchat_update_intent_threshold', array($this, 'mxchat_update_intent_threshold'));
43 add_action('admin_post_mxchat_stop_processing', array($this, 'mxchat_stop_processing'));
44 add_action('admin_post_mxchat_edit_intent', array($this, 'mxchat_handle_edit_intent'));
45 add_action('save_post', array($this, 'handle_post_update'), 10, 3);
46 add_action('post_updated', array($this, 'handle_post_update'), 10, 3);
47 add_action('wp_ajax_mxchat_save_setting', array($this, 'mxchat_save_setting_callback'));
48 add_action('wp_trash_post', array($this, 'mxchat_handle_post_delete'));
49 add_action('before_delete_post', array($this, 'mxchat_handle_post_delete'));
50 add_action('wp_ajax_mxchat_export_transcripts', array($this, 'export_chat_transcripts'));
51 add_action('wp_ajax_mxchat_save_prompts_setting', array($this, 'mxchat_save_prompts_setting_callback'));
52 add_action('admin_init', array($this, 'mxchat_transcripts_page_init'));
53 add_action('wp_ajax_mxchat_check_license_status', array($this, 'mxchat_check_license_status'));
54 add_action('wp_ajax_mxchat_get_content_list', array($this, 'ajax_mxchat_get_content_list'));
55 add_action('wp_ajax_mxchat_process_selected_content', array($this, 'ajax_mxchat_process_selected_content'));
56
57 if (isset($this->options['enable_woocommerce_integration']) &&
58 ($this->options['enable_woocommerce_integration'] === '1' ||
59 $this->options['enable_woocommerce_integration'] === 'on')) {
60
61 //error_log('MxChat Admin: WooCommerce integration is enabled, adding hooks');
62 add_action('wp_insert_post', array($this, 'mxchat_handle_product_change'), 10, 3);
63 add_action('wp_trash_post', array($this, 'mxchat_handle_product_delete'));
64 add_action('before_delete_post', array($this, 'mxchat_handle_product_delete'));
65 }
66
67 add_action('wp_ajax_mxchat_get_status_updates', array($this, 'ajax_get_status_updates'));
68 add_action('admin_notices', array($this, 'display_admin_notices'));
69
70 }
71
72 private function is_license_active() {
73 // Get the raw value without translation
74 $license_status = get_option('mxchat_license_status', 'inactive');
75
76 // Check against multiple possible values, bypassing translation issues
77 return ($license_status === 'active' || $license_status === esc_html__('active', 'mxchat'));
78 }
79
80
81 // Initialize default options
82 private function initialize_default_options() {
83 $default_options = array(
84 'api_key' => '',
85 'xai_api_key' => '',
86 'claude_api_key' => '',
87 'deepseek_api_key' => '',
88 'voyage_api_key' => '',
89 'gemini_api_key' => '',
90 'embedding_model' => 'text-embedding-ada-002',
91 'system_prompt_instructions' => esc_html__('[EXAMPLE INSTRUCTIONS] You are an AI Chatbot assistant for this website. The primary subject you should focus on is [insert proper subject here]. Your main goal is to assist visitors with questions related to this specific topic. Here are some key things to keep in mind:
92 - Your name is [Chatbot Name].
93 - Stay focused on topics related to [insert proper subject here]. If a visitor asks about an unrelated topic, politely redirect the conversation to how you can assist them with this subject.
94 - Do not make up links to pages. Only use specific links you see in your knowledgebase from [insert your domain]. Do not make assumptions or make up URLs.
95 - Keep your responses short, concise, and to the point. Provide clear and direct answers suitable for a chatbot interaction.
96 - Do not include headings in your response and you must hyperlink all URLs.
97 - Provide answers based on the knowledge available to you. If you do not have an answer to a specific question, let the visitor know that you don’t have the information.', 'mxchat'),
98 'model' => esc_html__('gpt-4o', 'mxchat'),
99 'rate_limit_logged_out' => esc_html__('100', 'mxchat'),
100 'role_rate_limits' => array(),
101 'rate_limit_message' => esc_html__('Rate limit exceeded. Please try again later.', 'mxchat'),
102 'enable_email_block' => '',
103 'email_blocker_header_content' => __("<h2>Welcome to Our Chat!</h2>\n<p>Let's get started. Enter your email to begin chatting with us.</p>", 'mxchat'),
104 'email_blocker_button_text' => esc_html__('Start Chat', 'mxchat'),
105 'top_bar_title' => esc_html__('MxChat', 'mxchat'),
106 'intro_message' => __('Hello! How can I assist you today?', 'mxchat'),
107 'ai_agent_text' => esc_html__('AI Agent', 'mxchat'),
108 'input_copy' => esc_html__('How can I assist?', 'mxchat'),
109 'append_to_body' => esc_html__('off', 'mxchat'),
110 'close_button_color' => esc_html__('#fff', 'mxchat'),
111 'chatbot_bg_color' => esc_html__('#fff', 'mxchat'),
112 'user_message_bg_color' => esc_html__('#fff', 'mxchat'),
113 'user_message_font_color' => esc_html__('#212121', 'mxchat'),
114 'bot_message_bg_color' => esc_html__('#212121', 'mxchat'),
115 'bot_message_font_color' => esc_html__('#fff', 'mxchat'),
116 'top_bar_bg_color' => esc_html__('#212121', 'mxchat'),
117 'send_button_font_color' => esc_html__('#212121', 'mxchat'),
118 'chat_input_font_color' => esc_html__('#212121', 'mxchat'),
119 'chatbot_background_color' => esc_html__('#212121', 'mxchat'),
120 'icon_color' => esc_html__('#fff', 'mxchat'),
121 'enable_woocommerce_integration' => esc_html__('0', 'mxchat'),
122 'link_target_toggle' => esc_html__('off', 'mxchat'),
123 'pre_chat_message' => esc_html__('Hey there! Ask me anything!', 'mxchat'),
124
125 // New fields for Loops Integration
126 'loops_api_key' => '',
127 'loops_mailing_list' => '',
128 'triggered_phrase_response' => __('Would you like to join our mailing list? Please provide your email below.', 'mxchat'),
129 'email_capture_response' => __('Thank you for providing your email! You\'ve been added to our list.', 'mxchat'),
130 'popular_question_1' => '',
131 'popular_question_2' => '',
132 'popular_question_3' => '',
133 'pdf_intent_trigger_text' => __("Please provide the URL to the PDF you'd like to discuss.", 'mxchat'),
134 'pdf_intent_success_text' => __("I've processed the PDF. What questions do you have about it?", 'mxchat'),
135 'pdf_intent_error_text' => __("Sorry, I couldn't process the PDF. Please ensure it's a valid file.", 'mxchat'),
136 'pdf_max_pages' => 69,
137 'show_pdf_upload_button' => 'on',
138 'show_word_upload_button' => 'on',
139
140 // Live Agent Integration
141 'live_agent_webhook_url' => '',
142 'live_agent_secret_key' => '',
143 'live_agent_bot_token' => '',
144 'live_agent_message_bg_color' => esc_html__('#ffffff', 'mxchat'),
145 'live_agent_message_font_color' => esc_html__('#333333', 'mxchat'),
146 'chat_toolbar_toggle' => esc_html__('off', 'mxchat'),
147 'mode_indicator_bg_color' => esc_html__('#767676', 'mxchat'),
148 'mode_indicator_font_color' => esc_html__('#ffffff', 'mxchat'),
149 'toolbar_icon_color' => esc_html__('#212121', 'mxchat'),
150 );
151
152
153 // Merge existing options with defaults
154 $existing_options = get_option('mxchat_options', array());
155 $merged_options = wp_parse_args($existing_options, $default_options);
156
157 // Update the options if they have changed
158 if ($existing_options !== $merged_options) {
159 update_option('mxchat_options', $merged_options);
160 }
161
162 // Add default limits for each role
163 $roles = wp_roles()->get_names();
164 foreach ($roles as $role_id => $role_name) {
165 $default_options['role_rate_limits'][$role_id] = esc_html__('100', 'mxchat');
166 }
167
168 return $default_options;
169
170 // Update the $this->options property
171 $this->options = $merged_options;
172 }
173
174
175
176 public function mxchat_add_plugin_page() {
177 // Main menu page
178 add_menu_page(
179 esc_html__('MxChat Settings', 'mxchat'),
180 esc_html__('MxChat', 'mxchat'),
181 'manage_options',
182 'mxchat-max',
183 array($this, 'mxchat_create_admin_page'),
184 'dashicons-testimonial',
185 6
186 );
187
188 // Submenu page for Knowledge
189 add_submenu_page(
190 'mxchat-max',
191 esc_html__('Prompts', 'mxchat'),
192 esc_html__('Knowledge', 'mxchat'),
193 'manage_options',
194 'mxchat-prompts',
195 array($this, 'mxchat_create_prompts_page')
196 );
197
198 add_submenu_page(
199 'mxchat-max',
200 esc_html__('Chat Transcripts', 'mxchat'),
201 esc_html__('Transcripts', 'mxchat'),
202 'manage_options',
203 'mxchat-transcripts',
204 array($this, 'mxchat_create_transcripts_page')
205 );
206
207 add_submenu_page(
208 'mxchat-max',
209 esc_html__('MxChat Actions', 'mxchat'),
210 esc_html__('Actions', 'mxchat'),
211 'manage_options',
212 'mxchat-actions',
213 array($this, 'mxchat_actions_page_html')
214 );
215
216 add_submenu_page(
217 'mxchat-max',
218 esc_html__('Add Ons', 'mxchat'),
219 esc_html__('Add Ons', 'mxchat'),
220 'manage_options',
221 'mxchat-addons',
222 array($this, 'mxchat_create_addons_page')
223 );
224
225 // Submenu page for Activation Key
226 add_submenu_page(
227 'mxchat-max',
228 esc_html__('Pro Upgrade', 'mxchat'),
229 esc_html__('Pro Upgrade', 'mxchat'),
230 'manage_options',
231 'mxchat-activation',
232 array($this, 'mxchat_create_activation_page')
233 );
234 }
235
236 public function mxchat_create_addons_page() {
237 require_once plugin_dir_path(__FILE__) . 'class-mxchat-addons.php';
238 $addons_page = new MxChat_Addons();
239 $addons_page->render_page();
240 }
241
242 public function mxchat_save_setting_callback() {
243 check_ajax_referer('mxchat_save_setting_nonce');
244 if (!current_user_can('manage_options')) {
245 ('MXChat Save: Unauthorized access attempt');
246 wp_send_json_error(['message' => esc_html__('Unauthorized', 'mxchat')]);
247 }
248
249 $name = isset($_POST['name']) ? $_POST['name'] : '';
250 // Strip slashes from the value before saving
251 $value = isset($_POST['value']) ? stripslashes($_POST['value']) : '';
252
253 //error_log('MXChat Save: Processing field name: ' . $name);
254 //error_log('MXChat Save: Field value: ' . $value);
255
256 if (empty($name)) {
257 //error_log('MXChat Save: Empty field name detected');
258 wp_send_json_error(['message' => esc_html__('Invalid field name', 'mxchat')]);
259 }
260
261 // Load the full options array
262 $options = get_option('mxchat_options', []);
263 //error_log('MXChat Save: Current options array: ' . print_r($options, true));
264
265 // Handle special cases
266 switch ($name) {
267 case 'additional_popular_questions':
268 //error_log('MXChat Save: Processing additional_popular_questions');
269 $questions = json_decode($value, true); // No need for stripslashes here
270 if (is_array($questions)) {
271 $options[$name] = $questions;
272 // Also update old option for backwards compatibility
273 update_option('additional_popular_questions', $questions);
274 //error_log('MXChat Save: Saved ' . count($questions) . ' additional questions');
275 } else {
276 //error_log('MXChat Save: Failed to decode questions JSON');
277 }
278 break;
279 case 'email_blocker_header_content':
280 //error_log('MXChat Save: Processing email_blocker_header_content');
281 // Allow HTML content but sanitize it safely
282 $options[$name] = wp_kses_post($value);
283 break;
284 case 'similarity_threshold':
285 //error_log('MXChat Save: Processing similarity_threshold');
286 // Save to the options array
287 $options[$name] = $value;
288 break;
289 case 'user_message_bg_color':
290 case 'user_message_font_color':
291 case 'bot_message_bg_color':
292 case 'bot_message_font_color':
293 case 'top_bar_bg_color':
294 case 'send_button_font_color':
295 case 'chatbot_background_color':
296 case 'icon_color':
297 case 'chat_input_font_color':
298 case 'live_agent_message_bg_color':
299 case 'live_agent_message_font_color':
300 case 'mode_indicator_bg_color':
301 case 'mode_indicator_font_color':
302 case 'toolbar_icon_color':
303 //error_log('MXChat Save: Processing color value: ' . $name);
304 // Store color values directly
305 $options[$name] = $value;
306 break;
307 case 'live_agent_status':
308 //error_log('MXChat Save: Processing live_agent_status');
309 // Set the new value
310 $options[$name] = ($value === 'on') ? 'on' : 'off';
311 break;
312 case 'enable_woocommerce_integration':
313 //error_log('MXChat Save: Processing enable_woocommerce_integration');
314 // Handle values that used to be 1/0
315 $options[$name] = ($value === 'on' || $value === '1') ? 'on' : 'off';
316 break;
317 default:
318 // First check for rate limits settings
319 if (strpos($name, 'mxchat_options[rate_limits]') !== false) {
320 //error_log('MXChat Save: Detected rate_limits field: ' . $name);
321
322 // Extract role ID and setting from the name
323 preg_match('/\[rate_limits\]\[(.*?)\]\[(.*?)\]/', $name, $matches);
324 //error_log('MXChat Save: Regex matches: ' . print_r($matches, true));
325
326 if (isset($matches[1]) && isset($matches[2])) {
327 $role_id = $matches[1];
328 $setting_key = $matches[2]; // limit, timeframe, or message
329
330 //error_log('MXChat Save: Role ID = ' . $role_id . ', Setting Key = ' . $setting_key);
331
332 // Initialize rate_limits if it doesn't exist
333 if (!isset($options['rate_limits'])) {
334 // //error_log('MXChat Save: Initializing rate_limits array');
335 $options['rate_limits'] = [];
336 }
337
338 // Initialize role settings if it doesn't exist
339 if (!isset($options['rate_limits'][$role_id])) {
340 //error_log('MXChat Save: Initializing rate_limits for role: ' . $role_id);
341 $options['rate_limits'][$role_id] = [
342 'limit' => ($role_id === 'logged_out') ? '10' : '100',
343 'timeframe' => 'daily',
344 'message' => 'Rate limit exceeded. Please try again later.'
345 ];
346 }
347
348 // Update the specific setting
349 $options['rate_limits'][$role_id][$setting_key] = $value;
350 //error_log('MXChat Save: Updated rate_limits[' . $role_id . '][' . $setting_key . '] = ' . $value);
351 } else {
352 //error_log('MXChat Save: Failed to parse rate_limits pattern: ' . $name);
353 }
354 }
355 // Then check for role rate limits (old format)
356 else if (strpos($name, 'mxchat_options[role_rate_limits]') !== false) {
357 //error_log('MXChat Save: Processing role_rate_limits field: ' . $name);
358 // Extract role ID from the name
359 preg_match('/\[role_rate_limits\]\[(.*?)\]/', $name, $matches);
360 //error_log('MXChat Save: Regex matches: ' . print_r($matches, true));
361
362 if (isset($matches[1])) {
363 $role_id = $matches[1];
364 // Initialize role_rate_limits if it doesn't exist
365 if (!isset($options['role_rate_limits'])) {
366 //error_log('MXChat Save: Initializing role_rate_limits array');
367 $options['role_rate_limits'] = [];
368 }
369 // Update the specific role's rate limit
370 $options['role_rate_limits'][$role_id] = sanitize_text_field($value);
371 //error_log('MXChat Save: Updated role_rate_limits[' . $role_id . '] = ' . $value);
372 } else {
373 //error_log('MXChat Save: Failed to parse role_rate_limits pattern: ' . $name);
374 }
375 }
376 // Handle toggles
377 else if (strpos($name, 'toggle') !== false || in_array($name, [
378 'chat_persistence_toggle',
379 'privacy_toggle',
380 'complianz_toggle',
381 'chat_toolbar_toggle',
382 'show_pdf_upload_button',
383 'show_word_upload_button'
384 ])) {
385 //error_log('MXChat Save: Processing toggle: ' . $name);
386 $options[$name] = ($value === 'on') ? 'on' : 'off';
387 } else {
388 //error_log('MXChat Save: Processing standard field: ' . $name);
389 // Store all other values directly
390 $options[$name] = $value;
391 }
392 break;
393 }
394
395 // Save all updates to the options array
396 $updated = update_option('mxchat_options', $options);
397 //error_log('MXChat Save: Update result: ' . ($updated ? 'success' : 'unchanged') . ' for field: ' . $name);
398 //error_log('MXChat Save: Updated options array: ' . print_r($options, true));
399
400 // Always return success even if WordPress says nothing changed
401 // (which happens when the value is the same as before)
402 wp_send_json_success(['message' => esc_html__('Setting saved', 'mxchat')]);
403 }
404
405 /**
406 * Helper function to compare if a value has changed
407 * Handles various data types appropriately
408 */
409 private function has_value_changed($old_value, $new_value) {
410 // Handle null values
411 if ($old_value === null && $new_value === '') {
412 return false;
413 }
414
415 // Handle array values (like additional_popular_questions)
416 if (is_array($old_value) && is_array($new_value)) {
417 // Convert both to JSON for comparison to handle ordering differences
418 return json_encode($old_value) !== json_encode($new_value);
419 }
420
421 // Handle toggle/checkbox values consistently
422 if (in_array($old_value, ['on', '1', 1, true]) && in_array($new_value, ['on', '1', 1, true])) {
423 return false;
424 }
425 if (in_array($old_value, ['off', '0', 0, false, '']) && in_array($new_value, ['off', '0', 0, false, ''])) {
426 return false;
427 }
428
429 // Default direct comparison
430 return $old_value !== $new_value;
431 }
432
433 /**
434 * Handles AJAX auto-save for prompts and auto-sync settings.
435 */
436 public function mxchat_save_prompts_setting_callback() {
437 check_ajax_referer('mxchat_prompts_setting_nonce', '_ajax_nonce');
438
439 if (!current_user_can('manage_options')) {
440 wp_send_json_error(['message' => esc_html__('Unauthorized', 'mxchat')]);
441 }
442
443 $name = isset($_POST['name']) ? sanitize_text_field($_POST['name']) : '';
444 $value = isset($_POST['value']) ? sanitize_text_field($_POST['value']) : '';
445
446 if (empty($name)) {
447 wp_send_json_error(['message' => esc_html__('Invalid field name', 'mxchat')]);
448 }
449
450 // Log the values we're trying to save (for debugging)
451 //error_log('Attempting to save setting: ' . $name . ' = ' . $value);
452
453 // For all auto-sync options
454 if (strpos($name, 'mxchat_auto_sync_') === 0) {
455 // Convert 'on'/'off' to '1'/'0' for consistency (if needed)
456 $option_value = ($value === 'on') ? '1' : '0';
457
458 $result = update_option($name, $option_value);
459
460 if ($result) {
461 //error_log('Successfully saved setting: ' . $name . ' = ' . $option_value);
462 wp_send_json_success(['message' => esc_html__('Setting saved', 'mxchat')]);
463 } else {
464 //error_log('Failed to save setting: ' . $name . ' (no changes or error)');
465 wp_send_json_error(['message' => esc_html__('Update failed or no changes', 'mxchat')]);
466 }
467 return;
468 }
469
470 // Handle fields stored in the 'mxchat_prompts_options' array.
471 // Handle fields stored in the 'mxchat_prompts_options' array.
472 if ( false !== strpos( $name, 'mxchat_prompts_options[' ) ) {
473 // Extract the key name using regex.
474 if ( preg_match( '/mxchat_prompts_options\[(.*?)\]/', $name, $matches ) && ! empty( $matches[1] ) ) {
475 $key = sanitize_text_field( $matches[1] );
476 $options = get_option( 'mxchat_prompts_options', [] );
477
478 // Remove Pinecone-specific settings from the main plugin handling.
479 if ( 'mxchat_use_pinecone' === $key ) {
480 // Optionally, you could simply ignore this field or set a default value.
481 // For example, ensure it's always 0 or remove it entirely:
482 // $options[ $key ] = '0';
483 // Or, if you prefer, just return an error so that the main plugin doesn't handle it:
484 wp_send_json_error( [ 'message' => esc_html__( 'Pinecone settings are now handled in the add-on.', 'mxchat' ) ] );
485 } else {
486 // For all other fields, use the existing logic.
487 $options[ $key ] = ( $value === 'on' || $value === '1' ) ? '1' : $value;
488 }
489 update_option( 'mxchat_prompts_options', $options );
490 wp_send_json_success( [ 'message' => esc_html__( 'Setting saved', 'mxchat' ) ] );
491 }
492 }
493
494
495 wp_send_json_error( [ 'message' => esc_html__( 'Field not recognized', 'mxchat' ) ] );
496 }
497 public function mxchat_display_admin_notice() {
498 // Success notice
499 if ($message = get_transient('mxchat_admin_notice_success')) {
500 ?>
501 <div class="notice notice-success is-dismissible">
502 <p><?php echo esc_html($message); ?></p>
503 <button type="button" class="notice-dismiss"><span class="screen-reader-text"><?php echo esc_html__('Dismiss this notice.', 'mxchat'); ?></span></button>
504 </div>
505 <?php
506 delete_transient('mxchat_admin_notice_success'); // Clear the transient after displaying
507 }
508
509 // Error notice
510 if ($message = get_transient('mxchat_admin_notice_error')) {
511 ?>
512 <div class="notice notice-error is-dismissible">
513 <p><?php echo esc_html($message); ?></p>
514 <button type="button" class="notice-dismiss"><span class="screen-reader-text"><?php echo esc_html__('Dismiss this notice.', 'mxchat'); ?></span></button>
515 </div>
516 <?php
517 delete_transient('mxchat_admin_notice_error'); // Clear the transient after displaying
518 }
519 }
520
521
522
523
524
525 public function mxchat_create_admin_page() {
526
527 ?>
528 <div class="wrap mxchat-wrapper">
529 <!-- Hero Section -->
530 <div class="mxchat-hero">
531 <h1 class="mxchat-main-title">
532 <span class="mxchat-gradient-text">MxChat</span> Settings
533 </h1>
534 <p class="mxchat-hero-subtitle">
535 <?php esc_html_e('Configure your AI chatbot, manage integrations and explore tutorials to get the most out of MxChat.', 'mxchat'); ?>
536 </p>
537 </div>
538
539 <div class="mxchat-content">
540 <?php if (!$this->is_activated): ?>
541 <div class="mxchat-pro-card">
542 <div class="mxchat-pro-notification">
543 <div class="mxchat-pro-content">
544 <h3>🚀 Limited Time Offer: Save $20 on MxChat Pro Lifetime Access!</h3>
545 <p>Unlock <strong>unlimited access</strong> to our growing collection of powerful add-ons including Admin AI Assistant (ChatGPT-like experience in your admin panel), Forms Builder, Theme Customizer, WooCommerce, Perplexity, and more all included with your lifetime license!</p> </div>
546 <div class="mxchat-pro-cta">
547 <a href="https://mxchat.ai/" target="_blank" class="mxchat-button"><?php echo esc_html__('Upgrade to Pro Today', 'mxchat'); ?></a>
548 <a href="<?php echo admin_url('admin.php?page=mxchat-addons'); ?>" class="mxchat-link"><?php echo esc_html__('Preview Add-ons', 'mxchat'); ?></a>
549 </div>
550 </div>
551 </div>
552 <?php endif; ?>
553
554 <!-- Tabs Navigation -->
555 <div class="mxchat-tabs">
556 <button class="mxchat-tab-button active" data-tab="chatbot"><?php echo esc_html__('Chatbot', 'mxchat'); ?></button>
557 <button class="mxchat-tab-button" data-tab="embed"><?php echo esc_html__('Toolbar & Components', 'mxchat'); ?></button>
558 <button class="mxchat-tab-button" data-tab="general"><?php echo esc_html__('YouTube Tutorials', 'mxchat'); ?></button>
559 </div>
560
561 <!-- Tab Contents -->
562 <div id="chatbot" class="mxchat-tab-content active">
563 <div class="mxchat-card">
564 <div class="mxchat-autosave-section">
565 <?php do_settings_sections('mxchat-chatbot'); ?>
566 </div>
567 </div>
568 </div>
569
570 <div id="embed" class="mxchat-tab-content">
571
572 <div class="mxchat-card">
573 <h2><?php esc_html_e('Toolbar Settings', 'mxchat'); ?></h2>
574 <div class="mxchat-autosave-section">
575 <table class="form-table">
576 <?php do_settings_fields('mxchat-embed', 'mxchat_pdf_intent_section'); ?>
577 </table>
578 </div>
579 </div>
580
581
582 <div class="mxchat-card">
583 <h2><?php esc_html_e('Loops Settings', 'mxchat'); ?></h2>
584 <div class="mxchat-autosave-section">
585 <table class="form-table">
586 <?php do_settings_fields('mxchat-embed', 'mxchat_loops_section'); ?>
587 </table>
588 </div>
589 </div>
590
591 <div class="mxchat-card">
592 <h2><?php esc_html_e('Brave Search Settings', 'mxchat'); ?></h2>
593 <div class="mxchat-autosave-section">
594 <table class="form-table">
595 <?php do_settings_fields('mxchat-embed', 'mxchat_brave_section'); ?>
596 </table>
597 </div>
598 </div>
599
600 <div class="mxchat-card">
601 <h2><?php esc_html_e('Live Agent Settings', 'mxchat'); ?></h2>
602 <div class="mxchat-autosave-section">
603 <p><?php echo esc_html__('Visit our', 'mxchat'); ?> <a href="https://mxchat.ai/documentation/#slack_integration" target="_blank"><?php echo esc_html__('documentation page', 'mxchat'); ?></a> <?php echo esc_html__('to set up live agent transfer via Slack.', 'mxchat'); ?></p>
604 <table class="form-table">
605 <?php do_settings_fields('mxchat-embed', 'mxchat_live_agent_section'); ?>
606 </table>
607 </div>
608 </div>
609 </div>
610
611 <div id="general" class="mxchat-tab-content">
612 <div class="mxchat-card">
613 <?php do_settings_sections('mxchat-general'); ?>
614 <div class="video-tutorials-section">
615
616 <div class="tutorial-grid">
617 <div class="tutorial-item">
618 <h3><?php echo esc_html__('MxChat Forms Tutorial', 'mxchat'); ?></h3>
619 <div class="video-description">
620 <p><?php echo esc_html__('Learn how to create and manage smart forms that automatically trigger during chat conversations.', 'mxchat'); ?></p>
621 <a href="https://www.youtube.com/watch?v=3MrWy5dRalA" target="_blank" rel="noopener" class="video-link">
622 <span class="video-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 19c-2.3 0-6.4-.2-8.1-.6-.7-.2-1.2-.7-1.4-1.4-.3-1.1-.5-3.4-.5-5s.2-3.9.5-5c.2-.7.7-1.2 1.4-1.4C5.6 5.2 9.7 5 12 5s6.4.2 8.1.6c.7.2 1.2.7 1.4 1.4.3 1.1.5 3.4.5 5s-.2 3.9-.5 5c-.2.7-.7 1.2-1.4 1.4-1.7.4-5.8.6-8.1.6z"></path><polygon points="10 15 15 12 10 9 10 15"></polygon></svg></span>
623 <?php echo esc_html__('Watch MxChat Forms Tutorial', 'mxchat'); ?>
624 </a>
625 </div>
626 </div>
627
628 <div class="tutorial-item">
629 <h3><?php echo esc_html__('Admin Assistant Add-on', 'mxchat'); ?></h3>
630 <div class="video-description">
631 <p><?php echo esc_html__('Discover how to use the MxChat Admin Assistant to bring a ChatGPT-like experience directly inside your WordPress dashboard. Learn to access multiple AI models, save conversations, generate images, and use web search - all without leaving your admin panel.', 'mxchat'); ?></p>
632 <a href="https://youtu.be/AdEA1k-UCFM" target="_blank" rel="noopener" class="video-link">
633 <span class="video-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 19c-2.3 0-6.4-.2-8.1-.6-.7-.2-1.2-.7-1.4-1.4-.3-1.1-.5-3.4-.5-5s.2-3.9.5-5c.2-.7.7-1.2 1.4-1.4C5.6 5.2 9.7 5 12 5s6.4.2 8.1.6c.7.2 1.2.7 1.4 1.4.3 1.1.5 3.4.5 5s-.2-3.9-.5 5c-.2.7-.7 1.2-1.4 1.4-1.7.4-5.8.6-8.1.6z"></path><polygon points="10 15 15 12 10 9 10 15"></polygon></svg></span>
634 <?php echo esc_html__('Watch Admin Assistant Tutorial', 'mxchat'); ?>
635 </a>
636 </div>
637 </div>
638
639 <div class="tutorial-item">
640 <h3><?php echo esc_html__('Intent Tester Guide', 'mxchat'); ?></h3>
641 <div class="video-description">
642 <p><?php echo esc_html__('Discover how to use the Intent Tester to fine-tune your chatbot\'s responses and ensure it accurately understands user queries.', 'mxchat'); ?></p>
643 <a href="https://www.youtube.com/watch?v=uTr14tn59Hc" target="_blank" rel="noopener" class="video-link">
644 <span class="video-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 19c-2.3 0-6.4-.2-8.1-.6-.7-.2-1.2-.7-1.4-1.4-.3-1.1-.5-3.4-.5-5s.2-3.9.5-5c.2-.7.7-1.2 1.4-1.4C5.6 5.2 9.7 5 12 5s6.4.2 8.1.6c.7.2 1.2.7 1.4 1.4.3 1.1.5 3.4.5 5s-.2 3.9-.5 5c-.2.7-.7 1.2-1.4 1.4-1.7.4-5.8.6-8.1.6z"></path><polygon points="10 15 15 12 10 9 10 15"></polygon></svg></span>
645 <?php echo esc_html__('Watch Intent Tester Tutorial', 'mxchat'); ?>
646 </a>
647 </div>
648 </div>
649
650 <div class="tutorial-item">
651 <h3><?php echo esc_html__('Theme Customizer Add-on', 'mxchat'); ?></h3>
652 <div class="video-description">
653 <p><?php echo esc_html__('Learn how to customize your chatbot appearance with the Theme Customizer add-on. Easily modify colors, fonts, and styles with real-time previews to match your brand perfectly.', 'mxchat'); ?></p>
654 <a href="https://youtu.be/MfbB9mZi6ag" target="_blank" rel="noopener" class="video-link">
655 <span class="video-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 19c-2.3 0-6.4-.2-8.1-.6-.7-.2-1.2-.7-1.4-1.4-.3-1.1-.5-3.4-.5-5s.2-3.9.5-5c.2-.7.7-1.2 1.4-1.4C5.6 5.2 9.7 5 12 5s6.4.2 8.1.6c.7.2 1.2.7 1.4 1.4.3 1.1.5 3.4.5 5s-.2 3.9-.5 5c-.2.7-.7 1.2-1.4 1.4-1.7.4-5.8.6-8.1.6z"></path><polygon points="10 15 15 12 10 9 10 15"></polygon></svg></span>
656 <?php echo esc_html__('Watch Theme Customizer Tutorial', 'mxchat'); ?>
657 </a>
658 </div>
659 </div>
660
661 <div class="tutorial-item">
662 <h3><?php echo esc_html__('WooCommerce Integration', 'mxchat'); ?></h3>
663 <div class="video-description">
664 <p><?php echo esc_html__('See how to integrate MxChat with your WooCommerce store to provide product recommendations and shopping assistance to your customers.', 'mxchat'); ?></p>
665 <a href="https://www.youtube.com/watch?v=WsqAppHRGdA" target="_blank" rel="noopener" class="video-link">
666 <span class="video-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 19c-2.3 0-6.4-.2-8.1-.6-.7-.2-1.2-.7-1.4-1.4-.3-1.1-.5-3.4-.5-5s.2-3.9.5-5c.2-.7.7-1.2 1.4-1.4C5.6 5.2 9.7 5 12 5s6.4.2 8.1.6c.7.2 1.2.7 1.4 1.4.3 1.1.5 3.4.5 5s-.2 3.9-.5 5c-.2.7-.7 1.2-1.4 1.4-1.7.4-5.8.6-8.1.6z"></path><polygon points="10 15 15 12 10 9 10 15"></polygon></svg></span>
667 <?php echo esc_html__('Watch WooCommerce Integration Tutorial', 'mxchat'); ?>
668 </a>
669 </div>
670 </div>
671
672 <div class="tutorial-item">
673 <h3><?php echo esc_html__('Knowledge Base Setup', 'mxchat'); ?></h3>
674 <div class="video-description">
675 <p><?php echo esc_html__('Learn how to set up your knowledge base using PDFs, sitemaps, and manual entries to enhance your chatbot\'s responses with site-specific information.', 'mxchat'); ?></p>
676 <p><small><?php echo esc_html__('Note: This tutorial uses an older UI, but the process remains the same.', 'mxchat'); ?></small></p>
677 <a href="https://www.youtube.com/watch?v=8Ztjs66-VTo" target="_blank" rel="noopener" class="video-link">
678 <span class="video-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 19c-2.3 0-6.4-.2-8.1-.6-.7-.2-1.2-.7-1.4-1.4-.3-1.1-.5-3.4-.5-5s.2-3.9.5-5c.2-.7.7-1.2 1.4-1.4C5.6 5.2 9.7 5 12 5s6.4.2 8.1.6c.7.2 1.2.7 1.4 1.4.3 1.1.5 3.4.5 5s-.2 3.9-.5 5c-.2.7-.7 1.2-1.4 1.4-1.7.4-5.8.6-8.1.6z"></path><polygon points="10 15 15 12 10 9 10 15"></polygon></svg></span>
679 <?php echo esc_html__('Watch Knowledge Base Setup Tutorial', 'mxchat'); ?>
680 </a>
681 </div>
682 </div>
683
684 <div class="tutorial-item">
685 <h3><?php echo esc_html__('Toolbar Chat with Documents', 'mxchat'); ?></h3>
686 <div class="video-description">
687 <p><?php echo esc_html__('See how to use the MxChat toolbar to chat with PDF and Word documents for enhanced document analysis and information retrieval.', 'mxchat'); ?></p>
688 <a href="https://www.youtube.com/watch?v=j_c45WWCTG0" target="_blank" rel="noopener" class="video-link">
689 <span class="video-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 19c-2.3 0-6.4-.2-8.1-.6-.7-.2-1.2-.7-1.4-1.4-.3-1.1-.5-3.4-.5-5s.2-3.9.5-5c.2-.7.7-1.2 1.4-1.4C5.6 5.2 9.7 5 12 5s6.4.2 8.1.6c.7.2 1.2.7 1.4 1.4.3 1.1.5 3.4.5 5s-.2 3.9-.5 5c-.2.7-.7 1.2-1.4 1.4-1.7.4-5.8.6-8.1.6z"></path><polygon points="10 15 15 12 10 9 10 15"></polygon></svg></span>
690 <?php echo esc_html__('Watch Document Chat Tutorial', 'mxchat'); ?>
691 </a>
692 </div>
693 </div>
694
695 <div class="tutorial-item">
696 <h3><?php echo esc_html__('MxChat Smart Recommender Tutorial', 'mxchat'); ?></h3>
697 <div class="video-description">
698 <p><?php echo esc_html__('Learn how to create intelligent recommendation flows that guide users to perfect matches based on their preferences.', 'mxchat'); ?></p>
699 <a href="https://www.youtube.com/watch?v=8te1KPa238g&t=1s" target="_blank" rel="noopener" class="video-link">
700 <span class="video-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 19c-2.3 0-6.4-.2-8.1-.6-.7-.2-1.2-.7-1.4-1.4-.3-1.1-.5-3.4-.5-5s.2-3.9.5-5c.2-.7.7-1.2 1.4-1.4C5.6 5.2 9.7 5 12 5s6.4.2 8.1.6c.7.2 1.2.7 1.4 1.4.3 1.1.5 3.4.5 5s-.2 3.9-.5 5c-.2.7-.7 1.2-1.4 1.4-1.7.4-5.8.6-8.1.6z"></path><polygon points="10 15 15 12 10 9 10 15"></polygon></svg></span>
701 <?php echo esc_html__('Watch Smart Recommender Tutorial', 'mxchat'); ?>
702 </a>
703 </div>
704 </div>
705
706 <div class="tutorial-item">
707 <h3><?php echo esc_html__('Perplexity Integration', 'mxchat'); ?></h3>
708 <div class="video-description">
709 <p><?php echo esc_html__('Learn how to integrate Perplexity with your chatbot for real-time web search capabilities. This tutorial covers intent recognition, the toolbar toggle button, and how to enable your chatbot to search the web and provide up-to-date information to your visitors.', 'mxchat'); ?></p>
710 <a href="https://youtu.be/wpKkbt24-bo" target="_blank" rel="noopener" class="video-link">
711 <span class="video-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 19c-2.3 0-6.4-.2-8.1-.6-.7-.2-1.2-.7-1.4-1.4-.3-1.1-.5-3.4-.5-5s.2-3.9.5-5c.2-.7.7-1.2 1.4-1.4C5.6 5.2 9.7 5 12 5s6.4.2 8.1.6c.7.2 1.2.7 1.4 1.4.3 1.1.5 3.4.5 5s-.2 3.9-.5 5c-.2.7-.7 1.2-1.4 1.4-1.7.4-5.8.6-8.1.6z"></path><polygon points="10 15 15 12 10 9 10 15"></polygon></svg></span>
712 <?php echo esc_html__('Watch Perplexity Integration Tutorial', 'mxchat'); ?>
713 </a>
714 </div>
715 </div>
716
717 <div class="tutorial-item">
718 <h3><?php echo esc_html__('Brave Search Intent', 'mxchat'); ?></h3>
719 <div class="video-description">
720 <p><?php echo esc_html__('Learn how to leverage Brave Search intent capabilities to improve your chatbot\'s understanding of user queries and provide more accurate responses.', 'mxchat'); ?></p>
721 <a href="https://www.youtube.com/watch?v=7vDL5H7vToc" target="_blank" rel="noopener" class="video-link">
722 <span class="video-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 19c-2.3 0-6.4-.2-8.1-.6-.7-.2-1.2-.7-1.4-1.4-.3-1.1-.5-3.4-.5-5s.2-3.9.5-5c.2-.7.7-1.2 1.4-1.4C5.6 5.2 9.7 5 12 5s6.4.2 8.1.6c.7.2 1.2.7 1.4 1.4.3 1.1.5 3.4.5 5s-.2 3.9-.5 5c-.2.7-.7 1.2-1.4 1.4-1.7.4-5.8.6-8.1.6z"></path><polygon points="10 15 15 12 10 9 10 15"></polygon></svg></span>
723 <?php echo esc_html__('Watch Brave Search Intent Tutorial', 'mxchat'); ?>
724 </a>
725 </div>
726 </div>
727
728 <div class="tutorial-item">
729 <h3><?php echo esc_html__('Loops Email Capture', 'mxchat'); ?></h3>
730 <div class="video-description">
731 <p><?php echo esc_html__('Discover how to set up email capture with MxChat using Loops to grow your mailing list while providing value through your chatbot.', 'mxchat'); ?></p>
732 <p><small><?php echo esc_html__('Note: This tutorial uses an older UI, but the process remains the same.', 'mxchat'); ?></small></p>
733 <a href="https://www.youtube.com/watch?v=CNgm5TYDyTc" target="_blank" rel="noopener" class="video-link">
734 <span class="video-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 19c-2.3 0-6.4-.2-8.1-.6-.7-.2-1.2-.7-1.4-1.4-.3-1.1-.5-3.4-.5-5s.2-3.9.5-5c.2-.7.7-1.2 1.4-1.4C5.6 5.2 9.7 5 12 5s6.4.2 8.1.6c.7.2 1.2.7 1.4 1.4.3 1.1.5 3.4.5 5s-.2 3.9-.5 5c-.2.7-.7 1.2-1.4 1.4-1.7.4-5.8.6-8.1.6z"></path><polygon points="10 15 15 12 10 9 10 15"></polygon></svg></span>
735 <?php echo esc_html__('Watch Loops Email Capture Tutorial', 'mxchat'); ?>
736 </a>
737 </div>
738 </div>
739
740 <div class="tutorial-item">
741 <h3><?php echo esc_html__('MxChat AI Agent Testing Service', 'mxchat'); ?></h3>
742 <div class="video-description">
743 <p><?php echo esc_html__('Learn how to use the MxChat AI Agent Testing Service to evaluate and improve your chatbot\'s performance and accuracy.', 'mxchat'); ?></p>
744 <p><small><?php echo esc_html__('Note: This tutorial uses an older UI, but the process remains the same.', 'mxchat'); ?></small></p>
745 <a href="https://www.youtube.com/watch?v=A0jowbpyX54" target="_blank" rel="noopener" class="video-link">
746 <span class="video-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 19c-2.3 0-6.4-.2-8.1-.6-.7-.2-1.2-.7-1.4-1.4-.3-1.1-.5-3.4-.5-5s.2-3.9.5-5c.2-.7.7-1.2 1.4-1.4C5.6 5.2 9.7 5 12 5s6.4.2 8.1.6c.7.2 1.2.7 1.4 1.4.3 1.1.5 3.4.5 5s-.2 3.9-.5 5c-.2.7-.7 1.2-1.4 1.4-1.7.4-5.8.6-8.1.6z"></path><polygon points="10 15 15 12 10 9 10 15"></polygon></svg></span>
747 <?php echo esc_html__('Watch AI Agent Testing Tutorial', 'mxchat'); ?>
748 </a>
749 </div>
750 </div>
751 </div>
752
753 <div class="support-section">
754 <h3><?php echo esc_html__('Need Help?', 'mxchat'); ?></h3>
755 <div class="support-content">
756 <p>
757 <?php echo esc_html__('If you\'re having trouble with setup or getting the responses you need, we encourage you to review our', 'mxchat'); ?>
758 <a href="https://mxchat.ai/documentation/" target="_blank" rel="noopener noreferrer"><?php echo esc_html__('documentation', 'mxchat'); ?></a> <?php echo esc_html__('or', 'mxchat'); ?>
759 <a href="https://wordpress.org/support/plugin/mxchat-basic/" target="_blank" rel="noopener noreferrer"><?php echo esc_html__('create a support ticket', 'mxchat'); ?></a>.
760 </p>
761 <p>
762 <?php echo esc_html__('If you like our plugin, please consider', 'mxchat'); ?> <a href="https://wordpress.org/plugins/mxchat-basic/#reviews" target="_blank" rel="noopener noreferrer"><?php echo esc_html__('leaving us a review', 'mxchat'); ?></a>.
763 </p>
764 </div>
765 </div>
766 </div>
767 </div>
768 </div>
769 </div>
770 </div>
771 <?php
772 }
773
774
775 public function mxchat_create_transcripts_page() {
776 global $wpdb;
777 $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
778
779 // Get basic stats
780 $total_chats = $wpdb->get_var("SELECT COUNT(DISTINCT session_id) FROM $table_name");
781 $total_messages = $wpdb->get_var("SELECT COUNT(*) FROM $table_name");
782
783 // Count unique users with detailed breakdown
784 $total_users = $wpdb->get_var("
785 SELECT COUNT(DISTINCT
786 CASE
787 WHEN user_email != '' AND user_email IS NOT NULL THEN user_email
788 WHEN user_id != 0 THEN CONCAT('user_', user_id)
789 WHEN user_identifier NOT LIKE 'Tech-Savvy User'
790 AND user_identifier NOT LIKE 'Detail-Oriented User'
791 AND user_identifier NOT LIKE 'Language Learner'
792 AND user_identifier NOT LIKE 'Casual Browser'
793 AND user_identifier NOT LIKE 'Policy Enforcer'
794 AND user_identifier NOT LIKE 'Researcher'
795 AND user_identifier NOT LIKE 'Loyalty Member'
796 AND user_identifier NOT LIKE 'Gift Buyer'
797 AND user_identifier NOT LIKE 'Parent or Caregiver'
798 THEN user_identifier
799 ELSE session_id
800 END
801 )
802 FROM $table_name
803 WHERE role != 'assistant'
804 ");
805
806 // Get user type breakdown
807 $registered_users = $wpdb->get_var("
808 SELECT COUNT(DISTINCT user_email)
809 FROM $table_name
810 WHERE user_email != '' AND user_email IS NOT NULL
811 ");
812
813 $guest_users = $wpdb->get_var("
814 SELECT COUNT(DISTINCT user_identifier)
815 FROM $table_name
816 WHERE (user_email = '' OR user_email IS NULL)
817 AND role != 'assistant'
818 AND user_identifier NOT LIKE 'Tech-Savvy User'
819 AND user_identifier NOT LIKE 'Detail-Oriented User'
820 AND user_identifier NOT LIKE 'Language Learner'
821 AND user_identifier NOT LIKE 'Casual Browser'
822 AND user_identifier NOT LIKE 'Policy Enforcer'
823 AND user_identifier NOT LIKE 'Researcher'
824 AND user_identifier NOT LIKE 'Loyalty Member'
825 AND user_identifier NOT LIKE 'Gift Buyer'
826 AND user_identifier NOT LIKE 'Parent or Caregiver'
827 ");
828
829 // Get agent test messages count
830 $agent_tests = $wpdb->get_var("
831 SELECT COUNT(DISTINCT session_id)
832 FROM $table_name
833 WHERE user_identifier IN (
834 'Tech-Savvy User',
835 'Detail-Oriented User',
836 'Language Learner',
837 'Casual Browser',
838 'Policy Enforcer',
839 'Researcher',
840 'Loyalty Member',
841 'Gift Buyer',
842 'Parent or Caregiver'
843 )
844 ");
845 ?>
846 <div class="wrap mxchat-transcripts-wrapper">
847 <!-- Hero Section -->
848 <div class="mxchat-transcripts-hero">
849 <h1 class="mxchat-main-title">
850 Chat <span class="mxchat-gradient-text">Transcripts</span>
851 </h1>
852 <p class="mxchat-hero-subtitle">
853 <?php esc_html_e('Review and manage your chatbot conversations with detailed message history.', 'mxchat'); ?>
854 </p>
855 </div>
856 <div class="mxchat-content">
857 <!-- Stats Cards -->
858 <div class="mxchat-stats-grid">
859 <div class="mxchat-stat-card">
860 <div class="stat-icon">💬</div>
861 <div class="stat-content">
862 <span class="stat-value"><?php echo esc_html($total_chats); ?></span>
863 <span class="stat-label"><?php esc_html_e('Total Chats', 'mxchat'); ?></span>
864 </div>
865 </div>
866 <div class="mxchat-stat-card">
867 <div class="stat-icon">📝</div>
868 <div class="stat-content">
869 <span class="stat-value"><?php echo esc_html($total_messages); ?></span>
870 <span class="stat-label"><?php esc_html_e('Total Messages', 'mxchat'); ?></span>
871 </div>
872 </div>
873 <div class="mxchat-stat-card">
874 <div class="stat-icon">👥</div>
875 <div class="stat-content">
876 <span class="stat-value"><?php echo esc_html($total_users); ?></span>
877 <span class="stat-label"><?php esc_html_e('Unique Users', 'mxchat'); ?></span>
878 <span class="stat-sublabel">
879 <?php
880 echo sprintf(
881 esc_html__('%d registered, %d guests, %d agent tests', 'mxchat'),
882 $registered_users,
883 $guest_users,
884 $agent_tests
885 );
886 ?>
887 </span>
888 </div>
889 </div>
890 </div>
891
892 <!-- Search and Filter Controls with Notification Settings Button -->
893 <div class="mxchat-controls-wrapper">
894 <div class="mxchat-search-box">
895 <input type="text" id="mxchat-search-transcripts"
896 placeholder="<?php esc_attr_e('Search transcripts...', 'mxchat'); ?>"
897 class="regular-text">
898 </div>
899 <form id="mxchat-delete-form" method="post">
900 <?php wp_nonce_field('mxchat_delete_chat_history', 'mxchat_delete_chat_nonce'); ?>
901 <div class="mxchat-controls">
902 <button type="button" id="mxchat-chat-email-notification-btn" class="mxchat-action-button">
903 <span class="dashicons dashicons-email"></span>
904 <?php esc_html_e('Notification Settings', 'mxchat'); ?>
905 </button>
906 <button type="button" id="mxchat-export-transcripts" class="mxchat-action-button">
907 <span class="dashicons dashicons-download"></span>
908 <?php esc_html_e('Export All Chats', 'mxchat'); ?>
909 </button>
910 <button type="button" id="mxchat-select-all-transcripts" class="mxchat-select-button">
911 <span class="dashicons dashicons-yes-alt"></span>
912 <span class="button-text"><?php esc_html_e('Select All', 'mxchat'); ?></span>
913 </button>
914 <button type="submit" class="button delete-chats-button">
915 <span class="dashicons dashicons-trash"></span>
916 <?php esc_html_e('Delete Selected', 'mxchat'); ?>
917 </button>
918 </div>
919 </form>
920 </div>
921
922 <!-- Transcripts Container -->
923 <div id="mxchat-transcripts"></div>
924 </div>
925 </div>
926
927 <!-- Modal for Chat Email Notification Settings -->
928 <div id="mxchat-chat-email-notification-modal" class="mxchat-chat-notification-modal-overlay" style="display: none;">
929 <div class="mxchat-chat-notification-modal-content">
930 <div class="mxchat-chat-notification-modal-header">
931 <h2><?php esc_html_e('Email Notification Settings', 'mxchat'); ?></h2>
932 <button type="button" class="mxchat-chat-notification-modal-close">&times;</button>
933 </div>
934 <div class="mxchat-chat-notification-modal-body">
935 <form method="post" action="options.php" id="mxchat-chat-email-notification-form">
936 <?php
937 settings_fields('mxchat_transcripts_options');
938 do_settings_sections('mxchat-transcripts');
939 ?>
940 <div class="mxchat-chat-notification-modal-footer">
941 <button type="submit" class="button button-primary">
942 <?php esc_html_e('Save Notification Settings', 'mxchat'); ?>
943 </button>
944 <button type="button" class="button mxchat-chat-notification-modal-cancel">
945 <?php esc_html_e('Cancel', 'mxchat'); ?>
946 </button>
947 </div>
948 </form>
949 </div>
950 </div>
951 </div>
952 <?php
953 }
954 public function mxchat_transcripts_notification_section_callback() {
955 echo '<p>' . esc_html__('Configure email notifications for new chat transcripts. You will receive an email notification when a new chat session begins.', 'mxchat') . '</p>';
956 }
957 public function mxchat_enable_notifications_callback() {
958 $options = get_option('mxchat_transcripts_options', array());
959 $enabled = isset($options['mxchat_enable_notifications']) ? $options['mxchat_enable_notifications'] : 0;
960 ?>
961 <label for="mxchat_enable_notifications">
962 <input type="checkbox" id="mxchat_enable_notifications"
963 name="mxchat_transcripts_options[mxchat_enable_notifications]"
964 value="1" <?php checked(1, $enabled); ?>>
965 <?php esc_html_e('Send email notification when a new chat session starts', 'mxchat'); ?>
966 </label>
967 <p class="description">
968 <?php esc_html_e('Enable this option to receive email notifications for new chat sessions.', 'mxchat'); ?>
969 </p>
970 <?php
971 }
972 public function mxchat_notification_email_callback() {
973 $options = get_option('mxchat_transcripts_options', array());
974 $email = isset($options['mxchat_notification_email']) ? $options['mxchat_notification_email'] : get_option('admin_email');
975 ?>
976 <input type="email" id="mxchat_notification_email"
977 name="mxchat_transcripts_options[mxchat_notification_email]"
978 value="<?php echo esc_attr($email); ?>"
979 class="regular-text">
980 <p class="description">
981 <?php esc_html_e('Enter the email address where notifications should be sent. Defaults to the admin email address.', 'mxchat'); ?>
982 </p>
983 <?php
984 }
985 public function sanitize_transcripts_options($input) {
986 $sanitized = array();
987
988 $sanitized['mxchat_enable_notifications'] = isset($input['mxchat_enable_notifications']) ? 1 : 0;
989
990 if (isset($input['mxchat_notification_email'])) {
991 $sanitized['mxchat_notification_email'] = sanitize_email($input['mxchat_notification_email']);
992 if (!is_email($sanitized['mxchat_notification_email'])) {
993 add_settings_error(
994 'mxchat_transcripts_options',
995 'invalid_email',
996 __('Please enter a valid email address for notifications.', 'mxchat'),
997 'error'
998 );
999 $sanitized['mxchat_notification_email'] = get_option('admin_email');
1000 }
1001 }
1002
1003 return $sanitized;
1004 }
1005 public function export_chat_transcripts() {
1006 if (!current_user_can('manage_options')) {
1007 wp_die(esc_html__('You do not have sufficient permissions to access this page.', 'mxchat'));
1008 }
1009
1010 check_ajax_referer('mxchat_export_transcripts', 'security');
1011
1012 global $wpdb;
1013 $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
1014
1015 // Get all transcripts ordered by session and timestamp
1016 $results = $wpdb->get_results(
1017 "SELECT session_id, user_email, user_identifier, role, message, timestamp
1018 FROM {$table_name}
1019 ORDER BY session_id, timestamp ASC"
1020 );
1021
1022 if (empty($results)) {
1023 wp_send_json_error(array('message' => 'No transcripts found.'));
1024 wp_die();
1025 }
1026
1027 // Set headers for CSV download
1028 header('Content-Type: text/csv');
1029 header('Content-Disposition: attachment; filename="chat-transcripts-' . date('Y-m-d') . '.csv"');
1030 header('Pragma: no-cache');
1031 header('Expires: 0');
1032
1033 // Create output stream
1034 $output = fopen('php://output', 'w');
1035
1036 // Add UTF-8 BOM for proper Excel encoding
1037 fputs($output, "\xEF\xBB\xBF");
1038
1039 // Add CSV headers
1040 fputcsv($output, array(
1041 'Session ID',
1042 'Email',
1043 'User Identifier',
1044 'Role',
1045 'Message',
1046 'Timestamp'
1047 ));
1048
1049 // Add data rows
1050 foreach ($results as $row) {
1051 fputcsv($output, array(
1052 $row->session_id,
1053 $row->user_email,
1054 $row->user_identifier,
1055 $row->role,
1056 $row->message,
1057 $row->timestamp
1058 ));
1059 }
1060
1061 fclose($output);
1062 wp_die();
1063 }
1064 public function mxchat_fetch_chat_history() {
1065 global $wpdb;
1066 $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
1067
1068 if (!current_user_can('manage_options')) {
1069 wp_die(esc_html__('You do not have sufficient permissions to view this page.', 'mxchat'));
1070 }
1071
1072 // Get pagination parameters
1073 $page = isset($_POST['page']) ? absint($_POST['page']) : 1;
1074 $per_page = isset($_POST['per_page']) ? absint($_POST['per_page']) : 50;
1075 $offset = ($page - 1) * $per_page;
1076
1077 // Get search parameter if any
1078 $search = isset($_POST['search']) ? sanitize_text_field($_POST['search']) : '';
1079
1080 // Build the query based on whether we have a search term
1081 $search_condition = '';
1082 $search_params = array();
1083
1084 if (!empty($search)) {
1085 $search_condition = "WHERE (
1086 session_id LIKE %s
1087 OR user_email LIKE %s
1088 OR user_identifier LIKE %s
1089 OR message LIKE %s
1090 )";
1091 $search_params = array(
1092 '%' . $wpdb->esc_like($search) . '%',
1093 '%' . $wpdb->esc_like($search) . '%',
1094 '%' . $wpdb->esc_like($search) . '%',
1095 '%' . $wpdb->esc_like($search) . '%'
1096 );
1097 }
1098
1099 // First count total sessions for pagination
1100 if (!empty($search)) {
1101 $count_query = $wpdb->prepare(
1102 "SELECT COUNT(DISTINCT session_id)
1103 FROM {$table_name}
1104 {$search_condition}",
1105 $search_params
1106 );
1107 } else {
1108 $count_query = "SELECT COUNT(DISTINCT session_id) FROM {$table_name}";
1109 }
1110
1111 $total_sessions = $wpdb->get_var($count_query);
1112
1113 // Get paginated session IDs ordered by most recent message in each session
1114 if (!empty($search)) {
1115 $session_query = $wpdb->prepare(
1116 "SELECT DISTINCT t.session_id
1117 FROM {$table_name} t
1118 {$search_condition}
1119 GROUP BY t.session_id
1120 ORDER BY MAX(t.timestamp) DESC
1121 LIMIT %d OFFSET %d",
1122 array_merge($search_params, array($per_page, $offset))
1123 );
1124 } else {
1125 $session_query = $wpdb->prepare(
1126 "SELECT DISTINCT session_id
1127 FROM {$table_name}
1128 GROUP BY session_id
1129 ORDER BY MAX(timestamp) DESC
1130 LIMIT %d OFFSET %d",
1131 $per_page, $offset
1132 );
1133 }
1134
1135 $session_ids = $wpdb->get_col($session_query);
1136
1137 if (empty($session_ids)) {
1138 ob_start();
1139 echo '<div class="mxchat-no-results">';
1140 echo esc_html__('No chat history found.', 'mxchat');
1141 if (!empty($search)) {
1142 echo ' ' . esc_html__('Try adjusting your search criteria.', 'mxchat');
1143 }
1144 echo '</div>';
1145 $output = ob_get_clean();
1146
1147 wp_send_json(array(
1148 'html' => $output,
1149 'page' => $page,
1150 'total_pages' => 0,
1151 'total_sessions' => 0
1152 ));
1153
1154 wp_die();
1155 }
1156
1157 ob_start();
1158 echo '<div class="mxchat-transcript">';
1159
1160 // Iterate through sessions from newest to oldest
1161 foreach ($session_ids as $session_id) {
1162 // Get the email associated with this session (if available)
1163 $email = $wpdb->get_var(
1164 $wpdb->prepare(
1165 "SELECT user_email
1166 FROM {$table_name}
1167 WHERE session_id = %s AND user_email != ''
1168 ORDER BY timestamp ASC
1169 LIMIT 1",
1170 $session_id
1171 )
1172 );
1173
1174 // Get messages for this session ordered by timestamp
1175 $messages = $wpdb->get_results(
1176 $wpdb->prepare(
1177 "SELECT * FROM {$table_name}
1178 WHERE session_id = %s
1179 ORDER BY timestamp ASC",
1180 $session_id
1181 )
1182 );
1183
1184 // Start session block
1185 echo '<div class="mxchat-session">';
1186 echo '<div class="mxchat-session-header">';
1187 // Wrap checkbox and session ID in one block
1188 echo '<div class="mxchat-session-id">';
1189 echo '<input type="checkbox" name="delete_session_ids[]" value="' . esc_attr($session_id) . '"> ';
1190 echo '<strong>' . esc_html__('Session ID:', 'mxchat') . '</strong> ' . esc_html($session_id);
1191 echo '</div>';
1192
1193 // Place email directly below the session ID block
1194 if (!empty($email)) {
1195 echo '<div class="mxchat-session-email">';
1196 echo '<strong>' . esc_html__('Email:', 'mxchat') . '</strong> ' . esc_html($email);
1197 echo '</div>';
1198 }
1199 echo '</div>';
1200
1201 echo '<div class="mxchat-messages">';
1202
1203 // Display messages for this session
1204 foreach ($messages as $transcript) {
1205 $formatted_timestamp = date_i18n('F j, Y g:i a', strtotime($transcript->timestamp));
1206
1207 // Determine message styling
1208 switch ($transcript->role) {
1209 case 'assistant':
1210 case 'bot':
1211 $message_class = 'bot-message';
1212 $display_role = esc_html__('Chatbot', 'mxchat');
1213 break;
1214 case 'user':
1215 $message_class = 'user-message';
1216 $display_role = !empty($transcript->user_identifier)
1217 ? sanitize_text_field($transcript->user_identifier)
1218 : esc_html__('User', 'mxchat');
1219 break;
1220 case 'agent':
1221 $message_class = 'agent-message';
1222 $display_role = esc_html__('Agent', 'mxchat');
1223 break;
1224 default:
1225 $message_class = 'unknown-message';
1226 $display_role = esc_html__('Unknown', 'mxchat');
1227 }
1228
1229 // Process message content
1230 $message_content = wp_kses(
1231 stripslashes($transcript->message),
1232 [
1233 'b' => [], 'strong' => [], 'i' => [], 'em' => [], 'u' => [],
1234 'br' => [], 'p' => [], 'ul' => [], 'ol' => [], 'li' => [],
1235 'a' => ['href' => [], 'title' => []]
1236 ]
1237 );
1238 $message_content = nl2br($message_content);
1239
1240 // Render message
1241 echo '<div class="mxchat-message ' . esc_attr($message_class) . '">';
1242 echo '<div class="mxchat-message-header">' . esc_html($display_role) . '</div>';
1243 echo '<div class="mxchat-message-content">' . $message_content . '</div>';
1244 echo '<div class="mxchat-timestamp">' . esc_html($formatted_timestamp) . '</div>';
1245 echo '</div>';
1246 }
1247
1248 // Close session block
1249 echo '</div></div>';
1250 }
1251
1252 echo '</div>';
1253
1254 // Add pagination info and controls
1255 $total_pages = ceil($total_sessions / $per_page);
1256
1257 echo '<div class="mxchat-pagination">';
1258 echo '<div class="mxchat-pagination-info">';
1259 echo sprintf(
1260 esc_html__('Showing %1$d to %2$d of %3$d sessions', 'mxchat'),
1261 $offset + 1,
1262 min($offset + $per_page, $total_sessions),
1263 $total_sessions
1264 );
1265 echo '</div>';
1266
1267 if ($total_pages > 1) {
1268 echo '<div class="mxchat-pagination-controls">';
1269
1270 // Previous page button
1271 if ($page > 1) {
1272 echo '<button class="mxchat-pagination-button" data-page="' . ($page - 1) . '">&laquo; ' . esc_html__('Previous', 'mxchat') . '</button>';
1273 }
1274
1275 // Page numbers
1276 $start_page = max(1, $page - 2);
1277 $end_page = min($total_pages, $page + 2);
1278
1279 if ($start_page > 1) {
1280 echo '<button class="mxchat-pagination-button" data-page="1">1</button>';
1281 if ($start_page > 2) {
1282 echo '<span class="mxchat-pagination-ellipsis">...</span>';
1283 }
1284 }
1285
1286 for ($i = $start_page; $i <= $end_page; $i++) {
1287 $class = $i === $page ? 'mxchat-pagination-button active' : 'mxchat-pagination-button';
1288 echo '<button class="' . $class . '" data-page="' . $i . '">' . $i . '</button>';
1289 }
1290
1291 if ($end_page < $total_pages) {
1292 if ($end_page < $total_pages - 1) {
1293 echo '<span class="mxchat-pagination-ellipsis">...</span>';
1294 }
1295 echo '<button class="mxchat-pagination-button" data-page="' . $total_pages . '">' . $total_pages . '</button>';
1296 }
1297
1298 // Next page button
1299 if ($page < $total_pages) {
1300 echo '<button class="mxchat-pagination-button" data-page="' . ($page + 1) . '">' . esc_html__('Next', 'mxchat') . ' &raquo;</button>';
1301 }
1302
1303 echo '</div>';
1304 }
1305 echo '</div>';
1306
1307 $output = ob_get_clean();
1308
1309 wp_send_json(array(
1310 'html' => $output,
1311 'page' => $page,
1312 'total_pages' => $total_pages,
1313 'total_sessions' => $total_sessions
1314 ));
1315
1316 wp_die();
1317 }
1318
1319
1320
1321 public function mxchat_create_prompts_page() {
1322 global $wpdb;
1323 $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
1324
1325 // Display success message if all prompts were deleted
1326 if (isset($_GET['all_deleted']) && $_GET['all_deleted'] === 'true') {
1327 echo '<div class="notice notice-success is-dismissible"><p>' . esc_html__('All knowledge has been deleted successfully.', 'mxchat') . '</p></div>';
1328 }
1329
1330 // Set up pagination and search query
1331 $nonce = isset($_GET['_wpnonce']) ? sanitize_text_field($_GET['_wpnonce']) : '';
1332 $search_query = (!empty($nonce) && wp_verify_nonce($nonce, 'mxchat_prompts_search_nonce') && isset($_GET['search'])) ? sanitize_text_field($_GET['search']) : '';
1333 $current_page = isset($_GET['paged']) ? absint($_GET['paged']) : 1;
1334 $per_page = 10;
1335 $offset = ($current_page - 1) * $per_page;
1336
1337 // Modify query to handle search input
1338 $sql_search = "";
1339 if ($search_query) {
1340 $sql_search = $wpdb->prepare("WHERE article_content LIKE %s", '%' . $wpdb->esc_like($search_query) . '%');
1341 }
1342
1343 // Retrieve total number of prompts, considering search filter
1344 $total_prompts = $wpdb->get_var("SELECT COUNT(*) FROM {$table_name} {$sql_search}");
1345 $total_pages = ceil($total_prompts / $per_page);
1346
1347 // Retrieve prompts from the database
1348 $prompts = $wpdb->get_results(
1349 $wpdb->prepare(
1350 "SELECT * FROM {$table_name} {$sql_search} ORDER BY timestamp DESC LIMIT %d OFFSET %d",
1351 $per_page,
1352 $offset
1353 )
1354 );
1355
1356 // Add the pagination links generation here
1357 $page_links = paginate_links(array(
1358 'base' => add_query_arg(array(
1359 'paged' => '%#%',
1360 'search' => urlencode($search_query),
1361 '_wpnonce' => wp_create_nonce('mxchat_prompts_search_nonce')
1362 ), admin_url('admin.php?page=mxchat-prompts')),
1363 'format' => '',
1364 'prev_text' => __('&laquo; Previous', 'mxchat'),
1365 'next_text' => __('Next &raquo;', 'mxchat'),
1366 'total' => $total_pages,
1367 'current' => $current_page,
1368 ));
1369
1370 // Retrieve processing statuses
1371 $pdf_url = get_transient('mxchat_last_pdf_url');
1372 $sitemap_url = get_transient('mxchat_last_sitemap_url');
1373 $pdf_status = $pdf_url ? $this->get_pdf_processing_status($pdf_url) : false;
1374 $sitemap_status = $sitemap_url ? $this->get_sitemap_processing_status($sitemap_url) : false;
1375
1376 if ($pdf_status && $pdf_status['status'] === 'complete') {
1377 delete_transient('mxchat_last_pdf_url');
1378 $pdf_status = false;
1379 }
1380 if ($sitemap_status && $sitemap_status['status'] === 'complete') {
1381 delete_transient('mxchat_last_sitemap_url');
1382 $sitemap_status = false;
1383 }
1384
1385 $is_processing = ($pdf_status && ($pdf_status['status'] === 'processing' || $pdf_status['status'] === 'error'))
1386 || ($sitemap_status && ($sitemap_status['status'] === 'processing' || $sitemap_status['status'] === 'error'));
1387 ?>
1388
1389
1390
1391 <div class="wrap mxchat-wrapper">
1392 <!-- Hero Section -->
1393 <div class="mxchat-hero">
1394 <h1 class="mxchat-main-title">
1395 <span class="mxchat-gradient-text">Knowledge Base</span> Manager
1396 </h1>
1397 <p class="mxchat-hero-subtitle">
1398 <?php esc_html_e('Enhance your AI chatbot with custom knowledge. Import, manage, and organize your content to keep responses accurate and relevant.', 'mxchat'); ?>
1399 </p>
1400 </div>
1401
1402 <div class="mxchat-content">
1403
1404 <!-- Tab Navigation -->
1405 <div class="mxchat-kb-tabs-nav">
1406 <button class="mxchat-kb-tab-button active" data-tab="import">
1407 <?php esc_html_e('Knowledge Import', 'mxchat'); ?>
1408 </button>
1409 <button class="mxchat-kb-tab-button" data-tab="sync">
1410 <?php esc_html_e('Auto-Sync Settings', 'mxchat'); ?>
1411 </button>
1412 </div>
1413
1414
1415 <div class="mxchat-kb-tabs-content">
1416 <div id="mxchat-kb-tab-import" class="mxchat-kb-tab-content active">
1417 <!-- Import Options Card -->
1418 <div class="mxchat-card">
1419
1420 <h2><?php esc_html_e('Knowledge Import Settings', 'mxchat'); ?></h2>
1421
1422 <?php
1423 // Check if the appropriate embedding API key exists
1424 $embedding_model = isset($this->options['embedding_model']) ? esc_attr($this->options['embedding_model']) : 'text-embedding-ada-002';
1425 $has_openai_key = !empty($this->options['api_key']);
1426 $has_voyage_key = !empty($this->options['voyage_api_key']);
1427
1428 // Determine if they have the needed API key for their selected embedding model
1429 $has_required_key = false;
1430 $required_key_type = '';
1431
1432 if (strpos($embedding_model, 'text-embedding-') !== false && $has_openai_key) {
1433 $has_required_key = true;
1434 $required_key_type = 'OpenAI';
1435 } elseif (strpos($embedding_model, 'voyage-') !== false && $has_voyage_key) {
1436 $has_required_key = true;
1437 $required_key_type = 'Voyage AI';
1438 } elseif (strpos($embedding_model, 'text-embedding-') !== false) {
1439 $required_key_type = 'OpenAI';
1440 } elseif (strpos($embedding_model, 'voyage-') !== false) {
1441 $required_key_type = 'Voyage AI';
1442 }
1443 ?>
1444
1445 <div class="mxchat-knowledge-warning <?php echo $has_required_key ? 'success' : 'warning'; ?>">
1446 <?php if ($has_required_key): ?>
1447 <p><span class="dashicons dashicons-yes-alt"></span> <?php echo wp_kses_post(sprintf(__('We detected your %s API key. <strong>You must have added credits to your %s account</strong> before using the knowledgebase.', 'mxchat'), $required_key_type, $required_key_type)); ?></p>
1448 <?php else: ?>
1449 <p><span class="dashicons dashicons-warning"></span> <strong><?php esc_html_e('Important:', 'mxchat'); ?></strong> <?php echo sprintf(esc_html__('Before importing knowledge, you must add a %s API key with sufficient credits in the Chatbot settings.', 'mxchat'), $required_key_type); ?> <a href="<?php echo admin_url('admin.php?page=mxchat-max'); ?>"><?php esc_html_e('Go to API Key Settings', 'mxchat'); ?></a></p>
1450 <?php endif; ?>
1451 </div>
1452
1453
1454
1455 <!-- Import Options Section -->
1456 <div class="mxchat-import-section">
1457 <h3><?php esc_html_e('Import Options', 'mxchat'); ?></h3>
1458
1459 <!-- Import Options Grid -->
1460 <div class="mxchat-import-options">
1461 <!-- WordPress Import Option -->
1462 <button type="button" id="mxchat-open-content-selector" class="mxchat-import-box mxchat-import-wordpress" data-option="wordpress">
1463 <div class="mxchat-import-icon">
1464 <span class="dashicons dashicons-wordpress"></span>
1465 </div>
1466 <div class="mxchat-import-content">
1467 <h4><?php esc_html_e('WordPress Content', 'mxchat'); ?></h4>
1468 <p><?php esc_html_e('Import specific posts and pages to your knowledge base.', 'mxchat'); ?></p>
1469 </div>
1470 <div class="mxchat-recommended-tag"><?php esc_html_e('Recommended', 'mxchat'); ?></div>
1471 </button>
1472
1473 <!-- PDF Import Option -->
1474 <button type="button" class="mxchat-import-box" data-option="pdf" data-placeholder="<?php esc_attr_e('Enter PDF URL here', 'mxchat'); ?>" data-type="pdf">
1475 <div class="mxchat-import-icon">
1476 <span class="dashicons dashicons-media-document"></span>
1477 </div>
1478 <div class="mxchat-import-content">
1479 <h4><?php esc_html_e('PDF Import', 'mxchat'); ?></h4>
1480 <p><?php esc_html_e('Import knowledge from PDF documents.', 'mxchat'); ?></p>
1481 </div>
1482 </button>
1483
1484 <!-- Sitemap Import Option -->
1485 <button type="button" class="mxchat-import-box" data-option="sitemap" data-placeholder="<?php esc_attr_e('Enter sitemap URL here', 'mxchat'); ?>" data-type="sitemap">
1486 <div class="mxchat-import-icon">
1487 <span class="dashicons dashicons-admin-site-alt"></span>
1488 </div>
1489 <div class="mxchat-import-content">
1490 <h4><?php esc_html_e('Sitemap Import', 'mxchat'); ?></h4>
1491 <p><?php esc_html_e('Use a content-specific sub-sitemap, not the sitemap index.', 'mxchat'); ?></p>
1492 </div>
1493 </button>
1494
1495 <!-- Direct URL Import Option -->
1496 <button type="button" class="mxchat-import-box" data-option="url" data-placeholder="<?php esc_attr_e('Enter webpage URL here', 'mxchat'); ?>" data-type="url">
1497 <div class="mxchat-import-icon">
1498 <span class="dashicons dashicons-admin-links"></span>
1499 </div>
1500 <div class="mxchat-import-content">
1501 <h4><?php esc_html_e('Direct URL', 'mxchat'); ?></h4>
1502 <p><?php esc_html_e('Import content from any webpage.', 'mxchat'); ?></p>
1503 </div>
1504 </button>
1505
1506 <!-- Direct Content Import Option -->
1507 <button type="button" class="mxchat-import-box" data-option="content" data-type="content">
1508 <div class="mxchat-import-icon">
1509 <span class="dashicons dashicons-editor-paste-text"></span>
1510 </div>
1511 <div class="mxchat-import-content">
1512 <h4><?php esc_html_e('Direct Content', 'mxchat'); ?></h4>
1513 <p><?php esc_html_e('Submit content to be vectorized.', 'mxchat'); ?></p>
1514 </div>
1515 </button>
1516 </div>
1517
1518 <!-- Input Areas for URL and Content -->
1519 <div class="mxchat-import-input-area" id="mxchat-url-input-area" style="display: none;">
1520 <?php if (!$is_processing) : ?>
1521 <form id="mxchat-url-form" method="post" action="<?php echo esc_url(admin_url('admin-post.php?action=mxchat_submit_sitemap')); ?>">
1522 <?php wp_nonce_field('mxchat_submit_sitemap_action', 'mxchat_submit_sitemap_nonce'); ?>
1523 <input type="hidden" name="import_type" id="import_type" value="url">
1524 <div class="mxchat-url-input-group">
1525 <input type="url"
1526 name="sitemap_url"
1527 id="sitemap_url"
1528 placeholder="<?php esc_attr_e('Enter URL here', 'mxchat'); ?>"
1529 required />
1530 <button type="submit"
1531 name="submit_sitemap"
1532 class="mxchat-button-primary">
1533 <?php esc_html_e('Import', 'mxchat'); ?>
1534 </button>
1535 </div>
1536 <p class="mxchat-url-description" id="url-description-text"></p>
1537 </form>
1538 <?php endif; ?>
1539 </div>
1540
1541 <div class="mxchat-import-input-area" id="mxchat-content-input-area" style="display: none;">
1542 <?php if (!$is_processing) : ?>
1543 <form id="mxchat-content-form" method="post" action="<?php echo esc_url(admin_url('admin-post.php?action=mxchat_submit_content')); ?>">
1544 <?php wp_nonce_field('mxchat_submit_content_action', 'mxchat_submit_content_nonce'); ?>
1545 <div class="mxchat-form-group">
1546 <textarea
1547 name="article_content"
1548 id="article_content"
1549 placeholder="<?php esc_attr_e('Enter your content here...', 'mxchat'); ?>"
1550 required
1551 rows="6"
1552 ></textarea>
1553 </div>
1554 <div class="mxchat-form-group">
1555 <input type="url"
1556 name="article_url"
1557 id="article_url"
1558 placeholder="<?php esc_attr_e('Enter source URL (Optional)', 'mxchat'); ?>">
1559 </div>
1560 <button type="submit"
1561 name="submit_content"
1562 class="mxchat-button-primary">
1563 <?php esc_html_e('Import Content', 'mxchat'); ?>
1564 </button>
1565 </form>
1566 <?php endif; ?>
1567 </div>
1568 </div>
1569
1570 <!-- Processing Status -->
1571 <?php if ($pdf_status && $pdf_status['status'] !== 'complete') : ?>
1572 <div class="mxchat-status-card">
1573 <div class="mxchat-status-header">
1574 <h4><?php esc_html_e('PDF Processing Status', 'mxchat'); ?></h4>
1575 <?php if ($is_processing) : ?>
1576 <form method="post" class="mxchat-stop-form"
1577 action="<?php echo esc_url(admin_url('admin-post.php?action=mxchat_stop_processing')); ?>">
1578 <?php wp_nonce_field('mxchat_stop_processing_action', 'mxchat_stop_processing_nonce'); ?>
1579 <button type="submit" name="stop_processing" class="mxchat-button-secondary">
1580 <?php esc_html_e('Stop Processing', 'mxchat'); ?>
1581 </button>
1582 </form>
1583 <?php endif; ?>
1584
1585 <?php if ($pdf_status['status'] === 'error') : ?>
1586 <span class="mxchat-status-badge mxchat-status-failed"><?php esc_html_e('Error', 'mxchat'); ?></span>
1587 <?php endif; ?>
1588 </div>
1589 <div class="mxchat-progress-bar">
1590 <div class="mxchat-progress-fill" style="width: <?php echo esc_attr($pdf_status['percentage']); ?>%"></div>
1591 </div>
1592 <div class="mxchat-status-details">
1593 <p><?php printf(
1594 esc_html__('Progress: %1$d of %2$d pages (%3$d%%)', 'mxchat'),
1595 absint($pdf_status['processed_pages']),
1596 absint($pdf_status['total_pages']),
1597 absint($pdf_status['percentage'])
1598 ); ?></p>
1599 <p><?php printf(
1600 esc_html__('Status: %s', 'mxchat'),
1601 esc_html(ucfirst($pdf_status['status']))
1602 ); ?></p>
1603 <p><?php printf(
1604 esc_html__('Last update: %s', 'mxchat'),
1605 esc_html($pdf_status['last_update'])
1606 ); ?></p>
1607
1608 <?php if (!empty($pdf_status['error'])) : ?>
1609 <div class="mxchat-error-notice">
1610 <p class="error"><?php echo esc_html($pdf_status['error']); ?></p>
1611 </div>
1612 <?php endif; ?>
1613 </div>
1614 </div>
1615 <?php endif; ?>
1616
1617 <!-- Single URL Submission Status -->
1618 <?php
1619 // Get single URL status
1620 $single_url_status = $this->get_single_url_status();
1621 $is_active_processing =
1622 ($sitemap_status && $sitemap_status['status'] === 'processing') ||
1623 ($pdf_status && $pdf_status['status'] === 'processing');
1624 ?>
1625
1626 <div id="mxchat-single-url-status-container" <?php echo $is_active_processing ? 'style="display:none;"' : ''; ?>>
1627 <?php if ($single_url_status && !$is_active_processing) : ?>
1628 <div class="mxchat-status-card">
1629 <div class="mxchat-status-header">
1630 <h4><?php esc_html_e('Last URL Submission', 'mxchat'); ?></h4>
1631 <?php if ($single_url_status['status'] === 'failed') : ?>
1632 <span class="mxchat-status-badge mxchat-status-failed"><?php esc_html_e('Failed', 'mxchat'); ?></span>
1633 <?php else : ?>
1634 <span class="mxchat-status-badge mxchat-status-success"><?php esc_html_e('Success', 'mxchat'); ?></span>
1635 <?php endif; ?>
1636 </div>
1637 <div class="mxchat-status-details">
1638 <p><strong><?php esc_html_e('URL:', 'mxchat'); ?></strong>
1639 <a href="<?php echo esc_url($single_url_status['url']); ?>" target="_blank">
1640 <?php echo esc_html(strlen($single_url_status['url']) > 60 ? substr($single_url_status['url'], 0, 57) . '...' : $single_url_status['url']); ?>
1641 </a>
1642 </p>
1643 <p><strong><?php esc_html_e('Submitted:', 'mxchat'); ?></strong> <?php echo esc_html($single_url_status['human_time']); ?></p>
1644
1645 <?php if ($single_url_status['status'] === 'failed' && !empty($single_url_status['error'])) : ?>
1646 <div class="mxchat-error-notice">
1647 <p class="error"><?php echo esc_html($single_url_status['error']); ?></p>
1648 </div>
1649 <?php endif; ?>
1650
1651 <?php if ($single_url_status['status'] === 'complete') : ?>
1652 <p><strong><?php esc_html_e('Content Length:', 'mxchat'); ?></strong> <?php echo esc_html($single_url_status['content_length']); ?> <?php esc_html_e('characters', 'mxchat'); ?></p>
1653 <p><strong><?php esc_html_e('Embedding Dimensions:', 'mxchat'); ?></strong> <?php echo esc_html($single_url_status['embedding_dimensions']); ?></p>
1654 <?php endif; ?>
1655 </div>
1656 </div>
1657 <?php endif; ?>
1658 </div>
1659
1660 <!-- Sitemap Processing Status -->
1661 <?php if ($sitemap_status && $sitemap_status['status'] !== 'complete') : ?>
1662 <div class="mxchat-status-card">
1663 <div class="mxchat-status-header">
1664 <h4><?php esc_html_e('Sitemap Processing Status', 'mxchat'); ?></h4>
1665 <?php if ($is_processing) : ?>
1666 <form method="post" class="mxchat-stop-form"
1667 action="<?php echo esc_url(admin_url('admin-post.php?action=mxchat_stop_processing')); ?>">
1668 <?php wp_nonce_field('mxchat_stop_processing_action', 'mxchat_stop_processing_nonce'); ?>
1669 <button type="submit" name="stop_processing" class="mxchat-button-secondary">
1670 <?php esc_html_e('Stop Processing', 'mxchat'); ?>
1671 </button>
1672 </form>
1673 <?php endif; ?>
1674 </div>
1675 <div class="mxchat-progress-bar">
1676 <div class="mxchat-progress-fill" style="width: <?php echo esc_attr($sitemap_status['percentage']); ?>%"></div>
1677 </div>
1678 <div class="mxchat-status-details">
1679 <p><?php printf(
1680 esc_html__('Progress: %1$d of %2$d URLs (%3$d%%)', 'mxchat'),
1681 absint($sitemap_status['processed_urls']),
1682 absint($sitemap_status['total_urls']),
1683 absint($sitemap_status['percentage'])
1684 ); ?></p>
1685 <?php if (!empty($sitemap_status['error']) || !empty($sitemap_status['last_error'])) : ?>
1686 <div class="mxchat-error-notice">
1687 <?php if (!empty($sitemap_status['error'])) : ?>
1688 <p class="error"><?php echo esc_html($sitemap_status['error']); ?></p>
1689 <?php endif; ?>
1690 <?php if (!empty($sitemap_status['last_error'])) : ?>
1691 <p class="last-error"><?php echo esc_html__('Last error:', 'mxchat') . ' ' . esc_html($sitemap_status['last_error']); ?></p>
1692 <?php endif; ?>
1693 </div>
1694 <?php endif; ?>
1695
1696 <?php if (!empty($sitemap_status['failed_urls']) && $sitemap_status['failed_urls'] > 0) : ?>
1697 <div class="mxchat-failed-urls">
1698 <h5><?php echo sprintf(esc_html__('Failed URLs (%d)', 'mxchat'), absint($sitemap_status['failed_urls'])); ?></h5>
1699 <?php if (!empty($sitemap_status['failed_urls_list'])) : ?>
1700 <div class="mxchat-failed-urls-list">
1701 <table class="widefat striped">
1702 <thead>
1703 <tr>
1704 <th><?php esc_html_e('URL', 'mxchat'); ?></th>
1705 <th><?php esc_html_e('Error', 'mxchat'); ?></th>
1706 <th><?php esc_html_e('Time', 'mxchat'); ?></th>
1707 </tr>
1708 </thead>
1709 <tbody>
1710 <?php foreach ($sitemap_status['failed_urls_list'] as $failed_url) : ?>
1711 <tr>
1712 <td style="word-break: break-all;">
1713 <a href="<?php echo esc_url($failed_url['url']); ?>" target="_blank" rel="noopener noreferrer">
1714 <?php echo esc_html(strlen($failed_url['url']) > 60 ? substr($failed_url['url'], 0, 57) . '...' : $failed_url['url']); ?>
1715 </a>
1716 </td>
1717 <td><?php echo esc_html($failed_url['error']); ?></td>
1718 <td><?php echo esc_html(human_time_diff($failed_url['time'], time()) . ' ' . __('ago', 'mxchat')); ?></td>
1719 </tr>
1720 <?php endforeach; ?>
1721 </tbody>
1722 </table>
1723 </div>
1724 <?php endif; ?>
1725 </div>
1726 <?php endif; ?>
1727 </div>
1728 </div>
1729 <?php endif; ?>
1730 </div>
1731 </div>
1732
1733 <!-- Sync Settings Tab (Initially Hidden) -->
1734 <div id="mxchat-kb-tab-sync" class="mxchat-kb-tab-content">
1735 <div class="mxchat-card">
1736 <!-- Auto-Sync Settings -->
1737 <div class="mxchat-settings-section">
1738 <h3><?php esc_html_e('Auto-Sync Settings', 'mxchat'); ?></h3>
1739 <p class="mxchat-description">
1740 <?php esc_html_e('Note: Auto-sync works only for newly published content. Existing posts and pages must be imported manually below. Works with Pinecone if enabled', 'mxchat'); ?>
1741 </p>
1742 <div class="mxchat-autosave-section">
1743 <div class="mxchat-toggle-group">
1744 <div class="mxchat-toggle-container">
1745 <label class="mxchat-toggle-switch">
1746 <input type="checkbox"
1747 name="mxchat_auto_sync_posts"
1748 class="mxchat-autosave-field"
1749 value="1"
1750 data-nonce="<?php echo wp_create_nonce('mxchat_prompts_setting_nonce'); ?>"
1751 <?php checked(get_option('mxchat_auto_sync_posts', '0'), '1'); ?>>
1752 <span class="mxchat-toggle-slider"></span>
1753 </label>
1754 <span class="mxchat-toggle-label">
1755 <?php esc_html_e('Auto-sync Posts', 'mxchat'); ?>
1756 </span>
1757 </div>
1758
1759 <div class="mxchat-toggle-container">
1760 <label class="mxchat-toggle-switch">
1761 <input type="checkbox"
1762 name="mxchat_auto_sync_pages"
1763 class="mxchat-autosave-field"
1764 value="1"
1765 data-nonce="<?php echo wp_create_nonce('mxchat_prompts_setting_nonce'); ?>"
1766 <?php checked(get_option('mxchat_auto_sync_pages', '0'), '1'); ?>>
1767 <span class="mxchat-toggle-slider"></span>
1768 </label>
1769 <span class="mxchat-toggle-label">
1770 <?php esc_html_e('Auto-sync Pages', 'mxchat'); ?>
1771 </span>
1772 </div>
1773
1774 <!-- Custom Post Types Section -->
1775 <div class="mxchat-section-content">
1776 <div class="mxchat-custom-post-types-header">
1777 <button id="mxchat-custom-post-types-toggle" class="mxchat-button-secondary">
1778 <?php esc_html_e('Advanced Custom Post Sync Settings', 'mxchat'); ?>
1779 <span class="mxchat-toggle-icon"></span>
1780 </button>
1781 </div>
1782
1783 <div id="mxchat-custom-post-types-container" class="mxchat-custom-post-types-container" style="display: none;">
1784 <h3><?php esc_html_e('Sync Custom Post Types', 'mxchat'); ?></h3>
1785 <p><?php esc_html_e('Select additional custom post types to automatically sync with the chatbot.', 'mxchat'); ?></p>
1786
1787 <div class="mxchat-custom-post-types">
1788 <?php
1789 $post_types = $this->get_public_post_types();
1790
1791 // Skip post and page as they're handled separately
1792 unset($post_types['post']);
1793 unset($post_types['page']);
1794
1795 if (!empty($post_types)) {
1796 foreach ($post_types as $post_type => $label) {
1797 $option_name = 'mxchat_auto_sync_' . $post_type;
1798 $is_enabled = get_option($option_name, '0');
1799 ?>
1800 <div class="mxchat-toggle-container">
1801 <label class="mxchat-toggle-switch">
1802 <input type="checkbox"
1803 name="<?php echo esc_attr($option_name); ?>"
1804 class="mxchat-autosave-field"
1805 value="1"
1806 data-nonce="<?php echo wp_create_nonce('mxchat_prompts_setting_nonce'); ?>"
1807 <?php checked($is_enabled, '1'); ?>>
1808 <span class="mxchat-toggle-slider"></span>
1809 </label>
1810 <span class="mxchat-toggle-label">
1811 <?php echo esc_html($label); ?> (<?php echo esc_html($post_type); ?>)
1812 </span>
1813 </div>
1814 <?php
1815 }
1816 } else {
1817 echo '<p>' . esc_html__('No custom post types found.', 'mxchat') . '</p>';
1818 }
1819 ?>
1820 </div>
1821 </div>
1822 </div>
1823 </div>
1824 </div>
1825 </div>
1826 </div>
1827 </div>
1828
1829 </div>
1830
1831 <!-- Knowledge Base Table Card -->
1832 <div class="mxchat-card">
1833 <div class="mxchat-card-header">
1834 <h2>
1835 <?php esc_html_e('Knowledge Base', 'mxchat'); ?>
1836 <span class="mxchat-record-count">
1837 <?php
1838 if ($prompts) {
1839 $total_records = count($prompts);
1840 if ($total_pages > 1) {
1841 // If pagination is active, get the total count from the database
1842 global $wpdb;
1843 $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
1844 $search_condition = $search_query ? $wpdb->prepare("WHERE article_content LIKE %s", '%' . $wpdb->esc_like($search_query) . '%') : '';
1845 $total_records = $wpdb->get_var("SELECT COUNT(*) FROM $table_name $search_condition");
1846 }
1847 echo '(' . esc_html($total_records) . ')';
1848 } else {
1849 echo '(0)';
1850 }
1851 ?>
1852 </span>
1853 </h2>
1854 <div class="mxchat-header-actions">
1855 <!-- Search -->
1856 <form method="get" id="knowledge-search" class="mxchat-search-form">
1857 <?php wp_nonce_field('mxchat_prompts_search_nonce'); ?>
1858 <input type="hidden" name="page" value="mxchat-prompts" />
1859 <div class="mxchat-search-group">
1860 <span class="dashicons dashicons-search"></span>
1861 <input type="text"
1862 name="search"
1863 placeholder="<?php esc_attr_e('Search Knowledge', 'mxchat'); ?>"
1864 value="<?php echo esc_attr($search_query); ?>" />
1865 </div>
1866 </form>
1867
1868 <!-- Delete All -->
1869 <form method="post"
1870 action="<?php echo esc_url(admin_url('admin-post.php?action=mxchat_delete_all_prompts')); ?>"
1871 class="mxchat-delete-form"
1872 onsubmit="return confirm('<?php esc_attr_e('Are you sure you want to delete all knowledge? This action cannot be undone.', 'mxchat'); ?>');">
1873 <?php wp_nonce_field('mxchat_delete_all_prompts_action', 'mxchat_delete_all_prompts_nonce'); ?>
1874 <button type="submit" name="delete_all_prompts" class="mxchat-button-danger">
1875 <span class="dashicons dashicons-trash"></span>
1876 <?php esc_html_e('Delete All', 'mxchat'); ?>
1877 </button>
1878 </form>
1879 </div>
1880 </div>
1881
1882 <!-- Table -->
1883 <div class="mxchat-table-wrapper">
1884 <table class="mxchat-records-table">
1885 <thead>
1886 <tr>
1887 <th><?php esc_html_e('ID', 'mxchat'); ?></th>
1888 <th><?php esc_html_e('Content', 'mxchat'); ?></th>
1889 <th><?php esc_html_e('Source', 'mxchat'); ?></th>
1890 <th><?php esc_html_e('Actions', 'mxchat'); ?></th>
1891 </tr>
1892 </thead>
1893 <tbody>
1894 <?php if ($prompts) : ?>
1895 <?php foreach ($prompts as $prompt) : ?>
1896 <tr id="prompt-<?php echo esc_attr($prompt->id); ?>">
1897 <td><?php echo esc_html($prompt->id); ?></td>
1898 <td class="mxchat-content-cell">
1899 <div class="content-view">
1900 <?php
1901 $content = $prompt->article_content;
1902 // Check if content contains Hebrew characters
1903 if (preg_match('/[\x{0590}-\x{05FF}]/u', $content)) {
1904 // Apply RTL direction for Hebrew content
1905 echo '<div dir="rtl" lang="he" class="rtl-content">';
1906 echo wp_kses_post(wpautop(esc_textarea($content)));
1907 echo '</div>';
1908 } else {
1909 echo wp_kses_post(wpautop(esc_textarea($content)));
1910 }
1911 ?>
1912 </div>
1913 <textarea class="content-edit" style="display:none;" <?php if (preg_match('/[\x{0590}-\x{05FF}]/u', $prompt->article_content)) echo 'dir="rtl" lang="he"'; ?>>
1914 <?php echo esc_textarea($prompt->article_content); ?>
1915 </textarea>
1916 </td>
1917 <td class="mxchat-url-cell">
1918 <div class="url-view">
1919 <?php if (!empty($prompt->source_url)) : ?>
1920 <a href="<?php echo esc_url($prompt->source_url); ?>" target="_blank">
1921 <span class="dashicons dashicons-external"></span>
1922 <?php esc_html_e('View Source', 'mxchat'); ?>
1923 </a>
1924 <?php else : ?>
1925 <span class="mxchat-na"><?php esc_html_e('N/A', 'mxchat'); ?></span>
1926 <?php endif; ?>
1927 </div>
1928 <input type="text" class="url-edit" style="display:none;" value="<?php echo esc_attr($prompt->source_url); ?>" />
1929 </td>
1930 <td class="mxchat-actions-cell">
1931 <button class="mxchat-button-icon edit-button"
1932 data-id="<?php echo esc_attr($prompt->id); ?>">
1933 <span class="dashicons dashicons-edit"></span>
1934 </button>
1935 <button class="mxchat-button-icon save-button"
1936 data-id="<?php echo esc_attr($prompt->id); ?>"
1937 style="display:none;"
1938 data-nonce="<?php echo wp_create_nonce('mxchat_save_inline_nonce'); ?>">
1939 <span class="dashicons dashicons-saved"></span>
1940 </button>
1941 <a href="<?php echo esc_url(admin_url(
1942 'admin-post.php?action=mxchat_delete_prompt&id=' . esc_attr($prompt->id)
1943 . '&_wpnonce=' . wp_create_nonce('mxchat_delete_prompt_nonce')
1944 )); ?>"
1945 class="mxchat-button-icon delete-button"
1946 onclick="return confirm('<?php esc_attr_e('Are you sure you want to delete this entry?', 'mxchat'); ?>');">
1947 <span class="dashicons dashicons-trash"></span>
1948 </a>
1949 </td>
1950 </tr>
1951 <?php endforeach; ?>
1952 <?php else : ?>
1953 <tr>
1954 <td colspan="4" class="mxchat-no-records">
1955 <?php esc_html_e('No knowledge base entries found.', 'mxchat'); ?>
1956 </td>
1957 </tr>
1958 <?php endif; ?>
1959 </tbody>
1960 </table>
1961 </div>
1962
1963 <?php if ($page_links) : ?>
1964 <div class="mxchat-pagination">
1965 <?php echo wp_kses_post($page_links); ?>
1966 </div>
1967 <?php endif; ?>
1968 </div>
1969
1970 </div>
1971 </div>
1972
1973
1974 <!-- Content Selector Modal -->
1975 <div id="mxchat-kb-content-selector-modal" class="mxchat-kb-modal">
1976 <div class="mxchat-kb-modal-content">
1977 <div class="mxchat-kb-modal-header">
1978 <h3>
1979 <?php esc_html_e('Select WordPress Content', 'mxchat'); ?><br>
1980 <span class="mxchat-kb-header-note"><?php esc_html_e('(Content imported here will be tagged "In Knowledge Base")', 'mxchat'); ?></span>
1981 </h3>
1982 <span class="mxchat-kb-modal-close">&times;</span>
1983 </div>
1984 <div class="mxchat-kb-modal-filters">
1985 <div class="mxchat-kb-search-group">
1986 <input type="text" id="mxchat-kb-content-search" placeholder="<?php esc_attr_e('Search...', 'mxchat'); ?>">
1987 </div>
1988
1989 <div class="mxchat-kb-filter-group">
1990 <select id="mxchat-kb-content-type-filter">
1991 <option value="all"><?php esc_html_e('All Content Types', 'mxchat'); ?></option>
1992 <option value="post"><?php esc_html_e('Posts', 'mxchat'); ?></option>
1993 <option value="page"><?php esc_html_e('Pages', 'mxchat'); ?></option>
1994 <?php
1995 // Add other post types dynamically
1996 $post_types = get_post_types(array('public' => true), 'objects');
1997 foreach ($post_types as $post_type) {
1998 // Skip post and page as they're already added
1999 if (!in_array($post_type->name, array('post', 'page'))) {
2000 echo '<option value="' . esc_attr($post_type->name) . '">' . esc_html($post_type->label) . '</option>';
2001 }
2002 }
2003 ?>
2004 </select>
2005
2006 <select id="mxchat-kb-content-status-filter">
2007 <option value="publish"><?php esc_html_e('Published', 'mxchat'); ?></option>
2008 <option value="draft"><?php esc_html_e('Drafts', 'mxchat'); ?></option>
2009 <option value="all"><?php esc_html_e('All Statuses', 'mxchat'); ?></option>
2010 </select>
2011
2012 <select id="mxchat-kb-processed-filter">
2013 <option value="all"><?php esc_html_e('All Content', 'mxchat'); ?></option>
2014 <option value="processed"><?php esc_html_e('In Knowledge Base', 'mxchat'); ?></option>
2015 <option value="unprocessed"><?php esc_html_e('Not In Knowledge Base', 'mxchat'); ?></option>
2016 </select>
2017 </div>
2018 </div>
2019
2020 <div class="mxchat-kb-content-selection">
2021 <div class="mxchat-kb-selection-header">
2022 <label>
2023 <input type="checkbox" id="mxchat-kb-select-all">
2024 <?php esc_html_e('Select All', 'mxchat'); ?>
2025 </label>
2026 <span class="mxchat-kb-selection-count">0 <?php esc_html_e('selected', 'mxchat'); ?></span>
2027 </div>
2028
2029 <div class="mxchat-kb-content-list">
2030 <!-- Content will be loaded here via AJAX -->
2031 <div class="mxchat-kb-loading">
2032 <span class="mxchat-kb-spinner is-active"></span>
2033 <?php esc_html_e('Loading content...', 'mxchat'); ?>
2034 </div>
2035 </div>
2036
2037 <div class="mxchat-kb-pagination">
2038 <!-- Pagination will be added here -->
2039 </div>
2040 </div>
2041
2042 <div class="mxchat-kb-modal-footer">
2043 <button type="button" id="mxchat-kb-process-selected" class="mxchat-kb-button-primary" disabled>
2044 <?php esc_html_e('Process Selected Content', 'mxchat'); ?>
2045 <span class="mxchat-kb-selected-count">(0)</span>
2046 </button>
2047 <button type="button" class="mxchat-kb-button-secondary mxchat-kb-modal-close">
2048 <?php esc_html_e('Cancel', 'mxchat'); ?>
2049 </button>
2050 </div>
2051 </div>
2052 </div>
2053
2054 <?php
2055 }
2056
2057 public function mxchat_handle_delete_all_prompts() {
2058 // Verify nonce
2059 if (!isset($_POST['mxchat_delete_all_prompts_nonce']) || !wp_verify_nonce($_POST['mxchat_delete_all_prompts_nonce'], 'mxchat_delete_all_prompts_action')) {
2060 wp_die(__('Nonce verification failed.', 'mxchat'));
2061 }
2062
2063 // Check permissions
2064 if (!current_user_can('manage_options')) {
2065 wp_die(__('You do not have sufficient permissions to delete all prompts.', 'mxchat'));
2066 }
2067
2068 global $wpdb;
2069 $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
2070
2071 // Delete all prompts from the table
2072 $wpdb->query("DELETE FROM {$table_name}");
2073
2074 // Clear relevant cache
2075 wp_cache_delete('all_prompts', 'mxchat_prompts');
2076
2077 // Redirect back with a success message
2078 $redirect_url = add_query_arg(array(
2079 'page' => 'mxchat-prompts',
2080 'all_deleted' => 'true'
2081 ), admin_url('admin.php'));
2082
2083 wp_safe_redirect($redirect_url);
2084 exit;
2085 }
2086 public function mxchat_handle_delete_prompt() {
2087 // Sanitize and validate nonce
2088 $nonce = isset($_GET['_wpnonce']) ? sanitize_text_field(wp_unslash($_GET['_wpnonce'])) : '';
2089 if (empty($nonce) || !wp_verify_nonce($nonce, 'mxchat_delete_prompt_nonce')) {
2090 wp_die(esc_html__('Nonce verification failed.', 'mxchat'));
2091 }
2092
2093 // Check permissions
2094 if (!current_user_can('manage_options')) {
2095 wp_die(esc_html__('You do not have sufficient permissions to delete prompts.', 'mxchat'));
2096 }
2097
2098 // Validate and sanitize ID parameter
2099 $id = isset($_GET['id']) ? intval($_GET['id']) : 0;
2100 if ($id <= 0) {
2101 wp_die(esc_html__('Invalid prompt ID.', 'mxchat'));
2102 }
2103
2104 global $wpdb;
2105 $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
2106
2107 // Clear cache and delete prompt
2108 wp_cache_delete('prompt_' . $id, 'mxchat_prompts');
2109 $wpdb->delete($table_name, array('id' => $id), array('%d'));
2110
2111 wp_safe_redirect(add_query_arg(array('page' => 'mxchat-prompts', 'deleted' => 'true'), admin_url('admin.php')));
2112 exit;
2113 }
2114
2115
2116 public function mxchat_generate_embedding($text) {
2117 // Enable detailed logging for debugging
2118 //error_log('[MXCHAT-EMBED] Starting embedding generation. Text length: ' . strlen($text) . ' bytes');
2119 //error_log('[MXCHAT-EMBED] Text preview: ' . substr($text, 0, 100) . '...');
2120
2121 $options = get_option('mxchat_options');
2122 $selected_model = $options['embedding_model'] ?? 'text-embedding-ada-002';
2123 //error_log('[MXCHAT-EMBED] Selected embedding model: ' . $selected_model);
2124
2125 // Determine provider and endpoint
2126 if (strpos($selected_model, 'voyage') === 0) {
2127 $api_key = $options['voyage_api_key'] ?? '';
2128 $endpoint = 'https://api.voyageai.com/v1/embeddings';
2129 $provider_name = 'Voyage AI';
2130 //error_log('[MXCHAT-EMBED] Using Voyage AI API');
2131 } else {
2132 $api_key = $options['api_key'] ?? '';
2133 $endpoint = 'https://api.openai.com/v1/embeddings';
2134 $provider_name = 'OpenAI';
2135 //error_log('[MXCHAT-EMBED] Using OpenAI API');
2136 }
2137
2138 //error_log('[MXCHAT-EMBED] Using endpoint: ' . $endpoint);
2139
2140 if (empty($api_key)) {
2141 $error_message = sprintf('Missing %s API key. Please configure your API key in the MxChat settings.', $provider_name);
2142 //error_log('[MXCHAT-EMBED] Error: ' . $error_message);
2143 return $error_message;
2144 }
2145
2146 // Check if text is too long (for OpenAI, roughly estimate tokens as words/0.75)
2147 $estimated_tokens = ceil(str_word_count($text) / 0.75);
2148 //error_log('[MXCHAT-EMBED] Estimated token count: ~' . $estimated_tokens);
2149
2150 if ($estimated_tokens > 8000 && strpos($selected_model, 'voyage') === false) {
2151 //error_log('[MXCHAT-EMBED] Warning: Text may exceed OpenAI token limits (8K for most models)');
2152 // Consider truncating text here
2153 }
2154
2155 // Prepare request body
2156 $request_body = array(
2157 'model' => $selected_model,
2158 'input' => $text
2159 );
2160
2161 // Add output_dimension for voyage-3-large model
2162 if ($selected_model === 'voyage-3-large') {
2163 $request_body['output_dimension'] = 2048;
2164 }
2165
2166 //error_log('[MXCHAT-EMBED] Request prepared with model: ' . $selected_model);
2167
2168 // Make API request
2169 //error_log('[MXCHAT-EMBED] Sending API request to: ' . $endpoint);
2170 $response = wp_remote_post($endpoint, array(
2171 'body' => wp_json_encode($request_body),
2172 'headers' => array(
2173 'Authorization' => 'Bearer ' . $api_key,
2174 'Content-Type' => 'application/json'
2175 ),
2176 'timeout' => 60 // Increased timeout for large inputs
2177 ));
2178
2179 // Handle wp_remote_post errors
2180 if (is_wp_error($response)) {
2181 $error_message = $response->get_error_message();
2182 //error_log('[MXCHAT-EMBED] WP Remote Post Error: ' . $error_message);
2183 return 'Connection error: ' . $error_message;
2184 }
2185
2186 // Get and check HTTP response code
2187 $http_code = wp_remote_retrieve_response_code($response);
2188 //error_log('[MXCHAT-EMBED] API Response Code: ' . $http_code);
2189
2190 if ($http_code !== 200) {
2191 $error_body = wp_remote_retrieve_body($response);
2192 //error_log('[MXCHAT-EMBED] API Error Response Body: ' . $error_body);
2193
2194 // Try to parse error for more details
2195 $error_json = json_decode($error_body, true);
2196 if (json_last_error() === JSON_ERROR_NONE && isset($error_json['error'])) {
2197 $error_type = $error_json['error']['type'] ?? 'unknown';
2198 $error_message = $error_json['error']['message'] ?? 'No message';
2199 //error_log('[MXCHAT-EMBED] API Error Type: ' . $error_type);
2200 //error_log('[MXCHAT-EMBED] API Error Message: ' . $error_message);
2201
2202 // Customize error message for common API errors
2203 if ($error_type === 'invalid_request_error' && strpos($error_message, 'API key') !== false) {
2204 $error_message = sprintf('Invalid %s API key. Please check your API key in the MxChat settings.', $provider_name);
2205 } elseif ($error_type === 'authentication_error') {
2206 $error_message = sprintf('%s authentication failed. Please verify your API key in the MxChat settings.', $provider_name);
2207 }
2208
2209 //error_log('[MXCHAT-EMBED] Returning error: ' . $error_message);
2210 return $error_message;
2211 }
2212
2213 $error_message = sprintf("API Error (HTTP %d): Unable to generate embedding", $http_code);
2214 //error_log('[MXCHAT-EMBED] Returning error: ' . $error_message);
2215 return $error_message;
2216 }
2217
2218 // Parse response body
2219 $response_body = wp_remote_retrieve_body($response);
2220 //error_log('[MXCHAT-EMBED] Received response length: ' . strlen($response_body) . ' bytes');
2221
2222 $response_data = json_decode($response_body, true);
2223
2224 if (json_last_error() !== JSON_ERROR_NONE) {
2225 $error = json_last_error_msg();
2226 //error_log('[MXCHAT-EMBED] JSON Parse Error: ' . $error);
2227 //error_log('[MXCHAT-EMBED] Response preview: ' . substr($response_body, 0, 200));
2228 return "Failed to parse API response: $error";
2229 }
2230
2231 // Both APIs use the same structure, so we can extract the embedding the same way
2232 if (isset($response_data['data'][0]['embedding'])) {
2233 $embedding_dimensions = count($response_data['data'][0]['embedding']);
2234 //error_log('[MXCHAT-EMBED] Successfully extracted embedding with ' . $embedding_dimensions . ' dimensions');
2235
2236 // Check if embedding dimensions are as expected
2237 if (($selected_model === 'text-embedding-ada-002' && $embedding_dimensions !== 1536) ||
2238 ($selected_model === 'voyage-3-large' && $embedding_dimensions !== 2048)) {
2239 //error_log('[MXCHAT-EMBED] Warning: Unexpected embedding dimensions');
2240 }
2241
2242 return $response_data['data'][0]['embedding'];
2243 } else {
2244 //error_log('[MXCHAT-EMBED] Error: No embedding found in response');
2245 //error_log('[MXCHAT-EMBED] Response structure: ' . wp_json_encode(array_keys($response_data)));
2246
2247 if (isset($response_data['error'])) {
2248 $error_message = "API Error in response: " . wp_json_encode($response_data['error']);
2249 //error_log('[MXCHAT-EMBED] ' . $error_message);
2250 return $error_message;
2251 }
2252
2253 $error_message = "Invalid API response format: No embedding found";
2254 //error_log('[MXCHAT-EMBED] ' . $error_message);
2255 return $error_message;
2256 }
2257 }
2258
2259
2260 public function mxchat_delete_chat_history() {
2261 if (!current_user_can('manage_options')) {
2262 echo wp_json_encode(['error' => esc_html__('You do not have sufficient permissions.', 'mxchat')]);
2263 wp_die();
2264 }
2265 check_ajax_referer('mxchat_delete_chat_history', 'security');
2266 global $wpdb;
2267 $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
2268
2269 if (isset($_POST['delete_session_ids']) && is_array($_POST['delete_session_ids'])) {
2270 $deleted_count = 0;
2271
2272 foreach ($_POST['delete_session_ids'] as $session_id) {
2273 $session_id_sanitized = sanitize_text_field($session_id);
2274
2275 // Clear relevant cache before deletion
2276 $cache_key = 'chat_session_' . $session_id_sanitized;
2277 wp_cache_delete($cache_key, 'mxchat_chat_sessions');
2278
2279 // Perform the deletion from the database table
2280 $wpdb->delete($table_name, ['session_id' => $session_id_sanitized]);
2281
2282 // Delete the corresponding option entry from wp_options table
2283 delete_option("mxchat_history_" . $session_id_sanitized);
2284
2285 // Delete any associated metadata options
2286 delete_option("mxchat_email_" . $session_id_sanitized);
2287 delete_option("mxchat_agent_name_" . $session_id_sanitized);
2288
2289 $deleted_count++;
2290 }
2291
2292 // Optionally, clear a general cache if you have one
2293 wp_cache_delete('all_chat_sessions', 'mxchat_chat_sessions');
2294
2295 echo wp_json_encode([
2296 'success' => sprintf(
2297 esc_html__('%d chat session(s) have been deleted from all storage locations.', 'mxchat'),
2298 $deleted_count
2299 )
2300 ]);
2301 } else {
2302 echo wp_json_encode(['error' => esc_html__('No chat sessions selected for deletion.', 'mxchat')]);
2303 }
2304
2305 wp_die();
2306 }
2307
2308 public function mxchat_save_inline_prompt() {
2309 // Check for nonce security
2310 check_ajax_referer('mxchat_save_inline_nonce');
2311
2312 // Verify permissions
2313 if (!current_user_can('manage_options')) {
2314 wp_send_json_error(esc_html__('Permission denied.', 'mxchat'));
2315 return;
2316 }
2317
2318 global $wpdb;
2319 $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
2320
2321 // Validate and sanitize input data
2322 $prompt_id = isset($_POST['id']) ? absint($_POST['id']) : 0;
2323 $article_content = isset($_POST['article_content']) ? sanitize_textarea_field($_POST['article_content']) : '';
2324 $article_url = isset($_POST['article_url']) ? esc_url_raw($_POST['article_url']) : '';
2325
2326 if ($prompt_id > 0 && !empty($article_content)) {
2327 // Re-generate the embedding vector for the updated content
2328 $embedding_vector = $this->mxchat_generate_embedding($article_content);
2329
2330 if (is_array($embedding_vector)) {
2331 // Serialize the embedding vector before storing it
2332 $embedding_vector_serialized = serialize($embedding_vector);
2333
2334 // Update the prompt in the database
2335 $updated = $wpdb->update(
2336 $table_name,
2337 array(
2338 'article_content' => $article_content,
2339 'embedding_vector' => $embedding_vector_serialized,
2340 'source_url' => $article_url,
2341 ),
2342 array('id' => $prompt_id),
2343 array('%s', '%s', '%s'),
2344 array('%d')
2345 );
2346
2347 if ($updated !== false) {
2348 wp_send_json_success();
2349 } else {
2350 wp_send_json_error(esc_html__('Database update failed.', 'mxchat'));
2351 }
2352 } else {
2353 wp_send_json_error(esc_html__('Embedding generation failed.', 'mxchat'));
2354 }
2355 } else {
2356 wp_send_json_error(esc_html__('Invalid data.', 'mxchat'));
2357 }
2358 }
2359
2360
2361 // Modified storage function to add type field
2362 private function store_in_pinecone_main($embedding_vector, $content, $url, $api_key, $environment, $index_name, $vector_id = null) {
2363 $vector_id = $vector_id ?: md5($url);
2364 $options = get_option('mxchat_pinecone_addon_options');
2365 $host = $options['mxchat_pinecone_host'] ?? '';
2366
2367 if (empty($host)) {
2368 return array(
2369 'success' => false,
2370 'message' => 'Pinecone host is not configured. Please set the host in your settings.'
2371 );
2372 }
2373
2374 // Determine if this is a product URL
2375 $is_product = (strpos($url, '/product/') !== false || strpos($url, '/shop/') !== false);
2376
2377 $api_endpoint = "https://{$host}/vectors/upsert";
2378 $request_body = array(
2379 'vectors' => array(
2380 array(
2381 'id' => $vector_id,
2382 'values' => $embedding_vector,
2383 'metadata' => array(
2384 'text' => $content,
2385 'source_url' => $url,
2386 'type' => $is_product ? 'product' : 'content',
2387 'last_updated' => time()
2388 )
2389 )
2390 )
2391 );
2392
2393 $response = wp_remote_post($api_endpoint, array(
2394 'headers' => array(
2395 'Api-Key' => $api_key,
2396 'accept' => 'application/json',
2397 'content-type' => 'application/json'
2398 ),
2399 'body' => wp_json_encode($request_body),
2400 'timeout' => 30,
2401 'data_format' => 'body'
2402 ));
2403
2404 if (is_wp_error($response)) {
2405 return array(
2406 'success' => false,
2407 'message' => $response->get_error_message()
2408 );
2409 }
2410
2411 $response_code = wp_remote_retrieve_response_code($response);
2412 if ($response_code !== 200) {
2413 $body = wp_remote_retrieve_body($response);
2414 return array(
2415 'success' => false,
2416 'message' => sprintf(
2417 'Pinecone API error (HTTP %d): %s',
2418 $response_code,
2419 $body
2420 )
2421 );
2422 }
2423
2424 return array(
2425 'success' => true,
2426 'message' => 'Successfully stored in Pinecone'
2427 );
2428 }
2429 private function store_in_wordpress_db($content, $url, $embedding_vector) {
2430 global $wpdb;
2431 $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
2432
2433 $existing_entry = $wpdb->get_row(
2434 $wpdb->prepare("SELECT id FROM $table_name WHERE source_url = %s", $url)
2435 );
2436
2437 if ($existing_entry) {
2438 $wpdb->update(
2439 $table_name,
2440 array(
2441 'article_content' => $content,
2442 'embedding_vector' => serialize($embedding_vector),
2443 'timestamp' => current_time('mysql')
2444 ),
2445 array('id' => $existing_entry->id),
2446 array('%s', '%s', '%s')
2447 );
2448 } else {
2449 $wpdb->insert(
2450 $table_name,
2451 array(
2452 'article_content' => $content,
2453 'source_url' => $url,
2454 'embedding_vector' => serialize($embedding_vector),
2455 'timestamp' => current_time('mysql')
2456 ),
2457 array('%s', '%s', '%s', '%s')
2458 );
2459 }
2460 }
2461 public function mxchat_handle_content_submission() {
2462 // Check if the form was submitted and the user has permission.
2463 if (!isset($_POST['submit_content']) || !current_user_can('manage_options')) {
2464 return;
2465 }
2466
2467 // Verify the nonce.
2468 $nonce = isset($_POST['mxchat_submit_content_nonce']) ? sanitize_text_field(wp_unslash($_POST['mxchat_submit_content_nonce'])) : '';
2469 if (!wp_verify_nonce($nonce, 'mxchat_submit_content_action')) {
2470 wp_die(esc_html__('Nonce verification failed.', 'mxchat'));
2471 }
2472
2473 // Sanitize the inputs.
2474 $article_content = sanitize_textarea_field($_POST['article_content']);
2475 $article_url = isset($_POST['article_url']) ? esc_url_raw($_POST['article_url']) : '';
2476
2477 // Generate the embedding vector.
2478 $embedding_vector = $this->mxchat_generate_embedding($article_content);
2479 if (!is_array($embedding_vector)) {
2480 set_transient('mxchat_admin_notice_error',
2481 esc_html__('Embedding generation failed. Please ensure your API key is correct and try again.', 'mxchat'),
2482 30
2483 );
2484 wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
2485 exit;
2486 }
2487
2488 // Store in the WordPress database.
2489 global $wpdb;
2490 $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
2491
2492 // Ensure the source_url column exists.
2493 if ($wpdb->get_var($wpdb->prepare("SHOW COLUMNS FROM {$table_name} LIKE %s", 'source_url')) != 'source_url') {
2494 $wpdb->query($wpdb->prepare("ALTER TABLE {$table_name} ADD source_url VARCHAR(255) DEFAULT ''"));
2495 }
2496
2497 // Serialize and store the embedding vector.
2498 $embedding_vector_serialized = serialize($embedding_vector);
2499 $inserted = $wpdb->insert(
2500 $table_name,
2501 array(
2502 'article_content' => $article_content,
2503 'embedding_vector' => $embedding_vector_serialized,
2504 'source_url' => $article_url,
2505 ),
2506 array('%s', '%s', '%s')
2507 );
2508
2509 if ($inserted === false) {
2510 set_transient('mxchat_admin_notice_error',
2511 esc_html__('Error inserting content into the database. Please try again.', 'mxchat'),
2512 30
2513 );
2514 } else {
2515 set_transient('mxchat_admin_notice_success',
2516 esc_html__('Content successfully submitted!', 'mxchat'),
2517 30
2518 );
2519 }
2520
2521 wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
2522 exit;
2523 }
2524 private function is_pdf_url($url, $response) {
2525 $content_type = wp_remote_retrieve_header($response, 'content-type');
2526 $file_extension = strtolower(pathinfo($url, PATHINFO_EXTENSION));
2527
2528 return strpos($content_type, 'pdf') !== false || $file_extension === 'pdf';
2529 }
2530 private function handle_pdf_for_knowledge_base($pdf_url, $response) {
2531 if (!current_user_can('manage_options')) {
2532 //error_log(esc_html__('Unauthorized PDF processing attempt', 'mxchat'));
2533 return false;
2534 }
2535
2536 $pdf_url = esc_url_raw($pdf_url);
2537 $upload_dir = wp_upload_dir();
2538
2539 if (isset($upload_dir['error']) && $upload_dir['error'] !== false) {
2540 //error_log(sprintf(esc_html__('Upload directory error: %s', 'mxchat'), esc_html($upload_dir['error'])));
2541 return false;
2542 }
2543
2544 $pdf_filename = sanitize_file_name('mxchat_kb_' . time() . '.pdf');
2545 $pdf_path = trailingslashit($upload_dir['path']) . $pdf_filename;
2546
2547 $response_body = wp_remote_retrieve_body($response);
2548 if (empty($response_body)) {
2549 //error_log(esc_html__('Empty PDF response body', 'mxchat'));
2550 return false;
2551 }
2552
2553 if (!wp_mkdir_p(dirname($pdf_path))) {
2554 //error_log(sprintf(esc_html__('Failed to create directory for PDF: %s', 'mxchat'), esc_html($pdf_path)));
2555 return false;
2556 }
2557
2558 try {
2559 file_put_contents($pdf_path, $response_body);
2560
2561 if (!file_exists($pdf_path)) {
2562 throw new Exception(__('Failed to save PDF file', 'mxchat'));
2563 }
2564
2565 $parser = new \Smalot\PdfParser\Parser();
2566 $pdf = $parser->parseFile($pdf_path);
2567 $total_pages = absint(count($pdf->getPages()));
2568
2569 if ($total_pages < 1) {
2570 throw new Exception(__('Invalid PDF: no pages found', 'mxchat'));
2571 }
2572
2573 wp_schedule_single_event(time(), 'mxchat_process_pdf_pages', array(
2574 'pdf_path' => $pdf_path,
2575 'pdf_url' => $pdf_url,
2576 'total_pages' => $total_pages,
2577 'batch_size' => absint(15),
2578 'batch_pause' => absint(10)
2579 ));
2580
2581 $status_data = array(
2582 'total_pages' => $total_pages,
2583 'processed_pages' => 0,
2584 'status' => 'processing',
2585 'last_update' => time()
2586 );
2587
2588 set_transient(
2589 sanitize_key('mxchat_pdf_status_' . md5($pdf_url)),
2590 array_map('sanitize_text_field', $status_data),
2591 DAY_IN_SECONDS
2592 );
2593
2594 return __('scheduled', 'mxchat');
2595
2596 } catch (Exception $e) {
2597 //error_log(sprintf(esc_html__('Error preparing PDF for processing: %s', 'mxchat'), esc_html($e->getMessage())));
2598 if (file_exists($pdf_path)) {
2599 wp_delete_file($pdf_path);
2600 }
2601 return false;
2602 }
2603 }
2604 public static function process_pdf_pages_cron($pdf_path, $pdf_url, $total_pages, $batch_size, $batch_pause) {
2605 // Validate inputs
2606 $pdf_path = sanitize_text_field($pdf_path);
2607 $pdf_url = esc_url_raw($pdf_url);
2608 $total_pages = absint($total_pages);
2609 $batch_size = absint($batch_size);
2610 $batch_pause = absint($batch_pause);
2611
2612 try {
2613 if (!file_exists($pdf_path)) {
2614 throw new Exception(sprintf('PDF file not found at path: %s', esc_html($pdf_path)));
2615 }
2616
2617 $parser = new \Smalot\PdfParser\Parser();
2618 $pdf = $parser->parseFile($pdf_path);
2619 $pages = $pdf->getPages();
2620
2621 // Get current progress
2622 $status_key = sanitize_key('mxchat_pdf_status_' . md5($pdf_url));
2623 $status = get_transient($status_key);
2624
2625 if (!$status || !is_array($status)) {
2626 throw new Exception('Invalid status data retrieved from transient');
2627 }
2628
2629 $start_page = absint($status['processed_pages']);
2630 $end_page = min($start_page + $batch_size, $total_pages);
2631
2632 $instance = new self(); // Create an instance of the class
2633 $options = get_option('mxchat_options');
2634
2635 if (empty($options['api_key'])) {
2636 throw new Exception('API key is missing or invalid');
2637 }
2638
2639 for ($i = $start_page; $i < $end_page; $i++) {
2640 $text = $pages[$i]->getText();
2641
2642 if (empty($text)) {
2643 // Log empty page but continue processing
2644 //error_log(sprintf('[MXCHAT-PDF] Warning: Empty text on page %d of %s', $i + 1, $pdf_url));
2645 continue;
2646 }
2647
2648 $sanitized_content = $instance->mxchat_sanitize_content_for_api($text); // Call via instance
2649
2650 if (empty($sanitized_content)) {
2651 // Log empty sanitized content but continue processing
2652 //error_log(sprintf('[MXCHAT-PDF] Warning: No valid content after sanitization on page %d of %s', $i + 1, $pdf_url));
2653 continue;
2654 }
2655
2656 $embedding_vector = $instance->mxchat_generate_embedding($sanitized_content); // Call via instance
2657
2658 if (!is_array($embedding_vector)) {
2659 // If embedding generation fails, log error and throw exception
2660 $error_msg = is_string($embedding_vector) ? $embedding_vector : 'Unknown embedding generation error';
2661 //error_log(sprintf('[MXCHAT-PDF] Error generating embedding for page %d: %s', $i + 1, $error_msg));
2662 throw new Exception(sprintf('Failed to generate embedding for page %d: %s', $i + 1, $error_msg));
2663 }
2664
2665 $metadata = array(
2666 'document_type' => 'pdf',
2667 'total_pages' => $total_pages,
2668 'current_page' => $i + 1,
2669 'prev_page' => $i > 0 ? $i : null,
2670 'next_page' => $i < ($total_pages - 1) ? $i + 2 : null,
2671 'source_url' => $pdf_url
2672 );
2673
2674 $content_with_metadata = wp_json_encode($metadata) . "\n---\n" . $sanitized_content;
2675 $page_url = esc_url($pdf_url . "#page=" . ($i + 1));
2676
2677 $db_result = MxChat_Utils::submit_content_to_db($content_with_metadata, $page_url, $options['api_key']);
2678
2679 if (is_wp_error($db_result)) {
2680 throw new Exception(sprintf('Failed to store content in database for page %d: %s',
2681 $i + 1, $db_result->get_error_message()));
2682 }
2683
2684 // Update progress with sanitized data
2685 $status['processed_pages'] = absint($i + 1);
2686 $status['last_update'] = time();
2687 set_transient($status_key, array_map('sanitize_text_field', $status), DAY_IN_SECONDS);
2688 }
2689
2690 // Schedule next batch if needed
2691 if ($end_page < $total_pages) {
2692 wp_schedule_single_event(time() + $batch_pause, 'mxchat_process_pdf_pages', array(
2693 'pdf_path' => $pdf_path,
2694 'pdf_url' => $pdf_url,
2695 'total_pages' => $total_pages,
2696 'batch_size' => $batch_size,
2697 'batch_pause' => $batch_pause
2698 ));
2699 } else {
2700 // Processing complete
2701 $status['status'] = 'complete';
2702 set_transient($status_key, array_map('sanitize_text_field', $status), DAY_IN_SECONDS);
2703 if (file_exists($pdf_path)) {
2704 wp_delete_file($pdf_path);
2705 }
2706 }
2707
2708 } catch (\Exception $e) {
2709 //error_log(sprintf('[MXCHAT-PDF] Error processing PDF: %s', $e->getMessage()));
2710
2711 // Get current status to update it
2712 $status_key = sanitize_key('mxchat_pdf_status_' . md5($pdf_url));
2713 $status = get_transient($status_key);
2714
2715 if (!$status || !is_array($status)) {
2716 $status = array(
2717 'total_pages' => $total_pages,
2718 'processed_pages' => 0,
2719 'status' => 'error',
2720 'error' => sanitize_text_field($e->getMessage()),
2721 'last_update' => time()
2722 );
2723 } else {
2724 $status['status'] = 'error';
2725 $status['error'] = sanitize_text_field($e->getMessage());
2726 $status['last_update'] = time();
2727 }
2728
2729 set_transient($status_key, array_map('sanitize_text_field', $status), DAY_IN_SECONDS);
2730
2731 if (file_exists($pdf_path)) {
2732 wp_delete_file($pdf_path);
2733 }
2734 }
2735 }
2736 public function get_pdf_processing_status($pdf_url) {
2737 $pdf_url = esc_url_raw($pdf_url);
2738 $status = get_transient(sanitize_key('mxchat_pdf_status_' . md5($pdf_url)));
2739
2740 if (!$status || !is_array($status)) {
2741 return false;
2742 }
2743
2744 // Check for stalled processing (no updates for 5 minutes)
2745 if ($status['status'] === 'processing' && (time() - absint($status['last_update'])) > 300) {
2746 $status['status'] = 'error';
2747 $status['error'] = __('PDF processing appears to be stalled. No updates for over 5 minutes.', 'mxchat');
2748
2749 // Save the updated status
2750 set_transient(
2751 sanitize_key('mxchat_pdf_status_' . md5($pdf_url)),
2752 array_map('sanitize_text_field', $status),
2753 DAY_IN_SECONDS
2754 );
2755 }
2756
2757 $result = array(
2758 'total_pages' => absint($status['total_pages']),
2759 'processed_pages' => absint($status['processed_pages']),
2760 'percentage' => ($status['total_pages'] > 0)
2761 ? round((absint($status['processed_pages']) / absint($status['total_pages'])) * 100)
2762 : 0,
2763 'status' => sanitize_text_field($status['status']),
2764 'last_update' => human_time_diff(absint($status['last_update']), time()) . ' ' . esc_html__('ago', 'mxchat')
2765 );
2766
2767 // Add error message if present
2768 if (isset($status['error']) && !empty($status['error'])) {
2769 $result['error'] = sanitize_text_field($status['error']);
2770 }
2771
2772 return $result;
2773 }
2774 public function mxchat_handle_sitemap_submission() {
2775 // Start logging the submission process
2776 //error_log('[MXCHAT-URL] ===== Starting URL submission process =====');
2777
2778 // Check if the form was submitted and verify permissions
2779 if (!isset($_POST['submit_sitemap']) || !current_user_can('manage_options')) {
2780 //error_log('[MXCHAT-URL] Error: Unauthorized access or form not submitted properly');
2781 wp_die(esc_html__('Unauthorized access', 'mxchat'));
2782 }
2783
2784 // Verify nonce
2785 //error_log('[MXCHAT-URL] Verifying nonce');
2786 check_admin_referer('mxchat_submit_sitemap_action', 'mxchat_submit_sitemap_nonce');
2787
2788 // Validate URL
2789 if (!isset($_POST['sitemap_url']) || empty($_POST['sitemap_url'])) {
2790 //error_log('[MXCHAT-URL] Error: Empty or missing URL');
2791 set_transient('mxchat_admin_notice_error',
2792 esc_html__('Please provide a valid URL.', 'mxchat'),
2793 30
2794 );
2795 wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
2796 exit;
2797 }
2798
2799 $submitted_url = esc_url_raw($_POST['sitemap_url']);
2800 //error_log('[MXCHAT-URL] Processing URL: ' . $submitted_url);
2801
2802 // Validate API key first
2803 $options = get_option('mxchat_options');
2804 $selected_model = $options['embedding_model'] ?? 'text-embedding-ada-002';
2805
2806 if (strpos($selected_model, 'voyage') === 0) {
2807 $api_key = $options['voyage_api_key'] ?? '';
2808 $provider_name = 'Voyage AI';
2809 } else {
2810 $api_key = $options['api_key'] ?? '';
2811 $provider_name = 'OpenAI';
2812 }
2813
2814 if (empty($api_key)) {
2815 $error_message = sprintf(
2816 esc_html__('%s API key is not configured. Please add your API key in the settings before submitting content.', 'mxchat'),
2817 $provider_name
2818 );
2819 //error_log('[MXCHAT-URL] Error: ' . $error_message);
2820 set_transient('mxchat_admin_notice_error', $error_message, 30);
2821 //error_log('[MXCHAT-URL] Set error transient: ' . $error_message);
2822 wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
2823 exit;
2824 }
2825
2826 //error_log('[MXCHAT-URL] Fetching URL content');
2827 $response = wp_remote_get($submitted_url, array('timeout' => 30));
2828
2829 if (is_wp_error($response) || wp_remote_retrieve_response_code($response) !== 200) {
2830 $error_message = is_wp_error($response) ? $response->get_error_message() : 'HTTP Status: ' . wp_remote_retrieve_response_code($response);
2831 //error_log('[MXCHAT-URL] Error fetching URL: ' . $error_message);
2832 set_transient('mxchat_admin_notice_error',
2833 sprintf(
2834 esc_html__('Failed to fetch the URL: %s', 'mxchat'),
2835 esc_html($error_message)
2836 ),
2837 30
2838 );
2839 wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
2840 exit;
2841 }
2842
2843 $content_type = wp_remote_retrieve_header($response, 'content-type');
2844 //error_log('[MXCHAT-URL] Content type: ' . $content_type);
2845 $body_content = wp_remote_retrieve_body($response);
2846
2847 if (empty($body_content)) {
2848 //error_log('[MXCHAT-URL] Error: Empty response body');
2849 set_transient('mxchat_admin_notice_error',
2850 esc_html__('Empty response received from URL.', 'mxchat'),
2851 30
2852 );
2853 wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
2854 exit;
2855 }
2856 //error_log('[MXCHAT-URL] Retrieved body content length: ' . strlen($body_content) . ' bytes');
2857
2858 // Handle PDF URL
2859 if ($this->is_pdf_url($submitted_url, $response)) {
2860 //error_log('[MXCHAT-URL] Detected PDF URL, handling PDF for knowledge base');
2861 $result = $this->handle_pdf_for_knowledge_base($submitted_url, $response);
2862 //error_log('[MXCHAT-URL] PDF handling result: ' . $result);
2863
2864 if ($result === 'scheduled') {
2865 set_transient(
2866 'mxchat_last_pdf_url',
2867 sanitize_text_field($submitted_url),
2868 DAY_IN_SECONDS
2869 );
2870 set_transient('mxchat_admin_notice_info',
2871 esc_html__('PDF processing has started in the background. You can check the progress in the Knowledge Base section.', 'mxchat'),
2872 30
2873 );
2874 } else {
2875 set_transient('mxchat_admin_notice_error',
2876 esc_html__('Failed to start PDF processing: ', 'mxchat') . esc_html($result),
2877 30
2878 );
2879 }
2880
2881 wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
2882 exit;
2883 }
2884
2885 // Handle Sitemap XML
2886 if (strpos($content_type, 'xml') !== false || strpos($body_content, '<urlset') !== false) {
2887 //error_log('[MXCHAT-URL] Detected XML content, processing as sitemap');
2888 libxml_use_internal_errors(true);
2889 $xml = simplexml_load_string($body_content);
2890 $xml_errors = libxml_get_errors();
2891 libxml_clear_errors();
2892
2893 if ($xml === false || !empty($xml_errors)) {
2894 //error_log('[MXCHAT-URL] Error: Invalid XML format');
2895 if (!empty($xml_errors)) {
2896 foreach ($xml_errors as $error) {
2897 //error_log('[MXCHAT-URL] XML Error: ' . $error->message);
2898 }
2899 }
2900
2901 set_transient('mxchat_admin_notice_error',
2902 esc_html__('Invalid sitemap XML. Please provide a valid sitemap.', 'mxchat'),
2903 30
2904 );
2905 wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
2906 exit;
2907 }
2908
2909 //error_log('[MXCHAT-URL] Valid XML found, handling sitemap for knowledge base');
2910 $result = $this->handle_sitemap_for_knowledge_base($xml, $submitted_url);
2911 //error_log('[MXCHAT-URL] Sitemap handling result: ' . $result);
2912
2913 if ($result === 'scheduled') {
2914 set_transient(
2915 'mxchat_last_sitemap_url',
2916 sanitize_text_field($submitted_url),
2917 DAY_IN_SECONDS
2918 );
2919 set_transient('mxchat_admin_notice_info',
2920 esc_html__('Sitemap processing has started in the background. You can check the progress in the Knowledge Base section.', 'mxchat'),
2921 30
2922 );
2923 } else {
2924 // Return to the admin page without a redirect for better error display
2925 // The error is already stored in the sitemap status transient
2926 set_transient('mxchat_admin_notice_error',
2927 esc_html__('Failed to start sitemap processing. Please check the status below for details.', 'mxchat'),
2928 30
2929 );
2930 }
2931
2932 wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
2933 exit;
2934 }
2935
2936 // Handle Regular URL
2937 // Handle Regular URL
2938 //error_log('[MXCHAT-URL] Processing as regular webpage');
2939 $page_content = $this->mxchat_extract_main_content($body_content);
2940 //error_log('[MXCHAT-URL] Extracted content length: ' . strlen($page_content) . ' bytes');
2941
2942 $sanitized_content = $this->mxchat_sanitize_content_for_api($page_content);
2943 //error_log('[MXCHAT-URL] Sanitized content length: ' . strlen($sanitized_content) . ' bytes');
2944
2945 if (empty($sanitized_content)) {
2946 //error_log('[MXCHAT-URL] Error: No valid content after sanitization');
2947
2948 // Set both transients - the error notice and the URL status
2949 set_transient('mxchat_admin_notice_error',
2950 esc_html__('No valid content found on the provided URL.', 'mxchat'),
2951 30
2952 );
2953
2954 // Set URL status transient
2955 set_transient('mxchat_single_url_status', [
2956 'url' => $submitted_url,
2957 'timestamp' => current_time('mysql'),
2958 'status' => 'failed',
2959 'error' => esc_html__('No valid content found on the provided URL.', 'mxchat')
2960 ], DAY_IN_SECONDS);
2961
2962 wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
2963 exit;
2964 }
2965
2966 //error_log('[MXCHAT-URL] Generating embedding for content');
2967 $embedding_vector = $this->mxchat_generate_embedding($sanitized_content);
2968
2969 // Check if embedding_vector is a string (error message)
2970 if (is_string($embedding_vector)) {
2971 //error_log('[MXCHAT-URL] Error generating embedding: ' . $embedding_vector);
2972 $error_message = esc_html__('Failed to generate embedding: ', 'mxchat') . esc_html($embedding_vector);
2973 //error_log('[MXCHAT-URL] Setting error transient: ' . $error_message);
2974
2975 // Set both transients
2976 set_transient('mxchat_admin_notice_error', $error_message, 30);
2977
2978 // Set URL status transient
2979 set_transient('mxchat_single_url_status', [
2980 'url' => $submitted_url,
2981 'timestamp' => current_time('mysql'),
2982 'status' => 'failed',
2983 'error' => $error_message
2984 ], DAY_IN_SECONDS);
2985
2986 wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
2987 exit;
2988 }
2989
2990 if (is_array($embedding_vector)) {
2991 //error_log('[MXCHAT-URL] Successfully generated embedding with ' . count($embedding_vector) . ' dimensions');
2992
2993 $db_result = MxChat_Utils::submit_content_to_db(
2994 $sanitized_content,
2995 $submitted_url,
2996 $this->options['api_key']
2997 );
2998
2999 if (is_wp_error($db_result)) {
3000 //error_log('[MXCHAT-URL] Error: Failed to store content in database: ' . $db_result->get_error_message());
3001 $error_message = esc_html__('Failed to store content in database: ', 'mxchat') . esc_html($db_result->get_error_message());
3002 //error_log('[MXCHAT-URL] Setting error transient: ' . $error_message);
3003
3004 // Set both transients
3005 set_transient('mxchat_admin_notice_error', $error_message, 30);
3006
3007 // Set URL status transient
3008 set_transient('mxchat_single_url_status', [
3009 'url' => $submitted_url,
3010 'timestamp' => current_time('mysql'),
3011 'status' => 'failed',
3012 'error' => $error_message
3013 ], DAY_IN_SECONDS);
3014
3015 wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
3016 exit;
3017 }
3018
3019 //error_log('[MXCHAT-URL] Successfully stored content in database');
3020 $success_message = esc_html__('URL content successfully submitted!', 'mxchat');
3021 //error_log('[MXCHAT-URL] Setting success transient: ' . $success_message);
3022
3023 // Set both transients
3024 set_transient('mxchat_admin_notice_success', $success_message, 30);
3025
3026 // Set URL status transient with success
3027 set_transient('mxchat_single_url_status', [
3028 'url' => $submitted_url,
3029 'timestamp' => current_time('mysql'),
3030 'status' => 'complete',
3031 'content_length' => strlen($sanitized_content),
3032 'embedding_dimensions' => count($embedding_vector)
3033 ], DAY_IN_SECONDS);
3034
3035 } else {
3036 //error_log('[MXCHAT-URL] Error: Failed to generate embedding. Unexpected result type: ' . gettype($embedding_vector));
3037 $error_message = esc_html__('Failed to generate embedding: Unexpected result type. Please check your API key and try again.', 'mxchat');
3038 //error_log('[MXCHAT-URL] Setting error transient: ' . $error_message);
3039
3040 // Set both transients
3041 set_transient('mxchat_admin_notice_error', $error_message, 30);
3042
3043 // Set URL status transient
3044 set_transient('mxchat_single_url_status', [
3045 'url' => $submitted_url,
3046 'timestamp' => current_time('mysql'),
3047 'status' => 'failed',
3048 'error' => $error_message
3049 ], DAY_IN_SECONDS);
3050 }
3051
3052 //error_log('[MXCHAT-URL] ===== Completed URL submission process =====');
3053 wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
3054 exit;
3055 }
3056 private function get_single_url_status() {
3057 $status = get_transient('mxchat_single_url_status');
3058 if (!$status) {
3059 return null;
3060 }
3061
3062 // Add human-readable time
3063 if (isset($status['timestamp'])) {
3064 $status['human_time'] = human_time_diff(strtotime($status['timestamp']), current_time('timestamp')) . ' ' . __('ago', 'mxchat');
3065 }
3066
3067 return $status;
3068 }
3069 private function handle_sitemap_for_knowledge_base($xml, $sitemap_url) {
3070 // Clear any single URL status when starting sitemap processing
3071 delete_transient('mxchat_single_url_status');
3072 if (!current_user_can('manage_options')) {
3073 //error_log(esc_html__('Unauthorized sitemap processing attempt', 'mxchat'));
3074 return false;
3075 }
3076
3077 try {
3078 $sitemap_url = esc_url_raw($sitemap_url);
3079
3080 if (!$xml || !is_object($xml)) {
3081 throw new Exception(__('Invalid XML object provided', 'mxchat'));
3082 }
3083
3084 // Add embedding validation before processing
3085 // Test embedding with a small sample text to verify API key is working
3086 $test_result = $this->mxchat_generate_embedding("This is a test to verify the embedding API key is working.");
3087
3088 // Check if test_result is a string (error message) rather than an array (valid embedding)
3089 if (is_string($test_result)) {
3090 //error_log('[MXCHAT-URL] Embedding API validation failed: ' . $test_result);
3091
3092 // Store the error in the status transient so it can be displayed later
3093 $status_data = array(
3094 'total_urls' => 0,
3095 'processed_urls' => 0,
3096 'status' => 'error',
3097 'error' => __('Embedding API validation failed: ', 'mxchat') . $test_result,
3098 'last_update' => time()
3099 );
3100
3101 set_transient(
3102 sanitize_key('mxchat_sitemap_status_' . md5($sitemap_url)),
3103 array_map('sanitize_text_field', $status_data),
3104 DAY_IN_SECONDS
3105 );
3106
3107 throw new Exception(__('Embedding API validation failed: ', 'mxchat') . $test_result);
3108 }
3109
3110 // Make sure it's an array (valid embedding)
3111 if (!is_array($test_result)) {
3112 //error_log('[MXCHAT-URL] Embedding API returned unexpected result type: ' . gettype($test_result));
3113 throw new Exception(__('Embedding API returned unexpected result type. Please check your configuration.', 'mxchat'));
3114 }
3115
3116 $urls = [];
3117 foreach ($xml->url as $url_element) {
3118 $url = esc_url_raw((string)$url_element->loc);
3119 if ($url) {
3120 $urls[] = $url;
3121 }
3122 }
3123
3124 $total_urls = absint(count($urls));
3125
3126 if ($total_urls < 1) {
3127 throw new Exception(__('No valid URLs found in sitemap', 'mxchat'));
3128 }
3129
3130 wp_schedule_single_event(time(), 'mxchat_process_sitemap_urls', array(
3131 'urls' => $urls,
3132 'sitemap_url' => $sitemap_url,
3133 'total_urls' => $total_urls,
3134 'batch_size' => absint(10),
3135 'batch_pause' => absint(5)
3136 ));
3137
3138 $status_data = array(
3139 'total_urls' => $total_urls,
3140 'processed_urls' => 0,
3141 'status' => 'processing',
3142 'last_update' => time()
3143 );
3144
3145 set_transient(
3146 sanitize_key('mxchat_sitemap_status_' . md5($sitemap_url)),
3147 array_map('sanitize_text_field', $status_data),
3148 DAY_IN_SECONDS
3149 );
3150
3151 return __('scheduled', 'mxchat');
3152
3153 } catch (\Exception $e) {
3154 $error_message = $e->getMessage();
3155 //error_log(sprintf(esc_html__('Error preparing sitemap for processing: %s', 'mxchat'), esc_html($error_message)));
3156
3157 // Store the sitemap URL and error in transients so they can be displayed
3158 set_transient(
3159 'mxchat_last_sitemap_url',
3160 sanitize_text_field($sitemap_url),
3161 DAY_IN_SECONDS
3162 );
3163
3164 $status_data = array(
3165 'total_urls' => 0,
3166 'processed_urls' => 0,
3167 'status' => 'error',
3168 'error' => $error_message,
3169 'last_update' => time()
3170 );
3171
3172 set_transient(
3173 sanitize_key('mxchat_sitemap_status_' . md5($sitemap_url)),
3174 array_map('sanitize_text_field', $status_data),
3175 DAY_IN_SECONDS
3176 );
3177
3178 return $error_message;
3179 }
3180 }
3181 public static function process_sitemap_urls_cron($urls, $sitemap_url, $total_urls, $batch_size, $batch_pause) {
3182 // Validate inputs
3183 $sitemap_url = esc_url_raw($sitemap_url);
3184 $total_urls = absint($total_urls);
3185 $batch_size = absint($batch_size);
3186 $batch_pause = absint($batch_pause);
3187
3188 if (!is_array($urls) || empty($urls)) {
3189 return;
3190 }
3191
3192 try {
3193 $status_key = sanitize_key('mxchat_sitemap_status_' . md5($sitemap_url));
3194 $status = get_transient($status_key);
3195
3196 if (!$status || !is_array($status)) {
3197 throw new Exception('Invalid status data retrieved from transient');
3198 }
3199
3200 // Initialize failed_urls array if it doesn't exist
3201 if (!isset($status['failed_urls_list']) || !is_array($status['failed_urls_list'])) {
3202 $status['failed_urls_list'] = [];
3203 }
3204
3205 $start_url = absint($status['processed_urls']);
3206 $end_url = min($start_url + $batch_size, $total_urls);
3207 $instance = new self();
3208
3209 // Track failures in batch
3210 $batch_stats = [
3211 'processed' => 0,
3212 'failed' => 0,
3213 'last_error' => '',
3214 'embedding_errors' => 0 // Track specifically embedding errors
3215 ];
3216
3217 // Check embedding configuration with first URL
3218 if ($start_url === 0) {
3219 $page_url = esc_url_raw($urls[0]);
3220 $page_response = wp_remote_get($page_url);
3221
3222 if (!is_wp_error($page_response) && wp_remote_retrieve_response_code($page_response) === 200) {
3223 $page_html = wp_remote_retrieve_body($page_response);
3224 $page_content = $instance->mxchat_extract_main_content($page_html);
3225 $sanitized_content = $instance->mxchat_sanitize_content_for_api($page_content);
3226
3227 if (!empty($sanitized_content)) {
3228 $embedding_vector = $instance->mxchat_generate_embedding($sanitized_content);
3229
3230 // Check if embedding_vector is a string (error message)
3231 if (is_string($embedding_vector)) {
3232 throw new Exception('Embedding generation failed: ' . $embedding_vector);
3233 }
3234
3235 // Make sure it's an array (valid embedding)
3236 if (!is_array($embedding_vector)) {
3237 throw new Exception('Embedding generation returned unexpected result type: ' . gettype($embedding_vector));
3238 }
3239 }
3240 }
3241 }
3242
3243 for ($i = $start_url; $i < $end_url; $i++) {
3244 $page_url = esc_url_raw($urls[$i]);
3245 $page_response = wp_remote_get($page_url);
3246
3247 if (is_wp_error($page_response) || wp_remote_retrieve_response_code($page_response) !== 200) {
3248 $batch_stats['failed']++;
3249 $error_message = is_wp_error($page_response) ? $page_response->get_error_message() : 'HTTP Status: ' . wp_remote_retrieve_response_code($page_response);
3250 $batch_stats['last_error'] = 'Failed to fetch URL: ' . $error_message;
3251
3252 // Add to failed URLs list with error message
3253 $status['failed_urls_list'][] = [
3254 'url' => $page_url,
3255 'error' => $error_message,
3256 'time' => time()
3257 ];
3258
3259 continue;
3260 }
3261
3262 $page_html = wp_remote_retrieve_body($page_response);
3263 $page_content = $instance->mxchat_extract_main_content($page_html);
3264 $sanitized_content = $instance->mxchat_sanitize_content_for_api($page_content);
3265
3266 if (!empty($sanitized_content)) {
3267 $embedding_vector = $instance->mxchat_generate_embedding($sanitized_content);
3268
3269 // Check if embedding_vector is a string (error message)
3270 if (is_string($embedding_vector)) {
3271 $batch_stats['failed']++;
3272 $batch_stats['embedding_errors']++;
3273 $batch_stats['last_error'] = 'Failed to generate embedding: ' . $embedding_vector;
3274
3275 // Add to failed URLs list with error message
3276 $status['failed_urls_list'][] = [
3277 'url' => $page_url,
3278 'error' => 'Embedding error: ' . $embedding_vector,
3279 'time' => time()
3280 ];
3281
3282 // If we have multiple embedding errors, stop processing
3283 if ($batch_stats['embedding_errors'] >= 10) {
3284 throw new Exception('Multiple embedding failures detected: ' . $embedding_vector);
3285 }
3286 continue;
3287 }
3288
3289 // Check if it's an array (valid embedding)
3290 if (is_array($embedding_vector)) {
3291 $options = get_option('mxchat_options');
3292 $submission_result = MxChat_Utils::submit_content_to_db($sanitized_content, $page_url, $options['api_key']);
3293
3294 if (is_wp_error($submission_result)) {
3295 $batch_stats['failed']++;
3296 $batch_stats['last_error'] = $submission_result->get_error_message();
3297
3298 // Add to failed URLs list with error message
3299 $status['failed_urls_list'][] = [
3300 'url' => $page_url,
3301 'error' => 'Database submission error: ' . $submission_result->get_error_message(),
3302 'time' => time()
3303 ];
3304
3305 continue;
3306 }
3307
3308 $batch_stats['processed']++;
3309 } else {
3310 $batch_stats['failed']++;
3311 $batch_stats['embedding_errors']++;
3312 $batch_stats['last_error'] = 'Failed to generate embedding: Unexpected result type: ' . gettype($embedding_vector);
3313
3314 // Add to failed URLs list with error message
3315 $status['failed_urls_list'][] = [
3316 'url' => $page_url,
3317 'error' => 'Embedding error: Unexpected result type: ' . gettype($embedding_vector),
3318 'time' => time()
3319 ];
3320
3321 // If we have multiple embedding errors, stop processing
3322 if ($batch_stats['embedding_errors'] >= 10) {
3323 throw new Exception('Multiple embedding failures detected. Please check your embedding API configuration.');
3324 }
3325 }
3326 }
3327
3328 $status['processed_urls'] = absint($i + 1);
3329 $status['last_update'] = time();
3330 $status['failed_urls'] = absint($status['failed_urls'] ?? 0) + $batch_stats['failed'];
3331 $status['last_error'] = $batch_stats['last_error'];
3332
3333 // Limit the number of failed URLs we store to prevent transient size issues
3334 if (count($status['failed_urls_list']) > 100) {
3335 $status['failed_urls_list'] = array_slice($status['failed_urls_list'], -100);
3336 }
3337
3338 set_transient($status_key, $status, DAY_IN_SECONDS);
3339 }
3340
3341 // If all URLs in this batch failed, stop processing
3342 if ($batch_stats['processed'] === 0 && $batch_stats['failed'] > 0) {
3343 $status['status'] = 'error';
3344 $status['error'] = sprintf(
3345 'Processing stopped: %d consecutive failures. Last error: %s',
3346 $batch_stats['failed'],
3347 $batch_stats['last_error']
3348 );
3349 set_transient($status_key, $status, DAY_IN_SECONDS);
3350 return;
3351 }
3352
3353 if ($end_url < $total_urls) {
3354 wp_schedule_single_event(time() + $batch_pause, 'mxchat_process_sitemap_urls', array(
3355 'urls' => $urls,
3356 'sitemap_url' => $sitemap_url,
3357 'total_urls' => $total_urls,
3358 'batch_size' => $batch_size,
3359 'batch_pause' => $batch_pause,
3360 ));
3361 } else {
3362 $status['status'] = 'complete';
3363 set_transient($status_key, $status, DAY_IN_SECONDS);
3364 }
3365 } catch (\Exception $e) {
3366 $status['status'] = 'error';
3367 $status['error'] = $e->getMessage();
3368 set_transient($status_key, $status, DAY_IN_SECONDS);
3369 }
3370 }
3371 private function mxchat_sanitize_content_for_api($content) {
3372 //error_log('[MXCHAT-SANITIZE] Original content preview: ' . substr($content, 0, 500) . '...');
3373
3374 // Remove script, style tags, and HTML comments
3375 $content = preg_replace('/<script\b[^>]*>(.*?)<\/script>/is', '', $content);
3376 $content = preg_replace('/<style\b[^>]*>(.*?)<\/style>/is', '', $content);
3377 $content = preg_replace('/<!--(.|\s)*?-->/', '', $content);
3378
3379 // Remove all HTML tags and decode HTML entities
3380 $content = wp_strip_all_tags($content);
3381 $content = html_entity_decode($content, ENT_QUOTES | ENT_HTML5);
3382
3383 // Trim and normalize whitespace
3384 $content = trim(preg_replace('/\s+/', ' ', $content));
3385
3386 // Remove control characters (which can cause database issues)
3387 $content = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/u', '', $content);
3388
3389 // Remove NULL bytes which can cause database errors
3390 $content = str_replace("\0", "", $content);
3391
3392 // Ensure valid UTF-8 encoding
3393 $content = wp_check_invalid_utf8($content);
3394
3395 // Remove any extremely long strings without spaces (often garbage)
3396 $content = preg_replace('/\S{300,}/', ' ', $content);
3397
3398 // Replace problematic characters that often cause database issues
3399 $content = preg_replace('/[\x{10000}-\x{10FFFF}]/u', '', $content); // Remove emoji and other high Unicode characters
3400
3401 // Replace any remaining potentially problematic characters with spaces
3402 $content = preg_replace('/[^\p{L}\p{N}\p{P}\p{Z}\p{Sm}]/u', ' ', $content);
3403
3404 // Limit to reasonable length if needed
3405 $max_length = 65000; // Just under MySQL TEXT field limit
3406 if (strlen($content) > $max_length) {
3407 $content = substr($content, 0, $max_length);
3408 }
3409
3410 //error_log('[MXCHAT-SANITIZE] Sanitized content preview: ' . substr($content, 0, 500) . '...');
3411 return $content;
3412 }
3413 private static function mxchat_extract_main_content($html) {
3414 if (empty($html)) {
3415 return '';
3416 }
3417 try {
3418 $dom = new DOMDocument;
3419 libxml_use_internal_errors(true); // Suppress HTML parsing errors
3420 @$dom->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'), LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
3421 $xpath = new DOMXPath($dom);
3422
3423 // For debugging purposes
3424 $debugEnabled = false; // Set to true to enable debugging output
3425 $debug = function($message) use ($debugEnabled) {
3426 if ($debugEnabled) {
3427 //error_log('[MXCHAT-DEBUG] ' . $message);
3428 }
3429 };
3430
3431 // Direct targeting for Gerow theme posts
3432 $post_text = $xpath->query('//div[contains(@class, "post-text")]');
3433 if ($post_text && $post_text->length > 0) {
3434 $debug("Found post-text directly");
3435 $content = '';
3436 foreach ($post_text as $node) {
3437 $content .= $dom->saveHTML($node);
3438 }
3439 if (!empty($content)) {
3440 $debug("Returning post-text content");
3441 return $content;
3442 }
3443 }
3444
3445 // Try to get the blog details content which contains the post-text
3446 $blog_details = $xpath->query('//div[contains(@class, "blog-details-content")]');
3447 if ($blog_details && $blog_details->length > 0) {
3448 $debug("Found blog-details-content");
3449 $content = '';
3450 foreach ($blog_details as $node) {
3451 $content .= $dom->saveHTML($node);
3452 }
3453 if (!empty($content)) {
3454 $debug("Returning blog-details-content");
3455 return $content;
3456 }
3457 }
3458
3459 // Try to get the article which contains the blog details
3460 $article = $xpath->query('//article[contains(@class, "blog-details-wrap")]');
3461 if ($article && $article->length > 0) {
3462 $debug("Found article with blog-details-wrap");
3463 $content = '';
3464 foreach ($article as $node) {
3465 $content .= $dom->saveHTML($node);
3466 }
3467 if (!empty($content)) {
3468 $debug("Returning article content");
3469 return $content;
3470 }
3471 }
3472
3473 // Try even broader with the blog-item-wrap
3474 $blog_item = $xpath->query('//div[contains(@class, "blog-item-wrap")]');
3475 if ($blog_item && $blog_item->length > 0) {
3476 $debug("Found blog-item-wrap");
3477 $content = '';
3478 foreach ($blog_item as $node) {
3479 $content .= $dom->saveHTML($node);
3480 }
3481 if (!empty($content)) {
3482 $debug("Returning blog-item-wrap content");
3483 return $content;
3484 }
3485 }
3486
3487 // Specific Gerow theme path
3488 $gerow_path = $xpath->query('//section[contains(@class, "blog-area")]//div[contains(@class, "post-text")]');
3489 if ($gerow_path && $gerow_path->length > 0) {
3490 $debug("Found Gerow theme path to post-text");
3491 $content = '';
3492 foreach ($gerow_path as $node) {
3493 $content .= $dom->saveHTML($node);
3494 }
3495 if (!empty($content)) {
3496 $debug("Returning Gerow post-text content");
3497 return $content;
3498 }
3499 }
3500
3501 // Generic blog post selectors
3502 $selectors = [
3503 // Blog post specific selectors
3504 '//div[contains(@class, "post-text")]',
3505 '//article[contains(@class, "blog-post-item")]//div[contains(@class, "post-text")]',
3506 '//div[contains(@class, "blog-details-content")]',
3507 '//article[contains(@class, "blog-details-wrap")]',
3508 '//div[contains(@class, "entry-content")]',
3509 '//div[contains(@class, "blog-content")]',
3510 '//div[contains(@class, "blog-item-wrap")]',
3511
3512 // More general content selectors
3513 '//div[contains(@class, "page__content")]',
3514 '//div[contains(@class, "elementor-widget-container")]',
3515 '//div[contains(@class, "elementor-text-editor")]',
3516 '//div[contains(@class, "elementor-widget-text-editor")]',
3517 '//*[contains(@class, "entry-content")]',
3518 '//*[contains(@class, "post-content")]',
3519 '//*[contains(@class, "article-content")]',
3520 '//*[@id="content"]',
3521 '//*[@id="main-content"]',
3522 '//section[contains(@class, "blog-area")]',
3523 '//article',
3524 '//main',
3525 '//div[contains(@class, "content")]'
3526 ];
3527
3528 // First handle Elementor content
3529 $debug("Checking for Elementor content");
3530 $elementor_widgets = $xpath->query('//div[contains(@class, "elementor-element")]//div[contains(@class, "elementor-widget-container")]');
3531 if ($elementor_widgets && $elementor_widgets->length > 0) {
3532 $debug("Found Elementor widgets");
3533 $combined_content = '';
3534 foreach ($elementor_widgets as $widget) {
3535 $widget_content = $dom->saveHTML($widget);
3536 if (!empty($widget_content)) {
3537 $combined_content .= $widget_content;
3538 }
3539 }
3540 if (!empty($combined_content)) {
3541 $debug("Returning Elementor content");
3542 return $combined_content;
3543 }
3544 }
3545
3546 // Try standard selectors one by one
3547 foreach ($selectors as $selector) {
3548 $debug("Trying selector: " . $selector);
3549 $nodes = $xpath->query($selector);
3550 if ($nodes && $nodes->length > 0) {
3551 $debug("Found matches for selector: " . $selector);
3552 $content = '';
3553 foreach ($nodes as $node) {
3554 $content .= $dom->saveHTML($node);
3555 }
3556 if (!empty($content)) {
3557 $debug("Returning content from selector: " . $selector);
3558 return $content;
3559 }
3560 }
3561 }
3562
3563 // Manual regex fallback for post-text if DOM methods fail
3564 $debug("Trying regex fallback");
3565 if (preg_match('/<div class="post-text">(.*?)<\/div>\s*<\/div>\s*<\/div>/s', $html, $matches)) {
3566 $debug("Found post-text via regex");
3567 return '<div class="post-text">' . $matches[1] . '</div>';
3568 }
3569
3570 // Try to extract the blog section as a whole
3571 $blog_section = $xpath->query('//section[contains(@class, "blog-area")]');
3572 if ($blog_section && $blog_section->length > 0) {
3573 $debug("Found blog-area section");
3574 $content = '';
3575 foreach ($blog_section as $node) {
3576 $content .= $dom->saveHTML($node);
3577 }
3578 if (!empty($content)) {
3579 $debug("Returning blog-area section content");
3580 return $content;
3581 }
3582 }
3583
3584 // Fallback: Return the body content if no specific selector matches
3585 $debug("Using body fallback");
3586 $body = $dom->getElementsByTagName('body');
3587 if ($body->length > 0) {
3588 return $dom->saveHTML($body->item(0));
3589 }
3590
3591 // Last resort: return the original HTML
3592 $debug("Returning original HTML");
3593 return $html;
3594 } catch (Exception $e) {
3595 //error_log('[MXCHAT-ERROR] Content extraction failed: ' . $e->getMessage());
3596 return $html; // Return original HTML if parsing fails
3597 } finally {
3598 libxml_clear_errors();
3599 }
3600 }
3601 public function get_sitemap_processing_status($sitemap_url) {
3602 $sitemap_url = esc_url_raw($sitemap_url);
3603 $status = get_transient(sanitize_key('mxchat_sitemap_status_' . md5($sitemap_url)));
3604
3605 if (!$status || !is_array($status)) {
3606 return false;
3607 }
3608
3609 return array(
3610 'total_urls' => absint($status['total_urls']),
3611 'processed_urls' => absint($status['processed_urls']),
3612 'failed_urls' => absint($status['failed_urls'] ?? 0),
3613 'percentage' => ($status['total_urls'] > 0)
3614 ? round((absint($status['processed_urls']) / absint($status['total_urls'])) * 100)
3615 : 0,
3616 'status' => sanitize_text_field($status['status']),
3617 'last_update' => human_time_diff(absint($status['last_update']), time()) . ' ' . esc_html__('ago', 'mxchat'),
3618 'error' => isset($status['error']) ? sanitize_text_field($status['error']) : '',
3619 'last_error' => isset($status['last_error']) ? sanitize_text_field($status['last_error']) : '',
3620 'failed_urls_list' => isset($status['failed_urls_list']) ? $status['failed_urls_list'] : array()
3621 );
3622 }
3623 public function ajax_get_status_updates() {
3624 try {
3625 // Verify the request
3626 check_ajax_referer('mxchat_status_nonce', 'nonce');
3627
3628 // Get the status just like in your admin page
3629 $pdf_url = get_transient('mxchat_last_pdf_url');
3630 $sitemap_url = get_transient('mxchat_last_sitemap_url');
3631
3632 $pdf_status = $pdf_url ? $this->get_pdf_processing_status($pdf_url) : false;
3633 $sitemap_status = $sitemap_url ? $this->get_sitemap_processing_status($sitemap_url) : false;
3634
3635 // Add the PDF URL to the status object
3636 if ($pdf_status && $pdf_url) {
3637 $pdf_status['pdf_url'] = $pdf_url;
3638 }
3639
3640 // Set the current PDF URL for the manual batch processing button
3641 $current_pdf_url = $pdf_url;
3642
3643 // Check for true processing status, not just presence of status
3644 $is_active_processing =
3645 ($sitemap_status && isset($sitemap_status['status']) && $sitemap_status['status'] === 'processing') ||
3646 ($pdf_status && isset($pdf_status['status']) && $pdf_status['status'] === 'processing');
3647
3648 // Get single URL status, but only if no processing is active
3649 $single_url_status = !$is_active_processing ? $this->get_single_url_status() : false;
3650
3651 // Only clear transients for completed processes, not error states
3652 if ($pdf_status && isset($pdf_status['status']) && $pdf_status['status'] === 'complete') {
3653 delete_transient('mxchat_last_pdf_url');
3654 $pdf_status = false;
3655 }
3656 if ($sitemap_status && isset($sitemap_status['status']) && $sitemap_status['status'] === 'complete') {
3657 delete_transient('mxchat_last_sitemap_url');
3658 $sitemap_status = false;
3659 }
3660
3661 // Return JSON response with the status data (without wp_content_status)
3662 wp_send_json(array(
3663 'pdf_status' => $pdf_status,
3664 'sitemap_status' => $sitemap_status,
3665 'single_url_status' => $single_url_status,
3666 'is_processing' => $is_active_processing,
3667 'current_pdf_url' => $current_pdf_url
3668 ));
3669
3670 } catch (Exception $e) {
3671 // Log the error
3672 //error_log('MxChat Status Update Error: ' . $e->getMessage());
3673
3674 // Return a friendly error response
3675 wp_send_json_error(array(
3676 'message' => 'Error getting status updates: ' . $e->getMessage(),
3677 'status' => 'error'
3678 ));
3679 }
3680 }
3681 public function mxchat_stop_processing() {
3682 // Verify permissions
3683 if (!current_user_can('manage_options')) {
3684 wp_die(esc_html__('Unauthorized access', 'mxchat'));
3685 }
3686
3687 // Verify nonce
3688 check_admin_referer('mxchat_stop_processing_action', 'mxchat_stop_processing_nonce');
3689
3690 // Get the last sitemap URL and clear its transient
3691 $sitemap_url = get_transient('mxchat_last_sitemap_url');
3692 if ($sitemap_url) {
3693 delete_transient('mxchat_sitemap_status_' . md5($sitemap_url));
3694 delete_transient('mxchat_last_sitemap_url');
3695 }
3696
3697 // Get the last PDF URL and clear its transient
3698 $pdf_url = get_transient('mxchat_last_pdf_url');
3699 if ($pdf_url) {
3700 delete_transient('mxchat_pdf_status_' . md5($pdf_url));
3701 delete_transient('mxchat_last_pdf_url');
3702 }
3703
3704 // Unschedule any pending sitemap events
3705 $timestamp = wp_next_scheduled('mxchat_process_sitemap_urls');
3706 if ($timestamp) {
3707 wp_unschedule_event($timestamp, 'mxchat_process_sitemap_urls');
3708 }
3709
3710 // Redirect back with a success message
3711 set_transient('mxchat_admin_notice_success',
3712 esc_html__('Processing has been stopped successfully.', 'mxchat'),
3713 30
3714 );
3715 wp_safe_redirect(admin_url('admin.php?page=mxchat-prompts'));
3716 exit;
3717 }
3718
3719 /**
3720 * Update your existing display_admin_notices function to show notices on all MXChat pages
3721 */
3722 public function display_admin_notices() {
3723 // Check if we're on a MXChat admin page
3724 $screen = get_current_screen();
3725 if (!$screen || strpos($screen->base, 'mxchat') === false) {
3726 return;
3727 }
3728
3729 //error_log('MxChat admin_notices hook fired on screen: ' . $screen->base);
3730
3731 // Check for error notices
3732 $error_notice = get_transient('mxchat_admin_notice_error');
3733 if ($error_notice) {
3734 //error_log('Found error transient: ' . $error_notice);
3735 echo '<div class="notice notice-error is-dismissible"><p>' . wp_kses_post($error_notice) . '</p></div>';
3736 delete_transient('mxchat_admin_notice_error');
3737 //error_log('Displayed and deleted error transient');
3738 } else {
3739 //error_log('No error transient found');
3740 }
3741
3742 // Check for success notices
3743 $success_notice = get_transient('mxchat_admin_notice_success');
3744 if ($success_notice) {
3745 //error_log('Found success transient: ' . $success_notice);
3746 echo '<div class="notice notice-success is-dismissible"><p>' . wp_kses_post($success_notice) . '</p></div>';
3747 delete_transient('mxchat_admin_notice_success');
3748 //error_log('Displayed and deleted success transient');
3749 }
3750
3751 // Check for info notices
3752 $info_notice = get_transient('mxchat_admin_notice_info');
3753 if ($info_notice) {
3754 //error_log('Found info transient: ' . $info_notice);
3755 echo '<div class="notice notice-info is-dismissible"><p>' . wp_kses_post($info_notice) . '</p></div>';
3756 delete_transient('mxchat_admin_notice_info');
3757 //error_log('Displayed and deleted info transient');
3758 }
3759
3760 // Display active processing status
3761 $this->display_processing_status();
3762 }
3763
3764 /**
3765 * Display current processing status
3766 */
3767 private function display_processing_status() {
3768 $pdf_url = get_transient('mxchat_last_pdf_url');
3769 $sitemap_url = get_transient('mxchat_last_sitemap_url');
3770
3771 if (!$pdf_url && !$sitemap_url) {
3772 return;
3773 }
3774
3775 $pdf_status = $pdf_url ? $this->get_pdf_processing_status($pdf_url) : false;
3776 $sitemap_status = $sitemap_url ? $this->get_sitemap_processing_status($sitemap_url) : false;
3777
3778 if ($sitemap_status && isset($sitemap_status['error']) && !empty($sitemap_status['error'])) {
3779 echo '<div class="notice notice-error is-dismissible">';
3780 echo '<p><strong>' . esc_html__('Sitemap Processing Error:', 'mxchat') . '</strong> ' . esc_html($sitemap_status['error']) . '</p>';
3781 echo '</div>';
3782 }
3783
3784 if ($pdf_status && isset($pdf_status['error']) && !empty($pdf_status['error'])) {
3785 echo '<div class="notice notice-error is-dismissible">';
3786 echo '<p><strong>' . esc_html__('PDF Processing Error:', 'mxchat') . '</strong> ' . esc_html($pdf_status['error']) . '</p>';
3787 echo '</div>';
3788 }
3789 }
3790
3791
3792
3793
3794
3795 /**
3796 * AJAX handler for content list retrieval
3797 */
3798 /**
3799 * AJAX handler for content list retrieval
3800 */
3801 public function ajax_mxchat_get_content_list() {
3802 // Verify the nonce
3803 check_ajax_referer('mxchat_content_selector_nonce', 'nonce');
3804
3805 if (!current_user_can('manage_options')) {
3806 wp_send_json_error(__('Unauthorized access', 'mxchat'));
3807 }
3808
3809 $page = isset($_GET['page']) ? absint($_GET['page']) : 1;
3810 $per_page = isset($_GET['per_page']) ? absint($_GET['per_page']) : 20;
3811 $search = isset($_GET['search']) ? sanitize_text_field($_GET['search']) : '';
3812 $post_type = isset($_GET['post_type']) ? sanitize_text_field($_GET['post_type']) : 'all';
3813 $post_status = isset($_GET['post_status']) ? sanitize_text_field($_GET['post_status']) : 'publish';
3814 $processed_filter = isset($_GET['processed_filter']) ? sanitize_text_field($_GET['processed_filter']) : 'all';
3815
3816 // Build query args
3817 $args = array(
3818 'posts_per_page' => $per_page,
3819 'paged' => $page,
3820 'post_status' => $post_status !== 'all' ? $post_status : array('publish', 'draft', 'pending'),
3821 'orderby' => 'date',
3822 'order' => 'DESC',
3823 );
3824
3825 // Handle post types
3826 if ($post_type !== 'all') {
3827 $args['post_type'] = $post_type;
3828 } else {
3829 // Default to post and page if we can't get post types
3830 $args['post_type'] = array('post', 'page');
3831
3832 // Try to get public post types
3833 $public_types = $this->get_public_post_types();
3834 if (is_array($public_types) && !empty($public_types)) {
3835 $args['post_type'] = array_keys($public_types);
3836 }
3837 }
3838
3839 if (!empty($search)) {
3840 $args['s'] = $search;
3841 }
3842
3843 // Get already vectorized content to mark as processed
3844 global $wpdb;
3845 $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
3846 $processed_items = $wpdb->get_results("SELECT id, source_url, timestamp FROM {$table_name}");
3847
3848 // Create a lookup array of processed posts with timestamps
3849 $processed_data = array();
3850
3851 if (!empty($processed_items)) {
3852 foreach ($processed_items as $item) {
3853 $post_id = url_to_postid($item->source_url);
3854 if ($post_id) {
3855 $processed_data[$post_id] = array(
3856 'db_id' => $item->id,
3857 'timestamp' => $item->timestamp,
3858 'url' => $item->source_url
3859 );
3860 }
3861 }
3862 }
3863
3864 // Get processed IDs as a simple array for in_array checks
3865 $processed_ids = array_keys($processed_data);
3866
3867 // Handle processed/unprocessed filter
3868 if ($processed_filter === 'processed' && !empty($processed_ids)) {
3869 $args['post__in'] = $processed_ids;
3870 } elseif ($processed_filter === 'unprocessed' && !empty($processed_ids)) {
3871 $args['post__not_in'] = $processed_ids;
3872 }
3873
3874 // Run the query
3875 $query = new WP_Query($args);
3876 $content_items = array();
3877
3878 if ($query->have_posts()) {
3879 while ($query->have_posts()) {
3880 $query->the_post();
3881 $id = get_the_ID();
3882 $post_date = get_the_date();
3883 $excerpt = wp_trim_words(get_the_excerpt(), 20, '...');
3884 $word_count = str_word_count(strip_tags(get_the_content()));
3885
3886 $is_processed = in_array($id, $processed_ids);
3887 $processed_date = '';
3888 $db_record_id = 0;
3889
3890 if ($is_processed && isset($processed_data[$id]['timestamp'])) {
3891 // Format the timestamp for display
3892 $timestamp = strtotime($processed_data[$id]['timestamp']);
3893 $processed_date = human_time_diff($timestamp, current_time('timestamp')) . ' ago';
3894 $db_record_id = $processed_data[$id]['db_id'];
3895 }
3896
3897 $content_items[] = array(
3898 'id' => $id,
3899 'title' => get_the_title(),
3900 'permalink' => get_permalink(),
3901 'date' => $post_date,
3902 'type' => get_post_type(),
3903 'status' => get_post_status(),
3904 'excerpt' => $excerpt,
3905 'word_count' => $word_count,
3906 'already_processed' => $is_processed,
3907 'processed_date' => $processed_date,
3908 'db_record_id' => $db_record_id
3909 );
3910 }
3911 wp_reset_postdata();
3912 }
3913
3914 $response = array(
3915 'items' => $content_items,
3916 'total' => $query->found_posts,
3917 'total_pages' => $query->max_num_pages,
3918 'current_page' => $page,
3919 'processed_count' => count($processed_ids)
3920 );
3921
3922 wp_send_json_success($response);
3923 exit;
3924 }
3925
3926 /**
3927 * AJAX handler for processing selected content
3928 */
3929 public function ajax_mxchat_process_selected_content() {
3930 // Basic request validation
3931 if (!check_ajax_referer('mxchat_content_selector_nonce', 'nonce', false)) {
3932 wp_send_json_error('Invalid nonce');
3933 exit;
3934 }
3935
3936 if (!current_user_can('manage_options')) {
3937 wp_send_json_error('Unauthorized access');
3938 exit;
3939 }
3940
3941 // Get post IDs - safely parse the array
3942 $post_ids = array();
3943 if (isset($_POST['post_ids']) && is_array($_POST['post_ids'])) {
3944 foreach ($_POST['post_ids'] as $id) {
3945 $post_ids[] = absint($id);
3946 }
3947 }
3948
3949 if (empty($post_ids)) {
3950 wp_send_json_error('No content selected');
3951 exit;
3952 }
3953
3954 // Is this an update? Check if db_record_id is provided
3955 $db_record_id = isset($_POST['db_record_id']) ? absint($_POST['db_record_id']) : 0;
3956
3957 // Process only ONE post at a time to avoid request size issues
3958 $post_id = reset($post_ids);
3959 $post = get_post($post_id);
3960
3961 if (!$post) {
3962 wp_send_json_error('Post not found');
3963 exit;
3964 }
3965
3966 // Get minimal content
3967 $content = $post->post_title . "\n\n" . wp_strip_all_tags($post->post_content);
3968 $content = substr($content, 0, 10000); // Limit content size
3969
3970 // Get API key - simple validation
3971 $options = get_option('mxchat_options');
3972 $api_key = isset($options['api_key']) ? $options['api_key'] : '';
3973
3974 if (empty($api_key)) {
3975 wp_send_json_error('API key not configured');
3976 exit;
3977 }
3978
3979 // Generate embedding with error handling
3980 $embedding_vector = $this->mxchat_generate_embedding($content);
3981
3982 if (!is_array($embedding_vector)) {
3983 wp_send_json_error('Failed to generate embedding');
3984 exit;
3985 }
3986
3987 // Database storage with minimal data
3988 global $wpdb;
3989 $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
3990 $source_url = get_permalink($post_id);
3991
3992 // Check if this is an update or new content
3993 $existing_record = $wpdb->get_row($wpdb->prepare(
3994 "SELECT id FROM $table_name WHERE source_url = %s",
3995 $source_url
3996 ));
3997
3998 $operation_type = $existing_record ? 'update' : 'new';
3999 $result = false;
4000
4001 if ($operation_type === 'update') {
4002 // Update existing record
4003 $result = $wpdb->update(
4004 $table_name,
4005 array(
4006 'article_content' => $content,
4007 'embedding_vector' => serialize($embedding_vector),
4008 'timestamp' => current_time('mysql')
4009 ),
4010 array('source_url' => $source_url),
4011 array('%s', '%s', '%s'),
4012 array('%s')
4013 );
4014 } else {
4015 // Insert new record
4016 $result = $wpdb->insert(
4017 $table_name,
4018 array(
4019 'article_content' => $content,
4020 'source_url' => $source_url,
4021 'embedding_vector' => serialize($embedding_vector),
4022 'timestamp' => current_time('mysql')
4023 ),
4024 array('%s', '%s', '%s', '%s')
4025 );
4026 }
4027
4028 if ($result === false) {
4029 wp_send_json_error('Database ' . ($operation_type === 'update' ? 'update' : 'insertion') . ' failed');
4030 exit;
4031 }
4032
4033 // Success response with minimal data
4034 wp_send_json_success(array(
4035 'message' => $operation_type === 'update' ? 'Content updated successfully' : 'Content processed successfully',
4036 'post_id' => $post_id,
4037 'title' => $post->post_title,
4038 'operation_type' => $operation_type
4039 ));
4040 exit;
4041 }
4042 private function get_public_post_types() {
4043 $post_types = get_post_types(array('public' => true), 'objects');
4044 $post_type_options = array();
4045
4046 foreach ($post_types as $post_type) {
4047 $post_type_options[$post_type->name] = $post_type->label;
4048 }
4049
4050 return $post_type_options;
4051 }
4052
4053
4054 /**
4055 * Handle post updates and process content for the chatbot
4056 *
4057 * @param int $post_id The ID of the post being saved
4058 * @param WP_Post $post The post object
4059 * @param bool $update Whether this is an existing post being updated
4060 */
4061 public function handle_post_update($post_id, $post, $update) {
4062 // Basic validation checks
4063 if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE || wp_is_post_revision($post_id)) {
4064 return;
4065 }
4066
4067 // Only process published content
4068 if ($post->post_status !== 'publish') {
4069 return;
4070 }
4071
4072 $post_type = $post->post_type;
4073
4074 // Check if sync is enabled for this post type
4075 $should_sync = false;
4076
4077 // Check built-in post types first
4078 if ($post_type === 'post' && get_option('mxchat_auto_sync_posts') === '1') {
4079 $should_sync = true;
4080 } else if ($post_type === 'page' && get_option('mxchat_auto_sync_pages') === '1') {
4081 $should_sync = true;
4082 } else {
4083 // Check custom post types
4084 $option_name = 'mxchat_auto_sync_' . $post_type;
4085 if (get_option($option_name) === '1') {
4086 $should_sync = true;
4087 }
4088 }
4089
4090 if (!$should_sync) {
4091 return;
4092 }
4093
4094 // Prepare content and URL
4095 $content = wp_strip_all_tags($post->post_content);
4096
4097 // For custom post types like job_listing, include additional fields
4098 if ($post_type === 'job_listing') {
4099 // Add title as it's important for job listings
4100 $content = $post->post_title . "\n\n" . $content;
4101
4102 // Add job-specific meta if available
4103 $job_location = get_post_meta($post_id, '_job_location', true);
4104 if (!empty($job_location)) {
4105 $content .= "\n\nLocation: " . $job_location;
4106 }
4107
4108 // Get job type terms
4109 $job_types = get_the_terms($post_id, 'job_listing_type');
4110 if (!empty($job_types) && !is_wp_error($job_types)) {
4111 $types = array();
4112 foreach ($job_types as $type) {
4113 $types[] = $type->name;
4114 }
4115 $content .= "\n\nJob Type: " . implode(', ', $types);
4116 }
4117
4118 // Get company name if available
4119 $company_name = get_post_meta($post_id, '_company_name', true);
4120 if (!empty($company_name)) {
4121 $content .= "\n\nCompany: " . $company_name;
4122 }
4123 }
4124
4125 $url = get_permalink($post_id);
4126
4127 // Generate embedding vector
4128 $embedding_vector = $this->mxchat_generate_embedding($content);
4129 if (!$embedding_vector) {
4130 return;
4131 }
4132
4133 // Check for Pinecone addon and its settings
4134 $pinecone_settings = get_option('mxchat_pinecone_addon_options');
4135 $use_pinecone = false;
4136 if ($pinecone_settings && is_array($pinecone_settings)) {
4137 // Check if Pinecone is enabled and all required settings are present
4138 $use_pinecone = (
4139 isset($pinecone_settings['mxchat_use_pinecone']) &&
4140 $pinecone_settings['mxchat_use_pinecone'] === '1' &&
4141 !empty($pinecone_settings['mxchat_pinecone_api_key']) &&
4142 !empty($pinecone_settings['mxchat_pinecone_host']) &&
4143 !empty($pinecone_settings['mxchat_pinecone_index']) &&
4144 !empty($pinecone_settings['mxchat_pinecone_environment'])
4145 );
4146 }
4147
4148 if ($use_pinecone) {
4149 // Use Pinecone
4150 $pinecone_result = $this->store_in_pinecone_main(
4151 $embedding_vector,
4152 $content,
4153 $url,
4154 $pinecone_settings['mxchat_pinecone_api_key'],
4155 $pinecone_settings['mxchat_pinecone_environment'],
4156 $pinecone_settings['mxchat_pinecone_index']
4157 );
4158 if (!$pinecone_result['success']) {
4159 // Fallback to WordPress DB
4160 $this->store_in_wordpress_db($content, $url, $embedding_vector);
4161 }
4162 } else {
4163 // Fallback to WordPress DB storage
4164 $this->store_in_wordpress_db($content, $url, $embedding_vector);
4165 }
4166
4167 }
4168
4169 /**
4170 * Handle deletion of posts from both Pinecone and WordPress DB
4171 *
4172 * @param int $post_id The ID of the post being deleted
4173 * @return void
4174 */
4175 public function mxchat_handle_post_delete($post_id) {
4176 // Get post data before it's deleted
4177 $post = get_post($post_id);
4178
4179 // Basic validation
4180 if (!$post || wp_is_post_revision($post_id)) {
4181 return;
4182 }
4183
4184 $post_type = $post->post_type;
4185
4186 // Check if sync is enabled for this post type
4187 $should_sync = false;
4188
4189 // Check built-in post types first
4190 if ($post_type === 'post' && get_option('mxchat_auto_sync_posts') === '1') {
4191 $should_sync = true;
4192 } else if ($post_type === 'page' && get_option('mxchat_auto_sync_pages') === '1') {
4193 $should_sync = true;
4194 } else {
4195 // Check custom post types
4196 $option_name = 'mxchat_auto_sync_' . $post_type;
4197 if (get_option($option_name) === '1') {
4198 $should_sync = true;
4199 }
4200 }
4201
4202 if (!$should_sync) {
4203 return;
4204 }
4205
4206 // Get the URL before post is deleted
4207 $source_url = get_permalink($post_id);
4208 if (!$source_url) {
4209 //error_log('MXChat: Failed to get permalink for post ' . $post_id);
4210 return;
4211 }
4212
4213 // Check for Pinecone addon and its settings
4214 $pinecone_settings = get_option('mxchat_pinecone_addon_options');
4215 $use_pinecone = false;
4216 if ($pinecone_settings && is_array($pinecone_settings)) {
4217 $use_pinecone = (
4218 isset($pinecone_settings['mxchat_use_pinecone']) &&
4219 $pinecone_settings['mxchat_use_pinecone'] === '1' &&
4220 !empty($pinecone_settings['mxchat_pinecone_api_key']) &&
4221 !empty($pinecone_settings['mxchat_pinecone_host']) &&
4222 !empty($pinecone_settings['mxchat_pinecone_index']) &&
4223 !empty($pinecone_settings['mxchat_pinecone_environment'])
4224 );
4225 }
4226
4227 $deletion_successful = false;
4228
4229 if ($use_pinecone) {
4230 try {
4231 $pinecone_result = $this->delete_from_pinecone(
4232 array($source_url),
4233 $pinecone_settings['mxchat_pinecone_api_key'],
4234 $pinecone_settings['mxchat_pinecone_environment'],
4235 $pinecone_settings['mxchat_pinecone_index']
4236 );
4237
4238 if (!$pinecone_result['success']) {
4239 //error_log('MXChat: Pinecone deletion failed for URL: ' . $source_url . ' - ' . $pinecone_result['message']);
4240 } else {
4241 $deletion_successful = true;
4242 }
4243 } catch (Exception $e) {
4244 //error_log('MXChat: Exception during Pinecone deletion - ' . $e->getMessage());
4245 }
4246 }
4247
4248 // Always attempt WordPress DB deletion, regardless of Pinecone status
4249 try {
4250 global $wpdb;
4251 $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
4252
4253 $result = $wpdb->delete(
4254 $table_name,
4255 array('source_url' => $source_url),
4256 array('%s')
4257 );
4258
4259 if ($result === false) {
4260 //error_log('MXChat: WordPress DB deletion failed for URL: ' . $source_url);
4261 } else {
4262 $deletion_successful = true;
4263 }
4264 } catch (Exception $e) {
4265 //error_log('MXChat: Exception during WordPress DB deletion - ' . $e->getMessage());
4266 }
4267
4268 if (!$deletion_successful) {
4269 //error_log('MXChat: Complete deletion failure for post ID: ' . $post_id . ' URL: ' . $source_url);
4270 } else {
4271
4272 }
4273 }
4274
4275
4276
4277
4278
4279
4280
4281
4282
4283
4284
4285
4286
4287 public function mxchat_handle_product_change($post_id, $post, $update) {
4288 if ($post->post_type !== 'product') {
4289 return;
4290 }
4291
4292 if ($post->post_status === 'publish') {
4293 add_action('shutdown', function() use ($post_id) {
4294 $product = wc_get_product($post_id);
4295 if ($product) {
4296 $this->mxchat_store_product_embedding($product);
4297 }
4298 });
4299 }
4300 }
4301 private function mxchat_store_product_embedding($product) {
4302 if (!isset($this->options['enable_woocommerce_integration']) ||
4303 !in_array($this->options['enable_woocommerce_integration'], ['1', 'on'])) {
4304 return;
4305 }
4306
4307 $source_url = get_permalink($product->get_id());
4308
4309 // Build description
4310 $regular_price = $product->get_regular_price();
4311 $sale_price = $product->get_sale_price();
4312 $description = $product->get_description() . "\n\n" .
4313 esc_html__('Short Description:', 'mxchat') . " " . $product->get_short_description() . "\n" .
4314 esc_html__('Price:', 'mxchat') . " " . $regular_price . "\n" .
4315 esc_html__('Sale Price:', 'mxchat') . " " . ($sale_price ?: esc_html__('N/A', 'mxchat')) . "\n" .
4316 esc_html__('SKU:', 'mxchat') . " " . $product->get_sku();
4317
4318 // Generate embedding vector
4319 $embedding_vector = $this->mxchat_generate_embedding($description);
4320 if (!is_array($embedding_vector)) {
4321 return;
4322 }
4323
4324 // Check for Pinecone addon and its settings
4325 $pinecone_settings = get_option('mxchat_pinecone_addon_options');
4326 $use_pinecone = false;
4327
4328 if ($pinecone_settings && is_array($pinecone_settings)) {
4329 $use_pinecone = (
4330 isset($pinecone_settings['mxchat_use_pinecone']) &&
4331 $pinecone_settings['mxchat_use_pinecone'] === '1' &&
4332 !empty($pinecone_settings['mxchat_pinecone_api_key']) &&
4333 !empty($pinecone_settings['mxchat_pinecone_host']) &&
4334 !empty($pinecone_settings['mxchat_pinecone_index']) &&
4335 !empty($pinecone_settings['mxchat_pinecone_environment'])
4336 );
4337 }
4338
4339 if ($use_pinecone) {
4340 $result = $this->store_in_pinecone_main(
4341 $embedding_vector,
4342 $description,
4343 $source_url,
4344 $pinecone_settings['mxchat_pinecone_api_key'],
4345 $pinecone_settings['mxchat_pinecone_environment'],
4346 $pinecone_settings['mxchat_pinecone_index']
4347 );
4348
4349 if (!$result['success']) {
4350 // Fallback to WordPress DB if Pinecone storage fails
4351 $this->store_in_wordpress_db($description, $source_url, $embedding_vector);
4352 }
4353 } else {
4354 // Use WordPress DB storage
4355 $this->store_in_wordpress_db($description, $source_url, $embedding_vector);
4356 }
4357 }
4358 public function mxchat_handle_product_delete($post_id) {
4359 if (get_post_type($post_id) !== 'product') {
4360 return;
4361 }
4362
4363 $source_url = get_permalink($post_id);
4364
4365 // Check for Pinecone addon and its settings
4366 $pinecone_settings = get_option('mxchat_pinecone_addon_options');
4367 $use_pinecone = false;
4368
4369 if ($pinecone_settings && is_array($pinecone_settings)) {
4370 $use_pinecone = (
4371 isset($pinecone_settings['mxchat_use_pinecone']) &&
4372 $pinecone_settings['mxchat_use_pinecone'] === '1' &&
4373 !empty($pinecone_settings['mxchat_pinecone_api_key']) &&
4374 !empty($pinecone_settings['mxchat_pinecone_host']) &&
4375 !empty($pinecone_settings['mxchat_pinecone_index']) &&
4376 !empty($pinecone_settings['mxchat_pinecone_environment'])
4377 );
4378 }
4379
4380 if ($use_pinecone) {
4381 $this->delete_from_pinecone(
4382 array($source_url),
4383 $pinecone_settings['mxchat_pinecone_api_key'],
4384 $pinecone_settings['mxchat_pinecone_environment'],
4385 $pinecone_settings['mxchat_pinecone_index']
4386 );
4387 } else {
4388 // Fallback to WordPress DB deletion
4389 global $wpdb;
4390 $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
4391 $wpdb->delete($table_name, array('source_url' => $source_url), array('%s'));
4392 }
4393 }
4394
4395 /**
4396 * Delete vectors from Pinecone by source URL
4397 *
4398 * @param array $urls Array of source URLs to delete
4399 * @param string $api_key Pinecone API key
4400 * @param string $environment Pinecone environment
4401 * @param string $index_name Pinecone index name
4402 * @return array Associative array with 'success' boolean and 'message' string
4403 */
4404 private function delete_from_pinecone($urls, $api_key, $environment, $index_name) {
4405 // Get the Pinecone host from options (matching your store_in_pinecone_main pattern)
4406 $options = get_option('mxchat_pinecone_addon_options');
4407 $host = $options['mxchat_pinecone_host'] ?? '';
4408
4409 if (empty($host)) {
4410 return array(
4411 'success' => false,
4412 'message' => 'Pinecone host is not configured. Please set the host in your settings.'
4413 );
4414 }
4415
4416 // Build API endpoint using the configured host
4417 $api_endpoint = "https://{$host}/vectors/delete";
4418
4419 // Create vector IDs from URLs (matching your store method's ID generation)
4420 $vector_ids = array_map('md5', $urls);
4421
4422 // Prepare the delete request body
4423 $request_body = array(
4424 'ids' => $vector_ids,
4425 'filter' => array(
4426 'source_url' => array(
4427 '$in' => $urls
4428 )
4429 )
4430 );
4431
4432 // Make the deletion request
4433 $response = wp_remote_post($api_endpoint, array(
4434 'headers' => array(
4435 'Api-Key' => $api_key,
4436 'accept' => 'application/json',
4437 'content-type' => 'application/json'
4438 ),
4439 'body' => wp_json_encode($request_body),
4440 'timeout' => 30,
4441 'data_format' => 'body'
4442 ));
4443
4444 // Handle WordPress HTTP API errors
4445 if (is_wp_error($response)) {
4446 return array(
4447 'success' => false,
4448 'message' => $response->get_error_message()
4449 );
4450 }
4451
4452 // Check response status
4453 $response_code = wp_remote_retrieve_response_code($response);
4454 if ($response_code !== 200) {
4455 $body = wp_remote_retrieve_body($response);
4456 return array(
4457 'success' => false,
4458 'message' => sprintf(
4459 'Pinecone API error (HTTP %d): %s',
4460 $response_code,
4461 $body
4462 )
4463 );
4464 }
4465
4466 // Parse response body
4467 $body = wp_remote_retrieve_body($response);
4468 $response_data = json_decode($body, true);
4469
4470 // Final validation of the response
4471 if (json_last_error() !== JSON_ERROR_NONE) {
4472 return array(
4473 'success' => false,
4474 'message' => 'Failed to parse Pinecone response: ' . json_last_error_msg()
4475 );
4476 }
4477
4478 return array(
4479 'success' => true,
4480 'message' => sprintf('Successfully deleted %d vectors from Pinecone', count($vector_ids))
4481 );
4482 }
4483
4484
4485
4486
4487
4488 public function mxchat_create_activation_page() {
4489 $license_status = get_option('mxchat_license_status', 'inactive');
4490 $license_error = get_option('mxchat_license_error', '');
4491 ?>
4492 <div class="wrap mxchat-admin-activation">
4493 <div class="mxchat-pro-hero">
4494 <h1 class="pro-title">
4495 <span class="pro-gradient-text">Activate</span> MxChat Pro
4496 </h1>
4497 <p class="pro-subtitle">
4498 <?php esc_html_e('Enter your license key to unlock premium features, advanced AI capabilities, and priority support.', 'mxchat'); ?>
4499 </p>
4500 </div>
4501
4502 <?php if ($license_status === 'inactive' && !empty($license_error)): ?>
4503 <div class="error notice">
4504 <p><?php echo esc_html($license_error); ?></p>
4505 </div>
4506 <?php endif; ?>
4507
4508 <form id="mxchat-activation-form" class="mxchat-pro-form" style="<?php echo $license_status === 'active' ? 'display: none;' : ''; ?>">
4509 <div class="mxchat-pro-form-container">
4510 <table class="form-table">
4511 <tr valign="top">
4512 <th scope="row"><?php esc_html_e('Email Address', 'mxchat'); ?></th>
4513 <td>
4514 <input type="email" id="mxchat_pro_email" name="mxchat_pro_email" value="<?php echo esc_attr(get_option('mxchat_pro_email')); ?>" class="regular-text mxchat-pro-input" required />
4515 </td>
4516 </tr>
4517 <tr valign="top">
4518 <th scope="row"><?php esc_html_e('Activation Key', 'mxchat'); ?></th>
4519 <td>
4520 <input type="text" id="mxchat_activation_key" name="mxchat_activation_key" value="<?php echo esc_attr(get_option('mxchat_activation_key')); ?>" class="regular-text mxchat-pro-input" required />
4521 </td>
4522 </tr>
4523 </table>
4524 <?php if ($license_status !== 'active'): ?>
4525 <div class="mxchat-pro-button-container">
4526 <button type="submit" id="activate_license_button" class="button button-primary mxchat-pro-button"><?php esc_html_e('Activate License', 'mxchat'); ?></button>
4527 <div id="mxchat-activation-spinner" class="mxchat-activation-spinner" style="display: none;"></div>
4528 </div>
4529 <?php endif; ?>
4530 </div>
4531 </form>
4532 <!-- License Status Display -->
4533 <div class="mxchat-pro-status">
4534 <h3><?php esc_html_e('License Status:', 'mxchat'); ?>
4535 <span id="mxchat-license-status" class="mxchat-status-badge <?php echo $license_status; ?>">
4536 <?php echo $license_status === 'active' ? esc_html__('Active', 'mxchat') : esc_html__('Inactive', 'mxchat'); ?>
4537 </span>
4538 </h3>
4539 </div>
4540
4541 </div>
4542 <?php
4543 }
4544
4545 public function mxchat_actions_page_html() {
4546 if (!current_user_can('manage_options')) {
4547 return;
4548 }
4549
4550 // Keep existing data fetching logic
4551 global $wpdb;
4552 $table_name = $wpdb->prefix . 'mxchat_intents';
4553 $page = isset($_GET['paged']) ? max(1, intval($_GET['paged'])) : 1;
4554 $per_page = 20;
4555 $offset = ($page - 1) * $per_page;
4556
4557 // Success message
4558 if (isset($_GET['updated']) && $_GET['updated'] === 'true') {
4559 echo '<div class="notice notice-success is-dismissible"><p>' .
4560 esc_html__('Action updated successfully.', 'mxchat') .
4561 '</p></div>';
4562 }
4563
4564 // Filtering logic
4565 $where = '1=1';
4566 $search_term = isset($_GET['s']) ? trim($_GET['s']) : '';
4567 $callback_filter = isset($_GET['callback_filter']) ? sanitize_text_field($_GET['callback_filter']) : '';
4568
4569 if ($search_term) {
4570 $search_term_like = '%' . $wpdb->esc_like($search_term) . '%';
4571 $where .= $wpdb->prepare(' AND (intent_label LIKE %s OR phrases LIKE %s)',
4572 $search_term_like, $search_term_like);
4573 }
4574
4575 if ($callback_filter) {
4576 $where .= $wpdb->prepare(' AND callback_function = %s', $callback_filter);
4577 }
4578
4579 // Pagination
4580 $total_intents = $wpdb->get_var("SELECT COUNT(*) FROM $table_name WHERE $where");
4581 $total_pages = ceil($total_intents / $per_page);
4582
4583 // Get intents (now called actions)
4584 $actions = $wpdb->get_results($wpdb->prepare(
4585 "SELECT * FROM $table_name WHERE $where LIMIT %d OFFSET %d",
4586 $per_page, $offset
4587 ));
4588
4589 // Get callbacks
4590 $available_callbacks = $this->mxchat_get_available_callbacks();
4591
4592 ?>
4593 <div class="wrap mxchat-wrapper">
4594 <!-- Hero Section -->
4595 <div class="mxchat-hero">
4596 <h1 class="mxchat-main-title">
4597 <span class="mxchat-gradient-text">Actions</span> Manager
4598 </h1>
4599 <p class="mxchat-hero-subtitle">
4600 <?php esc_html_e('Create and manage custom actions to enhance your chatbot\'s capabilities.', 'mxchat'); ?>
4601 </p>
4602 </div>
4603
4604 <!-- Actions Header with Search and Filter -->
4605 <div class="mxchat-actions-header">
4606 <div class="mxchat-actions-filters">
4607 <form method="get" class="mxchat-search-form">
4608 <input type="hidden" name="page" value="mxchat-actions">
4609 <div class="mxchat-search-group">
4610 <span class="dashicons dashicons-search"></span>
4611 <input type="text" name="s" class="mxchat-search-input"
4612 placeholder="<?php esc_attr_e('Search Actions', 'mxchat'); ?>"
4613 value="<?php echo esc_attr($search_term); ?>">
4614 </div>
4615 <select name="callback_filter" class="mxchat-action-filter">
4616 <option value=""><?php esc_html_e('All Action Types', 'mxchat'); ?></option>
4617 <?php foreach ($available_callbacks as $function => $callback_data) :
4618 $label = $callback_data['label']; ?>
4619 <option value="<?php echo esc_attr($function); ?>"
4620 <?php selected($callback_filter, $function); ?>>
4621 <?php echo esc_html($label); ?>
4622 </option>
4623 <?php endforeach; ?>
4624 </select>
4625 <button type="submit" class="mxchat-button-secondary">
4626 <?php esc_html_e('Filter', 'mxchat'); ?>
4627 </button>
4628 </form>
4629 </div>
4630 <div class="mxchat-actions-controls">
4631 <button type="button" id="mxchat-add-action-btn" class="mxchat-button-primary">
4632 <span class="dashicons dashicons-plus-alt"></span>
4633 <?php esc_html_e('Add New Action', 'mxchat'); ?>
4634 </button>
4635 </div>
4636 </div>
4637
4638 <!-- Actions Grid Layout - All actions in a single grid -->
4639 <div class="mxchat-actions-grid">
4640 <div class="mxchat-cards-container">
4641 <?php if (!empty($actions)) : ?>
4642 <?php foreach ($actions as $action) :
4643 $callback_function = $action->callback_function;
4644 $callback_label = isset($available_callbacks[$callback_function]['label'])
4645 ? $available_callbacks[$callback_function]['label']
4646 : $callback_function;
4647 $threshold_value = isset($action->similarity_threshold)
4648 ? round($action->similarity_threshold * 100)
4649 : 85;
4650
4651 // Check if this is a form action
4652 $is_form_action = strpos($action->intent_label, 'Form ') === 0;
4653
4654 // Get action status (enabled/disabled) - default to true if column doesn't exist
4655 $is_enabled = isset($action->enabled) ? (bool)$action->enabled : true;
4656 ?>
4657 <div class="mxchat-action-card <?php echo $is_form_action ? 'mxchat-form-action' : ''; ?>">
4658 <div class="mxchat-card-header">
4659 <div class="mxchat-card-title"><?php echo esc_html($action->intent_label); ?></div>
4660 <div class="mxchat-card-toggle">
4661 <label class="mxchat-switch">
4662 <input type="checkbox" class="mxchat-action-toggle"
4663 data-action-id="<?php echo esc_attr($action->id); ?>"
4664 <?php checked($is_enabled); ?>>
4665 <span class="mxchat-slider round"></span>
4666 </label>
4667 </div>
4668 </div>
4669
4670 <div class="mxchat-card-body">
4671 <div class="mxchat-card-description">
4672 <strong><?php esc_html_e('Type:', 'mxchat'); ?></strong>
4673 <?php echo esc_html($callback_label); ?>
4674 </div>
4675
4676 <div class="mxchat-card-phrases">
4677 <strong><?php esc_html_e('Trigger phrases:', 'mxchat'); ?></strong>
4678 <div class="mxchat-phrases-preview">
4679 <?php
4680 // Check if the helper function exists, otherwise use a simple substring
4681 if (method_exists($this, 'get_trimmed_phrases')) {
4682 echo esc_html($this->get_trimmed_phrases($action->phrases));
4683 } else {
4684 echo esc_html(strlen($action->phrases) > 100 ?
4685 substr($action->phrases, 0, 97) . '...' :
4686 $action->phrases);
4687 }
4688 ?>
4689 </div>
4690 </div>
4691
4692 <div class="mxchat-threshold-control">
4693 <div class="mxchat-threshold-label">
4694 <?php esc_html_e('Similarity Threshold:', 'mxchat'); ?>
4695 <span class="mxchat-threshold-value"><?php echo esc_html($threshold_value); ?>%</span>
4696 </div>
4697 </div>
4698 </div>
4699
4700 <div class="mxchat-card-footer">
4701 <?php
4702 // Check if it's a form action
4703 $is_form_action = preg_match('/Form (\d+)/', $action->intent_label, $form_matches);
4704
4705 // Check if it's a recommendation flow action
4706 $is_flow_action = preg_match('/Recommendation Flow (\d+)/', $action->intent_label, $flow_matches);
4707
4708 if ($is_form_action) {
4709 $form_id = isset($form_matches[1]) ? $form_matches[1] : '';
4710 ?>
4711 <a href="<?php echo esc_url(admin_url('admin.php?page=mxchat-forms&action=edit&form_id=' . $form_id)); ?>"
4712 class="mxchat-button-primary">
4713 <span class="dashicons dashicons-feedback"></span>
4714 <?php esc_html_e('Edit Form', 'mxchat'); ?>
4715 </a>
4716 <?php } elseif ($is_flow_action) {
4717 ?>
4718 <a href="<?php echo esc_url(admin_url('admin.php?page=mxchat-smart-recommender')); ?>"
4719 class="mxchat-button-primary">
4720 <span class="dashicons dashicons-list-view"></span>
4721 <?php esc_html_e('Manage Flows', 'mxchat'); ?>
4722 </a>
4723 <?php } else { ?>
4724 <button type="button"
4725 class="mxchat-button-secondary mxchat-edit-button"
4726 data-action-id="<?php echo esc_attr($action->id); ?>"
4727 data-phrases="<?php echo esc_attr($action->phrases); ?>"
4728 data-label="<?php echo esc_attr($action->intent_label); ?>"
4729 data-threshold="<?php echo esc_attr(round($action->similarity_threshold * 100)); ?>"
4730 data-callback-function="<?php echo esc_attr($action->callback_function); ?>">
4731 <span class="dashicons dashicons-edit"></span>
4732 <?php esc_html_e('Edit', 'mxchat'); ?>
4733 </button>
4734 <form method="post"
4735 action="<?php echo esc_url(admin_url('admin-post.php')); ?>"
4736 class="mxchat-delete-form"
4737 onsubmit="return confirm('<?php esc_attr_e('Are you sure you want to delete this action?', 'mxchat'); ?>');">
4738 <?php wp_nonce_field('mxchat_delete_intent_nonce'); ?>
4739 <input type="hidden" name="action" value="mxchat_delete_intent">
4740 <input type="hidden" name="intent_id" value="<?php echo esc_attr($action->id); ?>">
4741 <button type="submit" class="mxchat-button-text mxchat-delete-button">
4742 <span class="dashicons dashicons-trash"></span>
4743 <?php esc_html_e('Delete', 'mxchat'); ?>
4744 </button>
4745 </form>
4746 <?php } ?>
4747 </div>
4748
4749
4750 </div>
4751 <?php endforeach; ?>
4752 <?php else : ?>
4753 <!-- If no actions found -->
4754 <div class="mxchat-no-actions">
4755 <div class="mxchat-empty-state">
4756 <span class="dashicons dashicons-format-chat"></span>
4757 <h2><?php esc_html_e('No actions found', 'mxchat'); ?></h2>
4758 <p><?php esc_html_e('Get started by creating your first action to enhance your chatbot.', 'mxchat'); ?></p>
4759 <button type="button" id="mxchat-create-first-action" class="mxchat-button-primary">
4760 <?php esc_html_e('Create Your First Action', 'mxchat'); ?>
4761 </button>
4762 </div>
4763 </div>
4764 <?php endif; ?>
4765 </div>
4766 </div>
4767
4768 <?php if ($total_pages > 1) : ?>
4769 <div class="mxchat-pagination">
4770 <?php
4771 echo paginate_links(array(
4772 'base' => add_query_arg('paged', '%#%'),
4773 'format' => '',
4774 'prev_text' => __('&laquo; Previous', 'mxchat'),
4775 'next_text' => __('Next &raquo;', 'mxchat'),
4776 'total' => $total_pages,
4777 'current' => $page
4778 ));
4779 ?>
4780 </div>
4781 <?php endif; ?>
4782
4783 <!-- Add/Edit Action Modal with Step-Based Approach -->
4784 <!-- Complete Modal HTML with Defined Groups Variable -->
4785 <div id="mxchat-action-modal" class="mxchat-modal" style="display: none;">
4786 <div class="mxchat-modal-content">
4787 <span class="mxchat-modal-close">&times;</span>
4788
4789 <form id="mxchat-action-form" method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>">
4790 <!-- Dynamic nonce field -->
4791 <div id="action-nonce-container">
4792 <?php wp_nonce_field('mxchat_add_intent_nonce', 'add_intent_nonce'); ?>
4793 </div>
4794 <input type="hidden" name="action" id="form_action_type" value="mxchat_add_intent">
4795 <input type="hidden" name="intent_id" id="edit_action_id" value="">
4796 <input type="hidden" name="callback_function" id="callback_function" value="">
4797
4798 <!-- Step 1: Action Type Selection -->
4799 <div id="mxchat-action-step-1" class="mxchat-action-step active">
4800 <div class="mxchat-step-indicator">
4801 <div class="mxchat-step-number">1</div>
4802 <div class="mxchat-step-title"><?php esc_html_e('Select Action Type', 'mxchat'); ?></div>
4803 </div>
4804
4805 <div id="mxchat-action-type-selector" class="mxchat-action-type-selector">
4806 <div class="mxchat-action-type-search">
4807 <span class="dashicons dashicons-search"></span>
4808 <input type="text" id="action-type-search" placeholder="<?php esc_attr_e('Search action types...', 'mxchat'); ?>" class="mxchat-action-type-search-input">
4809 </div>
4810
4811 <?php
4812 // Get the callbacks - IMPORTANT: Define the $groups variable here
4813 $groups = $this->mxchat_get_available_callbacks(true, true);
4814 ?>
4815
4816 <div class="mxchat-action-type-categories">
4817 <button type="button" class="mxchat-category-button active" data-category="all"><?php esc_html_e('All', 'mxchat'); ?></button>
4818 <?php
4819 // Get unique categories from the defined groups
4820 foreach ($groups as $group_label => $group_callbacks) :
4821 $category_slug = sanitize_title($group_label);
4822 ?>
4823 <button type="button" class="mxchat-category-button" data-category="<?php echo esc_attr($category_slug); ?>"><?php echo esc_html($group_label); ?></button>
4824 <?php endforeach; ?>
4825 </div>
4826
4827 <div class="mxchat-action-types-grid">
4828 <?php
4829 // Generate action cards from available callbacks
4830 foreach ($groups as $group_label => $group_callbacks) :
4831 $category_slug = sanitize_title($group_label);
4832
4833 foreach ($group_callbacks as $function => $data) :
4834 $label = $data['label'];
4835 $pro_only = $data['pro_only'];
4836 $icon = isset($data['icon']) ? $data['icon'] : 'admin-generic';
4837 $description = isset($data['description']) ? $data['description'] : '';
4838 $is_addon = isset($data['addon']) && $data['addon'] !== false;
4839 $addon_name = isset($data['addon_name']) ? $data['addon_name'] : '';
4840 $is_installed = isset($data['installed']) ? $data['installed'] : true;
4841
4842 // Determine card status and styling
4843 $card_class = 'mxchat-action-type-card';
4844 $icon_class = 'mxchat-action-type-icon';
4845 $status_badge = '';
4846
4847 if ($pro_only && !$this->is_activated) {
4848 // Pro feature but no Pro license
4849 $icon_class .= ' pro-feature';
4850 $status_badge = '<span class="mxchat-pro-badge">' . esc_html__('Pro', 'mxchat') . '</span>';
4851 }
4852
4853 if ($is_addon && !$is_installed) {
4854 // Add-on not installed
4855 $card_class .= ' not-installed';
4856 $status_badge .= '<span class="mxchat-addon-badge">' . esc_html__('Add-on Required', 'mxchat') . '</span>';
4857 }
4858
4859 // Default description if none provided
4860 if (empty($description)) {
4861 $description = sprintf(
4862 esc_html__('Use the %s action in your chatbot', 'mxchat'),
4863 $label
4864 );
4865 }
4866 ?>
4867 <div class="<?php echo esc_attr($card_class); ?>"
4868 data-category="<?php echo esc_attr($category_slug); ?>"
4869 data-value="<?php echo esc_attr($function); ?>"
4870 data-label="<?php echo esc_attr($label); ?>"
4871 data-pro="<?php echo $pro_only ? 'true' : 'false'; ?>"
4872 data-addon="<?php echo esc_attr($is_addon ? $data['addon'] : ''); ?>"
4873 data-installed="<?php echo $is_installed ? 'true' : 'false'; ?>">
4874 <div class="<?php echo esc_attr($icon_class); ?>">
4875 <span class="dashicons dashicons-<?php echo esc_attr($icon); ?>"></span>
4876 </div>
4877 <div class="mxchat-action-type-info">
4878 <h4><?php echo esc_html($label); ?></h4>
4879 <p><?php echo esc_html($description); ?></p>
4880 <?php if (!empty($status_badge)) : ?>
4881 <?php echo $status_badge; ?>
4882 <?php endif; ?>
4883
4884 <?php if ($is_addon && !$is_installed) : ?>
4885 <div class="mxchat-addon-info">
4886 <?php echo esc_html(sprintf(
4887 __('Requires %s', 'mxchat'),
4888 $addon_name
4889 )); ?>
4890 </div>
4891 <?php endif; ?>
4892 </div>
4893 </div>
4894 <?php endforeach;
4895 endforeach; ?>
4896 </div>
4897 </div>
4898
4899 <div class="mxchat-modal-actions">
4900 <button type="button" class="mxchat-button-secondary mxchat-modal-cancel">
4901 <?php esc_html_e('Cancel', 'mxchat'); ?>
4902 </button>
4903 </div>
4904 </div>
4905
4906 <!-- Step 2: Action Configuration -->
4907 <div id="mxchat-action-step-2" class="mxchat-action-step">
4908 <div class="mxchat-step-indicator">
4909 <div class="mxchat-step-number">2</div>
4910 <div class="mxchat-step-title"><?php esc_html_e('Configure Action', 'mxchat'); ?></div>
4911 </div>
4912
4913 <div class="mxchat-selected-action">
4914 <button type="button" class="mxchat-back-button" id="mxchat-back-to-step-1">
4915 <span class="dashicons dashicons-arrow-left-alt"></span>
4916 <?php esc_html_e('Back to Action Types', 'mxchat'); ?>
4917 </button>
4918 <div class="mxchat-selected-action-info">
4919 <div id="selected-action-icon" class="mxchat-action-type-icon">
4920 <span class="dashicons dashicons-admin-generic"></span>
4921 </div>
4922 <div class="mxchat-selected-action-details">
4923 <h3 id="selected-action-title"><?php esc_html_e('Selected Action', 'mxchat'); ?></h3>
4924 <p id="selected-action-description"><?php esc_html_e('Configure this action for your chatbot', 'mxchat'); ?></p>
4925 </div>
4926 </div>
4927 </div>
4928
4929 <div class="mxchat-form-group">
4930 <label for="intent_label">
4931 <?php esc_html_e('Action Label (For your reference only)', 'mxchat'); ?>
4932 </label>
4933 <input name="intent_label" type="text" id="intent_label" required
4934 class="mxchat-intent-input"
4935 placeholder="<?php esc_attr_e('Example: Newsletter Signup', 'mxchat'); ?>">
4936 </div>
4937
4938 <div class="mxchat-form-group">
4939 <label for="phrases">
4940 <?php esc_html_e('Trigger Phrases (comma-separated)', 'mxchat'); ?>
4941 </label>
4942 <textarea name="phrases" id="action_phrases" rows="5" required
4943 class="mxchat-intent-textarea"
4944 placeholder="<?php esc_attr_e('Example: sign me up, subscribe me, I want to join, add me to the newsletter', 'mxchat'); ?>"></textarea>
4945 </div>
4946
4947 <div class="mxchat-form-group">
4948 <label for="similarity_threshold">
4949 <?php esc_html_e('Similarity Threshold', 'mxchat'); ?>
4950 <span class="mxchat-threshold-value-display">85%</span>
4951 </label>
4952 <div class="mxchat-slider-group modal-slider">
4953 <input type="range"
4954 name="similarity_threshold"
4955 id="similarity_threshold"
4956 min="70"
4957 max="95"
4958 value="85"
4959 class="mxchat-intent-slider"
4960 oninput="document.querySelector('.mxchat-threshold-value-display').textContent = this.value + '%'">
4961 </div>
4962 <div class="mxchat-threshold-hint">
4963 <?php esc_html_e('Lower values make the action trigger more easily. Higher values require more exact matches.', 'mxchat'); ?>
4964 </div>
4965 </div>
4966
4967 <div class="mxchat-modal-actions">
4968 <button type="button" class="mxchat-button-secondary mxchat-modal-cancel">
4969 <?php esc_html_e('Cancel', 'mxchat'); ?>
4970 </button>
4971 <button type="submit" class="mxchat-button-primary" id="mxchat-save-action-btn">
4972 <?php esc_html_e('Save Action', 'mxchat'); ?>
4973 </button>
4974 </div>
4975 </div>
4976 </form>
4977 </div>
4978 </div>
4979
4980 <div id="mxchat-action-loading" class="mxchat-action-loading" style="display: none;">
4981 <div class="mxchat-action-loading-spinner"></div>
4982 <div class="mxchat-action-loading-text">
4983 <?php esc_html_e('Saving action, please wait...', 'mxchat'); ?>
4984 </div>
4985 </div>
4986 </div><!-- .mxchat-wrapper -->
4987 <?php
4988 }
4989
4990 /**
4991 * Helper method to trim phrases for display
4992 * Adding this in case it doesn't exist in your class
4993 */
4994 private function get_trimmed_phrases($phrases, $max_length = 100) {
4995 if (strlen($phrases) <= $max_length) {
4996 return $phrases;
4997 }
4998
4999 $trimmed = substr($phrases, 0, $max_length);
5000 $last_comma = strrpos($trimmed, ',');
5001
5002 if ($last_comma !== false) {
5003 $trimmed = substr($trimmed, 0, $last_comma);
5004 }
5005
5006 return $trimmed . '...';
5007 }
5008
5009 /**
5010 * AJAX handler for toggling an action on/off
5011 */
5012 public function mxchat_toggle_action() {
5013 // Check nonce
5014 if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'mxchat_actions_nonce')) {
5015 wp_send_json_error(array('message' => 'Security check failed'));
5016 return;
5017 }
5018
5019 // Check permissions
5020 if (!current_user_can('manage_options')) {
5021 wp_send_json_error(array('message' => 'Permission denied'));
5022 return;
5023 }
5024
5025 // Validate params
5026 $intent_id = isset($_POST['intent_id']) ? intval($_POST['intent_id']) : 0;
5027 $enabled = isset($_POST['enabled']) ? (bool)$_POST['enabled'] : false;
5028
5029 if (!$intent_id) {
5030 wp_send_json_error(array('message' => 'Invalid action ID'));
5031 return;
5032 }
5033
5034 // Update the intent/action status in the database
5035 global $wpdb;
5036 $table_name = $wpdb->prefix . 'mxchat_intents';
5037
5038 // Using the 'enabled' field - add this field if it doesn't exist
5039 $result = $wpdb->update(
5040 $table_name,
5041 array('enabled' => $enabled ? 1 : 0),
5042 array('id' => $intent_id),
5043 array('%d'),
5044 array('%d')
5045 );
5046
5047 if ($result === false) {
5048 wp_send_json_error(array('message' => 'Database error'));
5049 return;
5050 }
5051
5052 wp_send_json_success();
5053 }
5054
5055 /**
5056 * Add the 'enabled' column to the intents table if it doesn't exist
5057 * Call this during plugin activation or update
5058 */
5059 public function mxchat_add_enabled_column_to_intents() {
5060 global $wpdb;
5061 $table_name = $wpdb->prefix . 'mxchat_intents';
5062
5063 // Check if the column already exists
5064 $columns = $wpdb->get_results("SHOW COLUMNS FROM $table_name LIKE 'enabled'");
5065
5066 if (empty($columns)) {
5067 // Add the column with default value of 1 (enabled)
5068 $wpdb->query("ALTER TABLE $table_name ADD COLUMN enabled TINYINT(1) NOT NULL DEFAULT 1");
5069 }
5070 }
5071
5072 /**
5073 * Handle embedding generation errors using existing admin notice system
5074 *
5075 * @param string $message Error message to display
5076 * @param bool $redirect Whether to redirect back to the actions page
5077 * @return void
5078 */
5079 private function handle_embedding_error($message, $redirect = true) {
5080 // Store the error message in the existing transient
5081 set_transient('mxchat_admin_notice_error', $message, 60);
5082
5083 if ($redirect) {
5084 // Redirect back to the actions page
5085 $redirect_url = add_query_arg(
5086 array(
5087 'page' => 'mxchat-actions'
5088 ),
5089 admin_url('admin.php')
5090 );
5091 wp_safe_redirect($redirect_url);
5092 exit;
5093 }
5094 }
5095
5096 /**
5097 * Handle editing of intent phrases - with improved error handling
5098 *
5099 * @since 1.0.0
5100 * @return void
5101 */
5102 public function mxchat_handle_edit_intent() {
5103 // Security checks (nonce and permissions)
5104 if (!current_user_can('manage_options')) {
5105 wp_die(esc_html__('Unauthorized user', 'mxchat'));
5106 }
5107 check_admin_referer('mxchat_edit_intent');
5108
5109 // Get POST data
5110 $intent_id = isset($_POST['intent_id']) ? absint($_POST['intent_id']) : 0;
5111 $intent_label = isset($_POST['intent_label']) ? sanitize_text_field($_POST['intent_label']) : '';
5112 $phrases_input = isset($_POST['phrases']) ? sanitize_textarea_field($_POST['phrases']) : '';
5113 $threshold_percentage = isset($_POST['similarity_threshold']) ? intval($_POST['similarity_threshold']) : 85;
5114 $similarity_threshold = min(95, max(70, $threshold_percentage)) / 100; // Convert to 0.70–0.95
5115
5116 // Validate inputs
5117 if (!$intent_id || empty($intent_label) || empty($phrases_input)) {
5118 $this->handle_embedding_error(__('Invalid input. Please ensure all fields are filled out.', 'mxchat'));
5119 return;
5120 }
5121
5122 $phrases_array = array_map('sanitize_text_field', array_filter(array_map('trim', explode(',', $phrases_input))));
5123 if (empty($phrases_array)) {
5124 $this->handle_embedding_error(__('Please enter at least one valid phrase.', 'mxchat'));
5125 return;
5126 }
5127
5128 // Generate embeddings with improved error handling
5129 $vectors = [];
5130 $failed_phrases = [];
5131
5132 foreach ($phrases_array as $phrase) {
5133 $embedding_vector = $this->mxchat_generate_embedding($phrase, $this->options['api_key']);
5134 if (is_array($embedding_vector)) {
5135 $vectors[] = $embedding_vector;
5136 } else {
5137 $failed_phrases[] = $phrase;
5138 }
5139 }
5140
5141 if (!empty($failed_phrases)) {
5142 $this->handle_embedding_error(
5143 sprintf(
5144 __('Error generating embeddings for phrases: %s. Check your embedding API.', 'mxchat'),
5145 implode(', ', $failed_phrases)
5146 )
5147 );
5148 return;
5149 }
5150
5151 if (empty($vectors)) {
5152 $this->handle_embedding_error(__('No valid embeddings generated. Please check your phrases.', 'mxchat'));
5153 return;
5154 }
5155
5156 $combined_vector = $this->mxchat_average_vectors($vectors);
5157 $serialized_vector = maybe_serialize($combined_vector);
5158
5159 // Update the database
5160 global $wpdb;
5161 $table_name = $wpdb->prefix . 'mxchat_intents';
5162
5163 $result = $wpdb->update(
5164 $table_name,
5165 array(
5166 'intent_label' => $intent_label,
5167 'phrases' => implode(', ', $phrases_array),
5168 'embedding_vector' => $serialized_vector,
5169 'similarity_threshold' => $similarity_threshold
5170 ),
5171 array('id' => $intent_id),
5172 array('%s', '%s', '%s', '%f'), // Format: string, string, string, float
5173 array('%d') // Where format: integer
5174 );
5175
5176 if (false === $result) {
5177 $this->handle_embedding_error(__('Failed to update action in database.', 'mxchat'));
5178 return;
5179 }
5180
5181 // Set success message and redirect
5182 set_transient('mxchat_admin_notice_success', __('Intent updated successfully!', 'mxchat'), 60);
5183
5184 $redirect_url = add_query_arg(
5185 array(
5186 'page' => 'mxchat-actions'
5187 ),
5188 admin_url('admin.php')
5189 );
5190 wp_safe_redirect($redirect_url);
5191 exit;
5192 }
5193
5194 /**
5195 * Handle adding new intent - with improved error handling
5196 *
5197 * @return void
5198 */
5199 public function mxchat_handle_add_intent() {
5200 if (!current_user_can('manage_options')) {
5201 wp_die(esc_html__('Unauthorized user', 'mxchat'));
5202 }
5203
5204 check_admin_referer('mxchat_add_intent_nonce');
5205
5206 global $wpdb;
5207 $table_name = $wpdb->prefix . 'mxchat_intents';
5208
5209 $intent_label = isset($_POST['intent_label']) ? sanitize_text_field($_POST['intent_label']) : '';
5210 $phrases_input = isset($_POST['phrases']) ? sanitize_textarea_field($_POST['phrases']) : '';
5211 $callback_function = isset($_POST['callback_function']) ? sanitize_text_field($_POST['callback_function']) : '';
5212 $default_threshold = 0.85;
5213
5214 if (empty($intent_label) || empty($callback_function) || empty($phrases_input)) {
5215 $this->handle_embedding_error(__('Invalid input. Please ensure all fields are filled out.', 'mxchat'));
5216 return;
5217 }
5218
5219 $available_callbacks = $this->mxchat_get_available_callbacks();
5220
5221 if (!array_key_exists($callback_function, $available_callbacks)) {
5222 $this->handle_embedding_error(__('Invalid callback function selected.', 'mxchat'));
5223 return;
5224 }
5225
5226 $is_pro_only = $available_callbacks[$callback_function]['pro_only'];
5227 if ($is_pro_only && !$this->is_activated) {
5228 $this->handle_embedding_error(__('This callback function is available in the Pro version only.', 'mxchat'));
5229 return;
5230 }
5231
5232 $phrases_array = array_map('sanitize_text_field', array_filter(array_map('trim', explode(',', $phrases_input))));
5233
5234 if (empty($phrases_array)) {
5235 $this->handle_embedding_error(__('Please enter at least one valid phrase.', 'mxchat'));
5236 return;
5237 }
5238
5239 // Generate embeddings with improved error handling
5240 $vectors = [];
5241 $failed_phrases = [];
5242
5243 foreach ($phrases_array as $phrase) {
5244 $embedding_vector = $this->mxchat_generate_embedding($phrase, $this->options['api_key']);
5245 if (is_array($embedding_vector)) {
5246 $vectors[] = $embedding_vector;
5247 } else {
5248 $failed_phrases[] = $phrase;
5249 }
5250 }
5251
5252 if (!empty($failed_phrases)) {
5253 $this->handle_embedding_error(
5254 sprintf(
5255 __('Error generating embeddings for phrases: %s. Check your embedding API.', 'mxchat'),
5256 implode(', ', $failed_phrases)
5257 )
5258 );
5259 return;
5260 }
5261
5262 if (empty($vectors)) {
5263 $this->handle_embedding_error(__('No valid embeddings generated. Please check your phrases.', 'mxchat'));
5264 return;
5265 }
5266
5267 $combined_vector = $this->mxchat_average_vectors($vectors);
5268 $serialized_vector = maybe_serialize($combined_vector);
5269
5270 $result = $wpdb->insert($table_name, [
5271 'intent_label' => $intent_label,
5272 'phrases' => implode(', ', $phrases_array),
5273 'embedding_vector' => $serialized_vector,
5274 'callback_function' => $callback_function,
5275 'similarity_threshold' => $default_threshold,
5276 ]);
5277
5278 if ($result === false) {
5279 $this->handle_embedding_error(__('Database error: ', 'mxchat') . $wpdb->last_error);
5280 return;
5281 }
5282
5283 // Set success message
5284 set_transient('mxchat_admin_notice_success', __('New intent added successfully!', 'mxchat'), 60);
5285
5286 wp_safe_redirect(admin_url('admin.php?page=mxchat-actions'));
5287 exit;
5288 }
5289
5290
5291 /**
5292 * Update intent threshold with AJAX support
5293 */
5294 public function mxchat_update_intent_threshold() {
5295 // Check permissions
5296 if (!current_user_can('manage_options')) {
5297 if (wp_doing_ajax()) {
5298 wp_send_json_error(array('message' => 'Unauthorized user'));
5299 return;
5300 }
5301 wp_die(esc_html__('Unauthorized user', 'mxchat'));
5302 }
5303
5304 // Verify nonce
5305 check_admin_referer('mxchat_update_intent_threshold_nonce');
5306
5307 // Process the update if we have valid data
5308 if (isset($_POST['intent_id'], $_POST['intent_threshold'])) {
5309 global $wpdb;
5310 $table_name = $wpdb->prefix . 'mxchat_intents';
5311 $intent_id = intval($_POST['intent_id']);
5312 $threshold_percentage = max(70, min(95, intval($_POST['intent_threshold'])));
5313 $similarity_threshold = $threshold_percentage / 100;
5314
5315 $result = $wpdb->update(
5316 $table_name,
5317 ['similarity_threshold' => $similarity_threshold],
5318 ['id' => $intent_id],
5319 ['%f'],
5320 ['%d']
5321 );
5322
5323 // Handle AJAX requests
5324 if (wp_doing_ajax()) {
5325 if ($result === false) {
5326 wp_send_json_error(array('message' => 'Failed to update threshold'));
5327 } else {
5328 wp_send_json_success(array('threshold' => $threshold_percentage));
5329 }
5330 return;
5331 }
5332 }
5333
5334 // Redirect for regular form submissions
5335 wp_safe_redirect(admin_url('admin.php?page=mxchat-actions&updated=true'));
5336 exit;
5337 }
5338
5339
5340
5341 /**
5342 * Enhanced get_available_callbacks function with form action exclusion
5343 *
5344 * @param bool $grouped Whether to return callbacks grouped by category
5345 * @param bool $include_all Whether to include all potential actions (even if add-on not installed)
5346 * @return array Callbacks data with icons, descriptions and availability status
5347 */
5348 private function mxchat_get_available_callbacks($grouped = false, $include_all = true) {
5349 // Load WordPress plugin functions if needed
5350 if (!function_exists('get_plugins')) {
5351 require_once ABSPATH . 'wp-admin/includes/plugin.php';
5352 }
5353
5354 // Get active plugins
5355 $active_plugins = get_option('active_plugins', array());
5356
5357 // Functions to exclude from the action selector only if Pro is activated
5358 // If user doesn't have Pro, show these so they can see what they're missing
5359 $excluded_when_pro_active_functions = array(
5360 'mxchat_handle_form_collection', // Forms add-on action
5361 'mxchat_sr_recommendation_flow' // Smart Recommender flow actions
5362 );
5363
5364 // Always excluded functions (regardless of Pro status)
5365 $always_excluded_functions = array();
5366
5367 // Combine exclusion lists based on Pro activation status
5368 $excluded_functions = $always_excluded_functions;
5369 if ($this->is_activated) {
5370 // Only exclude add-on managed functions if Pro is active
5371 $excluded_functions = array_merge($excluded_functions, $excluded_when_pro_active_functions);
5372 }
5373
5374 // Define add-on plugin files and their corresponding action functions
5375 $addon_plugins = array(
5376 'mxchat-woo/mxchat-woo.php' => array(
5377 'functions' => array(
5378 'mxchat_handle_product_recommendations',
5379 'mxchat_handle_order_history',
5380 'mxchat_show_product_card',
5381 'mxchat_add_to_cart',
5382 'mxchat_checkout_redirect'
5383 ),
5384 'name' => __('WooCommerce Add-on', 'mxchat'),
5385 'pro_required' => true
5386 ),
5387 'mxchat-perplexity/mxchat-perplexity.php' => array(
5388 'functions' => array('mxchat_perplexity_research'),
5389 'name' => __('Perplexity Add-on', 'mxchat'),
5390 'pro_required' => true
5391 ),
5392 'mxchat-forms/mxchat-forms.php' => array(
5393 'functions' => array('mxchat_handle_form_collection'),
5394 'name' => __('Forms Add-on', 'mxchat'),
5395 'pro_required' => true
5396 ),
5397 'mxchat-smart-recommender/mxchat-smart-recommender.php' => array(
5398 'functions' => array('mxchat_sr_recommendation_flow'),
5399 'name' => __('Smart Recommender Add-on', 'mxchat'),
5400 'pro_required' => true
5401 ),
5402 // Add other add-ons and their functions here
5403 );
5404
5405 // Get the functions that are provided by active add-ons
5406 $addon_provided_functions = array();
5407 $addon_function_mapping = array(); // Maps functions to their add-on info
5408
5409 // Check which add-ons are active
5410 foreach ($addon_plugins as $plugin_file => $addon_info) {
5411 $is_active = in_array($plugin_file, $active_plugins);
5412
5413 // For each function in this addon
5414 foreach ($addon_info['functions'] as $function) {
5415 // Consider a function installed only if:
5416 // 1. The add-on is active AND
5417 // 2. Either it doesn't require Pro OR Pro is activated
5418 $is_installed = $is_active && (!$addon_info['pro_required'] || $this->is_activated);
5419
5420 // If the add-on is installed, mark this function as provided by an add-on
5421 if ($is_installed) {
5422 $addon_provided_functions[] = $function;
5423 }
5424
5425 // Store addon info for this function regardless of installation status
5426 $addon_function_mapping[$function] = array(
5427 'addon' => basename(dirname($plugin_file)),
5428 'addon_name' => $addon_info['name'],
5429 'pro_required' => $addon_info['pro_required'],
5430 'is_active' => $is_active,
5431 'is_installed' => $is_installed
5432 );
5433 }
5434 }
5435
5436 // Core callbacks - always available in the base plugin
5437 $core_callbacks = array(
5438 'mxchat_handle_email_capture' => array(
5439 'label' => __('Loops Email Capture', 'mxchat'),
5440 'pro_only' => false,
5441 'group' => __('Customer Engagement', 'mxchat'),
5442 'icon' => 'email-alt',
5443 'description' => __('Collect visitor emails for your mailing list in Loops', 'mxchat'),
5444 'addon' => false, // Not from an add-on
5445 'installed' => true // Always installed with base plugin
5446 ),
5447 'mxchat_handle_search_request' => array(
5448 'label' => __('Brave Web Search', 'mxchat'),
5449 'pro_only' => false,
5450 'group' => __('Search Features', 'mxchat'),
5451 'icon' => 'search',
5452 'description' => __('Let users search the web directly from the chat', 'mxchat'),
5453 'addon' => false,
5454 'installed' => true
5455 ),
5456 'mxchat_handle_image_search_request' => array(
5457 'label' => __('Brave Image Search', 'mxchat'),
5458 'pro_only' => false,
5459 'group' => __('Search Features', 'mxchat'),
5460 'icon' => 'format-image',
5461 'description' => __('Search and display images in the chat conversation', 'mxchat'),
5462 'addon' => false,
5463 'installed' => true
5464 ),
5465 // Pro core features - check is_activated property
5466 'mxchat_generate_image' => array(
5467 'label' => __('Generate Image', 'mxchat'),
5468 'pro_only' => false,
5469 'group' => __('Other Features', 'mxchat'),
5470 'icon' => 'art',
5471 'description' => __('Create images with DALL-E 3 from OpenAI (requires OpenAI API key)', 'mxchat'),
5472 'addon' => false,
5473 'installed' => true
5474 ),
5475 'mxchat_handle_pdf_discussion' => array(
5476 'label' => __('Chat with PDF', 'mxchat'),
5477 'pro_only' => false,
5478 'group' => __('Other Features', 'mxchat'),
5479 'icon' => 'media-document',
5480 'description' => __('Answer questions about uploaded PDF documents', 'mxchat'),
5481 'addon' => false,
5482 'installed' => true
5483 ),
5484 'mxchat_live_agent_handover' => array(
5485 'label' => __('Slack Live Agent', 'mxchat'),
5486 'pro_only' => false,
5487 'group' => __('Customer Engagement', 'mxchat'),
5488 'icon' => 'admin-users',
5489 'description' => __('Transfer conversation to a human support agent on Slack', 'mxchat'),
5490 'addon' => false,
5491 'installed' => true
5492 ),
5493 'mxchat_handle_switch_to_chatbot_intent' => array(
5494 'label' => __('Back to Chatbot', 'mxchat'),
5495 'pro_only' => false,
5496 'group' => __('Customer Engagement', 'mxchat'),
5497 'icon' => 'backup',
5498 'description' => __('Return from live agent mode to AI chatbot', 'mxchat'),
5499 'addon' => false,
5500 'installed' => true
5501 ),
5502 );
5503
5504 // Add-on callbacks with placeholders - only include if the add-on is NOT active
5505 $addon_callbacks = array(
5506 // WooCommerce Add-on
5507 'mxchat_handle_product_recommendations' => array(
5508 'label' => __('Product Recommendations', 'mxchat'),
5509 'pro_only' => true,
5510 'group' => __('WooCommerce Features', 'mxchat'),
5511 'icon' => 'cart',
5512 'description' => __('Suggest products based on customer preferences', 'mxchat'),
5513 ),
5514 'mxchat_handle_order_history' => array(
5515 'label' => __('Order History', 'mxchat'),
5516 'pro_only' => true,
5517 'group' => __('WooCommerce Features', 'mxchat'),
5518 'icon' => 'clipboard',
5519 'description' => __('Allow customers to check their order status', 'mxchat'),
5520 ),
5521 'mxchat_show_product_card' => array(
5522 'label' => __('Show Product Card', 'mxchat'),
5523 'pro_only' => true,
5524 'group' => __('WooCommerce Features', 'mxchat'),
5525 'icon' => 'products',
5526 'description' => __('Display product information in the chat', 'mxchat'),
5527 ),
5528 'mxchat_add_to_cart' => array(
5529 'label' => __('Add to Cart', 'mxchat'),
5530 'pro_only' => true,
5531 'group' => __('WooCommerce Features', 'mxchat'),
5532 'icon' => 'plus-alt',
5533 'description' => __('Add products to cart directly from chat', 'mxchat'),
5534 ),
5535 'mxchat_checkout_redirect' => array(
5536 'label' => __('Proceed to Checkout', 'mxchat'),
5537 'pro_only' => true,
5538 'group' => __('WooCommerce Features', 'mxchat'),
5539 'icon' => 'arrow-right-alt',
5540 'description' => __('Redirect customer to checkout page', 'mxchat'),
5541 ),
5542
5543 // Perplexity Add-on
5544 'mxchat_perplexity_research' => array(
5545 'label' => __('Perplexity Research', 'mxchat'),
5546 'pro_only' => true,
5547 'group' => __('Search Features', 'mxchat'),
5548 'icon' => 'book-alt',
5549 'description' => __('Allows the chatbot to search the web for accurate, up-to-date answers', 'mxchat'),
5550 ),
5551
5552 // Forms Add-on (only shown when Pro is not activated)
5553 'mxchat_handle_form_collection' => array(
5554 'label' => __('Form Collection', 'mxchat'),
5555 'pro_only' => true,
5556 'group' => __('Form Features', 'mxchat'),
5557 'icon' => 'feedback',
5558 'description' => __('Collect user information through custom forms in chat', 'mxchat'),
5559 ),
5560
5561 // Smart Recommender Add-on (only shown when Pro is not activated)
5562 'mxchat_sr_recommendation_flow' => array(
5563 'label' => __('Smart Recommender Flow', 'mxchat'),
5564 'pro_only' => true,
5565 'group' => __('Recommendation Features', 'mxchat'),
5566 'icon' => 'cart',
5567 'description' => __('Create interactive conversation flows that collect user preferences and deliver personalized product or service recommendations', 'mxchat'),
5568 ),
5569 );
5570
5571 // Enhance add-on callbacks with installation status and addon info
5572 foreach ($addon_callbacks as $function => $data) {
5573 if (isset($addon_function_mapping[$function])) {
5574 $addon_info = $addon_function_mapping[$function];
5575
5576 $addon_callbacks[$function]['addon'] = $addon_info['addon'];
5577 $addon_callbacks[$function]['addon_name'] = $addon_info['addon_name'];
5578 $addon_callbacks[$function]['installed'] = $addon_info['is_installed'];
5579
5580 // Set pro_only based on add-on configuration
5581 $addon_callbacks[$function]['pro_only'] = $addon_info['pro_required'];
5582 } else {
5583 $addon_callbacks[$function]['addon'] = 'unknown';
5584 $addon_callbacks[$function]['addon_name'] = __('Unknown Add-on', 'mxchat');
5585 $addon_callbacks[$function]['installed'] = false;
5586 }
5587 }
5588
5589 // Initialize callbacks with core features
5590 $callbacks = $core_callbacks;
5591
5592 // Get callbacks from active add-ons
5593 $active_addon_callbacks = apply_filters('mxchat_available_callbacks', array());
5594
5595 // Add placeholder callbacks only for add-ons that aren't active
5596 if ($include_all) {
5597 foreach ($addon_callbacks as $function => $data) {
5598 // Skip placeholders for functions provided by active add-ons
5599 if (in_array($function, $addon_provided_functions)) {
5600 continue;
5601 }
5602
5603 // Skip excluded functions
5604 if (in_array($function, $excluded_functions)) {
5605 continue;
5606 }
5607
5608 // Add the placeholder
5609 $callbacks[$function] = $data;
5610 }
5611 }
5612
5613 // Add callbacks from active add-ons (will override placeholders)
5614 foreach ($active_addon_callbacks as $function => $data) {
5615 // Skip excluded functions
5616 if (in_array($function, $excluded_functions)) {
5617 continue;
5618 }
5619
5620 // Always include callbacks from add-ons
5621 $callbacks[$function] = $data;
5622
5623 // Ensure they have the proper add-on info
5624 if (isset($addon_function_mapping[$function])) {
5625 $addon_info = $addon_function_mapping[$function];
5626 $callbacks[$function]['addon'] = $addon_info['addon'];
5627 $callbacks[$function]['addon_name'] = $addon_info['addon_name'];
5628 $callbacks[$function]['installed'] = $addon_info['is_installed'];
5629 $callbacks[$function]['pro_only'] = $addon_info['pro_required'];
5630 }
5631 }
5632
5633 // Just before returning callbacks, sort them to prioritize free features
5634 if (!$grouped) {
5635 // Create temporary arrays for sorting
5636 $free_callbacks = array();
5637 $pro_callbacks = array();
5638
5639 // Split callbacks into free and pro
5640 foreach ($callbacks as $key => $data) {
5641 if (isset($data['pro_only']) && $data['pro_only']) {
5642 $pro_callbacks[$key] = $data;
5643 } else {
5644 $free_callbacks[$key] = $data;
5645 }
5646 }
5647
5648 // Merge with free callbacks first
5649 $callbacks = array_merge($free_callbacks, $pro_callbacks);
5650 }
5651
5652 // Return grouped structure if requested
5653 if ($grouped) {
5654 $grouped_callbacks = array();
5655 foreach ($callbacks as $key => $data) {
5656 $group_label = isset($data['group']) ? $data['group'] : __('Other Features', 'mxchat');
5657
5658 // Ensure we carry forward all the new fields in grouped mode
5659 $callback_data = array(
5660 'label' => $data['label'],
5661 'pro_only' => isset($data['pro_only']) ? $data['pro_only'] : false,
5662 'icon' => isset($data['icon']) ? $data['icon'] : 'admin-generic',
5663 'description' => isset($data['description']) ? $data['description'] : __('Custom action for your chatbot', 'mxchat'),
5664 'addon' => isset($data['addon']) ? $data['addon'] : false,
5665 'addon_name' => isset($data['addon_name']) ? $data['addon_name'] : '',
5666 'installed' => isset($data['installed']) ? $data['installed'] : true
5667 );
5668
5669 $grouped_callbacks[$group_label][$key] = $callback_data;
5670 }
5671
5672 // Sort within each group to prioritize free features
5673 foreach ($grouped_callbacks as $group => $items) {
5674 $free_items = array();
5675 $pro_items = array();
5676
5677 foreach ($items as $key => $data) {
5678 if (isset($data['pro_only']) && $data['pro_only']) {
5679 $pro_items[$key] = $data;
5680 } else {
5681 $free_items[$key] = $data;
5682 }
5683 }
5684
5685 $grouped_callbacks[$group] = array_merge($free_items, $pro_items);
5686 }
5687
5688 return $grouped_callbacks;
5689 }
5690
5691 return $callbacks;
5692 }
5693
5694
5695 private function mxchat_average_vectors($vectors) {
5696 $vector_length = count($vectors[0]);
5697 $sum_vector = array_fill(0, $vector_length, 0);
5698
5699 foreach ($vectors as $vector) {
5700 for ($i = 0; $i < $vector_length; $i++) {
5701 $sum_vector[$i] += $vector[$i];
5702 }
5703 }
5704
5705 // Divide each component by the number of vectors to get the average
5706 $num_vectors = count($vectors);
5707 for ($i = 0; $i < $vector_length; $i++) {
5708 $sum_vector[$i] /= $num_vectors;
5709 }
5710
5711 return $sum_vector;
5712 }
5713
5714 public function mxchat_handle_delete_intent() {
5715 if ( ! current_user_can( 'manage_options' ) ) {
5716 wp_die( esc_html__('Unauthorized user', 'mxchat') );
5717 }
5718
5719 check_admin_referer('mxchat_delete_intent_nonce');
5720
5721 if (isset($_POST['intent_id'])) {
5722 global $wpdb;
5723 $table_name = $wpdb->prefix . 'mxchat_intents';
5724 $intent_id = intval($_POST['intent_id']);
5725
5726 $wpdb->delete($table_name, ['id' => $intent_id], ['%d']);
5727 }
5728
5729 wp_safe_redirect(admin_url('admin.php?page=mxchat-actions'));
5730 exit;
5731 }
5732
5733
5734 public function mxchat_page_init() {
5735 register_setting(
5736 'mxchat_option_group',
5737 'mxchat_options',
5738 array($this, 'mxchat_sanitize')
5739 );
5740
5741 register_setting(
5742 'mxchat_option_group',
5743 'mxchat_similarity_threshold',
5744 array(
5745 'type' => 'number',
5746 'sanitize_callback' => function($value) {
5747 $value = absint($value);
5748 return min(max($value, 20), 95);
5749 },
5750 'default' => 80,
5751 )
5752 );
5753
5754 // Chatbot Settings Section
5755 add_settings_section(
5756 'mxchat_chatbot_section',
5757 esc_html__('Chatbot Settings', 'mxchat'),
5758 null,
5759 'mxchat-chatbot'
5760 );
5761
5762 // Similarity Threshold Slider
5763 add_settings_field(
5764 'similarity_threshold', // Field ID
5765 esc_html__('Similarity Threshold', 'mxchat'), // Field title
5766 array($this, 'mxchat_similarity_threshold_callback'), // Callback function
5767 'mxchat-chatbot', // Page
5768 'mxchat_chatbot_section' // Section
5769 );
5770
5771 add_settings_field(
5772 'append_to_body',
5773 esc_html__('Auto-Display Chatbot', 'mxchat'),
5774 array($this, 'mxchat_append_to_body_callback'),
5775 'mxchat-chatbot',
5776 'mxchat_chatbot_section'
5777 );
5778
5779 add_settings_field(
5780 'api_key',
5781 esc_html__('OpenAI API Key', 'mxchat'),
5782 array($this, 'api_key_callback'),
5783 'mxchat-chatbot',
5784 'mxchat_chatbot_section',
5785 array(
5786 'class' => 'mxchat-setting-row',
5787 'data-provider' => 'openai'
5788 )
5789 );
5790
5791 add_settings_field(
5792 'xai_api_key',
5793 esc_html__('X.AI API Key', 'mxchat'),
5794 array($this, 'xai_api_key_callback'),
5795 'mxchat-chatbot',
5796 'mxchat_chatbot_section',
5797 array(
5798 'class' => 'mxchat-setting-row',
5799 'data-provider' => 'xai'
5800 )
5801 );
5802
5803 add_settings_field(
5804 'claude_api_key',
5805 esc_html__('Claude API Key', 'mxchat'),
5806 array($this, 'claude_api_key_callback'),
5807 'mxchat-chatbot',
5808 'mxchat_chatbot_section',
5809 array(
5810 'class' => 'mxchat-setting-row',
5811 'data-provider' => 'claude'
5812 )
5813 );
5814
5815 add_settings_field(
5816 'deepseek_api_key',
5817 esc_html__('DeepSeek API Key', 'mxchat'),
5818 array($this, 'deepseek_api_key_callback'),
5819 'mxchat-chatbot',
5820 'mxchat_chatbot_section',
5821 array(
5822 'class' => 'mxchat-setting-row',
5823 'data-provider' => 'deepseek'
5824 )
5825 );
5826
5827 add_settings_field(
5828 'gemini_api_key',
5829 esc_html__('Google Gemini API Key', 'mxchat'),
5830 array($this, 'gemini_api_key_callback'),
5831 'mxchat-chatbot',
5832 'mxchat_chatbot_section',
5833 array(
5834 'class' => 'mxchat-setting-row',
5835 'data-provider' => 'gemini'
5836 )
5837 );
5838
5839 add_settings_field(
5840 'voyage_api_key',
5841 esc_html__('Voyage AI API Key', 'mxchat'),
5842 array($this, 'voyage_api_key_callback'),
5843 'mxchat-chatbot',
5844 'mxchat_chatbot_section',
5845 array(
5846 'class' => 'mxchat-setting-row',
5847 'data-provider' => 'voyage'
5848 )
5849 );
5850
5851 add_settings_field(
5852 'model',
5853 esc_html__('Chat Model', 'mxchat'),
5854 array($this, 'mxchat_model_callback'),
5855 'mxchat-chatbot',
5856 'mxchat_chatbot_section'
5857 );
5858
5859 // Add the settings field
5860 add_settings_field(
5861 'embedding_model',
5862 esc_html__('Embedding Model', 'mxchat'),
5863 array($this, 'embedding_model_callback'),
5864 'mxchat-chatbot',
5865 'mxchat_chatbot_section'
5866 );
5867
5868 add_settings_field(
5869 'system_prompt_instructions',
5870 esc_html__('AI Instructions (Behavior)', 'mxchat'),
5871 array($this, 'system_prompt_instructions_callback'),
5872 'mxchat-chatbot',
5873 'mxchat_chatbot_section'
5874 );
5875
5876
5877 add_settings_field(
5878 'top_bar_title',
5879 esc_html__('Top Bar Title', 'mxchat'),
5880 array($this, 'mxchat_top_bar_title_callback'),
5881 'mxchat-chatbot',
5882 'mxchat_chatbot_section'
5883 );
5884
5885 add_settings_field(
5886 'ai_agent_text',
5887 esc_html__('AI Agent Text', 'mxchat'),
5888 array($this, 'mxchat_ai_agent_text_callback'),
5889 'mxchat-chatbot',
5890 'mxchat_chatbot_section'
5891 );
5892
5893 add_settings_field(
5894 'enable_email_block',
5895 esc_html__('Require Email To Chat', 'mxchat'),
5896 array($this, 'enable_email_block_callback'),
5897 'mxchat-chatbot',
5898 'mxchat_chatbot_section'
5899 );
5900
5901 add_settings_field(
5902 'email_blocker_header_content',
5903 esc_html__('Require Email Chat Content', 'mxchat'),
5904 array($this, 'email_blocker_header_content_callback'),
5905 'mxchat-chatbot',
5906 'mxchat_chatbot_section'
5907 );
5908
5909 add_settings_field(
5910 'email_blocker_button_text',
5911 esc_html__('Require Email Chat Button Text', 'mxchat'),
5912 [$this, 'email_blocker_button_text_callback'],
5913 'mxchat-chatbot',
5914 'mxchat_chatbot_section'
5915 );
5916
5917 add_settings_field(
5918 'intro_message',
5919 esc_html__('Introductory Message', 'mxchat'),
5920 array($this, 'mxchat_intro_message_callback'),
5921 'mxchat-chatbot',
5922 'mxchat_chatbot_section'
5923 );
5924
5925 add_settings_field(
5926 'input_copy',
5927 esc_html__('Input Copy', 'mxchat'),
5928 array($this, 'mxchat_input_copy_callback'),
5929 'mxchat-chatbot',
5930 'mxchat_chatbot_section'
5931 );
5932
5933 add_settings_field(
5934 'pre_chat_message',
5935 esc_html__('Chat Teaser Pop-up', 'mxchat'),
5936 array($this, 'mxchat_pre_chat_message_callback'),
5937 'mxchat-chatbot',
5938 'mxchat_chatbot_section'
5939 );
5940
5941 add_settings_field(
5942 'privacy_toggle',
5943 esc_html__('Toggle Privacy Notice', 'mxchat'),
5944 array($this, 'mxchat_privacy_toggle_callback'),
5945 'mxchat-chatbot',
5946 'mxchat_chatbot_section'
5947 );
5948
5949 add_settings_field(
5950 'complianz_toggle',
5951 esc_html__('Enable Complianz', 'mxchat'),
5952 array($this, 'mxchat_complianz_toggle_callback'),
5953 'mxchat-chatbot',
5954 'mxchat_chatbot_section'
5955 );
5956
5957 add_settings_field(
5958 'link_target_toggle',
5959 esc_html__('Open Links in a New Tab', 'mxchat'),
5960 array($this, 'mxchat_link_target_toggle_callback'),
5961 'mxchat-chatbot',
5962 'mxchat_chatbot_section'
5963 );
5964
5965 add_settings_field(
5966 'chat_persistence_toggle',
5967 esc_html__('Enable Chat Persistence', 'mxchat'),
5968 array($this, 'mxchat_chat_persistence_toggle_callback'),
5969 'mxchat-chatbot',
5970 'mxchat_chatbot_section'
5971 );
5972
5973 add_settings_field(
5974 'popular_question_1',
5975 esc_html__('Quick Question 1', 'mxchat'),
5976 array($this, 'mxchat_popular_question_1_callback'),
5977 'mxchat-chatbot',
5978 'mxchat_chatbot_section'
5979 );
5980
5981 add_settings_field(
5982 'popular_question_2',
5983 esc_html__('Quick Question 2', 'mxchat'),
5984 array($this, 'mxchat_popular_question_2_callback'),
5985 'mxchat-chatbot',
5986 'mxchat_chatbot_section'
5987 );
5988
5989 add_settings_field(
5990 'popular_question_3',
5991 esc_html__('Quick Question 3', 'mxchat'),
5992 array($this, 'mxchat_popular_question_3_callback'),
5993 'mxchat-chatbot',
5994 'mxchat_chatbot_section'
5995 );
5996
5997 add_settings_field(
5998 'additional_popular_questions',
5999 esc_html__('Additional Quick Questions', 'mxchat'),
6000 array($this, 'mxchat_additional_popular_questions_callback'),
6001 'mxchat-chatbot',
6002 'mxchat_chatbot_section'
6003 );
6004
6005
6006 add_settings_field(
6007 'rate_limits',
6008 __('Rate Limits Settings', 'mxchat'),
6009 array($this, 'mxchat_rate_limits_callback'),
6010 'mxchat-chatbot',
6011 'mxchat_chatbot_section'
6012 );
6013
6014 // Loops Settings Section
6015 add_settings_section(
6016 'mxchat_loops_section',
6017 esc_html__('Loops Settings', 'mxchat'),
6018 null,
6019 'mxchat-embed'
6020 );
6021
6022 // Loops Settings Fields
6023 add_settings_field(
6024 'loops_api_key',
6025 esc_html__('Loops API Key', 'mxchat'),
6026 array($this, 'mxchat_loops_api_key_callback'),
6027 'mxchat-embed',
6028 'mxchat_loops_section'
6029 );
6030
6031 add_settings_field(
6032 'loops_mailing_list',
6033 esc_html__('Loops Mailing List', 'mxchat'),
6034 array($this, 'mxchat_loops_mailing_list_callback'),
6035 'mxchat-embed',
6036 'mxchat_loops_section'
6037 );
6038
6039 add_settings_field(
6040 'triggered_phrase_response',
6041 esc_html__('Triggered Phrase Response', 'mxchat'),
6042 array($this, 'mxchat_triggered_phrase_response_callback'),
6043 'mxchat-embed',
6044 'mxchat_loops_section'
6045 );
6046
6047 add_settings_field(
6048 'email_capture_response',
6049 esc_html__('Email Capture Response', 'mxchat'),
6050 array($this, 'mxchat_email_capture_response_callback'),
6051 'mxchat-embed',
6052 'mxchat_loops_section'
6053 );
6054
6055 // Brave Search Settings Fields
6056 add_settings_section(
6057 'mxchat_brave_section',
6058 __('Brave Search Settings', 'mxchat'),
6059 array($this, 'mxchat_brave_section_callback'),
6060 'mxchat-embed'
6061 );
6062
6063 add_settings_field(
6064 'brave_api_key',
6065 __('Brave API Key', 'mxchat'),
6066 array($this, 'mxchat_brave_api_key_callback'),
6067 'mxchat-embed',
6068 'mxchat_brave_section'
6069 );
6070
6071 add_settings_field(
6072 'brave_image_count',
6073 __('Number of Images to Return', 'mxchat'),
6074 array($this, 'mxchat_brave_image_count_callback'),
6075 'mxchat-embed',
6076 'mxchat_brave_section'
6077 );
6078
6079 add_settings_field(
6080 'brave_safe_search',
6081 __('Safe Search', 'mxchat'),
6082 array($this, 'mxchat_brave_safe_search_callback'),
6083 'mxchat-embed',
6084 'mxchat_brave_section'
6085 );
6086
6087 add_settings_field(
6088 'brave_news_count',
6089 __('Number of News Articles', 'mxchat'),
6090 array($this, 'mxchat_brave_news_count_callback'),
6091 'mxchat-embed',
6092 'mxchat_brave_section'
6093 );
6094
6095 add_settings_field(
6096 'brave_country',
6097 __('Country', 'mxchat'),
6098 array($this, 'mxchat_brave_country_callback'),
6099 'mxchat-embed',
6100 'mxchat_brave_section'
6101 );
6102
6103 add_settings_field(
6104 'brave_language',
6105 __('Language', 'mxchat'),
6106 array($this, 'mxchat_brave_language_callback'),
6107 'mxchat-embed',
6108 'mxchat_brave_section'
6109 );
6110
6111 // Chat with PDF Intent Settings Fields
6112 add_settings_section(
6113 'mxchat_pdf_intent_section',
6114 __('Toolbar Settings & Intents', 'mxchat'),
6115 array($this, 'mxchat_pdf_intent_section_callback'),
6116 'mxchat-embed'
6117 );
6118
6119 add_settings_field(
6120 'chat_toolbar_toggle',
6121 __('Show Chat Toolbar', 'mxchat'),
6122 array($this, 'mxchat_chat_toolbar_toggle_callback'),
6123 'mxchat-embed',
6124 'mxchat_pdf_intent_section'
6125 );
6126
6127 // PDF Upload Button Toggle
6128 add_settings_field(
6129 'show_pdf_upload_button',
6130 __('Show PDF Upload Button', 'mxchat'),
6131 array($this, 'mxchat_show_pdf_upload_button_callback'),
6132 'mxchat-embed',
6133 'mxchat_pdf_intent_section'
6134 );
6135
6136 // Word Upload Button Toggle
6137 add_settings_field(
6138 'show_word_upload_button',
6139 __('Show Word Upload Button', 'mxchat'),
6140 array($this, 'mxchat_show_word_upload_button_callback'),
6141 'mxchat-embed',
6142 'mxchat_pdf_intent_section'
6143 );
6144
6145 add_settings_field(
6146 'pdf_intent_trigger_text',
6147 __('Intent Trigger Text', 'mxchat'),
6148 array($this, 'mxchat_pdf_intent_trigger_text_callback'),
6149 'mxchat-embed',
6150 'mxchat_pdf_intent_section'
6151 );
6152
6153 add_settings_field(
6154 'pdf_intent_success_text',
6155 __('Success Text', 'mxchat'),
6156 array($this, 'mxchat_pdf_intent_success_text_callback'),
6157 'mxchat-embed',
6158 'mxchat_pdf_intent_section'
6159 );
6160
6161 add_settings_field(
6162 'pdf_intent_error_text',
6163 __('Error Text', 'mxchat'),
6164 array($this, 'mxchat_pdf_intent_error_text_callback'),
6165 'mxchat-embed',
6166 'mxchat_pdf_intent_section'
6167 );
6168
6169 // Add PDF Maximum Pages Field
6170 add_settings_field(
6171 'pdf_max_pages',
6172 __('Maximum Document Pages', 'mxchat'),
6173 array($this, 'mxchat_pdf_max_pages_callback'),
6174 'mxchat-embed',
6175 'mxchat_pdf_intent_section'
6176 );
6177
6178 // Live Agent Settings Fields
6179 add_settings_section(
6180 'mxchat_live_agent_section',
6181 __('Live Agent Settings', 'mxchat'),
6182 array($this, 'mxchat_live_agent_section_callback'),
6183 'mxchat-embed'
6184 );
6185
6186 // Live Agent Status Fields (add at top of live agent settings)
6187 add_settings_field(
6188 'live_agent_status',
6189 __('Live Agent Status', 'mxchat'),
6190 array($this, 'mxchat_live_agent_status_callback'),
6191 'mxchat-embed',
6192 'mxchat_live_agent_section'
6193 );
6194
6195 add_settings_field(
6196 'live_agent_notification_message',
6197 __('Notification Message', 'mxchat'),
6198 array($this, 'mxchat_live_agent_notification_message_callback'),
6199 'mxchat-embed',
6200 'mxchat_live_agent_section'
6201 );
6202
6203 add_settings_field(
6204 'live_agent_away_message',
6205 __('Away Message', 'mxchat'),
6206 array($this, 'mxchat_live_agent_away_message_callback'),
6207 'mxchat-embed',
6208 'mxchat_live_agent_section'
6209 );
6210
6211 add_settings_field(
6212 'live_agent_webhook_url',
6213 __('Slack Webhook URL', 'mxchat'),
6214 array($this, 'mxchat_live_agent_webhook_url_callback'),
6215 'mxchat-embed',
6216 'mxchat_live_agent_section'
6217 );
6218
6219 add_settings_field(
6220 'live_agent_secret_key',
6221 __('Slack Secret Key', 'mxchat'),
6222 array($this, 'mxchat_live_agent_secret_key_callback'),
6223 'mxchat-embed',
6224 'mxchat_live_agent_section'
6225 );
6226
6227 // Live Agent Integration Fields
6228 add_settings_field(
6229 'live_agent_bot_token',
6230 __('Slack Bot OAuth Token', 'mxchat'),
6231 array($this, 'mxchat_live_agent_bot_token_callback'),
6232 'mxchat-embed',
6233 'mxchat_live_agent_section'
6234 );
6235
6236
6237
6238
6239 // General Settings Section
6240 add_settings_section(
6241 'mxchat_general_section',
6242 esc_html__('YouTube Tutorials', 'mxchat'),
6243 null,
6244 'mxchat-general'
6245 );
6246 }
6247 public function mxchat_prompts_page_init() {
6248 register_setting(
6249 'mxchat_prompts_options',
6250 'mxchat_prompts_options',
6251 array(
6252 'type' => 'array',
6253 'description' => __('MXChat Knowledge Base Settings', 'mxchat'),
6254 'default' => array(
6255 'mxchat_auto_sync_posts' => 0,
6256 'mxchat_auto_sync_pages' => 0,
6257 'mxchat_use_pinecone' => 0,
6258 'mxchat_pinecone_api_key' => '',
6259 'mxchat_pinecone_environment' => '',
6260 'mxchat_pinecone_index' => '',
6261 'mxchat_pinecone_host' => '', // Add this line
6262 ),
6263 'sanitize_callback' => array($this, 'sanitize_prompts_options'),
6264 )
6265 );
6266
6267 add_action('admin_notices', array($this, 'sync_settings_notice'));
6268 }
6269
6270 public function mxchat_transcripts_page_init() {
6271 register_setting(
6272 'mxchat_transcripts_options',
6273 'mxchat_transcripts_options',
6274 array(
6275 'type' => 'array',
6276 'description' => __('MXChat Transcripts Notification Settings', 'mxchat'),
6277 'default' => array(
6278 'mxchat_enable_notifications' => 0,
6279 'mxchat_notification_email' => get_option('admin_email'),
6280 ),
6281 'sanitize_callback' => array($this, 'sanitize_transcripts_options'),
6282 )
6283 );
6284
6285 add_settings_section(
6286 'mxchat_transcripts_notification_section',
6287 esc_html__('Chat Notification Settings', 'mxchat'),
6288 array($this, 'mxchat_transcripts_notification_section_callback'),
6289 'mxchat-transcripts'
6290 );
6291
6292 add_settings_field(
6293 'mxchat_enable_notifications',
6294 esc_html__('Enable Chat Notifications', 'mxchat'),
6295 array($this, 'mxchat_enable_notifications_callback'),
6296 'mxchat-transcripts',
6297 'mxchat_transcripts_notification_section'
6298 );
6299
6300 add_settings_field(
6301 'mxchat_notification_email',
6302 esc_html__('Notification Email Address', 'mxchat'),
6303 array($this, 'mxchat_notification_email_callback'),
6304 'mxchat-transcripts',
6305 'mxchat_transcripts_notification_section'
6306 );
6307 }
6308
6309
6310 /**
6311 * Sanitize all prompts options
6312 *
6313 * @param array $input The unsanitized options array
6314 * @return array The sanitized options array
6315 */
6316 public function sanitize_prompts_options($input) {
6317 // Log the incoming input.
6318 //error_log('Sanitizing inputs: ' . print_r($input, true));
6319
6320 $sanitized = array();
6321
6322 // Boolean options
6323 $sanitized['mxchat_auto_sync_posts'] = isset($input['mxchat_auto_sync_posts']) ? 1 : 0;
6324 $sanitized['mxchat_auto_sync_pages'] = isset($input['mxchat_auto_sync_pages']) ? 1 : 0;
6325 $sanitized['mxchat_use_pinecone'] = !empty($input['mxchat_use_pinecone']) ? 1 : 0;
6326
6327 // API Key: if less than 32 characters, flag as invalid.
6328 $api_key = sanitize_text_field($input['mxchat_pinecone_api_key'] ?? '');
6329 if (!empty($api_key) && strlen($api_key) < 32) {
6330 add_settings_error(
6331 'mxchat_prompts_options',
6332 'invalid_api_key',
6333 __('The Pinecone API key appears to be invalid. Please check your API key.', 'mxchat')
6334 );
6335 $existing_options = get_option('mxchat_prompts_options', array());
6336 $sanitized['mxchat_pinecone_api_key'] = $existing_options['mxchat_pinecone_api_key'] ?? '';
6337 } else {
6338 $sanitized['mxchat_pinecone_api_key'] = $api_key;
6339 }
6340
6341 // Environment and Index Name
6342 $sanitized['mxchat_pinecone_environment'] = sanitize_text_field($input['mxchat_pinecone_environment'] ?? '');
6343 $sanitized['mxchat_pinecone_index'] = sanitize_text_field($input['mxchat_pinecone_index'] ?? '');
6344
6345 // Host: Remove protocol and validate format.
6346 $host = sanitize_text_field($input['mxchat_pinecone_host'] ?? '');
6347 $host = preg_replace('#^https?://#', '', $host);
6348 //error_log('Host after removing protocol: ' . $host);
6349 if (!empty($host)) {
6350 if (!preg_match('/^[\w-]+\.svc\.[\w-]+\.pinecone\.io$/', $host)) {
6351 add_settings_error(
6352 'mxchat_prompts_options',
6353 'invalid_host',
6354 __('The Pinecone host appears to be invalid. It should look like "mxchat-vectors-zrmsquq.svc.aped-4627-b74a.pinecone.io"', 'mxchat')
6355 );
6356 $existing_options = get_option('mxchat_prompts_options', array());
6357 $sanitized['mxchat_pinecone_host'] = $existing_options['mxchat_pinecone_host'] ?? '';
6358 } else {
6359 $sanitized['mxchat_pinecone_host'] = $host;
6360 }
6361 } else {
6362 $sanitized['mxchat_pinecone_host'] = '';
6363 }
6364
6365 //error_log('Final sanitized array: ' . print_r($sanitized, true));
6366
6367 return $sanitized;
6368 }
6369
6370
6371 public function sync_settings_notice() {
6372 // Only show notice on our plugin page
6373 if (!isset($_GET['page']) || $_GET['page'] !== 'mxchat-prompts') {
6374 return;
6375 }
6376
6377 // Check if settings were updated
6378 if (isset($_GET['settings-updated'])) {
6379
6380 ?>
6381 <div class="notice notice-success is-dismissible">
6382 <p><?php esc_html_e('Sync settings updated successfully.', 'mxchat'); ?></p>
6383 </div>
6384 <?php
6385
6386 }
6387 }
6388 // Add this sanitization function to your class
6389 public function sanitize_sync_setting($input) {
6390 return (bool)$input ? __('1', 'mxchat') : __('', 'mxchat');
6391 }
6392
6393
6394
6395 public function mxchat_handle_activate_license() {
6396 // Check nonce
6397 if (!check_ajax_referer('mxchat_activate_license_nonce', 'security', false)) {
6398 wp_send_json_error(esc_html__('Invalid security token', 'mxchat'));
6399 return;
6400 }
6401
6402 // Verify user capabilities
6403 if (!current_user_can('manage_options')) {
6404 wp_send_json_error(esc_html__('Unauthorized access', 'mxchat'));
6405 return;
6406 }
6407
6408 $license_key = isset($_POST['mxchat_activation_key']) ? sanitize_text_field($_POST['mxchat_activation_key']) : '';
6409 $customer_email = isset($_POST['mxchat_pro_email']) ? sanitize_email($_POST['mxchat_pro_email']) : '';
6410
6411 if (empty($license_key) || empty($customer_email)) {
6412 wp_send_json_error(esc_html__('Email or License Key is missing', 'mxchat'));
6413 return;
6414 }
6415
6416 $product_id = 'MxChatPRO';
6417 $response = wp_remote_get(
6418 add_query_arg(
6419 array(
6420 'wc-api' => 'software-api',
6421 'request' => 'activation',
6422 'email' => $customer_email,
6423 'license_key' => $license_key,
6424 'product_id' => $product_id
6425 ),
6426 'http://mxchat.ai/'
6427 )
6428 );
6429
6430 if (is_wp_error($response)) {
6431 wp_send_json_error(esc_html__('Activation failed due to a server error: ', 'mxchat') . $response->get_error_message());
6432 return;
6433 }
6434
6435 $body = wp_remote_retrieve_body($response);
6436 $data = json_decode($body);
6437
6438 if ($data && isset($data->activated) && $data->activated) {
6439 update_option('mxchat_license_status', 'active');
6440 update_option('mxchat_pro_email', $customer_email);
6441 update_option('mxchat_activation_key', $license_key);
6442 wp_send_json_success(array('message' => esc_html__('License activated successfully', 'mxchat')));
6443 } else {
6444 $error_message = isset($data->error) ? $data->error : esc_html__('Activation failed', 'mxchat');
6445 update_option('mxchat_license_status', 'inactive');
6446 update_option('mxchat_license_error', $error_message);
6447 wp_send_json_error($error_message);
6448 }
6449 }
6450 /**
6451 * AJAX handler to check license status
6452 */
6453 public function mxchat_check_license_status() {
6454 // Verify nonce
6455 check_ajax_referer($this->get_nonce_action(), 'security');
6456
6457 $email = sanitize_email($_POST['email']);
6458 $key = sanitize_text_field($_POST['key']);
6459
6460 // Check if this license is actually active in your system
6461 $is_active = (get_option('mxchat_license_status') === 'active' &&
6462 get_option('mxchat_pro_email') === $email &&
6463 get_option('mxchat_activation_key') === $key);
6464
6465 wp_send_json(array(
6466 'is_active' => $is_active
6467 ));
6468 }
6469 /**
6470 * Helper method to get the nonce action
6471 */
6472 private function get_nonce_action() {
6473 return 'mxchat_license_nonce';
6474 }
6475
6476
6477
6478
6479 public function mxchat_rate_limits_callback() {
6480 $all_options = get_option('mxchat_options', []);
6481
6482 // Define available rate limits
6483 $rate_limits = array('1', '3', '5', '10', '15', '20', '50', '100', 'unlimited');
6484
6485 // Define available timeframes
6486 $timeframes = array(
6487 'hourly' => __('Per Hour', 'mxchat'),
6488 'daily' => __('Per Day', 'mxchat'),
6489 'weekly' => __('Per Week', 'mxchat'),
6490 'monthly' => __('Per Month', 'mxchat')
6491 );
6492
6493 // Get all roles plus a "logged_out" pseudo-role
6494 $roles = wp_roles()->get_names();
6495 $roles['logged_out'] = __('Logged Out Users', 'mxchat');
6496
6497 // Start the wrapper
6498 echo '<div class="pro-feature-wrapper active">';
6499 echo '<div class="mxchat-rate-limits-container">';
6500
6501 echo '<p class="description" style="margin-bottom: 20px;">' .
6502 esc_html__('Set message limits for each user role and customize the experience when users reach those limits. You can use {limit}, {timeframe}, {count}, and {remaining} as placeholders.', 'mxchat') .
6503 '</p>';
6504
6505 // Output the controls for each role
6506 foreach ($roles as $role_id => $role_name) {
6507 // Get saved options or defaults
6508 $default_limit = ($role_id === 'logged_out') ? '10' : '100';
6509 $default_timeframe = 'daily';
6510 $default_message = __('Rate limit exceeded. Please try again later.', 'mxchat');
6511
6512 $selected_limit = isset($all_options['rate_limits'][$role_id]['limit'])
6513 ? $all_options['rate_limits'][$role_id]['limit']
6514 : $default_limit;
6515
6516 $selected_timeframe = isset($all_options['rate_limits'][$role_id]['timeframe'])
6517 ? $all_options['rate_limits'][$role_id]['timeframe']
6518 : $default_timeframe;
6519
6520 $custom_message = isset($all_options['rate_limits'][$role_id]['message'])
6521 ? $all_options['rate_limits'][$role_id]['message']
6522 : $default_message;
6523
6524 // Output the row
6525 echo '<div class="mxchat-rate-limit-row mxchat-autosave-section">';
6526
6527 // Role label
6528 echo '<div class="mxchat-rate-limit-role">' . esc_html($role_name) . '</div>';
6529
6530 // Controls section
6531 echo '<div class="mxchat-rate-limit-controls-wrapper">';
6532
6533 // Rate limit and timeframe controls
6534 echo '<div class="mxchat-rate-limit-controls">';
6535
6536 // Limit dropdown
6537 echo '<div>';
6538 echo '<label for="rate_limits_' . esc_attr($role_id) . '_limit">' . esc_html__('Limit:', 'mxchat') . '</label>';
6539 echo '<select
6540 id="rate_limits_' . esc_attr($role_id) . '_limit"
6541 name="mxchat_options[rate_limits][' . esc_attr($role_id) . '][limit]"
6542 class="mxchat-autosave-field">';
6543 foreach ($rate_limits as $limit) {
6544 echo '<option value="' . esc_attr($limit) . '" ' . selected($selected_limit, $limit, false) . '>' . esc_html($limit) . '</option>';
6545 }
6546 echo '</select>';
6547 echo '</div>';
6548
6549 // Timeframe dropdown
6550 echo '<div>';
6551 echo '<label for="rate_limits_' . esc_attr($role_id) . '_timeframe">' . esc_html__('Timeframe:', 'mxchat') . '</label>';
6552 echo '<select
6553 id="rate_limits_' . esc_attr($role_id) . '_timeframe"
6554 name="mxchat_options[rate_limits][' . esc_attr($role_id) . '][timeframe]"
6555 class="mxchat-autosave-field">';
6556 foreach ($timeframes as $value => $label) {
6557 echo '<option value="' . esc_attr($value) . '" ' . selected($selected_timeframe, $value, false) . '>' . esc_html($label) . '</option>';
6558 }
6559 echo '</select>';
6560 echo '</div>';
6561
6562 echo '</div>'; // End controls
6563
6564 // Custom message textarea
6565 echo '<div class="mxchat-rate-limit-message">';
6566 echo '<label for="rate_limits_' . esc_attr($role_id) . '_message">' . esc_html__('Custom Message:', 'mxchat') . '</label>';
6567 echo '<textarea
6568 id="rate_limits_' . esc_attr($role_id) . '_message"
6569 name="mxchat_options[rate_limits][' . esc_attr($role_id) . '][message]"
6570 class="mxchat-autosave-field"
6571 placeholder="' . esc_attr__('Enter custom message when rate limit is exceeded', 'mxchat') . '">' .
6572 esc_textarea($custom_message) .
6573 '</textarea>';
6574 echo '</div>'; // End message
6575
6576 echo '</div>'; // End controls wrapper
6577
6578 echo '</div>'; // End row
6579 }
6580
6581 echo '</div>'; // End container
6582
6583 echo '</div>'; // End pro-feature-wrapper
6584 }
6585 private function mxchat_add_option_field($id, $title, $callback = '') {
6586 add_settings_field(
6587 $id,
6588 __($title, 'mxchat'),
6589 $callback ? array($this, $callback) : array($this, $id . '_callback'),
6590 'mxchat-max',
6591 'mxchat_setting_section_id',
6592 $id === 'model' ? ['label_for' => 'model'] : []
6593 );
6594 }
6595
6596 // OpenAI API Key
6597 public function api_key_callback() {
6598 $apiKey = isset($this->options['api_key']) ? esc_attr($this->options['api_key']) : '';
6599
6600 echo '<div class="api-key-wrapper" data-provider="openai">';
6601 echo '<input type="password" id="api_key" name="api_key" value="' . $apiKey . '" class="regular-text" autocomplete="off" />';
6602 echo '<button type="button" id="toggleApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
6603 echo '<p class="description api-key-notice">' . esc_html__('Required for your selected chat model. Important: You must add credits before use.', 'mxchat') . '</p>';
6604 echo '</div>';
6605 }
6606
6607 // X.AI API Key
6608 public function xai_api_key_callback() {
6609 $xaiApiKey = isset($this->options['xai_api_key']) ? esc_attr($this->options['xai_api_key']) : '';
6610
6611 echo '<div class="api-key-wrapper" data-provider="xai">';
6612 echo '<input type="password" id="xai_api_key" name="xai_api_key" value="' . $xaiApiKey . '" class="regular-text" autocomplete="off" />';
6613 echo '<button type="button" id="toggleXaiApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
6614 echo '<p class="description api-key-notice">' . esc_html__('Required for your selected chat model. Important: You must add credits before use.', 'mxchat') . '</p>';
6615 echo '</div>';
6616 }
6617 // Claude API Key
6618 public function claude_api_key_callback() {
6619 $claudeApiKey = isset($this->options['claude_api_key']) ? esc_attr($this->options['claude_api_key']) : '';
6620
6621 echo '<div class="api-key-wrapper" data-provider="claude">';
6622 echo '<input type="password" id="claude_api_key" name="claude_api_key" value="' . $claudeApiKey . '" class="regular-text" autocomplete="off" />';
6623 echo '<button type="button" id="toggleClaudeApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
6624 echo '<p class="description api-key-notice">' . esc_html__('Required for your selected chat model. Important: You must add credits before use.', 'mxchat') . '</p>';
6625 echo '</div>';
6626 }
6627
6628 // DeepSeek API Key
6629 public function deepseek_api_key_callback() {
6630 $apiKey = isset($this->options['deepseek_api_key']) ? esc_attr($this->options['deepseek_api_key']) : '';
6631
6632 echo '<div class="api-key-wrapper" data-provider="deepseek">';
6633 echo '<input type="password" id="deepseek_api_key" name="deepseek_api_key" value="' . $apiKey . '" class="regular-text" autocomplete="off" />';
6634 echo '<button type="button" id="toggleDeepSeekApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
6635 echo '<p class="description api-key-notice">' . esc_html__('Required for your selected chat model. Important: You must add credits before use.', 'mxchat') . '</p>';
6636 echo '</div>';
6637 }
6638
6639 // Gemini API Key
6640 public function gemini_api_key_callback() {
6641 $geminiApiKey = isset($this->options['gemini_api_key']) ? esc_attr($this->options['gemini_api_key']) : '';
6642
6643 echo '<div class="api-key-wrapper" data-provider="gemini">';
6644 echo '<input type="password" id="gemini_api_key" name="gemini_api_key" value="' . $geminiApiKey . '" class="regular-text" autocomplete="off" />';
6645 echo '<button type="button" id="toggleGeminiApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
6646 echo '<p class="description api-key-notice">' . esc_html__('Required for Google Gemini models. Get your API key from Google AI Studio.', 'mxchat') . '</p>';
6647 echo '</div>';
6648 }
6649
6650 // Voyage API Key
6651 public function voyage_api_key_callback() {
6652 $apiKey = isset($this->options['voyage_api_key']) ? esc_attr($this->options['voyage_api_key']) : '';
6653
6654 echo '<div class="api-key-wrapper" data-provider="voyage">';
6655 echo '<input type="password" id="voyage_api_key" name="voyage_api_key" value="' . $apiKey . '" class="regular-text" autocomplete="off" />';
6656 echo '<button type="button" id="toggleVoyageAPIKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
6657 echo '<p class="description api-key-notice">' . esc_html__('Required for your selected embedding model. Important: You must add credits before use.', 'mxchat') . '</p>';
6658 echo '</div>';
6659 }
6660
6661 public function mxchat_loops_api_key_callback() {
6662 $loops_api_key = isset($this->options['loops_api_key']) ? esc_attr($this->options['loops_api_key']) : '';
6663
6664 // Hidden fields to "trap" autofill
6665 echo '<input type="text" style="display:none" autocomplete="username" />';
6666 echo '<input type="password" style="display:none" autocomplete="current-password" />';
6667
6668 echo '<div class="api-key-wrapper" data-provider="loops">';
6669 echo sprintf(
6670 '<input type="password" id="loops_api_key" name="loops_api_key" value="%s" class="regular-text" autocomplete="new-password" />',
6671 $loops_api_key
6672 );
6673 echo '<button type="button" id="toggleLoopsApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
6674 echo '</div>';
6675 echo '<p class="description">' . esc_html__('Enter your Loops API Key here. Once entered, refreshed page to load list (See FAQ for details)', 'mxchat') . '</p>';
6676 }
6677 public function mxchat_loops_mailing_list_callback() {
6678 // Add error handling and type checking
6679 $loops_api_key = '';
6680 $selected_list = '';
6681
6682 // Safely get the API key
6683 if (isset($this->options['loops_api_key']) && is_string($this->options['loops_api_key'])) {
6684 $loops_api_key = $this->options['loops_api_key'];
6685 }
6686
6687 // Safely get the selected list
6688 if (isset($this->options['loops_mailing_list']) && is_string($this->options['loops_mailing_list'])) {
6689 $selected_list = $this->options['loops_mailing_list'];
6690 }
6691
6692 if (!empty($loops_api_key)) {
6693 $lists = $this->mxchat_fetch_loops_mailing_lists($loops_api_key);
6694 if (is_array($lists) && !empty($lists)) {
6695 echo '<select id="loops_mailing_list" name="loops_mailing_list">';
6696
6697 // Add a default "Select a list" option
6698 echo '<option value="" ' . selected($selected_list, '', false) . '>' . esc_html__('Select a list', 'mxchat') . '</option>';
6699
6700 foreach ($lists as $list) {
6701 if (is_array($list) && isset($list['id']) && isset($list['name'])) {
6702 echo sprintf(
6703 '<option value="%s" %s>%s</option>',
6704 esc_attr($list['id']),
6705 selected($selected_list, $list['id'], false),
6706 esc_html($list['name'])
6707 );
6708 }
6709 }
6710 echo '</select>';
6711 echo '<p class="description">' . esc_html__('Please select a mailing list to use with Loops.', 'mxchat') . '</p>';
6712 } else {
6713 echo '<p class="description">' . esc_html__('No lists found. Please verify your API Key.', 'mxchat') . '</p>';
6714 }
6715 } else {
6716 echo '<p class="description">' . esc_html__('Enter a valid Loops API Key to load mailing lists.', 'mxchat') . '</p>';
6717 }
6718 }
6719 public function mxchat_triggered_phrase_response_callback() {
6720 $default_response = __('Would you like to join our mailing list? Please provide your email below.', 'mxchat');
6721 $triggered_response = isset($this->options['triggered_phrase_response'])
6722 ? $this->options['triggered_phrase_response']
6723 : $default_response;
6724
6725 echo sprintf(
6726 '<textarea id="triggered_phrase_response" name="triggered_phrase_response" rows="3" cols="50">%s</textarea>',
6727 esc_textarea($triggered_response)
6728 );
6729 echo '<p class="description">' . esc_html__('Enter the chatbot response when a trigger keyword is detected, prompting the user to share their email.', 'mxchat') . '</p>';
6730 }
6731
6732 public function mxchat_email_capture_response_callback() {
6733 $default_response = __('Thank you for providing your email! You\'ve been added to our list.', 'mxchat');
6734 $email_capture_response = isset($this->options['email_capture_response'])
6735 ? $this->options['email_capture_response']
6736 : $default_response;
6737
6738 echo sprintf(
6739 '<textarea id="email_capture_response" name="email_capture_response" rows="3" cols="50">%s</textarea>',
6740 esc_textarea($email_capture_response)
6741 );
6742 echo '<p class="description">' . esc_html__('Enter the message to send when a user provides their email.', 'mxchat') . '</p>';
6743 }
6744
6745 public function mxchat_pre_chat_message_callback() {
6746 // Load the entire 'mxchat_options' array
6747 $all_options = get_option('mxchat_options', []);
6748
6749 // Retrieve the saved message or use the default value
6750 $default_message = __('Hey there! Ask me anything!', 'mxchat');
6751 $pre_chat_message = isset($all_options['pre_chat_message']) ? $all_options['pre_chat_message'] : $default_message;
6752
6753 // Output the textarea
6754 printf(
6755 '<textarea id="pre_chat_message" name="pre_chat_message" rows="5" cols="50">%s</textarea>',
6756 esc_textarea($pre_chat_message)
6757 );
6758 echo '<p class="description">' . esc_html__('Set the message displayed to users before they start a chat. Use this to provide a friendly greeting or instructions.', 'mxchat') . '</p>';
6759 }
6760
6761
6762
6763 // Callback for AI Instructions textarea
6764 public function system_prompt_instructions_callback() {
6765 // Retrieve the current value of the system prompt instructions
6766 $instructions = isset($this->options['system_prompt_instructions']) ? esc_textarea($this->options['system_prompt_instructions']) : '';
6767
6768 // Render the textarea field
6769 printf(
6770 '<textarea id="system_prompt_instructions" name="system_prompt_instructions" rows="5" cols="50">%s</textarea>',
6771 $instructions
6772 );
6773
6774 // Provide a helpful description
6775 echo '<p class="description">' . esc_html__('Provide system-level instructions for the AI to guide its behavior. Be clear and concise for better results.', 'mxchat') . '</p>';
6776 }
6777
6778
6779 public function mxchat_model_callback() {
6780 // Define available models grouped by provider
6781 $models = array(
6782 esc_html__('Google Gemini Models', 'mxchat') => array(
6783 'gemini-2.0-flash' => esc_html__('Gemini 2.0 Flash (Next-Gen Features)', 'mxchat'),
6784 'gemini-2.0-flash-lite' => esc_html__('Gemini 2.0 Flash-Lite (Cost-Efficient)', 'mxchat'),
6785 'gemini-1.5-pro' => esc_html__('Gemini 1.5 Pro (Complex Reasoning)', 'mxchat'),
6786 'gemini-1.5-flash' => esc_html__('Gemini 1.5 Flash (Fast & Versatile)', 'mxchat'),
6787 ),
6788 esc_html__('X.AI Models', 'mxchat') => array(
6789 'grok-3-beta' => esc_html__('Grok-3 (Powerful)', 'mxchat'),
6790 'grok-3-fast-beta' => esc_html__('Grok-3 Fast (High Performance)', 'mxchat'),
6791 'grok-3-mini-beta' => esc_html__('Grok-3 Mini (Affordable)', 'mxchat'),
6792 'grok-3-mini-fast-beta' => esc_html__('Grok-3 Mini Fast (Quick Response)', 'mxchat'),
6793 'grok-2' => esc_html__('Grok 2', 'mxchat')
6794 ),
6795 esc_html__('DeepSeek Models', 'mxchat') => array(
6796 'deepseek-chat' => esc_html__('DeepSeek-V3', 'mxchat'),
6797 ),
6798 esc_html__('Claude Models', 'mxchat') => array(
6799 'claude-3-7-sonnet-20250219' => esc_html__('Claude 3.7 Sonnet (Most Intelligent)', 'mxchat'),
6800 'claude-3-5-sonnet-20241022' => esc_html__('Claude 3.5 Sonnet (Intelligent)', 'mxchat'),
6801 'claude-3-opus-20240229' => esc_html__('Claude 3 Opus (Highly Complex Tasks)', 'mxchat'),
6802 'claude-3-sonnet-20240229' => esc_html__('Claude 3 Sonnet (Balanced)', 'mxchat'),
6803 'claude-3-haiku-20240307' => esc_html__('Claude 3 Haiku (Fastest)', 'mxchat')
6804 ),
6805 esc_html__('OpenAI Models', 'mxchat') => array(
6806 'gpt-4.1-2025-04-14' => esc_html__('GPT-4.1 (Flagship for Complex Tasks)', 'mxchat'),
6807 'gpt-4o' => esc_html__('GPT-4o (Recommended)', 'mxchat'),
6808 'gpt-4o-mini' => esc_html__('GPT-4o Mini (Fast and Lightweight)', 'mxchat'),
6809 'gpt-4-turbo' => esc_html__('GPT-4 Turbo (High-Performance)', 'mxchat'),
6810 'gpt-4' => esc_html__('GPT-4 (High Intelligence)', 'mxchat'),
6811 'gpt-3.5-turbo' => esc_html__('GPT-3.5 Turbo (Affordable and Fast)', 'mxchat')
6812 )
6813 );
6814
6815 // Retrieve the currently selected model from saved options
6816 $selected_model = isset($this->options['model']) ? esc_attr($this->options['model']) : 'gpt-4o';
6817
6818 // Begin the select dropdown
6819 echo '<select id="model" name="model">';
6820
6821 // Iterate over groups of models
6822 foreach ($models as $group_label => $group_models) {
6823 echo '<optgroup label="' . esc_attr($group_label) . '">';
6824
6825 foreach ($group_models as $model_value => $model_label) {
6826 // All models enabled - no disabled attribute or Pro Only label
6827 echo '<option value="' . esc_attr($model_value) . '" ' . selected($selected_model, $model_value, false) . '>' . esc_html($model_label) . '</option>';
6828 }
6829
6830 echo '</optgroup>';
6831 }
6832
6833 // Close the select dropdown
6834 echo '</select>';
6835
6836 // Updated description to remove mention of Pro-only models
6837 echo '<p class="description">' . esc_html__('Select the AI model your chatbot will use for chatting.', 'mxchat') . '</p>';
6838 }
6839
6840
6841 // Callback function for embedding model selection
6842 public function embedding_model_callback() {
6843 $models = array(
6844 esc_html__('OpenAI Embeddings', 'mxchat') => array(
6845 'text-embedding-3-small' => esc_html__('TE3 Small (1536, Efficient)', 'mxchat'),
6846 'text-embedding-ada-002' => esc_html__('Ada 2 (1536, Recommended)', 'mxchat'),
6847 'text-embedding-3-large' => esc_html__('TE3 Large (3072, Powerful)', 'mxchat'),
6848 ),
6849 esc_html__('Voyage AI Embeddings', 'mxchat') => array(
6850 'voyage-3-large' => esc_html__('Voyage-3 Large (2048, Most Capable)', 'mxchat'),
6851 )
6852 );
6853
6854 $selected_model = isset($this->options['embedding_model']) ? esc_attr($this->options['embedding_model']) : 'text-embedding-ada-002';
6855
6856 echo '<select id="embedding_model" name="embedding_model">';
6857 foreach ($models as $group_label => $group_models) {
6858 echo '<optgroup label="' . esc_attr($group_label) . '">';
6859 foreach ($group_models as $model_value => $model_label) {
6860 echo '<option value="' . esc_attr($model_value) . '" ' . selected($selected_model, $model_value, false) . '>' . esc_html($model_label) . '</option>';
6861 }
6862 echo '</optgroup>';
6863 }
6864 echo '</select>';
6865 echo '<p class="description"><span class="red-warning">IMPORTANT:</span> Select the model for vector embeddings. Changing models is not recommended; if you do, you must delete all existing knowledge & intent data and reconfigure them.</p>';
6866
6867 }
6868
6869
6870 public function mxchat_top_bar_title_callback() {
6871 // Retrieve the current value of the top bar title from saved options
6872 $top_bar_title = isset($this->options['top_bar_title']) ? esc_attr($this->options['top_bar_title']) : '';
6873
6874 // Render the input field
6875 echo '<input type="text" id="top_bar_title" name="top_bar_title" value="' . $top_bar_title . '" />';
6876
6877 // Add a description
6878 echo '<p class="description">' . esc_html__('Enter the title text that will appear on the top bar of the chatbot.', 'mxchat') . '</p>';
6879 }
6880 public function mxchat_ai_agent_text_callback() {
6881 // Retrieve the current value of the AI agent text from saved options
6882 $ai_agent_text = isset($this->options['ai_agent_text']) ? esc_attr($this->options['ai_agent_text']) : '';
6883 // Render the input field
6884 echo '<input type="text" id="ai_agent_text" name="ai_agent_text" value="' . $ai_agent_text . '" />';
6885 // Add a description
6886 echo '<p class="description">' . esc_html__('Enter the text that will appear for AI agents in the status indicator. Default: "AI Agent"', 'mxchat') . '</p>';
6887 }
6888 public function enable_email_block_callback() {
6889 // Load full plugin options array
6890 $all_options = get_option('mxchat_options', []);
6891
6892 // Get the value, default to 'off'
6893 $enable_email_block = isset($all_options['enable_email_block']) ? $all_options['enable_email_block'] : 'off';
6894
6895 // Check if it's 'on'
6896 $checked = ($enable_email_block === 'on') ? 'checked' : '';
6897
6898 echo '<label class="toggle-switch">';
6899 echo sprintf(
6900 '<input type="checkbox" id="enable_email_block" name="enable_email_block" value="on" %s />',
6901 esc_attr($checked)
6902 );
6903 echo '<span class="slider"></span>';
6904 echo '</label>';
6905 echo '<p class="description">' . esc_html__('Their email will appear at the top of the transcript. Email form will show for users who are not logged in or have not provided an email within 24h.', 'mxchat') . '</p>';
6906 }
6907
6908
6909 public function email_blocker_header_content_callback() {
6910 // Load the entire 'mxchat_options' array
6911 $all_options = get_option('mxchat_options', []);
6912
6913 // Retrieve the saved content or default to empty
6914 $content = isset($all_options['email_blocker_header_content'])
6915 ? $all_options['email_blocker_header_content']
6916 : '';
6917
6918 // Render the textarea - IMPORTANT: name should be just "email_blocker_header_content"
6919 echo '<textarea
6920 id="email_blocker_header_content"
6921 name="email_blocker_header_content"
6922 rows="5"
6923 cols="70"
6924 data-setting="email_blocker_header_content"
6925 >' . esc_textarea($content) . '</textarea>';
6926
6927 echo '<p class="description">';
6928 echo esc_html__('You may enter HTML here, such as &lt;h2&gt;Welcome&lt;/h2&gt; or &lt;p&gt;Let\'s get started&lt;/p&gt;.', 'mxchat');
6929 echo '</p>';
6930 }
6931
6932 public function email_blocker_button_text_callback() {
6933 // Load the entire 'mxchat_options' array
6934 $all_options = get_option('mxchat_options', []);
6935
6936 // Retrieve the saved button text or default to empty
6937 $button_text = isset($all_options['email_blocker_button_text'])
6938 ? $all_options['email_blocker_button_text']
6939 : '';
6940
6941 // Use esc_attr to safely render the existing text
6942 echo '<input type="text" id="email_blocker_button_text" name="email_blocker_button_text" value="' . esc_attr($button_text) . '" style="width: 300px;" />';
6943
6944 echo '<p class="description">';
6945 echo esc_html__('Enter the text you want on the submit button, e.g. "Start Chat".', 'mxchat');
6946 echo '</p>';
6947 }
6948
6949
6950
6951 public function mxchat_intro_message_callback() {
6952 // Load the entire 'mxchat_options' array
6953 $all_options = get_option('mxchat_options', []);
6954 // Retrieve the saved intro message or use the default
6955 $default_message = __('Hello! How can I assist you today?', 'mxchat');
6956 $saved_message = isset($all_options['intro_message']) ? $all_options['intro_message'] : $default_message;
6957 // Output the textarea with the saved value without escaping HTML
6958 ?>
6959 <textarea id="intro_message" name="intro_message" rows="5" cols="50"><?php echo $saved_message; ?></textarea>
6960 <p class="description">
6961 <?php esc_html_e('Enter your message. HTML tags and line breaks will be preserved.', 'mxchat'); ?>
6962 </p>
6963 <?php
6964 }
6965
6966 public function mxchat_input_copy_callback() {
6967 // Load the entire 'mxchat_options' array
6968 $all_options = get_option('mxchat_options', []);
6969
6970 // Retrieve the saved input copy or use the default value
6971 $default_copy = __('How can I assist?', 'mxchat');
6972 $input_copy = isset($all_options['input_copy']) ? $all_options['input_copy'] : $default_copy;
6973
6974 // Output the input field with the saved value
6975 printf(
6976 '<input type="text" id="input_copy" name="input_copy" value="%s" placeholder="%s" />',
6977 esc_attr($input_copy),
6978 esc_attr__('How can I assist?', 'mxchat')
6979 );
6980
6981 // Output the description
6982 echo '<p class="description">' . esc_html__('This is the placeholder text for the chat input field.', 'mxchat') . '</p>';
6983 }
6984
6985
6986
6987 public function mxchat_user_message_font_color_callback() {
6988 $disabled = $this->is_activated ? '' : 'disabled';
6989 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6990
6991 echo '<div class="' . esc_attr($class) . '">';
6992 echo sprintf(
6993 '<input type="text"
6994 id="user_message_font_color"
6995 name="user_message_font_color"
6996 value="%s"
6997 class="my-color-field"
6998 data-default-color="#ffffff"
6999 %s />',
7000 isset($this->options['user_message_font_color']) ? esc_attr($this->options['user_message_font_color']) : '#ffffff',
7001 esc_attr($disabled)
7002 );
7003
7004 if (!$this->is_activated) {
7005 echo '<div class="pro-feature-overlay">';
7006 echo '<a href="https://mxchat.ai/" target="_blank">';
7007 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
7008 echo '</a>';
7009 echo '</div>';
7010 }
7011 echo '</div>';
7012 }
7013
7014 public function mxchat_bot_message_bg_color_callback() {
7015 $disabled = $this->is_activated ? '' : 'disabled';
7016 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
7017
7018 echo '<div class="' . esc_attr($class) . '">';
7019 echo sprintf(
7020 '<input type="text"
7021 id="bot_message_bg_color"
7022 name="bot_message_bg_color"
7023 value="%s"
7024 class="my-color-field"
7025 data-default-color="#e1e1e1"
7026 %s />',
7027 isset($this->options['bot_message_bg_color']) ? esc_attr($this->options['bot_message_bg_color']) : '#e1e1e1',
7028 esc_attr($disabled)
7029 );
7030
7031 if (!$this->is_activated) {
7032 echo '<div class="pro-feature-overlay">';
7033 echo '<a href="https://mxchat.ai/" target="_blank">';
7034 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
7035 echo '</a>';
7036 echo '</div>';
7037 }
7038 echo '</div>';
7039 }
7040
7041 public function mxchat_bot_message_font_color_callback() {
7042 $disabled = $this->is_activated ? '' : 'disabled';
7043 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
7044
7045 echo '<div class="' . esc_attr($class) . '">';
7046 echo sprintf(
7047 '<input type="text"
7048 id="bot_message_font_color"
7049 name="bot_message_font_color"
7050 value="%s"
7051 class="my-color-field"
7052 data-default-color="#333333"
7053 %s />',
7054 isset($this->options['bot_message_font_color']) ? esc_attr($this->options['bot_message_font_color']) : '#333333',
7055 esc_attr($disabled)
7056 );
7057
7058 if (!$this->is_activated) {
7059 echo '<div class="pro-feature-overlay">';
7060 echo '<a href="https://mxchat.ai/" target="_blank">';
7061 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
7062 echo '</a>';
7063 echo '</div>';
7064 }
7065 echo '</div>';
7066 }
7067
7068 public function mxchat_live_agent_message_bg_color_callback() {
7069 $disabled = $this->is_activated ? '' : 'disabled';
7070 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
7071
7072 echo '<div class="' . esc_attr($class) . '">';
7073 echo sprintf(
7074 '<input type="text"
7075 id="live_agent_message_bg_color"
7076 name="live_agent_message_bg_color"
7077 value="%s"
7078 class="my-color-field"
7079 data-default-color="#ffffff"
7080 %s />',
7081 isset($this->options['live_agent_message_bg_color']) ? esc_attr($this->options['live_agent_message_bg_color']) : '#ffffff',
7082 esc_attr($disabled)
7083 );
7084
7085 if (!$this->is_activated) {
7086 echo '<div class="pro-feature-overlay">';
7087 echo '<a href="https://mxchat.ai/" target="_blank">';
7088 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
7089 echo '</a>';
7090 echo '</div>';
7091 }
7092 echo '</div>';
7093 }
7094
7095 public function mxchat_live_agent_message_font_color_callback() {
7096 $disabled = $this->is_activated ? '' : 'disabled';
7097 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
7098
7099 echo '<div class="' . esc_attr($class) . '">';
7100 echo sprintf(
7101 '<input type="text"
7102 id="live_agent_message_font_color"
7103 name="live_agent_message_font_color"
7104 value="%s"
7105 class="my-color-field"
7106 data-default-color="#333333"
7107 %s />',
7108 isset($this->options['live_agent_message_font_color']) ? esc_attr($this->options['live_agent_message_font_color']) : '#333333',
7109 esc_attr($disabled)
7110 );
7111
7112 if (!$this->is_activated) {
7113 echo '<div class="pro-feature-overlay">';
7114 echo '<a href="https://mxchat.ai/" target="_blank">';
7115 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
7116 echo '</a>';
7117 echo '</div>';
7118 }
7119 echo '</div>';
7120 }
7121
7122 public function mxchat_mode_indicator_bg_color_callback() {
7123 $disabled = $this->is_activated ? '' : 'disabled';
7124 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
7125
7126 echo '<div class="' . esc_attr($class) . '">';
7127 echo sprintf(
7128 '<input type="text"
7129 id="mode_indicator_bg_color"
7130 name="mode_indicator_bg_color"
7131 value="%s"
7132 class="my-color-field"
7133 data-default-color="#767676"
7134 %s />',
7135 isset($this->options['mode_indicator_bg_color']) ? esc_attr($this->options['mode_indicator_bg_color']) : '#767676',
7136 esc_attr($disabled)
7137 );
7138
7139 if (!$this->is_activated) {
7140 echo '<div class="pro-feature-overlay">';
7141 echo '<a href="https://mxchat.ai/" target="_blank">';
7142 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
7143 echo '</a>';
7144 echo '</div>';
7145 }
7146 echo '</div>';
7147 }
7148
7149 public function mxchat_mode_indicator_font_color_callback() {
7150 $disabled = $this->is_activated ? '' : 'disabled';
7151 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
7152
7153 echo '<div class="' . esc_attr($class) . '">';
7154 echo sprintf(
7155 '<input type="text"
7156 id="mode_indicator_font_color"
7157 name="mode_indicator_font_color"
7158 value="%s"
7159 class="my-color-field"
7160 data-default-color="#ffffff"
7161 %s />',
7162 isset($this->options['mode_indicator_font_color']) ? esc_attr($this->options['mode_indicator_font_color']) : '#ffffff',
7163 esc_attr($disabled)
7164 );
7165
7166 if (!$this->is_activated) {
7167 echo '<div class="pro-feature-overlay">';
7168 echo '<a href="https://mxchat.ai/" target="_blank">';
7169 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
7170 echo '</a>';
7171 echo '</div>';
7172 }
7173 echo '</div>';
7174 }
7175
7176 public function mxchat_toolbar_icon_color_callback() {
7177 $disabled = $this->is_activated ? '' : 'disabled';
7178 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
7179
7180 echo '<div class="' . esc_attr($class) . '">';
7181 echo sprintf(
7182 '<input type="text"
7183 id="toolbar_icon_color"
7184 name="toolbar_icon_color"
7185 value="%s"
7186 class="my-color-field"
7187 data-default-color="#212121"
7188 %s />',
7189 isset($this->options['toolbar_icon_color']) ? esc_attr($this->options['toolbar_icon_color']) : '#212121',
7190 esc_attr($disabled)
7191 );
7192
7193 if (!$this->is_activated) {
7194 echo '<div class="pro-feature-overlay">';
7195 echo '<a href="https://mxchat.ai/" target="_blank">';
7196 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
7197 echo '</a>';
7198 echo '</div>';
7199 }
7200 echo '</div>';
7201 }
7202
7203 public function mxchat_top_bar_bg_color_callback() {
7204 $disabled = $this->is_activated ? '' : 'disabled';
7205 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
7206
7207 echo '<div class="' . esc_attr($class) . '">';
7208 echo sprintf(
7209 '<input type="text"
7210 id="top_bar_bg_color"
7211 name="top_bar_bg_color"
7212 value="%s"
7213 class="my-color-field"
7214 data-default-color="#00b294"
7215 %s />',
7216 isset($this->options['top_bar_bg_color']) ? esc_attr($this->options['top_bar_bg_color']) : '#00b294',
7217 esc_attr($disabled)
7218 );
7219
7220 if (!$this->is_activated) {
7221 echo '<div class="pro-feature-overlay">';
7222 echo '<a href="https://mxchat.ai/" target="_blank">';
7223 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
7224 echo '</a>';
7225 echo '</div>';
7226 }
7227 echo '</div>';
7228 }
7229
7230 public function mxchat_send_button_font_color_callback() {
7231 $disabled = $this->is_activated ? '' : 'disabled';
7232 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
7233
7234 echo '<div class="' . esc_attr($class) . '">';
7235 echo sprintf(
7236 '<input type="text"
7237 id="send_button_font_color"
7238 name="send_button_font_color"
7239 value="%s"
7240 class="my-color-field"
7241 data-default-color="#ffffff"
7242 %s />',
7243 isset($this->options['send_button_font_color']) ? esc_attr($this->options['send_button_font_color']) : '#ffffff',
7244 esc_attr($disabled)
7245 );
7246
7247 if (!$this->is_activated) {
7248 echo '<div class="pro-feature-overlay">';
7249 echo '<a href="https://mxchat.ai/" target="_blank">';
7250 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
7251 echo '</a>';
7252 echo '</div>';
7253 }
7254 echo '</div>';
7255 }
7256
7257 public function mxchat_chatbot_background_color_callback() {
7258 $disabled = $this->is_activated ? '' : 'disabled';
7259 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
7260
7261 echo '<div class="' . esc_attr($class) . '">';
7262 echo sprintf(
7263 '<input type="text"
7264 id="chatbot_background_color"
7265 name="chatbot_background_color"
7266 value="%s"
7267 class="my-color-field"
7268 data-default-color="#000000"
7269 %s />',
7270 isset($this->options['chatbot_background_color']) ? esc_attr($this->options['chatbot_background_color']) : '#000000',
7271 esc_attr($disabled)
7272 );
7273
7274 if (!$this->is_activated) {
7275 echo '<div class="pro-feature-overlay">';
7276 echo '<a href="https://mxchat.ai/" target="_blank">';
7277 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
7278 echo '</a>';
7279 echo '</div>';
7280 }
7281 echo '</div>';
7282 }
7283
7284 public function mxchat_icon_color_callback() {
7285 $disabled = $this->is_activated ? '' : 'disabled';
7286 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
7287
7288 echo '<div class="' . esc_attr($class) . '">';
7289 echo sprintf(
7290 '<input type="text"
7291 id="icon_color"
7292 name="icon_color"
7293 value="%s"
7294 class="my-color-field"
7295 data-default-color="#ffffff"
7296 %s />',
7297 isset($this->options['icon_color']) ? esc_attr($this->options['icon_color']) : '#ffffff',
7298 esc_attr($disabled)
7299 );
7300
7301 if (!$this->is_activated) {
7302 echo '<div class="pro-feature-overlay">';
7303 echo '<a href="https://mxchat.ai/" target="_blank">';
7304 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
7305 echo '</a>';
7306 echo '</div>';
7307 }
7308 echo '</div>';
7309 }
7310
7311 public function mxchat_custom_icon_callback() {
7312 $disabled = $this->is_activated ? '' : 'disabled';
7313 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
7314 $custom_icon_url = isset($this->options['custom_icon']) ? esc_url($this->options['custom_icon']) : '';
7315
7316 echo '<div class="' . esc_attr($class) . '">';
7317 echo sprintf(
7318 '<input type="url"
7319 id="custom_icon"
7320 name="custom_icon"
7321 value="%s"
7322 placeholder="%s"
7323 class="regular-text"
7324 %s />',
7325 $custom_icon_url,
7326 esc_attr__('Enter PNG URL', 'mxchat'),
7327 esc_attr($disabled)
7328 );
7329
7330 // Preview container for the icon
7331 if (!empty($custom_icon_url)) {
7332 echo '<div class="icon-preview" style="margin-top: 10px;">';
7333 echo '<img src="' . esc_url($custom_icon_url) . '" alt="' . esc_attr__('Custom Icon Preview', 'mxchat') . '" style="max-width: 48px; height: auto;" />';
7334 echo '</div>';
7335 }
7336
7337 echo '<p class="description">' . esc_html__('Upload your PNG icon and paste the URL here. Recommended size: 48x48 pixels.', 'mxchat') . '</p>';
7338
7339 if (!$this->is_activated) {
7340 echo '<div class="pro-feature-overlay">';
7341 echo '<a href="https://mxchat.ai/" target="_blank">';
7342 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
7343 echo '</a>';
7344 echo '</div>';
7345 }
7346 echo '</div>';
7347 }
7348
7349 public function mxchat_title_icon_callback() {
7350 $disabled = $this->is_activated ? '' : 'disabled';
7351 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
7352 // Fixed the variable reference - it was using custom_icon instead of title_icon
7353 $title_icon_url = isset($this->options['title_icon']) ? esc_url($this->options['title_icon']) : '';
7354
7355 echo '<div class="' . esc_attr($class) . '">';
7356 echo sprintf(
7357 '<input type="url"
7358 id="title_icon"
7359 name="title_icon"
7360 value="%s"
7361 placeholder="%s"
7362 class="regular-text"
7363 %s />',
7364 $title_icon_url,
7365 esc_attr__('Enter PNG URL', 'mxchat'),
7366 esc_attr($disabled)
7367 );
7368
7369 // Preview container for the icon
7370 if (!empty($title_icon_url)) {
7371 echo '<div class="icon-preview" style="margin-top: 10px;">';
7372 echo '<img src="' . esc_url($title_icon_url) . '" alt="' . esc_attr__('Title Icon Preview', 'mxchat') . '" style="max-width: 48px; height: auto;" />';
7373 echo '</div>';
7374 }
7375
7376 echo '<p class="description">' . esc_html__('Upload your PNG icon and paste the URL here. Recommended size: 48x48 pixels.', 'mxchat') . '</p>';
7377
7378 if (!$this->is_activated) {
7379 echo '<div class="pro-feature-overlay">';
7380 echo '<a href="https://mxchat.ai/" target="_blank">';
7381 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
7382 echo '</a>';
7383 echo '</div>';
7384 }
7385 echo '</div>';
7386 }
7387
7388 public function mxchat_chat_input_font_color_callback() {
7389 $disabled = $this->is_activated ? '' : 'disabled';
7390 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
7391
7392 echo '<div class="' . esc_attr($class) . '">';
7393 echo sprintf(
7394 '<input type="text"
7395 id="chat_input_font_color"
7396 name="chat_input_font_color"
7397 value="%s"
7398 class="my-color-field"
7399 data-default-color="#555555"
7400 %s />',
7401 isset($this->options['chat_input_font_color']) ? esc_attr($this->options['chat_input_font_color']) : '#555555',
7402 esc_attr($disabled)
7403 );
7404
7405 if (!$this->is_activated) {
7406 echo '<div class="pro-feature-overlay">';
7407 echo '<a href="https://mxchat.ai/" target="_blank">';
7408 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
7409 echo '</a>';
7410 echo '</div>';
7411 }
7412 echo '</div>';
7413 }
7414
7415 public function mxchat_append_to_body_callback() {
7416 // Get value from options array, default to 'off'
7417 $append_to_body = isset($this->options['append_to_body']) ? $this->options['append_to_body'] : 'off';
7418 $checked = ($append_to_body === 'on') ? 'checked' : '';
7419
7420 echo '<label class="toggle-switch">';
7421 echo sprintf(
7422 '<input type="checkbox" id="append_to_body" name="append_to_body" value="on" %s />',
7423 esc_attr($checked)
7424 );
7425 echo '<span class="slider"></span>';
7426 echo '</label>';
7427 echo '<p class="description">' .
7428 esc_html__('Show chatbot automatically on all pages. When disabled, you can place the chatbot manually using shortcode [mxchat_chatbot floating="yes"].', 'mxchat') .
7429 '</p>';
7430
7431 }
7432
7433
7434
7435 public function mxchat_privacy_toggle_callback() {
7436 // Load from mxchat_options array
7437 $options = get_option('mxchat_options', []);
7438
7439 // Get privacy toggle value with fallback
7440 $privacy_toggle = isset($options['privacy_toggle']) ? $options['privacy_toggle'] : 'off';
7441 $checked = ($privacy_toggle === 'on') ? 'checked' : '';
7442
7443 // Get privacy text with fallback
7444 $privacy_text = isset($options['privacy_text'])
7445 ? $options['privacy_text']
7446 : __('By chatting, you agree to our <a href="https://example.com/privacy-policy" target="_blank">privacy policy</a>.', 'mxchat');
7447
7448 // Output the toggle switch
7449 echo '<label class="toggle-switch">';
7450 echo sprintf(
7451 '<input type="checkbox" id="privacy_toggle" name="privacy_toggle" value="on" %s />',
7452 esc_attr($checked)
7453 );
7454 echo '<span class="slider"></span>';
7455 echo '</label>';
7456 echo '<p class="description">' . esc_html__('Enable this option to display a privacy notice below the chat widget.', 'mxchat') . '</p>';
7457
7458 // Output the custom text input field
7459 echo sprintf(
7460 '<textarea id="privacy_text" name="privacy_text" rows="5" cols="50" class="regular-text">%s</textarea>',
7461 esc_textarea($privacy_text)
7462 );
7463 echo '<p class="description">' . esc_html__('Enter the privacy policy text. You can include HTML links.', 'mxchat') . '</p>';
7464 }
7465
7466
7467 public function mxchat_complianz_toggle_callback() {
7468 // Load from mxchat_options array
7469 $options = get_option('mxchat_options', []);
7470
7471 // Get complianz toggle value with fallback
7472 $complianz_toggle = isset($options['complianz_toggle']) ? $options['complianz_toggle'] : 'off';
7473 $checked = ($complianz_toggle === 'on') ? 'checked' : '';
7474
7475 // Output the toggle switch
7476 echo '<label class="toggle-switch">';
7477 echo sprintf(
7478 '<input type="checkbox" id="complianz_toggle" name="complianz_toggle" value="on" %s />',
7479 esc_attr($checked)
7480 );
7481 echo '<span class="slider"></span>';
7482 echo '</label>';
7483
7484 echo '<p class="description">' . esc_html__('Enable this option to apply Complianz consent logic to the chatbot (must have Complianz Plugin).', 'mxchat') . '</p>';
7485 }
7486
7487 public function mxchat_link_target_toggle_callback() {
7488 // Load from mxchat_options array
7489 $options = get_option('mxchat_options', []);
7490
7491 // Get link target toggle value with fallback
7492 $link_target_toggle = isset($options['link_target_toggle']) ? $options['link_target_toggle'] : 'off';
7493 $checked = ($link_target_toggle === 'on') ? 'checked' : '';
7494
7495 // Output the toggle switch
7496 echo '<label class="toggle-switch">';
7497 echo sprintf(
7498 '<input type="checkbox" id="link_target_toggle" name="link_target_toggle" value="on" %s />',
7499 esc_attr($checked)
7500 );
7501 echo '<span class="slider"></span>';
7502 echo '</label>';
7503 echo '<p class="description">' . esc_html__('Enable to open links in a new tab (default is to open in the same tab).', 'mxchat') . '</p>';
7504 }
7505
7506 public function mxchat_chat_persistence_toggle_callback() {
7507 // Load from mxchat_options array
7508 $options = get_option('mxchat_options', []);
7509
7510 // Get chat persistence toggle value with fallback
7511 $chat_persistence_toggle = isset($options['chat_persistence_toggle']) ? $options['chat_persistence_toggle'] : 'off';
7512 $checked = ($chat_persistence_toggle === 'on') ? 'checked' : '';
7513
7514 // Output the toggle switch
7515 echo '<label class="toggle-switch">';
7516 echo sprintf(
7517 '<input type="checkbox" id="chat_persistence_toggle" name="chat_persistence_toggle" value="on" %s />',
7518 esc_attr($checked)
7519 );
7520 echo '<span class="slider"></span>';
7521 echo '</label>';
7522
7523 echo '<p class="description">' . esc_html__('Enable to keep chat history when users navigate tabs or return to the site within 24 hours.', 'mxchat') . '</p>';
7524 }
7525
7526 public function mxchat_popular_question_1_callback() {
7527 // Load the full plugin options array
7528 $all_options = get_option('mxchat_options', []);
7529
7530 // Retrieve the specific option for popular_question_1
7531 $popular_question_1 = isset($all_options['popular_question_1']) ? $all_options['popular_question_1'] : '';
7532
7533 // Render the input field
7534 printf(
7535 '<input type="text" id="popular_question_1" name="popular_question_1" value="%s" placeholder="%s" class="regular-text" />',
7536 esc_attr($popular_question_1),
7537 esc_attr__('Enter Quick Question 1', 'mxchat')
7538 );
7539
7540 // Add a description for the field
7541 echo '<p class="description">' . esc_html__('This will be the first Quick Question in the chatbot, displayed above the input field.', 'mxchat') . '</p>';
7542 }
7543
7544
7545 public function mxchat_popular_question_2_callback() {
7546 // Load the full plugin options array
7547 $all_options = get_option('mxchat_options', []);
7548
7549 // Retrieve the specific option for popular_question_2
7550 $popular_question_2 = isset($all_options['popular_question_2']) ? $all_options['popular_question_2'] : '';
7551
7552 // Render the input field
7553 printf(
7554 '<input type="text" id="popular_question_2" name="popular_question_2" value="%s" placeholder="%s" class="regular-text" />',
7555 esc_attr($popular_question_2),
7556 esc_attr__('Enter Quick Question 2', 'mxchat')
7557 );
7558
7559 // Add a description for the field
7560 echo '<p class="description">' . esc_html__('This will be the second Quick Question in the chatbot.', 'mxchat') . '</p>';
7561 }
7562
7563
7564 public function mxchat_popular_question_3_callback() {
7565 // Load the full plugin options array
7566 $all_options = get_option('mxchat_options', []);
7567
7568 // Retrieve the specific option for popular_question_3
7569 $popular_question_3 = isset($all_options['popular_question_3']) ? $all_options['popular_question_3'] : '';
7570
7571 // Render the input field
7572 printf(
7573 '<input type="text" id="popular_question_3" name="popular_question_3" value="%s" placeholder="%s" class="regular-text" />',
7574 esc_attr($popular_question_3),
7575 esc_attr(__('Enter Quick Question 3', 'mxchat'))
7576 );
7577
7578 // Add a description for the field
7579 echo '<p class="description">' . esc_html__('This will be the third Quick Question in the chatbot.', 'mxchat') . '</p>';
7580 }
7581
7582 public function mxchat_additional_popular_questions_callback() {
7583 $options = get_option('mxchat_options', []);
7584 $additional_questions = isset($options['additional_popular_questions'])
7585 ? $options['additional_popular_questions']
7586 : get_option('additional_popular_questions', array());
7587
7588 echo '<div id="mxchat-additional-questions-container">';
7589 if (!empty($additional_questions)) {
7590 foreach ($additional_questions as $index => $question) {
7591 printf(
7592 '<div class="mxchat-question-row">
7593 <input type="text" name="additional_popular_questions[]"
7594 value="%s"
7595 placeholder="%s"
7596 class="regular-text mxchat-question-input"
7597 data-question-index="%d" />
7598 <button type="button" class="button mxchat-remove-question"
7599 aria-label="%s">%s</button>
7600 </div>',
7601 esc_attr($question),
7602 esc_attr(sprintf(__('Enter Additional Quick Question %d', 'mxchat'), $index + 4)),
7603 $index,
7604 esc_attr(__('Remove question', 'mxchat')),
7605 esc_html__('Remove', 'mxchat')
7606 );
7607 }
7608 } else {
7609 printf(
7610 '<div class="mxchat-question-row">
7611 <input type="text" name="additional_popular_questions[]"
7612 value=""
7613 placeholder="%s"
7614 class="regular-text mxchat-question-input"
7615 data-question-index="0" />
7616 <button type="button" class="button mxchat-remove-question"
7617 aria-label="%s">%s</button>
7618 </div>',
7619 esc_attr(__('Enter Additional Quick Question 4', 'mxchat')),
7620 esc_attr(__('Remove question', 'mxchat')),
7621 esc_html__('Remove', 'mxchat')
7622 );
7623 }
7624 echo '</div>';
7625 printf(
7626 '<button type="button" class="button mxchat-add-question" aria-label="%s">%s</button>',
7627 esc_attr(__('Add question', 'mxchat')),
7628 esc_html__('Add Question', 'mxchat')
7629 );
7630 echo '<p class="description">' . esc_html__('Add as many Quick Questions as you need.', 'mxchat') . '</p>';
7631 echo '</div>';
7632 }
7633
7634 public function mxchat_brave_api_key_callback() {
7635 $brave_api_key = isset($this->options['brave_api_key']) ? esc_attr($this->options['brave_api_key']) : '';
7636
7637 echo '<div class="api-key-wrapper">';
7638 echo sprintf(
7639 '<input type="password" id="brave_api_key" name="brave_api_key" value="%s" class="regular-text" />',
7640 $brave_api_key
7641 );
7642 echo '<button type="button" id="toggleBraveApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
7643 echo '</div>';
7644 echo '<p class="description">' . __('Enter your Brave Search API Key here. (See FAQ for details)', 'mxchat') . '</p>';
7645 }
7646
7647 public function mxchat_brave_image_count_callback() {
7648 $brave_image_count = isset($this->options['brave_image_count'])
7649 ? intval($this->options['brave_image_count'])
7650 : 4;
7651
7652 echo sprintf(
7653 '<input type="number" id="brave_image_count" name="brave_image_count"
7654 value="%d" min="1" max="6" class="small-text" />',
7655 $brave_image_count
7656 );
7657 echo '<p class="description">' . __('Select the number of images to return (1-6).', 'mxchat') . '</p>';
7658 }
7659
7660 public function mxchat_brave_safe_search_callback() {
7661 $brave_safe_search = isset($this->options['brave_safe_search'])
7662 ? esc_attr($this->options['brave_safe_search'])
7663 : 'strict';
7664
7665 echo '<select id="brave_safe_search" name="brave_safe_search">';
7666 echo sprintf(
7667 '<option value="strict" %s>%s</option>',
7668 selected($brave_safe_search, 'strict', false),
7669 __('Strict', 'mxchat')
7670 );
7671 echo sprintf(
7672 '<option value="off" %s>%s</option>',
7673 selected($brave_safe_search, 'off', false),
7674 __('Off', 'mxchat')
7675 );
7676 echo '</select>';
7677 echo '<p class="description">' .
7678 esc_html__('Set the Safe Search level for image searches. Brave Search only supports "Strict" and "Off" options.', 'mxchat') .
7679 '</p>';
7680 }
7681
7682 public function mxchat_brave_news_count_callback() {
7683 $brave_news_count = isset($this->options['brave_news_count'])
7684 ? intval($this->options['brave_news_count'])
7685 : 3;
7686
7687 echo sprintf(
7688 '<input type="number" id="brave_news_count" name="brave_news_count"
7689 value="%d" min="1" max="10" class="small-text" />',
7690 $brave_news_count
7691 );
7692 echo '<p class="description">' . esc_html__('Select the number of news articles to retrieve (1-10).', 'mxchat') . '</p>';
7693 }
7694
7695 public function mxchat_brave_country_callback() {
7696 $brave_country = isset($this->options['brave_country'])
7697 ? esc_attr($this->options['brave_country'])
7698 : 'us';
7699
7700 echo sprintf(
7701 '<input type="text" id="brave_country" name="brave_country"
7702 value="%s" maxlength="2" class="small-text" />',
7703 $brave_country
7704 );
7705 echo '<p class="description">' . esc_html__('Enter the country code (e.g., "us" for United States).', 'mxchat') . '</p>';
7706 }
7707
7708 public function mxchat_brave_language_callback() {
7709 $brave_language = isset($this->options['brave_language'])
7710 ? esc_attr($this->options['brave_language'])
7711 : 'en';
7712
7713 echo sprintf(
7714 '<input type="text" id="brave_language" name="brave_language"
7715 value="%s" maxlength="2" class="small-text" />',
7716 $brave_language
7717 );
7718 echo '<p class="description">' . esc_html__('Enter the language code (e.g., "en" for English).', 'mxchat') . '</p>';
7719 }
7720
7721
7722
7723
7724
7725 // Section Callback
7726 public function mxchat_pdf_intent_section_callback() {
7727 echo '<p>' . esc_html__('Configure the intent settings for the Chat with PDF feature.', 'mxchat') . '</p>';
7728 }
7729
7730 public function mxchat_chat_toolbar_toggle_callback() {
7731 // Get chat toolbar toggle value with fallback
7732 $chat_toolbar_toggle = isset($this->options['chat_toolbar_toggle']) ? $this->options['chat_toolbar_toggle'] : 'off';
7733 $checked = ($chat_toolbar_toggle === 'on') ? 'checked' : '';
7734
7735 // Output the toggle switch
7736 echo '<label class="toggle-switch">';
7737 echo sprintf(
7738 '<input type="checkbox" id="chat_toolbar_toggle" name="chat_toolbar_toggle" value="on" %s />',
7739 esc_attr($checked)
7740 );
7741 echo '<span class="slider"></span>';
7742 echo '</label>';
7743
7744 echo '<p class="description">' . esc_html__('Enable to display the chat toolbar, adding two icons below the chatbot input field for uploading PDF and Word documents (default is hidden).', 'mxchat') . '</p>';
7745 }
7746 /**
7747 * Callback for PDF upload button toggle setting
7748 */
7749 public function mxchat_show_pdf_upload_button_callback() {
7750 // Get toggle value with fallback
7751 $show_pdf_button = isset($this->options['show_pdf_upload_button']) ? $this->options['show_pdf_upload_button'] : 'on';
7752 $checked = ($show_pdf_button === 'on') ? 'checked' : '';
7753
7754 // Output the toggle switch
7755 echo '<label class="toggle-switch">';
7756 echo sprintf(
7757 '<input type="checkbox" id="show_pdf_upload_button" name="show_pdf_upload_button" value="on" %s />',
7758 esc_attr($checked)
7759 );
7760 echo '<span class="slider"></span>';
7761 echo '</label>';
7762
7763 echo '<p class="description">' . esc_html__('Enable to show the PDF upload button in the chatbot toolbar. Disable to hide it.', 'mxchat') . '</p>';
7764 }
7765 /**
7766 * Callback for Word upload button toggle setting
7767 */
7768 public function mxchat_show_word_upload_button_callback() {
7769 // Get toggle value with fallback
7770 $show_word_button = isset($this->options['show_word_upload_button']) ? $this->options['show_word_upload_button'] : 'on';
7771 $checked = ($show_word_button === 'on') ? 'checked' : '';
7772
7773 // Output the toggle switch
7774 echo '<label class="toggle-switch">';
7775 echo sprintf(
7776 '<input type="checkbox" id="show_word_upload_button" name="show_word_upload_button" value="on" %s />',
7777 esc_attr($checked)
7778 );
7779 echo '<span class="slider"></span>';
7780 echo '</label>';
7781
7782 echo '<p class="description">' . esc_html__('Enable to show the Word document upload button in the chatbot toolbar. Disable to hide it.', 'mxchat') . '</p>';
7783 }
7784
7785 public function mxchat_pdf_intent_trigger_text_callback() {
7786 $default_text = __("Please provide the URL to the PDF you'd like to discuss.", 'mxchat');
7787
7788 echo sprintf(
7789 '<textarea id="pdf_intent_trigger_text"
7790 name="pdf_intent_trigger_text"
7791 rows="3"
7792 cols="50"
7793 placeholder="%s">%s</textarea>',
7794 esc_attr__('Enter trigger text', 'mxchat'),
7795 isset($this->options['pdf_intent_trigger_text'])
7796 ? esc_textarea($this->options['pdf_intent_trigger_text'])
7797 : esc_textarea($default_text)
7798 );
7799 echo '<p class="description">' . esc_html__('Text displayed when the intent is triggered.', 'mxchat') . '</p>';
7800 }
7801
7802 public function mxchat_pdf_intent_success_text_callback() {
7803 $default_text = __("I've processed the PDF. What questions do you have about it?", 'mxchat');
7804
7805 echo sprintf(
7806 '<textarea id="pdf_intent_success_text"
7807 name="pdf_intent_success_text"
7808 rows="3"
7809 cols="50"
7810 placeholder="%s">%s</textarea>',
7811 esc_attr__('Enter success text', 'mxchat'),
7812 isset($this->options['pdf_intent_success_text'])
7813 ? esc_textarea($this->options['pdf_intent_success_text'])
7814 : esc_textarea($default_text)
7815 );
7816 echo '<p class="description">' . esc_html__('Text displayed when the intent is successful.', 'mxchat') . '</p>';
7817 }
7818
7819 public function mxchat_pdf_intent_error_text_callback() {
7820 $default_text = __("Sorry, I couldn't process the PDF. Please ensure it's a valid file.", 'mxchat');
7821
7822 echo sprintf(
7823 '<textarea id="pdf_intent_error_text"
7824 name="pdf_intent_error_text"
7825 rows="3"
7826 cols="50"
7827 placeholder="%s">%s</textarea>',
7828 esc_attr__('Enter error text', 'mxchat'),
7829 isset($this->options['pdf_intent_error_text'])
7830 ? esc_textarea($this->options['pdf_intent_error_text'])
7831 : esc_textarea($default_text)
7832 );
7833 echo '<p class="description">' . esc_html__('Text displayed when an error occurs during the intent.', 'mxchat') . '</p>';
7834 }
7835
7836 public function mxchat_pdf_max_pages_callback() {
7837 $max_pages = isset($this->options['pdf_max_pages']) ? intval($this->options['pdf_max_pages']) : 69;
7838
7839 echo sprintf(
7840 '<input type="range"
7841 id="pdf_max_pages"
7842 name="pdf_max_pages"
7843 min="1"
7844 max="69"
7845 value="%d"
7846 class="range-slider" />',
7847 esc_attr($max_pages)
7848 );
7849 echo '<span id="pdf_max_pages_output">' . esc_html($max_pages) . '</span>';
7850 echo '<p class="description">' . esc_html__('Set the maximum number of document pages users can upload for processing. (1-69 pages)', 'mxchat') . '</p>';
7851 }
7852
7853 public function mxchat_live_agent_status_callback() {
7854 $status = isset($this->options['live_agent_status']) ? $this->options['live_agent_status'] : 'off';
7855
7856 echo '<label class="toggle-switch">';
7857 echo sprintf(
7858 '<input type="checkbox" id="live_agent_status" name="live_agent_status" value="on" %s />',
7859 checked($status, 'on', false)
7860 );
7861 echo '<span class="slider"></span>';
7862 echo '</label>';
7863 echo '<label for="live_agent_status" class="mxchat-status-label">';
7864 echo '<span class="status-text">' . ($status === 'on' ? esc_html__('Online', 'mxchat') : esc_html__('Offline', 'mxchat')) . '</span>';
7865 echo '</label>';
7866 }
7867
7868 public function mxchat_live_agent_away_message_callback() {
7869 $message = isset($this->options['live_agent_away_message'])
7870 ? $this->options['live_agent_away_message']
7871 : __('Sorry, live agents are currently unavailable. I can continue helping you as an AI assistant.', 'mxchat');
7872
7873 printf(
7874 '<textarea id="live_agent_away_message" name="live_agent_away_message" rows="3" cols="50">%s</textarea>',
7875 esc_textarea($message)
7876 );
7877 echo '<p class="description">' . esc_html__('Message shown when live agents are offline.', 'mxchat') . '</p>';
7878 }
7879
7880 public function mxchat_live_agent_notification_message_callback() {
7881 $message = isset($this->options['live_agent_notification_message'])
7882 ? $this->options['live_agent_notification_message']
7883 : __('Live agent has been notified.', 'mxchat');
7884
7885 printf(
7886 '<textarea id="live_agent_notification_message" name="live_agent_notification_message" rows="3" cols="50">%s</textarea>',
7887 esc_textarea($message)
7888 );
7889 echo '<p class="description">' . esc_html__('Message shown when live transfer activated.', 'mxchat') . '</p>';
7890 }
7891
7892 public function mxchat_live_agent_webhook_url_callback() {
7893 $webhook_url = isset($this->options['live_agent_webhook_url'])
7894 ? esc_url($this->options['live_agent_webhook_url'])
7895 : esc_url(get_option('live_agent_webhook_url', ''));
7896
7897 printf(
7898 '<input type="password" id="live_agent_webhook_url" name="live_agent_webhook_url" value="%s" class="regular-text" />',
7899 $webhook_url
7900 );
7901 echo '<button type="button" id="toggleWebhookUrlVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
7902 echo '<p class="description">' . esc_html__('Enter your Slack webhook URL for live agent notifications.', 'mxchat') . '</p>';
7903 }
7904
7905 public function mxchat_live_agent_secret_key_callback() {
7906 printf(
7907 '<input type="password" id="live_agent_secret_key" name="live_agent_secret_key" value="%s" class="regular-text" />',
7908 isset($this->options['live_agent_secret_key']) ? esc_attr($this->options['live_agent_secret_key']) : ''
7909 );
7910 echo '<button type="button" id="toggleSecretKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
7911 echo '<p class="description">' . esc_html__('Secret key for validating Slack requests. Keep this secure.', 'mxchat') . '</p>';
7912 }
7913
7914 public function mxchat_live_agent_bot_token_callback() {
7915 printf(
7916 '<input type="password" id="live_agent_bot_token" name="live_agent_bot_token" value="%s" class="regular-text" />',
7917 isset($this->options['live_agent_bot_token']) ? esc_attr($this->options['live_agent_bot_token']) : ''
7918 );
7919 echo '<button type="button" id="toggleBotTokenVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
7920 echo '<p class="description">' . esc_html__('Your Slack Bot OAuth Token (starts with xoxb-). Keep this secure.', 'mxchat') . '</p>';
7921 }
7922
7923 public function mxchat_similarity_threshold_callback() {
7924 // Load from mxchat_options array
7925 $options = get_option('mxchat_options', []);
7926
7927 // Get value from options array with default of 80
7928 $threshold = isset($options['similarity_threshold']) ? $options['similarity_threshold'] : 35;
7929
7930 echo '<div class="slider-container">';
7931 echo sprintf(
7932 '<input type="range"
7933 id="similarity_threshold"
7934 name="similarity_threshold"
7935 min="20"
7936 max="85"
7937 step="1"
7938 value="%s"
7939 class="range-slider" />',
7940 esc_attr($threshold)
7941 );
7942 echo sprintf(
7943 '<span id="threshold_value" class="range-value">%s</span>',
7944 esc_html($threshold)
7945 );
7946 echo '</div>';
7947 echo '<p class="description">';
7948 echo sprintf(
7949 esc_html__('Adjust similarity threshold for optimal content matching. Too high may limit knowledge retrieval. We highly recommend downloading our %sfree Similarity Tester add-on%s to fine-tune your responses.', 'mxchat'),
7950 '<a href="' . admin_url('admin.php?page=mxchat-addons') . '">',
7951 '</a>'
7952 );
7953 echo '</p>';
7954 }
7955
7956 public function mxchat_enqueue_admin_assets() {
7957 wp_enqueue_style('wp-color-picker');
7958
7959 // Get the plugin version or file modification time for cache busting
7960 $plugin_version = '2.2.0'; // Replace this with your plugin's version
7961
7962 // File paths
7963 $color_picker_js_path = plugin_dir_path(__FILE__) . '../js/my-color-picker.js';
7964 $embedding_check_js_path = plugin_dir_path(__FILE__) . '../js/embedding-check.js';
7965 $admin_css_path = plugin_dir_path(__FILE__) . '../css/admin-style.css';
7966 $knowledge_css_path = plugin_dir_path(__FILE__) . '../css/knowledge-style.css';
7967 $intent_css_path = plugin_dir_path(__FILE__) . '../css/intent-style.css';
7968 $transcripts_css_path = plugin_dir_path(__FILE__) . '../css/chat-transcripts.css';
7969 $transcripts_js_path = plugin_dir_path(__FILE__) . '../js/mxchat_transcripts.js';
7970 $activation_js_path = plugin_dir_path(__FILE__) . '../js/activation-script.js';
7971
7972 // Add the new content selector files
7973 $content_selector_css_path = plugin_dir_path(__FILE__) . '../css/content-selector.css';
7974 $content_selector_js_path = plugin_dir_path(__FILE__) . '../js/content-selector.js';
7975
7976 // Check if files exist and get modification times
7977 $color_picker_version = file_exists($color_picker_js_path) ? filemtime($color_picker_js_path) : $plugin_version;
7978 $embedding_check_version = file_exists($embedding_check_js_path) ? filemtime($embedding_check_js_path) : $plugin_version;
7979 $admin_css_version = file_exists($admin_css_path) ? filemtime($admin_css_path) : $plugin_version;
7980 $knowledge_css_version = file_exists($knowledge_css_path) ? filemtime($knowledge_css_path) : $plugin_version;
7981 $intent_css_version = file_exists($intent_css_path) ? filemtime($intent_css_path) : $plugin_version;
7982 $transcripts_css_version = file_exists($transcripts_css_path) ? filemtime($transcripts_css_path) : $plugin_version;
7983 $transcripts_js_version = file_exists($transcripts_js_path) ? filemtime($transcripts_js_path) : $plugin_version;
7984 $activation_js_version = file_exists($activation_js_path) ? filemtime($activation_js_path) : $plugin_version;
7985
7986 // Get versions for the new content selector files
7987 $content_selector_css_version = file_exists($content_selector_css_path) ? filemtime($content_selector_css_path) : $plugin_version;
7988 $content_selector_js_version = file_exists($content_selector_js_path) ? filemtime($content_selector_js_path) : $plugin_version;
7989
7990 $admin_status_js_path = plugin_dir_path(__FILE__) . '../js/admin-status.js';
7991 $admin_status_js_version = file_exists($admin_status_js_path) ? filemtime($admin_status_js_path) : $plugin_version;
7992
7993 // Check current admin page
7994 $current_page = isset($_GET['page']) ? $_GET['page'] : '';
7995
7996 // Only enqueue on the prompts page
7997 if ($current_page === 'mxchat-prompts') {
7998 wp_enqueue_script(
7999 'mxchat-status-updater',
8000 plugin_dir_url(__FILE__) . '../js/admin-status.js',
8001 array('jquery'),
8002 $admin_status_js_version,
8003 true
8004 );
8005
8006 // Add the nonce for the status updater
8007 wp_localize_script(
8008 'mxchat-status-updater',
8009 'mxchat_status_data',
8010 array(
8011 'ajax_url' => admin_url('admin-ajax.php'),
8012 'nonce' => wp_create_nonce('mxchat_status_nonce')
8013 )
8014 );
8015
8016 // Enqueue the content selector files only on the prompts page
8017 wp_enqueue_style(
8018 'mxchat-content-selector-css',
8019 plugin_dir_url(__FILE__) . '../css/content-selector.css',
8020 array(),
8021 $content_selector_css_version
8022 );
8023
8024 wp_enqueue_script(
8025 'mxchat-content-selector-js',
8026 plugin_dir_url(__FILE__) . '../js/content-selector.js',
8027 array('jquery'),
8028 $content_selector_js_version,
8029 true
8030 );
8031
8032 // Add the nonce for the content selector
8033 wp_localize_script(
8034 'mxchat-content-selector-js',
8035 'mxchatSelector',
8036 array(
8037 'ajaxurl' => admin_url('admin-ajax.php'),
8038 'nonce' => wp_create_nonce('mxchat_content_selector_nonce'),
8039 'i18n' => array(
8040 'searchPlaceholder' => __('Search posts and pages...', 'mxchat'),
8041 'selectAll' => __('Select All', 'mxchat'),
8042 'process' => __('Process Selected', 'mxchat'),
8043 'cancel' => __('Cancel', 'mxchat'),
8044 'noResults' => __('No content found.', 'mxchat')
8045 )
8046 )
8047 );
8048 }
8049
8050 // Enqueue activation script if on the activation page
8051 if ($current_page === 'mxchat-activation') {
8052 wp_enqueue_script(
8053 'mxchat-activation-js',
8054 plugin_dir_url(__FILE__) . '../js/activation-script.js',
8055 array('jquery'),
8056 $activation_js_version,
8057 true
8058 );
8059
8060 // Pass needed data to the activation script
8061 wp_localize_script(
8062 'mxchat-activation-js',
8063 'mxchatAdmin',
8064 array(
8065 'ajax_url' => admin_url('admin-ajax.php'),
8066 'license_nonce' => wp_create_nonce('mxchat_activate_license_nonce')
8067 )
8068 );
8069 }
8070
8071 // Enqueue scripts and styles with corrected paths
8072 wp_enqueue_script(
8073 'mxchat-color-picker',
8074 plugin_dir_url(__FILE__) . '../js/my-color-picker.js',
8075 array('wp-color-picker'),
8076 $color_picker_version,
8077 true
8078 );
8079
8080 wp_enqueue_script(
8081 'mxchat-embedding-check',
8082 plugin_dir_url(__FILE__) . '../js/embedding-check.js',
8083 array(),
8084 $embedding_check_version,
8085 true
8086 );
8087
8088 wp_enqueue_script(
8089 'mxchat-admin-js',
8090 plugin_dir_url(__FILE__) . '../js/mxchat-admin.js',
8091 array('jquery'),
8092 $plugin_version,
8093 true
8094 );
8095
8096 wp_localize_script('mxchat-admin-js', 'mxchatAdmin', array(
8097 'ajax_url' => admin_url('admin-ajax.php'),
8098 'license_nonce' => wp_create_nonce('mxchat_activate_license_nonce'),
8099 'inline_edit_nonce' => wp_create_nonce('mxchat_save_inline_nonce'),
8100 'setting_nonce' => wp_create_nonce('mxchat_save_setting_nonce'),
8101 'export_nonce' => wp_create_nonce('mxchat_export_transcripts'),
8102 'actions_nonce' => wp_create_nonce('mxchat_actions_nonce'),
8103 'add_intent_nonce' => wp_create_nonce('mxchat_add_intent_nonce'),
8104 'edit_intent_nonce' => wp_create_nonce('mxchat_edit_intent'),
8105 'toggle_action_nonce' => wp_create_nonce('mxchat_actions_nonce'),
8106 'is_activated' => $this->is_activated ? '1' : '0',
8107 // Add these two new lines
8108 'status_nonce' => wp_create_nonce('mxchat_status_nonce'),
8109 'status_refresh_interval' => 5000, // Update every 5 seconds
8110 ));
8111
8112 // Enqueue the admin CSS
8113 wp_enqueue_style(
8114 'mxchat-admin-css',
8115 plugin_dir_url(__FILE__) . '../css/admin-style.css',
8116 array(),
8117 $admin_css_version
8118 );
8119
8120 // Conditional enqueue for transcripts page
8121 if ($current_page === 'mxchat-transcripts') {
8122 wp_enqueue_style(
8123 'mxchat-chat-transcripts-css',
8124 plugin_dir_url(__FILE__) . '../css/chat-transcripts.css',
8125 array(),
8126 $transcripts_css_version
8127 );
8128
8129 wp_enqueue_script(
8130 'mxchat-transcripts-js',
8131 plugin_dir_url(__FILE__) . '../js/mxchat_transcripts.js',
8132 array('jquery'),
8133 $transcripts_js_version,
8134 true
8135 );
8136 }
8137
8138 // Only enqueue knowledge CSS on knowledge-related pages or all plugin pages
8139 if (strpos($current_page, 'mxchat') !== false) {
8140 wp_enqueue_style(
8141 'mxchat-knowledge-css',
8142 plugin_dir_url(__FILE__) . '../css/knowledge-style.css',
8143 array(),
8144 $knowledge_css_version
8145 );
8146 }
8147
8148 // Only enqueue intent-style.css on the mxchat-actions page
8149 if ($current_page === 'mxchat-actions') {
8150 wp_enqueue_style(
8151 'mxchat-intent-css',
8152 plugin_dir_url(__FILE__) . '../css/intent-style.css',
8153 array(),
8154 $intent_css_version
8155 );
8156 }
8157
8158 // IMPORTANT: Use the same script handle as above for localizing mxchatPromptsAdmin
8159 wp_localize_script('mxchat-admin-js', 'mxchatPromptsAdmin', array(
8160 'ajax_url' => admin_url('admin-ajax.php'),
8161 'prompts_setting_nonce' => wp_create_nonce('mxchat_prompts_setting_nonce'),
8162 ));
8163
8164 // Add this to the function that enqueues your admin scripts
8165 wp_localize_script('mxchat-admin-status', 'mxchat_status_data', array(
8166 'ajax_url' => admin_url('admin-ajax.php'),
8167 'nonce' => wp_create_nonce('mxchat_process_batch') // Make sure this matches what you check in the PHP
8168 ));
8169
8170 // Localize the script for color picker and settings
8171 wp_localize_script('mxchat-color-picker', 'mxchatStyleSettings', array(
8172 'ajax_url' => admin_url('admin-ajax.php'),
8173 'link_target_toggle' => $this->options['link_target_toggle'] ?? 'off',
8174 'user_message_bg_color' => $this->options['user_message_bg_color'] ?? '#fff',
8175 'user_message_font_color' => $this->options['user_message_font_color'] ?? '#212121',
8176 'bot_message_bg_color' => $this->options['bot_message_bg_color'] ?? '#212121',
8177 'bot_message_font_color' => $this->options['bot_message_font_color'] ?? '#fff',
8178 'top_bar_bg_color' => $this->options['top_bar_bg_color'] ?? '#212121',
8179 'send_button_font_color' => $this->options['send_button_font_color'] ?? '#212121',
8180 'close_button_color' => $this->options['close_button_color'] ?? '#fff',
8181 'chatbot_background_color' => $this->options['chatbot_background_color'] ?? '#212121',
8182 'icon_color' => $this->options['icon_color'] ?? '#fff',
8183 'chat_input_font_color' => $this->options['chat_input_font_color'] ?? '#212121',
8184 'pre_chat_message' => $this->options['pre_chat_message'] ?? esc_html__('Hey there! Ask me anything!', 'mxchat'),
8185 'rate_limit_message' => $this->options['rate_limit_message'] ?? esc_html__('Rate limit exceeded. Please try again later.', 'mxchat'),
8186 'loops_api_key' => $this->options['loops_api_key'] ?? '',
8187 'loops_mailing_list' => $this->options['loops_mailing_list'] ?? '',
8188 'triggered_phrase_response' => $this->options['triggered_phrase_response'] ?? esc_html__('Would you like to join our mailing list? Please provide your email below.', 'mxchat'),
8189 'email_capture_response' => $this->options['email_capture_response'] ?? esc_html__('Thank you for providing your email! You\'ve been added to our list.', 'mxchat'),
8190 'pdf_intent_trigger_text' => $this->options['pdf_intent_trigger_text'] ?? esc_html__("Please provide the URL to the PDF you'd like to discuss.", 'mxchat'),
8191 'pdf_intent_success_text' => $this->options['pdf_intent_success_text'] ?? esc_html__("I've processed the PDF. What questions do you have about it?", 'mxchat'),
8192 'pdf_intent_error_text' => $this->options['pdf_intent_error_text'] ?? esc_html__("Sorry, I couldn't process the PDF. Please ensure it's a valid file.", 'mxchat'),
8193 'pdf_max_pages' => $this->options['pdf_max_pages'] ?? 69,
8194 'live_agent_webhook_url' => $this->options['live_agent_webhook_url'] ?? '',
8195 'live_agent_secret_key' => $this->options['live_agent_secret_key'] ?? '',
8196 'live_agent_bot_token' => $this->options['live_agent_bot_token'] ?? '',
8197 'live_agent_message_bg_color' => $this->options['live_agent_message_bg_color'] ?? '#ffffff',
8198 'live_agent_message_font_color' => $this->options['live_agent_message_font_color'] ?? '#333333',
8199 'chat_toolbar_toggle' => $this->options['chat_toolbar_toggle'] ?? 'off',
8200 'show_pdf_upload_button' => $this->options['show_pdf_upload_button'] ?? 'on',
8201 'show_word_upload_button' => $this->options['show_word_upload_button'] ?? 'on',
8202 'mode_indicator_bg_color' => $this->options['mode_indicator_bg_color'] ?? '#767676',
8203 'mode_indicator_font_color' => $this->options['mode_indicator_font_color'] ?? '#ffffff',
8204 'toolbar_icon_color' => $this->options['toolbar_icon_color'] ?? '#212121',
8205 ));
8206 }
8207
8208 public function mxchat_sanitize($input) {
8209 $new_input = array();
8210
8211 if (isset($input['api_key'])) {
8212 $new_input['api_key'] = sanitize_text_field($input['api_key']);
8213 }
8214
8215 if (isset($input['similarity_threshold'])) {
8216 $new_input['similarity_threshold'] = absint($input['similarity_threshold']); // Ensure it's an integer
8217 $new_input['similarity_threshold'] = min(max($new_input['similarity_threshold'], 20), 85); // Enforce range
8218 }
8219
8220 if (isset($input['xai_api_key'])) {
8221 $new_input['xai_api_key'] = sanitize_text_field($input['xai_api_key']);
8222 }
8223
8224 if (isset($input['claude_api_key'])) {
8225 $new_input['claude_api_key'] = sanitize_text_field($input['claude_api_key']);
8226 }
8227
8228 if (isset($input['deepseek_api_key'])) {
8229 $new_input['deepseek_api_key'] = sanitize_text_field($input['deepseek_api_key']);
8230 }
8231
8232 if (isset($input['gemini_api_key'])) {
8233 $new_input['gemini_api_key'] = sanitize_text_field($input['gemini_api_key']);
8234 }
8235
8236 if (isset($input['enable_woocommerce_integration'])) {
8237 $new_input['enable_woocommerce_integration'] = $input['enable_woocommerce_integration'] === 'on' ? 'on' : 'off';
8238 }
8239
8240 if (isset($input['privacy_toggle'])) {
8241 $new_input['privacy_toggle'] = $input['privacy_toggle'];
8242 }
8243
8244 if (isset($input['complianz_toggle'])) {
8245 $new_input['complianz_toggle'] = $input['complianz_toggle'];
8246 }
8247
8248 // Handle custom privacy text input
8249 if (isset($input['privacy_text'])) {
8250 // Allow basic HTML for links
8251 $new_input['privacy_text'] = wp_kses_post($input['privacy_text']);
8252 }
8253
8254 if (isset($input['system_prompt_instructions'])) {
8255 $new_input['system_prompt_instructions'] = sanitize_textarea_field($input['system_prompt_instructions']);
8256 }
8257
8258 if (isset($input['mxchat_pro_email'])) {
8259 $new_input['mxchat_pro_email'] = sanitize_email($input['mxchat_pro_email']);
8260 }
8261
8262 if (isset($input['mxchat_activation_key'])) {
8263 $new_input['mxchat_activation_key'] = sanitize_text_field($input['mxchat_activation_key']);
8264 }
8265
8266 if (isset($input['append_to_body'])) {
8267 $new_input['append_to_body'] = $input['append_to_body'] === 'on' ? 'on' : 'off';
8268 }
8269
8270 if (isset($input['top_bar_title'])) {
8271 $new_input['top_bar_title'] = sanitize_text_field($input['top_bar_title']);
8272 }
8273
8274 if (isset($input['ai_agent_text'])) {
8275 $new_input['ai_agent_text'] = sanitize_text_field($input['ai_agent_text']);
8276 }
8277
8278 if (isset($input['enable_email_block'])) {
8279 $new_input['enable_email_block'] = sanitize_text_field($input['enable_email_block']);
8280 }
8281
8282 if (isset($input['email_blocker_header_content'])) {
8283 // wp_kses_post() allows standard HTML tags permitted by WordPress
8284 $new_input['email_blocker_header_content'] = wp_kses_post($input['email_blocker_header_content']);
8285 }
8286 if (isset($input['email_blocker_button_text'])) {
8287 $new_input['email_blocker_button_text'] = sanitize_text_field($input['email_blocker_button_text']);
8288 }
8289
8290 if (isset($input['intro_message'])) {
8291 $new_input['intro_message'] = wp_kses_post($input['intro_message']); // Use wp_kses_post instead
8292 }
8293
8294 if (isset($input['input_copy'])) {
8295 $new_input['input_copy'] = sanitize_text_field($input['input_copy']);
8296 }
8297
8298 if (isset($input['rate_limit_message'])) {
8299 $new_input['rate_limit_message'] = sanitize_text_field($input['rate_limit_message']);
8300 }
8301
8302 // Handle the new rate limits format
8303 if (isset($input['rate_limits']) && is_array($input['rate_limits'])) {
8304 $new_input['rate_limits'] = array();
8305 $allowed_limits = array('1', '3', '5', '10', '15', '20', '50', '100', 'unlimited');
8306 $allowed_timeframes = array('hourly', 'daily', 'weekly', 'monthly');
8307
8308 foreach ($input['rate_limits'] as $role_id => $settings) {
8309 $new_input['rate_limits'][$role_id] = array();
8310
8311 // Sanitize limit
8312 if (isset($settings['limit'])) {
8313 $limit = sanitize_text_field($settings['limit']);
8314 if (in_array($limit, $allowed_limits, true)) {
8315 $new_input['rate_limits'][$role_id]['limit'] = $limit;
8316 } else {
8317 $new_input['rate_limits'][$role_id]['limit'] = ($role_id === 'logged_out') ? '10' : '100'; // Default
8318 }
8319 }
8320
8321 // Sanitize timeframe
8322 if (isset($settings['timeframe'])) {
8323 $timeframe = sanitize_text_field($settings['timeframe']);
8324 if (in_array($timeframe, $allowed_timeframes, true)) {
8325 $new_input['rate_limits'][$role_id]['timeframe'] = $timeframe;
8326 } else {
8327 $new_input['rate_limits'][$role_id]['timeframe'] = 'daily'; // Default
8328 }
8329 }
8330
8331 // Sanitize message
8332 if (isset($settings['message'])) {
8333 $new_input['rate_limits'][$role_id]['message'] = sanitize_textarea_field($settings['message']);
8334 }
8335 }
8336 }
8337
8338 if (isset($input['pre_chat_message'])) {
8339 $new_input['pre_chat_message'] = sanitize_textarea_field($input['pre_chat_message']);
8340 }
8341
8342 if (isset($input['voyage_api_key'])) {
8343 $new_input['voyage_api_key'] = sanitize_text_field($input['voyage_api_key']);
8344 }
8345
8346 // Add to your sanitize function
8347 if (isset($input['embedding_model'])) {
8348 $allowed_models = array(
8349 'text-embedding-ada-002',
8350 'text-embedding-3-small',
8351 'text-embedding-3-large',
8352 'voyage-3-large'
8353 );
8354 if (in_array($input['embedding_model'], $allowed_models)) {
8355 $new_input['embedding_model'] = sanitize_text_field($input['embedding_model']);
8356 }
8357 }
8358
8359 if (isset($input['model'])) {
8360 $allowed_models = array(
8361 'gemini-2.0-flash',
8362 'gemini-2.0-flash-lite',
8363 'gemini-1.5-pro',
8364 'gemini-1.5-flash',
8365 'grok-3-beta',
8366 'grok-3-fast-beta',
8367 'grok-3-mini-beta',
8368 'grok-3-mini-fast-beta',
8369 'grok-2',
8370 'deepseek-chat',
8371 'claude-3-7-sonnet-20250219',
8372 'claude-3-5-sonnet-20241022',
8373 'claude-3-opus-20240229',
8374 'claude-3-sonnet-20240229',
8375 'claude-3-haiku-20240307',
8376 'gpt-4o',
8377 'gpt-4.1-2025-04-14',
8378 'gpt-4o-mini',
8379 'gpt-4-turbo',
8380 'gpt-4',
8381 'gpt-3.5-turbo',
8382 );
8383 if (in_array($input['model'], $allowed_models)) {
8384 $new_input['model'] = sanitize_text_field($input['model']);
8385 }
8386 }
8387
8388 if (isset($input['close_button_color'])) {
8389 $new_input['close_button_color'] = sanitize_hex_color($input['close_button_color']);
8390 }
8391
8392 if (isset($input['chatbot_bg_color'])) {
8393 $new_input['chatbot_bg_color'] = sanitize_hex_color($input['chatbot_bg_color']);
8394 }
8395
8396 if (isset($input['woocommerce_consumer_key'])) {
8397 $new_input['woocommerce_consumer_key'] = sanitize_text_field($input['woocommerce_consumer_key']);
8398 }
8399
8400 if (isset($input['woocommerce_consumer_secret'])) {
8401 $new_input['woocommerce_consumer_secret'] = sanitize_text_field($input['woocommerce_consumer_secret']);
8402 }
8403
8404 if (isset($input['user_message_bg_color'])) {
8405 $new_input['user_message_bg_color'] = sanitize_hex_color($input['user_message_bg_color']);
8406 }
8407
8408 if (isset($input['user_message_font_color'])) {
8409 $new_input['user_message_font_color'] = sanitize_hex_color($input['user_message_font_color']);
8410 }
8411
8412 if (isset($input['bot_message_bg_color'])) {
8413 $new_input['bot_message_bg_color'] = sanitize_hex_color($input['bot_message_bg_color']);
8414 }
8415
8416 if (isset($input['bot_message_font_color'])) {
8417 $new_input['bot_message_font_color'] = sanitize_hex_color($input['bot_message_font_color']);
8418 }
8419
8420 if (isset($input['live_agent_message_bg_color'])) {
8421 $new_input['live_agent_message_bg_color'] = sanitize_hex_color($input['live_agent_message_bg_color']);
8422 }
8423
8424 if (isset($input['live_agent_message_font_color'])) {
8425 $new_input['live_agent_message_font_color'] = sanitize_hex_color($input['live_agent_message_font_color']);
8426 }
8427
8428 if (isset($input['mode_indicator_bg_color'])) {
8429 $new_input['mode_indicator_bg_color'] = sanitize_hex_color($input['mode_indicator_bg_color']);
8430 }
8431
8432 if (isset($input['mode_indicator_font_color'])) {
8433 $new_input['mode_indicator_font_color'] = sanitize_hex_color($input['mode_indicator_font_color']);
8434 }
8435
8436 if (isset($input['toolbar_icon_color'])) {
8437 $new_input['toolbar_icon_color'] = sanitize_hex_color($input['toolbar_icon_color']);
8438 }
8439
8440 if (isset($input['top_bar_bg_color'])) {
8441 $new_input['top_bar_bg_color'] = sanitize_hex_color($input['top_bar_bg_color']);
8442 }
8443
8444 if (isset($input['send_button_font_color'])) {
8445 $new_input['send_button_font_color'] = sanitize_hex_color($input['send_button_font_color']);
8446 }
8447
8448 if (isset($input['chatbot_background_color'])) {
8449 $new_input['chatbot_background_color'] = sanitize_hex_color($input['chatbot_background_color']);
8450 }
8451
8452 if (isset($input['icon_color'])) {
8453 $new_input['icon_color'] = sanitize_hex_color($input['icon_color']);
8454 }
8455
8456 if (isset($input['custom_icon'])) {
8457 $new_input['custom_icon'] = esc_url_raw($input['custom_icon']);
8458 }
8459
8460 if (isset($input['title_icon'])) {
8461 $new_input['title_icon'] = esc_url_raw($input['title_icon']);
8462 }
8463
8464 if (isset($input['chat_input_font_color'])) {
8465 $new_input['chat_input_font_color'] = sanitize_hex_color($input['chat_input_font_color']);
8466 }
8467
8468 // Sanitize link_target_toggle
8469 if (isset($input['link_target_toggle'])) {
8470 $new_input['link_target_toggle'] = $input['link_target_toggle'] === 'on' ? 'on' : 'off';
8471 }
8472
8473 // Sanitize Loops API Key
8474 if (isset($input['loops_api_key'])) {
8475 $new_input['loops_api_key'] = sanitize_text_field($input['loops_api_key']);
8476 }
8477
8478 if (isset($input['chat_persistence_toggle'])) {
8479 $new_input['chat_persistence_toggle'] = $input['chat_persistence_toggle'] === 'on' ? 'on' : 'off';
8480 }
8481
8482 if (isset($input['popular_question_1'])) {
8483 $new_input['popular_question_1'] = sanitize_text_field($input['popular_question_1']);
8484 }
8485
8486 if (isset($input['popular_question_2'])) {
8487 $new_input['popular_question_2'] = sanitize_text_field($input['popular_question_2']);
8488 }
8489
8490 if (isset($input['popular_question_3'])) {
8491 $new_input['popular_question_3'] = sanitize_text_field($input['popular_question_3']);
8492 }
8493
8494 if (isset($input['additional_popular_questions']) && is_array($input['additional_popular_questions'])) {
8495 $new_input['additional_popular_questions'] = array_map('sanitize_text_field', $input['additional_popular_questions']);
8496 }
8497
8498 // Sanitize Loops Mailing List
8499 if (isset($input['loops_mailing_list'])) {
8500 $new_input['loops_mailing_list'] = sanitize_text_field($input['loops_mailing_list']);
8501 }
8502
8503 // Sanitize Triggered Phrase Response
8504 if (isset($input['triggered_phrase_response'])) {
8505 $new_input['triggered_phrase_response'] = wp_kses_post($input['triggered_phrase_response']);
8506 }
8507
8508 if (isset($input['email_capture_response'])) {
8509 $new_input['email_capture_response'] = sanitize_textarea_field($input['email_capture_response']);
8510 }
8511
8512 // Sanitize Brave Search Settings
8513 if (isset($input['brave_api_key'])) {
8514 $new_input['brave_api_key'] = sanitize_text_field($input['brave_api_key']);
8515 }
8516
8517 if (isset($input['brave_image_count'])) {
8518 $image_count = intval($input['brave_image_count']);
8519 $new_input['brave_image_count'] = ($image_count >=1 && $image_count <=6) ? $image_count : 4;
8520 }
8521
8522 if (isset($input['brave_safe_search'])) {
8523 $allowed = array('strict', 'off');
8524 $new_input['brave_safe_search'] = in_array($input['brave_safe_search'], $allowed, true) ? $input['brave_safe_search'] : 'strict';
8525 }
8526
8527 if (isset($input['brave_news_count'])) {
8528 $news_count = intval($input['brave_news_count']);
8529 $new_input['brave_news_count'] = ($news_count >=1 && $news_count <=10) ? $news_count : 3;
8530 }
8531
8532 if (isset($input['brave_country'])) {
8533 $new_input['brave_country'] = sanitize_text_field($input['brave_country']);
8534 }
8535
8536 if (isset($input['brave_language'])) {
8537 $new_input['brave_language'] = sanitize_text_field($input['brave_language']);
8538 }
8539
8540 if (isset($input['chat_toolbar_toggle'])) {
8541 $new_input['chat_toolbar_toggle'] = $input['chat_toolbar_toggle'] === 'on' ? 'on' : 'off';
8542 }
8543
8544 // Sanitize PDF upload button toggle
8545 if (isset($input['show_pdf_upload_button'])) {
8546 $new_input['show_pdf_upload_button'] = $input['show_pdf_upload_button'] === 'on' ? 'on' : 'off';
8547 } else {
8548 $new_input['show_pdf_upload_button'] = 'off'; // If checkbox is unchecked
8549 }
8550
8551 // Sanitize Word upload button toggle
8552 if (isset($input['show_word_upload_button'])) {
8553 $new_input['show_word_upload_button'] = $input['show_word_upload_button'] === 'on' ? 'on' : 'off';
8554 } else {
8555 $new_input['show_word_upload_button'] = 'off'; // If checkbox is unchecked
8556 }
8557
8558 if (isset($input['pdf_intent_trigger_text'])) {
8559 $new_input['pdf_intent_trigger_text'] = sanitize_text_field($input['pdf_intent_trigger_text']);
8560 }
8561
8562 if (isset($input['pdf_intent_success_text'])) {
8563 $new_input['pdf_intent_success_text'] = sanitize_text_field($input['pdf_intent_success_text']);
8564 }
8565
8566 if (isset($input['pdf_intent_error_text'])) {
8567 $new_input['pdf_intent_error_text'] = sanitize_text_field($input['pdf_intent_error_text']);
8568 }
8569
8570 if (isset($input['pdf_max_pages'])) {
8571 $new_input['pdf_max_pages'] = intval($input['pdf_max_pages']);
8572 if ($new_input['pdf_max_pages'] < 1 || $new_input['pdf_max_pages'] > 69) {
8573 $new_input['pdf_max_pages'] = 69; // Default to 69 if out of range
8574 }
8575 }
8576
8577 if (isset($input['live_agent_webhook_url'])) {
8578 $new_input['live_agent_webhook_url'] = esc_url_raw($input['live_agent_webhook_url']);
8579 }
8580 if (isset($input['live_agent_secret_key'])) {
8581 $new_input['live_agent_secret_key'] = sanitize_text_field($input['live_agent_secret_key']);
8582 }
8583
8584 // Live Agent Integration
8585 if (isset($input['live_agent_bot_token'])) {
8586 $new_input['live_agent_bot_token'] = sanitize_text_field($input['live_agent_bot_token']);
8587 }
8588
8589 if (isset($input['live_agent_status'])) {
8590 $new_input['live_agent_status'] = ($input['live_agent_status'] === 'on') ? 'on' : 'off';
8591 }
8592 if (isset($input['live_agent_away_message'])) {
8593 $new_input['live_agent_away_message'] = sanitize_textarea_field($input['live_agent_away_message']);
8594 }
8595 if (isset($input['live_agent_notification_message'])) {
8596 $new_input['live_agent_notification_message'] = sanitize_textarea_field($input['live_agent_notification_message']);
8597 }
8598
8599 return $new_input;
8600 }
8601
8602
8603 // Method to append the chatbot to the body
8604 public function mxchat_append_chatbot_to_body() {
8605 $options = get_option('mxchat_options');
8606 if (isset($options['append_to_body']) && $options['append_to_body'] === 'on') {
8607 echo do_shortcode('[mxchat_chatbot floating="yes"]');
8608 }
8609 }
8610
8611
8612
8613
8614
8615 private function mxchat_fetch_loops_mailing_lists($api_key) {
8616 $url = 'https://app.loops.so/api/v1/lists';
8617 $response = wp_remote_get($url, array(
8618 'headers' => array(
8619 'Authorization' => 'Bearer ' . $api_key,
8620 'Content-Type' => 'application/json'
8621 )
8622 ));
8623
8624 if (is_wp_error($response)) {
8625 return array();
8626 }
8627
8628 $body = wp_remote_retrieve_body($response);
8629 $lists = json_decode($body, true);
8630
8631 return isset($lists) && is_array($lists) ? $lists : array();
8632 }
8633
8634 function mxchat_calculate_cosine_similarity($vec1, $vec2) {
8635 if (empty($vec1) || empty($vec2)) {
8636 return 0.0;
8637 }
8638
8639 $dot_product = 0.0;
8640 $norm_a = 0.0;
8641 $norm_b = 0.0;
8642
8643 for ($i = 0; $i < count($vec1); $i++) {
8644 $dot_product += $vec1[$i] * $vec2[$i];
8645 $norm_a += pow($vec1[$i], 2);
8646 $norm_b += pow($vec2[$i], 2);
8647 }
8648
8649 if ($norm_a == 0.0 || $norm_b == 0.0) {
8650 return 0.0;
8651 } else {
8652 return $dot_product / (sqrt($norm_a) * sqrt($norm_b));
8653 }
8654 }
8655
8656
8657 }
8658 ?>
8659