PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 2.2.1
MxChat – AI Chatbot & Content Generation for WordPress v2.2.1
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.1, at includes/class-mxchat-admin.php

10,147 lines 437.9 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 add_action('wp_ajax_mxchat_migrate_pinecone_settings', array($this, 'ajax_migrate_pinecone_settings'));
58 add_action('admin_init', array($this, 'register_pinecone_settings'));
59
60 if (isset($this->options['enable_woocommerce_integration']) &&
61 ($this->options['enable_woocommerce_integration'] === '1' ||
62 $this->options['enable_woocommerce_integration'] === 'on')) {
63
64 //error_log('MxChat Admin: WooCommerce integration is enabled, adding hooks');
65 add_action('wp_insert_post', array($this, 'mxchat_handle_product_change'), 10, 3);
66 add_action('wp_trash_post', array($this, 'mxchat_handle_product_delete'));
67 add_action('before_delete_post', array($this, 'mxchat_handle_product_delete'));
68 }
69
70 add_action('wp_ajax_mxchat_get_status_updates', array($this, 'ajax_get_status_updates'));
71 add_action('admin_notices', array($this, 'display_admin_notices'));
72
73 }
74
75 private function is_license_active() {
76 // Get the raw value without translation
77 $license_status = get_option('mxchat_license_status', 'inactive');
78
79 // Check against multiple possible values, bypassing translation issues
80 return ($license_status === 'active' || $license_status === esc_html__('active', 'mxchat'));
81 }
82
83
84 // Initialize default options
85 private function initialize_default_options() {
86 $default_options = array(
87 'api_key' => '',
88 'xai_api_key' => '',
89 'claude_api_key' => '',
90 'deepseek_api_key' => '',
91 'voyage_api_key' => '',
92 'gemini_api_key' => '',
93 'embedding_model' => 'text-embedding-ada-002',
94 'system_prompt_instructions' => 'You are an AI Chatbot assistant for this website. Your main goal is to assist visitors with questions and provide helpful information. Here are your key guidelines:
95
96 # Response Style - CRITICALLY IMPORTANT
97 - MAXIMUM LENGTH: 1-3 short sentences per response
98 - Ultra-concise: Get straight to the answer with no filler
99 - No introductions like "Sure!" or "I\'d be happy to help"
100 - No phrases like "based on my knowledge" or "according to information"
101 - No explanatory text before giving the answer
102 - No summaries or repetition
103 - Hyperlink all URLs
104 - Respond in user\'s language
105 - Minor chit chat or conversation is okay, but try to keep it focused on [insert topic]
106
107 # Knowledge Base Requirements - PREVENT HALLUCINATIONS
108 - ONLY answer using information explicitly provided in OFFICIAL KNOWLEDGE DATABASE CONTENT sections marked with ===== delimiters
109 - If required information is NOT in the knowledge database: "I don\'t have enough information in my knowledge base to answer that question accurately."
110 - NEVER invent or hallucinate URLs, links, product specs, procedures, dates, statistics, names, contacts, or company information
111 - When knowledge base information is unclear or contradictory, acknowledge the limitation rather than guessing
112 - If asked about something not in knowledge base, explicitly state information is not available - DO NOT provide general information
113 - Better to admit insufficient information than provide inaccurate answers',
114 'model' => esc_html__('gpt-4o', 'mxchat'),
115 'rate_limit_logged_out' => esc_html__('100', 'mxchat'),
116 'role_rate_limits' => array(),
117 'rate_limit_message' => esc_html__('Rate limit exceeded. Please try again later.', 'mxchat'),
118 'enable_email_block' => '',
119 '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'),
120 'email_blocker_button_text' => esc_html__('Start Chat', 'mxchat'),
121 'top_bar_title' => esc_html__('MxChat', 'mxchat'),
122 'intro_message' => __('Hello! How can I assist you today?', 'mxchat'),
123 'ai_agent_text' => esc_html__('AI Agent', 'mxchat'),
124 'input_copy' => esc_html__('How can I assist?', 'mxchat'),
125 'append_to_body' => esc_html__('off', 'mxchat'),
126 'close_button_color' => esc_html__('#fff', 'mxchat'),
127 'chatbot_bg_color' => esc_html__('#fff', 'mxchat'),
128 'user_message_bg_color' => esc_html__('#fff', 'mxchat'),
129 'user_message_font_color' => esc_html__('#212121', 'mxchat'),
130 'bot_message_bg_color' => esc_html__('#212121', 'mxchat'),
131 'bot_message_font_color' => esc_html__('#fff', 'mxchat'),
132 'top_bar_bg_color' => esc_html__('#212121', 'mxchat'),
133 'send_button_font_color' => esc_html__('#212121', 'mxchat'),
134 'chat_input_font_color' => esc_html__('#212121', 'mxchat'),
135 'chatbot_background_color' => esc_html__('#212121', 'mxchat'),
136 'icon_color' => esc_html__('#fff', 'mxchat'),
137 'enable_woocommerce_integration' => esc_html__('0', 'mxchat'),
138 'link_target_toggle' => esc_html__('off', 'mxchat'),
139 'pre_chat_message' => esc_html__('Hey there! Ask me anything!', 'mxchat'),
140
141 // New fields for Loops Integration
142 'loops_api_key' => '',
143 'loops_mailing_list' => '',
144 'triggered_phrase_response' => __('Would you like to join our mailing list? Please provide your email below.', 'mxchat'),
145 'email_capture_response' => __('Thank you for providing your email! You\'ve been added to our list.', 'mxchat'),
146 'popular_question_1' => '',
147 'popular_question_2' => '',
148 'popular_question_3' => '',
149 'pdf_intent_trigger_text' => __("Please provide the URL to the PDF you'd like to discuss.", 'mxchat'),
150 'pdf_intent_success_text' => __("I've processed the PDF. What questions do you have about it?", 'mxchat'),
151 'pdf_intent_error_text' => __("Sorry, I couldn't process the PDF. Please ensure it's a valid file.", 'mxchat'),
152 'pdf_max_pages' => 69,
153 'show_pdf_upload_button' => 'on',
154 'show_word_upload_button' => 'on',
155
156 // Live Agent Integration
157 'live_agent_webhook_url' => '',
158 'live_agent_secret_key' => '',
159 'live_agent_bot_token' => '',
160 'live_agent_message_bg_color' => esc_html__('#ffffff', 'mxchat'),
161 'live_agent_message_font_color' => esc_html__('#333333', 'mxchat'),
162 'chat_toolbar_toggle' => esc_html__('off', 'mxchat'),
163 'mode_indicator_bg_color' => esc_html__('#767676', 'mxchat'),
164 'mode_indicator_font_color' => esc_html__('#ffffff', 'mxchat'),
165 'toolbar_icon_color' => esc_html__('#212121', 'mxchat'),
166 );
167
168
169 // Merge existing options with defaults
170 $existing_options = get_option('mxchat_options', array());
171 $merged_options = wp_parse_args($existing_options, $default_options);
172
173 // Update the options if they have changed
174 if ($existing_options !== $merged_options) {
175 update_option('mxchat_options', $merged_options);
176 }
177
178 // Add default limits for each role
179 $roles = wp_roles()->get_names();
180 foreach ($roles as $role_id => $role_name) {
181 $default_options['role_rate_limits'][$role_id] = esc_html__('100', 'mxchat');
182 }
183
184 return $default_options;
185
186 // Update the $this->options property
187 $this->options = $merged_options;
188 }
189
190
191
192 public function mxchat_add_plugin_page() {
193 // Main menu page
194 add_menu_page(
195 esc_html__('MxChat Settings', 'mxchat'),
196 esc_html__('MxChat', 'mxchat'),
197 'manage_options',
198 'mxchat-max',
199 array($this, 'mxchat_create_admin_page'),
200 'dashicons-testimonial',
201 6
202 );
203
204 // Submenu page for Knowledge
205 add_submenu_page(
206 'mxchat-max',
207 esc_html__('Prompts', 'mxchat'),
208 esc_html__('Knowledge', 'mxchat'),
209 'manage_options',
210 'mxchat-prompts',
211 array($this, 'mxchat_create_prompts_page')
212 );
213
214 add_submenu_page(
215 'mxchat-max',
216 esc_html__('Chat Transcripts', 'mxchat'),
217 esc_html__('Transcripts', 'mxchat'),
218 'manage_options',
219 'mxchat-transcripts',
220 array($this, 'mxchat_create_transcripts_page')
221 );
222
223 add_submenu_page(
224 'mxchat-max',
225 esc_html__('MxChat Actions', 'mxchat'),
226 esc_html__('Actions', 'mxchat'),
227 'manage_options',
228 'mxchat-actions',
229 array($this, 'mxchat_actions_page_html')
230 );
231
232 add_submenu_page(
233 'mxchat-max',
234 esc_html__('Add Ons', 'mxchat'),
235 esc_html__('Add Ons', 'mxchat'),
236 'manage_options',
237 'mxchat-addons',
238 array($this, 'mxchat_create_addons_page')
239 );
240
241 // Submenu page for Activation Key
242 add_submenu_page(
243 'mxchat-max',
244 esc_html__('Pro Upgrade', 'mxchat'),
245 esc_html__('Pro Upgrade', 'mxchat'),
246 'manage_options',
247 'mxchat-activation',
248 array($this, 'mxchat_create_activation_page')
249 );
250 }
251
252 public function mxchat_create_addons_page() {
253 require_once plugin_dir_path(__FILE__) . 'class-mxchat-addons.php';
254 $addons_page = new MxChat_Addons();
255 $addons_page->render_page();
256 }
257
258 public function mxchat_save_setting_callback() {
259 check_ajax_referer('mxchat_save_setting_nonce');
260 if (!current_user_can('manage_options')) {
261 ('MXChat Save: Unauthorized access attempt');
262 wp_send_json_error(['message' => esc_html__('Unauthorized', 'mxchat')]);
263 }
264
265 $name = isset($_POST['name']) ? $_POST['name'] : '';
266 // Strip slashes from the value before saving
267 $value = isset($_POST['value']) ? stripslashes($_POST['value']) : '';
268
269 //error_log('MXChat Save: Processing field name: ' . $name);
270 //error_log('MXChat Save: Field value: ' . $value);
271
272 if (empty($name)) {
273 //error_log('MXChat Save: Empty field name detected');
274 wp_send_json_error(['message' => esc_html__('Invalid field name', 'mxchat')]);
275 }
276
277 // Load the full options array
278 $options = get_option('mxchat_options', []);
279 //error_log('MXChat Save: Current options array: ' . print_r($options, true));
280
281 // Handle special cases
282 switch ($name) {
283 case 'additional_popular_questions':
284 //error_log('MXChat Save: Processing additional_popular_questions');
285 $questions = json_decode($value, true); // No need for stripslashes here
286 if (is_array($questions)) {
287 $options[$name] = $questions;
288 // Also update old option for backwards compatibility
289 update_option('additional_popular_questions', $questions);
290 //error_log('MXChat Save: Saved ' . count($questions) . ' additional questions');
291 } else {
292 //error_log('MXChat Save: Failed to decode questions JSON');
293 }
294 break;
295 case 'email_blocker_header_content':
296 //error_log('MXChat Save: Processing email_blocker_header_content');
297 // Allow HTML content but sanitize it safely
298 $options[$name] = wp_kses_post($value);
299 break;
300 case 'similarity_threshold':
301 //error_log('MXChat Save: Processing similarity_threshold');
302 // Save to the options array
303 $options[$name] = $value;
304 break;
305 case 'user_message_bg_color':
306 case 'user_message_font_color':
307 case 'bot_message_bg_color':
308 case 'bot_message_font_color':
309 case 'top_bar_bg_color':
310 case 'send_button_font_color':
311 case 'chatbot_background_color':
312 case 'icon_color':
313 case 'chat_input_font_color':
314 case 'live_agent_message_bg_color':
315 case 'live_agent_message_font_color':
316 case 'mode_indicator_bg_color':
317 case 'mode_indicator_font_color':
318 case 'toolbar_icon_color':
319 //error_log('MXChat Save: Processing color value: ' . $name);
320 // Store color values directly
321 $options[$name] = $value;
322 break;
323 case 'live_agent_status':
324 //error_log('MXChat Save: Processing live_agent_status');
325 // Set the new value
326 $options[$name] = ($value === 'on') ? 'on' : 'off';
327 break;
328 case 'enable_woocommerce_integration':
329 //error_log('MXChat Save: Processing enable_woocommerce_integration');
330 // Handle values that used to be 1/0
331 $options[$name] = ($value === 'on' || $value === '1') ? 'on' : 'off';
332 break;
333 default:
334 // First check for rate limits settings
335 if (strpos($name, 'mxchat_options[rate_limits]') !== false) {
336 //error_log('MXChat Save: Detected rate_limits field: ' . $name);
337
338 // Extract role ID and setting from the name
339 preg_match('/\[rate_limits\]\[(.*?)\]\[(.*?)\]/', $name, $matches);
340 //error_log('MXChat Save: Regex matches: ' . print_r($matches, true));
341
342 if (isset($matches[1]) && isset($matches[2])) {
343 $role_id = $matches[1];
344 $setting_key = $matches[2]; // limit, timeframe, or message
345
346 //error_log('MXChat Save: Role ID = ' . $role_id . ', Setting Key = ' . $setting_key);
347
348 // Initialize rate_limits if it doesn't exist
349 if (!isset($options['rate_limits'])) {
350 // //error_log('MXChat Save: Initializing rate_limits array');
351 $options['rate_limits'] = [];
352 }
353
354 // Initialize role settings if it doesn't exist
355 if (!isset($options['rate_limits'][$role_id])) {
356 //error_log('MXChat Save: Initializing rate_limits for role: ' . $role_id);
357 $options['rate_limits'][$role_id] = [
358 'limit' => ($role_id === 'logged_out') ? '10' : '100',
359 'timeframe' => 'daily',
360 'message' => 'Rate limit exceeded. Please try again later.'
361 ];
362 }
363
364 // Update the specific setting
365 $options['rate_limits'][$role_id][$setting_key] = $value;
366 //error_log('MXChat Save: Updated rate_limits[' . $role_id . '][' . $setting_key . '] = ' . $value);
367 } else {
368 //error_log('MXChat Save: Failed to parse rate_limits pattern: ' . $name);
369 }
370 }
371 // Then check for role rate limits (old format)
372 else if (strpos($name, 'mxchat_options[role_rate_limits]') !== false) {
373 //error_log('MXChat Save: Processing role_rate_limits field: ' . $name);
374 // Extract role ID from the name
375 preg_match('/\[role_rate_limits\]\[(.*?)\]/', $name, $matches);
376 //error_log('MXChat Save: Regex matches: ' . print_r($matches, true));
377
378 if (isset($matches[1])) {
379 $role_id = $matches[1];
380 // Initialize role_rate_limits if it doesn't exist
381 if (!isset($options['role_rate_limits'])) {
382 //error_log('MXChat Save: Initializing role_rate_limits array');
383 $options['role_rate_limits'] = [];
384 }
385 // Update the specific role's rate limit
386 $options['role_rate_limits'][$role_id] = sanitize_text_field($value);
387 //error_log('MXChat Save: Updated role_rate_limits[' . $role_id . '] = ' . $value);
388 } else {
389 //error_log('MXChat Save: Failed to parse role_rate_limits pattern: ' . $name);
390 }
391 }
392 // Handle toggles
393 else if (strpos($name, 'toggle') !== false || in_array($name, [
394 'chat_persistence_toggle',
395 'privacy_toggle',
396 'complianz_toggle',
397 'chat_toolbar_toggle',
398 'show_pdf_upload_button',
399 'show_word_upload_button'
400 ])) {
401 //error_log('MXChat Save: Processing toggle: ' . $name);
402 $options[$name] = ($value === 'on') ? 'on' : 'off';
403 } else {
404 //error_log('MXChat Save: Processing standard field: ' . $name);
405 // Store all other values directly
406 $options[$name] = $value;
407 }
408 break;
409 }
410
411 // Save all updates to the options array
412 $updated = update_option('mxchat_options', $options);
413 //error_log('MXChat Save: Update result: ' . ($updated ? 'success' : 'unchanged') . ' for field: ' . $name);
414 //error_log('MXChat Save: Updated options array: ' . print_r($options, true));
415
416 // Always return success even if WordPress says nothing changed
417 // (which happens when the value is the same as before)
418 wp_send_json_success(['message' => esc_html__('Setting saved', 'mxchat')]);
419 }
420
421 /**
422 * Helper function to compare if a value has changed
423 * Handles various data types appropriately
424 */
425 private function has_value_changed($old_value, $new_value) {
426 // Handle null values
427 if ($old_value === null && $new_value === '') {
428 return false;
429 }
430
431 // Handle array values (like additional_popular_questions)
432 if (is_array($old_value) && is_array($new_value)) {
433 // Convert both to JSON for comparison to handle ordering differences
434 return json_encode($old_value) !== json_encode($new_value);
435 }
436
437 // Handle toggle/checkbox values consistently
438 if (in_array($old_value, ['on', '1', 1, true]) && in_array($new_value, ['on', '1', 1, true])) {
439 return false;
440 }
441 if (in_array($old_value, ['off', '0', 0, false, '']) && in_array($new_value, ['off', '0', 0, false, ''])) {
442 return false;
443 }
444
445 // Default direct comparison
446 return $old_value !== $new_value;
447 }
448
449 /**
450 * AJAX handler for migrating Pinecone settings from old add-on
451 */
452 public function ajax_migrate_pinecone_settings() {
453 // Verify nonce
454 if (!wp_verify_nonce($_POST['_ajax_nonce'] ?? '', 'mxchat_save_setting_nonce')) {
455 wp_send_json_error('Invalid nonce');
456 }
457
458 // Check permissions
459 if (!current_user_can('manage_options')) {
460 wp_send_json_error('Unauthorized access');
461 }
462
463 // Check if old Pinecone addon options exist
464 $old_options = get_option('mxchat_pinecone_addon_options', array());
465
466 if (empty($old_options)) {
467 wp_send_json_success(array('migrated' => false, 'message' => 'No old settings found'));
468 }
469
470 // Get current core plugin options
471 $current_options = get_option('mxchat_pinecone_addon_options', array());
472
473 // Only migrate if core options are empty or if explicitly requested
474 $should_migrate = empty($current_options) ||
475 (empty($current_options['mxchat_pinecone_api_key']) && !empty($old_options['mxchat_pinecone_api_key']));
476
477 if ($should_migrate) {
478 // Migrate settings with proper sanitization
479 $migrated_options = array(
480 'mxchat_use_pinecone' => $old_options['mxchat_use_pinecone'] ?? '0',
481 'mxchat_pinecone_api_key' => sanitize_text_field($old_options['mxchat_pinecone_api_key'] ?? ''),
482 'mxchat_pinecone_host' => sanitize_text_field($old_options['mxchat_pinecone_host'] ?? ''),
483 'mxchat_pinecone_index' => sanitize_text_field($old_options['mxchat_pinecone_index'] ?? ''),
484 'mxchat_pinecone_environment' => sanitize_text_field($old_options['mxchat_pinecone_environment'] ?? '')
485 );
486
487 update_option('mxchat_pinecone_addon_options', $migrated_options);
488
489 wp_send_json_success(array(
490 'migrated' => true,
491 'message' => 'Settings migrated successfully from Pinecone add-on'
492 ));
493 } else {
494 wp_send_json_success(array(
495 'migrated' => false,
496 'message' => 'Settings already exist in core plugin'
497 ));
498 }
499 }
500
501 /**
502 * Fixed AJAX handler that bypasses the problematic sanitization
503 */
504 /**
505 * Fixed AJAX handler with improved verification
506 */
507 public function mxchat_save_prompts_setting_callback() {
508 check_ajax_referer('mxchat_prompts_setting_nonce');
509
510 if (!current_user_can('manage_options')) {
511 wp_send_json_error(['message' => esc_html__('Unauthorized', 'mxchat')]);
512 }
513
514 $name = isset($_POST['name']) ? $_POST['name'] : '';
515 $value = isset($_POST['value']) ? stripslashes($_POST['value']) : '';
516
517 error_log('[MXCHAT-PROMPTS] Saving setting: ' . $name . ' = ' . $value);
518
519 if (empty($name)) {
520 wp_send_json_error(['message' => esc_html__('Invalid field name', 'mxchat')]);
521 }
522
523 // Handle Pinecone settings - BYPASS WORDPRESS SANITIZATION
524 if (strpos($name, 'mxchat_pinecone_addon_options') !== false) {
525 error_log('[MXCHAT-PROMPTS] Processing Pinecone setting: ' . $name);
526
527 // Extract the field name
528 if (preg_match('/mxchat_pinecone_addon_options\[([^\]]+)\]/', $name, $matches)) {
529 $field_name = $matches[1];
530 error_log('[MXCHAT-PROMPTS] Extracted field name: ' . $field_name);
531
532 // Get current options directly from database - NO WordPress filters
533 global $wpdb;
534 $current_options_raw = $wpdb->get_var(
535 $wpdb->prepare(
536 "SELECT option_value FROM {$wpdb->options} WHERE option_name = %s",
537 'mxchat_pinecone_addon_options'
538 )
539 );
540
541 // Unserialize the raw data
542 $current_options = maybe_unserialize($current_options_raw);
543 if (!is_array($current_options)) {
544 $current_options = array();
545 }
546
547 error_log('[MXCHAT-PROMPTS] Current options from DB: ' . print_r($current_options, true));
548
549 // Update the specific field with proper sanitization
550 switch ($field_name) {
551 case 'mxchat_use_pinecone':
552 $new_value = ($value === '1') ? '1' : '0';
553 break;
554 case 'mxchat_pinecone_api_key':
555 case 'mxchat_pinecone_host':
556 case 'mxchat_pinecone_index':
557 case 'mxchat_pinecone_environment':
558 $new_value = sanitize_text_field($value);
559 if ($field_name === 'mxchat_pinecone_host') {
560 $new_value = str_replace(['https://', 'http://'], '', $new_value);
561 }
562 break;
563 default:
564 wp_send_json_error(['message' => esc_html__('Unknown Pinecone field', 'mxchat')]);
565 }
566
567 $current_options[$field_name] = $new_value;
568 error_log('[MXCHAT-PROMPTS] New value for ' . $field_name . ': "' . $new_value . '"');
569 error_log('[MXCHAT-PROMPTS] Updated options: ' . print_r($current_options, true));
570
571 // Save directly to database to bypass WordPress sanitization
572 $serialized_options = maybe_serialize($current_options);
573 $save_result = $wpdb->update(
574 $wpdb->options,
575 array('option_value' => $serialized_options),
576 array('option_name' => 'mxchat_pinecone_addon_options'),
577 array('%s'),
578 array('%s')
579 );
580
581 error_log('[MXCHAT-PROMPTS] Direct DB save result: ' . ($save_result !== false ? 'SUCCESS' : 'FAILED'));
582
583 // Clear any WordPress option cache to ensure get_option() returns fresh data
584 wp_cache_delete('mxchat_pinecone_addon_options', 'options');
585
586 // IMPROVED VERIFICATION - Check if the database operation succeeded
587 if ($save_result !== false) {
588 // Double-check by reading fresh from database
589 $verification_raw = $wpdb->get_var(
590 $wpdb->prepare(
591 "SELECT option_value FROM {$wpdb->options} WHERE option_name = %s",
592 'mxchat_pinecone_addon_options'
593 )
594 );
595 $verification_options = maybe_unserialize($verification_raw);
596 $verified_value = isset($verification_options[$field_name]) ? $verification_options[$field_name] : 'NOT_FOUND';
597
598 error_log('[MXCHAT-PROMPTS] Final verification - Expected: "' . $new_value . '", Got: "' . $verified_value . '"');
599
600 // Use loose comparison (==) instead of strict (===) to avoid type issues
601 if ($verified_value == $new_value || $save_result > 0) {
602 wp_send_json_success(['message' => esc_html__('Pinecone setting saved', 'mxchat')]);
603 } else {
604 // Still return success if the DB operation worked, even if verification is quirky
605 error_log('[MXCHAT-PROMPTS] Verification mismatch but DB operation succeeded');
606 wp_send_json_success(['message' => esc_html__('Pinecone setting saved (DB success)', 'mxchat')]);
607 }
608 } else {
609 wp_send_json_error(['message' => esc_html__('Database save failed', 'mxchat')]);
610 }
611 } else {
612 wp_send_json_error(['message' => esc_html__('Invalid field name format', 'mxchat')]);
613 }
614
615 return; // Exit here for Pinecone settings
616 }
617
618 // Handle auto-sync settings (existing functionality)
619 if (strpos($name, 'mxchat_auto_sync_') === 0) {
620 $value = ($value === 'on' || $value === '1') ? '1' : '0';
621 $updated = update_option($name, $value);
622
623 if ($updated || get_option($name) === $value) {
624 wp_send_json_success(['message' => esc_html__('Auto-sync setting saved', 'mxchat')]);
625 } else {
626 wp_send_json_error(['message' => esc_html__('No changes detected', 'mxchat')]);
627 }
628 }
629
630 // Handle other prompts options
631 $options = get_option('mxchat_prompts_options', []);
632 $options[$name] = $value;
633 $updated = update_option('mxchat_prompts_options', $options);
634
635 if ($updated) {
636 wp_send_json_success(['message' => esc_html__('Setting saved', 'mxchat')]);
637 } else {
638 wp_send_json_error(['message' => esc_html__('No changes detected', 'mxchat')]);
639 }
640 }
641
642
643 /**
644 * Register Pinecone settings (add this to your admin_init hook)
645 */
646 public function register_pinecone_settings() {
647 register_setting(
648 'mxchat_pinecone_addon_options',
649 'mxchat_pinecone_addon_options',
650 array(
651 'type' => 'array',
652 'sanitize_callback' => array($this, 'sanitize_pinecone_settings'),
653 'default' => array(
654 'mxchat_use_pinecone' => '0',
655 'mxchat_pinecone_api_key' => '',
656 'mxchat_pinecone_host' => '',
657 'mxchat_pinecone_index' => '',
658 'mxchat_pinecone_environment' => ''
659 )
660 )
661 );
662 }
663
664 /**
665 * Sanitize Pinecone settings
666 */
667 public function sanitize_pinecone_settings($input) {
668 $sanitized = array();
669
670 $sanitized['mxchat_use_pinecone'] = isset($input['mxchat_use_pinecone']) ? '1' : '0';
671 $sanitized['mxchat_pinecone_api_key'] = sanitize_text_field($input['mxchat_pinecone_api_key'] ?? '');
672 $sanitized['mxchat_pinecone_host'] = sanitize_text_field($input['mxchat_pinecone_host'] ?? '');
673 $sanitized['mxchat_pinecone_index'] = sanitize_text_field($input['mxchat_pinecone_index'] ?? '');
674 $sanitized['mxchat_pinecone_environment'] = sanitize_text_field($input['mxchat_pinecone_environment'] ?? '');
675
676 // Remove https:// from host if present
677 $sanitized['mxchat_pinecone_host'] = str_replace(['https://', 'http://'], '', $sanitized['mxchat_pinecone_host']);
678
679 return $sanitized;
680 }
681
682
683 public function mxchat_display_admin_notice() {
684 // Success notice
685 if ($message = get_transient('mxchat_admin_notice_success')) {
686 ?>
687 <div class="notice notice-success is-dismissible">
688 <p><?php echo esc_html($message); ?></p>
689 <button type="button" class="notice-dismiss"><span class="screen-reader-text"><?php echo esc_html__('Dismiss this notice.', 'mxchat'); ?></span></button>
690 </div>
691 <?php
692 delete_transient('mxchat_admin_notice_success'); // Clear the transient after displaying
693 }
694
695 // Error notice
696 if ($message = get_transient('mxchat_admin_notice_error')) {
697 ?>
698 <div class="notice notice-error is-dismissible">
699 <p><?php echo esc_html($message); ?></p>
700 <button type="button" class="notice-dismiss"><span class="screen-reader-text"><?php echo esc_html__('Dismiss this notice.', 'mxchat'); ?></span></button>
701 </div>
702 <?php
703 delete_transient('mxchat_admin_notice_error'); // Clear the transient after displaying
704 }
705 }
706
707
708
709
710
711 public function mxchat_create_admin_page() {
712
713 ?>
714 <div class="wrap mxchat-wrapper">
715 <!-- Hero Section -->
716 <div class="mxchat-hero">
717 <h1 class="mxchat-main-title">
718 <span class="mxchat-gradient-text">MxChat</span> Settings
719 </h1>
720 <p class="mxchat-hero-subtitle">
721 <?php esc_html_e('Configure your AI chatbot, manage integrations and explore tutorials to get the most out of MxChat.', 'mxchat'); ?>
722 </p>
723 </div>
724
725 <div class="mxchat-content">
726 <?php if (!$this->is_activated): ?>
727 <div class="mxchat-pro-card">
728 <div class="mxchat-pro-notification">
729 <div class="mxchat-pro-content">
730 <h3>🚀 Limited Lifetime Offer: Save 30% on MxChat Pro, Agency, or Agency Plus!</h3>
731 <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 <strong>lifetime license!</strong></p> </div>
732 <div class="mxchat-pro-cta">
733 <a href="https://mxchat.ai/" target="_blank" class="mxchat-button"><?php echo esc_html__('Upgrade Today', 'mxchat'); ?></a>
734 <a href="<?php echo admin_url('admin.php?page=mxchat-addons'); ?>" class="mxchat-link"><?php echo esc_html__('Preview Add-ons', 'mxchat'); ?></a>
735 </div>
736 </div>
737 </div>
738 <?php endif; ?>
739
740 <!-- Tabs Navigation -->
741 <div class="mxchat-tabs">
742 <button class="mxchat-tab-button active" data-tab="chatbot"><?php echo esc_html__('Chatbot', 'mxchat'); ?></button>
743 <button class="mxchat-tab-button" data-tab="embed"><?php echo esc_html__('Toolbar & Components', 'mxchat'); ?></button>
744 <button class="mxchat-tab-button" data-tab="general"><?php echo esc_html__('YouTube Tutorials', 'mxchat'); ?></button>
745 </div>
746
747 <!-- Tab Contents -->
748 <div id="chatbot" class="mxchat-tab-content active">
749 <div class="mxchat-card">
750 <div class="mxchat-autosave-section">
751 <?php do_settings_sections('mxchat-chatbot'); ?>
752 </div>
753 </div>
754 </div>
755
756 <div id="embed" class="mxchat-tab-content">
757
758 <div class="mxchat-card">
759 <h2><?php esc_html_e('Toolbar Settings', 'mxchat'); ?></h2>
760 <div class="mxchat-autosave-section">
761 <table class="form-table">
762 <?php do_settings_fields('mxchat-embed', 'mxchat_pdf_intent_section'); ?>
763 </table>
764 </div>
765 </div>
766
767
768 <div class="mxchat-card">
769 <h2><?php esc_html_e('Loops Settings', 'mxchat'); ?></h2>
770 <div class="mxchat-autosave-section">
771 <table class="form-table">
772 <?php do_settings_fields('mxchat-embed', 'mxchat_loops_section'); ?>
773 </table>
774 </div>
775 </div>
776
777 <div class="mxchat-card">
778 <h2><?php esc_html_e('Brave Search Settings', 'mxchat'); ?></h2>
779 <div class="mxchat-autosave-section">
780 <table class="form-table">
781 <?php do_settings_fields('mxchat-embed', 'mxchat_brave_section'); ?>
782 </table>
783 </div>
784 </div>
785
786 <div class="mxchat-card">
787 <h2><?php esc_html_e('Live Agent Settings', 'mxchat'); ?></h2>
788 <div class="mxchat-autosave-section">
789 <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>
790 <table class="form-table">
791 <?php do_settings_fields('mxchat-embed', 'mxchat_live_agent_section'); ?>
792 </table>
793 </div>
794 </div>
795 </div>
796
797 <div id="general" class="mxchat-tab-content">
798 <div class="mxchat-card">
799 <?php do_settings_sections('mxchat-general'); ?>
800 <div class="video-tutorials-section">
801
802
803 <div class="support-notification">
804 <div class="support-notification-icon">
805 <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">
806 <path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"/>
807 <path d="M12 9v4"/>
808 <path d="M12 17h.01"/>
809 </svg>
810 </div>
811 <div class="support-notification-content">
812 <h3 class="support-notification-title"><?php echo esc_html__('Need Help? We\'re Here for You!', 'mxchat'); ?></h3>
813 <p class="support-notification-message">
814 <?php echo esc_html__('Our goal is to provide the best experience and AI chatbot plugin available. If you\'re having trouble or believe something is not working as expected, please don\'t hesitate to submit a support ticket.', 'mxchat'); ?>
815 </p>
816 <a href="https://wordpress.org/support/plugin/mxchat-basic/" target="_blank" rel="noopener noreferrer" class="support-notification-button">
817 <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
818 <path d="M14 9a2 2 0 0 1-2 2H6l-4 4V4c0-1.1.9-2 2-2h8a2 2 0 0 1 2 2v5Z"/>
819 <path d="M18 9h2a2 2 0 0 1 2 2v11l-4-4h-6a2 2 0 0 1-2-2v-1"/>
820 </svg>
821 <?php echo esc_html__('Submit Support Ticket', 'mxchat'); ?>
822 </a>
823 </div>
824 </div>
825
826 <div class="tutorial-grid">
827 <div class="tutorial-item">
828 <h3><?php echo esc_html__('MxChat Forms Tutorial', 'mxchat'); ?></h3>
829 <div class="video-description">
830 <p><?php echo esc_html__('Learn how to create and manage smart forms that automatically trigger during chat conversations.', 'mxchat'); ?></p>
831 <a href="https://www.youtube.com/watch?v=3MrWy5dRalA" target="_blank" rel="noopener" class="video-link">
832 <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>
833 <?php echo esc_html__('Watch MxChat Forms Tutorial', 'mxchat'); ?>
834 </a>
835 </div>
836 </div>
837
838 <div class="tutorial-item">
839 <h3><?php echo esc_html__('Admin Assistant Add-on', 'mxchat'); ?></h3>
840 <div class="video-description">
841 <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>
842 <a href="https://youtu.be/AdEA1k-UCFM" target="_blank" rel="noopener" class="video-link">
843 <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>
844 <?php echo esc_html__('Watch Admin Assistant Tutorial', 'mxchat'); ?>
845 </a>
846 </div>
847 </div>
848
849 <div class="tutorial-item">
850 <h3><?php echo esc_html__('Intent Tester Guide', 'mxchat'); ?></h3>
851 <div class="video-description">
852 <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>
853 <a href="https://www.youtube.com/watch?v=uTr14tn59Hc" target="_blank" rel="noopener" class="video-link">
854 <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>
855 <?php echo esc_html__('Watch Intent Tester Tutorial', 'mxchat'); ?>
856 </a>
857 </div>
858 </div>
859
860 <div class="tutorial-item">
861 <h3><?php echo esc_html__('Theme Customizer Add-on', 'mxchat'); ?></h3>
862 <div class="video-description">
863 <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>
864 <a href="https://youtu.be/MfbB9mZi6ag" target="_blank" rel="noopener" class="video-link">
865 <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>
866 <?php echo esc_html__('Watch Theme Customizer Tutorial', 'mxchat'); ?>
867 </a>
868 </div>
869 </div>
870
871 <div class="tutorial-item">
872 <h3><?php echo esc_html__('WooCommerce Integration', 'mxchat'); ?></h3>
873 <div class="video-description">
874 <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>
875 <a href="https://www.youtube.com/watch?v=WsqAppHRGdA" target="_blank" rel="noopener" class="video-link">
876 <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>
877 <?php echo esc_html__('Watch WooCommerce Integration Tutorial', 'mxchat'); ?>
878 </a>
879 </div>
880 </div>
881
882 <div class="tutorial-item">
883 <h3><?php echo esc_html__('Knowledge Base Setup', 'mxchat'); ?></h3>
884 <div class="video-description">
885 <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>
886 <p><small><?php echo esc_html__('Note: This tutorial uses an older UI, but the process remains the same.', 'mxchat'); ?></small></p>
887 <a href="https://www.youtube.com/watch?v=8Ztjs66-VTo" target="_blank" rel="noopener" class="video-link">
888 <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>
889 <?php echo esc_html__('Watch Knowledge Base Setup Tutorial', 'mxchat'); ?>
890 </a>
891 </div>
892 </div>
893
894 <div class="tutorial-item">
895 <h3><?php echo esc_html__('Toolbar Chat with Documents', 'mxchat'); ?></h3>
896 <div class="video-description">
897 <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>
898 <a href="https://www.youtube.com/watch?v=j_c45WWCTG0" target="_blank" rel="noopener" class="video-link">
899 <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>
900 <?php echo esc_html__('Watch Document Chat Tutorial', 'mxchat'); ?>
901 </a>
902 </div>
903 </div>
904
905 <div class="tutorial-item">
906 <h3><?php echo esc_html__('MxChat Smart Recommender Tutorial', 'mxchat'); ?></h3>
907 <div class="video-description">
908 <p><?php echo esc_html__('Learn how to create intelligent recommendation flows that guide users to perfect matches based on their preferences.', 'mxchat'); ?></p>
909 <a href="https://www.youtube.com/watch?v=8te1KPa238g&t=1s" target="_blank" rel="noopener" class="video-link">
910 <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>
911 <?php echo esc_html__('Watch Smart Recommender Tutorial', 'mxchat'); ?>
912 </a>
913 </div>
914 </div>
915
916 <div class="tutorial-item">
917 <h3><?php echo esc_html__('Perplexity Integration', 'mxchat'); ?></h3>
918 <div class="video-description">
919 <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>
920 <a href="https://youtu.be/wpKkbt24-bo" target="_blank" rel="noopener" class="video-link">
921 <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>
922 <?php echo esc_html__('Watch Perplexity Integration Tutorial', 'mxchat'); ?>
923 </a>
924 </div>
925 </div>
926
927 <div class="tutorial-item">
928 <h3><?php echo esc_html__('Brave Search Intent', 'mxchat'); ?></h3>
929 <div class="video-description">
930 <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>
931 <a href="https://www.youtube.com/watch?v=7vDL5H7vToc" target="_blank" rel="noopener" class="video-link">
932 <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>
933 <?php echo esc_html__('Watch Brave Search Intent Tutorial', 'mxchat'); ?>
934 </a>
935 </div>
936 </div>
937
938 <div class="tutorial-item">
939 <h3><?php echo esc_html__('Loops Email Capture', 'mxchat'); ?></h3>
940 <div class="video-description">
941 <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>
942 <p><small><?php echo esc_html__('Note: This tutorial uses an older UI, but the process remains the same.', 'mxchat'); ?></small></p>
943 <a href="https://www.youtube.com/watch?v=CNgm5TYDyTc" target="_blank" rel="noopener" class="video-link">
944 <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>
945 <?php echo esc_html__('Watch Loops Email Capture Tutorial', 'mxchat'); ?>
946 </a>
947 </div>
948 </div>
949
950 <div class="tutorial-item">
951 <h3><?php echo esc_html__('MxChat AI Agent Testing Service', 'mxchat'); ?></h3>
952 <div class="video-description">
953 <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>
954 <p><small><?php echo esc_html__('Note: This tutorial uses an older UI, but the process remains the same.', 'mxchat'); ?></small></p>
955 <a href="https://www.youtube.com/watch?v=A0jowbpyX54" target="_blank" rel="noopener" class="video-link">
956 <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>
957 <?php echo esc_html__('Watch AI Agent Testing Tutorial', 'mxchat'); ?>
958 </a>
959 </div>
960 </div>
961 </div>
962
963 </div>
964 </div>
965 </div>
966 </div>
967 </div>
968 <?php
969 }
970
971
972 public function mxchat_create_transcripts_page() {
973 global $wpdb;
974 $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
975
976 // Get basic stats
977 $total_chats = $wpdb->get_var("SELECT COUNT(DISTINCT session_id) FROM $table_name");
978 $total_messages = $wpdb->get_var("SELECT COUNT(*) FROM $table_name");
979
980 // Count unique users with detailed breakdown
981 $total_users = $wpdb->get_var("
982 SELECT COUNT(DISTINCT
983 CASE
984 WHEN user_email != '' AND user_email IS NOT NULL THEN user_email
985 WHEN user_id != 0 THEN CONCAT('user_', user_id)
986 WHEN user_identifier NOT LIKE 'Tech-Savvy User'
987 AND user_identifier NOT LIKE 'Detail-Oriented User'
988 AND user_identifier NOT LIKE 'Language Learner'
989 AND user_identifier NOT LIKE 'Casual Browser'
990 AND user_identifier NOT LIKE 'Policy Enforcer'
991 AND user_identifier NOT LIKE 'Researcher'
992 AND user_identifier NOT LIKE 'Loyalty Member'
993 AND user_identifier NOT LIKE 'Gift Buyer'
994 AND user_identifier NOT LIKE 'Parent or Caregiver'
995 THEN user_identifier
996 ELSE session_id
997 END
998 )
999 FROM $table_name
1000 WHERE role != 'assistant'
1001 ");
1002
1003 // Get user type breakdown
1004 $registered_users = $wpdb->get_var("
1005 SELECT COUNT(DISTINCT user_email)
1006 FROM $table_name
1007 WHERE user_email != '' AND user_email IS NOT NULL
1008 ");
1009
1010 $guest_users = $wpdb->get_var("
1011 SELECT COUNT(DISTINCT user_identifier)
1012 FROM $table_name
1013 WHERE (user_email = '' OR user_email IS NULL)
1014 AND role != 'assistant'
1015 AND user_identifier NOT LIKE 'Tech-Savvy User'
1016 AND user_identifier NOT LIKE 'Detail-Oriented User'
1017 AND user_identifier NOT LIKE 'Language Learner'
1018 AND user_identifier NOT LIKE 'Casual Browser'
1019 AND user_identifier NOT LIKE 'Policy Enforcer'
1020 AND user_identifier NOT LIKE 'Researcher'
1021 AND user_identifier NOT LIKE 'Loyalty Member'
1022 AND user_identifier NOT LIKE 'Gift Buyer'
1023 AND user_identifier NOT LIKE 'Parent or Caregiver'
1024 ");
1025
1026 // Get agent test messages count
1027 $agent_tests = $wpdb->get_var("
1028 SELECT COUNT(DISTINCT session_id)
1029 FROM $table_name
1030 WHERE user_identifier IN (
1031 'Tech-Savvy User',
1032 'Detail-Oriented User',
1033 'Language Learner',
1034 'Casual Browser',
1035 'Policy Enforcer',
1036 'Researcher',
1037 'Loyalty Member',
1038 'Gift Buyer',
1039 'Parent or Caregiver'
1040 )
1041 ");
1042 ?>
1043 <div class="wrap mxchat-transcripts-wrapper">
1044 <!-- Hero Section -->
1045 <div class="mxchat-transcripts-hero">
1046 <h1 class="mxchat-main-title">
1047 Chat <span class="mxchat-gradient-text">Transcripts</span>
1048 </h1>
1049 <p class="mxchat-hero-subtitle">
1050 <?php esc_html_e('Review and manage your chatbot conversations with detailed message history.', 'mxchat'); ?>
1051 </p>
1052 </div>
1053 <div class="mxchat-content">
1054 <!-- Stats Cards -->
1055 <div class="mxchat-stats-grid">
1056 <div class="mxchat-stat-card">
1057 <div class="stat-icon">💬</div>
1058 <div class="stat-content">
1059 <span class="stat-value"><?php echo esc_html($total_chats); ?></span>
1060 <span class="stat-label"><?php esc_html_e('Total Chats', 'mxchat'); ?></span>
1061 </div>
1062 </div>
1063 <div class="mxchat-stat-card">
1064 <div class="stat-icon">📝</div>
1065 <div class="stat-content">
1066 <span class="stat-value"><?php echo esc_html($total_messages); ?></span>
1067 <span class="stat-label"><?php esc_html_e('Total Messages', 'mxchat'); ?></span>
1068 </div>
1069 </div>
1070 <div class="mxchat-stat-card">
1071 <div class="stat-icon">👥</div>
1072 <div class="stat-content">
1073 <span class="stat-value"><?php echo esc_html($total_users); ?></span>
1074 <span class="stat-label"><?php esc_html_e('Unique Users', 'mxchat'); ?></span>
1075 <span class="stat-sublabel">
1076 <?php
1077 echo sprintf(
1078 esc_html__('%d registered, %d guests, %d agent tests', 'mxchat'),
1079 $registered_users,
1080 $guest_users,
1081 $agent_tests
1082 );
1083 ?>
1084 </span>
1085 </div>
1086 </div>
1087 </div>
1088
1089 <!-- Search and Filter Controls with Notification Settings Button -->
1090 <div class="mxchat-controls-wrapper">
1091 <div class="mxchat-search-box">
1092 <input type="text" id="mxchat-search-transcripts"
1093 placeholder="<?php esc_attr_e('Search transcripts...', 'mxchat'); ?>"
1094 class="regular-text">
1095 </div>
1096 <form id="mxchat-delete-form" method="post">
1097 <?php wp_nonce_field('mxchat_delete_chat_history', 'mxchat_delete_chat_nonce'); ?>
1098 <div class="mxchat-controls">
1099 <button type="button" id="mxchat-chat-email-notification-btn" class="mxchat-action-button">
1100 <span class="dashicons dashicons-email"></span>
1101 <?php esc_html_e('Notification Settings', 'mxchat'); ?>
1102 </button>
1103 <button type="button" id="mxchat-export-transcripts" class="mxchat-action-button">
1104 <span class="dashicons dashicons-download"></span>
1105 <?php esc_html_e('Export All Chats', 'mxchat'); ?>
1106 </button>
1107 <button type="button" id="mxchat-select-all-transcripts" class="mxchat-select-button">
1108 <span class="dashicons dashicons-yes-alt"></span>
1109 <span class="button-text"><?php esc_html_e('Select All', 'mxchat'); ?></span>
1110 </button>
1111 <button type="submit" class="button delete-chats-button">
1112 <span class="dashicons dashicons-trash"></span>
1113 <?php esc_html_e('Delete Selected', 'mxchat'); ?>
1114 </button>
1115 </div>
1116 </form>
1117 </div>
1118
1119 <!-- Transcripts Container -->
1120 <div id="mxchat-transcripts"></div>
1121 </div>
1122 </div>
1123
1124 <!-- Modal for Chat Email Notification Settings -->
1125 <div id="mxchat-chat-email-notification-modal" class="mxchat-chat-notification-modal-overlay" style="display: none;">
1126 <div class="mxchat-chat-notification-modal-content">
1127 <div class="mxchat-chat-notification-modal-header">
1128 <h2><?php esc_html_e('Email Notification Settings', 'mxchat'); ?></h2>
1129 <button type="button" class="mxchat-chat-notification-modal-close">&times;</button>
1130 </div>
1131 <div class="mxchat-chat-notification-modal-body">
1132 <form method="post" action="options.php" id="mxchat-chat-email-notification-form">
1133 <?php
1134 settings_fields('mxchat_transcripts_options');
1135 do_settings_sections('mxchat-transcripts');
1136 ?>
1137 <div class="mxchat-chat-notification-modal-footer">
1138 <button type="submit" class="button button-primary">
1139 <?php esc_html_e('Save Notification Settings', 'mxchat'); ?>
1140 </button>
1141 <button type="button" class="button mxchat-chat-notification-modal-cancel">
1142 <?php esc_html_e('Cancel', 'mxchat'); ?>
1143 </button>
1144 </div>
1145 </form>
1146 </div>
1147 </div>
1148 </div>
1149 <?php
1150 }
1151 public function mxchat_transcripts_notification_section_callback() {
1152 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>';
1153 }
1154 public function mxchat_enable_notifications_callback() {
1155 $options = get_option('mxchat_transcripts_options', array());
1156 $enabled = isset($options['mxchat_enable_notifications']) ? $options['mxchat_enable_notifications'] : 0;
1157 ?>
1158 <label for="mxchat_enable_notifications">
1159 <input type="checkbox" id="mxchat_enable_notifications"
1160 name="mxchat_transcripts_options[mxchat_enable_notifications]"
1161 value="1" <?php checked(1, $enabled); ?>>
1162 <?php esc_html_e('Send email notification when a new chat session starts', 'mxchat'); ?>
1163 </label>
1164 <p class="description">
1165 <?php esc_html_e('Enable this option to receive email notifications for new chat sessions.', 'mxchat'); ?>
1166 </p>
1167 <?php
1168 }
1169 public function mxchat_notification_email_callback() {
1170 $options = get_option('mxchat_transcripts_options', array());
1171 $email = isset($options['mxchat_notification_email']) ? $options['mxchat_notification_email'] : get_option('admin_email');
1172 ?>
1173 <input type="email" id="mxchat_notification_email"
1174 name="mxchat_transcripts_options[mxchat_notification_email]"
1175 value="<?php echo esc_attr($email); ?>"
1176 class="regular-text">
1177 <p class="description">
1178 <?php esc_html_e('Enter the email address where notifications should be sent. Defaults to the admin email address.', 'mxchat'); ?>
1179 </p>
1180 <?php
1181 }
1182 public function sanitize_transcripts_options($input) {
1183 $sanitized = array();
1184
1185 $sanitized['mxchat_enable_notifications'] = isset($input['mxchat_enable_notifications']) ? 1 : 0;
1186
1187 if (isset($input['mxchat_notification_email'])) {
1188 $sanitized['mxchat_notification_email'] = sanitize_email($input['mxchat_notification_email']);
1189 if (!is_email($sanitized['mxchat_notification_email'])) {
1190 add_settings_error(
1191 'mxchat_transcripts_options',
1192 'invalid_email',
1193 __('Please enter a valid email address for notifications.', 'mxchat'),
1194 'error'
1195 );
1196 $sanitized['mxchat_notification_email'] = get_option('admin_email');
1197 }
1198 }
1199
1200 return $sanitized;
1201 }
1202 public function export_chat_transcripts() {
1203 if (!current_user_can('manage_options')) {
1204 wp_die(esc_html__('You do not have sufficient permissions to access this page.', 'mxchat'));
1205 }
1206
1207 check_ajax_referer('mxchat_export_transcripts', 'security');
1208
1209 global $wpdb;
1210 $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
1211
1212 // Get all transcripts ordered by session and timestamp
1213 $results = $wpdb->get_results(
1214 "SELECT session_id, user_email, user_identifier, role, message, timestamp
1215 FROM {$table_name}
1216 ORDER BY session_id, timestamp ASC"
1217 );
1218
1219 if (empty($results)) {
1220 wp_send_json_error(array('message' => 'No transcripts found.'));
1221 wp_die();
1222 }
1223
1224 // Set headers for CSV download
1225 header('Content-Type: text/csv');
1226 header('Content-Disposition: attachment; filename="chat-transcripts-' . date('Y-m-d') . '.csv"');
1227 header('Pragma: no-cache');
1228 header('Expires: 0');
1229
1230 // Create output stream
1231 $output = fopen('php://output', 'w');
1232
1233 // Add UTF-8 BOM for proper Excel encoding
1234 fputs($output, "\xEF\xBB\xBF");
1235
1236 // Add CSV headers
1237 fputcsv($output, array(
1238 'Session ID',
1239 'Email',
1240 'User Identifier',
1241 'Role',
1242 'Message',
1243 'Timestamp'
1244 ));
1245
1246 // Add data rows
1247 foreach ($results as $row) {
1248 fputcsv($output, array(
1249 $row->session_id,
1250 $row->user_email,
1251 $row->user_identifier,
1252 $row->role,
1253 $row->message,
1254 $row->timestamp
1255 ));
1256 }
1257
1258 fclose($output);
1259 wp_die();
1260 }
1261 public function mxchat_fetch_chat_history() {
1262 global $wpdb;
1263 $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
1264
1265 if (!current_user_can('manage_options')) {
1266 wp_die(esc_html__('You do not have sufficient permissions to view this page.', 'mxchat'));
1267 }
1268
1269 // Get pagination parameters
1270 $page = isset($_POST['page']) ? absint($_POST['page']) : 1;
1271 $per_page = isset($_POST['per_page']) ? absint($_POST['per_page']) : 50;
1272 $offset = ($page - 1) * $per_page;
1273
1274 // Get search parameter if any
1275 $search = isset($_POST['search']) ? sanitize_text_field($_POST['search']) : '';
1276
1277 // Build the query based on whether we have a search term
1278 $search_condition = '';
1279 $search_params = array();
1280
1281 if (!empty($search)) {
1282 $search_condition = "WHERE (
1283 session_id LIKE %s
1284 OR user_email LIKE %s
1285 OR user_identifier LIKE %s
1286 OR message LIKE %s
1287 )";
1288 $search_params = array(
1289 '%' . $wpdb->esc_like($search) . '%',
1290 '%' . $wpdb->esc_like($search) . '%',
1291 '%' . $wpdb->esc_like($search) . '%',
1292 '%' . $wpdb->esc_like($search) . '%'
1293 );
1294 }
1295
1296 // First count total sessions for pagination
1297 if (!empty($search)) {
1298 $count_query = $wpdb->prepare(
1299 "SELECT COUNT(DISTINCT session_id)
1300 FROM {$table_name}
1301 {$search_condition}",
1302 $search_params
1303 );
1304 } else {
1305 $count_query = "SELECT COUNT(DISTINCT session_id) FROM {$table_name}";
1306 }
1307
1308 $total_sessions = $wpdb->get_var($count_query);
1309
1310 // Get paginated session IDs ordered by most recent message in each session
1311 if (!empty($search)) {
1312 $session_query = $wpdb->prepare(
1313 "SELECT DISTINCT t.session_id
1314 FROM {$table_name} t
1315 {$search_condition}
1316 GROUP BY t.session_id
1317 ORDER BY MAX(t.timestamp) DESC
1318 LIMIT %d OFFSET %d",
1319 array_merge($search_params, array($per_page, $offset))
1320 );
1321 } else {
1322 $session_query = $wpdb->prepare(
1323 "SELECT DISTINCT session_id
1324 FROM {$table_name}
1325 GROUP BY session_id
1326 ORDER BY MAX(timestamp) DESC
1327 LIMIT %d OFFSET %d",
1328 $per_page, $offset
1329 );
1330 }
1331
1332 $session_ids = $wpdb->get_col($session_query);
1333
1334 if (empty($session_ids)) {
1335 ob_start();
1336 echo '<div class="mxchat-no-results">';
1337 echo esc_html__('No chat history found.', 'mxchat');
1338 if (!empty($search)) {
1339 echo ' ' . esc_html__('Try adjusting your search criteria.', 'mxchat');
1340 }
1341 echo '</div>';
1342 $output = ob_get_clean();
1343
1344 wp_send_json(array(
1345 'html' => $output,
1346 'page' => $page,
1347 'total_pages' => 0,
1348 'total_sessions' => 0
1349 ));
1350
1351 wp_die();
1352 }
1353
1354 ob_start();
1355 echo '<div class="mxchat-transcript">';
1356
1357 // Iterate through sessions from newest to oldest
1358 foreach ($session_ids as $session_id) {
1359 // Get the email associated with this session (if available)
1360 $email = $wpdb->get_var(
1361 $wpdb->prepare(
1362 "SELECT user_email
1363 FROM {$table_name}
1364 WHERE session_id = %s AND user_email != ''
1365 ORDER BY timestamp ASC
1366 LIMIT 1",
1367 $session_id
1368 )
1369 );
1370
1371 // Get messages for this session ordered by timestamp
1372 $messages = $wpdb->get_results(
1373 $wpdb->prepare(
1374 "SELECT * FROM {$table_name}
1375 WHERE session_id = %s
1376 ORDER BY timestamp ASC",
1377 $session_id
1378 )
1379 );
1380
1381 // Start session block
1382 echo '<div class="mxchat-session">';
1383 echo '<div class="mxchat-session-header">';
1384 // Wrap checkbox and session ID in one block
1385 echo '<div class="mxchat-session-id">';
1386 echo '<input type="checkbox" name="delete_session_ids[]" value="' . esc_attr($session_id) . '"> ';
1387 echo '<strong>' . esc_html__('Session ID:', 'mxchat') . '</strong> ' . esc_html($session_id);
1388 echo '</div>';
1389
1390 // Place email directly below the session ID block
1391 if (!empty($email)) {
1392 echo '<div class="mxchat-session-email">';
1393 echo '<strong>' . esc_html__('Email:', 'mxchat') . '</strong> ' . esc_html($email);
1394 echo '</div>';
1395 }
1396 echo '</div>';
1397
1398 echo '<div class="mxchat-messages">';
1399
1400 // Display messages for this session
1401 foreach ($messages as $transcript) {
1402 $formatted_timestamp = date_i18n('F j, Y g:i a', strtotime($transcript->timestamp));
1403
1404 // Determine message styling
1405 switch ($transcript->role) {
1406 case 'assistant':
1407 case 'bot':
1408 $message_class = 'bot-message';
1409 $display_role = esc_html__('Chatbot', 'mxchat');
1410 break;
1411 case 'user':
1412 $message_class = 'user-message';
1413 $display_role = !empty($transcript->user_identifier)
1414 ? sanitize_text_field($transcript->user_identifier)
1415 : esc_html__('User', 'mxchat');
1416 break;
1417 case 'agent':
1418 $message_class = 'agent-message';
1419 $display_role = esc_html__('Agent', 'mxchat');
1420 break;
1421 default:
1422 $message_class = 'unknown-message';
1423 $display_role = esc_html__('Unknown', 'mxchat');
1424 }
1425
1426 // Process message content
1427 $message_content = wp_kses(
1428 stripslashes($transcript->message),
1429 [
1430 'b' => [], 'strong' => [], 'i' => [], 'em' => [], 'u' => [],
1431 'br' => [], 'p' => [], 'ul' => [], 'ol' => [], 'li' => [],
1432 'a' => ['href' => [], 'title' => []]
1433 ]
1434 );
1435 $message_content = nl2br($message_content);
1436
1437 // Render message
1438 echo '<div class="mxchat-message ' . esc_attr($message_class) . '">';
1439 echo '<div class="mxchat-message-header">' . esc_html($display_role) . '</div>';
1440 echo '<div class="mxchat-message-content">' . $message_content . '</div>';
1441 echo '<div class="mxchat-timestamp">' . esc_html($formatted_timestamp) . '</div>';
1442 echo '</div>';
1443 }
1444
1445 // Close session block
1446 echo '</div></div>';
1447 }
1448
1449 echo '</div>';
1450
1451 // Add pagination info and controls
1452 $total_pages = ceil($total_sessions / $per_page);
1453
1454 echo '<div class="mxchat-pagination">';
1455 echo '<div class="mxchat-pagination-info">';
1456 echo sprintf(
1457 esc_html__('Showing %1$d to %2$d of %3$d sessions', 'mxchat'),
1458 $offset + 1,
1459 min($offset + $per_page, $total_sessions),
1460 $total_sessions
1461 );
1462 echo '</div>';
1463
1464 if ($total_pages > 1) {
1465 echo '<div class="mxchat-pagination-controls">';
1466
1467 // Previous page button
1468 if ($page > 1) {
1469 echo '<button class="mxchat-pagination-button" data-page="' . ($page - 1) . '">&laquo; ' . esc_html__('Previous', 'mxchat') . '</button>';
1470 }
1471
1472 // Page numbers
1473 $start_page = max(1, $page - 2);
1474 $end_page = min($total_pages, $page + 2);
1475
1476 if ($start_page > 1) {
1477 echo '<button class="mxchat-pagination-button" data-page="1">1</button>';
1478 if ($start_page > 2) {
1479 echo '<span class="mxchat-pagination-ellipsis">...</span>';
1480 }
1481 }
1482
1483 for ($i = $start_page; $i <= $end_page; $i++) {
1484 $class = $i === $page ? 'mxchat-pagination-button active' : 'mxchat-pagination-button';
1485 echo '<button class="' . $class . '" data-page="' . $i . '">' . $i . '</button>';
1486 }
1487
1488 if ($end_page < $total_pages) {
1489 if ($end_page < $total_pages - 1) {
1490 echo '<span class="mxchat-pagination-ellipsis">...</span>';
1491 }
1492 echo '<button class="mxchat-pagination-button" data-page="' . $total_pages . '">' . $total_pages . '</button>';
1493 }
1494
1495 // Next page button
1496 if ($page < $total_pages) {
1497 echo '<button class="mxchat-pagination-button" data-page="' . ($page + 1) . '">' . esc_html__('Next', 'mxchat') . ' &raquo;</button>';
1498 }
1499
1500 echo '</div>';
1501 }
1502 echo '</div>';
1503
1504 $output = ob_get_clean();
1505
1506 wp_send_json(array(
1507 'html' => $output,
1508 'page' => $page,
1509 'total_pages' => $total_pages,
1510 'total_sessions' => $total_sessions
1511 ));
1512
1513 wp_die();
1514 }
1515
1516
1517
1518 public function mxchat_create_prompts_page() {
1519 global $wpdb;
1520 $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
1521
1522 // Display success message if all prompts were deleted
1523 if (isset($_GET['all_deleted']) && $_GET['all_deleted'] === 'true') {
1524 echo '<div class="notice notice-success is-dismissible"><p>' . esc_html__('All knowledge has been deleted successfully.', 'mxchat') . '</p></div>';
1525 }
1526
1527 // Set up pagination and search query
1528 $nonce = isset($_GET['_wpnonce']) ? sanitize_text_field($_GET['_wpnonce']) : '';
1529 $search_query = (!empty($nonce) && wp_verify_nonce($nonce, 'mxchat_prompts_search_nonce') && isset($_GET['search'])) ? sanitize_text_field($_GET['search']) : '';
1530 $current_page = isset($_GET['paged']) ? absint($_GET['paged']) : 1;
1531 $per_page = 10;
1532
1533 // ================================
1534 // REPLACE THIS SECTION WITH UNIFIED TABLE LOGIC
1535 // ================================
1536
1537 // Get Pinecone settings to determine data source
1538 $pinecone_options = get_option('mxchat_pinecone_addon_options', array());
1539 $use_pinecone = ($pinecone_options['mxchat_use_pinecone'] ?? '0') === '1';
1540
1541 // Determine data source and fetch records accordingly
1542 if ($use_pinecone && !empty($pinecone_options['mxchat_pinecone_api_key'])) {
1543 // PINECONE DATA SOURCE
1544 $data_source = 'pinecone';
1545 $records = $this->fetch_pinecone_records($pinecone_options, $search_query, $current_page, $per_page);
1546 $total_records = $records['total'] ?? 0;
1547 $prompts = $records['data'] ?? array();
1548 $total_pages = ceil($total_records / $per_page);
1549 } else {
1550 // WORDPRESS DB DATA SOURCE (your existing logic)
1551 $data_source = 'wordpress';
1552
1553 $offset = ($current_page - 1) * $per_page;
1554
1555 // Modify query to handle search input
1556 $sql_search = "";
1557 if ($search_query) {
1558 $sql_search = $wpdb->prepare("WHERE article_content LIKE %s", '%' . $wpdb->esc_like($search_query) . '%');
1559 }
1560
1561 // Retrieve total number of prompts, considering search filter
1562 $total_records = $wpdb->get_var("SELECT COUNT(*) FROM {$table_name} {$sql_search}");
1563 $total_pages = ceil($total_records / $per_page);
1564
1565 // Retrieve prompts from the database
1566 $prompts = $wpdb->get_results(
1567 $wpdb->prepare(
1568 "SELECT * FROM {$table_name} {$sql_search} ORDER BY timestamp DESC LIMIT %d OFFSET %d",
1569 $per_page,
1570 $offset
1571 )
1572 );
1573 }
1574
1575 // Add the pagination links generation here
1576 $page_links = '';
1577 if ($total_pages > 1) {
1578 $page_links = paginate_links(array(
1579 'base' => add_query_arg(array(
1580 'paged' => '%#%',
1581 'search' => urlencode($search_query),
1582 '_wpnonce' => wp_create_nonce('mxchat_prompts_search_nonce')
1583 ), admin_url('admin.php?page=mxchat-prompts')),
1584 'format' => '',
1585 'prev_text' => __('&laquo; Previous', 'mxchat'),
1586 'next_text' => __('Next &raquo;', 'mxchat'),
1587 'total' => $total_pages,
1588 'current' => $current_page,
1589 ));
1590 }
1591
1592 // ================================
1593 // END OF REPLACEMENT SECTION
1594 // ================================
1595
1596 // Retrieve processing statuses
1597 $pdf_url = get_transient('mxchat_last_pdf_url');
1598 $sitemap_url = get_transient('mxchat_last_sitemap_url');
1599 $pdf_status = $pdf_url ? $this->get_pdf_processing_status($pdf_url) : false;
1600 $sitemap_status = $sitemap_url ? $this->get_sitemap_processing_status($sitemap_url) : false;
1601
1602 if ($pdf_status && $pdf_status['status'] === 'complete') {
1603 delete_transient('mxchat_last_pdf_url');
1604 $pdf_status = false;
1605 }
1606 if ($sitemap_status && $sitemap_status['status'] === 'complete') {
1607 delete_transient('mxchat_last_sitemap_url');
1608 $sitemap_status = false;
1609 }
1610
1611 $is_processing = ($pdf_status && ($pdf_status['status'] === 'processing' || $pdf_status['status'] === 'error'))
1612 || ($sitemap_status && ($sitemap_status['status'] === 'processing' || $sitemap_status['status'] === 'error'));
1613 ?>
1614
1615 <div class="wrap mxchat-wrapper">
1616 <!-- Hero Section -->
1617 <div class="mxchat-hero">
1618 <h1 class="mxchat-main-title">
1619 <span class="mxchat-gradient-text">Knowledge Base</span> Manager
1620 </h1>
1621 <p class="mxchat-hero-subtitle">
1622 <?php esc_html_e('Enhance your AI chatbot with custom knowledge. Import, manage, and organize your content to keep responses accurate and relevant.', 'mxchat'); ?>
1623 </p>
1624 </div>
1625
1626 <div class="mxchat-content">
1627
1628
1629 <!-- Tab Navigation -->
1630 <div class="mxchat-kb-tabs-nav">
1631 <button class="mxchat-kb-tab-button active" data-tab="import">
1632 <?php esc_html_e('Knowledge Import', 'mxchat'); ?>
1633 </button>
1634 <button class="mxchat-kb-tab-button" data-tab="sync">
1635 <?php esc_html_e('Auto-Sync Settings', 'mxchat'); ?>
1636 </button>
1637 <button class="mxchat-kb-tab-button" data-tab="pinecone">
1638 <?php esc_html_e('Pinecone Settings', 'mxchat'); ?>
1639 </button>
1640 </div>
1641
1642
1643 <div class="mxchat-kb-tabs-content">
1644 <div id="mxchat-kb-tab-import" class="mxchat-kb-tab-content active">
1645 <!-- Import Options Card -->
1646 <div class="mxchat-card">
1647
1648 <h2><?php esc_html_e('Knowledge Import Settings', 'mxchat'); ?></h2>
1649
1650 <?php
1651 // Check if the appropriate embedding API key exists
1652 $embedding_model = isset($this->options['embedding_model']) ? esc_attr($this->options['embedding_model']) : 'text-embedding-ada-002';
1653 $has_openai_key = !empty($this->options['api_key']);
1654 $has_voyage_key = !empty($this->options['voyage_api_key']);
1655 $has_gemini_key = !empty($this->options['gemini_api_key']);
1656
1657 // Determine if they have the needed API key for their selected embedding model
1658 $has_required_key = false;
1659 $required_key_type = '';
1660
1661 if (strpos($embedding_model, 'text-embedding-') !== false && $has_openai_key) {
1662 $has_required_key = true;
1663 $required_key_type = 'OpenAI';
1664 } elseif (strpos($embedding_model, 'voyage-') !== false && $has_voyage_key) {
1665 $has_required_key = true;
1666 $required_key_type = 'Voyage AI';
1667 } elseif (strpos($embedding_model, 'gemini-embedding-') !== false && $has_gemini_key) {
1668 $has_required_key = true;
1669 $required_key_type = 'Google Gemini';
1670 } elseif (strpos($embedding_model, 'text-embedding-') !== false) {
1671 $required_key_type = 'OpenAI';
1672 } elseif (strpos($embedding_model, 'voyage-') !== false) {
1673 $required_key_type = 'Voyage AI';
1674 } elseif (strpos($embedding_model, 'gemini-embedding-') !== false) {
1675 $required_key_type = 'Google Gemini';
1676 }
1677 ?>
1678
1679 <div class="mxchat-knowledge-warning <?php echo $has_required_key ? 'success' : 'warning'; ?>">
1680 <?php if ($has_required_key): ?>
1681 <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>
1682 <?php else: ?>
1683 <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>
1684 <?php endif; ?>
1685 </div>
1686
1687
1688
1689 <!-- Import Options Section -->
1690 <div class="mxchat-import-section">
1691 <h3><?php esc_html_e('Import Options', 'mxchat'); ?></h3>
1692
1693 <!-- Import Options Grid -->
1694 <div class="mxchat-import-options">
1695 <!-- WordPress Import Option -->
1696 <button type="button" id="mxchat-open-content-selector" class="mxchat-import-box mxchat-import-wordpress" data-option="wordpress">
1697 <div class="mxchat-import-icon">
1698 <span class="dashicons dashicons-wordpress"></span>
1699 </div>
1700 <div class="mxchat-import-content">
1701 <h4><?php esc_html_e('WordPress Content', 'mxchat'); ?></h4>
1702 <p><?php esc_html_e('Import specific posts and pages to your knowledge base.', 'mxchat'); ?></p>
1703 </div>
1704 <div class="mxchat-recommended-tag"><?php esc_html_e('Recommended', 'mxchat'); ?></div>
1705 </button>
1706
1707 <!-- PDF Import Option -->
1708 <button type="button" class="mxchat-import-box" data-option="pdf" data-placeholder="<?php esc_attr_e('Enter PDF URL here', 'mxchat'); ?>" data-type="pdf">
1709 <div class="mxchat-import-icon">
1710 <span class="dashicons dashicons-media-document"></span>
1711 </div>
1712 <div class="mxchat-import-content">
1713 <h4><?php esc_html_e('PDF Import', 'mxchat'); ?></h4>
1714 <p><?php esc_html_e('Import knowledge from PDF documents.', 'mxchat'); ?></p>
1715 </div>
1716 </button>
1717
1718 <!-- Sitemap Import Option -->
1719 <button type="button" class="mxchat-import-box" data-option="sitemap" data-placeholder="<?php esc_attr_e('Enter sitemap URL here', 'mxchat'); ?>" data-type="sitemap">
1720 <div class="mxchat-import-icon">
1721 <span class="dashicons dashicons-admin-site-alt"></span>
1722 </div>
1723 <div class="mxchat-import-content">
1724 <h4><?php esc_html_e('Sitemap Import', 'mxchat'); ?></h4>
1725 <p><?php esc_html_e('Use a content-specific sub-sitemap, not the sitemap index.', 'mxchat'); ?></p>
1726 </div>
1727 </button>
1728
1729 <!-- Direct URL Import Option -->
1730 <button type="button" class="mxchat-import-box" data-option="url" data-placeholder="<?php esc_attr_e('Enter webpage URL here', 'mxchat'); ?>" data-type="url">
1731 <div class="mxchat-import-icon">
1732 <span class="dashicons dashicons-admin-links"></span>
1733 </div>
1734 <div class="mxchat-import-content">
1735 <h4><?php esc_html_e('Direct URL', 'mxchat'); ?></h4>
1736 <p><?php esc_html_e('Import content from any webpage.', 'mxchat'); ?></p>
1737 </div>
1738 </button>
1739
1740 <!-- Direct Content Import Option -->
1741 <button type="button" class="mxchat-import-box" data-option="content" data-type="content">
1742 <div class="mxchat-import-icon">
1743 <span class="dashicons dashicons-editor-paste-text"></span>
1744 </div>
1745 <div class="mxchat-import-content">
1746 <h4><?php esc_html_e('Direct Content', 'mxchat'); ?></h4>
1747 <p><?php esc_html_e('Submit content to be vectorized.', 'mxchat'); ?></p>
1748 </div>
1749 </button>
1750 </div>
1751
1752 <!-- Input Areas for URL and Content -->
1753 <div class="mxchat-import-input-area" id="mxchat-url-input-area" style="display: none;">
1754 <?php if (!$is_processing) : ?>
1755 <form id="mxchat-url-form" method="post" action="<?php echo esc_url(admin_url('admin-post.php?action=mxchat_submit_sitemap')); ?>">
1756 <?php wp_nonce_field('mxchat_submit_sitemap_action', 'mxchat_submit_sitemap_nonce'); ?>
1757 <input type="hidden" name="import_type" id="import_type" value="url">
1758 <div class="mxchat-url-input-group">
1759 <input type="url"
1760 name="sitemap_url"
1761 id="sitemap_url"
1762 placeholder="<?php esc_attr_e('Enter URL here', 'mxchat'); ?>"
1763 required />
1764 <button type="submit"
1765 name="submit_sitemap"
1766 class="mxchat-button-primary">
1767 <?php esc_html_e('Import', 'mxchat'); ?>
1768 </button>
1769 </div>
1770 <p class="mxchat-url-description" id="url-description-text"></p>
1771 </form>
1772 <?php endif; ?>
1773 </div>
1774
1775 <div class="mxchat-import-input-area" id="mxchat-content-input-area" style="display: none;">
1776 <?php if (!$is_processing) : ?>
1777 <form id="mxchat-content-form" method="post" action="<?php echo esc_url(admin_url('admin-post.php?action=mxchat_submit_content')); ?>">
1778 <?php wp_nonce_field('mxchat_submit_content_action', 'mxchat_submit_content_nonce'); ?>
1779 <div class="mxchat-form-group">
1780 <textarea
1781 name="article_content"
1782 id="article_content"
1783 placeholder="<?php esc_attr_e('Enter your content here...', 'mxchat'); ?>"
1784 required
1785 rows="6"
1786 ></textarea>
1787 </div>
1788 <div class="mxchat-form-group">
1789 <input type="url"
1790 name="article_url"
1791 id="article_url"
1792 placeholder="<?php esc_attr_e('Enter source URL (Optional)', 'mxchat'); ?>">
1793 </div>
1794 <button type="submit"
1795 name="submit_content"
1796 class="mxchat-button-primary">
1797 <?php esc_html_e('Import Content', 'mxchat'); ?>
1798 </button>
1799 </form>
1800 <?php endif; ?>
1801 </div>
1802 </div>
1803
1804 <!-- Processing Status -->
1805 <?php if ($pdf_status && $pdf_status['status'] !== 'complete') : ?>
1806 <div class="mxchat-status-card">
1807 <div class="mxchat-status-header">
1808 <h4><?php esc_html_e('PDF Processing Status', 'mxchat'); ?></h4>
1809 <?php if ($is_processing) : ?>
1810 <form method="post" class="mxchat-stop-form"
1811 action="<?php echo esc_url(admin_url('admin-post.php?action=mxchat_stop_processing')); ?>">
1812 <?php wp_nonce_field('mxchat_stop_processing_action', 'mxchat_stop_processing_nonce'); ?>
1813 <button type="submit" name="stop_processing" class="mxchat-button-secondary">
1814 <?php esc_html_e('Stop Processing', 'mxchat'); ?>
1815 </button>
1816 </form>
1817 <?php endif; ?>
1818
1819 <?php if ($pdf_status['status'] === 'error') : ?>
1820 <span class="mxchat-status-badge mxchat-status-failed"><?php esc_html_e('Error', 'mxchat'); ?></span>
1821 <?php endif; ?>
1822 </div>
1823 <div class="mxchat-progress-bar">
1824 <div class="mxchat-progress-fill" style="width: <?php echo esc_attr($pdf_status['percentage']); ?>%"></div>
1825 </div>
1826 <div class="mxchat-status-details">
1827 <p><?php printf(
1828 esc_html__('Progress: %1$d of %2$d pages (%3$d%%)', 'mxchat'),
1829 absint($pdf_status['processed_pages']),
1830 absint($pdf_status['total_pages']),
1831 absint($pdf_status['percentage'])
1832 ); ?></p>
1833 <p><?php printf(
1834 esc_html__('Status: %s', 'mxchat'),
1835 esc_html(ucfirst($pdf_status['status']))
1836 ); ?></p>
1837 <p><?php printf(
1838 esc_html__('Last update: %s', 'mxchat'),
1839 esc_html($pdf_status['last_update'])
1840 ); ?></p>
1841
1842 <?php if (!empty($pdf_status['error'])) : ?>
1843 <div class="mxchat-error-notice">
1844 <p class="error"><?php echo esc_html($pdf_status['error']); ?></p>
1845 </div>
1846 <?php endif; ?>
1847 </div>
1848 </div>
1849 <?php endif; ?>
1850
1851 <!-- Single URL Submission Status -->
1852 <?php
1853 // Get single URL status
1854 $single_url_status = $this->get_single_url_status();
1855 $is_active_processing =
1856 ($sitemap_status && $sitemap_status['status'] === 'processing') ||
1857 ($pdf_status && $pdf_status['status'] === 'processing');
1858 ?>
1859
1860 <div id="mxchat-single-url-status-container" <?php echo $is_active_processing ? 'style="display:none;"' : ''; ?>>
1861 <?php if ($single_url_status && !$is_active_processing) : ?>
1862 <div class="mxchat-status-card">
1863 <div class="mxchat-status-header">
1864 <h4><?php esc_html_e('Last URL Submission', 'mxchat'); ?></h4>
1865 <?php if ($single_url_status['status'] === 'failed') : ?>
1866 <span class="mxchat-status-badge mxchat-status-failed"><?php esc_html_e('Failed', 'mxchat'); ?></span>
1867 <?php else : ?>
1868 <span class="mxchat-status-badge mxchat-status-success"><?php esc_html_e('Success', 'mxchat'); ?></span>
1869 <?php endif; ?>
1870 </div>
1871 <div class="mxchat-status-details">
1872 <p><strong><?php esc_html_e('URL:', 'mxchat'); ?></strong>
1873 <a href="<?php echo esc_url($single_url_status['url']); ?>" target="_blank">
1874 <?php echo esc_html(strlen($single_url_status['url']) > 60 ? substr($single_url_status['url'], 0, 57) . '...' : $single_url_status['url']); ?>
1875 </a>
1876 </p>
1877 <p><strong><?php esc_html_e('Submitted:', 'mxchat'); ?></strong> <?php echo esc_html($single_url_status['human_time']); ?></p>
1878
1879 <?php if ($single_url_status['status'] === 'failed' && !empty($single_url_status['error'])) : ?>
1880 <div class="mxchat-error-notice">
1881 <p class="error"><?php echo esc_html($single_url_status['error']); ?></p>
1882 </div>
1883 <?php endif; ?>
1884
1885 <?php if ($single_url_status['status'] === 'complete') : ?>
1886 <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>
1887 <p><strong><?php esc_html_e('Embedding Dimensions:', 'mxchat'); ?></strong> <?php echo esc_html($single_url_status['embedding_dimensions']); ?></p>
1888 <?php endif; ?>
1889 </div>
1890 </div>
1891 <?php endif; ?>
1892 </div>
1893
1894 <!-- Sitemap Processing Status -->
1895 <?php if ($sitemap_status && $sitemap_status['status'] !== 'complete') : ?>
1896 <div class="mxchat-status-card">
1897 <div class="mxchat-status-header">
1898 <h4><?php esc_html_e('Sitemap Processing Status', 'mxchat'); ?></h4>
1899 <?php if ($is_processing) : ?>
1900 <form method="post" class="mxchat-stop-form"
1901 action="<?php echo esc_url(admin_url('admin-post.php?action=mxchat_stop_processing')); ?>">
1902 <?php wp_nonce_field('mxchat_stop_processing_action', 'mxchat_stop_processing_nonce'); ?>
1903 <button type="submit" name="stop_processing" class="mxchat-button-secondary">
1904 <?php esc_html_e('Stop Processing', 'mxchat'); ?>
1905 </button>
1906 </form>
1907 <?php endif; ?>
1908 </div>
1909 <div class="mxchat-progress-bar">
1910 <div class="mxchat-progress-fill" style="width: <?php echo esc_attr($sitemap_status['percentage']); ?>%"></div>
1911 </div>
1912 <div class="mxchat-status-details">
1913 <p><?php printf(
1914 esc_html__('Progress: %1$d of %2$d URLs (%3$d%%)', 'mxchat'),
1915 absint($sitemap_status['processed_urls']),
1916 absint($sitemap_status['total_urls']),
1917 absint($sitemap_status['percentage'])
1918 ); ?></p>
1919 <?php if (!empty($sitemap_status['error']) || !empty($sitemap_status['last_error'])) : ?>
1920 <div class="mxchat-error-notice">
1921 <?php if (!empty($sitemap_status['error'])) : ?>
1922 <p class="error"><?php echo esc_html($sitemap_status['error']); ?></p>
1923 <?php endif; ?>
1924 <?php if (!empty($sitemap_status['last_error'])) : ?>
1925 <p class="last-error"><?php echo esc_html__('Last error:', 'mxchat') . ' ' . esc_html($sitemap_status['last_error']); ?></p>
1926 <?php endif; ?>
1927 </div>
1928 <?php endif; ?>
1929
1930 <?php if (!empty($sitemap_status['failed_urls']) && $sitemap_status['failed_urls'] > 0) : ?>
1931 <div class="mxchat-failed-urls">
1932 <h5><?php echo sprintf(esc_html__('Failed URLs (%d)', 'mxchat'), absint($sitemap_status['failed_urls'])); ?></h5>
1933 <?php if (!empty($sitemap_status['failed_urls_list'])) : ?>
1934 <div class="mxchat-failed-urls-list">
1935 <table class="widefat striped">
1936 <thead>
1937 <tr>
1938 <th><?php esc_html_e('URL', 'mxchat'); ?></th>
1939 <th><?php esc_html_e('Error', 'mxchat'); ?></th>
1940 <th><?php esc_html_e('Time', 'mxchat'); ?></th>
1941 </tr>
1942 </thead>
1943 <tbody>
1944 <?php foreach ($sitemap_status['failed_urls_list'] as $failed_url) : ?>
1945 <tr>
1946 <td style="word-break: break-all;">
1947 <a href="<?php echo esc_url($failed_url['url']); ?>" target="_blank" rel="noopener noreferrer">
1948 <?php echo esc_html(strlen($failed_url['url']) > 60 ? substr($failed_url['url'], 0, 57) . '...' : $failed_url['url']); ?>
1949 </a>
1950 </td>
1951 <td><?php echo esc_html($failed_url['error']); ?></td>
1952 <td><?php echo esc_html(human_time_diff($failed_url['time'], time()) . ' ' . __('ago', 'mxchat')); ?></td>
1953 </tr>
1954 <?php endforeach; ?>
1955 </tbody>
1956 </table>
1957 </div>
1958 <?php endif; ?>
1959 </div>
1960 <?php endif; ?>
1961 </div>
1962 </div>
1963 <?php endif; ?>
1964 </div>
1965
1966 <!-- Knowledge Base Table Card -->
1967 <div class="mxchat-card">
1968 <div class="mxchat-card-header">
1969 <h2>
1970 <?php esc_html_e('Knowledge Base', 'mxchat'); ?>
1971 <span class="mxchat-record-count">
1972 (<?php echo esc_html($total_records); ?>)
1973 </span>
1974 <?php if ($use_pinecone) : ?>
1975 <span class="mxchat-data-source-badge pinecone" style="display: inline-flex; align-items: center; gap: 4px; padding: 4px 8px; border-radius: 12px; font-size: 12px; font-weight: 500; margin-left: 8px; background: #e3f2fd; color: #1976d2;">
1976 <span class="dashicons dashicons-cloud"></span>
1977 <?php esc_html_e('Pinecone', 'mxchat'); ?>
1978 </span>
1979 <?php else : ?>
1980 <span class="mxchat-data-source-badge wordpress" style="display: inline-flex; align-items: center; gap: 4px; padding: 4px 8px; border-radius: 12px; font-size: 12px; font-weight: 500; margin-left: 8px; background: #f3e5f5; color: #7b1fa2;">
1981 <span class="dashicons dashicons-database-view"></span>
1982 <?php esc_html_e('WordPress DB', 'mxchat'); ?>
1983 </span>
1984 <?php endif; ?>
1985 </h2>
1986 <div class="mxchat-header-actions">
1987 <!-- Search -->
1988 <form method="get" id="knowledge-search" class="mxchat-search-form">
1989 <?php wp_nonce_field('mxchat_prompts_search_nonce'); ?>
1990 <input type="hidden" name="page" value="mxchat-prompts" />
1991 <div class="mxchat-search-group">
1992 <span class="dashicons dashicons-search"></span>
1993 <input type="text"
1994 name="search"
1995 placeholder="<?php esc_attr_e('Search Knowledge', 'mxchat'); ?>"
1996 value="<?php echo esc_attr($search_query); ?>" />
1997 </div>
1998 </form>
1999
2000 <!-- Delete All -->
2001 <form method="post"
2002 action="<?php echo esc_url(admin_url('admin-post.php?action=mxchat_delete_all_prompts')); ?>"
2003 class="mxchat-delete-form"
2004 onsubmit="return confirm('<?php esc_attr_e('Are you sure you want to delete all knowledge? This action cannot be undone.', 'mxchat'); ?>');">
2005 <?php wp_nonce_field('mxchat_delete_all_prompts_action', 'mxchat_delete_all_prompts_nonce'); ?>
2006 <input type="hidden" name="data_source" value="<?php echo esc_attr($data_source); ?>" />
2007 <button type="submit" name="delete_all_prompts" class="mxchat-button-danger">
2008 <span class="dashicons dashicons-trash"></span>
2009 <?php esc_html_e('Delete All', 'mxchat'); ?>
2010 </button>
2011 </form>
2012 </div>
2013 </div>
2014
2015 <!-- Data Source Info Banner -->
2016 <?php if ($use_pinecone) : ?>
2017 <div class="mxchat-info-banner pinecone" style="display: flex; align-items: center; gap: 8px; padding: 12px 16px; margin-bottom: 20px; border-radius: 6px; font-size: 14px; background: #e8f5e8; border-left: 4px solid #4caf50; color: #2e7d2e;">
2018 <span class="dashicons dashicons-cloud"></span>
2019 <span><?php esc_html_e('Data is stored in Pinecone vector database for enhanced AI performance. Refresh page after adding content to database.', 'mxchat'); ?></span>
2020 </div>
2021 <?php else : ?>
2022 <div class="mxchat-info-banner wordpress" style="display: flex; align-items: center; gap: 8px; padding: 12px 16px; margin-bottom: 20px; border-radius: 6px; font-size: 14px; background: #fff3e0; border-left: 4px solid #ff9800; color: #e65100;">
2023 <span class="dashicons dashicons-database-view"></span>
2024 <span><?php esc_html_e('Data is stored in WordPress database. Refresh page after adding content to database.', 'mxchat'); ?></span>
2025 </div>
2026 <?php endif; ?>
2027
2028 <!-- Table -->
2029 <div class="mxchat-table-wrapper">
2030 <table class="mxchat-records-table">
2031 <thead>
2032 <tr>
2033 <th><?php esc_html_e('ID', 'mxchat'); ?></th>
2034 <th><?php esc_html_e('Content', 'mxchat'); ?></th>
2035 <th><?php esc_html_e('Source', 'mxchat'); ?></th>
2036 <?php if ($data_source === 'pinecone') : ?>
2037 <th><?php esc_html_e('Vector ID', 'mxchat'); ?></th>
2038 <?php endif; ?>
2039 <th><?php esc_html_e('Actions', 'mxchat'); ?></th>
2040 </tr>
2041 </thead>
2042 <tbody>
2043 <?php if ($prompts) : ?>
2044 <?php foreach ($prompts as $index => $prompt) : ?>
2045 <tr id="prompt-<?php echo esc_attr($prompt->id); ?>"
2046 data-source="<?php echo esc_attr($data_source); ?>"
2047 <?php if ($data_source === 'pinecone') : ?>
2048 style="background: rgba(33, 150, 243, 0.02);"
2049 <?php endif; ?>>
2050 <td>
2051 <?php if ($data_source === 'pinecone') : ?>
2052 <?php echo esc_html($index + 1 + (($current_page - 1) * $per_page)); ?>
2053 <?php else : ?>
2054 <?php echo esc_html($prompt->id); ?>
2055 <?php endif; ?>
2056 </td>
2057 <td class="mxchat-content-cell">
2058 <div class="content-view">
2059 <?php
2060 $content = $prompt->article_content;
2061 // Check if content contains Hebrew characters
2062 if (preg_match('/[\x{0590}-\x{05FF}]/u', $content)) {
2063 // Apply RTL direction for Hebrew content
2064 echo '<div dir="rtl" lang="he" class="rtl-content">';
2065 echo wp_kses_post(wpautop(esc_textarea($content)));
2066 echo '</div>';
2067 } else {
2068 echo wp_kses_post(wpautop(esc_textarea($content)));
2069 }
2070 ?>
2071 </div>
2072 <?php if ($data_source === 'wordpress') : ?>
2073 <textarea class="content-edit" style="display:none;"
2074 <?php if (preg_match('/[\x{0590}-\x{05FF}]/u', $prompt->article_content)) echo 'dir="rtl" lang="he"'; ?>>
2075 <?php echo esc_textarea($prompt->article_content); ?>
2076 </textarea>
2077 <?php endif; ?>
2078 </td>
2079 <td class="mxchat-url-cell">
2080 <div class="url-view">
2081 <?php if (!empty($prompt->source_url)) : ?>
2082 <a href="<?php echo esc_url($prompt->source_url); ?>" target="_blank">
2083 <span class="dashicons dashicons-external"></span>
2084 <?php esc_html_e('View Source', 'mxchat'); ?>
2085 </a>
2086 <?php else : ?>
2087 <span class="mxchat-na"><?php esc_html_e('N/A', 'mxchat'); ?></span>
2088 <?php endif; ?>
2089 </div>
2090 <?php if ($data_source === 'wordpress') : ?>
2091 <input type="text" class="url-edit" style="display:none;"
2092 value="<?php echo esc_attr($prompt->source_url); ?>" />
2093 <?php endif; ?>
2094 </td>
2095 <?php if ($data_source === 'pinecone') : ?>
2096 <td class="mxchat-vector-id-cell">
2097 <code style="background: #f5f5f5; padding: 2px 6px; border-radius: 3px; font-size: 11px;">
2098 <?php echo esc_html(substr($prompt->id, 0, 12) . '...'); ?>
2099 </code>
2100 </td>
2101 <?php endif; ?>
2102 <td class="mxchat-actions-cell">
2103 <?php if ($data_source === 'wordpress') : ?>
2104 <!-- WordPress DB - Full edit capabilities -->
2105 <button class="mxchat-button-icon edit-button"
2106 data-id="<?php echo esc_attr($prompt->id); ?>">
2107 <span class="dashicons dashicons-edit"></span>
2108 </button>
2109 <button class="mxchat-button-icon save-button"
2110 data-id="<?php echo esc_attr($prompt->id); ?>"
2111 style="display:none;"
2112 data-nonce="<?php echo wp_create_nonce('mxchat_save_inline_nonce'); ?>">
2113 <span class="dashicons dashicons-saved"></span>
2114 </button>
2115 <?php else : ?>
2116 <!-- Pinecone - Read-only with note -->
2117 <span class="mxchat-readonly-note"
2118 title="<?php esc_attr_e('Pinecone records are read-only', 'mxchat'); ?>"
2119 style="opacity: 0.6; cursor: help;">
2120 <span class="dashicons dashicons-visibility"></span>
2121 </span>
2122 <?php endif; ?>
2123
2124 <!-- Delete button for both sources -->
2125 <a href="<?php echo esc_url(admin_url(
2126 'admin-post.php?action=mxchat_delete_prompt&id=' . esc_attr($prompt->id)
2127 . '&source=' . esc_attr($data_source)
2128 . '&_wpnonce=' . wp_create_nonce('mxchat_delete_prompt_nonce')
2129 )); ?>"
2130 class="mxchat-button-icon delete-button"
2131 onclick="return confirm('<?php esc_attr_e('Are you sure you want to delete this entry?', 'mxchat'); ?>');">
2132 <span class="dashicons dashicons-trash"></span>
2133 </a>
2134 </td>
2135 </tr>
2136 <?php endforeach; ?>
2137 <?php else : ?>
2138 <tr>
2139 <td colspan="<?php echo $data_source === 'pinecone' ? '5' : '4'; ?>" class="mxchat-no-records">
2140 <?php if ($use_pinecone) : ?>
2141 <?php esc_html_e('No vectors found in Pinecone database.', 'mxchat'); ?>
2142 <?php else : ?>
2143 <?php esc_html_e('No knowledge base entries found.', 'mxchat'); ?>
2144 <?php endif; ?>
2145 </td>
2146 </tr>
2147 <?php endif; ?>
2148 </tbody>
2149 </table>
2150 </div>
2151
2152 <?php if ($page_links) : ?>
2153 <div class="mxchat-pagination">
2154 <?php echo wp_kses_post($page_links); ?>
2155 </div>
2156 <?php endif; ?>
2157 </div>
2158
2159 </div>
2160
2161 <!-- Sync Settings Tab (Initially Hidden) -->
2162 <div id="mxchat-kb-tab-sync" class="mxchat-kb-tab-content">
2163 <div class="mxchat-card">
2164 <!-- Auto-Sync Settings -->
2165 <div class="mxchat-settings-section">
2166 <h3><?php esc_html_e('Auto-Sync Settings', 'mxchat'); ?></h3>
2167 <p class="mxchat-description">
2168 <?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'); ?>
2169 </p>
2170 <div class="mxchat-autosave-section">
2171 <div class="mxchat-toggle-group">
2172 <div class="mxchat-toggle-container">
2173 <label class="mxchat-toggle-switch">
2174 <input type="checkbox"
2175 name="mxchat_auto_sync_posts"
2176 class="mxchat-autosave-field"
2177 value="1"
2178 data-nonce="<?php echo wp_create_nonce('mxchat_prompts_setting_nonce'); ?>"
2179 <?php checked(get_option('mxchat_auto_sync_posts', '0'), '1'); ?>>
2180 <span class="mxchat-toggle-slider"></span>
2181 </label>
2182 <span class="mxchat-toggle-label">
2183 <?php esc_html_e('Auto-sync Posts', 'mxchat'); ?>
2184 </span>
2185 </div>
2186
2187 <div class="mxchat-toggle-container">
2188 <label class="mxchat-toggle-switch">
2189 <input type="checkbox"
2190 name="mxchat_auto_sync_pages"
2191 class="mxchat-autosave-field"
2192 value="1"
2193 data-nonce="<?php echo wp_create_nonce('mxchat_prompts_setting_nonce'); ?>"
2194 <?php checked(get_option('mxchat_auto_sync_pages', '0'), '1'); ?>>
2195 <span class="mxchat-toggle-slider"></span>
2196 </label>
2197 <span class="mxchat-toggle-label">
2198 <?php esc_html_e('Auto-sync Pages', 'mxchat'); ?>
2199 </span>
2200 </div>
2201
2202 <!-- Custom Post Types Section -->
2203 <div class="mxchat-section-content">
2204 <div class="mxchat-custom-post-types-header">
2205 <button id="mxchat-custom-post-types-toggle" class="mxchat-button-secondary">
2206 <?php esc_html_e('Advanced Custom Post Sync Settings', 'mxchat'); ?>
2207 <span class="mxchat-toggle-icon">▼</span>
2208 </button>
2209 </div>
2210
2211 <div id="mxchat-custom-post-types-container" class="mxchat-custom-post-types-container" style="display: none;">
2212 <h3><?php esc_html_e('Sync Custom Post Types', 'mxchat'); ?></h3>
2213 <p><?php esc_html_e('Select additional custom post types to automatically sync with the chatbot.', 'mxchat'); ?></p>
2214
2215 <div class="mxchat-custom-post-types">
2216 <?php
2217 $post_types = $this->get_public_post_types();
2218
2219 // Skip post and page as they're handled separately
2220 unset($post_types['post']);
2221 unset($post_types['page']);
2222
2223 if (!empty($post_types)) {
2224 foreach ($post_types as $post_type => $label) {
2225 $option_name = 'mxchat_auto_sync_' . $post_type;
2226 $is_enabled = get_option($option_name, '0');
2227 ?>
2228 <div class="mxchat-toggle-container">
2229 <label class="mxchat-toggle-switch">
2230 <input type="checkbox"
2231 name="<?php echo esc_attr($option_name); ?>"
2232 class="mxchat-autosave-field"
2233 value="1"
2234 data-nonce="<?php echo wp_create_nonce('mxchat_prompts_setting_nonce'); ?>"
2235 <?php checked($is_enabled, '1'); ?>>
2236 <span class="mxchat-toggle-slider"></span>
2237 </label>
2238 <span class="mxchat-toggle-label">
2239 <?php echo esc_html($label); ?> (<?php echo esc_html($post_type); ?>)
2240 </span>
2241 </div>
2242 <?php
2243 }
2244 } else {
2245 echo '<p>' . esc_html__('No custom post types found.', 'mxchat') . '</p>';
2246 }
2247 ?>
2248 </div>
2249 </div>
2250 </div>
2251 </div>
2252 </div>
2253 </div>
2254 </div>
2255 </div>
2256
2257 <!-- NEW PINECONE TAB -->
2258 <div id="mxchat-kb-tab-pinecone" class="mxchat-kb-tab-content mxchat-autosave-section">
2259 <div class="mxchat-card">
2260 <h2><?php esc_html_e('Pinecone Vector Database Settings', 'mxchat'); ?></h2>
2261 <p class="mxchat-description">
2262 <?php echo wp_kses(
2263 __('<strong>Pinecone is optional</strong> and not required for MxChat to function. It provides enhanced search performance for larger knowledge bases. When enabled, content will be stored in Pinecone instead of the WordPress database, but you can use MxChat without it.', 'mxchat'),
2264 array('strong' => array())
2265 ); ?>
2266 </p>
2267
2268 <div class="mxchat-pinecone-info-box">
2269 <div class="mxchat-info-icon">
2270 <span class="dashicons dashicons-info"></span>
2271 </div>
2272 <div class="mxchat-info-content">
2273 <h4><?php esc_html_e('What is Pinecone?', 'mxchat'); ?></h4>
2274 <p><?php esc_html_e('Pinecone is a specialized vector database designed for AI applications. It provides faster similarity searches and better scalability compared to traditional databases for AI-powered features.', 'mxchat'); ?></p>
2275 <p><a href="https://www.pinecone.io/" target="_blank" rel="noopener noreferrer"><?php esc_html_e('Learn more about Pinecone →', 'mxchat'); ?></a></p>
2276 </div>
2277 </div>
2278
2279 <div class="mxchat-database-settings-form">
2280 <!-- REMOVED the WordPress form - we're using AJAX auto-save instead -->
2281 <?php
2282 $pinecone_options = get_option('mxchat_pinecone_addon_options', array());
2283 $use_pinecone = $pinecone_options['mxchat_use_pinecone'] ?? '0';
2284 ?>
2285
2286 <div class="mxchat-toggle-container">
2287 <label class="mxchat-toggle-switch">
2288 <input type="checkbox"
2289 name="mxchat_pinecone_addon_options[mxchat_use_pinecone]"
2290 value="1"
2291 <?php checked($use_pinecone, '1'); ?>>
2292 <span class="mxchat-toggle-slider"></span>
2293 </label>
2294 <span class="mxchat-toggle-label">
2295 <?php esc_html_e('Enable Pinecone Database', 'mxchat'); ?>
2296 </span>
2297 </div>
2298
2299 <div class="mxchat-pinecone-settings" <?php echo $use_pinecone ? '' : 'style="display: none;"'; ?>>
2300
2301 <?php if ($use_pinecone) : ?>
2302 <div class="mxchat-knowledge-warning success">
2303 <p><span class="dashicons dashicons-yes-alt"></span>
2304 <?php esc_html_e('Pinecone is enabled. All new knowledge base content will be stored in Pinecone.', 'mxchat'); ?>
2305 </p>
2306 </div>
2307 <?php endif; ?>
2308
2309 <div class="mxchat-form-group">
2310 <label for="mxchat_pinecone_api_key">
2311 <?php esc_html_e('Pinecone API Key', 'mxchat'); ?> <span class="required">*</span>
2312 </label>
2313 <input type="password"
2314 id="mxchat_pinecone_api_key"
2315 name="mxchat_pinecone_addon_options[mxchat_pinecone_api_key]"
2316 value="<?php echo esc_attr($pinecone_options['mxchat_pinecone_api_key'] ?? ''); ?>"
2317 class="regular-text"
2318 placeholder="pcsk_..." />
2319 <p class="description">
2320 <?php esc_html_e('Found in your Pinecone dashboard under API Keys.', 'mxchat'); ?>
2321 <a href="https://app.pinecone.io/" target="_blank" rel="noopener noreferrer"><?php esc_html_e('Open Pinecone Dashboard', 'mxchat'); ?></a>
2322 </p>
2323 </div>
2324
2325 <div class="mxchat-form-group">
2326 <label for="mxchat_pinecone_environment">
2327 <?php esc_html_e('Region', 'mxchat'); ?>
2328 </label>
2329 <input type="text"
2330 id="mxchat_pinecone_environment"
2331 name="mxchat_pinecone_addon_options[mxchat_pinecone_environment]"
2332 value="<?php echo esc_attr($pinecone_options['mxchat_pinecone_environment'] ?? ''); ?>"
2333 placeholder="e.g., gcp-starter"
2334 class="regular-text" />
2335 <p class="description">
2336 <?php esc_html_e('Your Pinecone environment/region (e.g., gcp-starter, us-west1-gcp, us-east-1-aws)', 'mxchat'); ?>
2337 </p>
2338 </div>
2339
2340 <div class="mxchat-form-group">
2341 <label for="mxchat_pinecone_index">
2342 <?php esc_html_e('Index Name', 'mxchat'); ?> <span class="required">*</span>
2343 </label>
2344 <input type="text"
2345 id="mxchat_pinecone_index"
2346 name="mxchat_pinecone_addon_options[mxchat_pinecone_index]"
2347 value="<?php echo esc_attr($pinecone_options['mxchat_pinecone_index'] ?? ''); ?>"
2348 placeholder="e.g., my-wordpress-vectors"
2349 class="regular-text" />
2350 <p class="description">
2351 <?php esc_html_e('The name of your Pinecone index. Must be created in your Pinecone dashboard first.', 'mxchat'); ?>
2352 </p>
2353 </div>
2354
2355 <div class="mxchat-form-group">
2356 <label for="mxchat_pinecone_host">
2357 <?php esc_html_e('Pinecone Host', 'mxchat'); ?> <span class="required">*</span>
2358 </label>
2359 <input type="text"
2360 id="mxchat_pinecone_host"
2361 name="mxchat_pinecone_addon_options[mxchat_pinecone_host]"
2362 value="<?php echo esc_attr($pinecone_options['mxchat_pinecone_host'] ?? ''); ?>"
2363 placeholder="e.g., my-index-xyz123.svc.pinecone.io"
2364 class="regular-text" />
2365 <p class="description">
2366 <?php esc_html_e('The hostname from your Pinecone index URL (exclude https://). Found in your index details.', 'mxchat'); ?>
2367 </p>
2368 </div>
2369
2370 <div class="mxchat-setup-steps">
2371 <h3><?php esc_html_e('Setup Instructions', 'mxchat'); ?></h3>
2372 <div class="mxchat-setup-step">
2373 <span class="step-number">1</span>
2374 <div class="step-content">
2375 <h4><?php esc_html_e('Create Account', 'mxchat'); ?></h4>
2376 <p><?php esc_html_e('Create a free account at', 'mxchat'); ?> <a href="https://www.pinecone.io/" target="_blank">pinecone.io</a></p>
2377 </div>
2378 </div>
2379 <div class="mxchat-setup-step">
2380 <span class="step-number">2</span>
2381 <div class="step-content">
2382 <h4><?php esc_html_e('Create Index', 'mxchat'); ?></h4>
2383 <p><?php esc_html_e('Create a new index with these settings:', 'mxchat'); ?></p>
2384 <ul>
2385 <li><?php esc_html_e('Dimensions: Choose based on your embedding model - 1536 (OpenAI Ada 2, TE3 Small), 2048 (Voyage-3 Large), 3072 (TE3 Large, Gemini Embedding), 1536 (Gemini Embedding), or 768 (Gemini Embedding)', 'mxchat'); ?></li>
2386 <li><?php esc_html_e('Metric: Cosine', 'mxchat'); ?></li>
2387 <li><?php esc_html_e('Cloud & Region: Your preferred region', 'mxchat'); ?></li>
2388 </ul>
2389 </div>
2390 </div>
2391 <div class="mxchat-setup-step">
2392 <span class="step-number">3</span>
2393 <div class="step-content">
2394 <h4><?php esc_html_e('Get API Key', 'mxchat'); ?></h4>
2395 <p><?php esc_html_e('Copy your API key from the API Keys section', 'mxchat'); ?></p>
2396 </div>
2397 </div>
2398 <div class="mxchat-setup-step">
2399 <span class="step-number">4</span>
2400 <div class="step-content">
2401 <h4><?php esc_html_e('Get Index Host', 'mxchat'); ?></h4>
2402 <p><?php esc_html_e('Copy your index host URL from the index details', 'mxchat'); ?></p>
2403 </div>
2404 </div>
2405 <div class="mxchat-setup-step">
2406 <span class="step-number">5</span>
2407 <div class="step-content">
2408 <h4><?php esc_html_e('Save Settings', 'mxchat'); ?></h4>
2409 <p><?php esc_html_e('Fill in the form above and save settings', 'mxchat'); ?></p>
2410 </div>
2411 </div>
2412 </div>
2413
2414
2415
2416 </div>
2417
2418
2419 </div>
2420 </div>
2421 </div>
2422
2423 </div>
2424
2425
2426
2427
2428 </div>
2429 </div>
2430
2431
2432 <!-- Content Selector Modal -->
2433 <div id="mxchat-kb-content-selector-modal" class="mxchat-kb-modal">
2434 <div class="mxchat-kb-modal-content">
2435 <div class="mxchat-kb-modal-header">
2436 <h3>
2437 <?php esc_html_e('Select WordPress Content', 'mxchat'); ?><br>
2438 <span class="mxchat-kb-header-note"><?php esc_html_e('(Content imported here will be tagged "In Knowledge Base")', 'mxchat'); ?></span>
2439 </h3>
2440 <span class="mxchat-kb-modal-close">&times;</span>
2441 </div>
2442 <div class="mxchat-kb-modal-filters">
2443 <div class="mxchat-kb-search-group">
2444 <input type="text" id="mxchat-kb-content-search" placeholder="<?php esc_attr_e('Search...', 'mxchat'); ?>">
2445 </div>
2446
2447 <div class="mxchat-kb-filter-group">
2448 <select id="mxchat-kb-content-type-filter">
2449 <option value="all"><?php esc_html_e('All Content Types', 'mxchat'); ?></option>
2450 <option value="post"><?php esc_html_e('Posts', 'mxchat'); ?></option>
2451 <option value="page"><?php esc_html_e('Pages', 'mxchat'); ?></option>
2452 <?php
2453 // Add other post types dynamically
2454 $post_types = get_post_types(array('public' => true), 'objects');
2455 foreach ($post_types as $post_type) {
2456 // Skip post and page as they're already added
2457 if (!in_array($post_type->name, array('post', 'page'))) {
2458 echo '<option value="' . esc_attr($post_type->name) . '">' . esc_html($post_type->label) . '</option>';
2459 }
2460 }
2461 ?>
2462 </select>
2463
2464 <select id="mxchat-kb-content-status-filter">
2465 <option value="publish"><?php esc_html_e('Published', 'mxchat'); ?></option>
2466 <option value="draft"><?php esc_html_e('Drafts', 'mxchat'); ?></option>
2467 <option value="all"><?php esc_html_e('All Statuses', 'mxchat'); ?></option>
2468 </select>
2469
2470 <select id="mxchat-kb-processed-filter">
2471 <option value="all"><?php esc_html_e('All Content', 'mxchat'); ?></option>
2472 <option value="processed"><?php esc_html_e('In Knowledge Base', 'mxchat'); ?></option>
2473 <option value="unprocessed"><?php esc_html_e('Not In Knowledge Base', 'mxchat'); ?></option>
2474 </select>
2475 </div>
2476 </div>
2477
2478 <div class="mxchat-kb-content-selection">
2479 <div class="mxchat-kb-selection-header">
2480 <label>
2481 <input type="checkbox" id="mxchat-kb-select-all">
2482 <?php esc_html_e('Select All', 'mxchat'); ?>
2483 </label>
2484 <span class="mxchat-kb-selection-count">0 <?php esc_html_e('selected', 'mxchat'); ?></span>
2485 </div>
2486
2487 <div class="mxchat-kb-content-list">
2488 <!-- Content will be loaded here via AJAX -->
2489 <div class="mxchat-kb-loading">
2490 <span class="mxchat-kb-spinner is-active"></span>
2491 <?php esc_html_e('Loading content...', 'mxchat'); ?>
2492 </div>
2493 </div>
2494
2495 <div class="mxchat-kb-pagination">
2496 <!-- Pagination will be added here -->
2497 </div>
2498 </div>
2499
2500 <div class="mxchat-kb-modal-footer">
2501 <button type="button" id="mxchat-kb-process-selected" class="mxchat-kb-button-primary" disabled>
2502 <?php esc_html_e('Process Selected Content', 'mxchat'); ?>
2503 <span class="mxchat-kb-selected-count">(0)</span>
2504 </button>
2505 <button type="button" class="mxchat-kb-button-secondary mxchat-kb-modal-close">
2506 <?php esc_html_e('Cancel', 'mxchat'); ?>
2507 </button>
2508 </div>
2509 </div>
2510 </div>
2511
2512 <?php
2513 }
2514
2515
2516 // ADD THIS NEW METHOD TO YOUR CLASS
2517 private function fetch_pinecone_records($pinecone_options, $search_query = '', $page = 1, $per_page = 20) {
2518 $api_key = $pinecone_options['mxchat_pinecone_api_key'] ?? '';
2519 $host = $pinecone_options['mxchat_pinecone_host'] ?? '';
2520 $index = $pinecone_options['mxchat_pinecone_index'] ?? '';
2521
2522 if (empty($api_key) || empty($host) || empty($index)) {
2523 return array('data' => array(), 'total' => 0);
2524 }
2525
2526 try {
2527 // First, try to get index statistics to understand the data structure
2528 $stats_url = "https://{$host}/describe_index_stats";
2529
2530 $stats_response = wp_remote_post($stats_url, array(
2531 'headers' => array(
2532 'Api-Key' => $api_key,
2533 'Content-Type' => 'application/json'
2534 ),
2535 'body' => json_encode(array()),
2536 'timeout' => 30
2537 ));
2538
2539 // Check if we can get vector IDs from local cache first
2540 $cached_vector_ids = get_option('mxchat_pinecone_vector_ids_cache', array());
2541
2542 $all_records = array();
2543
2544 // Method 1: Use cached vector IDs to fetch specific vectors
2545 if (!empty($cached_vector_ids)) {
2546 $all_records = $this->fetch_vectors_by_ids($pinecone_options, $cached_vector_ids);
2547 }
2548
2549 // Method 2: Fallback to scanning approach if cache is empty or incomplete
2550 if (empty($all_records)) {
2551 $all_records = $this->scan_pinecone_vectors($pinecone_options);
2552 }
2553
2554 // Filter by search query if provided
2555 if (!empty($search_query)) {
2556 $all_records = array_filter($all_records, function($record) use ($search_query) {
2557 $content = $record->article_content ?? '';
2558 $source_url = $record->source_url ?? '';
2559 return stripos($content, $search_query) !== false ||
2560 stripos($source_url, $search_query) !== false;
2561 });
2562 }
2563
2564 // Sort records by created_at in descending order (newest first)
2565 usort($all_records, function($a, $b) {
2566 $time_a = is_numeric($a->created_at) ? $a->created_at : strtotime($a->created_at);
2567 $time_b = is_numeric($b->created_at) ? $b->created_at : strtotime($b->created_at);
2568 return $time_b - $time_a;
2569 });
2570
2571 // Handle pagination
2572 $total = count($all_records);
2573 $offset = ($page - 1) * $per_page;
2574 $paged_records = array_slice($all_records, $offset, $per_page);
2575
2576 return array(
2577 'data' => $paged_records,
2578 'total' => $total
2579 );
2580
2581 } catch (Exception $e) {
2582 error_log('Pinecone fetch exception: ' . $e->getMessage());
2583 return array('data' => array(), 'total' => 0);
2584 }
2585 }
2586
2587 // Helper method to fetch vectors by specific IDs
2588 private function fetch_vectors_by_ids($pinecone_options, $vector_ids) {
2589 $api_key = $pinecone_options['mxchat_pinecone_api_key'] ?? '';
2590 $host = $pinecone_options['mxchat_pinecone_host'] ?? '';
2591
2592 if (empty($api_key) || empty($host) || empty($vector_ids)) {
2593 return array();
2594 }
2595
2596 try {
2597 $fetch_url = "https://{$host}/vectors/fetch";
2598
2599 // Pinecone fetch API allows fetching specific vectors by ID
2600 $fetch_data = array(
2601 'ids' => array_values($vector_ids)
2602 );
2603
2604 $response = wp_remote_post($fetch_url, array(
2605 'headers' => array(
2606 'Api-Key' => $api_key,
2607 'Content-Type' => 'application/json'
2608 ),
2609 'body' => json_encode($fetch_data),
2610 'timeout' => 30
2611 ));
2612
2613 if (is_wp_error($response)) {
2614 error_log('Pinecone fetch by IDs error: ' . $response->get_error_message());
2615 return array();
2616 }
2617
2618 $response_code = wp_remote_retrieve_response_code($response);
2619 if ($response_code !== 200) {
2620 error_log('Pinecone fetch by IDs failed with code: ' . $response_code);
2621 return array();
2622 }
2623
2624 $body = wp_remote_retrieve_body($response);
2625 $data = json_decode($body, true);
2626
2627 if (!isset($data['vectors'])) {
2628 return array();
2629 }
2630
2631 $converted_records = array();
2632 foreach ($data['vectors'] as $vector_id => $vector_data) {
2633 $metadata = $vector_data['metadata'] ?? array();
2634
2635 $converted_records[] = (object) array(
2636 'id' => $vector_id,
2637 'article_content' => $metadata['text'] ?? '',
2638 'source_url' => $metadata['source_url'] ?? '',
2639 'created_at' => $metadata['created_at'] ?? $metadata['last_updated'] ?? time(),
2640 'data_source' => 'pinecone'
2641 );
2642 }
2643
2644 return $converted_records;
2645
2646 } catch (Exception $e) {
2647 error_log('Pinecone fetch by IDs exception: ' . $e->getMessage());
2648 return array();
2649 }
2650 }
2651
2652 // Fallback method using improved scanning approach
2653 private function scan_pinecone_vectors($pinecone_options) {
2654 $api_key = $pinecone_options['mxchat_pinecone_api_key'] ?? '';
2655 $host = $pinecone_options['mxchat_pinecone_host'] ?? '';
2656
2657 if (empty($api_key) || empty($host)) {
2658 return array();
2659 }
2660
2661 try {
2662 // Instead of using a dummy zero vector, try multiple random vectors
2663 // to get better coverage of the vector space
2664 $all_matches = array();
2665 $seen_ids = array();
2666
2667 // Try 3-5 different random vectors to get better coverage
2668 for ($i = 0; $i < 3; $i++) {
2669 $query_url = "https://{$host}/query";
2670
2671 // Generate a random unit vector instead of zeros
2672 $random_vector = array();
2673 for ($j = 0; $j < 1536; $j++) {
2674 $random_vector[] = (rand(-1000, 1000) / 1000.0); // Random values between -1 and 1
2675 }
2676
2677 // Normalize the vector to unit length
2678 $magnitude = sqrt(array_sum(array_map(function($x) { return $x * $x; }, $random_vector)));
2679 if ($magnitude > 0) {
2680 $random_vector = array_map(function($x) use ($magnitude) { return $x / $magnitude; }, $random_vector);
2681 }
2682
2683 $query_data = array(
2684 'includeMetadata' => true,
2685 'includeValues' => false,
2686 'topK' => 10000, // Get many results
2687 'vector' => $random_vector
2688 );
2689
2690 $response = wp_remote_post($query_url, array(
2691 'headers' => array(
2692 'Api-Key' => $api_key,
2693 'Content-Type' => 'application/json'
2694 ),
2695 'body' => json_encode($query_data),
2696 'timeout' => 30
2697 ));
2698
2699 if (is_wp_error($response)) {
2700 continue;
2701 }
2702
2703 $body = wp_remote_retrieve_body($response);
2704 $data = json_decode($body, true);
2705
2706 if (isset($data['matches'])) {
2707 foreach ($data['matches'] as $match) {
2708 $match_id = $match['id'] ?? '';
2709 if (!empty($match_id) && !isset($seen_ids[$match_id])) {
2710 $all_matches[] = $match;
2711 $seen_ids[$match_id] = true;
2712 }
2713 }
2714 }
2715 }
2716
2717 // Convert matches to records
2718 $converted_records = array();
2719 $vector_ids_cache = array();
2720
2721 foreach ($all_matches as $match) {
2722 $metadata = $match['metadata'] ?? array();
2723 $match_id = $match['id'] ?? '';
2724
2725 if (!empty($match_id)) {
2726 $vector_ids_cache[] = $match_id;
2727 }
2728
2729 $converted_records[] = (object) array(
2730 'id' => $match_id,
2731 'article_content' => $metadata['text'] ?? '',
2732 'source_url' => $metadata['source_url'] ?? '',
2733 'created_at' => $metadata['created_at'] ?? $metadata['last_updated'] ?? time(),
2734 'data_source' => 'pinecone'
2735 );
2736 }
2737
2738 // Update the cache with found vector IDs for future use
2739 if (!empty($vector_ids_cache)) {
2740 update_option('mxchat_pinecone_vector_ids_cache', $vector_ids_cache);
2741 }
2742
2743 return $converted_records;
2744
2745 } catch (Exception $e) {
2746 error_log('Pinecone scan exception: ' . $e->getMessage());
2747 return array();
2748 }
2749 }
2750
2751 // Method to update vector IDs cache when new content is added
2752 private function update_pinecone_vector_cache($vector_id) {
2753 $cached_ids = get_option('mxchat_pinecone_vector_ids_cache', array());
2754 if (!in_array($vector_id, $cached_ids)) {
2755 $cached_ids[] = $vector_id;
2756 update_option('mxchat_pinecone_vector_ids_cache', $cached_ids);
2757 }
2758 }
2759 public function mxchat_handle_delete_all_prompts() {
2760 // Verify nonce
2761 if (!isset($_POST['mxchat_delete_all_prompts_nonce']) || !wp_verify_nonce($_POST['mxchat_delete_all_prompts_nonce'], 'mxchat_delete_all_prompts_action')) {
2762 wp_die(__('Nonce verification failed.', 'mxchat'));
2763 }
2764
2765 // Check permissions
2766 if (!current_user_can('manage_options')) {
2767 wp_die(__('You do not have sufficient permissions to delete all prompts.', 'mxchat'));
2768 }
2769
2770 $success = true;
2771 $error_messages = array();
2772
2773 // Check if Pinecone is enabled and configured
2774 $pinecone_options = get_option('mxchat_pinecone_addon_options', array());
2775 $use_pinecone = ($pinecone_options['mxchat_use_pinecone'] ?? '0') === '1';
2776
2777 if ($use_pinecone && !empty($pinecone_options['mxchat_pinecone_api_key'])) {
2778 error_log('[MXCHAT-DELETE] Pinecone is enabled - deleting all from Pinecone');
2779
2780 // Get Pinecone configuration
2781 $pinecone_options = get_option('mxchat_pinecone_addon_options', array());
2782
2783 // Delete all vectors from Pinecone
2784 $result = $this->delete_all_from_pinecone($pinecone_options);
2785
2786 if (!$result['success']) {
2787 $success = false;
2788 $error_messages[] = $result['message'];
2789 }
2790
2791 // Clear Pinecone vector cache
2792 delete_option('mxchat_pinecone_vector_ids_cache');
2793
2794 // Clear processed content caches
2795 delete_option('mxchat_pinecone_processed_cache');
2796 delete_option('mxchat_processed_content_cache');
2797
2798 } else {
2799 error_log('[MXCHAT-DELETE] Pinecone not enabled - deleting from WordPress database');
2800
2801 // Delete from WordPress database
2802 global $wpdb;
2803 $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
2804
2805 $result = $wpdb->query("DELETE FROM {$table_name}");
2806
2807 if ($result === false) {
2808 $success = false;
2809 $error_messages[] = 'Failed to delete from WordPress database';
2810 }
2811 }
2812
2813 // Clear relevant cache
2814 wp_cache_delete('all_prompts', 'mxchat_prompts');
2815
2816 // Set appropriate admin notice
2817 if ($success) {
2818 set_transient('mxchat_admin_notice_success',
2819 __('All prompts deleted successfully.', 'mxchat'), 30);
2820 } else {
2821 set_transient('mxchat_admin_notice_error',
2822 __('Failed to delete all prompts: ', 'mxchat') . implode(', ', $error_messages), 30);
2823 }
2824
2825 // Redirect back with a success message
2826 $redirect_url = add_query_arg(array(
2827 'page' => 'mxchat-prompts',
2828 'all_deleted' => $success ? 'true' : 'false'
2829 ), admin_url('admin.php'));
2830
2831 wp_safe_redirect($redirect_url);
2832 exit;
2833 }
2834
2835 public function mxchat_handle_delete_prompt() {
2836 // Sanitize and validate nonce
2837 $nonce = isset($_GET['_wpnonce']) ? sanitize_text_field(wp_unslash($_GET['_wpnonce'])) : '';
2838 if (empty($nonce) || !wp_verify_nonce($nonce, 'mxchat_delete_prompt_nonce')) {
2839 wp_die(esc_html__('Nonce verification failed.', 'mxchat'));
2840 }
2841
2842 // Check permissions
2843 if (!current_user_can('manage_options')) {
2844 wp_die(esc_html__('You do not have sufficient permissions to delete prompts.', 'mxchat'));
2845 }
2846
2847 // Get ID and source parameters
2848 $id = isset($_GET['id']) ? sanitize_text_field($_GET['id']) : '';
2849 $source = isset($_GET['source']) ? sanitize_text_field($_GET['source']) : '';
2850
2851 if (empty($id)) {
2852 wp_die(esc_html__('Invalid prompt ID.', 'mxchat'));
2853 }
2854
2855 $success = false;
2856 $error_message = '';
2857
2858 // Check if Pinecone is enabled and determine source automatically if not specified
2859 $pinecone_options = get_option('mxchat_pinecone_addon_options', array());
2860 $use_pinecone = ($pinecone_options['mxchat_use_pinecone'] ?? '0') === '1';
2861
2862 // If source is not specified, determine based on Pinecone configuration
2863 if (empty($source)) {
2864 $source = $use_pinecone ? 'pinecone' : 'wordpress';
2865 }
2866
2867 if ($source === 'pinecone' || $use_pinecone) {
2868 error_log('[MXCHAT-DELETE] Deleting from Pinecone, ID: ' . $id);
2869
2870 // Handle Pinecone deletion
2871 if (empty($pinecone_options['mxchat_pinecone_host']) ||
2872 empty($pinecone_options['mxchat_pinecone_api_key'])) {
2873 wp_die(esc_html__('Pinecone configuration is missing.', 'mxchat'));
2874 }
2875
2876 // Delete from Pinecone using the vector ID directly
2877 $result = $this->delete_from_pinecone_by_vector_id(
2878 $id,
2879 $pinecone_options['mxchat_pinecone_api_key'],
2880 $pinecone_options['mxchat_pinecone_host']
2881 );
2882
2883 if ($result['success']) {
2884 $success = true;
2885
2886 // Remove from vector cache
2887 $this->remove_from_pinecone_vector_cache($id);
2888
2889 // Clear processed content caches for this specific item
2890 $this->remove_from_processed_content_caches($id);
2891
2892 set_transient('mxchat_admin_notice_success',
2893 esc_html__('Vector deleted successfully from Pinecone.', 'mxchat'), 30);
2894 } else {
2895 $error_message = $result['message'];
2896 set_transient('mxchat_admin_notice_error',
2897 esc_html__('Failed to delete from Pinecone: ', 'mxchat') . esc_html($error_message), 30);
2898 }
2899 } else {
2900 error_log('[MXCHAT-DELETE] Deleting from WordPress database, ID: ' . $id);
2901
2902 // Handle WordPress database deletion
2903 global $wpdb;
2904 $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
2905
2906 // Clear cache and delete prompt
2907 wp_cache_delete('prompt_' . $id, 'mxchat_prompts');
2908
2909 $result = $wpdb->delete(
2910 $table_name,
2911 array('id' => intval($id)),
2912 array('%d')
2913 );
2914
2915 if ($result !== false) {
2916 $success = true;
2917 set_transient('mxchat_admin_notice_success',
2918 esc_html__('Entry deleted successfully.', 'mxchat'), 30);
2919 } else {
2920 set_transient('mxchat_admin_notice_error',
2921 esc_html__('Failed to delete entry from database.', 'mxchat'), 30);
2922 }
2923 }
2924
2925 // Redirect back to the prompts page
2926 wp_safe_redirect(add_query_arg(
2927 array(
2928 'page' => 'mxchat-prompts',
2929 'deleted' => $success ? 'true' : 'false'
2930 ),
2931 admin_url('admin.php')
2932 ));
2933 exit;
2934 }
2935
2936 /**
2937 * Delete all vectors from Pinecone
2938 *
2939 * @param array $pinecone_options Pinecone configuration options
2940 * @return array Array with 'success' boolean and 'message' string
2941 */
2942 private function delete_all_from_pinecone($pinecone_options) {
2943 $api_key = $pinecone_options['mxchat_pinecone_api_key'] ?? '';
2944 $host = $pinecone_options['mxchat_pinecone_host'] ?? '';
2945
2946 if (empty($api_key) || empty($host)) {
2947 return array(
2948 'success' => false,
2949 'message' => 'Missing Pinecone API credentials'
2950 );
2951 }
2952
2953 try {
2954 // First, get all vector IDs
2955 $all_vector_ids = array();
2956
2957 // Try to get from cache first
2958 $cached_vector_ids = get_option('mxchat_pinecone_vector_ids_cache', array());
2959 if (!empty($cached_vector_ids)) {
2960 $all_vector_ids = $cached_vector_ids;
2961 } else {
2962 // Fallback: scan to get vector IDs
2963 $records = $this->scan_pinecone_vectors($pinecone_options);
2964 foreach ($records as $record) {
2965 if (!empty($record->id)) {
2966 $all_vector_ids[] = $record->id;
2967 }
2968 }
2969 }
2970
2971 if (empty($all_vector_ids)) {
2972 return array(
2973 'success' => true,
2974 'message' => 'No vectors found to delete'
2975 );
2976 }
2977
2978 // Delete vectors in batches (Pinecone has limits on batch operations)
2979 $batch_size = 100;
2980 $batches = array_chunk($all_vector_ids, $batch_size);
2981 $deleted_count = 0;
2982 $failed_batches = 0;
2983
2984 foreach ($batches as $batch) {
2985 $result = $this->delete_pinecone_batch($batch, $api_key, $host);
2986 if ($result['success']) {
2987 $deleted_count += count($batch);
2988 } else {
2989 $failed_batches++;
2990 error_log('Failed to delete Pinecone batch: ' . $result['message']);
2991 }
2992 }
2993
2994 if ($failed_batches > 0) {
2995 return array(
2996 'success' => false,
2997 'message' => sprintf('Deleted %d vectors, but %d batches failed', $deleted_count, $failed_batches)
2998 );
2999 }
3000
3001 return array(
3002 'success' => true,
3003 'message' => "Successfully deleted {$deleted_count} vectors from Pinecone"
3004 );
3005
3006 } catch (Exception $e) {
3007 error_log('Pinecone delete all exception: ' . $e->getMessage());
3008 return array(
3009 'success' => false,
3010 'message' => $e->getMessage()
3011 );
3012 }
3013 }
3014
3015 /**
3016 * Delete a batch of vectors from Pinecone
3017 *
3018 * @param array $vector_ids Array of vector IDs to delete
3019 * @param string $api_key The Pinecone API key
3020 * @param string $host The Pinecone host
3021 * @return array Array with 'success' boolean and 'message' string
3022 */
3023 private function delete_pinecone_batch($vector_ids, $api_key, $host) {
3024 // Build the API endpoint
3025 $api_endpoint = "https://{$host}/vectors/delete";
3026
3027 // Prepare the request body with the IDs
3028 $request_body = array(
3029 'ids' => $vector_ids
3030 );
3031
3032 // Make the deletion request
3033 $response = wp_remote_post($api_endpoint, array(
3034 'headers' => array(
3035 'Api-Key' => $api_key,
3036 'accept' => 'application/json',
3037 'content-type' => 'application/json'
3038 ),
3039 'body' => wp_json_encode($request_body),
3040 'timeout' => 60, // Increased timeout for batch operations
3041 'method' => 'POST'
3042 ));
3043
3044 // Handle WordPress HTTP API errors
3045 if (is_wp_error($response)) {
3046 return array(
3047 'success' => false,
3048 'message' => $response->get_error_message()
3049 );
3050 }
3051
3052 // Check response status
3053 $response_code = wp_remote_retrieve_response_code($response);
3054 $response_body = wp_remote_retrieve_body($response);
3055
3056 // Pinecone returns 200 for successful deletion
3057 if ($response_code !== 200) {
3058 error_log('Pinecone batch deletion failed: HTTP ' . $response_code . ' - ' . $response_body);
3059 return array(
3060 'success' => false,
3061 'message' => sprintf(
3062 'Pinecone API error (HTTP %d): %s',
3063 $response_code,
3064 $response_body
3065 )
3066 );
3067 }
3068
3069 return array(
3070 'success' => true,
3071 'message' => 'Batch deleted successfully from Pinecone'
3072 );
3073 }
3074
3075 /**
3076 * Delete a specific vector from Pinecone by its ID
3077 *
3078 * @param string $vector_id The Pinecone vector ID to delete
3079 * @param string $api_key The Pinecone API key
3080 * @param string $host The Pinecone host
3081 * @return array Array with 'success' boolean and 'message' string
3082 */
3083 private function delete_from_pinecone_by_vector_id($vector_id, $api_key, $host) {
3084 // Build the API endpoint
3085 $api_endpoint = "https://{$host}/vectors/delete";
3086
3087 // Prepare the request body with just the ID
3088 $request_body = array(
3089 'ids' => array($vector_id)
3090 );
3091
3092 // Make the deletion request
3093 $response = wp_remote_post($api_endpoint, array(
3094 'headers' => array(
3095 'Api-Key' => $api_key,
3096 'accept' => 'application/json',
3097 'content-type' => 'application/json'
3098 ),
3099 'body' => wp_json_encode($request_body),
3100 'timeout' => 30,
3101 'method' => 'POST'
3102 ));
3103
3104 // Handle WordPress HTTP API errors
3105 if (is_wp_error($response)) {
3106 return array(
3107 'success' => false,
3108 'message' => $response->get_error_message()
3109 );
3110 }
3111
3112 // Check response status
3113 $response_code = wp_remote_retrieve_response_code($response);
3114 $response_body = wp_remote_retrieve_body($response);
3115
3116 // Pinecone returns 200 for successful deletion
3117 if ($response_code !== 200) {
3118 error_log('Pinecone deletion failed: HTTP ' . $response_code . ' - ' . $response_body);
3119 return array(
3120 'success' => false,
3121 'message' => sprintf(
3122 'Pinecone API error (HTTP %d): %s',
3123 $response_code,
3124 $response_body
3125 )
3126 );
3127 }
3128
3129 // Parse response to check if it was successful
3130 $response_data = json_decode($response_body, true);
3131
3132 // Log successful deletion
3133 error_log('Pinecone vector ' . $vector_id . ' deleted successfully');
3134
3135 return array(
3136 'success' => true,
3137 'message' => 'Vector deleted successfully from Pinecone'
3138 );
3139 }
3140
3141 /**
3142 * Remove a vector ID from the Pinecone cache
3143 *
3144 * @param string $vector_id The vector ID to remove from cache
3145 */
3146 private function remove_from_pinecone_vector_cache($vector_id) {
3147 $cached_ids = get_option('mxchat_pinecone_vector_ids_cache', array());
3148 $key = array_search($vector_id, $cached_ids);
3149 if ($key !== false) {
3150 unset($cached_ids[$key]);
3151 update_option('mxchat_pinecone_vector_ids_cache', array_values($cached_ids));
3152 }
3153 }
3154
3155 /**
3156 * Remove a post from the processed content caches
3157 *
3158 * @param string $vector_id The vector ID (which corresponds to post URL)
3159 */
3160 private function remove_from_processed_content_caches($vector_id) {
3161 // Get all caches
3162 $pinecone_cache = get_option('mxchat_pinecone_processed_cache', array());
3163 $processed_cache = get_option('mxchat_processed_content_cache', array());
3164
3165 // We need to find the post ID that corresponds to this vector ID
3166 // Vector ID is typically md5 of the source URL
3167 $post_id_to_remove = null;
3168
3169 // Search through caches to find matching post
3170 foreach ($pinecone_cache as $post_id => $cache_data) {
3171 if (isset($cache_data['db_id']) && $cache_data['db_id'] === $vector_id) {
3172 $post_id_to_remove = $post_id;
3173 break;
3174 }
3175 }
3176
3177 // Also check the processed cache
3178 if (!$post_id_to_remove) {
3179 foreach ($processed_cache as $post_id => $cache_data) {
3180 if (isset($cache_data['db_id']) && $cache_data['db_id'] === $vector_id) {
3181 $post_id_to_remove = $post_id;
3182 break;
3183 }
3184 }
3185 }
3186
3187 // If we found the post ID, remove it from both caches
3188 if ($post_id_to_remove) {
3189 unset($pinecone_cache[$post_id_to_remove]);
3190 unset($processed_cache[$post_id_to_remove]);
3191
3192 update_option('mxchat_pinecone_processed_cache', $pinecone_cache);
3193 update_option('mxchat_processed_content_cache', $processed_cache);
3194
3195 error_log('Removed post ID ' . $post_id_to_remove . ' from processed content caches');
3196 } else {
3197 // If we can't find by vector ID, we might need to reconstruct the URL
3198 // and find the post ID that way
3199 error_log('Could not find post ID for vector ID: ' . $vector_id);
3200 }
3201 }
3202
3203 public function mxchat_generate_embedding($text) {
3204 // Enable detailed logging for debugging
3205 //error_log('[MXCHAT-EMBED] Starting embedding generation. Text length: ' . strlen($text) . ' bytes');
3206 //error_log('[MXCHAT-EMBED] Text preview: ' . substr($text, 0, 100) . '...');
3207
3208 $options = get_option('mxchat_options');
3209 $selected_model = $options['embedding_model'] ?? 'text-embedding-ada-002';
3210 //error_log('[MXCHAT-EMBED] Selected embedding model: ' . $selected_model);
3211
3212 // Determine provider and endpoint
3213 if (strpos($selected_model, 'voyage') === 0) {
3214 $api_key = $options['voyage_api_key'] ?? '';
3215 $endpoint = 'https://api.voyageai.com/v1/embeddings';
3216 $provider_name = 'Voyage AI';
3217 //error_log('[MXCHAT-EMBED] Using Voyage AI API');
3218 } elseif (strpos($selected_model, 'gemini-embedding') === 0) {
3219 $api_key = $options['gemini_api_key'] ?? '';
3220 $endpoint = 'https://generativelanguage.googleapis.com/v1beta/models/' . $selected_model . ':embedContent';
3221 $provider_name = 'Google Gemini';
3222 //error_log('[MXCHAT-EMBED] Using Google Gemini API');
3223 } else {
3224 $api_key = $options['api_key'] ?? '';
3225 $endpoint = 'https://api.openai.com/v1/embeddings';
3226 $provider_name = 'OpenAI';
3227 //error_log('[MXCHAT-EMBED] Using OpenAI API');
3228 }
3229
3230 //error_log('[MXCHAT-EMBED] Using endpoint: ' . $endpoint);
3231
3232 if (empty($api_key)) {
3233 $error_message = sprintf('Missing %s API key. Please configure your API key in the MxChat settings.', $provider_name);
3234 //error_log('[MXCHAT-EMBED] Error: ' . $error_message);
3235 return $error_message;
3236 }
3237
3238 // Check if text is too long (for OpenAI, roughly estimate tokens as words/0.75)
3239 $estimated_tokens = ceil(str_word_count($text) / 0.75);
3240 //error_log('[MXCHAT-EMBED] Estimated token count: ~' . $estimated_tokens);
3241
3242 if ($estimated_tokens > 8000 && strpos($selected_model, 'voyage') === false && strpos($selected_model, 'gemini-embedding') === false) {
3243 //error_log('[MXCHAT-EMBED] Warning: Text may exceed OpenAI token limits (8K for most models)');
3244 // Consider truncating text here
3245 }
3246
3247 // Prepare request body based on provider
3248 if (strpos($selected_model, 'gemini-embedding') === 0) {
3249 // Gemini API format
3250 $request_body = array(
3251 'model' => 'models/' . $selected_model,
3252 'content' => array(
3253 'parts' => array(
3254 array('text' => $text)
3255 )
3256 )
3257 );
3258
3259 // Set output dimensionality to 1536 for consistency with other models
3260 $request_body['outputDimensionality'] = 1536;
3261 } else {
3262 // OpenAI/Voyage API format
3263 $request_body = array(
3264 'model' => $selected_model,
3265 'input' => $text
3266 );
3267
3268 // Add output_dimension for voyage-3-large model
3269 if ($selected_model === 'voyage-3-large') {
3270 $request_body['output_dimension'] = 2048;
3271 }
3272 }
3273
3274 //error_log('[MXCHAT-EMBED] Request prepared with model: ' . $selected_model);
3275
3276 // Prepare headers based on provider
3277 if (strpos($selected_model, 'gemini-embedding') === 0) {
3278 // Gemini uses API key as query parameter
3279 $endpoint .= '?key=' . $api_key;
3280 $headers = array(
3281 'Content-Type' => 'application/json'
3282 );
3283 } else {
3284 // OpenAI/Voyage use Bearer token
3285 $headers = array(
3286 'Authorization' => 'Bearer ' . $api_key,
3287 'Content-Type' => 'application/json'
3288 );
3289 }
3290
3291 // Make API request
3292 //error_log('[MXCHAT-EMBED] Sending API request to: ' . $endpoint);
3293 $response = wp_remote_post($endpoint, array(
3294 'body' => wp_json_encode($request_body),
3295 'headers' => $headers,
3296 'timeout' => 60 // Increased timeout for large inputs
3297 ));
3298
3299 // Handle wp_remote_post errors
3300 if (is_wp_error($response)) {
3301 $error_message = $response->get_error_message();
3302 //error_log('[MXCHAT-EMBED] WP Remote Post Error: ' . $error_message);
3303 return 'Connection error: ' . $error_message;
3304 }
3305
3306 // Get and check HTTP response code
3307 $http_code = wp_remote_retrieve_response_code($response);
3308 //error_log('[MXCHAT-EMBED] API Response Code: ' . $http_code);
3309
3310 if ($http_code !== 200) {
3311 $error_body = wp_remote_retrieve_body($response);
3312 //error_log('[MXCHAT-EMBED] API Error Response Body: ' . $error_body);
3313
3314 // Try to parse error for more details
3315 $error_json = json_decode($error_body, true);
3316 if (json_last_error() === JSON_ERROR_NONE && isset($error_json['error'])) {
3317 $error_type = $error_json['error']['type'] ?? 'unknown';
3318 $error_message = $error_json['error']['message'] ?? 'No message';
3319 //error_log('[MXCHAT-EMBED] API Error Type: ' . $error_type);
3320 //error_log('[MXCHAT-EMBED] API Error Message: ' . $error_message);
3321
3322 // Customize error message for common API errors
3323 if ($error_type === 'invalid_request_error' && strpos($error_message, 'API key') !== false) {
3324 $error_message = sprintf('Invalid %s API key. Please check your API key in the MxChat settings.', $provider_name);
3325 } elseif ($error_type === 'authentication_error') {
3326 $error_message = sprintf('%s authentication failed. Please verify your API key in the MxChat settings.', $provider_name);
3327 }
3328
3329 //error_log('[MXCHAT-EMBED] Returning error: ' . $error_message);
3330 return $error_message;
3331 }
3332
3333 $error_message = sprintf("API Error (HTTP %d): Unable to generate embedding", $http_code);
3334 //error_log('[MXCHAT-EMBED] Returning error: ' . $error_message);
3335 return $error_message;
3336 }
3337
3338 // Parse response body
3339 $response_body = wp_remote_retrieve_body($response);
3340 //error_log('[MXCHAT-EMBED] Received response length: ' . strlen($response_body) . ' bytes');
3341
3342 $response_data = json_decode($response_body, true);
3343
3344 if (json_last_error() !== JSON_ERROR_NONE) {
3345 $error = json_last_error_msg();
3346 //error_log('[MXCHAT-EMBED] JSON Parse Error: ' . $error);
3347 //error_log('[MXCHAT-EMBED] Response preview: ' . substr($response_body, 0, 200));
3348 return "Failed to parse API response: $error";
3349 }
3350
3351 // Handle different response formats based on provider
3352 if (strpos($selected_model, 'gemini-embedding') === 0) {
3353 // Gemini API response format
3354 if (isset($response_data['embedding']['values'])) {
3355 $embedding_dimensions = count($response_data['embedding']['values']);
3356 //error_log('[MXCHAT-EMBED] Successfully extracted Gemini embedding with ' . $embedding_dimensions . ' dimensions');
3357
3358 // Check if embedding dimensions are as expected (should be 1536)
3359 if ($embedding_dimensions !== 1536) {
3360 //error_log('[MXCHAT-EMBED] Warning: Unexpected Gemini embedding dimensions: ' . $embedding_dimensions);
3361 }
3362
3363 return $response_data['embedding']['values'];
3364 } else {
3365 //error_log('[MXCHAT-EMBED] Error: No embedding found in Gemini response');
3366 //error_log('[MXCHAT-EMBED] Response structure: ' . wp_json_encode(array_keys($response_data)));
3367
3368 if (isset($response_data['error'])) {
3369 $error_message = "Gemini API Error in response: " . wp_json_encode($response_data['error']);
3370 //error_log('[MXCHAT-EMBED] ' . $error_message);
3371 return $error_message;
3372 }
3373
3374 $error_message = "Invalid Gemini API response format: No embedding found";
3375 //error_log('[MXCHAT-EMBED] ' . $error_message);
3376 return $error_message;
3377 }
3378 } else {
3379 // OpenAI/Voyage API response format
3380 if (isset($response_data['data'][0]['embedding'])) {
3381 $embedding_dimensions = count($response_data['data'][0]['embedding']);
3382 //error_log('[MXCHAT-EMBED] Successfully extracted embedding with ' . $embedding_dimensions . ' dimensions');
3383
3384 // Check if embedding dimensions are as expected
3385 if (($selected_model === 'text-embedding-ada-002' && $embedding_dimensions !== 1536) ||
3386 ($selected_model === 'voyage-3-large' && $embedding_dimensions !== 2048)) {
3387 //error_log('[MXCHAT-EMBED] Warning: Unexpected embedding dimensions');
3388 }
3389
3390 return $response_data['data'][0]['embedding'];
3391 } else {
3392 //error_log('[MXCHAT-EMBED] Error: No embedding found in response');
3393 //error_log('[MXCHAT-EMBED] Response structure: ' . wp_json_encode(array_keys($response_data)));
3394
3395 if (isset($response_data['error'])) {
3396 $error_message = "API Error in response: " . wp_json_encode($response_data['error']);
3397 //error_log('[MXCHAT-EMBED] ' . $error_message);
3398 return $error_message;
3399 }
3400
3401 $error_message = "Invalid API response format: No embedding found";
3402 //error_log('[MXCHAT-EMBED] ' . $error_message);
3403 return $error_message;
3404 }
3405 }
3406 }
3407
3408 public function mxchat_delete_chat_history() {
3409 if (!current_user_can('manage_options')) {
3410 echo wp_json_encode(['error' => esc_html__('You do not have sufficient permissions.', 'mxchat')]);
3411 wp_die();
3412 }
3413 check_ajax_referer('mxchat_delete_chat_history', 'security');
3414 global $wpdb;
3415 $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
3416
3417 if (isset($_POST['delete_session_ids']) && is_array($_POST['delete_session_ids'])) {
3418 $deleted_count = 0;
3419
3420 foreach ($_POST['delete_session_ids'] as $session_id) {
3421 $session_id_sanitized = sanitize_text_field($session_id);
3422
3423 // Clear relevant cache before deletion
3424 $cache_key = 'chat_session_' . $session_id_sanitized;
3425 wp_cache_delete($cache_key, 'mxchat_chat_sessions');
3426
3427 // Perform the deletion from the database table
3428 $wpdb->delete($table_name, ['session_id' => $session_id_sanitized]);
3429
3430 // Delete the corresponding option entry from wp_options table
3431 delete_option("mxchat_history_" . $session_id_sanitized);
3432
3433 // Delete any associated metadata options
3434 delete_option("mxchat_email_" . $session_id_sanitized);
3435 delete_option("mxchat_agent_name_" . $session_id_sanitized);
3436
3437 $deleted_count++;
3438 }
3439
3440 // Optionally, clear a general cache if you have one
3441 wp_cache_delete('all_chat_sessions', 'mxchat_chat_sessions');
3442
3443 echo wp_json_encode([
3444 'success' => sprintf(
3445 esc_html__('%d chat session(s) have been deleted from all storage locations.', 'mxchat'),
3446 $deleted_count
3447 )
3448 ]);
3449 } else {
3450 echo wp_json_encode(['error' => esc_html__('No chat sessions selected for deletion.', 'mxchat')]);
3451 }
3452
3453 wp_die();
3454 }
3455
3456 public function mxchat_save_inline_prompt() {
3457 // Check for nonce security
3458 check_ajax_referer('mxchat_save_inline_nonce');
3459
3460 // Verify permissions
3461 if (!current_user_can('manage_options')) {
3462 wp_send_json_error(esc_html__('Permission denied.', 'mxchat'));
3463 return;
3464 }
3465
3466 global $wpdb;
3467 $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
3468
3469 // Validate and sanitize input data
3470 $prompt_id = isset($_POST['id']) ? absint($_POST['id']) : 0;
3471 $article_content = isset($_POST['article_content']) ? sanitize_textarea_field($_POST['article_content']) : '';
3472 $article_url = isset($_POST['article_url']) ? esc_url_raw($_POST['article_url']) : '';
3473
3474 if ($prompt_id > 0 && !empty($article_content)) {
3475 // Re-generate the embedding vector for the updated content
3476 $embedding_vector = $this->mxchat_generate_embedding($article_content);
3477
3478 if (is_array($embedding_vector)) {
3479 // Serialize the embedding vector before storing it
3480 $embedding_vector_serialized = serialize($embedding_vector);
3481
3482 // Update the prompt in the database
3483 $updated = $wpdb->update(
3484 $table_name,
3485 array(
3486 'article_content' => $article_content,
3487 'embedding_vector' => $embedding_vector_serialized,
3488 'source_url' => $article_url,
3489 ),
3490 array('id' => $prompt_id),
3491 array('%s', '%s', '%s'),
3492 array('%d')
3493 );
3494
3495 if ($updated !== false) {
3496 wp_send_json_success();
3497 } else {
3498 wp_send_json_error(esc_html__('Database update failed.', 'mxchat'));
3499 }
3500 } else {
3501 wp_send_json_error(esc_html__('Embedding generation failed.', 'mxchat'));
3502 }
3503 } else {
3504 wp_send_json_error(esc_html__('Invalid data.', 'mxchat'));
3505 }
3506 }
3507
3508 public function mxchat_handle_content_submission() {
3509 // Check if the form was submitted and the user has permission.
3510 if (!isset($_POST['submit_content']) || !current_user_can('manage_options')) {
3511 return;
3512 }
3513
3514 // Verify the nonce.
3515 $nonce = isset($_POST['mxchat_submit_content_nonce']) ? sanitize_text_field(wp_unslash($_POST['mxchat_submit_content_nonce'])) : '';
3516 if (!wp_verify_nonce($nonce, 'mxchat_submit_content_action')) {
3517 wp_die(esc_html__('Nonce verification failed.', 'mxchat'));
3518 }
3519
3520 // Sanitize the inputs.
3521 $article_content = sanitize_textarea_field($_POST['article_content']);
3522 $article_url = isset($_POST['article_url']) ? esc_url_raw($_POST['article_url']) : '';
3523
3524 // Get API key for submission
3525 $options = get_option('mxchat_options');
3526 $selected_model = $options['embedding_model'] ?? 'text-embedding-ada-002';
3527
3528 if (strpos($selected_model, 'voyage') === 0) {
3529 $api_key = $options['voyage_api_key'] ?? '';
3530 } elseif (strpos($selected_model, 'gemini-embedding') === 0) {
3531 $api_key = $options['gemini_api_key'] ?? '';
3532 } else {
3533 $api_key = $options['api_key'] ?? '';
3534 }
3535
3536 if (empty($api_key)) {
3537 set_transient('mxchat_admin_notice_error',
3538 esc_html__('API key is not configured. Please add your API key in the settings before submitting content.', 'mxchat'),
3539 30
3540 );
3541 wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
3542 exit;
3543 }
3544
3545 // Use centralized utility function for storage
3546 $result = MxChat_Utils::submit_content_to_db($article_content, $article_url, $api_key);
3547
3548 if (is_wp_error($result)) {
3549 set_transient('mxchat_admin_notice_error',
3550 esc_html__('Error storing content: ', 'mxchat') . $result->get_error_message(),
3551 30
3552 );
3553 } else {
3554 set_transient('mxchat_admin_notice_success',
3555 esc_html__('Content successfully submitted!', 'mxchat'),
3556 30
3557 );
3558 }
3559
3560 wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
3561 exit;
3562 }
3563
3564 private function is_pdf_url($url, $response) {
3565 $content_type = wp_remote_retrieve_header($response, 'content-type');
3566 $file_extension = strtolower(pathinfo($url, PATHINFO_EXTENSION));
3567
3568 return strpos($content_type, 'pdf') !== false || $file_extension === 'pdf';
3569 }
3570 private function handle_pdf_for_knowledge_base($pdf_url, $response) {
3571 if (!current_user_can('manage_options')) {
3572 //error_log(esc_html__('Unauthorized PDF processing attempt', 'mxchat'));
3573 return false;
3574 }
3575
3576 $pdf_url = esc_url_raw($pdf_url);
3577 $upload_dir = wp_upload_dir();
3578
3579 if (isset($upload_dir['error']) && $upload_dir['error'] !== false) {
3580 //error_log(sprintf(esc_html__('Upload directory error: %s', 'mxchat'), esc_html($upload_dir['error'])));
3581 return false;
3582 }
3583
3584 $pdf_filename = sanitize_file_name('mxchat_kb_' . time() . '.pdf');
3585 $pdf_path = trailingslashit($upload_dir['path']) . $pdf_filename;
3586
3587 $response_body = wp_remote_retrieve_body($response);
3588 if (empty($response_body)) {
3589 //error_log(esc_html__('Empty PDF response body', 'mxchat'));
3590 return false;
3591 }
3592
3593 if (!wp_mkdir_p(dirname($pdf_path))) {
3594 //error_log(sprintf(esc_html__('Failed to create directory for PDF: %s', 'mxchat'), esc_html($pdf_path)));
3595 return false;
3596 }
3597
3598 try {
3599 file_put_contents($pdf_path, $response_body);
3600
3601 if (!file_exists($pdf_path)) {
3602 throw new Exception(__('Failed to save PDF file', 'mxchat'));
3603 }
3604
3605 $parser = new \Smalot\PdfParser\Parser();
3606 $pdf = $parser->parseFile($pdf_path);
3607 $total_pages = absint(count($pdf->getPages()));
3608
3609 if ($total_pages < 1) {
3610 throw new Exception(__('Invalid PDF: no pages found', 'mxchat'));
3611 }
3612
3613 wp_schedule_single_event(time(), 'mxchat_process_pdf_pages', array(
3614 'pdf_path' => $pdf_path,
3615 'pdf_url' => $pdf_url,
3616 'total_pages' => $total_pages,
3617 'batch_size' => absint(15),
3618 'batch_pause' => absint(10)
3619 ));
3620
3621 $status_data = array(
3622 'total_pages' => $total_pages,
3623 'processed_pages' => 0,
3624 'status' => 'processing',
3625 'last_update' => time()
3626 );
3627
3628 set_transient(
3629 sanitize_key('mxchat_pdf_status_' . md5($pdf_url)),
3630 array_map('sanitize_text_field', $status_data),
3631 DAY_IN_SECONDS
3632 );
3633
3634 return __('scheduled', 'mxchat');
3635
3636 } catch (Exception $e) {
3637 //error_log(sprintf(esc_html__('Error preparing PDF for processing: %s', 'mxchat'), esc_html($e->getMessage())));
3638 if (file_exists($pdf_path)) {
3639 wp_delete_file($pdf_path);
3640 }
3641 return false;
3642 }
3643 }
3644 public static function process_pdf_pages_cron($pdf_path, $pdf_url, $total_pages, $batch_size, $batch_pause) {
3645 // Validate inputs
3646 $pdf_path = sanitize_text_field($pdf_path);
3647 $pdf_url = esc_url_raw($pdf_url);
3648 $total_pages = absint($total_pages);
3649 $batch_size = absint($batch_size);
3650 $batch_pause = absint($batch_pause);
3651
3652 try {
3653 if (!file_exists($pdf_path)) {
3654 throw new Exception(sprintf('PDF file not found at path: %s', esc_html($pdf_path)));
3655 }
3656
3657 $parser = new \Smalot\PdfParser\Parser();
3658 $pdf = $parser->parseFile($pdf_path);
3659 $pages = $pdf->getPages();
3660
3661 // Get current progress
3662 $status_key = sanitize_key('mxchat_pdf_status_' . md5($pdf_url));
3663 $status = get_transient($status_key);
3664
3665 if (!$status || !is_array($status)) {
3666 throw new Exception('Invalid status data retrieved from transient');
3667 }
3668
3669 $start_page = absint($status['processed_pages']);
3670 $end_page = min($start_page + $batch_size, $total_pages);
3671
3672 $instance = new self(); // Create an instance of the class
3673 $options = get_option('mxchat_options');
3674
3675 if (empty($options['api_key'])) {
3676 throw new Exception('API key is missing or invalid');
3677 }
3678
3679 for ($i = $start_page; $i < $end_page; $i++) {
3680 $text = $pages[$i]->getText();
3681
3682 if (empty($text)) {
3683 // Log empty page but continue processing
3684 //error_log(sprintf('[MXCHAT-PDF] Warning: Empty text on page %d of %s', $i + 1, $pdf_url));
3685 continue;
3686 }
3687
3688 $sanitized_content = $instance->mxchat_sanitize_content_for_api($text); // Call via instance
3689
3690 if (empty($sanitized_content)) {
3691 // Log empty sanitized content but continue processing
3692 //error_log(sprintf('[MXCHAT-PDF] Warning: No valid content after sanitization on page %d of %s', $i + 1, $pdf_url));
3693 continue;
3694 }
3695
3696 $embedding_vector = $instance->mxchat_generate_embedding($sanitized_content); // Call via instance
3697
3698 if (!is_array($embedding_vector)) {
3699 // If embedding generation fails, log error and throw exception
3700 $error_msg = is_string($embedding_vector) ? $embedding_vector : 'Unknown embedding generation error';
3701 //error_log(sprintf('[MXCHAT-PDF] Error generating embedding for page %d: %s', $i + 1, $error_msg));
3702 throw new Exception(sprintf('Failed to generate embedding for page %d: %s', $i + 1, $error_msg));
3703 }
3704
3705 $metadata = array(
3706 'document_type' => 'pdf',
3707 'total_pages' => $total_pages,
3708 'current_page' => $i + 1,
3709 'prev_page' => $i > 0 ? $i : null,
3710 'next_page' => $i < ($total_pages - 1) ? $i + 2 : null,
3711 'source_url' => $pdf_url
3712 );
3713
3714 $content_with_metadata = wp_json_encode($metadata) . "\n---\n" . $sanitized_content;
3715 $page_url = esc_url($pdf_url . "#page=" . ($i + 1));
3716
3717 $db_result = MxChat_Utils::submit_content_to_db($content_with_metadata, $page_url, $options['api_key']);
3718
3719 if (is_wp_error($db_result)) {
3720 throw new Exception(sprintf('Failed to store content in database for page %d: %s',
3721 $i + 1, $db_result->get_error_message()));
3722 }
3723
3724 // Update progress with sanitized data
3725 $status['processed_pages'] = absint($i + 1);
3726 $status['last_update'] = time();
3727 set_transient($status_key, array_map('sanitize_text_field', $status), DAY_IN_SECONDS);
3728 }
3729
3730 // Schedule next batch if needed
3731 if ($end_page < $total_pages) {
3732 wp_schedule_single_event(time() + $batch_pause, 'mxchat_process_pdf_pages', array(
3733 'pdf_path' => $pdf_path,
3734 'pdf_url' => $pdf_url,
3735 'total_pages' => $total_pages,
3736 'batch_size' => $batch_size,
3737 'batch_pause' => $batch_pause
3738 ));
3739 } else {
3740 // Processing complete
3741 $status['status'] = 'complete';
3742 set_transient($status_key, array_map('sanitize_text_field', $status), DAY_IN_SECONDS);
3743 if (file_exists($pdf_path)) {
3744 wp_delete_file($pdf_path);
3745 }
3746 }
3747
3748 } catch (\Exception $e) {
3749 //error_log(sprintf('[MXCHAT-PDF] Error processing PDF: %s', $e->getMessage()));
3750
3751 // Get current status to update it
3752 $status_key = sanitize_key('mxchat_pdf_status_' . md5($pdf_url));
3753 $status = get_transient($status_key);
3754
3755 if (!$status || !is_array($status)) {
3756 $status = array(
3757 'total_pages' => $total_pages,
3758 'processed_pages' => 0,
3759 'status' => 'error',
3760 'error' => sanitize_text_field($e->getMessage()),
3761 'last_update' => time()
3762 );
3763 } else {
3764 $status['status'] = 'error';
3765 $status['error'] = sanitize_text_field($e->getMessage());
3766 $status['last_update'] = time();
3767 }
3768
3769 set_transient($status_key, array_map('sanitize_text_field', $status), DAY_IN_SECONDS);
3770
3771 if (file_exists($pdf_path)) {
3772 wp_delete_file($pdf_path);
3773 }
3774 }
3775 }
3776 public function get_pdf_processing_status($pdf_url) {
3777 $pdf_url = esc_url_raw($pdf_url);
3778 $status = get_transient(sanitize_key('mxchat_pdf_status_' . md5($pdf_url)));
3779
3780 if (!$status || !is_array($status)) {
3781 return false;
3782 }
3783
3784 // Check for stalled processing (no updates for 5 minutes)
3785 if ($status['status'] === 'processing' && (time() - absint($status['last_update'])) > 300) {
3786 $status['status'] = 'error';
3787 $status['error'] = __('PDF processing appears to be stalled. No updates for over 5 minutes.', 'mxchat');
3788
3789 // Save the updated status
3790 set_transient(
3791 sanitize_key('mxchat_pdf_status_' . md5($pdf_url)),
3792 array_map('sanitize_text_field', $status),
3793 DAY_IN_SECONDS
3794 );
3795 }
3796
3797 $result = array(
3798 'total_pages' => absint($status['total_pages']),
3799 'processed_pages' => absint($status['processed_pages']),
3800 'percentage' => ($status['total_pages'] > 0)
3801 ? round((absint($status['processed_pages']) / absint($status['total_pages'])) * 100)
3802 : 0,
3803 'status' => sanitize_text_field($status['status']),
3804 'last_update' => human_time_diff(absint($status['last_update']), time()) . ' ' . esc_html__('ago', 'mxchat')
3805 );
3806
3807 // Add error message if present
3808 if (isset($status['error']) && !empty($status['error'])) {
3809 $result['error'] = sanitize_text_field($status['error']);
3810 }
3811
3812 return $result;
3813 }
3814
3815
3816 public function mxchat_handle_sitemap_submission() {
3817 // Start logging the submission process
3818 //error_log('[MXCHAT-URL] ===== Starting URL submission process =====');
3819
3820 // Check if the form was submitted and verify permissions
3821 if (!isset($_POST['submit_sitemap']) || !current_user_can('manage_options')) {
3822 //error_log('[MXCHAT-URL] Error: Unauthorized access or form not submitted properly');
3823 wp_die(esc_html__('Unauthorized access', 'mxchat'));
3824 }
3825
3826 // Verify nonce
3827 //error_log('[MXCHAT-URL] Verifying nonce');
3828 check_admin_referer('mxchat_submit_sitemap_action', 'mxchat_submit_sitemap_nonce');
3829
3830 // Validate URL
3831 if (!isset($_POST['sitemap_url']) || empty($_POST['sitemap_url'])) {
3832 //error_log('[MXCHAT-URL] Error: Empty or missing URL');
3833 set_transient('mxchat_admin_notice_error',
3834 esc_html__('Please provide a valid URL.', 'mxchat'),
3835 30
3836 );
3837 wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
3838 exit;
3839 }
3840
3841 $submitted_url = esc_url_raw($_POST['sitemap_url']);
3842 //error_log('[MXCHAT-URL] Processing URL: ' . $submitted_url);
3843
3844 // Validate API key first
3845 $options = get_option('mxchat_options');
3846 $selected_model = $options['embedding_model'] ?? 'text-embedding-ada-002';
3847
3848 if (strpos($selected_model, 'voyage') === 0) {
3849 $api_key = $options['voyage_api_key'] ?? '';
3850 $provider_name = 'Voyage AI';
3851 } elseif (strpos($selected_model, 'gemini-embedding') === 0) {
3852 $api_key = $options['gemini_api_key'] ?? '';
3853 $provider_name = 'Google Gemini';
3854 } else {
3855 $api_key = $options['api_key'] ?? '';
3856 $provider_name = 'OpenAI';
3857 }
3858
3859 if (empty($api_key)) {
3860 $error_message = sprintf(
3861 esc_html__('%s API key is not configured. Please add your API key in the settings before submitting content.', 'mxchat'),
3862 $provider_name
3863 );
3864 //error_log('[MXCHAT-URL] Error: ' . $error_message);
3865 set_transient('mxchat_admin_notice_error', $error_message, 30);
3866 //error_log('[MXCHAT-URL] Set error transient: ' . $error_message);
3867 wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
3868 exit;
3869 }
3870
3871 //error_log('[MXCHAT-URL] Fetching URL content');
3872 $response = wp_remote_get($submitted_url, array('timeout' => 30));
3873
3874 if (is_wp_error($response) || wp_remote_retrieve_response_code($response) !== 200) {
3875 $error_message = is_wp_error($response) ? $response->get_error_message() : 'HTTP Status: ' . wp_remote_retrieve_response_code($response);
3876 //error_log('[MXCHAT-URL] Error fetching URL: ' . $error_message);
3877 set_transient('mxchat_admin_notice_error',
3878 sprintf(
3879 esc_html__('Failed to fetch the URL: %s', 'mxchat'),
3880 esc_html($error_message)
3881 ),
3882 30
3883 );
3884 wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
3885 exit;
3886 }
3887
3888 $content_type = wp_remote_retrieve_header($response, 'content-type');
3889 //error_log('[MXCHAT-URL] Content type: ' . $content_type);
3890 $body_content = wp_remote_retrieve_body($response);
3891
3892 if (empty($body_content)) {
3893 //error_log('[MXCHAT-URL] Error: Empty response body');
3894 set_transient('mxchat_admin_notice_error',
3895 esc_html__('Empty response received from URL.', 'mxchat'),
3896 30
3897 );
3898 wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
3899 exit;
3900 }
3901 //error_log('[MXCHAT-URL] Retrieved body content length: ' . strlen($body_content) . ' bytes');
3902
3903 // Handle PDF URL
3904 if ($this->is_pdf_url($submitted_url, $response)) {
3905 //error_log('[MXCHAT-URL] Detected PDF URL, handling PDF for knowledge base');
3906 $result = $this->handle_pdf_for_knowledge_base($submitted_url, $response);
3907 //error_log('[MXCHAT-URL] PDF handling result: ' . $result);
3908
3909 if ($result === 'scheduled') {
3910 set_transient(
3911 'mxchat_last_pdf_url',
3912 sanitize_text_field($submitted_url),
3913 DAY_IN_SECONDS
3914 );
3915 set_transient('mxchat_admin_notice_info',
3916 esc_html__('PDF processing has started in the background. You can check the progress in the Knowledge Base section.', 'mxchat'),
3917 30
3918 );
3919 } else {
3920 set_transient('mxchat_admin_notice_error',
3921 esc_html__('Failed to start PDF processing: ', 'mxchat') . esc_html($result),
3922 30
3923 );
3924 }
3925
3926 wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
3927 exit;
3928 }
3929
3930 // Handle Sitemap XML
3931 if (strpos($content_type, 'xml') !== false || strpos($body_content, '<urlset') !== false) {
3932 //error_log('[MXCHAT-URL] Detected XML content, processing as sitemap');
3933 libxml_use_internal_errors(true);
3934 $xml = simplexml_load_string($body_content);
3935 $xml_errors = libxml_get_errors();
3936 libxml_clear_errors();
3937
3938 if ($xml === false || !empty($xml_errors)) {
3939 //error_log('[MXCHAT-URL] Error: Invalid XML format');
3940 if (!empty($xml_errors)) {
3941 foreach ($xml_errors as $error) {
3942 //error_log('[MXCHAT-URL] XML Error: ' . $error->message);
3943 }
3944 }
3945
3946 set_transient('mxchat_admin_notice_error',
3947 esc_html__('Invalid sitemap XML. Please provide a valid sitemap.', 'mxchat'),
3948 30
3949 );
3950 wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
3951 exit;
3952 }
3953
3954 //error_log('[MXCHAT-URL] Valid XML found, handling sitemap for knowledge base');
3955 $result = $this->handle_sitemap_for_knowledge_base($xml, $submitted_url);
3956 //error_log('[MXCHAT-URL] Sitemap handling result: ' . $result);
3957
3958 if ($result === 'scheduled') {
3959 set_transient(
3960 'mxchat_last_sitemap_url',
3961 sanitize_text_field($submitted_url),
3962 DAY_IN_SECONDS
3963 );
3964 set_transient('mxchat_admin_notice_info',
3965 esc_html__('Sitemap processing has started in the background. You can check the progress in the Knowledge Base section.', 'mxchat'),
3966 30
3967 );
3968 } else {
3969 // Return to the admin page without a redirect for better error display
3970 // The error is already stored in the sitemap status transient
3971 set_transient('mxchat_admin_notice_error',
3972 esc_html__('Failed to start sitemap processing. Please check the status below for details.', 'mxchat'),
3973 30
3974 );
3975 }
3976
3977 wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
3978 exit;
3979 }
3980
3981 // Handle Regular URL
3982 //error_log('[MXCHAT-URL] Processing as regular webpage');
3983 $page_content = $this->mxchat_extract_main_content($body_content);
3984 //error_log('[MXCHAT-URL] Extracted content length: ' . strlen($page_content) . ' bytes');
3985
3986 $sanitized_content = $this->mxchat_sanitize_content_for_api($page_content);
3987 //error_log('[MXCHAT-URL] Sanitized content length: ' . strlen($sanitized_content) . ' bytes');
3988
3989 if (empty($sanitized_content)) {
3990 //error_log('[MXCHAT-URL] Error: No valid content after sanitization');
3991
3992 // Set both transients - the error notice and the URL status
3993 set_transient('mxchat_admin_notice_error',
3994 esc_html__('No valid content found on the provided URL.', 'mxchat'),
3995 30
3996 );
3997
3998 // Set URL status transient
3999 set_transient('mxchat_single_url_status', [
4000 'url' => $submitted_url,
4001 'timestamp' => current_time('mysql'),
4002 'status' => 'failed',
4003 'error' => esc_html__('No valid content found on the provided URL.', 'mxchat')
4004 ], DAY_IN_SECONDS);
4005
4006 wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
4007 exit;
4008 }
4009
4010 //error_log('[MXCHAT-URL] Generating embedding for content');
4011 $embedding_vector = $this->mxchat_generate_embedding($sanitized_content);
4012
4013 // Check if embedding_vector is a string (error message)
4014 if (is_string($embedding_vector)) {
4015 //error_log('[MXCHAT-URL] Error generating embedding: ' . $embedding_vector);
4016 $error_message = esc_html__('Failed to generate embedding: ', 'mxchat') . esc_html($embedding_vector);
4017 //error_log('[MXCHAT-URL] Setting error transient: ' . $error_message);
4018
4019 // Set both transients
4020 set_transient('mxchat_admin_notice_error', $error_message, 30);
4021
4022 // Set URL status transient
4023 set_transient('mxchat_single_url_status', [
4024 'url' => $submitted_url,
4025 'timestamp' => current_time('mysql'),
4026 'status' => 'failed',
4027 'error' => $error_message
4028 ], DAY_IN_SECONDS);
4029
4030 wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
4031 exit;
4032 }
4033
4034 if (is_array($embedding_vector)) {
4035 //error_log('[MXCHAT-URL] Successfully generated embedding with ' . count($embedding_vector) . ' dimensions');
4036
4037 $db_result = MxChat_Utils::submit_content_to_db(
4038 $sanitized_content,
4039 $submitted_url,
4040 $api_key
4041 );
4042
4043 if (is_wp_error($db_result)) {
4044 //error_log('[MXCHAT-URL] Error: Failed to store content in database: ' . $db_result->get_error_message());
4045 $error_message = esc_html__('Failed to store content in database: ', 'mxchat') . esc_html($db_result->get_error_message());
4046 //error_log('[MXCHAT-URL] Setting error transient: ' . $error_message);
4047
4048 // Set both transients
4049 set_transient('mxchat_admin_notice_error', $error_message, 30);
4050
4051 // Set URL status transient
4052 set_transient('mxchat_single_url_status', [
4053 'url' => $submitted_url,
4054 'timestamp' => current_time('mysql'),
4055 'status' => 'failed',
4056 'error' => $error_message
4057 ], DAY_IN_SECONDS);
4058
4059 wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
4060 exit;
4061 }
4062
4063 //error_log('[MXCHAT-URL] Successfully stored content in database');
4064 $success_message = esc_html__('URL content successfully submitted!', 'mxchat');
4065 //error_log('[MXCHAT-URL] Setting success transient: ' . $success_message);
4066
4067 // Set both transients
4068 set_transient('mxchat_admin_notice_success', $success_message, 30);
4069
4070 // Set URL status transient with success
4071 set_transient('mxchat_single_url_status', [
4072 'url' => $submitted_url,
4073 'timestamp' => current_time('mysql'),
4074 'status' => 'complete',
4075 'content_length' => strlen($sanitized_content),
4076 'embedding_dimensions' => count($embedding_vector)
4077 ], DAY_IN_SECONDS);
4078
4079 } else {
4080 //error_log('[MXCHAT-URL] Error: Failed to generate embedding. Unexpected result type: ' . gettype($embedding_vector));
4081 $error_message = esc_html__('Failed to generate embedding: Unexpected result type. Please check your API key and try again.', 'mxchat');
4082 //error_log('[MXCHAT-URL] Setting error transient: ' . $error_message);
4083
4084 // Set both transients
4085 set_transient('mxchat_admin_notice_error', $error_message, 30);
4086
4087 // Set URL status transient
4088 set_transient('mxchat_single_url_status', [
4089 'url' => $submitted_url,
4090 'timestamp' => current_time('mysql'),
4091 'status' => 'failed',
4092 'error' => $error_message
4093 ], DAY_IN_SECONDS);
4094 }
4095
4096 //error_log('[MXCHAT-URL] ===== Completed URL submission process =====');
4097 wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
4098 exit;
4099 }
4100 private function get_single_url_status() {
4101 $status = get_transient('mxchat_single_url_status');
4102 if (!$status) {
4103 return null;
4104 }
4105
4106 // Add human-readable time
4107 if (isset($status['timestamp'])) {
4108 $status['human_time'] = human_time_diff(strtotime($status['timestamp']), current_time('timestamp')) . ' ' . __('ago', 'mxchat');
4109 }
4110
4111 return $status;
4112 }
4113 private function handle_sitemap_for_knowledge_base($xml, $sitemap_url) {
4114 // Clear any single URL status when starting sitemap processing
4115 delete_transient('mxchat_single_url_status');
4116 if (!current_user_can('manage_options')) {
4117 //error_log(esc_html__('Unauthorized sitemap processing attempt', 'mxchat'));
4118 return false;
4119 }
4120
4121 try {
4122 $sitemap_url = esc_url_raw($sitemap_url);
4123
4124 if (!$xml || !is_object($xml)) {
4125 throw new Exception(__('Invalid XML object provided', 'mxchat'));
4126 }
4127
4128 // Add embedding validation before processing
4129 // Test embedding with a small sample text to verify API key is working
4130 $test_result = $this->mxchat_generate_embedding("This is a test to verify the embedding API key is working.");
4131
4132 // Check if test_result is a string (error message) rather than an array (valid embedding)
4133 if (is_string($test_result)) {
4134 //error_log('[MXCHAT-URL] Embedding API validation failed: ' . $test_result);
4135
4136 // Store the error in the status transient so it can be displayed later
4137 $status_data = array(
4138 'total_urls' => 0,
4139 'processed_urls' => 0,
4140 'status' => 'error',
4141 'error' => __('Embedding API validation failed: ', 'mxchat') . $test_result,
4142 'last_update' => time()
4143 );
4144
4145 set_transient(
4146 sanitize_key('mxchat_sitemap_status_' . md5($sitemap_url)),
4147 array_map('sanitize_text_field', $status_data),
4148 DAY_IN_SECONDS
4149 );
4150
4151 throw new Exception(__('Embedding API validation failed: ', 'mxchat') . $test_result);
4152 }
4153
4154 // Make sure it's an array (valid embedding)
4155 if (!is_array($test_result)) {
4156 //error_log('[MXCHAT-URL] Embedding API returned unexpected result type: ' . gettype($test_result));
4157 throw new Exception(__('Embedding API returned unexpected result type. Please check your configuration.', 'mxchat'));
4158 }
4159
4160 $urls = [];
4161 foreach ($xml->url as $url_element) {
4162 $url = esc_url_raw((string)$url_element->loc);
4163 if ($url) {
4164 $urls[] = $url;
4165 }
4166 }
4167
4168 $total_urls = absint(count($urls));
4169
4170 if ($total_urls < 1) {
4171 throw new Exception(__('No valid URLs found in sitemap', 'mxchat'));
4172 }
4173
4174 wp_schedule_single_event(time(), 'mxchat_process_sitemap_urls', array(
4175 'urls' => $urls,
4176 'sitemap_url' => $sitemap_url,
4177 'total_urls' => $total_urls,
4178 'batch_size' => absint(10),
4179 'batch_pause' => absint(5)
4180 ));
4181
4182 $status_data = array(
4183 'total_urls' => $total_urls,
4184 'processed_urls' => 0,
4185 'status' => 'processing',
4186 'last_update' => time()
4187 );
4188
4189 set_transient(
4190 sanitize_key('mxchat_sitemap_status_' . md5($sitemap_url)),
4191 array_map('sanitize_text_field', $status_data),
4192 DAY_IN_SECONDS
4193 );
4194
4195 return __('scheduled', 'mxchat');
4196
4197 } catch (\Exception $e) {
4198 $error_message = $e->getMessage();
4199 //error_log(sprintf(esc_html__('Error preparing sitemap for processing: %s', 'mxchat'), esc_html($error_message)));
4200
4201 // Store the sitemap URL and error in transients so they can be displayed
4202 set_transient(
4203 'mxchat_last_sitemap_url',
4204 sanitize_text_field($sitemap_url),
4205 DAY_IN_SECONDS
4206 );
4207
4208 $status_data = array(
4209 'total_urls' => 0,
4210 'processed_urls' => 0,
4211 'status' => 'error',
4212 'error' => $error_message,
4213 'last_update' => time()
4214 );
4215
4216 set_transient(
4217 sanitize_key('mxchat_sitemap_status_' . md5($sitemap_url)),
4218 array_map('sanitize_text_field', $status_data),
4219 DAY_IN_SECONDS
4220 );
4221
4222 return $error_message;
4223 }
4224 }
4225 public static function process_sitemap_urls_cron($urls, $sitemap_url, $total_urls, $batch_size, $batch_pause) {
4226 // Validate inputs
4227 $sitemap_url = esc_url_raw($sitemap_url);
4228 $total_urls = absint($total_urls);
4229 $batch_size = absint($batch_size);
4230 $batch_pause = absint($batch_pause);
4231
4232 if (!is_array($urls) || empty($urls)) {
4233 return;
4234 }
4235
4236 try {
4237 $status_key = sanitize_key('mxchat_sitemap_status_' . md5($sitemap_url));
4238 $status = get_transient($status_key);
4239
4240 if (!$status || !is_array($status)) {
4241 throw new Exception('Invalid status data retrieved from transient');
4242 }
4243
4244 // Initialize failed_urls array if it doesn't exist
4245 if (!isset($status['failed_urls_list']) || !is_array($status['failed_urls_list'])) {
4246 $status['failed_urls_list'] = [];
4247 }
4248
4249 $start_url = absint($status['processed_urls']);
4250 $end_url = min($start_url + $batch_size, $total_urls);
4251 $instance = new self();
4252
4253 // Track failures in batch
4254 $batch_stats = [
4255 'processed' => 0,
4256 'failed' => 0,
4257 'last_error' => '',
4258 'embedding_errors' => 0 // Track specifically embedding errors
4259 ];
4260
4261 // Check embedding configuration with first URL
4262 if ($start_url === 0) {
4263 $page_url = esc_url_raw($urls[0]);
4264 $page_response = wp_remote_get($page_url);
4265
4266 if (!is_wp_error($page_response) && wp_remote_retrieve_response_code($page_response) === 200) {
4267 $page_html = wp_remote_retrieve_body($page_response);
4268 $page_content = $instance->mxchat_extract_main_content($page_html);
4269 $sanitized_content = $instance->mxchat_sanitize_content_for_api($page_content);
4270
4271 if (!empty($sanitized_content)) {
4272 $embedding_vector = $instance->mxchat_generate_embedding($sanitized_content);
4273
4274 // Check if embedding_vector is a string (error message)
4275 if (is_string($embedding_vector)) {
4276 throw new Exception('Embedding generation failed: ' . $embedding_vector);
4277 }
4278
4279 // Make sure it's an array (valid embedding)
4280 if (!is_array($embedding_vector)) {
4281 throw new Exception('Embedding generation returned unexpected result type: ' . gettype($embedding_vector));
4282 }
4283 }
4284 }
4285 }
4286
4287 for ($i = $start_url; $i < $end_url; $i++) {
4288 $page_url = esc_url_raw($urls[$i]);
4289 $page_response = wp_remote_get($page_url);
4290
4291 if (is_wp_error($page_response) || wp_remote_retrieve_response_code($page_response) !== 200) {
4292 $batch_stats['failed']++;
4293 $error_message = is_wp_error($page_response) ? $page_response->get_error_message() : 'HTTP Status: ' . wp_remote_retrieve_response_code($page_response);
4294 $batch_stats['last_error'] = 'Failed to fetch URL: ' . $error_message;
4295
4296 // Add to failed URLs list with error message
4297 $status['failed_urls_list'][] = [
4298 'url' => $page_url,
4299 'error' => $error_message,
4300 'time' => time()
4301 ];
4302
4303 continue;
4304 }
4305
4306 $page_html = wp_remote_retrieve_body($page_response);
4307 $page_content = $instance->mxchat_extract_main_content($page_html);
4308 $sanitized_content = $instance->mxchat_sanitize_content_for_api($page_content);
4309
4310 if (!empty($sanitized_content)) {
4311 $embedding_vector = $instance->mxchat_generate_embedding($sanitized_content);
4312
4313 // Check if embedding_vector is a string (error message)
4314 if (is_string($embedding_vector)) {
4315 $batch_stats['failed']++;
4316 $batch_stats['embedding_errors']++;
4317 $batch_stats['last_error'] = 'Failed to generate embedding: ' . $embedding_vector;
4318
4319 // Add to failed URLs list with error message
4320 $status['failed_urls_list'][] = [
4321 'url' => $page_url,
4322 'error' => 'Embedding error: ' . $embedding_vector,
4323 'time' => time()
4324 ];
4325
4326 // If we have multiple embedding errors, stop processing
4327 if ($batch_stats['embedding_errors'] >= 10) {
4328 throw new Exception('Multiple embedding failures detected: ' . $embedding_vector);
4329 }
4330 continue;
4331 }
4332
4333 // Check if it's an array (valid embedding)
4334 if (is_array($embedding_vector)) {
4335 $options = get_option('mxchat_options');
4336 $submission_result = MxChat_Utils::submit_content_to_db($sanitized_content, $page_url, $options['api_key']);
4337
4338 if (is_wp_error($submission_result)) {
4339 $batch_stats['failed']++;
4340 $batch_stats['last_error'] = $submission_result->get_error_message();
4341
4342 // Add to failed URLs list with error message
4343 $status['failed_urls_list'][] = [
4344 'url' => $page_url,
4345 'error' => 'Database submission error: ' . $submission_result->get_error_message(),
4346 'time' => time()
4347 ];
4348
4349 continue;
4350 }
4351
4352 $batch_stats['processed']++;
4353 } else {
4354 $batch_stats['failed']++;
4355 $batch_stats['embedding_errors']++;
4356 $batch_stats['last_error'] = 'Failed to generate embedding: Unexpected result type: ' . gettype($embedding_vector);
4357
4358 // Add to failed URLs list with error message
4359 $status['failed_urls_list'][] = [
4360 'url' => $page_url,
4361 'error' => 'Embedding error: Unexpected result type: ' . gettype($embedding_vector),
4362 'time' => time()
4363 ];
4364
4365 // If we have multiple embedding errors, stop processing
4366 if ($batch_stats['embedding_errors'] >= 10) {
4367 throw new Exception('Multiple embedding failures detected. Please check your embedding API configuration.');
4368 }
4369 }
4370 }
4371
4372 $status['processed_urls'] = absint($i + 1);
4373 $status['last_update'] = time();
4374 $status['failed_urls'] = absint($status['failed_urls'] ?? 0) + $batch_stats['failed'];
4375 $status['last_error'] = $batch_stats['last_error'];
4376
4377 // Limit the number of failed URLs we store to prevent transient size issues
4378 if (count($status['failed_urls_list']) > 100) {
4379 $status['failed_urls_list'] = array_slice($status['failed_urls_list'], -100);
4380 }
4381
4382 set_transient($status_key, $status, DAY_IN_SECONDS);
4383 }
4384
4385 // If all URLs in this batch failed, stop processing
4386 if ($batch_stats['processed'] === 0 && $batch_stats['failed'] > 0) {
4387 $status['status'] = 'error';
4388 $status['error'] = sprintf(
4389 'Processing stopped: %d consecutive failures. Last error: %s',
4390 $batch_stats['failed'],
4391 $batch_stats['last_error']
4392 );
4393 set_transient($status_key, $status, DAY_IN_SECONDS);
4394 return;
4395 }
4396
4397 // After the loop, make sure we have the accurate count
4398 $status['processed_urls'] = min($end_url, $total_urls);
4399 $status['last_update'] = time();
4400 set_transient($status_key, $status, DAY_IN_SECONDS);
4401
4402 // Check if we've processed all URLs
4403 if ($end_url >= $total_urls) {
4404 // All URLs have been processed - mark as complete
4405 $status['status'] = 'complete';
4406 $status['processed_urls'] = $total_urls; // Ensure it shows the exact total
4407 set_transient($status_key, $status, DAY_IN_SECONDS);
4408 } else {
4409 // Schedule next batch
4410 wp_schedule_single_event(time() + $batch_pause, 'mxchat_process_sitemap_urls', array(
4411 'urls' => $urls,
4412 'sitemap_url' => $sitemap_url,
4413 'total_urls' => $total_urls,
4414 'batch_size' => $batch_size,
4415 'batch_pause' => $batch_pause,
4416 ));
4417 }
4418 } catch (\Exception $e) {
4419 $status['status'] = 'error';
4420 $status['error'] = $e->getMessage();
4421 set_transient($status_key, $status, DAY_IN_SECONDS);
4422 }
4423 }
4424 private function mxchat_sanitize_content_for_api($content) {
4425 //error_log('[MXCHAT-SANITIZE] Original content preview: ' . substr($content, 0, 500) . '...');
4426
4427 // Remove script, style tags, and HTML comments
4428 $content = preg_replace('/<script\b[^>]*>(.*?)<\/script>/is', '', $content);
4429 $content = preg_replace('/<style\b[^>]*>(.*?)<\/style>/is', '', $content);
4430 $content = preg_replace('/<!--(.|\s)*?-->/', '', $content);
4431
4432 // Remove all HTML tags and decode HTML entities
4433 $content = wp_strip_all_tags($content);
4434 $content = html_entity_decode($content, ENT_QUOTES | ENT_HTML5);
4435
4436 // Normalize whitespace but preserve paragraph breaks
4437 // First, normalize line endings to \n
4438 $content = str_replace(["\r\n", "\r"], "\n", $content);
4439 // Replace multiple spaces/tabs with single space, but preserve newlines
4440 $content = preg_replace('/[ \t]+/', ' ', $content);
4441 // Replace 3+ newlines with 2 newlines (max 2 blank lines)
4442 $content = preg_replace('/\n{3,}/', "\n\n", $content);
4443 // Trim each line
4444 $lines = explode("\n", $content);
4445 $lines = array_map('trim', $lines);
4446 $content = implode("\n", $lines);
4447 // Final trim
4448 $content = trim($content);
4449
4450 // Remove control characters (which can cause database issues) but preserve \n (0x0A) and \r (0x0D)
4451 $content = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/u', '', $content);
4452
4453 // Remove NULL bytes which can cause database errors
4454 $content = str_replace("\0", "", $content);
4455
4456 // Ensure valid UTF-8 encoding
4457 $content = wp_check_invalid_utf8($content);
4458
4459 // Remove any extremely long strings without spaces (often garbage)
4460 $content = preg_replace('/\S{300,}/', ' ', $content);
4461
4462 // Replace problematic characters that often cause database issues
4463 $content = preg_replace('/[\x{10000}-\x{10FFFF}]/u', '', $content); // Remove emoji and other high Unicode characters
4464
4465 // Replace any remaining potentially problematic characters with spaces
4466 // BUT preserve newlines by temporarily replacing them
4467 $content = str_replace("\n", "NEWLINE_PLACEHOLDER", $content);
4468 $content = preg_replace('/[^\p{L}\p{N}\p{P}\p{Z}\p{Sm}]/u', ' ', $content);
4469 $content = str_replace("NEWLINE_PLACEHOLDER", "\n", $content);
4470
4471 // Limit to reasonable length if needed
4472 $max_length = 65000; // Just under MySQL TEXT field limit
4473 if (strlen($content) > $max_length) {
4474 $content = substr($content, 0, $max_length);
4475 }
4476
4477 //error_log('[MXCHAT-SANITIZE] Sanitized content preview: ' . substr($content, 0, 500) . '...');
4478 return $content;
4479 }
4480 private static function mxchat_extract_main_content($html) {
4481 if (empty($html)) {
4482 return '';
4483 }
4484 try {
4485 $dom = new DOMDocument;
4486 libxml_use_internal_errors(true); // Suppress HTML parsing errors
4487 @$dom->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'), LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
4488 $xpath = new DOMXPath($dom);
4489
4490 // For debugging purposes
4491 $debugEnabled = false; // Set to true to enable debugging output
4492 $debug = function($message) use ($debugEnabled) {
4493 if ($debugEnabled) {
4494 //error_log('[MXCHAT-DEBUG] ' . $message);
4495 }
4496 };
4497
4498 // Direct targeting for Gerow theme posts
4499 $post_text = $xpath->query('//div[contains(@class, "post-text")]');
4500 if ($post_text && $post_text->length > 0) {
4501 $debug("Found post-text directly");
4502 $content = '';
4503 foreach ($post_text as $node) {
4504 $content .= $dom->saveHTML($node);
4505 }
4506 if (!empty($content)) {
4507 $debug("Returning post-text content");
4508 return $content;
4509 }
4510 }
4511
4512 // Try to get the blog details content which contains the post-text
4513 $blog_details = $xpath->query('//div[contains(@class, "blog-details-content")]');
4514 if ($blog_details && $blog_details->length > 0) {
4515 $debug("Found blog-details-content");
4516 $content = '';
4517 foreach ($blog_details as $node) {
4518 $content .= $dom->saveHTML($node);
4519 }
4520 if (!empty($content)) {
4521 $debug("Returning blog-details-content");
4522 return $content;
4523 }
4524 }
4525
4526 // Try to get the article which contains the blog details
4527 $article = $xpath->query('//article[contains(@class, "blog-details-wrap")]');
4528 if ($article && $article->length > 0) {
4529 $debug("Found article with blog-details-wrap");
4530 $content = '';
4531 foreach ($article as $node) {
4532 $content .= $dom->saveHTML($node);
4533 }
4534 if (!empty($content)) {
4535 $debug("Returning article content");
4536 return $content;
4537 }
4538 }
4539
4540 // Try even broader with the blog-item-wrap
4541 $blog_item = $xpath->query('//div[contains(@class, "blog-item-wrap")]');
4542 if ($blog_item && $blog_item->length > 0) {
4543 $debug("Found blog-item-wrap");
4544 $content = '';
4545 foreach ($blog_item as $node) {
4546 $content .= $dom->saveHTML($node);
4547 }
4548 if (!empty($content)) {
4549 $debug("Returning blog-item-wrap content");
4550 return $content;
4551 }
4552 }
4553
4554 // Specific Gerow theme path
4555 $gerow_path = $xpath->query('//section[contains(@class, "blog-area")]//div[contains(@class, "post-text")]');
4556 if ($gerow_path && $gerow_path->length > 0) {
4557 $debug("Found Gerow theme path to post-text");
4558 $content = '';
4559 foreach ($gerow_path as $node) {
4560 $content .= $dom->saveHTML($node);
4561 }
4562 if (!empty($content)) {
4563 $debug("Returning Gerow post-text content");
4564 return $content;
4565 }
4566 }
4567
4568 // Generic blog post selectors
4569 $selectors = [
4570 // Blog post specific selectors
4571 '//div[contains(@class, "post-text")]',
4572 '//article[contains(@class, "blog-post-item")]//div[contains(@class, "post-text")]',
4573 '//div[contains(@class, "blog-details-content")]',
4574 '//article[contains(@class, "blog-details-wrap")]',
4575 '//div[contains(@class, "entry-content")]',
4576 '//div[contains(@class, "blog-content")]',
4577 '//div[contains(@class, "blog-item-wrap")]',
4578
4579 // More general content selectors
4580 '//div[contains(@class, "page__content")]',
4581 '//div[contains(@class, "elementor-widget-container")]',
4582 '//div[contains(@class, "elementor-text-editor")]',
4583 '//div[contains(@class, "elementor-widget-text-editor")]',
4584 '//*[contains(@class, "entry-content")]',
4585 '//*[contains(@class, "post-content")]',
4586 '//*[contains(@class, "article-content")]',
4587 '//*[@id="content"]',
4588 '//*[@id="main-content"]',
4589 '//section[contains(@class, "blog-area")]',
4590 '//article',
4591 '//main',
4592 '//div[contains(@class, "content")]'
4593 ];
4594
4595 // First handle Elementor content
4596 $debug("Checking for Elementor content");
4597 $elementor_widgets = $xpath->query('//div[contains(@class, "elementor-element")]//div[contains(@class, "elementor-widget-container")]');
4598 if ($elementor_widgets && $elementor_widgets->length > 0) {
4599 $debug("Found Elementor widgets");
4600 $combined_content = '';
4601 foreach ($elementor_widgets as $widget) {
4602 $widget_content = $dom->saveHTML($widget);
4603 if (!empty($widget_content)) {
4604 $combined_content .= $widget_content;
4605 }
4606 }
4607 if (!empty($combined_content)) {
4608 $debug("Returning Elementor content");
4609 return $combined_content;
4610 }
4611 }
4612
4613 // Try standard selectors one by one
4614 foreach ($selectors as $selector) {
4615 $debug("Trying selector: " . $selector);
4616 $nodes = $xpath->query($selector);
4617 if ($nodes && $nodes->length > 0) {
4618 $debug("Found matches for selector: " . $selector);
4619 $content = '';
4620 foreach ($nodes as $node) {
4621 $content .= $dom->saveHTML($node);
4622 }
4623 if (!empty($content)) {
4624 $debug("Returning content from selector: " . $selector);
4625 return $content;
4626 }
4627 }
4628 }
4629
4630 // Manual regex fallback for post-text if DOM methods fail
4631 $debug("Trying regex fallback");
4632 if (preg_match('/<div class="post-text">(.*?)<\/div>\s*<\/div>\s*<\/div>/s', $html, $matches)) {
4633 $debug("Found post-text via regex");
4634 return '<div class="post-text">' . $matches[1] . '</div>';
4635 }
4636
4637 // Try to extract the blog section as a whole
4638 $blog_section = $xpath->query('//section[contains(@class, "blog-area")]');
4639 if ($blog_section && $blog_section->length > 0) {
4640 $debug("Found blog-area section");
4641 $content = '';
4642 foreach ($blog_section as $node) {
4643 $content .= $dom->saveHTML($node);
4644 }
4645 if (!empty($content)) {
4646 $debug("Returning blog-area section content");
4647 return $content;
4648 }
4649 }
4650
4651 // Fallback: Return the body content if no specific selector matches
4652 $debug("Using body fallback");
4653 $body = $dom->getElementsByTagName('body');
4654 if ($body->length > 0) {
4655 return $dom->saveHTML($body->item(0));
4656 }
4657
4658 // Last resort: return the original HTML
4659 $debug("Returning original HTML");
4660 return $html;
4661 } catch (Exception $e) {
4662 //error_log('[MXCHAT-ERROR] Content extraction failed: ' . $e->getMessage());
4663 return $html; // Return original HTML if parsing fails
4664 } finally {
4665 libxml_clear_errors();
4666 }
4667 }
4668 public function get_sitemap_processing_status($sitemap_url) {
4669 $sitemap_url = esc_url_raw($sitemap_url);
4670 $status_key = sanitize_key('mxchat_sitemap_status_' . md5($sitemap_url));
4671 $status = get_transient($status_key);
4672
4673 if (!$status || !is_array($status)) {
4674 return false;
4675 }
4676
4677 // Auto-complete check: if all URLs are processed but status isn't complete
4678 if (isset($status['processed_urls']) && isset($status['total_urls']) &&
4679 $status['processed_urls'] >= $status['total_urls'] &&
4680 isset($status['status']) && $status['status'] !== 'complete' &&
4681 $status['status'] !== 'error') {
4682
4683 // Mark as complete
4684 $status['status'] = 'complete';
4685 $status['processed_urls'] = $status['total_urls']; // Ensure exact match
4686
4687 // Update the transient with the corrected status
4688 set_transient($status_key, $status, DAY_IN_SECONDS);
4689 }
4690
4691 return array(
4692 'total_urls' => absint($status['total_urls']),
4693 'processed_urls' => absint($status['processed_urls']),
4694 'failed_urls' => absint($status['failed_urls'] ?? 0),
4695 'percentage' => ($status['total_urls'] > 0)
4696 ? round((absint($status['processed_urls']) / absint($status['total_urls'])) * 100)
4697 : 0,
4698 'status' => sanitize_text_field($status['status']),
4699 'last_update' => human_time_diff(absint($status['last_update']), time()) . ' ' . esc_html__('ago', 'mxchat'),
4700 'error' => isset($status['error']) ? sanitize_text_field($status['error']) : '',
4701 'last_error' => isset($status['last_error']) ? sanitize_text_field($status['last_error']) : '',
4702 'failed_urls_list' => isset($status['failed_urls_list']) ? $status['failed_urls_list'] : array()
4703 );
4704 }
4705 public function ajax_get_status_updates() {
4706 try {
4707 // Verify the request
4708 check_ajax_referer('mxchat_status_nonce', 'nonce');
4709
4710 // Get the status just like in your admin page
4711 $pdf_url = get_transient('mxchat_last_pdf_url');
4712 $sitemap_url = get_transient('mxchat_last_sitemap_url');
4713
4714 $pdf_status = $pdf_url ? $this->get_pdf_processing_status($pdf_url) : false;
4715 $sitemap_status = $sitemap_url ? $this->get_sitemap_processing_status($sitemap_url) : false;
4716
4717 // Add the PDF URL to the status object
4718 if ($pdf_status && $pdf_url) {
4719 $pdf_status['pdf_url'] = $pdf_url;
4720 }
4721
4722 // Set the current PDF URL for the manual batch processing button
4723 $current_pdf_url = $pdf_url;
4724
4725 // Check for true processing status, not just presence of status
4726 $is_active_processing =
4727 ($sitemap_status && isset($sitemap_status['status']) && $sitemap_status['status'] === 'processing') ||
4728 ($pdf_status && isset($pdf_status['status']) && $pdf_status['status'] === 'processing');
4729
4730 // Get single URL status, but only if no processing is active
4731 $single_url_status = !$is_active_processing ? $this->get_single_url_status() : false;
4732
4733 // IMPORTANT: Don't delete transients here! Let JavaScript see the complete status first
4734 // Check if this is a "clear completed" request
4735 $clear_completed = isset($_POST['clear_completed']) && $_POST['clear_completed'] === 'true';
4736
4737 if ($clear_completed) {
4738 // Only clear if status is complete
4739 if ($pdf_status && isset($pdf_status['status']) && $pdf_status['status'] === 'complete') {
4740 delete_transient('mxchat_last_pdf_url');
4741 $pdf_status = false;
4742 }
4743 if ($sitemap_status && isset($sitemap_status['status']) && $sitemap_status['status'] === 'complete') {
4744 delete_transient('mxchat_last_sitemap_url');
4745 $sitemap_status = false;
4746 }
4747 }
4748
4749 // Return JSON response with the status data
4750 wp_send_json(array(
4751 'pdf_status' => $pdf_status,
4752 'sitemap_status' => $sitemap_status,
4753 'single_url_status' => $single_url_status,
4754 'is_processing' => $is_active_processing,
4755 'current_pdf_url' => $current_pdf_url
4756 ));
4757
4758 } catch (Exception $e) {
4759 // Log the error
4760 //error_log('MxChat Status Update Error: ' . $e->getMessage());
4761
4762 // Return a friendly error response
4763 wp_send_json_error(array(
4764 'message' => 'Error getting status updates: ' . $e->getMessage(),
4765 'status' => 'error'
4766 ));
4767 }
4768 }
4769 public function mxchat_stop_processing() {
4770 // Verify permissions
4771 if (!current_user_can('manage_options')) {
4772 wp_die(esc_html__('Unauthorized access', 'mxchat'));
4773 }
4774
4775 // Verify nonce
4776 check_admin_referer('mxchat_stop_processing_action', 'mxchat_stop_processing_nonce');
4777
4778 // Get the last sitemap URL and clear its transient
4779 $sitemap_url = get_transient('mxchat_last_sitemap_url');
4780 if ($sitemap_url) {
4781 delete_transient('mxchat_sitemap_status_' . md5($sitemap_url));
4782 delete_transient('mxchat_last_sitemap_url');
4783 }
4784
4785 // Get the last PDF URL and clear its transient
4786 $pdf_url = get_transient('mxchat_last_pdf_url');
4787 if ($pdf_url) {
4788 delete_transient('mxchat_pdf_status_' . md5($pdf_url));
4789 delete_transient('mxchat_last_pdf_url');
4790 }
4791
4792 // Unschedule any pending sitemap events
4793 $timestamp = wp_next_scheduled('mxchat_process_sitemap_urls');
4794 if ($timestamp) {
4795 wp_unschedule_event($timestamp, 'mxchat_process_sitemap_urls');
4796 }
4797
4798 // Redirect back with a success message
4799 set_transient('mxchat_admin_notice_success',
4800 esc_html__('Processing has been stopped successfully.', 'mxchat'),
4801 30
4802 );
4803 wp_safe_redirect(admin_url('admin.php?page=mxchat-prompts'));
4804 exit;
4805 }
4806
4807
4808 public function ajax_mxchat_get_content_list() {
4809 // Verify the nonce
4810 check_ajax_referer('mxchat_content_selector_nonce', 'nonce');
4811
4812 if (!current_user_can('manage_options')) {
4813 wp_send_json_error(__('Unauthorized access', 'mxchat'));
4814 }
4815
4816 $page = isset($_GET['page']) ? absint($_GET['page']) : 1;
4817 $per_page = isset($_GET['per_page']) ? absint($_GET['per_page']) : 20;
4818 $search = isset($_GET['search']) ? sanitize_text_field($_GET['search']) : '';
4819 $post_type = isset($_GET['post_type']) ? sanitize_text_field($_GET['post_type']) : 'all';
4820 $post_status = isset($_GET['post_status']) ? sanitize_text_field($_GET['post_status']) : 'publish';
4821 $processed_filter = isset($_GET['processed_filter']) ? sanitize_text_field($_GET['processed_filter']) : 'all';
4822
4823 // Build query args
4824 $args = array(
4825 'posts_per_page' => $per_page,
4826 'paged' => $page,
4827 'post_status' => $post_status !== 'all' ? $post_status : array('publish', 'draft', 'pending'),
4828 'orderby' => 'date',
4829 'order' => 'DESC',
4830 );
4831
4832 // Handle post types
4833 if ($post_type !== 'all') {
4834 $args['post_type'] = $post_type;
4835 } else {
4836 // Default to post and page if we can't get post types
4837 $args['post_type'] = array('post', 'page');
4838
4839 // Try to get public post types
4840 $public_types = $this->get_public_post_types();
4841 if (is_array($public_types) && !empty($public_types)) {
4842 $args['post_type'] = array_keys($public_types);
4843 }
4844 }
4845
4846 if (!empty($search)) {
4847 $args['s'] = $search;
4848 }
4849
4850 // ================================
4851 // UPDATED: Get already vectorized content from BOTH WordPress DB AND Pinecone
4852 // ================================
4853
4854 // FIRST: Get from WordPress DB (your original working code)
4855 global $wpdb;
4856 $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
4857 $processed_items = $wpdb->get_results("SELECT id, source_url, timestamp FROM {$table_name}");
4858
4859 // Create a lookup array of processed posts with timestamps
4860 $processed_data = array();
4861
4862 if (!empty($processed_items)) {
4863 foreach ($processed_items as $item) {
4864 $post_id = url_to_postid($item->source_url);
4865 if ($post_id) {
4866 $processed_data[$post_id] = array(
4867 'db_id' => $item->id,
4868 'timestamp' => $item->timestamp,
4869 'url' => $item->source_url,
4870 'source' => 'wordpress'
4871 );
4872 }
4873 }
4874 }
4875
4876 // SECOND: ALSO check Pinecone if it's enabled
4877 $pinecone_options = get_option('mxchat_pinecone_addon_options', array());
4878 $use_pinecone = ($pinecone_options['mxchat_use_pinecone'] ?? '0') === '1';
4879
4880 if ($use_pinecone && !empty($pinecone_options['mxchat_pinecone_api_key'])) {
4881 $pinecone_data = $this->get_pinecone_processed_content($pinecone_options);
4882
4883 // Merge Pinecone data with WordPress data
4884 foreach ($pinecone_data as $post_id => $pinecone_info) {
4885 // If not already found in WordPress DB, add from Pinecone
4886 if (!isset($processed_data[$post_id])) {
4887 $processed_data[$post_id] = $pinecone_info;
4888 }
4889 }
4890 }
4891
4892 // ================================
4893
4894 // Get processed IDs as a simple array for in_array checks
4895 $processed_ids = array_keys($processed_data);
4896
4897 // Handle processed/unprocessed filter
4898 if ($processed_filter === 'processed' && !empty($processed_ids)) {
4899 $args['post__in'] = $processed_ids;
4900 } elseif ($processed_filter === 'unprocessed' && !empty($processed_ids)) {
4901 $args['post__not_in'] = $processed_ids;
4902 }
4903
4904 // Run the query
4905 $query = new WP_Query($args);
4906 $content_items = array();
4907
4908 if ($query->have_posts()) {
4909 while ($query->have_posts()) {
4910 $query->the_post();
4911 $id = get_the_ID();
4912 $post_date = get_the_date();
4913 $excerpt = wp_trim_words(get_the_excerpt(), 20, '...');
4914 $word_count = str_word_count(strip_tags(get_the_content()));
4915
4916 $is_processed = in_array($id, $processed_ids);
4917 $processed_date = '';
4918 $db_record_id = 0;
4919 $data_source = 'none';
4920
4921 if ($is_processed && isset($processed_data[$id])) {
4922 $item_data = $processed_data[$id];
4923 $data_source = $item_data['source'];
4924
4925 if ($data_source === 'wordpress' && isset($item_data['timestamp'])) {
4926 // WordPress DB format
4927 $timestamp = strtotime($item_data['timestamp']);
4928 $processed_date = human_time_diff($timestamp, current_time('timestamp')) . ' ago';
4929 $db_record_id = $item_data['db_id'];
4930 } elseif ($data_source === 'pinecone') {
4931 // Pinecone format
4932 $processed_date = $item_data['processed_date'];
4933 $db_record_id = $item_data['db_id'];
4934 }
4935 }
4936
4937 $content_items[] = array(
4938 'id' => $id,
4939 'title' => get_the_title(),
4940 'permalink' => get_permalink(),
4941 'date' => $post_date,
4942 'type' => get_post_type(),
4943 'status' => get_post_status(),
4944 'excerpt' => $excerpt,
4945 'word_count' => $word_count,
4946 'already_processed' => $is_processed,
4947 'processed_date' => $processed_date,
4948 'db_record_id' => $db_record_id,
4949 'data_source' => $data_source
4950 );
4951 }
4952 wp_reset_postdata();
4953 }
4954
4955 $response = array(
4956 'items' => $content_items,
4957 'total' => $query->found_posts,
4958 'total_pages' => $query->max_num_pages,
4959 'current_page' => $page,
4960 'processed_count' => count($processed_ids)
4961 );
4962
4963 wp_send_json_success($response);
4964 exit;
4965 }
4966 public function ajax_mxchat_process_selected_content() {
4967 // Basic request validation
4968 if (!check_ajax_referer('mxchat_content_selector_nonce', 'nonce', false)) {
4969 wp_send_json_error('Invalid nonce');
4970 exit;
4971 }
4972
4973 if (!current_user_can('manage_options')) {
4974 wp_send_json_error('Unauthorized access');
4975 exit;
4976 }
4977
4978 // Get post IDs - safely parse the array
4979 $post_ids = array();
4980 if (isset($_POST['post_ids']) && is_array($_POST['post_ids'])) {
4981 foreach ($_POST['post_ids'] as $id) {
4982 $post_ids[] = absint($id);
4983 }
4984 }
4985
4986 if (empty($post_ids)) {
4987 wp_send_json_error('No content selected');
4988 exit;
4989 }
4990
4991 // Process only ONE post at a time to avoid request size issues
4992 $post_id = reset($post_ids);
4993 $post = get_post($post_id);
4994
4995 if (!$post) {
4996 wp_send_json_error('Post not found');
4997 exit;
4998 }
4999
5000 // Get minimal content
5001 $content = $post->post_title . "\n\n" . wp_strip_all_tags($post->post_content);
5002 $content = substr($content, 0, 10000); // Limit content size
5003
5004 // Get API key with proper model detection
5005 $options = get_option('mxchat_options');
5006 $selected_model = $options['embedding_model'] ?? 'text-embedding-ada-002';
5007
5008 if (strpos($selected_model, 'voyage') === 0) {
5009 $api_key = $options['voyage_api_key'] ?? '';
5010 $provider_name = 'Voyage AI';
5011 } elseif (strpos($selected_model, 'gemini-embedding') === 0) {
5012 $api_key = $options['gemini_api_key'] ?? '';
5013 $provider_name = 'Google Gemini';
5014 } else {
5015 $api_key = $options['api_key'] ?? '';
5016 $provider_name = 'OpenAI';
5017 }
5018
5019 if (empty($api_key)) {
5020 wp_send_json_error($provider_name . ' API key not configured');
5021 exit;
5022 }
5023
5024 $source_url = get_permalink($post_id);
5025 $vector_id = md5($source_url); // Vector ID for Pinecone
5026
5027 // ================================
5028 // UPDATED: Check for existing content in BOTH sources for backwards compatibility
5029 // ================================
5030
5031 $is_update = false;
5032
5033 // Check WordPress DB first (backwards compatibility)
5034 global $wpdb;
5035 $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
5036 $existing_record = $wpdb->get_row($wpdb->prepare(
5037 "SELECT id FROM $table_name WHERE source_url = %s",
5038 $source_url
5039 ));
5040
5041 if ($existing_record) {
5042 $is_update = true;
5043 } else {
5044 // Also check Pinecone if enabled
5045 $pinecone_options = get_option('mxchat_pinecone_addon_options', array());
5046 $use_pinecone = ($pinecone_options['mxchat_use_pinecone'] ?? '0') === '1';
5047
5048 if ($use_pinecone && !empty($pinecone_options['mxchat_pinecone_api_key'])) {
5049 $pinecone_data = $this->get_pinecone_processed_content($pinecone_options);
5050 if (isset($pinecone_data[$post_id])) {
5051 $is_update = true;
5052 }
5053 }
5054 }
5055
5056 // Use the centralized utility function for storage
5057 $result = MxChat_Utils::submit_content_to_db(
5058 $content,
5059 $source_url,
5060 $api_key,
5061 $vector_id
5062 );
5063
5064 if (is_wp_error($result)) {
5065 wp_send_json_error('Storage failed: ' . $result->get_error_message());
5066 exit;
5067 }
5068
5069 // ================================
5070 // NEW: Update caches immediately after successful storage
5071 // ================================
5072
5073 // Check if Pinecone is enabled and update caches
5074 $pinecone_options = get_option('mxchat_pinecone_addon_options', array());
5075 $use_pinecone = ($pinecone_options['mxchat_use_pinecone'] ?? '0') === '1';
5076
5077 if ($use_pinecone && !empty($pinecone_options['mxchat_pinecone_api_key'])) {
5078 // Update vector ID cache for improved fetching
5079 $this->update_pinecone_vector_cache($vector_id);
5080
5081 // Update local processed content cache for immediate UI feedback
5082 $pinecone_cache = get_option('mxchat_pinecone_processed_cache', array());
5083 $pinecone_cache[$post_id] = array(
5084 'db_id' => $vector_id,
5085 'processed_date' => 'Just now',
5086 'url' => $source_url,
5087 'source' => 'pinecone',
5088 'timestamp' => current_time('timestamp')
5089 );
5090 update_option('mxchat_pinecone_processed_cache', $pinecone_cache);
5091
5092 // Also update the general processed content cache
5093 $processed_cache = get_option('mxchat_processed_content_cache', array());
5094 $processed_cache[$post_id] = array(
5095 'db_id' => $vector_id,
5096 'timestamp' => current_time('timestamp'),
5097 'url' => $source_url,
5098 'source' => 'pinecone'
5099 );
5100 update_option('mxchat_processed_content_cache', $processed_cache);
5101 }
5102
5103 $operation_type = $is_update ? 'update' : 'new';
5104
5105 // Success response with minimal data
5106 wp_send_json_success(array(
5107 'message' => $operation_type === 'update' ? 'Content updated successfully' : 'Content processed successfully',
5108 'post_id' => $post_id,
5109 'title' => $post->post_title,
5110 'operation_type' => $operation_type,
5111 'vector_id' => $vector_id, // Include vector ID for debugging
5112 'cache_updated' => $use_pinecone // Indicate if cache was updated
5113 ));
5114 exit;
5115 }
5116 private function get_public_post_types() {
5117 $post_types = get_post_types(array('public' => true), 'objects');
5118 $post_type_options = array();
5119
5120 foreach ($post_types as $post_type) {
5121 $post_type_options[$post_type->name] = $post_type->label;
5122 }
5123
5124 return $post_type_options;
5125 }
5126 private function get_pinecone_processed_content($pinecone_options) {
5127 // First check local cache for immediate updates
5128 $cached_data = get_option('mxchat_pinecone_processed_cache', array());
5129
5130 $api_key = $pinecone_options['mxchat_pinecone_api_key'] ?? '';
5131 $host = $pinecone_options['mxchat_pinecone_host'] ?? '';
5132
5133 if (empty($api_key) || empty($host)) {
5134 // Return only cached data if API credentials are missing
5135 return $cached_data;
5136 }
5137
5138 $pinecone_data = array();
5139
5140 try {
5141 // Method 1: Try to get vectors using cached vector IDs first
5142 $cached_vector_ids = get_option('mxchat_pinecone_vector_ids_cache', array());
5143
5144 if (!empty($cached_vector_ids)) {
5145 $pinecone_data = $this->fetch_pinecone_vectors_by_ids($pinecone_options, $cached_vector_ids);
5146 }
5147
5148 // Method 2: If no cached IDs or fetch failed, use scanning approach
5149 if (empty($pinecone_data)) {
5150 $pinecone_data = $this->scan_pinecone_for_processed_content($pinecone_options);
5151 }
5152
5153 // Method 3: Final fallback - try stats endpoint (if available)
5154 if (empty($pinecone_data)) {
5155 $stats_url = "https://{$host}/describe_index_stats";
5156
5157 $response = wp_remote_post($stats_url, array(
5158 'headers' => array(
5159 'Api-Key' => $api_key,
5160 'Content-Type' => 'application/json'
5161 ),
5162 'body' => json_encode(array()),
5163 'timeout' => 30
5164 ));
5165
5166 if (!is_wp_error($response) && wp_remote_retrieve_response_code($response) === 200) {
5167 $body = wp_remote_retrieve_body($response);
5168 $stats_data = json_decode($body, true);
5169
5170 // Log stats for debugging but don't rely on them for vector listing
5171 error_log('Pinecone index stats: ' . print_r($stats_data, true));
5172 }
5173 }
5174
5175 } catch (Exception $e) {
5176 error_log('Pinecone processed content exception: ' . $e->getMessage());
5177 }
5178
5179 // Merge cached data with Pinecone data
5180 // Cache takes priority for recent updates (within last 5 minutes)
5181 $merged_data = $pinecone_data;
5182
5183 foreach ($cached_data as $post_id => $cache_item) {
5184 $cache_timestamp = $cache_item['timestamp'] ?? 0;
5185 $time_diff = current_time('timestamp') - $cache_timestamp;
5186
5187 // If cache item is recent (less than 5 minutes), prioritize it
5188 if ($time_diff < 300) { // 5 minutes = 300 seconds
5189 $merged_data[$post_id] = $cache_item;
5190 } else {
5191 // If not in Pinecone data and cache is old, keep cache but mark as potentially stale
5192 if (!isset($merged_data[$post_id])) {
5193 $merged_data[$post_id] = $cache_item;
5194 }
5195 }
5196 }
5197
5198 return $merged_data;
5199 }
5200
5201 // Helper method to fetch vectors by specific IDs
5202 private function fetch_pinecone_vectors_by_ids($pinecone_options, $vector_ids) {
5203 $api_key = $pinecone_options['mxchat_pinecone_api_key'] ?? '';
5204 $host = $pinecone_options['mxchat_pinecone_host'] ?? '';
5205
5206 if (empty($api_key) || empty($host) || empty($vector_ids)) {
5207 return array();
5208 }
5209
5210 try {
5211 $fetch_url = "https://{$host}/vectors/fetch";
5212
5213 // Pinecone fetch API allows fetching specific vectors by ID
5214 $fetch_data = array(
5215 'ids' => array_values($vector_ids)
5216 );
5217
5218 $response = wp_remote_post($fetch_url, array(
5219 'headers' => array(
5220 'Api-Key' => $api_key,
5221 'Content-Type' => 'application/json'
5222 ),
5223 'body' => json_encode($fetch_data),
5224 'timeout' => 30
5225 ));
5226
5227 if (is_wp_error($response)) {
5228 error_log('Pinecone fetch by IDs error: ' . $response->get_error_message());
5229 return array();
5230 }
5231
5232 $response_code = wp_remote_retrieve_response_code($response);
5233 if ($response_code !== 200) {
5234 error_log('Pinecone fetch by IDs failed with code: ' . $response_code);
5235 return array();
5236 }
5237
5238 $body = wp_remote_retrieve_body($response);
5239 $data = json_decode($body, true);
5240
5241 if (!isset($data['vectors'])) {
5242 return array();
5243 }
5244
5245 $processed_data = array();
5246
5247 foreach ($data['vectors'] as $vector_id => $vector_data) {
5248 $metadata = $vector_data['metadata'] ?? array();
5249 $source_url = $metadata['source_url'] ?? '';
5250
5251 if (!empty($source_url)) {
5252 $post_id = url_to_postid($source_url);
5253 if ($post_id) {
5254 $created_at = $metadata['created_at'] ?? '';
5255 $processed_date = 'Recently'; // Default
5256
5257 if (!empty($created_at)) {
5258 $timestamp = is_numeric($created_at) ? $created_at : strtotime($created_at);
5259 if ($timestamp) {
5260 $processed_date = human_time_diff($timestamp, current_time('timestamp')) . ' ago';
5261 }
5262 }
5263
5264 $processed_data[$post_id] = array(
5265 'db_id' => $vector_id,
5266 'processed_date' => $processed_date,
5267 'url' => $source_url,
5268 'source' => 'pinecone',
5269 'timestamp' => $timestamp ?? current_time('timestamp')
5270 );
5271 }
5272 }
5273 }
5274
5275 return $processed_data;
5276
5277 } catch (Exception $e) {
5278 error_log('Pinecone fetch by IDs exception: ' . $e->getMessage());
5279 return array();
5280 }
5281 }
5282
5283 // Fallback scanning method for when vector IDs cache is empty
5284 private function scan_pinecone_for_processed_content($pinecone_options) {
5285 $api_key = $pinecone_options['mxchat_pinecone_api_key'] ?? '';
5286 $host = $pinecone_options['mxchat_pinecone_host'] ?? '';
5287
5288 if (empty($api_key) || empty($host)) {
5289 return array();
5290 }
5291
5292 try {
5293 // Use multiple random vectors to get better coverage
5294 $all_matches = array();
5295 $seen_ids = array();
5296
5297 // Try 3 different random vectors to get better coverage
5298 for ($i = 0; $i < 3; $i++) {
5299 $query_url = "https://{$host}/query";
5300
5301 // Generate a random unit vector instead of zeros
5302 $random_vector = array();
5303 for ($j = 0; $j < 1536; $j++) {
5304 $random_vector[] = (rand(-1000, 1000) / 1000.0); // Random values between -1 and 1
5305 }
5306
5307 // Normalize the vector to unit length
5308 $magnitude = sqrt(array_sum(array_map(function($x) { return $x * $x; }, $random_vector)));
5309 if ($magnitude > 0) {
5310 $random_vector = array_map(function($x) use ($magnitude) { return $x / $magnitude; }, $random_vector);
5311 }
5312
5313 $query_data = array(
5314 'includeMetadata' => true,
5315 'includeValues' => false,
5316 'topK' => 10000, // Get many results
5317 'vector' => $random_vector
5318 );
5319
5320 $response = wp_remote_post($query_url, array(
5321 'headers' => array(
5322 'Api-Key' => $api_key,
5323 'Content-Type' => 'application/json'
5324 ),
5325 'body' => json_encode($query_data),
5326 'timeout' => 30
5327 ));
5328
5329 if (is_wp_error($response)) {
5330 continue;
5331 }
5332
5333 $response_code = wp_remote_retrieve_response_code($response);
5334 if ($response_code !== 200) {
5335 continue;
5336 }
5337
5338 $body = wp_remote_retrieve_body($response);
5339 $data = json_decode($body, true);
5340
5341 if (isset($data['matches'])) {
5342 foreach ($data['matches'] as $match) {
5343 $match_id = $match['id'] ?? '';
5344 if (!empty($match_id) && !isset($seen_ids[$match_id])) {
5345 $all_matches[] = $match;
5346 $seen_ids[$match_id] = true;
5347 }
5348 }
5349 }
5350 }
5351
5352 // Convert matches to processed data format
5353 $processed_data = array();
5354 $vector_ids_for_cache = array();
5355
5356 foreach ($all_matches as $match) {
5357 $metadata = $match['metadata'] ?? array();
5358 $source_url = $metadata['source_url'] ?? '';
5359 $match_id = $match['id'] ?? '';
5360
5361 if (!empty($source_url) && !empty($match_id)) {
5362 $post_id = url_to_postid($source_url);
5363 if ($post_id) {
5364 $created_at = $metadata['created_at'] ?? '';
5365 $processed_date = 'Recently'; // Default
5366
5367 if (!empty($created_at)) {
5368 $timestamp = is_numeric($created_at) ? $created_at : strtotime($created_at);
5369 if ($timestamp) {
5370 $processed_date = human_time_diff($timestamp, current_time('timestamp')) . ' ago';
5371 }
5372 }
5373
5374 $processed_data[$post_id] = array(
5375 'db_id' => $match_id,
5376 'processed_date' => $processed_date,
5377 'url' => $source_url,
5378 'source' => 'pinecone',
5379 'timestamp' => $timestamp ?? current_time('timestamp')
5380 );
5381
5382 $vector_ids_for_cache[] = $match_id;
5383 }
5384 }
5385 }
5386
5387 // Update the vector IDs cache for future use
5388 if (!empty($vector_ids_for_cache)) {
5389 update_option('mxchat_pinecone_vector_ids_cache', $vector_ids_for_cache);
5390 }
5391
5392 return $processed_data;
5393
5394 } catch (Exception $e) {
5395 error_log('Pinecone scan exception: ' . $e->getMessage());
5396 return array();
5397 }
5398 }
5399 /**
5400 * Update your existing display_admin_notices function to show notices on all MXChat pages
5401 */
5402 public function display_admin_notices() {
5403 // Check if we're on a MXChat admin page
5404 $screen = get_current_screen();
5405 if (!$screen || strpos($screen->base, 'mxchat') === false) {
5406 return;
5407 }
5408
5409 //error_log('MxChat admin_notices hook fired on screen: ' . $screen->base);
5410
5411 // Check for error notices
5412 $error_notice = get_transient('mxchat_admin_notice_error');
5413 if ($error_notice) {
5414 //error_log('Found error transient: ' . $error_notice);
5415 echo '<div class="notice notice-error is-dismissible"><p>' . wp_kses_post($error_notice) . '</p></div>';
5416 delete_transient('mxchat_admin_notice_error');
5417 //error_log('Displayed and deleted error transient');
5418 } else {
5419 //error_log('No error transient found');
5420 }
5421
5422 // Check for success notices
5423 $success_notice = get_transient('mxchat_admin_notice_success');
5424 if ($success_notice) {
5425 //error_log('Found success transient: ' . $success_notice);
5426 echo '<div class="notice notice-success is-dismissible"><p>' . wp_kses_post($success_notice) . '</p></div>';
5427 delete_transient('mxchat_admin_notice_success');
5428 //error_log('Displayed and deleted success transient');
5429 }
5430
5431 // Check for info notices
5432 $info_notice = get_transient('mxchat_admin_notice_info');
5433 if ($info_notice) {
5434 //error_log('Found info transient: ' . $info_notice);
5435 echo '<div class="notice notice-info is-dismissible"><p>' . wp_kses_post($info_notice) . '</p></div>';
5436 delete_transient('mxchat_admin_notice_info');
5437 //error_log('Displayed and deleted info transient');
5438 }
5439
5440 // Display active processing status
5441 $this->display_processing_status();
5442 }
5443
5444 /**
5445 * Display current processing status
5446 */
5447 private function display_processing_status() {
5448 $pdf_url = get_transient('mxchat_last_pdf_url');
5449 $sitemap_url = get_transient('mxchat_last_sitemap_url');
5450
5451 if (!$pdf_url && !$sitemap_url) {
5452 return;
5453 }
5454
5455 $pdf_status = $pdf_url ? $this->get_pdf_processing_status($pdf_url) : false;
5456 $sitemap_status = $sitemap_url ? $this->get_sitemap_processing_status($sitemap_url) : false;
5457
5458 if ($sitemap_status && isset($sitemap_status['error']) && !empty($sitemap_status['error'])) {
5459 echo '<div class="notice notice-error is-dismissible">';
5460 echo '<p><strong>' . esc_html__('Sitemap Processing Error:', 'mxchat') . '</strong> ' . esc_html($sitemap_status['error']) . '</p>';
5461 echo '</div>';
5462 }
5463
5464 if ($pdf_status && isset($pdf_status['error']) && !empty($pdf_status['error'])) {
5465 echo '<div class="notice notice-error is-dismissible">';
5466 echo '<p><strong>' . esc_html__('PDF Processing Error:', 'mxchat') . '</strong> ' . esc_html($pdf_status['error']) . '</p>';
5467 echo '</div>';
5468 }
5469 }
5470
5471 /**
5472 * Handle post updates and process content for the chatbot
5473 *
5474 * @param int $post_id The ID of the post being saved
5475 * @param WP_Post $post The post object
5476 * @param bool $update Whether this is an existing post being updated
5477 */
5478 public function handle_post_update($post_id, $post, $update) {
5479 // Basic validation checks
5480 if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE || wp_is_post_revision($post_id)) {
5481 return;
5482 }
5483
5484 // Only process published content
5485 if ($post->post_status !== 'publish') {
5486 return;
5487 }
5488
5489 $post_type = $post->post_type;
5490
5491 // Check if sync is enabled for this post type
5492 $should_sync = false;
5493
5494 // Check built-in post types first
5495 if ($post_type === 'post' && get_option('mxchat_auto_sync_posts') === '1') {
5496 $should_sync = true;
5497 } else if ($post_type === 'page' && get_option('mxchat_auto_sync_pages') === '1') {
5498 $should_sync = true;
5499 } else {
5500 // Check custom post types
5501 $option_name = 'mxchat_auto_sync_' . $post_type;
5502 if (get_option($option_name) === '1') {
5503 $should_sync = true;
5504 }
5505 }
5506
5507 if (!$should_sync) {
5508 return;
5509 }
5510
5511 // Get content with proper formatting (matching ajax_mxchat_process_selected_content)
5512 $title = get_the_title($post_id);
5513 $content = get_post_field('post_content', $post_id);
5514
5515 // Apply WordPress content filters to get properly formatted content
5516 $content = apply_filters('the_content', $content);
5517
5518 // Strip tags but preserve structure
5519 $content = wp_strip_all_tags($content);
5520
5521 // Combine title and content
5522 $final_content = $title . "\n\n" . $content;
5523
5524 // For custom post types like job_listing, include additional fields
5525 if ($post_type === 'job_listing') {
5526 // Add job-specific meta if available
5527 $job_location = get_post_meta($post_id, '_job_location', true);
5528 if (!empty($job_location)) {
5529 $final_content .= "\n\nLocation: " . $job_location;
5530 }
5531
5532 // Get job type terms
5533 $job_types = get_the_terms($post_id, 'job_listing_type');
5534 if (!empty($job_types) && !is_wp_error($job_types)) {
5535 $types = array();
5536 foreach ($job_types as $type) {
5537 $types[] = $type->name;
5538 }
5539 $final_content .= "\n\nJob Type: " . implode(', ', $types);
5540 }
5541
5542 // Get company name if available
5543 $company_name = get_post_meta($post_id, '_company_name', true);
5544 if (!empty($company_name)) {
5545 $final_content .= "\n\nCompany: " . $company_name;
5546 }
5547 }
5548
5549 // Get the source URL
5550 $source_url = get_permalink($post_id);
5551
5552 // Get API key with proper model detection
5553 $options = get_option('mxchat_options');
5554 $selected_model = $options['embedding_model'] ?? 'text-embedding-ada-002';
5555
5556 if (strpos($selected_model, 'voyage') === 0) {
5557 $api_key = $options['voyage_api_key'] ?? '';
5558 } elseif (strpos($selected_model, 'gemini-embedding') === 0) {
5559 $api_key = $options['gemini_api_key'] ?? '';
5560 } else {
5561 $api_key = $options['api_key'] ?? '';
5562 }
5563
5564 if (empty($api_key)) {
5565 error_log('MxChat Auto-sync: No API key configured for embedding model');
5566 return;
5567 }
5568
5569 // Use the centralized utility function for storage
5570 $result = MxChat_Utils::submit_content_to_db(
5571 $final_content,
5572 $source_url,
5573 $api_key,
5574 md5($source_url) // Vector ID for Pinecone
5575 );
5576
5577 if (is_wp_error($result)) {
5578 error_log('MxChat Auto-sync failed for post ' . $post_id . ': ' . $result->get_error_message());
5579 }
5580 }
5581
5582 /**
5583 * Handle deletion of posts from both Pinecone and WordPress DB
5584 *
5585 * @param int $post_id The ID of the post being deleted
5586 * @return void
5587 */
5588 public function mxchat_handle_post_delete($post_id) {
5589 // Get post data before it's deleted
5590 $post = get_post($post_id);
5591
5592 // Basic validation
5593 if (!$post || wp_is_post_revision($post_id)) {
5594 return;
5595 }
5596
5597 $post_type = $post->post_type;
5598
5599 // Check if sync is enabled for this post type
5600 $should_sync = false;
5601
5602 // Check built-in post types first
5603 if ($post_type === 'post' && get_option('mxchat_auto_sync_posts') === '1') {
5604 $should_sync = true;
5605 } else if ($post_type === 'page' && get_option('mxchat_auto_sync_pages') === '1') {
5606 $should_sync = true;
5607 } else {
5608 // Check custom post types
5609 $option_name = 'mxchat_auto_sync_' . $post_type;
5610 if (get_option($option_name) === '1') {
5611 $should_sync = true;
5612 }
5613 }
5614
5615 if (!$should_sync) {
5616 return;
5617 }
5618
5619 // Get the URL before post is deleted
5620 $source_url = get_permalink($post_id);
5621 if (!$source_url) {
5622 error_log('MXChat: Failed to get permalink for post ' . $post_id);
5623 return;
5624 }
5625
5626 // Check if Pinecone is enabled
5627 $pinecone_options = get_option('mxchat_pinecone_addon_options', array());
5628 $use_pinecone = ($pinecone_options['mxchat_use_pinecone'] ?? '0') === '1';
5629
5630 if ($use_pinecone && !empty($pinecone_options['mxchat_pinecone_api_key'])) {
5631 // Delete from Pinecone
5632 $this->delete_from_pinecone_by_url($source_url, $pinecone_options);
5633 } else {
5634 // Delete from WordPress DB
5635 global $wpdb;
5636 $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
5637
5638 $result = $wpdb->delete(
5639 $table_name,
5640 array('source_url' => $source_url),
5641 array('%s')
5642 );
5643
5644 if ($result === false) {
5645 error_log('MXChat: WordPress DB deletion failed for URL: ' . $source_url);
5646 }
5647 }
5648 }
5649
5650 /**
5651 * Helper function to delete from Pinecone by URL
5652 */
5653 private function delete_from_pinecone_by_url($source_url, $pinecone_options) {
5654 $host = $pinecone_options['mxchat_pinecone_host'] ?? '';
5655 $api_key = $pinecone_options['mxchat_pinecone_api_key'] ?? '';
5656
5657 if (empty($host) || empty($api_key)) {
5658 error_log('MXChat: Pinecone deletion failed - missing configuration');
5659 return false;
5660 }
5661
5662 $api_endpoint = "https://{$host}/vectors/delete";
5663 $vector_id = md5($source_url);
5664
5665 $request_body = array(
5666 'ids' => array($vector_id)
5667 );
5668
5669 $response = wp_remote_post($api_endpoint, array(
5670 'headers' => array(
5671 'Api-Key' => $api_key,
5672 'accept' => 'application/json',
5673 'content-type' => 'application/json'
5674 ),
5675 'body' => wp_json_encode($request_body),
5676 'timeout' => 30
5677 ));
5678
5679 if (is_wp_error($response)) {
5680 error_log('MXChat: Pinecone deletion error - ' . $response->get_error_message());
5681 return false;
5682 }
5683
5684 $response_code = wp_remote_retrieve_response_code($response);
5685 if ($response_code !== 200) {
5686 error_log('MXChat: Pinecone deletion failed with status ' . $response_code);
5687 return false;
5688 }
5689
5690 return true;
5691 }
5692
5693 /**
5694 * Handle WooCommerce product changes
5695 */
5696 public function mxchat_handle_product_change($post_id, $post, $update) {
5697 if ($post->post_type !== 'product') {
5698 return;
5699 }
5700
5701 if ($post->post_status === 'publish') {
5702 add_action('shutdown', function() use ($post_id) {
5703 $product = wc_get_product($post_id);
5704 if ($product) {
5705 $this->mxchat_store_product_embedding($product);
5706 }
5707 });
5708 }
5709 }
5710
5711 /**
5712 * Store WooCommerce product embeddings
5713 */
5714 private function mxchat_store_product_embedding($product) {
5715 if (!isset($this->options['enable_woocommerce_integration']) ||
5716 !in_array($this->options['enable_woocommerce_integration'], ['1', 'on'])) {
5717 return;
5718 }
5719
5720 $source_url = get_permalink($product->get_id());
5721
5722 // Build product content
5723 $title = $product->get_name();
5724 $description = $product->get_description();
5725 $short_description = $product->get_short_description();
5726 $regular_price = $product->get_regular_price();
5727 $sale_price = $product->get_sale_price();
5728 $sku = $product->get_sku();
5729
5730 // Format content consistently
5731 $content = $title . "\n\n";
5732
5733 if (!empty($description)) {
5734 $content .= wp_strip_all_tags($description) . "\n\n";
5735 }
5736
5737 if (!empty($short_description)) {
5738 $content .= "Short Description: " . wp_strip_all_tags($short_description) . "\n\n";
5739 }
5740
5741 $content .= "Price: $" . $regular_price . "\n";
5742
5743 if (!empty($sale_price)) {
5744 $content .= "Sale Price: $" . $sale_price . "\n";
5745 }
5746
5747 if (!empty($sku)) {
5748 $content .= "SKU: " . $sku . "\n";
5749 }
5750
5751 // Get API key with proper model detection
5752 $options = get_option('mxchat_options');
5753 $selected_model = $options['embedding_model'] ?? 'text-embedding-ada-002';
5754
5755 if (strpos($selected_model, 'voyage') === 0) {
5756 $api_key = $options['voyage_api_key'] ?? '';
5757 } elseif (strpos($selected_model, 'gemini-embedding') === 0) {
5758 $api_key = $options['gemini_api_key'] ?? '';
5759 } else {
5760 $api_key = $options['api_key'] ?? '';
5761 }
5762
5763 if (empty($api_key)) {
5764 error_log('MxChat Auto-sync: No API key configured for embedding model');
5765 return;
5766 }
5767
5768 // Use the centralized utility function for storage
5769 $result = MxChat_Utils::submit_content_to_db(
5770 $content,
5771 $source_url,
5772 $api_key,
5773 md5($source_url) // Vector ID for Pinecone
5774 );
5775
5776 if (is_wp_error($result)) {
5777 error_log('MxChat WooCommerce sync failed for product ' . $product->get_id() . ': ' . $result->get_error_message());
5778 }
5779 }
5780
5781
5782 /**
5783 * Handle WooCommerce product deletion
5784 */
5785 public function mxchat_handle_product_delete($post_id) {
5786 if (get_post_type($post_id) !== 'product') {
5787 return;
5788 }
5789
5790 $source_url = get_permalink($post_id);
5791
5792 // Check if Pinecone is enabled
5793 $pinecone_options = get_option('mxchat_pinecone_addon_options', array());
5794 $use_pinecone = ($pinecone_options['mxchat_use_pinecone'] ?? '0') === '1';
5795
5796 if ($use_pinecone && !empty($pinecone_options['mxchat_pinecone_api_key'])) {
5797 // Delete from Pinecone
5798 $this->delete_from_pinecone_by_url($source_url, $pinecone_options);
5799 } else {
5800 // Delete from WordPress DB
5801 global $wpdb;
5802 $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
5803
5804 $wpdb->delete(
5805 $table_name,
5806 array('source_url' => $source_url),
5807 array('%s')
5808 );
5809 }
5810 }
5811
5812 /**
5813 * Delete vectors from Pinecone by source URL
5814 *
5815 * @param array $urls Array of source URLs to delete
5816 * @param string $api_key Pinecone API key
5817 * @param string $environment Pinecone environment
5818 * @param string $index_name Pinecone index name
5819 * @return array Associative array with 'success' boolean and 'message' string
5820 */
5821 private function delete_from_pinecone($urls, $api_key, $environment, $index_name) {
5822 // Get the Pinecone host from options (matching your store_in_pinecone_main pattern)
5823 $options = get_option('mxchat_pinecone_addon_options');
5824 $host = $options['mxchat_pinecone_host'] ?? '';
5825
5826 if (empty($host)) {
5827 return array(
5828 'success' => false,
5829 'message' => 'Pinecone host is not configured. Please set the host in your settings.'
5830 );
5831 }
5832
5833 // Build API endpoint using the configured host
5834 $api_endpoint = "https://{$host}/vectors/delete";
5835
5836 // Create vector IDs from URLs (matching your store method's ID generation)
5837 $vector_ids = array_map('md5', $urls);
5838
5839 // Prepare the delete request body
5840 $request_body = array(
5841 'ids' => $vector_ids,
5842 'filter' => array(
5843 'source_url' => array(
5844 '$in' => $urls
5845 )
5846 )
5847 );
5848
5849 // Make the deletion request
5850 $response = wp_remote_post($api_endpoint, array(
5851 'headers' => array(
5852 'Api-Key' => $api_key,
5853 'accept' => 'application/json',
5854 'content-type' => 'application/json'
5855 ),
5856 'body' => wp_json_encode($request_body),
5857 'timeout' => 30,
5858 'data_format' => 'body'
5859 ));
5860
5861 // Handle WordPress HTTP API errors
5862 if (is_wp_error($response)) {
5863 return array(
5864 'success' => false,
5865 'message' => $response->get_error_message()
5866 );
5867 }
5868
5869 // Check response status
5870 $response_code = wp_remote_retrieve_response_code($response);
5871 if ($response_code !== 200) {
5872 $body = wp_remote_retrieve_body($response);
5873 return array(
5874 'success' => false,
5875 'message' => sprintf(
5876 'Pinecone API error (HTTP %d): %s',
5877 $response_code,
5878 $body
5879 )
5880 );
5881 }
5882
5883 // Parse response body
5884 $body = wp_remote_retrieve_body($response);
5885 $response_data = json_decode($body, true);
5886
5887 // Final validation of the response
5888 if (json_last_error() !== JSON_ERROR_NONE) {
5889 return array(
5890 'success' => false,
5891 'message' => 'Failed to parse Pinecone response: ' . json_last_error_msg()
5892 );
5893 }
5894
5895 return array(
5896 'success' => true,
5897 'message' => sprintf('Successfully deleted %d vectors from Pinecone', count($vector_ids))
5898 );
5899 }
5900
5901
5902 public function mxchat_create_activation_page() {
5903 $license_status = get_option('mxchat_license_status', 'inactive');
5904 $license_error = get_option('mxchat_license_error', '');
5905 ?>
5906 <div class="wrap mxchat-admin-activation">
5907 <div class="mxchat-pro-hero">
5908 <h1 class="pro-title">
5909 <span class="pro-gradient-text">Activate</span> MxChat Pro
5910 </h1>
5911 <p class="pro-subtitle">
5912 <?php esc_html_e('Enter your license key to unlock premium features, advanced AI capabilities, and priority support.', 'mxchat'); ?>
5913 </p>
5914 </div>
5915
5916 <?php if ($license_status === 'inactive' && !empty($license_error)): ?>
5917 <div class="error notice">
5918 <p><?php echo esc_html($license_error); ?></p>
5919 </div>
5920 <?php endif; ?>
5921
5922 <form id="mxchat-activation-form" class="mxchat-pro-form" style="<?php echo $license_status === 'active' ? 'display: none;' : ''; ?>">
5923 <div class="mxchat-pro-form-container">
5924 <table class="form-table">
5925 <tr valign="top">
5926 <th scope="row"><?php esc_html_e('Email Address', 'mxchat'); ?></th>
5927 <td>
5928 <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 />
5929 </td>
5930 </tr>
5931 <tr valign="top">
5932 <th scope="row"><?php esc_html_e('Activation Key', 'mxchat'); ?></th>
5933 <td>
5934 <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 />
5935 </td>
5936 </tr>
5937 </table>
5938 <?php if ($license_status !== 'active'): ?>
5939 <div class="mxchat-pro-button-container">
5940 <button type="submit" id="activate_license_button" class="button button-primary mxchat-pro-button"><?php esc_html_e('Activate License', 'mxchat'); ?></button>
5941 <div id="mxchat-activation-spinner" class="mxchat-activation-spinner" style="display: none;"></div>
5942 </div>
5943 <?php endif; ?>
5944 </div>
5945 </form>
5946 <!-- License Status Display -->
5947 <div class="mxchat-pro-status">
5948 <h3><?php esc_html_e('License Status:', 'mxchat'); ?>
5949 <span id="mxchat-license-status" class="mxchat-status-badge <?php echo $license_status; ?>">
5950 <?php echo $license_status === 'active' ? esc_html__('Active', 'mxchat') : esc_html__('Inactive', 'mxchat'); ?>
5951 </span>
5952 </h3>
5953 </div>
5954
5955 </div>
5956 <?php
5957 }
5958
5959 public function mxchat_actions_page_html() {
5960 if (!current_user_can('manage_options')) {
5961 return;
5962 }
5963
5964 // Keep existing data fetching logic
5965 global $wpdb;
5966 $table_name = $wpdb->prefix . 'mxchat_intents';
5967 $page = isset($_GET['paged']) ? max(1, intval($_GET['paged'])) : 1;
5968 $per_page = 20;
5969 $offset = ($page - 1) * $per_page;
5970
5971 // Success message
5972 if (isset($_GET['updated']) && $_GET['updated'] === 'true') {
5973 echo '<div class="notice notice-success is-dismissible"><p>' .
5974 esc_html__('Action updated successfully.', 'mxchat') .
5975 '</p></div>';
5976 }
5977
5978 // Filtering logic
5979 $where = '1=1';
5980 $search_term = isset($_GET['s']) ? trim($_GET['s']) : '';
5981 $callback_filter = isset($_GET['callback_filter']) ? sanitize_text_field($_GET['callback_filter']) : '';
5982
5983 if ($search_term) {
5984 $search_term_like = '%' . $wpdb->esc_like($search_term) . '%';
5985 $where .= $wpdb->prepare(' AND (intent_label LIKE %s OR phrases LIKE %s)',
5986 $search_term_like, $search_term_like);
5987 }
5988
5989 if ($callback_filter) {
5990 $where .= $wpdb->prepare(' AND callback_function = %s', $callback_filter);
5991 }
5992
5993 // Pagination
5994 $total_intents = $wpdb->get_var("SELECT COUNT(*) FROM $table_name WHERE $where");
5995 $total_pages = ceil($total_intents / $per_page);
5996
5997 // Get intents (now called actions)
5998 $actions = $wpdb->get_results($wpdb->prepare(
5999 "SELECT * FROM $table_name WHERE $where LIMIT %d OFFSET %d",
6000 $per_page, $offset
6001 ));
6002
6003 // Get callbacks
6004 $available_callbacks = $this->mxchat_get_available_callbacks();
6005
6006 ?>
6007 <div class="wrap mxchat-wrapper">
6008 <!-- Hero Section -->
6009 <div class="mxchat-hero">
6010 <h1 class="mxchat-main-title">
6011 <span class="mxchat-gradient-text">Actions</span> Manager
6012 </h1>
6013 <p class="mxchat-hero-subtitle">
6014 <?php esc_html_e('Create and manage custom actions to enhance your chatbot\'s capabilities.', 'mxchat'); ?>
6015 </p>
6016 </div>
6017
6018 <!-- Actions Header with Search and Filter -->
6019 <div class="mxchat-actions-header">
6020 <div class="mxchat-actions-filters">
6021 <form method="get" class="mxchat-search-form">
6022 <input type="hidden" name="page" value="mxchat-actions">
6023 <div class="mxchat-search-group">
6024 <span class="dashicons dashicons-search"></span>
6025 <input type="text" name="s" class="mxchat-search-input"
6026 placeholder="<?php esc_attr_e('Search Actions', 'mxchat'); ?>"
6027 value="<?php echo esc_attr($search_term); ?>">
6028 </div>
6029 <select name="callback_filter" class="mxchat-action-filter">
6030 <option value=""><?php esc_html_e('All Action Types', 'mxchat'); ?></option>
6031 <?php foreach ($available_callbacks as $function => $callback_data) :
6032 $label = $callback_data['label']; ?>
6033 <option value="<?php echo esc_attr($function); ?>"
6034 <?php selected($callback_filter, $function); ?>>
6035 <?php echo esc_html($label); ?>
6036 </option>
6037 <?php endforeach; ?>
6038 </select>
6039 <button type="submit" class="mxchat-button-secondary">
6040 <?php esc_html_e('Filter', 'mxchat'); ?>
6041 </button>
6042 </form>
6043 </div>
6044 <div class="mxchat-actions-controls">
6045 <button type="button" id="mxchat-add-action-btn" class="mxchat-button-primary">
6046 <span class="dashicons dashicons-plus-alt"></span>
6047 <?php esc_html_e('Add New Action', 'mxchat'); ?>
6048 </button>
6049 </div>
6050 </div>
6051
6052 <!-- Actions Grid Layout - All actions in a single grid -->
6053 <div class="mxchat-actions-grid">
6054 <div class="mxchat-cards-container">
6055 <?php if (!empty($actions)) : ?>
6056 <?php foreach ($actions as $action) :
6057 $callback_function = $action->callback_function;
6058 $callback_label = isset($available_callbacks[$callback_function]['label'])
6059 ? $available_callbacks[$callback_function]['label']
6060 : $callback_function;
6061 $threshold_value = isset($action->similarity_threshold)
6062 ? round($action->similarity_threshold * 100)
6063 : 85;
6064
6065 // Check if this is a form action
6066 $is_form_action = strpos($action->intent_label, 'Form ') === 0;
6067
6068 // Get action status (enabled/disabled) - default to true if column doesn't exist
6069 $is_enabled = isset($action->enabled) ? (bool)$action->enabled : true;
6070 ?>
6071 <div class="mxchat-action-card <?php echo $is_form_action ? 'mxchat-form-action' : ''; ?>">
6072 <div class="mxchat-card-header">
6073 <div class="mxchat-card-title"><?php echo esc_html($action->intent_label); ?></div>
6074 <div class="mxchat-card-toggle">
6075 <label class="mxchat-switch">
6076 <input type="checkbox" class="mxchat-action-toggle"
6077 data-action-id="<?php echo esc_attr($action->id); ?>"
6078 <?php checked($is_enabled); ?>>
6079 <span class="mxchat-slider round"></span>
6080 </label>
6081 </div>
6082 </div>
6083
6084 <div class="mxchat-card-body">
6085 <div class="mxchat-card-description">
6086 <strong><?php esc_html_e('Type:', 'mxchat'); ?></strong>
6087 <?php echo esc_html($callback_label); ?>
6088 </div>
6089
6090 <div class="mxchat-card-phrases">
6091 <strong><?php esc_html_e('Trigger phrases:', 'mxchat'); ?></strong>
6092 <div class="mxchat-phrases-preview">
6093 <?php
6094 // Check if the helper function exists, otherwise use a simple substring
6095 if (method_exists($this, 'get_trimmed_phrases')) {
6096 echo esc_html($this->get_trimmed_phrases($action->phrases));
6097 } else {
6098 echo esc_html(strlen($action->phrases) > 100 ?
6099 substr($action->phrases, 0, 97) . '...' :
6100 $action->phrases);
6101 }
6102 ?>
6103 </div>
6104 </div>
6105
6106 <div class="mxchat-threshold-control">
6107 <div class="mxchat-threshold-label">
6108 <?php esc_html_e('Similarity Threshold:', 'mxchat'); ?>
6109 <span class="mxchat-threshold-value"><?php echo esc_html($threshold_value); ?>%</span>
6110 </div>
6111 </div>
6112 </div>
6113
6114 <div class="mxchat-card-footer">
6115 <?php
6116 // Check if it's a form action
6117 $is_form_action = preg_match('/Form (\d+)/', $action->intent_label, $form_matches);
6118
6119 // Check if it's a recommendation flow action
6120 $is_flow_action = preg_match('/Recommendation Flow (\d+)/', $action->intent_label, $flow_matches);
6121
6122 if ($is_form_action) {
6123 $form_id = isset($form_matches[1]) ? $form_matches[1] : '';
6124 ?>
6125 <a href="<?php echo esc_url(admin_url('admin.php?page=mxchat-forms&action=edit&form_id=' . $form_id)); ?>"
6126 class="mxchat-button-primary">
6127 <span class="dashicons dashicons-feedback"></span>
6128 <?php esc_html_e('Edit Form', 'mxchat'); ?>
6129 </a>
6130 <?php } elseif ($is_flow_action) {
6131 ?>
6132 <a href="<?php echo esc_url(admin_url('admin.php?page=mxchat-smart-recommender')); ?>"
6133 class="mxchat-button-primary">
6134 <span class="dashicons dashicons-list-view"></span>
6135 <?php esc_html_e('Manage Flows', 'mxchat'); ?>
6136 </a>
6137 <?php } else { ?>
6138 <button type="button"
6139 class="mxchat-button-secondary mxchat-edit-button"
6140 data-action-id="<?php echo esc_attr($action->id); ?>"
6141 data-phrases="<?php echo esc_attr($action->phrases); ?>"
6142 data-label="<?php echo esc_attr($action->intent_label); ?>"
6143 data-threshold="<?php echo esc_attr(round($action->similarity_threshold * 100)); ?>"
6144 data-callback-function="<?php echo esc_attr($action->callback_function); ?>">
6145 <span class="dashicons dashicons-edit"></span>
6146 <?php esc_html_e('Edit', 'mxchat'); ?>
6147 </button>
6148 <form method="post"
6149 action="<?php echo esc_url(admin_url('admin-post.php')); ?>"
6150 class="mxchat-delete-form"
6151 onsubmit="return confirm('<?php esc_attr_e('Are you sure you want to delete this action?', 'mxchat'); ?>');">
6152 <?php wp_nonce_field('mxchat_delete_intent_nonce'); ?>
6153 <input type="hidden" name="action" value="mxchat_delete_intent">
6154 <input type="hidden" name="intent_id" value="<?php echo esc_attr($action->id); ?>">
6155 <button type="submit" class="mxchat-button-text mxchat-delete-button">
6156 <span class="dashicons dashicons-trash"></span>
6157 <?php esc_html_e('Delete', 'mxchat'); ?>
6158 </button>
6159 </form>
6160 <?php } ?>
6161 </div>
6162
6163
6164 </div>
6165 <?php endforeach; ?>
6166 <?php else : ?>
6167 <!-- If no actions found -->
6168 <div class="mxchat-no-actions">
6169 <div class="mxchat-empty-state">
6170 <span class="dashicons dashicons-format-chat"></span>
6171 <h2><?php esc_html_e('No actions found', 'mxchat'); ?></h2>
6172 <p><?php esc_html_e('Get started by creating your first action to enhance your chatbot.', 'mxchat'); ?></p>
6173 <button type="button" id="mxchat-create-first-action" class="mxchat-button-primary">
6174 <?php esc_html_e('Create Your First Action', 'mxchat'); ?>
6175 </button>
6176 </div>
6177 </div>
6178 <?php endif; ?>
6179 </div>
6180 </div>
6181
6182 <?php if ($total_pages > 1) : ?>
6183 <div class="mxchat-pagination">
6184 <?php
6185 echo paginate_links(array(
6186 'base' => add_query_arg('paged', '%#%'),
6187 'format' => '',
6188 'prev_text' => __('&laquo; Previous', 'mxchat'),
6189 'next_text' => __('Next &raquo;', 'mxchat'),
6190 'total' => $total_pages,
6191 'current' => $page
6192 ));
6193 ?>
6194 </div>
6195 <?php endif; ?>
6196
6197 <!-- Add/Edit Action Modal with Step-Based Approach -->
6198 <!-- Complete Modal HTML with Defined Groups Variable -->
6199 <div id="mxchat-action-modal" class="mxchat-modal" style="display: none;">
6200 <div class="mxchat-modal-content">
6201 <span class="mxchat-modal-close">&times;</span>
6202
6203 <form id="mxchat-action-form" method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>">
6204 <!-- Dynamic nonce field -->
6205 <div id="action-nonce-container">
6206 <?php wp_nonce_field('mxchat_add_intent_nonce', 'add_intent_nonce'); ?>
6207 </div>
6208 <input type="hidden" name="action" id="form_action_type" value="mxchat_add_intent">
6209 <input type="hidden" name="intent_id" id="edit_action_id" value="">
6210 <input type="hidden" name="callback_function" id="callback_function" value="">
6211
6212 <!-- Step 1: Action Type Selection -->
6213 <div id="mxchat-action-step-1" class="mxchat-action-step active">
6214 <div class="mxchat-step-indicator">
6215 <div class="mxchat-step-number">1</div>
6216 <div class="mxchat-step-title"><?php esc_html_e('Select Action Type', 'mxchat'); ?></div>
6217 </div>
6218
6219 <div id="mxchat-action-type-selector" class="mxchat-action-type-selector">
6220 <div class="mxchat-action-type-search">
6221 <span class="dashicons dashicons-search"></span>
6222 <input type="text" id="action-type-search" placeholder="<?php esc_attr_e('Search action types...', 'mxchat'); ?>" class="mxchat-action-type-search-input">
6223 </div>
6224
6225 <?php
6226 // Get the callbacks - IMPORTANT: Define the $groups variable here
6227 $groups = $this->mxchat_get_available_callbacks(true, true);
6228 ?>
6229
6230 <div class="mxchat-action-type-categories">
6231 <button type="button" class="mxchat-category-button active" data-category="all"><?php esc_html_e('All', 'mxchat'); ?></button>
6232 <?php
6233 // Get unique categories from the defined groups
6234 foreach ($groups as $group_label => $group_callbacks) :
6235 $category_slug = sanitize_title($group_label);
6236 ?>
6237 <button type="button" class="mxchat-category-button" data-category="<?php echo esc_attr($category_slug); ?>"><?php echo esc_html($group_label); ?></button>
6238 <?php endforeach; ?>
6239 </div>
6240
6241 <div class="mxchat-action-types-grid">
6242 <?php
6243 // Generate action cards from available callbacks
6244 foreach ($groups as $group_label => $group_callbacks) :
6245 $category_slug = sanitize_title($group_label);
6246
6247 foreach ($group_callbacks as $function => $data) :
6248 $label = $data['label'];
6249 $pro_only = $data['pro_only'];
6250 $icon = isset($data['icon']) ? $data['icon'] : 'admin-generic';
6251 $description = isset($data['description']) ? $data['description'] : '';
6252 $is_addon = isset($data['addon']) && $data['addon'] !== false;
6253 $addon_name = isset($data['addon_name']) ? $data['addon_name'] : '';
6254 $is_installed = isset($data['installed']) ? $data['installed'] : true;
6255
6256 // Determine card status and styling
6257 $card_class = 'mxchat-action-type-card';
6258 $icon_class = 'mxchat-action-type-icon';
6259 $status_badge = '';
6260
6261 if ($pro_only && !$this->is_activated) {
6262 // Pro feature but no Pro license
6263 $icon_class .= ' pro-feature';
6264 $status_badge = '<span class="mxchat-pro-badge">' . esc_html__('Pro', 'mxchat') . '</span>';
6265 }
6266
6267 if ($is_addon && !$is_installed) {
6268 // Add-on not installed
6269 $card_class .= ' not-installed';
6270 $status_badge .= '<span class="mxchat-addon-badge">' . esc_html__('Add-on Required', 'mxchat') . '</span>';
6271 }
6272
6273 // Default description if none provided
6274 if (empty($description)) {
6275 $description = sprintf(
6276 esc_html__('Use the %s action in your chatbot', 'mxchat'),
6277 $label
6278 );
6279 }
6280 ?>
6281 <div class="<?php echo esc_attr($card_class); ?>"
6282 data-category="<?php echo esc_attr($category_slug); ?>"
6283 data-value="<?php echo esc_attr($function); ?>"
6284 data-label="<?php echo esc_attr($label); ?>"
6285 data-pro="<?php echo $pro_only ? 'true' : 'false'; ?>"
6286 data-addon="<?php echo esc_attr($is_addon ? $data['addon'] : ''); ?>"
6287 data-installed="<?php echo $is_installed ? 'true' : 'false'; ?>">
6288 <div class="<?php echo esc_attr($icon_class); ?>">
6289 <span class="dashicons dashicons-<?php echo esc_attr($icon); ?>"></span>
6290 </div>
6291 <div class="mxchat-action-type-info">
6292 <h4><?php echo esc_html($label); ?></h4>
6293 <p><?php echo esc_html($description); ?></p>
6294 <?php if (!empty($status_badge)) : ?>
6295 <?php echo $status_badge; ?>
6296 <?php endif; ?>
6297
6298 <?php if ($is_addon && !$is_installed) : ?>
6299 <div class="mxchat-addon-info">
6300 <?php echo esc_html(sprintf(
6301 __('Requires %s', 'mxchat'),
6302 $addon_name
6303 )); ?>
6304 </div>
6305 <?php endif; ?>
6306 </div>
6307 </div>
6308 <?php endforeach;
6309 endforeach; ?>
6310 </div>
6311 </div>
6312
6313 <div class="mxchat-modal-actions">
6314 <button type="button" class="mxchat-button-secondary mxchat-modal-cancel">
6315 <?php esc_html_e('Cancel', 'mxchat'); ?>
6316 </button>
6317 </div>
6318 </div>
6319
6320 <!-- Step 2: Action Configuration -->
6321 <div id="mxchat-action-step-2" class="mxchat-action-step">
6322 <div class="mxchat-step-indicator">
6323 <div class="mxchat-step-number">2</div>
6324 <div class="mxchat-step-title"><?php esc_html_e('Configure Action', 'mxchat'); ?></div>
6325 </div>
6326
6327 <div class="mxchat-selected-action">
6328 <button type="button" class="mxchat-back-button" id="mxchat-back-to-step-1">
6329 <span class="dashicons dashicons-arrow-left-alt"></span>
6330 <?php esc_html_e('Back to Action Types', 'mxchat'); ?>
6331 </button>
6332 <div class="mxchat-selected-action-info">
6333 <div id="selected-action-icon" class="mxchat-action-type-icon">
6334 <span class="dashicons dashicons-admin-generic"></span>
6335 </div>
6336 <div class="mxchat-selected-action-details">
6337 <h3 id="selected-action-title"><?php esc_html_e('Selected Action', 'mxchat'); ?></h3>
6338 <p id="selected-action-description"><?php esc_html_e('Configure this action for your chatbot', 'mxchat'); ?></p>
6339 </div>
6340 </div>
6341 </div>
6342
6343 <div class="mxchat-form-group">
6344 <label for="intent_label">
6345 <?php esc_html_e('Action Label (For your reference only)', 'mxchat'); ?>
6346 </label>
6347 <input name="intent_label" type="text" id="intent_label" required
6348 class="mxchat-intent-input"
6349 placeholder="<?php esc_attr_e('Example: Newsletter Signup', 'mxchat'); ?>">
6350 </div>
6351
6352 <div class="mxchat-form-group">
6353 <label for="phrases">
6354 <?php esc_html_e('Trigger Phrases (comma-separated)', 'mxchat'); ?>
6355 </label>
6356 <textarea name="phrases" id="action_phrases" rows="5" required
6357 class="mxchat-intent-textarea"
6358 placeholder="<?php esc_attr_e('Example: sign me up, subscribe me, I want to join, add me to the newsletter', 'mxchat'); ?>"></textarea>
6359 </div>
6360
6361 <div class="mxchat-form-group">
6362 <label for="similarity_threshold">
6363 <?php esc_html_e('Similarity Threshold', 'mxchat'); ?>
6364 <span class="mxchat-threshold-value-display">85%</span>
6365 </label>
6366 <div class="mxchat-slider-group modal-slider">
6367 <input type="range"
6368 name="similarity_threshold"
6369 id="similarity_threshold"
6370 min="70"
6371 max="95"
6372 value="85"
6373 class="mxchat-intent-slider"
6374 oninput="document.querySelector('.mxchat-threshold-value-display').textContent = this.value + '%'">
6375 </div>
6376 <div class="mxchat-threshold-hint">
6377 <?php esc_html_e('Lower values make the action trigger more easily. Higher values require more exact matches.', 'mxchat'); ?>
6378 </div>
6379 </div>
6380
6381 <div class="mxchat-modal-actions">
6382 <button type="button" class="mxchat-button-secondary mxchat-modal-cancel">
6383 <?php esc_html_e('Cancel', 'mxchat'); ?>
6384 </button>
6385 <button type="submit" class="mxchat-button-primary" id="mxchat-save-action-btn">
6386 <?php esc_html_e('Save Action', 'mxchat'); ?>
6387 </button>
6388 </div>
6389 </div>
6390 </form>
6391 </div>
6392 </div>
6393
6394 <div id="mxchat-action-loading" class="mxchat-action-loading" style="display: none;">
6395 <div class="mxchat-action-loading-spinner"></div>
6396 <div class="mxchat-action-loading-text">
6397 <?php esc_html_e('Saving action, please wait...', 'mxchat'); ?>
6398 </div>
6399 </div>
6400 </div><!-- .mxchat-wrapper -->
6401 <?php
6402 }
6403
6404 /**
6405 * Helper method to trim phrases for display
6406 * Adding this in case it doesn't exist in your class
6407 */
6408 private function get_trimmed_phrases($phrases, $max_length = 100) {
6409 if (strlen($phrases) <= $max_length) {
6410 return $phrases;
6411 }
6412
6413 $trimmed = substr($phrases, 0, $max_length);
6414 $last_comma = strrpos($trimmed, ',');
6415
6416 if ($last_comma !== false) {
6417 $trimmed = substr($trimmed, 0, $last_comma);
6418 }
6419
6420 return $trimmed . '...';
6421 }
6422
6423 /**
6424 * AJAX handler for toggling an action on/off
6425 */
6426 public function mxchat_toggle_action() {
6427 // Check nonce
6428 if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'mxchat_actions_nonce')) {
6429 wp_send_json_error(array('message' => 'Security check failed'));
6430 return;
6431 }
6432
6433 // Check permissions
6434 if (!current_user_can('manage_options')) {
6435 wp_send_json_error(array('message' => 'Permission denied'));
6436 return;
6437 }
6438
6439 // Validate params
6440 $intent_id = isset($_POST['intent_id']) ? intval($_POST['intent_id']) : 0;
6441 $enabled = isset($_POST['enabled']) ? (bool)$_POST['enabled'] : false;
6442
6443 if (!$intent_id) {
6444 wp_send_json_error(array('message' => 'Invalid action ID'));
6445 return;
6446 }
6447
6448 // Update the intent/action status in the database
6449 global $wpdb;
6450 $table_name = $wpdb->prefix . 'mxchat_intents';
6451
6452 // Using the 'enabled' field - add this field if it doesn't exist
6453 $result = $wpdb->update(
6454 $table_name,
6455 array('enabled' => $enabled ? 1 : 0),
6456 array('id' => $intent_id),
6457 array('%d'),
6458 array('%d')
6459 );
6460
6461 if ($result === false) {
6462 wp_send_json_error(array('message' => 'Database error'));
6463 return;
6464 }
6465
6466 wp_send_json_success();
6467 }
6468
6469 /**
6470 * Add the 'enabled' column to the intents table if it doesn't exist
6471 * Call this during plugin activation or update
6472 */
6473 public function mxchat_add_enabled_column_to_intents() {
6474 global $wpdb;
6475 $table_name = $wpdb->prefix . 'mxchat_intents';
6476
6477 // Check if the column already exists
6478 $columns = $wpdb->get_results("SHOW COLUMNS FROM $table_name LIKE 'enabled'");
6479
6480 if (empty($columns)) {
6481 // Add the column with default value of 1 (enabled)
6482 $wpdb->query("ALTER TABLE $table_name ADD COLUMN enabled TINYINT(1) NOT NULL DEFAULT 1");
6483 }
6484 }
6485
6486 /**
6487 * Handle embedding generation errors using existing admin notice system
6488 *
6489 * @param string $message Error message to display
6490 * @param bool $redirect Whether to redirect back to the actions page
6491 * @return void
6492 */
6493 private function handle_embedding_error($message, $redirect = true) {
6494 // Store the error message in the existing transient
6495 set_transient('mxchat_admin_notice_error', $message, 60);
6496
6497 if ($redirect) {
6498 // Redirect back to the actions page
6499 $redirect_url = add_query_arg(
6500 array(
6501 'page' => 'mxchat-actions'
6502 ),
6503 admin_url('admin.php')
6504 );
6505 wp_safe_redirect($redirect_url);
6506 exit;
6507 }
6508 }
6509
6510 /**
6511 * Handle editing of intent phrases - with improved error handling
6512 *
6513 * @since 1.0.0
6514 * @return void
6515 */
6516 public function mxchat_handle_edit_intent() {
6517 // Security checks (nonce and permissions)
6518 if (!current_user_can('manage_options')) {
6519 wp_die(esc_html__('Unauthorized user', 'mxchat'));
6520 }
6521 check_admin_referer('mxchat_edit_intent');
6522
6523 // Get POST data
6524 $intent_id = isset($_POST['intent_id']) ? absint($_POST['intent_id']) : 0;
6525 $intent_label = isset($_POST['intent_label']) ? sanitize_text_field($_POST['intent_label']) : '';
6526 $phrases_input = isset($_POST['phrases']) ? sanitize_textarea_field($_POST['phrases']) : '';
6527 $threshold_percentage = isset($_POST['similarity_threshold']) ? intval($_POST['similarity_threshold']) : 85;
6528 $similarity_threshold = min(95, max(70, $threshold_percentage)) / 100; // Convert to 0.70–0.95
6529
6530 // Validate inputs
6531 if (!$intent_id || empty($intent_label) || empty($phrases_input)) {
6532 $this->handle_embedding_error(__('Invalid input. Please ensure all fields are filled out.', 'mxchat'));
6533 return;
6534 }
6535
6536 $phrases_array = array_map('sanitize_text_field', array_filter(array_map('trim', explode(',', $phrases_input))));
6537 if (empty($phrases_array)) {
6538 $this->handle_embedding_error(__('Please enter at least one valid phrase.', 'mxchat'));
6539 return;
6540 }
6541
6542 // Generate embeddings with improved error handling
6543 $vectors = [];
6544 $failed_phrases = [];
6545
6546 foreach ($phrases_array as $phrase) {
6547 $embedding_vector = $this->mxchat_generate_embedding($phrase, $this->options['api_key']);
6548 if (is_array($embedding_vector)) {
6549 $vectors[] = $embedding_vector;
6550 } else {
6551 $failed_phrases[] = $phrase;
6552 }
6553 }
6554
6555 if (!empty($failed_phrases)) {
6556 $this->handle_embedding_error(
6557 sprintf(
6558 __('Error generating embeddings for phrases: %s. Check your embedding API.', 'mxchat'),
6559 implode(', ', $failed_phrases)
6560 )
6561 );
6562 return;
6563 }
6564
6565 if (empty($vectors)) {
6566 $this->handle_embedding_error(__('No valid embeddings generated. Please check your phrases.', 'mxchat'));
6567 return;
6568 }
6569
6570 $combined_vector = $this->mxchat_average_vectors($vectors);
6571 $serialized_vector = maybe_serialize($combined_vector);
6572
6573 // Update the database
6574 global $wpdb;
6575 $table_name = $wpdb->prefix . 'mxchat_intents';
6576
6577 $result = $wpdb->update(
6578 $table_name,
6579 array(
6580 'intent_label' => $intent_label,
6581 'phrases' => implode(', ', $phrases_array),
6582 'embedding_vector' => $serialized_vector,
6583 'similarity_threshold' => $similarity_threshold
6584 ),
6585 array('id' => $intent_id),
6586 array('%s', '%s', '%s', '%f'), // Format: string, string, string, float
6587 array('%d') // Where format: integer
6588 );
6589
6590 if (false === $result) {
6591 $this->handle_embedding_error(__('Failed to update action in database.', 'mxchat'));
6592 return;
6593 }
6594
6595 // Set success message and redirect
6596 set_transient('mxchat_admin_notice_success', __('Intent updated successfully!', 'mxchat'), 60);
6597
6598 $redirect_url = add_query_arg(
6599 array(
6600 'page' => 'mxchat-actions'
6601 ),
6602 admin_url('admin.php')
6603 );
6604 wp_safe_redirect($redirect_url);
6605 exit;
6606 }
6607
6608 /**
6609 * Handle adding new intent - with improved error handling
6610 *
6611 * @return void
6612 */
6613 public function mxchat_handle_add_intent() {
6614 if (!current_user_can('manage_options')) {
6615 wp_die(esc_html__('Unauthorized user', 'mxchat'));
6616 }
6617
6618 check_admin_referer('mxchat_add_intent_nonce');
6619
6620 global $wpdb;
6621 $table_name = $wpdb->prefix . 'mxchat_intents';
6622
6623 $intent_label = isset($_POST['intent_label']) ? sanitize_text_field($_POST['intent_label']) : '';
6624 $phrases_input = isset($_POST['phrases']) ? sanitize_textarea_field($_POST['phrases']) : '';
6625 $callback_function = isset($_POST['callback_function']) ? sanitize_text_field($_POST['callback_function']) : '';
6626 $default_threshold = 0.85;
6627
6628 if (empty($intent_label) || empty($callback_function) || empty($phrases_input)) {
6629 $this->handle_embedding_error(__('Invalid input. Please ensure all fields are filled out.', 'mxchat'));
6630 return;
6631 }
6632
6633 $available_callbacks = $this->mxchat_get_available_callbacks();
6634
6635 if (!array_key_exists($callback_function, $available_callbacks)) {
6636 $this->handle_embedding_error(__('Invalid callback function selected.', 'mxchat'));
6637 return;
6638 }
6639
6640 $is_pro_only = $available_callbacks[$callback_function]['pro_only'];
6641 if ($is_pro_only && !$this->is_activated) {
6642 $this->handle_embedding_error(__('This callback function is available in the Pro version only.', 'mxchat'));
6643 return;
6644 }
6645
6646 $phrases_array = array_map('sanitize_text_field', array_filter(array_map('trim', explode(',', $phrases_input))));
6647
6648 if (empty($phrases_array)) {
6649 $this->handle_embedding_error(__('Please enter at least one valid phrase.', 'mxchat'));
6650 return;
6651 }
6652
6653 // Generate embeddings with improved error handling
6654 $vectors = [];
6655 $failed_phrases = [];
6656
6657 foreach ($phrases_array as $phrase) {
6658 $embedding_vector = $this->mxchat_generate_embedding($phrase, $this->options['api_key']);
6659 if (is_array($embedding_vector)) {
6660 $vectors[] = $embedding_vector;
6661 } else {
6662 $failed_phrases[] = $phrase;
6663 }
6664 }
6665
6666 if (!empty($failed_phrases)) {
6667 $this->handle_embedding_error(
6668 sprintf(
6669 __('Error generating embeddings for phrases: %s. Check your embedding API.', 'mxchat'),
6670 implode(', ', $failed_phrases)
6671 )
6672 );
6673 return;
6674 }
6675
6676 if (empty($vectors)) {
6677 $this->handle_embedding_error(__('No valid embeddings generated. Please check your phrases.', 'mxchat'));
6678 return;
6679 }
6680
6681 $combined_vector = $this->mxchat_average_vectors($vectors);
6682 $serialized_vector = maybe_serialize($combined_vector);
6683
6684 $result = $wpdb->insert($table_name, [
6685 'intent_label' => $intent_label,
6686 'phrases' => implode(', ', $phrases_array),
6687 'embedding_vector' => $serialized_vector,
6688 'callback_function' => $callback_function,
6689 'similarity_threshold' => $default_threshold,
6690 ]);
6691
6692 if ($result === false) {
6693 $this->handle_embedding_error(__('Database error: ', 'mxchat') . $wpdb->last_error);
6694 return;
6695 }
6696
6697 // Set success message
6698 set_transient('mxchat_admin_notice_success', __('New intent added successfully!', 'mxchat'), 60);
6699
6700 wp_safe_redirect(admin_url('admin.php?page=mxchat-actions'));
6701 exit;
6702 }
6703
6704
6705 /**
6706 * Update intent threshold with AJAX support
6707 */
6708 public function mxchat_update_intent_threshold() {
6709 // Check permissions
6710 if (!current_user_can('manage_options')) {
6711 if (wp_doing_ajax()) {
6712 wp_send_json_error(array('message' => 'Unauthorized user'));
6713 return;
6714 }
6715 wp_die(esc_html__('Unauthorized user', 'mxchat'));
6716 }
6717
6718 // Verify nonce
6719 check_admin_referer('mxchat_update_intent_threshold_nonce');
6720
6721 // Process the update if we have valid data
6722 if (isset($_POST['intent_id'], $_POST['intent_threshold'])) {
6723 global $wpdb;
6724 $table_name = $wpdb->prefix . 'mxchat_intents';
6725 $intent_id = intval($_POST['intent_id']);
6726 $threshold_percentage = max(70, min(95, intval($_POST['intent_threshold'])));
6727 $similarity_threshold = $threshold_percentage / 100;
6728
6729 $result = $wpdb->update(
6730 $table_name,
6731 ['similarity_threshold' => $similarity_threshold],
6732 ['id' => $intent_id],
6733 ['%f'],
6734 ['%d']
6735 );
6736
6737 // Handle AJAX requests
6738 if (wp_doing_ajax()) {
6739 if ($result === false) {
6740 wp_send_json_error(array('message' => 'Failed to update threshold'));
6741 } else {
6742 wp_send_json_success(array('threshold' => $threshold_percentage));
6743 }
6744 return;
6745 }
6746 }
6747
6748 // Redirect for regular form submissions
6749 wp_safe_redirect(admin_url('admin.php?page=mxchat-actions&updated=true'));
6750 exit;
6751 }
6752
6753
6754
6755 /**
6756 * Enhanced get_available_callbacks function with form action exclusion
6757 *
6758 * @param bool $grouped Whether to return callbacks grouped by category
6759 * @param bool $include_all Whether to include all potential actions (even if add-on not installed)
6760 * @return array Callbacks data with icons, descriptions and availability status
6761 */
6762 private function mxchat_get_available_callbacks($grouped = false, $include_all = true) {
6763 // Load WordPress plugin functions if needed
6764 if (!function_exists('get_plugins')) {
6765 require_once ABSPATH . 'wp-admin/includes/plugin.php';
6766 }
6767
6768 // Get active plugins
6769 $active_plugins = get_option('active_plugins', array());
6770
6771 // Functions to exclude from the action selector only if Pro is activated
6772 // If user doesn't have Pro, show these so they can see what they're missing
6773 $excluded_when_pro_active_functions = array(
6774 'mxchat_handle_form_collection', // Forms add-on action
6775 'mxchat_sr_recommendation_flow' // Smart Recommender flow actions
6776 );
6777
6778 // Always excluded functions (regardless of Pro status)
6779 $always_excluded_functions = array();
6780
6781 // Combine exclusion lists based on Pro activation status
6782 $excluded_functions = $always_excluded_functions;
6783 if ($this->is_activated) {
6784 // Only exclude add-on managed functions if Pro is active
6785 $excluded_functions = array_merge($excluded_functions, $excluded_when_pro_active_functions);
6786 }
6787
6788 // Define add-on plugin files and their corresponding action functions
6789 $addon_plugins = array(
6790 'mxchat-woo/mxchat-woo.php' => array(
6791 'functions' => array(
6792 'mxchat_handle_product_recommendations',
6793 'mxchat_handle_order_history',
6794 'mxchat_show_product_card',
6795 'mxchat_add_to_cart',
6796 'mxchat_checkout_redirect'
6797 ),
6798 'name' => __('WooCommerce Add-on', 'mxchat'),
6799 'pro_required' => true
6800 ),
6801 'mxchat-perplexity/mxchat-perplexity.php' => array(
6802 'functions' => array('mxchat_perplexity_research'),
6803 'name' => __('Perplexity Add-on', 'mxchat'),
6804 'pro_required' => true
6805 ),
6806 'mxchat-forms/mxchat-forms.php' => array(
6807 'functions' => array('mxchat_handle_form_collection'),
6808 'name' => __('Forms Add-on', 'mxchat'),
6809 'pro_required' => true
6810 ),
6811 'mxchat-smart-recommender/mxchat-smart-recommender.php' => array(
6812 'functions' => array('mxchat_sr_recommendation_flow'),
6813 'name' => __('Smart Recommender Add-on', 'mxchat'),
6814 'pro_required' => true
6815 ),
6816 // Add other add-ons and their functions here
6817 );
6818
6819 // Get the functions that are provided by active add-ons
6820 $addon_provided_functions = array();
6821 $addon_function_mapping = array(); // Maps functions to their add-on info
6822
6823 // Check which add-ons are active
6824 foreach ($addon_plugins as $plugin_file => $addon_info) {
6825 $is_active = in_array($plugin_file, $active_plugins);
6826
6827 // For each function in this addon
6828 foreach ($addon_info['functions'] as $function) {
6829 // Consider a function installed only if:
6830 // 1. The add-on is active AND
6831 // 2. Either it doesn't require Pro OR Pro is activated
6832 $is_installed = $is_active && (!$addon_info['pro_required'] || $this->is_activated);
6833
6834 // If the add-on is installed, mark this function as provided by an add-on
6835 if ($is_installed) {
6836 $addon_provided_functions[] = $function;
6837 }
6838
6839 // Store addon info for this function regardless of installation status
6840 $addon_function_mapping[$function] = array(
6841 'addon' => basename(dirname($plugin_file)),
6842 'addon_name' => $addon_info['name'],
6843 'pro_required' => $addon_info['pro_required'],
6844 'is_active' => $is_active,
6845 'is_installed' => $is_installed
6846 );
6847 }
6848 }
6849
6850 // Core callbacks - always available in the base plugin
6851 $core_callbacks = array(
6852 'mxchat_handle_email_capture' => array(
6853 'label' => __('Loops Email Capture', 'mxchat'),
6854 'pro_only' => false,
6855 'group' => __('Customer Engagement', 'mxchat'),
6856 'icon' => 'email-alt',
6857 'description' => __('Collect visitor emails for your mailing list in Loops', 'mxchat'),
6858 'addon' => false, // Not from an add-on
6859 'installed' => true // Always installed with base plugin
6860 ),
6861 'mxchat_handle_search_request' => array(
6862 'label' => __('Brave Web Search', 'mxchat'),
6863 'pro_only' => false,
6864 'group' => __('Search Features', 'mxchat'),
6865 'icon' => 'search',
6866 'description' => __('Let users search the web directly from the chat', 'mxchat'),
6867 'addon' => false,
6868 'installed' => true
6869 ),
6870 'mxchat_handle_image_search_request' => array(
6871 'label' => __('Brave Image Search', 'mxchat'),
6872 'pro_only' => false,
6873 'group' => __('Search Features', 'mxchat'),
6874 'icon' => 'format-image',
6875 'description' => __('Search and display images in the chat conversation', 'mxchat'),
6876 'addon' => false,
6877 'installed' => true
6878 ),
6879 // Pro core features - check is_activated property
6880 'mxchat_generate_image' => array(
6881 'label' => __('Generate Image', 'mxchat'),
6882 'pro_only' => false,
6883 'group' => __('Other Features', 'mxchat'),
6884 'icon' => 'art',
6885 'description' => __('Create images with DALL-E 3 from OpenAI (requires OpenAI API key)', 'mxchat'),
6886 'addon' => false,
6887 'installed' => true
6888 ),
6889 'mxchat_handle_pdf_discussion' => array(
6890 'label' => __('Chat with PDF', 'mxchat'),
6891 'pro_only' => false,
6892 'group' => __('Other Features', 'mxchat'),
6893 'icon' => 'media-document',
6894 'description' => __('Answer questions about uploaded PDF documents', 'mxchat'),
6895 'addon' => false,
6896 'installed' => true
6897 ),
6898 'mxchat_live_agent_handover' => array(
6899 'label' => __('Slack Live Agent', 'mxchat'),
6900 'pro_only' => false,
6901 'group' => __('Customer Engagement', 'mxchat'),
6902 'icon' => 'admin-users',
6903 'description' => __('Transfer conversation to a human support agent on Slack', 'mxchat'),
6904 'addon' => false,
6905 'installed' => true
6906 ),
6907 'mxchat_handle_switch_to_chatbot_intent' => array(
6908 'label' => __('Back to Chatbot', 'mxchat'),
6909 'pro_only' => false,
6910 'group' => __('Customer Engagement', 'mxchat'),
6911 'icon' => 'backup',
6912 'description' => __('Return from live agent mode to AI chatbot', 'mxchat'),
6913 'addon' => false,
6914 'installed' => true
6915 ),
6916 );
6917
6918 // Add-on callbacks with placeholders - only include if the add-on is NOT active
6919 $addon_callbacks = array(
6920 // WooCommerce Add-on
6921 'mxchat_handle_product_recommendations' => array(
6922 'label' => __('Product Recommendations', 'mxchat'),
6923 'pro_only' => true,
6924 'group' => __('WooCommerce Features', 'mxchat'),
6925 'icon' => 'cart',
6926 'description' => __('Suggest products based on customer preferences', 'mxchat'),
6927 ),
6928 'mxchat_handle_order_history' => array(
6929 'label' => __('Order History', 'mxchat'),
6930 'pro_only' => true,
6931 'group' => __('WooCommerce Features', 'mxchat'),
6932 'icon' => 'clipboard',
6933 'description' => __('Allow customers to check their order status', 'mxchat'),
6934 ),
6935 'mxchat_show_product_card' => array(
6936 'label' => __('Show Product Card', 'mxchat'),
6937 'pro_only' => true,
6938 'group' => __('WooCommerce Features', 'mxchat'),
6939 'icon' => 'products',
6940 'description' => __('Display product information in the chat', 'mxchat'),
6941 ),
6942 'mxchat_add_to_cart' => array(
6943 'label' => __('Add to Cart', 'mxchat'),
6944 'pro_only' => true,
6945 'group' => __('WooCommerce Features', 'mxchat'),
6946 'icon' => 'plus-alt',
6947 'description' => __('Add products to cart directly from chat', 'mxchat'),
6948 ),
6949 'mxchat_checkout_redirect' => array(
6950 'label' => __('Proceed to Checkout', 'mxchat'),
6951 'pro_only' => true,
6952 'group' => __('WooCommerce Features', 'mxchat'),
6953 'icon' => 'arrow-right-alt',
6954 'description' => __('Redirect customer to checkout page', 'mxchat'),
6955 ),
6956
6957 // Perplexity Add-on
6958 'mxchat_perplexity_research' => array(
6959 'label' => __('Perplexity Research', 'mxchat'),
6960 'pro_only' => true,
6961 'group' => __('Search Features', 'mxchat'),
6962 'icon' => 'book-alt',
6963 'description' => __('Allows the chatbot to search the web for accurate, up-to-date answers', 'mxchat'),
6964 ),
6965
6966 // Forms Add-on (only shown when Pro is not activated)
6967 'mxchat_handle_form_collection' => array(
6968 'label' => __('Form Collection', 'mxchat'),
6969 'pro_only' => true,
6970 'group' => __('Form Features', 'mxchat'),
6971 'icon' => 'feedback',
6972 'description' => __('Collect user information through custom forms in chat', 'mxchat'),
6973 ),
6974
6975 // Smart Recommender Add-on (only shown when Pro is not activated)
6976 'mxchat_sr_recommendation_flow' => array(
6977 'label' => __('Smart Recommender Flow', 'mxchat'),
6978 'pro_only' => true,
6979 'group' => __('Recommendation Features', 'mxchat'),
6980 'icon' => 'cart',
6981 'description' => __('Create interactive conversation flows that collect user preferences and deliver personalized product or service recommendations', 'mxchat'),
6982 ),
6983 );
6984
6985 // Enhance add-on callbacks with installation status and addon info
6986 foreach ($addon_callbacks as $function => $data) {
6987 if (isset($addon_function_mapping[$function])) {
6988 $addon_info = $addon_function_mapping[$function];
6989
6990 $addon_callbacks[$function]['addon'] = $addon_info['addon'];
6991 $addon_callbacks[$function]['addon_name'] = $addon_info['addon_name'];
6992 $addon_callbacks[$function]['installed'] = $addon_info['is_installed'];
6993
6994 // Set pro_only based on add-on configuration
6995 $addon_callbacks[$function]['pro_only'] = $addon_info['pro_required'];
6996 } else {
6997 $addon_callbacks[$function]['addon'] = 'unknown';
6998 $addon_callbacks[$function]['addon_name'] = __('Unknown Add-on', 'mxchat');
6999 $addon_callbacks[$function]['installed'] = false;
7000 }
7001 }
7002
7003 // Initialize callbacks with core features
7004 $callbacks = $core_callbacks;
7005
7006 // Get callbacks from active add-ons
7007 $active_addon_callbacks = apply_filters('mxchat_available_callbacks', array());
7008
7009 // Add placeholder callbacks only for add-ons that aren't active
7010 if ($include_all) {
7011 foreach ($addon_callbacks as $function => $data) {
7012 // Skip placeholders for functions provided by active add-ons
7013 if (in_array($function, $addon_provided_functions)) {
7014 continue;
7015 }
7016
7017 // Skip excluded functions
7018 if (in_array($function, $excluded_functions)) {
7019 continue;
7020 }
7021
7022 // Add the placeholder
7023 $callbacks[$function] = $data;
7024 }
7025 }
7026
7027 // Add callbacks from active add-ons (will override placeholders)
7028 foreach ($active_addon_callbacks as $function => $data) {
7029 // Skip excluded functions
7030 if (in_array($function, $excluded_functions)) {
7031 continue;
7032 }
7033
7034 // Always include callbacks from add-ons
7035 $callbacks[$function] = $data;
7036
7037 // Ensure they have the proper add-on info
7038 if (isset($addon_function_mapping[$function])) {
7039 $addon_info = $addon_function_mapping[$function];
7040 $callbacks[$function]['addon'] = $addon_info['addon'];
7041 $callbacks[$function]['addon_name'] = $addon_info['addon_name'];
7042 $callbacks[$function]['installed'] = $addon_info['is_installed'];
7043 $callbacks[$function]['pro_only'] = $addon_info['pro_required'];
7044 }
7045 }
7046
7047 // Just before returning callbacks, sort them to prioritize free features
7048 if (!$grouped) {
7049 // Create temporary arrays for sorting
7050 $free_callbacks = array();
7051 $pro_callbacks = array();
7052
7053 // Split callbacks into free and pro
7054 foreach ($callbacks as $key => $data) {
7055 if (isset($data['pro_only']) && $data['pro_only']) {
7056 $pro_callbacks[$key] = $data;
7057 } else {
7058 $free_callbacks[$key] = $data;
7059 }
7060 }
7061
7062 // Merge with free callbacks first
7063 $callbacks = array_merge($free_callbacks, $pro_callbacks);
7064 }
7065
7066 // Return grouped structure if requested
7067 if ($grouped) {
7068 $grouped_callbacks = array();
7069 foreach ($callbacks as $key => $data) {
7070 $group_label = isset($data['group']) ? $data['group'] : __('Other Features', 'mxchat');
7071
7072 // Ensure we carry forward all the new fields in grouped mode
7073 $callback_data = array(
7074 'label' => $data['label'],
7075 'pro_only' => isset($data['pro_only']) ? $data['pro_only'] : false,
7076 'icon' => isset($data['icon']) ? $data['icon'] : 'admin-generic',
7077 'description' => isset($data['description']) ? $data['description'] : __('Custom action for your chatbot', 'mxchat'),
7078 'addon' => isset($data['addon']) ? $data['addon'] : false,
7079 'addon_name' => isset($data['addon_name']) ? $data['addon_name'] : '',
7080 'installed' => isset($data['installed']) ? $data['installed'] : true
7081 );
7082
7083 $grouped_callbacks[$group_label][$key] = $callback_data;
7084 }
7085
7086 // Sort within each group to prioritize free features
7087 foreach ($grouped_callbacks as $group => $items) {
7088 $free_items = array();
7089 $pro_items = array();
7090
7091 foreach ($items as $key => $data) {
7092 if (isset($data['pro_only']) && $data['pro_only']) {
7093 $pro_items[$key] = $data;
7094 } else {
7095 $free_items[$key] = $data;
7096 }
7097 }
7098
7099 $grouped_callbacks[$group] = array_merge($free_items, $pro_items);
7100 }
7101
7102 return $grouped_callbacks;
7103 }
7104
7105 return $callbacks;
7106 }
7107
7108
7109 private function mxchat_average_vectors($vectors) {
7110 $vector_length = count($vectors[0]);
7111 $sum_vector = array_fill(0, $vector_length, 0);
7112
7113 foreach ($vectors as $vector) {
7114 for ($i = 0; $i < $vector_length; $i++) {
7115 $sum_vector[$i] += $vector[$i];
7116 }
7117 }
7118
7119 // Divide each component by the number of vectors to get the average
7120 $num_vectors = count($vectors);
7121 for ($i = 0; $i < $vector_length; $i++) {
7122 $sum_vector[$i] /= $num_vectors;
7123 }
7124
7125 return $sum_vector;
7126 }
7127
7128 public function mxchat_handle_delete_intent() {
7129 if ( ! current_user_can( 'manage_options' ) ) {
7130 wp_die( esc_html__('Unauthorized user', 'mxchat') );
7131 }
7132
7133 check_admin_referer('mxchat_delete_intent_nonce');
7134
7135 if (isset($_POST['intent_id'])) {
7136 global $wpdb;
7137 $table_name = $wpdb->prefix . 'mxchat_intents';
7138 $intent_id = intval($_POST['intent_id']);
7139
7140 $wpdb->delete($table_name, ['id' => $intent_id], ['%d']);
7141 }
7142
7143 wp_safe_redirect(admin_url('admin.php?page=mxchat-actions'));
7144 exit;
7145 }
7146
7147
7148 public function mxchat_page_init() {
7149 register_setting(
7150 'mxchat_option_group',
7151 'mxchat_options',
7152 array($this, 'mxchat_sanitize')
7153 );
7154
7155 register_setting(
7156 'mxchat_option_group',
7157 'mxchat_similarity_threshold',
7158 array(
7159 'type' => 'number',
7160 'sanitize_callback' => function($value) {
7161 $value = absint($value);
7162 return min(max($value, 20), 95);
7163 },
7164 'default' => 80,
7165 )
7166 );
7167
7168 // Chatbot Settings Section
7169 add_settings_section(
7170 'mxchat_chatbot_section',
7171 esc_html__('Chatbot Settings', 'mxchat'),
7172 null,
7173 'mxchat-chatbot'
7174 );
7175
7176 // Similarity Threshold Slider
7177 add_settings_field(
7178 'similarity_threshold', // Field ID
7179 esc_html__('Similarity Threshold', 'mxchat'), // Field title
7180 array($this, 'mxchat_similarity_threshold_callback'), // Callback function
7181 'mxchat-chatbot', // Page
7182 'mxchat_chatbot_section' // Section
7183 );
7184
7185 add_settings_field(
7186 'append_to_body',
7187 esc_html__('Auto-Display Chatbot', 'mxchat'),
7188 array($this, 'mxchat_append_to_body_callback'),
7189 'mxchat-chatbot',
7190 'mxchat_chatbot_section'
7191 );
7192
7193 add_settings_field(
7194 'api_key',
7195 esc_html__('OpenAI API Key', 'mxchat'),
7196 array($this, 'api_key_callback'),
7197 'mxchat-chatbot',
7198 'mxchat_chatbot_section',
7199 array(
7200 'class' => 'mxchat-setting-row',
7201 'data-provider' => 'openai'
7202 )
7203 );
7204
7205 add_settings_field(
7206 'xai_api_key',
7207 esc_html__('X.AI API Key', 'mxchat'),
7208 array($this, 'xai_api_key_callback'),
7209 'mxchat-chatbot',
7210 'mxchat_chatbot_section',
7211 array(
7212 'class' => 'mxchat-setting-row',
7213 'data-provider' => 'xai'
7214 )
7215 );
7216
7217 add_settings_field(
7218 'claude_api_key',
7219 esc_html__('Claude API Key', 'mxchat'),
7220 array($this, 'claude_api_key_callback'),
7221 'mxchat-chatbot',
7222 'mxchat_chatbot_section',
7223 array(
7224 'class' => 'mxchat-setting-row',
7225 'data-provider' => 'claude'
7226 )
7227 );
7228
7229 add_settings_field(
7230 'deepseek_api_key',
7231 esc_html__('DeepSeek API Key', 'mxchat'),
7232 array($this, 'deepseek_api_key_callback'),
7233 'mxchat-chatbot',
7234 'mxchat_chatbot_section',
7235 array(
7236 'class' => 'mxchat-setting-row',
7237 'data-provider' => 'deepseek'
7238 )
7239 );
7240
7241 add_settings_field(
7242 'gemini_api_key',
7243 esc_html__('Google Gemini API Key', 'mxchat'),
7244 array($this, 'gemini_api_key_callback'),
7245 'mxchat-chatbot',
7246 'mxchat_chatbot_section',
7247 array(
7248 'class' => 'mxchat-setting-row',
7249 'data-provider' => 'gemini'
7250 )
7251 );
7252
7253 add_settings_field(
7254 'voyage_api_key',
7255 esc_html__('Voyage AI API Key', 'mxchat'),
7256 array($this, 'voyage_api_key_callback'),
7257 'mxchat-chatbot',
7258 'mxchat_chatbot_section',
7259 array(
7260 'class' => 'mxchat-setting-row',
7261 'data-provider' => 'voyage'
7262 )
7263 );
7264
7265 add_settings_field(
7266 'model',
7267 esc_html__('Chat Model', 'mxchat'),
7268 array($this, 'mxchat_model_callback'),
7269 'mxchat-chatbot',
7270 'mxchat_chatbot_section'
7271 );
7272
7273 // Add the settings field
7274 add_settings_field(
7275 'embedding_model',
7276 esc_html__('Embedding Model', 'mxchat'),
7277 array($this, 'embedding_model_callback'),
7278 'mxchat-chatbot',
7279 'mxchat_chatbot_section'
7280 );
7281
7282 add_settings_field(
7283 'system_prompt_instructions',
7284 esc_html__('AI Instructions (Behavior)', 'mxchat'),
7285 array($this, 'system_prompt_instructions_callback'),
7286 'mxchat-chatbot',
7287 'mxchat_chatbot_section'
7288 );
7289
7290
7291 add_settings_field(
7292 'top_bar_title',
7293 esc_html__('Top Bar Title', 'mxchat'),
7294 array($this, 'mxchat_top_bar_title_callback'),
7295 'mxchat-chatbot',
7296 'mxchat_chatbot_section'
7297 );
7298
7299 add_settings_field(
7300 'ai_agent_text',
7301 esc_html__('AI Agent Text', 'mxchat'),
7302 array($this, 'mxchat_ai_agent_text_callback'),
7303 'mxchat-chatbot',
7304 'mxchat_chatbot_section'
7305 );
7306
7307 add_settings_field(
7308 'enable_email_block',
7309 esc_html__('Require Email To Chat', 'mxchat'),
7310 array($this, 'enable_email_block_callback'),
7311 'mxchat-chatbot',
7312 'mxchat_chatbot_section'
7313 );
7314
7315 add_settings_field(
7316 'email_blocker_header_content',
7317 esc_html__('Require Email Chat Content', 'mxchat'),
7318 array($this, 'email_blocker_header_content_callback'),
7319 'mxchat-chatbot',
7320 'mxchat_chatbot_section'
7321 );
7322
7323 add_settings_field(
7324 'email_blocker_button_text',
7325 esc_html__('Require Email Chat Button Text', 'mxchat'),
7326 [$this, 'email_blocker_button_text_callback'],
7327 'mxchat-chatbot',
7328 'mxchat_chatbot_section'
7329 );
7330
7331 add_settings_field(
7332 'intro_message',
7333 esc_html__('Introductory Message', 'mxchat'),
7334 array($this, 'mxchat_intro_message_callback'),
7335 'mxchat-chatbot',
7336 'mxchat_chatbot_section'
7337 );
7338
7339 add_settings_field(
7340 'input_copy',
7341 esc_html__('Input Copy', 'mxchat'),
7342 array($this, 'mxchat_input_copy_callback'),
7343 'mxchat-chatbot',
7344 'mxchat_chatbot_section'
7345 );
7346
7347 add_settings_field(
7348 'pre_chat_message',
7349 esc_html__('Chat Teaser Pop-up', 'mxchat'),
7350 array($this, 'mxchat_pre_chat_message_callback'),
7351 'mxchat-chatbot',
7352 'mxchat_chatbot_section'
7353 );
7354
7355 add_settings_field(
7356 'privacy_toggle',
7357 esc_html__('Toggle Privacy Notice', 'mxchat'),
7358 array($this, 'mxchat_privacy_toggle_callback'),
7359 'mxchat-chatbot',
7360 'mxchat_chatbot_section'
7361 );
7362
7363 add_settings_field(
7364 'complianz_toggle',
7365 esc_html__('Enable Complianz', 'mxchat'),
7366 array($this, 'mxchat_complianz_toggle_callback'),
7367 'mxchat-chatbot',
7368 'mxchat_chatbot_section'
7369 );
7370
7371 add_settings_field(
7372 'link_target_toggle',
7373 esc_html__('Open Links in a New Tab', 'mxchat'),
7374 array($this, 'mxchat_link_target_toggle_callback'),
7375 'mxchat-chatbot',
7376 'mxchat_chatbot_section'
7377 );
7378
7379 add_settings_field(
7380 'chat_persistence_toggle',
7381 esc_html__('Enable Chat Persistence', 'mxchat'),
7382 array($this, 'mxchat_chat_persistence_toggle_callback'),
7383 'mxchat-chatbot',
7384 'mxchat_chatbot_section'
7385 );
7386
7387 add_settings_field(
7388 'popular_question_1',
7389 esc_html__('Quick Question 1', 'mxchat'),
7390 array($this, 'mxchat_popular_question_1_callback'),
7391 'mxchat-chatbot',
7392 'mxchat_chatbot_section'
7393 );
7394
7395 add_settings_field(
7396 'popular_question_2',
7397 esc_html__('Quick Question 2', 'mxchat'),
7398 array($this, 'mxchat_popular_question_2_callback'),
7399 'mxchat-chatbot',
7400 'mxchat_chatbot_section'
7401 );
7402
7403 add_settings_field(
7404 'popular_question_3',
7405 esc_html__('Quick Question 3', 'mxchat'),
7406 array($this, 'mxchat_popular_question_3_callback'),
7407 'mxchat-chatbot',
7408 'mxchat_chatbot_section'
7409 );
7410
7411 add_settings_field(
7412 'additional_popular_questions',
7413 esc_html__('Additional Quick Questions', 'mxchat'),
7414 array($this, 'mxchat_additional_popular_questions_callback'),
7415 'mxchat-chatbot',
7416 'mxchat_chatbot_section'
7417 );
7418
7419
7420 add_settings_field(
7421 'rate_limits',
7422 __('Rate Limits Settings', 'mxchat'),
7423 array($this, 'mxchat_rate_limits_callback'),
7424 'mxchat-chatbot',
7425 'mxchat_chatbot_section'
7426 );
7427
7428 // Loops Settings Section
7429 add_settings_section(
7430 'mxchat_loops_section',
7431 esc_html__('Loops Settings', 'mxchat'),
7432 null,
7433 'mxchat-embed'
7434 );
7435
7436 // Loops Settings Fields
7437 add_settings_field(
7438 'loops_api_key',
7439 esc_html__('Loops API Key', 'mxchat'),
7440 array($this, 'mxchat_loops_api_key_callback'),
7441 'mxchat-embed',
7442 'mxchat_loops_section'
7443 );
7444
7445 add_settings_field(
7446 'loops_mailing_list',
7447 esc_html__('Loops Mailing List', 'mxchat'),
7448 array($this, 'mxchat_loops_mailing_list_callback'),
7449 'mxchat-embed',
7450 'mxchat_loops_section'
7451 );
7452
7453 add_settings_field(
7454 'triggered_phrase_response',
7455 esc_html__('Triggered Phrase Response', 'mxchat'),
7456 array($this, 'mxchat_triggered_phrase_response_callback'),
7457 'mxchat-embed',
7458 'mxchat_loops_section'
7459 );
7460
7461 add_settings_field(
7462 'email_capture_response',
7463 esc_html__('Email Capture Response', 'mxchat'),
7464 array($this, 'mxchat_email_capture_response_callback'),
7465 'mxchat-embed',
7466 'mxchat_loops_section'
7467 );
7468
7469 // Brave Search Settings Fields
7470 add_settings_section(
7471 'mxchat_brave_section',
7472 __('Brave Search Settings', 'mxchat'),
7473 array($this, 'mxchat_brave_section_callback'),
7474 'mxchat-embed'
7475 );
7476
7477 add_settings_field(
7478 'brave_api_key',
7479 __('Brave API Key', 'mxchat'),
7480 array($this, 'mxchat_brave_api_key_callback'),
7481 'mxchat-embed',
7482 'mxchat_brave_section'
7483 );
7484
7485 add_settings_field(
7486 'brave_image_count',
7487 __('Number of Images to Return', 'mxchat'),
7488 array($this, 'mxchat_brave_image_count_callback'),
7489 'mxchat-embed',
7490 'mxchat_brave_section'
7491 );
7492
7493 add_settings_field(
7494 'brave_safe_search',
7495 __('Safe Search', 'mxchat'),
7496 array($this, 'mxchat_brave_safe_search_callback'),
7497 'mxchat-embed',
7498 'mxchat_brave_section'
7499 );
7500
7501 add_settings_field(
7502 'brave_news_count',
7503 __('Number of News Articles', 'mxchat'),
7504 array($this, 'mxchat_brave_news_count_callback'),
7505 'mxchat-embed',
7506 'mxchat_brave_section'
7507 );
7508
7509 add_settings_field(
7510 'brave_country',
7511 __('Country', 'mxchat'),
7512 array($this, 'mxchat_brave_country_callback'),
7513 'mxchat-embed',
7514 'mxchat_brave_section'
7515 );
7516
7517 add_settings_field(
7518 'brave_language',
7519 __('Language', 'mxchat'),
7520 array($this, 'mxchat_brave_language_callback'),
7521 'mxchat-embed',
7522 'mxchat_brave_section'
7523 );
7524
7525 // Chat with PDF Intent Settings Fields
7526 add_settings_section(
7527 'mxchat_pdf_intent_section',
7528 __('Toolbar Settings & Intents', 'mxchat'),
7529 array($this, 'mxchat_pdf_intent_section_callback'),
7530 'mxchat-embed'
7531 );
7532
7533 add_settings_field(
7534 'chat_toolbar_toggle',
7535 __('Show Chat Toolbar', 'mxchat'),
7536 array($this, 'mxchat_chat_toolbar_toggle_callback'),
7537 'mxchat-embed',
7538 'mxchat_pdf_intent_section'
7539 );
7540
7541 // PDF Upload Button Toggle
7542 add_settings_field(
7543 'show_pdf_upload_button',
7544 __('Show PDF Upload Button', 'mxchat'),
7545 array($this, 'mxchat_show_pdf_upload_button_callback'),
7546 'mxchat-embed',
7547 'mxchat_pdf_intent_section'
7548 );
7549
7550 // Word Upload Button Toggle
7551 add_settings_field(
7552 'show_word_upload_button',
7553 __('Show Word Upload Button', 'mxchat'),
7554 array($this, 'mxchat_show_word_upload_button_callback'),
7555 'mxchat-embed',
7556 'mxchat_pdf_intent_section'
7557 );
7558
7559 add_settings_field(
7560 'pdf_intent_trigger_text',
7561 __('Intent Trigger Text', 'mxchat'),
7562 array($this, 'mxchat_pdf_intent_trigger_text_callback'),
7563 'mxchat-embed',
7564 'mxchat_pdf_intent_section'
7565 );
7566
7567 add_settings_field(
7568 'pdf_intent_success_text',
7569 __('Success Text', 'mxchat'),
7570 array($this, 'mxchat_pdf_intent_success_text_callback'),
7571 'mxchat-embed',
7572 'mxchat_pdf_intent_section'
7573 );
7574
7575 add_settings_field(
7576 'pdf_intent_error_text',
7577 __('Error Text', 'mxchat'),
7578 array($this, 'mxchat_pdf_intent_error_text_callback'),
7579 'mxchat-embed',
7580 'mxchat_pdf_intent_section'
7581 );
7582
7583 // Add PDF Maximum Pages Field
7584 add_settings_field(
7585 'pdf_max_pages',
7586 __('Maximum Document Pages', 'mxchat'),
7587 array($this, 'mxchat_pdf_max_pages_callback'),
7588 'mxchat-embed',
7589 'mxchat_pdf_intent_section'
7590 );
7591
7592 // Live Agent Settings Fields
7593 add_settings_section(
7594 'mxchat_live_agent_section',
7595 __('Live Agent Settings', 'mxchat'),
7596 array($this, 'mxchat_live_agent_section_callback'),
7597 'mxchat-embed'
7598 );
7599
7600 // Live Agent Status Fields (add at top of live agent settings)
7601 add_settings_field(
7602 'live_agent_status',
7603 __('Live Agent Status', 'mxchat'),
7604 array($this, 'mxchat_live_agent_status_callback'),
7605 'mxchat-embed',
7606 'mxchat_live_agent_section'
7607 );
7608
7609 add_settings_field(
7610 'live_agent_notification_message',
7611 __('Notification Message', 'mxchat'),
7612 array($this, 'mxchat_live_agent_notification_message_callback'),
7613 'mxchat-embed',
7614 'mxchat_live_agent_section'
7615 );
7616
7617 add_settings_field(
7618 'live_agent_away_message',
7619 __('Away Message', 'mxchat'),
7620 array($this, 'mxchat_live_agent_away_message_callback'),
7621 'mxchat-embed',
7622 'mxchat_live_agent_section'
7623 );
7624
7625 add_settings_field(
7626 'live_agent_webhook_url',
7627 __('Slack Webhook URL', 'mxchat'),
7628 array($this, 'mxchat_live_agent_webhook_url_callback'),
7629 'mxchat-embed',
7630 'mxchat_live_agent_section'
7631 );
7632
7633 add_settings_field(
7634 'live_agent_secret_key',
7635 __('Slack Secret Key', 'mxchat'),
7636 array($this, 'mxchat_live_agent_secret_key_callback'),
7637 'mxchat-embed',
7638 'mxchat_live_agent_section'
7639 );
7640
7641 // Live Agent Integration Fields
7642 add_settings_field(
7643 'live_agent_bot_token',
7644 __('Slack Bot OAuth Token', 'mxchat'),
7645 array($this, 'mxchat_live_agent_bot_token_callback'),
7646 'mxchat-embed',
7647 'mxchat_live_agent_section'
7648 );
7649
7650
7651
7652
7653 // General Settings Section
7654 add_settings_section(
7655 'mxchat_general_section',
7656 esc_html__('YouTube Tutorials', 'mxchat'),
7657 null,
7658 'mxchat-general'
7659 );
7660 }
7661
7662 public function mxchat_prompts_page_init() {
7663 register_setting(
7664 'mxchat_prompts_options',
7665 'mxchat_prompts_options',
7666 array(
7667 'type' => 'array',
7668 'description' => __('MXChat Knowledge Base Settings', 'mxchat'),
7669 'default' => array(
7670 'mxchat_auto_sync_posts' => 0,
7671 'mxchat_auto_sync_pages' => 0,
7672 'mxchat_use_pinecone' => 0,
7673 'mxchat_pinecone_api_key' => '',
7674 'mxchat_pinecone_environment' => '',
7675 'mxchat_pinecone_index' => '',
7676 'mxchat_pinecone_host' => '',
7677 ),
7678 'sanitize_callback' => array($this, 'sanitize_prompts_options'),
7679 )
7680 );
7681
7682 add_action('admin_notices', array($this, 'sync_settings_notice'));
7683 }
7684
7685 public function mxchat_transcripts_page_init() {
7686 register_setting(
7687 'mxchat_transcripts_options',
7688 'mxchat_transcripts_options',
7689 array(
7690 'type' => 'array',
7691 'description' => __('MXChat Transcripts Notification Settings', 'mxchat'),
7692 'default' => array(
7693 'mxchat_enable_notifications' => 0,
7694 'mxchat_notification_email' => get_option('admin_email'),
7695 ),
7696 'sanitize_callback' => array($this, 'sanitize_transcripts_options'),
7697 )
7698 );
7699
7700 add_settings_section(
7701 'mxchat_transcripts_notification_section',
7702 esc_html__('Chat Notification Settings', 'mxchat'),
7703 array($this, 'mxchat_transcripts_notification_section_callback'),
7704 'mxchat-transcripts'
7705 );
7706
7707 add_settings_field(
7708 'mxchat_enable_notifications',
7709 esc_html__('Enable Chat Notifications', 'mxchat'),
7710 array($this, 'mxchat_enable_notifications_callback'),
7711 'mxchat-transcripts',
7712 'mxchat_transcripts_notification_section'
7713 );
7714
7715 add_settings_field(
7716 'mxchat_notification_email',
7717 esc_html__('Notification Email Address', 'mxchat'),
7718 array($this, 'mxchat_notification_email_callback'),
7719 'mxchat-transcripts',
7720 'mxchat_transcripts_notification_section'
7721 );
7722 }
7723
7724
7725 /**
7726 * Sanitize all prompts options
7727 *
7728 * @param array $input The unsanitized options array
7729 * @return array The sanitized options array
7730 */
7731 public function sanitize_prompts_options($input) {
7732 // Log the incoming input.
7733 //error_log('Sanitizing inputs: ' . print_r($input, true));
7734
7735 $sanitized = array();
7736
7737 // Boolean options
7738 $sanitized['mxchat_auto_sync_posts'] = isset($input['mxchat_auto_sync_posts']) ? 1 : 0;
7739 $sanitized['mxchat_auto_sync_pages'] = isset($input['mxchat_auto_sync_pages']) ? 1 : 0;
7740 $sanitized['mxchat_use_pinecone'] = !empty($input['mxchat_use_pinecone']) ? 1 : 0;
7741
7742 // API Key: if less than 32 characters, flag as invalid.
7743 $api_key = sanitize_text_field($input['mxchat_pinecone_api_key'] ?? '');
7744 if (!empty($api_key) && strlen($api_key) < 32) {
7745 add_settings_error(
7746 'mxchat_prompts_options',
7747 'invalid_api_key',
7748 __('The Pinecone API key appears to be invalid. Please check your API key.', 'mxchat')
7749 );
7750 $existing_options = get_option('mxchat_prompts_options', array());
7751 $sanitized['mxchat_pinecone_api_key'] = $existing_options['mxchat_pinecone_api_key'] ?? '';
7752 } else {
7753 $sanitized['mxchat_pinecone_api_key'] = $api_key;
7754 }
7755
7756 // Environment and Index Name
7757 $sanitized['mxchat_pinecone_environment'] = sanitize_text_field($input['mxchat_pinecone_environment'] ?? '');
7758 $sanitized['mxchat_pinecone_index'] = sanitize_text_field($input['mxchat_pinecone_index'] ?? '');
7759
7760 // Host: Remove protocol and validate format.
7761 $host = sanitize_text_field($input['mxchat_pinecone_host'] ?? '');
7762 $host = preg_replace('#^https?://#', '', $host);
7763 //error_log('Host after removing protocol: ' . $host);
7764 if (!empty($host)) {
7765 if (!preg_match('/^[\w-]+\.svc\.[\w-]+\.pinecone\.io$/', $host)) {
7766 add_settings_error(
7767 'mxchat_prompts_options',
7768 'invalid_host',
7769 __('The Pinecone host appears to be invalid. It should look like "mxchat-vectors-zrmsquq.svc.aped-4627-b74a.pinecone.io"', 'mxchat')
7770 );
7771 $existing_options = get_option('mxchat_prompts_options', array());
7772 $sanitized['mxchat_pinecone_host'] = $existing_options['mxchat_pinecone_host'] ?? '';
7773 } else {
7774 $sanitized['mxchat_pinecone_host'] = $host;
7775 }
7776 } else {
7777 $sanitized['mxchat_pinecone_host'] = '';
7778 }
7779
7780 //error_log('Final sanitized array: ' . print_r($sanitized, true));
7781
7782 return $sanitized;
7783 }
7784
7785
7786 public function sync_settings_notice() {
7787 // Only show notice on our plugin page
7788 if (!isset($_GET['page']) || $_GET['page'] !== 'mxchat-prompts') {
7789 return;
7790 }
7791
7792 // Check if settings were updated
7793 if (isset($_GET['settings-updated'])) {
7794
7795 ?>
7796 <div class="notice notice-success is-dismissible">
7797 <p><?php esc_html_e('Sync settings updated successfully.', 'mxchat'); ?></p>
7798 </div>
7799 <?php
7800
7801 }
7802 }
7803 // Add this sanitization function to your class
7804 public function sanitize_sync_setting($input) {
7805 return (bool)$input ? __('1', 'mxchat') : __('', 'mxchat');
7806 }
7807
7808
7809
7810 public function mxchat_handle_activate_license() {
7811 // Check nonce
7812 if (!check_ajax_referer('mxchat_activate_license_nonce', 'security', false)) {
7813 wp_send_json_error(esc_html__('Invalid security token', 'mxchat'));
7814 return;
7815 }
7816
7817 // Verify user capabilities
7818 if (!current_user_can('manage_options')) {
7819 wp_send_json_error(esc_html__('Unauthorized access', 'mxchat'));
7820 return;
7821 }
7822
7823 $license_key = isset($_POST['mxchat_activation_key']) ? sanitize_text_field($_POST['mxchat_activation_key']) : '';
7824 $customer_email = isset($_POST['mxchat_pro_email']) ? sanitize_email($_POST['mxchat_pro_email']) : '';
7825
7826 if (empty($license_key) || empty($customer_email)) {
7827 wp_send_json_error(esc_html__('Email or License Key is missing', 'mxchat'));
7828 return;
7829 }
7830
7831 $product_id = 'MxChatPRO';
7832 $response = wp_remote_get(
7833 add_query_arg(
7834 array(
7835 'wc-api' => 'software-api',
7836 'request' => 'activation',
7837 'email' => $customer_email,
7838 'license_key' => $license_key,
7839 'product_id' => $product_id
7840 ),
7841 'http://mxchat.ai/'
7842 )
7843 );
7844
7845 if (is_wp_error($response)) {
7846 wp_send_json_error(esc_html__('Activation failed due to a server error: ', 'mxchat') . $response->get_error_message());
7847 return;
7848 }
7849
7850 $body = wp_remote_retrieve_body($response);
7851 $data = json_decode($body);
7852
7853 if ($data && isset($data->activated) && $data->activated) {
7854 update_option('mxchat_license_status', 'active');
7855 update_option('mxchat_pro_email', $customer_email);
7856 update_option('mxchat_activation_key', $license_key);
7857 wp_send_json_success(array('message' => esc_html__('License activated successfully', 'mxchat')));
7858 } else {
7859 $error_message = isset($data->error) ? $data->error : esc_html__('Activation failed', 'mxchat');
7860 update_option('mxchat_license_status', 'inactive');
7861 update_option('mxchat_license_error', $error_message);
7862 wp_send_json_error($error_message);
7863 }
7864 }
7865 /**
7866 * AJAX handler to check license status
7867 */
7868 public function mxchat_check_license_status() {
7869 // Verify nonce
7870 check_ajax_referer($this->get_nonce_action(), 'security');
7871
7872 $email = sanitize_email($_POST['email']);
7873 $key = sanitize_text_field($_POST['key']);
7874
7875 // Check if this license is actually active in your system
7876 $is_active = (get_option('mxchat_license_status') === 'active' &&
7877 get_option('mxchat_pro_email') === $email &&
7878 get_option('mxchat_activation_key') === $key);
7879
7880 wp_send_json(array(
7881 'is_active' => $is_active
7882 ));
7883 }
7884 /**
7885 * Helper method to get the nonce action
7886 */
7887 private function get_nonce_action() {
7888 return 'mxchat_license_nonce';
7889 }
7890
7891
7892
7893
7894 public function mxchat_rate_limits_callback() {
7895 $all_options = get_option('mxchat_options', []);
7896
7897 // Define available rate limits
7898 $rate_limits = array('1', '3', '5', '10', '15', '20', '50', '100', 'unlimited');
7899
7900 // Define available timeframes
7901 $timeframes = array(
7902 'hourly' => __('Per Hour', 'mxchat'),
7903 'daily' => __('Per Day', 'mxchat'),
7904 'weekly' => __('Per Week', 'mxchat'),
7905 'monthly' => __('Per Month', 'mxchat')
7906 );
7907
7908 // Get all roles plus a "logged_out" pseudo-role
7909 $roles = wp_roles()->get_names();
7910 $roles['logged_out'] = __('Logged Out Users', 'mxchat');
7911
7912 // Start the wrapper
7913 echo '<div class="pro-feature-wrapper active">';
7914 echo '<div class="mxchat-rate-limits-container">';
7915
7916 echo '<p class="description" style="margin-bottom: 20px;">' .
7917 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') .
7918 '</p>';
7919
7920 // Output the controls for each role
7921 foreach ($roles as $role_id => $role_name) {
7922 // Get saved options or defaults
7923 $default_limit = ($role_id === 'logged_out') ? '10' : '100';
7924 $default_timeframe = 'daily';
7925 $default_message = __('Rate limit exceeded. Please try again later.', 'mxchat');
7926
7927 $selected_limit = isset($all_options['rate_limits'][$role_id]['limit'])
7928 ? $all_options['rate_limits'][$role_id]['limit']
7929 : $default_limit;
7930
7931 $selected_timeframe = isset($all_options['rate_limits'][$role_id]['timeframe'])
7932 ? $all_options['rate_limits'][$role_id]['timeframe']
7933 : $default_timeframe;
7934
7935 $custom_message = isset($all_options['rate_limits'][$role_id]['message'])
7936 ? $all_options['rate_limits'][$role_id]['message']
7937 : $default_message;
7938
7939 // Output the row
7940 echo '<div class="mxchat-rate-limit-row mxchat-autosave-section">';
7941
7942 // Role label
7943 echo '<div class="mxchat-rate-limit-role">' . esc_html($role_name) . '</div>';
7944
7945 // Controls section
7946 echo '<div class="mxchat-rate-limit-controls-wrapper">';
7947
7948 // Rate limit and timeframe controls
7949 echo '<div class="mxchat-rate-limit-controls">';
7950
7951 // Limit dropdown
7952 echo '<div>';
7953 echo '<label for="rate_limits_' . esc_attr($role_id) . '_limit">' . esc_html__('Limit:', 'mxchat') . '</label>';
7954 echo '<select
7955 id="rate_limits_' . esc_attr($role_id) . '_limit"
7956 name="mxchat_options[rate_limits][' . esc_attr($role_id) . '][limit]"
7957 class="mxchat-autosave-field">';
7958 foreach ($rate_limits as $limit) {
7959 echo '<option value="' . esc_attr($limit) . '" ' . selected($selected_limit, $limit, false) . '>' . esc_html($limit) . '</option>';
7960 }
7961 echo '</select>';
7962 echo '</div>';
7963
7964 // Timeframe dropdown
7965 echo '<div>';
7966 echo '<label for="rate_limits_' . esc_attr($role_id) . '_timeframe">' . esc_html__('Timeframe:', 'mxchat') . '</label>';
7967 echo '<select
7968 id="rate_limits_' . esc_attr($role_id) . '_timeframe"
7969 name="mxchat_options[rate_limits][' . esc_attr($role_id) . '][timeframe]"
7970 class="mxchat-autosave-field">';
7971 foreach ($timeframes as $value => $label) {
7972 echo '<option value="' . esc_attr($value) . '" ' . selected($selected_timeframe, $value, false) . '>' . esc_html($label) . '</option>';
7973 }
7974 echo '</select>';
7975 echo '</div>';
7976
7977 echo '</div>'; // End controls
7978
7979 // Custom message textarea
7980 echo '<div class="mxchat-rate-limit-message">';
7981 echo '<label for="rate_limits_' . esc_attr($role_id) . '_message">' . esc_html__('Custom Message:', 'mxchat') . '</label>';
7982 echo '<textarea
7983 id="rate_limits_' . esc_attr($role_id) . '_message"
7984 name="mxchat_options[rate_limits][' . esc_attr($role_id) . '][message]"
7985 class="mxchat-autosave-field"
7986 placeholder="' . esc_attr__('Enter custom message when rate limit is exceeded', 'mxchat') . '">' .
7987 esc_textarea($custom_message) .
7988 '</textarea>';
7989 echo '</div>'; // End message
7990
7991 echo '</div>'; // End controls wrapper
7992
7993 echo '</div>'; // End row
7994 }
7995
7996 echo '</div>'; // End container
7997
7998 echo '</div>'; // End pro-feature-wrapper
7999 }
8000 private function mxchat_add_option_field($id, $title, $callback = '') {
8001 add_settings_field(
8002 $id,
8003 __($title, 'mxchat'),
8004 $callback ? array($this, $callback) : array($this, $id . '_callback'),
8005 'mxchat-max',
8006 'mxchat_setting_section_id',
8007 $id === 'model' ? ['label_for' => 'model'] : []
8008 );
8009 }
8010
8011 // OpenAI API Key
8012 public function api_key_callback() {
8013 $apiKey = isset($this->options['api_key']) ? esc_attr($this->options['api_key']) : '';
8014
8015 echo '<div class="api-key-wrapper" data-provider="openai">';
8016 echo '<input type="password" id="api_key" name="api_key" value="' . $apiKey . '" class="regular-text" autocomplete="off" />';
8017 echo '<button type="button" id="toggleApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
8018 echo '<p class="description api-key-notice">' . esc_html__('Required for your selected chat model. Important: You must add credits before use.', 'mxchat') . '</p>';
8019 echo '</div>';
8020 }
8021
8022 // X.AI API Key
8023 public function xai_api_key_callback() {
8024 $xaiApiKey = isset($this->options['xai_api_key']) ? esc_attr($this->options['xai_api_key']) : '';
8025
8026 echo '<div class="api-key-wrapper" data-provider="xai">';
8027 echo '<input type="password" id="xai_api_key" name="xai_api_key" value="' . $xaiApiKey . '" class="regular-text" autocomplete="off" />';
8028 echo '<button type="button" id="toggleXaiApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
8029 echo '<p class="description api-key-notice">' . esc_html__('Required for your selected chat model. Important: You must add credits before use.', 'mxchat') . '</p>';
8030 echo '</div>';
8031 }
8032 // Claude API Key
8033 public function claude_api_key_callback() {
8034 $claudeApiKey = isset($this->options['claude_api_key']) ? esc_attr($this->options['claude_api_key']) : '';
8035
8036 echo '<div class="api-key-wrapper" data-provider="claude">';
8037 echo '<input type="password" id="claude_api_key" name="claude_api_key" value="' . $claudeApiKey . '" class="regular-text" autocomplete="off" />';
8038 echo '<button type="button" id="toggleClaudeApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
8039 echo '<p class="description api-key-notice">' . esc_html__('Required for your selected chat model. Important: You must add credits before use.', 'mxchat') . '</p>';
8040 echo '</div>';
8041 }
8042
8043 // DeepSeek API Key
8044 public function deepseek_api_key_callback() {
8045 $apiKey = isset($this->options['deepseek_api_key']) ? esc_attr($this->options['deepseek_api_key']) : '';
8046
8047 echo '<div class="api-key-wrapper" data-provider="deepseek">';
8048 echo '<input type="password" id="deepseek_api_key" name="deepseek_api_key" value="' . $apiKey . '" class="regular-text" autocomplete="off" />';
8049 echo '<button type="button" id="toggleDeepSeekApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
8050 echo '<p class="description api-key-notice">' . esc_html__('Required for your selected chat model. Important: You must add credits before use.', 'mxchat') . '</p>';
8051 echo '</div>';
8052 }
8053
8054 // Gemini API Key
8055 public function gemini_api_key_callback() {
8056 $geminiApiKey = isset($this->options['gemini_api_key']) ? esc_attr($this->options['gemini_api_key']) : '';
8057
8058 echo '<div class="api-key-wrapper" data-provider="gemini">';
8059 echo '<input type="password" id="gemini_api_key" name="gemini_api_key" value="' . $geminiApiKey . '" class="regular-text" autocomplete="off" />';
8060 echo '<button type="button" id="toggleGeminiApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
8061 echo '<p class="description api-key-notice">' . esc_html__('Required for Google Gemini models. Get your API key from Google AI Studio.', 'mxchat') . '</p>';
8062 echo '</div>';
8063 }
8064
8065 // Voyage API Key
8066 public function voyage_api_key_callback() {
8067 $apiKey = isset($this->options['voyage_api_key']) ? esc_attr($this->options['voyage_api_key']) : '';
8068
8069 echo '<div class="api-key-wrapper" data-provider="voyage">';
8070 echo '<input type="password" id="voyage_api_key" name="voyage_api_key" value="' . $apiKey . '" class="regular-text" autocomplete="off" />';
8071 echo '<button type="button" id="toggleVoyageAPIKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
8072 echo '<p class="description api-key-notice">' . esc_html__('Required for your selected embedding model. Important: You must add credits before use.', 'mxchat') . '</p>';
8073 echo '</div>';
8074 }
8075
8076 public function mxchat_loops_api_key_callback() {
8077 $loops_api_key = isset($this->options['loops_api_key']) ? esc_attr($this->options['loops_api_key']) : '';
8078
8079 // Hidden fields to "trap" autofill
8080 echo '<input type="text" style="display:none" autocomplete="username" />';
8081 echo '<input type="password" style="display:none" autocomplete="current-password" />';
8082
8083 echo '<div class="api-key-wrapper" data-provider="loops">';
8084 echo sprintf(
8085 '<input type="password" id="loops_api_key" name="loops_api_key" value="%s" class="regular-text" autocomplete="new-password" />',
8086 $loops_api_key
8087 );
8088 echo '<button type="button" id="toggleLoopsApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
8089 echo '</div>';
8090 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>';
8091 }
8092 public function mxchat_loops_mailing_list_callback() {
8093 // Add error handling and type checking
8094 $loops_api_key = '';
8095 $selected_list = '';
8096
8097 // Safely get the API key
8098 if (isset($this->options['loops_api_key']) && is_string($this->options['loops_api_key'])) {
8099 $loops_api_key = $this->options['loops_api_key'];
8100 }
8101
8102 // Safely get the selected list
8103 if (isset($this->options['loops_mailing_list']) && is_string($this->options['loops_mailing_list'])) {
8104 $selected_list = $this->options['loops_mailing_list'];
8105 }
8106
8107 if (!empty($loops_api_key)) {
8108 $lists = $this->mxchat_fetch_loops_mailing_lists($loops_api_key);
8109 if (is_array($lists) && !empty($lists)) {
8110 echo '<select id="loops_mailing_list" name="loops_mailing_list">';
8111
8112 // Add a default "Select a list" option
8113 echo '<option value="" ' . selected($selected_list, '', false) . '>' . esc_html__('Select a list', 'mxchat') . '</option>';
8114
8115 foreach ($lists as $list) {
8116 if (is_array($list) && isset($list['id']) && isset($list['name'])) {
8117 echo sprintf(
8118 '<option value="%s" %s>%s</option>',
8119 esc_attr($list['id']),
8120 selected($selected_list, $list['id'], false),
8121 esc_html($list['name'])
8122 );
8123 }
8124 }
8125 echo '</select>';
8126 echo '<p class="description">' . esc_html__('Please select a mailing list to use with Loops.', 'mxchat') . '</p>';
8127 } else {
8128 echo '<p class="description">' . esc_html__('No lists found. Please verify your API Key.', 'mxchat') . '</p>';
8129 }
8130 } else {
8131 echo '<p class="description">' . esc_html__('Enter a valid Loops API Key to load mailing lists.', 'mxchat') . '</p>';
8132 }
8133 }
8134 public function mxchat_triggered_phrase_response_callback() {
8135 $default_response = __('Would you like to join our mailing list? Please provide your email below.', 'mxchat');
8136 $triggered_response = isset($this->options['triggered_phrase_response'])
8137 ? $this->options['triggered_phrase_response']
8138 : $default_response;
8139
8140 echo sprintf(
8141 '<textarea id="triggered_phrase_response" name="triggered_phrase_response" rows="3" cols="50">%s</textarea>',
8142 esc_textarea($triggered_response)
8143 );
8144 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>';
8145 }
8146
8147 public function mxchat_email_capture_response_callback() {
8148 $default_response = __('Thank you for providing your email! You\'ve been added to our list.', 'mxchat');
8149 $email_capture_response = isset($this->options['email_capture_response'])
8150 ? $this->options['email_capture_response']
8151 : $default_response;
8152
8153 echo sprintf(
8154 '<textarea id="email_capture_response" name="email_capture_response" rows="3" cols="50">%s</textarea>',
8155 esc_textarea($email_capture_response)
8156 );
8157 echo '<p class="description">' . esc_html__('Enter the message to send when a user provides their email.', 'mxchat') . '</p>';
8158 }
8159
8160 public function mxchat_pre_chat_message_callback() {
8161 // Load the entire 'mxchat_options' array
8162 $all_options = get_option('mxchat_options', []);
8163
8164 // Retrieve the saved message or use the default value
8165 $default_message = __('Hey there! Ask me anything!', 'mxchat');
8166 $pre_chat_message = isset($all_options['pre_chat_message']) ? $all_options['pre_chat_message'] : $default_message;
8167
8168 // Output the textarea
8169 printf(
8170 '<textarea id="pre_chat_message" name="pre_chat_message" rows="5" cols="50">%s</textarea>',
8171 esc_textarea($pre_chat_message)
8172 );
8173 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>';
8174 }
8175
8176 // Callback for AI Instructions textarea
8177 public function system_prompt_instructions_callback() {
8178 // Retrieve the current value of the system prompt instructions
8179 $instructions = isset($this->options['system_prompt_instructions']) ? esc_textarea($this->options['system_prompt_instructions']) : '';
8180 // Render the textarea field
8181 printf(
8182 '<textarea id="system_prompt_instructions" name="system_prompt_instructions" rows="5" cols="50">%s</textarea>',
8183 $instructions
8184 );
8185 // Provide a helpful description with sample instructions button
8186 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>';
8187 echo '<div class="mxchat-instructions-container">';
8188 echo '<button type="button" class="mxchat-instructions-btn" id="mxchatViewSampleBtn">';
8189 echo '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">';
8190 echo '<path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/>';
8191 echo '<path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/>';
8192 echo '</svg>';
8193 echo esc_html__('View Sample Instructions', 'mxchat');
8194 echo '</button>';
8195 echo '</div>';
8196
8197 // Add modal to WordPress admin footer instead of inline
8198 add_action('admin_footer', array($this, 'render_sample_instructions_modal'));
8199 }
8200
8201 // New method to render modal in admin footer
8202 public function render_sample_instructions_modal() {
8203 static $modal_rendered = false;
8204 if ($modal_rendered) return; // Prevent duplicate modals
8205 $modal_rendered = true;
8206
8207 echo '<div class="mxchat-instructions-modal-overlay" id="mxchatSampleModal">';
8208 echo '<div class="mxchat-instructions-modal-content">';
8209 echo '<div class="mxchat-instructions-modal-header">';
8210 echo '<h3 class="mxchat-instructions-modal-title">';
8211 echo '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">';
8212 echo '<path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/>';
8213 echo '<path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/>';
8214 echo '</svg>';
8215 echo esc_html__('Sample AI Instructions', 'mxchat');
8216 echo '</h3>';
8217 echo '<button type="button" class="mxchat-instructions-modal-close" id="mxchatModalClose">';
8218 echo '<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">';
8219 echo '<line x1="18" y1="6" x2="6" y2="18"/>';
8220 echo '<line x1="6" y1="6" x2="18" y2="18"/>';
8221 echo '</svg>';
8222 echo '</button>';
8223 echo '</div>';
8224 echo '<div class="mxchat-instructions-modal-body">';
8225 echo '<div class="mxchat-instructions-content">';
8226 echo esc_html('You are an AI Chatbot assistant for this website. Your main goal is to assist visitors with questions and provide helpful information. Here are your key guidelines:
8227
8228 # Response Style - CRITICALLY IMPORTANT
8229 - MAXIMUM LENGTH: 1-3 short sentences per response
8230 - Ultra-concise: Get straight to the answer with no filler
8231 - No introductions like "Sure!" or "I\'d be happy to help"
8232 - No phrases like "based on my knowledge" or "according to information"
8233 - No explanatory text before giving the answer
8234 - No summaries or repetition
8235 - Hyperlink all URLs
8236 - Respond in user\'s language
8237 - Minor chit chat or conversation is okay, but try to keep it focused on [insert topic]
8238
8239 # Knowledge Base Requirements - PREVENT HALLUCINATIONS
8240 - ONLY answer using information explicitly provided in OFFICIAL KNOWLEDGE DATABASE CONTENT sections marked with ===== delimiters
8241 - If required information is NOT in the knowledge database: "I don\'t have enough information in my knowledge base to answer that question accurately."
8242 - NEVER invent or hallucinate URLs, links, product specs, procedures, dates, statistics, names, contacts, or company information
8243 - When knowledge base information is unclear or contradictory, acknowledge the limitation rather than guessing
8244 - If asked about something not in knowledge base, explicitly state information is not available - DO NOT provide general information
8245 - Better to admit insufficient information than provide inaccurate answers');
8246 echo '</div>';
8247 echo '<button type="button" class="mxchat-instructions-copy-btn" id="mxchatCopyBtn">';
8248 echo '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">';
8249 echo '<rect x="9" y="9" width="13" height="13" rx="2" ry="2"/>';
8250 echo '<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/>';
8251 echo '</svg>';
8252 echo esc_html__('Copy Instructions', 'mxchat');
8253 echo '</button>';
8254 echo '</div>';
8255 echo '<div class="mxchat-instructions-modal-footer">';
8256 echo '<button type="button" class="mxchat-instructions-btn-secondary" id="mxchatCloseBtn">' . esc_html__('Close', 'mxchat') . '</button>';
8257 echo '</div>';
8258 echo '</div>';
8259 echo '</div>';
8260 }
8261
8262
8263 public function mxchat_model_callback() {
8264 // Define available models grouped by provider
8265 $models = array(
8266 esc_html__('Google Gemini Models', 'mxchat') => array(
8267 'gemini-2.0-flash' => esc_html__('Gemini 2.0 Flash (Next-Gen Features)', 'mxchat'),
8268 'gemini-2.0-flash-lite' => esc_html__('Gemini 2.0 Flash-Lite (Cost-Efficient)', 'mxchat'),
8269 'gemini-1.5-pro' => esc_html__('Gemini 1.5 Pro (Complex Reasoning)', 'mxchat'),
8270 'gemini-1.5-flash' => esc_html__('Gemini 1.5 Flash (Fast & Versatile)', 'mxchat'),
8271 ),
8272 esc_html__('X.AI Models', 'mxchat') => array(
8273 'grok-3-beta' => esc_html__('Grok-3 (Powerful)', 'mxchat'),
8274 'grok-3-fast-beta' => esc_html__('Grok-3 Fast (High Performance)', 'mxchat'),
8275 'grok-3-mini-beta' => esc_html__('Grok-3 Mini (Affordable)', 'mxchat'),
8276 'grok-3-mini-fast-beta' => esc_html__('Grok-3 Mini Fast (Quick Response)', 'mxchat'),
8277 'grok-2' => esc_html__('Grok 2', 'mxchat')
8278 ),
8279 esc_html__('DeepSeek Models', 'mxchat') => array(
8280 'deepseek-chat' => esc_html__('DeepSeek-V3', 'mxchat'),
8281 ),
8282 esc_html__('Claude Models', 'mxchat') => array(
8283 'claude-opus-4-20250514' => esc_html__('Claude 4 Opus (Most Capable)', 'mxchat'),
8284 'claude-sonnet-4-20250514' => esc_html__('Claude 4 Sonnet (High Performance)', 'mxchat'),
8285 'claude-3-7-sonnet-20250219' => esc_html__('Claude 3.7 Sonnet (High Intelligence)', 'mxchat'),
8286 'claude-3-5-sonnet-20241022' => esc_html__('Claude 3.5 Sonnet (Intelligent)', 'mxchat'),
8287 'claude-3-opus-20240229' => esc_html__('Claude 3 Opus (Complex Tasks)', 'mxchat'),
8288 'claude-3-sonnet-20240229' => esc_html__('Claude 3 Sonnet (Balanced)', 'mxchat'),
8289 'claude-3-haiku-20240307' => esc_html__('Claude 3 Haiku (Fastest)', 'mxchat')
8290 ),
8291 esc_html__('OpenAI Models', 'mxchat') => array(
8292 'gpt-4.1-2025-04-14' => esc_html__('GPT-4.1 (Flagship for Complex Tasks)', 'mxchat'),
8293 'gpt-4o' => esc_html__('GPT-4o (Recommended)', 'mxchat'),
8294 'gpt-4o-mini' => esc_html__('GPT-4o Mini (Fast and Lightweight)', 'mxchat'),
8295 'gpt-4-turbo' => esc_html__('GPT-4 Turbo (High-Performance)', 'mxchat'),
8296 'gpt-4' => esc_html__('GPT-4 (High Intelligence)', 'mxchat'),
8297 'gpt-3.5-turbo' => esc_html__('GPT-3.5 Turbo (Affordable and Fast)', 'mxchat')
8298 )
8299 );
8300
8301 // Retrieve the currently selected model from saved options
8302 $selected_model = isset($this->options['model']) ? esc_attr($this->options['model']) : 'gpt-4o';
8303
8304 // Begin the select dropdown
8305 echo '<select id="model" name="model">';
8306
8307 // Iterate over groups of models
8308 foreach ($models as $group_label => $group_models) {
8309 echo '<optgroup label="' . esc_attr($group_label) . '">';
8310
8311 foreach ($group_models as $model_value => $model_label) {
8312 // All models enabled - no disabled attribute or Pro Only label
8313 echo '<option value="' . esc_attr($model_value) . '" ' . selected($selected_model, $model_value, false) . '>' . esc_html($model_label) . '</option>';
8314 }
8315
8316 echo '</optgroup>';
8317 }
8318
8319 // Close the select dropdown
8320 echo '</select>';
8321
8322 // Updated description to remove mention of Pro-only models
8323 echo '<p class="description">' . esc_html__('Select the AI model your chatbot will use for chatting.', 'mxchat') . '</p>';
8324 }
8325
8326 // Callback function for embedding model selection
8327 public function embedding_model_callback() {
8328 $models = array(
8329 esc_html__('OpenAI Embeddings', 'mxchat') => array(
8330 'text-embedding-3-small' => esc_html__('TE3 Small (1536, Efficient)', 'mxchat'),
8331 'text-embedding-ada-002' => esc_html__('Ada 2 (1536, Recommended)', 'mxchat'),
8332 'text-embedding-3-large' => esc_html__('TE3 Large (3072, Powerful)', 'mxchat'),
8333 ),
8334 esc_html__('Voyage AI Embeddings', 'mxchat') => array(
8335 'voyage-3-large' => esc_html__('Voyage-3 Large (2048, Most Capable)', 'mxchat'),
8336 ),
8337 esc_html__('Google Gemini Embeddings', 'mxchat') => array(
8338 'gemini-embedding-exp-03-07' => esc_html__('Gemini Embedding (1536, Experimental)', 'mxchat'),
8339 )
8340 );
8341 $selected_model = isset($this->options['embedding_model']) ? esc_attr($this->options['embedding_model']) : 'text-embedding-ada-002';
8342 echo '<select id="embedding_model" name="embedding_model">';
8343 foreach ($models as $group_label => $group_models) {
8344 echo '<optgroup label="' . esc_attr($group_label) . '">';
8345 foreach ($group_models as $model_value => $model_label) {
8346 echo '<option value="' . esc_attr($model_value) . '" ' . selected($selected_model, $model_value, false) . '>' . esc_html($model_label) . '</option>';
8347 }
8348 echo '</optgroup>';
8349 }
8350 echo '</select>';
8351 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>';
8352 }
8353
8354
8355 public function mxchat_top_bar_title_callback() {
8356 // Retrieve the current value of the top bar title from saved options
8357 $top_bar_title = isset($this->options['top_bar_title']) ? esc_attr($this->options['top_bar_title']) : '';
8358
8359 // Render the input field
8360 echo '<input type="text" id="top_bar_title" name="top_bar_title" value="' . $top_bar_title . '" />';
8361
8362 // Add a description
8363 echo '<p class="description">' . esc_html__('Enter the title text that will appear on the top bar of the chatbot.', 'mxchat') . '</p>';
8364 }
8365 public function mxchat_ai_agent_text_callback() {
8366 // Retrieve the current value of the AI agent text from saved options
8367 $ai_agent_text = isset($this->options['ai_agent_text']) ? esc_attr($this->options['ai_agent_text']) : '';
8368 // Render the input field
8369 echo '<input type="text" id="ai_agent_text" name="ai_agent_text" value="' . $ai_agent_text . '" />';
8370 // Add a description
8371 echo '<p class="description">' . esc_html__('Enter the text that will appear for AI agents in the status indicator. Default: "AI Agent"', 'mxchat') . '</p>';
8372 }
8373 public function enable_email_block_callback() {
8374 // Load full plugin options array
8375 $all_options = get_option('mxchat_options', []);
8376
8377 // Get the value, default to 'off'
8378 $enable_email_block = isset($all_options['enable_email_block']) ? $all_options['enable_email_block'] : 'off';
8379
8380 // Check if it's 'on'
8381 $checked = ($enable_email_block === 'on') ? 'checked' : '';
8382
8383 echo '<label class="toggle-switch">';
8384 echo sprintf(
8385 '<input type="checkbox" id="enable_email_block" name="enable_email_block" value="on" %s />',
8386 esc_attr($checked)
8387 );
8388 echo '<span class="slider"></span>';
8389 echo '</label>';
8390 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>';
8391 }
8392
8393
8394 public function email_blocker_header_content_callback() {
8395 // Load the entire 'mxchat_options' array
8396 $all_options = get_option('mxchat_options', []);
8397
8398 // Retrieve the saved content or default to empty
8399 $content = isset($all_options['email_blocker_header_content'])
8400 ? $all_options['email_blocker_header_content']
8401 : '';
8402
8403 // Render the textarea - IMPORTANT: name should be just "email_blocker_header_content"
8404 echo '<textarea
8405 id="email_blocker_header_content"
8406 name="email_blocker_header_content"
8407 rows="5"
8408 cols="70"
8409 data-setting="email_blocker_header_content"
8410 >' . esc_textarea($content) . '</textarea>';
8411
8412 echo '<p class="description">';
8413 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');
8414 echo '</p>';
8415 }
8416
8417 public function email_blocker_button_text_callback() {
8418 // Load the entire 'mxchat_options' array
8419 $all_options = get_option('mxchat_options', []);
8420
8421 // Retrieve the saved button text or default to empty
8422 $button_text = isset($all_options['email_blocker_button_text'])
8423 ? $all_options['email_blocker_button_text']
8424 : '';
8425
8426 // Use esc_attr to safely render the existing text
8427 echo '<input type="text" id="email_blocker_button_text" name="email_blocker_button_text" value="' . esc_attr($button_text) . '" style="width: 300px;" />';
8428
8429 echo '<p class="description">';
8430 echo esc_html__('Enter the text you want on the submit button, e.g. "Start Chat".', 'mxchat');
8431 echo '</p>';
8432 }
8433
8434
8435
8436 public function mxchat_intro_message_callback() {
8437 // Load the entire 'mxchat_options' array
8438 $all_options = get_option('mxchat_options', []);
8439 // Retrieve the saved intro message or use the default
8440 $default_message = __('Hello! How can I assist you today?', 'mxchat');
8441 $saved_message = isset($all_options['intro_message']) ? $all_options['intro_message'] : $default_message;
8442 // Output the textarea with the saved value without escaping HTML
8443 ?>
8444 <textarea id="intro_message" name="intro_message" rows="5" cols="50"><?php echo $saved_message; ?></textarea>
8445 <p class="description">
8446 <?php esc_html_e('Enter your message. HTML tags and line breaks will be preserved.', 'mxchat'); ?>
8447 </p>
8448 <?php
8449 }
8450
8451 public function mxchat_input_copy_callback() {
8452 // Load the entire 'mxchat_options' array
8453 $all_options = get_option('mxchat_options', []);
8454
8455 // Retrieve the saved input copy or use the default value
8456 $default_copy = __('How can I assist?', 'mxchat');
8457 $input_copy = isset($all_options['input_copy']) ? $all_options['input_copy'] : $default_copy;
8458
8459 // Output the input field with the saved value
8460 printf(
8461 '<input type="text" id="input_copy" name="input_copy" value="%s" placeholder="%s" />',
8462 esc_attr($input_copy),
8463 esc_attr__('How can I assist?', 'mxchat')
8464 );
8465
8466 // Output the description
8467 echo '<p class="description">' . esc_html__('This is the placeholder text for the chat input field.', 'mxchat') . '</p>';
8468 }
8469
8470
8471
8472 public function mxchat_user_message_font_color_callback() {
8473 $disabled = $this->is_activated ? '' : 'disabled';
8474 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
8475
8476 echo '<div class="' . esc_attr($class) . '">';
8477 echo sprintf(
8478 '<input type="text"
8479 id="user_message_font_color"
8480 name="user_message_font_color"
8481 value="%s"
8482 class="my-color-field"
8483 data-default-color="#ffffff"
8484 %s />',
8485 isset($this->options['user_message_font_color']) ? esc_attr($this->options['user_message_font_color']) : '#ffffff',
8486 esc_attr($disabled)
8487 );
8488
8489 if (!$this->is_activated) {
8490 echo '<div class="pro-feature-overlay">';
8491 echo '<a href="https://mxchat.ai/" target="_blank">';
8492 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
8493 echo '</a>';
8494 echo '</div>';
8495 }
8496 echo '</div>';
8497 }
8498
8499 public function mxchat_bot_message_bg_color_callback() {
8500 $disabled = $this->is_activated ? '' : 'disabled';
8501 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
8502
8503 echo '<div class="' . esc_attr($class) . '">';
8504 echo sprintf(
8505 '<input type="text"
8506 id="bot_message_bg_color"
8507 name="bot_message_bg_color"
8508 value="%s"
8509 class="my-color-field"
8510 data-default-color="#e1e1e1"
8511 %s />',
8512 isset($this->options['bot_message_bg_color']) ? esc_attr($this->options['bot_message_bg_color']) : '#e1e1e1',
8513 esc_attr($disabled)
8514 );
8515
8516 if (!$this->is_activated) {
8517 echo '<div class="pro-feature-overlay">';
8518 echo '<a href="https://mxchat.ai/" target="_blank">';
8519 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
8520 echo '</a>';
8521 echo '</div>';
8522 }
8523 echo '</div>';
8524 }
8525
8526 public function mxchat_bot_message_font_color_callback() {
8527 $disabled = $this->is_activated ? '' : 'disabled';
8528 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
8529
8530 echo '<div class="' . esc_attr($class) . '">';
8531 echo sprintf(
8532 '<input type="text"
8533 id="bot_message_font_color"
8534 name="bot_message_font_color"
8535 value="%s"
8536 class="my-color-field"
8537 data-default-color="#333333"
8538 %s />',
8539 isset($this->options['bot_message_font_color']) ? esc_attr($this->options['bot_message_font_color']) : '#333333',
8540 esc_attr($disabled)
8541 );
8542
8543 if (!$this->is_activated) {
8544 echo '<div class="pro-feature-overlay">';
8545 echo '<a href="https://mxchat.ai/" target="_blank">';
8546 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
8547 echo '</a>';
8548 echo '</div>';
8549 }
8550 echo '</div>';
8551 }
8552
8553 public function mxchat_live_agent_message_bg_color_callback() {
8554 $disabled = $this->is_activated ? '' : 'disabled';
8555 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
8556
8557 echo '<div class="' . esc_attr($class) . '">';
8558 echo sprintf(
8559 '<input type="text"
8560 id="live_agent_message_bg_color"
8561 name="live_agent_message_bg_color"
8562 value="%s"
8563 class="my-color-field"
8564 data-default-color="#ffffff"
8565 %s />',
8566 isset($this->options['live_agent_message_bg_color']) ? esc_attr($this->options['live_agent_message_bg_color']) : '#ffffff',
8567 esc_attr($disabled)
8568 );
8569
8570 if (!$this->is_activated) {
8571 echo '<div class="pro-feature-overlay">';
8572 echo '<a href="https://mxchat.ai/" target="_blank">';
8573 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
8574 echo '</a>';
8575 echo '</div>';
8576 }
8577 echo '</div>';
8578 }
8579
8580 public function mxchat_live_agent_message_font_color_callback() {
8581 $disabled = $this->is_activated ? '' : 'disabled';
8582 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
8583
8584 echo '<div class="' . esc_attr($class) . '">';
8585 echo sprintf(
8586 '<input type="text"
8587 id="live_agent_message_font_color"
8588 name="live_agent_message_font_color"
8589 value="%s"
8590 class="my-color-field"
8591 data-default-color="#333333"
8592 %s />',
8593 isset($this->options['live_agent_message_font_color']) ? esc_attr($this->options['live_agent_message_font_color']) : '#333333',
8594 esc_attr($disabled)
8595 );
8596
8597 if (!$this->is_activated) {
8598 echo '<div class="pro-feature-overlay">';
8599 echo '<a href="https://mxchat.ai/" target="_blank">';
8600 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
8601 echo '</a>';
8602 echo '</div>';
8603 }
8604 echo '</div>';
8605 }
8606
8607 public function mxchat_mode_indicator_bg_color_callback() {
8608 $disabled = $this->is_activated ? '' : 'disabled';
8609 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
8610
8611 echo '<div class="' . esc_attr($class) . '">';
8612 echo sprintf(
8613 '<input type="text"
8614 id="mode_indicator_bg_color"
8615 name="mode_indicator_bg_color"
8616 value="%s"
8617 class="my-color-field"
8618 data-default-color="#767676"
8619 %s />',
8620 isset($this->options['mode_indicator_bg_color']) ? esc_attr($this->options['mode_indicator_bg_color']) : '#767676',
8621 esc_attr($disabled)
8622 );
8623
8624 if (!$this->is_activated) {
8625 echo '<div class="pro-feature-overlay">';
8626 echo '<a href="https://mxchat.ai/" target="_blank">';
8627 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
8628 echo '</a>';
8629 echo '</div>';
8630 }
8631 echo '</div>';
8632 }
8633
8634 public function mxchat_mode_indicator_font_color_callback() {
8635 $disabled = $this->is_activated ? '' : 'disabled';
8636 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
8637
8638 echo '<div class="' . esc_attr($class) . '">';
8639 echo sprintf(
8640 '<input type="text"
8641 id="mode_indicator_font_color"
8642 name="mode_indicator_font_color"
8643 value="%s"
8644 class="my-color-field"
8645 data-default-color="#ffffff"
8646 %s />',
8647 isset($this->options['mode_indicator_font_color']) ? esc_attr($this->options['mode_indicator_font_color']) : '#ffffff',
8648 esc_attr($disabled)
8649 );
8650
8651 if (!$this->is_activated) {
8652 echo '<div class="pro-feature-overlay">';
8653 echo '<a href="https://mxchat.ai/" target="_blank">';
8654 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
8655 echo '</a>';
8656 echo '</div>';
8657 }
8658 echo '</div>';
8659 }
8660
8661 public function mxchat_toolbar_icon_color_callback() {
8662 $disabled = $this->is_activated ? '' : 'disabled';
8663 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
8664
8665 echo '<div class="' . esc_attr($class) . '">';
8666 echo sprintf(
8667 '<input type="text"
8668 id="toolbar_icon_color"
8669 name="toolbar_icon_color"
8670 value="%s"
8671 class="my-color-field"
8672 data-default-color="#212121"
8673 %s />',
8674 isset($this->options['toolbar_icon_color']) ? esc_attr($this->options['toolbar_icon_color']) : '#212121',
8675 esc_attr($disabled)
8676 );
8677
8678 if (!$this->is_activated) {
8679 echo '<div class="pro-feature-overlay">';
8680 echo '<a href="https://mxchat.ai/" target="_blank">';
8681 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
8682 echo '</a>';
8683 echo '</div>';
8684 }
8685 echo '</div>';
8686 }
8687
8688 public function mxchat_top_bar_bg_color_callback() {
8689 $disabled = $this->is_activated ? '' : 'disabled';
8690 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
8691
8692 echo '<div class="' . esc_attr($class) . '">';
8693 echo sprintf(
8694 '<input type="text"
8695 id="top_bar_bg_color"
8696 name="top_bar_bg_color"
8697 value="%s"
8698 class="my-color-field"
8699 data-default-color="#00b294"
8700 %s />',
8701 isset($this->options['top_bar_bg_color']) ? esc_attr($this->options['top_bar_bg_color']) : '#00b294',
8702 esc_attr($disabled)
8703 );
8704
8705 if (!$this->is_activated) {
8706 echo '<div class="pro-feature-overlay">';
8707 echo '<a href="https://mxchat.ai/" target="_blank">';
8708 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
8709 echo '</a>';
8710 echo '</div>';
8711 }
8712 echo '</div>';
8713 }
8714
8715 public function mxchat_send_button_font_color_callback() {
8716 $disabled = $this->is_activated ? '' : 'disabled';
8717 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
8718
8719 echo '<div class="' . esc_attr($class) . '">';
8720 echo sprintf(
8721 '<input type="text"
8722 id="send_button_font_color"
8723 name="send_button_font_color"
8724 value="%s"
8725 class="my-color-field"
8726 data-default-color="#ffffff"
8727 %s />',
8728 isset($this->options['send_button_font_color']) ? esc_attr($this->options['send_button_font_color']) : '#ffffff',
8729 esc_attr($disabled)
8730 );
8731
8732 if (!$this->is_activated) {
8733 echo '<div class="pro-feature-overlay">';
8734 echo '<a href="https://mxchat.ai/" target="_blank">';
8735 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
8736 echo '</a>';
8737 echo '</div>';
8738 }
8739 echo '</div>';
8740 }
8741
8742 public function mxchat_chatbot_background_color_callback() {
8743 $disabled = $this->is_activated ? '' : 'disabled';
8744 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
8745
8746 echo '<div class="' . esc_attr($class) . '">';
8747 echo sprintf(
8748 '<input type="text"
8749 id="chatbot_background_color"
8750 name="chatbot_background_color"
8751 value="%s"
8752 class="my-color-field"
8753 data-default-color="#000000"
8754 %s />',
8755 isset($this->options['chatbot_background_color']) ? esc_attr($this->options['chatbot_background_color']) : '#000000',
8756 esc_attr($disabled)
8757 );
8758
8759 if (!$this->is_activated) {
8760 echo '<div class="pro-feature-overlay">';
8761 echo '<a href="https://mxchat.ai/" target="_blank">';
8762 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
8763 echo '</a>';
8764 echo '</div>';
8765 }
8766 echo '</div>';
8767 }
8768
8769 public function mxchat_icon_color_callback() {
8770 $disabled = $this->is_activated ? '' : 'disabled';
8771 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
8772
8773 echo '<div class="' . esc_attr($class) . '">';
8774 echo sprintf(
8775 '<input type="text"
8776 id="icon_color"
8777 name="icon_color"
8778 value="%s"
8779 class="my-color-field"
8780 data-default-color="#ffffff"
8781 %s />',
8782 isset($this->options['icon_color']) ? esc_attr($this->options['icon_color']) : '#ffffff',
8783 esc_attr($disabled)
8784 );
8785
8786 if (!$this->is_activated) {
8787 echo '<div class="pro-feature-overlay">';
8788 echo '<a href="https://mxchat.ai/" target="_blank">';
8789 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
8790 echo '</a>';
8791 echo '</div>';
8792 }
8793 echo '</div>';
8794 }
8795
8796 public function mxchat_custom_icon_callback() {
8797 $disabled = $this->is_activated ? '' : 'disabled';
8798 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
8799 $custom_icon_url = isset($this->options['custom_icon']) ? esc_url($this->options['custom_icon']) : '';
8800
8801 echo '<div class="' . esc_attr($class) . '">';
8802 echo sprintf(
8803 '<input type="url"
8804 id="custom_icon"
8805 name="custom_icon"
8806 value="%s"
8807 placeholder="%s"
8808 class="regular-text"
8809 %s />',
8810 $custom_icon_url,
8811 esc_attr__('Enter PNG URL', 'mxchat'),
8812 esc_attr($disabled)
8813 );
8814
8815 // Preview container for the icon
8816 if (!empty($custom_icon_url)) {
8817 echo '<div class="icon-preview" style="margin-top: 10px;">';
8818 echo '<img src="' . esc_url($custom_icon_url) . '" alt="' . esc_attr__('Custom Icon Preview', 'mxchat') . '" style="max-width: 48px; height: auto;" />';
8819 echo '</div>';
8820 }
8821
8822 echo '<p class="description">' . esc_html__('Upload your PNG icon and paste the URL here. Recommended size: 48x48 pixels.', 'mxchat') . '</p>';
8823
8824 if (!$this->is_activated) {
8825 echo '<div class="pro-feature-overlay">';
8826 echo '<a href="https://mxchat.ai/" target="_blank">';
8827 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
8828 echo '</a>';
8829 echo '</div>';
8830 }
8831 echo '</div>';
8832 }
8833
8834 public function mxchat_title_icon_callback() {
8835 $disabled = $this->is_activated ? '' : 'disabled';
8836 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
8837 // Fixed the variable reference - it was using custom_icon instead of title_icon
8838 $title_icon_url = isset($this->options['title_icon']) ? esc_url($this->options['title_icon']) : '';
8839
8840 echo '<div class="' . esc_attr($class) . '">';
8841 echo sprintf(
8842 '<input type="url"
8843 id="title_icon"
8844 name="title_icon"
8845 value="%s"
8846 placeholder="%s"
8847 class="regular-text"
8848 %s />',
8849 $title_icon_url,
8850 esc_attr__('Enter PNG URL', 'mxchat'),
8851 esc_attr($disabled)
8852 );
8853
8854 // Preview container for the icon
8855 if (!empty($title_icon_url)) {
8856 echo '<div class="icon-preview" style="margin-top: 10px;">';
8857 echo '<img src="' . esc_url($title_icon_url) . '" alt="' . esc_attr__('Title Icon Preview', 'mxchat') . '" style="max-width: 48px; height: auto;" />';
8858 echo '</div>';
8859 }
8860
8861 echo '<p class="description">' . esc_html__('Upload your PNG icon and paste the URL here. Recommended size: 48x48 pixels.', 'mxchat') . '</p>';
8862
8863 if (!$this->is_activated) {
8864 echo '<div class="pro-feature-overlay">';
8865 echo '<a href="https://mxchat.ai/" target="_blank">';
8866 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
8867 echo '</a>';
8868 echo '</div>';
8869 }
8870 echo '</div>';
8871 }
8872
8873 public function mxchat_chat_input_font_color_callback() {
8874 $disabled = $this->is_activated ? '' : 'disabled';
8875 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
8876
8877 echo '<div class="' . esc_attr($class) . '">';
8878 echo sprintf(
8879 '<input type="text"
8880 id="chat_input_font_color"
8881 name="chat_input_font_color"
8882 value="%s"
8883 class="my-color-field"
8884 data-default-color="#555555"
8885 %s />',
8886 isset($this->options['chat_input_font_color']) ? esc_attr($this->options['chat_input_font_color']) : '#555555',
8887 esc_attr($disabled)
8888 );
8889
8890 if (!$this->is_activated) {
8891 echo '<div class="pro-feature-overlay">';
8892 echo '<a href="https://mxchat.ai/" target="_blank">';
8893 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
8894 echo '</a>';
8895 echo '</div>';
8896 }
8897 echo '</div>';
8898 }
8899
8900 public function mxchat_append_to_body_callback() {
8901 // Get value from options array, default to 'off'
8902 $append_to_body = isset($this->options['append_to_body']) ? $this->options['append_to_body'] : 'off';
8903 $checked = ($append_to_body === 'on') ? 'checked' : '';
8904
8905 echo '<label class="toggle-switch">';
8906 echo sprintf(
8907 '<input type="checkbox" id="append_to_body" name="append_to_body" value="on" %s />',
8908 esc_attr($checked)
8909 );
8910 echo '<span class="slider"></span>';
8911 echo '</label>';
8912 echo '<p class="description">' .
8913 esc_html__('Show chatbot automatically on all pages. When disabled, you can place the chatbot manually using shortcode [mxchat_chatbot floating="yes"].', 'mxchat') .
8914 '</p>';
8915
8916 }
8917
8918
8919
8920 public function mxchat_privacy_toggle_callback() {
8921 // Load from mxchat_options array
8922 $options = get_option('mxchat_options', []);
8923
8924 // Get privacy toggle value with fallback
8925 $privacy_toggle = isset($options['privacy_toggle']) ? $options['privacy_toggle'] : 'off';
8926 $checked = ($privacy_toggle === 'on') ? 'checked' : '';
8927
8928 // Get privacy text with fallback
8929 $privacy_text = isset($options['privacy_text'])
8930 ? $options['privacy_text']
8931 : __('By chatting, you agree to our <a href="https://example.com/privacy-policy" target="_blank">privacy policy</a>.', 'mxchat');
8932
8933 // Output the toggle switch
8934 echo '<label class="toggle-switch">';
8935 echo sprintf(
8936 '<input type="checkbox" id="privacy_toggle" name="privacy_toggle" value="on" %s />',
8937 esc_attr($checked)
8938 );
8939 echo '<span class="slider"></span>';
8940 echo '</label>';
8941 echo '<p class="description">' . esc_html__('Enable this option to display a privacy notice below the chat widget.', 'mxchat') . '</p>';
8942
8943 // Output the custom text input field
8944 echo sprintf(
8945 '<textarea id="privacy_text" name="privacy_text" rows="5" cols="50" class="regular-text">%s</textarea>',
8946 esc_textarea($privacy_text)
8947 );
8948 echo '<p class="description">' . esc_html__('Enter the privacy policy text. You can include HTML links.', 'mxchat') . '</p>';
8949 }
8950
8951
8952 public function mxchat_complianz_toggle_callback() {
8953 // Load from mxchat_options array
8954 $options = get_option('mxchat_options', []);
8955
8956 // Get complianz toggle value with fallback
8957 $complianz_toggle = isset($options['complianz_toggle']) ? $options['complianz_toggle'] : 'off';
8958 $checked = ($complianz_toggle === 'on') ? 'checked' : '';
8959
8960 // Output the toggle switch
8961 echo '<label class="toggle-switch">';
8962 echo sprintf(
8963 '<input type="checkbox" id="complianz_toggle" name="complianz_toggle" value="on" %s />',
8964 esc_attr($checked)
8965 );
8966 echo '<span class="slider"></span>';
8967 echo '</label>';
8968
8969 echo '<p class="description">' . esc_html__('Enable this option to apply Complianz consent logic to the chatbot (must have Complianz Plugin).', 'mxchat') . '</p>';
8970 }
8971
8972 public function mxchat_link_target_toggle_callback() {
8973 // Load from mxchat_options array
8974 $options = get_option('mxchat_options', []);
8975
8976 // Get link target toggle value with fallback
8977 $link_target_toggle = isset($options['link_target_toggle']) ? $options['link_target_toggle'] : 'off';
8978 $checked = ($link_target_toggle === 'on') ? 'checked' : '';
8979
8980 // Output the toggle switch
8981 echo '<label class="toggle-switch">';
8982 echo sprintf(
8983 '<input type="checkbox" id="link_target_toggle" name="link_target_toggle" value="on" %s />',
8984 esc_attr($checked)
8985 );
8986 echo '<span class="slider"></span>';
8987 echo '</label>';
8988 echo '<p class="description">' . esc_html__('Enable to open links in a new tab (default is to open in the same tab).', 'mxchat') . '</p>';
8989 }
8990
8991 public function mxchat_chat_persistence_toggle_callback() {
8992 // Load from mxchat_options array
8993 $options = get_option('mxchat_options', []);
8994
8995 // Get chat persistence toggle value with fallback
8996 $chat_persistence_toggle = isset($options['chat_persistence_toggle']) ? $options['chat_persistence_toggle'] : 'off';
8997 $checked = ($chat_persistence_toggle === 'on') ? 'checked' : '';
8998
8999 // Output the toggle switch
9000 echo '<label class="toggle-switch">';
9001 echo sprintf(
9002 '<input type="checkbox" id="chat_persistence_toggle" name="chat_persistence_toggle" value="on" %s />',
9003 esc_attr($checked)
9004 );
9005 echo '<span class="slider"></span>';
9006 echo '</label>';
9007
9008 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>';
9009 }
9010
9011 public function mxchat_popular_question_1_callback() {
9012 // Load the full plugin options array
9013 $all_options = get_option('mxchat_options', []);
9014
9015 // Retrieve the specific option for popular_question_1
9016 $popular_question_1 = isset($all_options['popular_question_1']) ? $all_options['popular_question_1'] : '';
9017
9018 // Render the input field
9019 printf(
9020 '<input type="text" id="popular_question_1" name="popular_question_1" value="%s" placeholder="%s" class="regular-text" />',
9021 esc_attr($popular_question_1),
9022 esc_attr__('Enter Quick Question 1', 'mxchat')
9023 );
9024
9025 // Add a description for the field
9026 echo '<p class="description">' . esc_html__('This will be the first Quick Question in the chatbot, displayed above the input field.', 'mxchat') . '</p>';
9027 }
9028
9029
9030 public function mxchat_popular_question_2_callback() {
9031 // Load the full plugin options array
9032 $all_options = get_option('mxchat_options', []);
9033
9034 // Retrieve the specific option for popular_question_2
9035 $popular_question_2 = isset($all_options['popular_question_2']) ? $all_options['popular_question_2'] : '';
9036
9037 // Render the input field
9038 printf(
9039 '<input type="text" id="popular_question_2" name="popular_question_2" value="%s" placeholder="%s" class="regular-text" />',
9040 esc_attr($popular_question_2),
9041 esc_attr__('Enter Quick Question 2', 'mxchat')
9042 );
9043
9044 // Add a description for the field
9045 echo '<p class="description">' . esc_html__('This will be the second Quick Question in the chatbot.', 'mxchat') . '</p>';
9046 }
9047
9048
9049 public function mxchat_popular_question_3_callback() {
9050 // Load the full plugin options array
9051 $all_options = get_option('mxchat_options', []);
9052
9053 // Retrieve the specific option for popular_question_3
9054 $popular_question_3 = isset($all_options['popular_question_3']) ? $all_options['popular_question_3'] : '';
9055
9056 // Render the input field
9057 printf(
9058 '<input type="text" id="popular_question_3" name="popular_question_3" value="%s" placeholder="%s" class="regular-text" />',
9059 esc_attr($popular_question_3),
9060 esc_attr(__('Enter Quick Question 3', 'mxchat'))
9061 );
9062
9063 // Add a description for the field
9064 echo '<p class="description">' . esc_html__('This will be the third Quick Question in the chatbot.', 'mxchat') . '</p>';
9065 }
9066
9067 public function mxchat_additional_popular_questions_callback() {
9068 $options = get_option('mxchat_options', []);
9069 $additional_questions = isset($options['additional_popular_questions'])
9070 ? $options['additional_popular_questions']
9071 : get_option('additional_popular_questions', array());
9072
9073 echo '<div id="mxchat-additional-questions-container">';
9074 if (!empty($additional_questions)) {
9075 foreach ($additional_questions as $index => $question) {
9076 printf(
9077 '<div class="mxchat-question-row">
9078 <input type="text" name="additional_popular_questions[]"
9079 value="%s"
9080 placeholder="%s"
9081 class="regular-text mxchat-question-input"
9082 data-question-index="%d" />
9083 <button type="button" class="button mxchat-remove-question"
9084 aria-label="%s">%s</button>
9085 </div>',
9086 esc_attr($question),
9087 esc_attr(sprintf(__('Enter Additional Quick Question %d', 'mxchat'), $index + 4)),
9088 $index,
9089 esc_attr(__('Remove question', 'mxchat')),
9090 esc_html__('Remove', 'mxchat')
9091 );
9092 }
9093 } else {
9094 printf(
9095 '<div class="mxchat-question-row">
9096 <input type="text" name="additional_popular_questions[]"
9097 value=""
9098 placeholder="%s"
9099 class="regular-text mxchat-question-input"
9100 data-question-index="0" />
9101 <button type="button" class="button mxchat-remove-question"
9102 aria-label="%s">%s</button>
9103 </div>',
9104 esc_attr(__('Enter Additional Quick Question 4', 'mxchat')),
9105 esc_attr(__('Remove question', 'mxchat')),
9106 esc_html__('Remove', 'mxchat')
9107 );
9108 }
9109 echo '</div>';
9110 printf(
9111 '<button type="button" class="button mxchat-add-question" aria-label="%s">%s</button>',
9112 esc_attr(__('Add question', 'mxchat')),
9113 esc_html__('Add Question', 'mxchat')
9114 );
9115 echo '<p class="description">' . esc_html__('Add as many Quick Questions as you need.', 'mxchat') . '</p>';
9116 echo '</div>';
9117 }
9118
9119 public function mxchat_brave_api_key_callback() {
9120 $brave_api_key = isset($this->options['brave_api_key']) ? esc_attr($this->options['brave_api_key']) : '';
9121
9122 echo '<div class="api-key-wrapper">';
9123 echo sprintf(
9124 '<input type="password" id="brave_api_key" name="brave_api_key" value="%s" class="regular-text" />',
9125 $brave_api_key
9126 );
9127 echo '<button type="button" id="toggleBraveApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
9128 echo '</div>';
9129 echo '<p class="description">' . __('Enter your Brave Search API Key here. (See FAQ for details)', 'mxchat') . '</p>';
9130 }
9131
9132 public function mxchat_brave_image_count_callback() {
9133 $brave_image_count = isset($this->options['brave_image_count'])
9134 ? intval($this->options['brave_image_count'])
9135 : 4;
9136
9137 echo sprintf(
9138 '<input type="number" id="brave_image_count" name="brave_image_count"
9139 value="%d" min="1" max="6" class="small-text" />',
9140 $brave_image_count
9141 );
9142 echo '<p class="description">' . __('Select the number of images to return (1-6).', 'mxchat') . '</p>';
9143 }
9144
9145 public function mxchat_brave_safe_search_callback() {
9146 $brave_safe_search = isset($this->options['brave_safe_search'])
9147 ? esc_attr($this->options['brave_safe_search'])
9148 : 'strict';
9149
9150 echo '<select id="brave_safe_search" name="brave_safe_search">';
9151 echo sprintf(
9152 '<option value="strict" %s>%s</option>',
9153 selected($brave_safe_search, 'strict', false),
9154 __('Strict', 'mxchat')
9155 );
9156 echo sprintf(
9157 '<option value="off" %s>%s</option>',
9158 selected($brave_safe_search, 'off', false),
9159 __('Off', 'mxchat')
9160 );
9161 echo '</select>';
9162 echo '<p class="description">' .
9163 esc_html__('Set the Safe Search level for image searches. Brave Search only supports "Strict" and "Off" options.', 'mxchat') .
9164 '</p>';
9165 }
9166
9167 public function mxchat_brave_news_count_callback() {
9168 $brave_news_count = isset($this->options['brave_news_count'])
9169 ? intval($this->options['brave_news_count'])
9170 : 3;
9171
9172 echo sprintf(
9173 '<input type="number" id="brave_news_count" name="brave_news_count"
9174 value="%d" min="1" max="10" class="small-text" />',
9175 $brave_news_count
9176 );
9177 echo '<p class="description">' . esc_html__('Select the number of news articles to retrieve (1-10).', 'mxchat') . '</p>';
9178 }
9179
9180 public function mxchat_brave_country_callback() {
9181 $brave_country = isset($this->options['brave_country'])
9182 ? esc_attr($this->options['brave_country'])
9183 : 'us';
9184
9185 echo sprintf(
9186 '<input type="text" id="brave_country" name="brave_country"
9187 value="%s" maxlength="2" class="small-text" />',
9188 $brave_country
9189 );
9190 echo '<p class="description">' . esc_html__('Enter the country code (e.g., "us" for United States).', 'mxchat') . '</p>';
9191 }
9192
9193 public function mxchat_brave_language_callback() {
9194 $brave_language = isset($this->options['brave_language'])
9195 ? esc_attr($this->options['brave_language'])
9196 : 'en';
9197
9198 echo sprintf(
9199 '<input type="text" id="brave_language" name="brave_language"
9200 value="%s" maxlength="2" class="small-text" />',
9201 $brave_language
9202 );
9203 echo '<p class="description">' . esc_html__('Enter the language code (e.g., "en" for English).', 'mxchat') . '</p>';
9204 }
9205
9206
9207
9208
9209
9210 // Section Callback
9211 public function mxchat_pdf_intent_section_callback() {
9212 echo '<p>' . esc_html__('Configure the intent settings for the Chat with PDF feature.', 'mxchat') . '</p>';
9213 }
9214
9215 public function mxchat_chat_toolbar_toggle_callback() {
9216 // Get chat toolbar toggle value with fallback
9217 $chat_toolbar_toggle = isset($this->options['chat_toolbar_toggle']) ? $this->options['chat_toolbar_toggle'] : 'off';
9218 $checked = ($chat_toolbar_toggle === 'on') ? 'checked' : '';
9219
9220 // Output the toggle switch
9221 echo '<label class="toggle-switch">';
9222 echo sprintf(
9223 '<input type="checkbox" id="chat_toolbar_toggle" name="chat_toolbar_toggle" value="on" %s />',
9224 esc_attr($checked)
9225 );
9226 echo '<span class="slider"></span>';
9227 echo '</label>';
9228
9229 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>';
9230 }
9231 /**
9232 * Callback for PDF upload button toggle setting
9233 */
9234 public function mxchat_show_pdf_upload_button_callback() {
9235 // Get toggle value with fallback
9236 $show_pdf_button = isset($this->options['show_pdf_upload_button']) ? $this->options['show_pdf_upload_button'] : 'on';
9237 $checked = ($show_pdf_button === 'on') ? 'checked' : '';
9238
9239 // Output the toggle switch
9240 echo '<label class="toggle-switch">';
9241 echo sprintf(
9242 '<input type="checkbox" id="show_pdf_upload_button" name="show_pdf_upload_button" value="on" %s />',
9243 esc_attr($checked)
9244 );
9245 echo '<span class="slider"></span>';
9246 echo '</label>';
9247
9248 echo '<p class="description">' . esc_html__('Enable to show the PDF upload button in the chatbot toolbar. Disable to hide it.', 'mxchat') . '</p>';
9249 }
9250 /**
9251 * Callback for Word upload button toggle setting
9252 */
9253 public function mxchat_show_word_upload_button_callback() {
9254 // Get toggle value with fallback
9255 $show_word_button = isset($this->options['show_word_upload_button']) ? $this->options['show_word_upload_button'] : 'on';
9256 $checked = ($show_word_button === 'on') ? 'checked' : '';
9257
9258 // Output the toggle switch
9259 echo '<label class="toggle-switch">';
9260 echo sprintf(
9261 '<input type="checkbox" id="show_word_upload_button" name="show_word_upload_button" value="on" %s />',
9262 esc_attr($checked)
9263 );
9264 echo '<span class="slider"></span>';
9265 echo '</label>';
9266
9267 echo '<p class="description">' . esc_html__('Enable to show the Word document upload button in the chatbot toolbar. Disable to hide it.', 'mxchat') . '</p>';
9268 }
9269
9270 public function mxchat_pdf_intent_trigger_text_callback() {
9271 $default_text = __("Please provide the URL to the PDF you'd like to discuss.", 'mxchat');
9272
9273 echo sprintf(
9274 '<textarea id="pdf_intent_trigger_text"
9275 name="pdf_intent_trigger_text"
9276 rows="3"
9277 cols="50"
9278 placeholder="%s">%s</textarea>',
9279 esc_attr__('Enter trigger text', 'mxchat'),
9280 isset($this->options['pdf_intent_trigger_text'])
9281 ? esc_textarea($this->options['pdf_intent_trigger_text'])
9282 : esc_textarea($default_text)
9283 );
9284 echo '<p class="description">' . esc_html__('Text displayed when the intent is triggered.', 'mxchat') . '</p>';
9285 }
9286
9287 public function mxchat_pdf_intent_success_text_callback() {
9288 $default_text = __("I've processed the PDF. What questions do you have about it?", 'mxchat');
9289
9290 echo sprintf(
9291 '<textarea id="pdf_intent_success_text"
9292 name="pdf_intent_success_text"
9293 rows="3"
9294 cols="50"
9295 placeholder="%s">%s</textarea>',
9296 esc_attr__('Enter success text', 'mxchat'),
9297 isset($this->options['pdf_intent_success_text'])
9298 ? esc_textarea($this->options['pdf_intent_success_text'])
9299 : esc_textarea($default_text)
9300 );
9301 echo '<p class="description">' . esc_html__('Text displayed when the intent is successful.', 'mxchat') . '</p>';
9302 }
9303
9304 public function mxchat_pdf_intent_error_text_callback() {
9305 $default_text = __("Sorry, I couldn't process the PDF. Please ensure it's a valid file.", 'mxchat');
9306
9307 echo sprintf(
9308 '<textarea id="pdf_intent_error_text"
9309 name="pdf_intent_error_text"
9310 rows="3"
9311 cols="50"
9312 placeholder="%s">%s</textarea>',
9313 esc_attr__('Enter error text', 'mxchat'),
9314 isset($this->options['pdf_intent_error_text'])
9315 ? esc_textarea($this->options['pdf_intent_error_text'])
9316 : esc_textarea($default_text)
9317 );
9318 echo '<p class="description">' . esc_html__('Text displayed when an error occurs during the intent.', 'mxchat') . '</p>';
9319 }
9320
9321 public function mxchat_pdf_max_pages_callback() {
9322 $max_pages = isset($this->options['pdf_max_pages']) ? intval($this->options['pdf_max_pages']) : 69;
9323
9324 echo sprintf(
9325 '<input type="range"
9326 id="pdf_max_pages"
9327 name="pdf_max_pages"
9328 min="1"
9329 max="69"
9330 value="%d"
9331 class="range-slider" />',
9332 esc_attr($max_pages)
9333 );
9334 echo '<span id="pdf_max_pages_output">' . esc_html($max_pages) . '</span>';
9335 echo '<p class="description">' . esc_html__('Set the maximum number of document pages users can upload for processing. (1-69 pages)', 'mxchat') . '</p>';
9336 }
9337
9338 public function mxchat_live_agent_status_callback() {
9339 $status = isset($this->options['live_agent_status']) ? $this->options['live_agent_status'] : 'off';
9340
9341 echo '<label class="toggle-switch">';
9342 echo sprintf(
9343 '<input type="checkbox" id="live_agent_status" name="live_agent_status" value="on" %s />',
9344 checked($status, 'on', false)
9345 );
9346 echo '<span class="slider"></span>';
9347 echo '</label>';
9348 echo '<label for="live_agent_status" class="mxchat-status-label">';
9349 echo '<span class="status-text">' . ($status === 'on' ? esc_html__('Online', 'mxchat') : esc_html__('Offline', 'mxchat')) . '</span>';
9350 echo '</label>';
9351 }
9352
9353 public function mxchat_live_agent_away_message_callback() {
9354 $message = isset($this->options['live_agent_away_message'])
9355 ? $this->options['live_agent_away_message']
9356 : __('Sorry, live agents are currently unavailable. I can continue helping you as an AI assistant.', 'mxchat');
9357
9358 printf(
9359 '<textarea id="live_agent_away_message" name="live_agent_away_message" rows="3" cols="50">%s</textarea>',
9360 esc_textarea($message)
9361 );
9362 echo '<p class="description">' . esc_html__('Message shown when live agents are offline.', 'mxchat') . '</p>';
9363 }
9364
9365 public function mxchat_live_agent_notification_message_callback() {
9366 $message = isset($this->options['live_agent_notification_message'])
9367 ? $this->options['live_agent_notification_message']
9368 : __('Live agent has been notified.', 'mxchat');
9369
9370 printf(
9371 '<textarea id="live_agent_notification_message" name="live_agent_notification_message" rows="3" cols="50">%s</textarea>',
9372 esc_textarea($message)
9373 );
9374 echo '<p class="description">' . esc_html__('Message shown when live transfer activated.', 'mxchat') . '</p>';
9375 }
9376
9377 public function mxchat_live_agent_webhook_url_callback() {
9378 $webhook_url = isset($this->options['live_agent_webhook_url'])
9379 ? esc_url($this->options['live_agent_webhook_url'])
9380 : esc_url(get_option('live_agent_webhook_url', ''));
9381
9382 printf(
9383 '<input type="password" id="live_agent_webhook_url" name="live_agent_webhook_url" value="%s" class="regular-text" />',
9384 $webhook_url
9385 );
9386 echo '<button type="button" id="toggleWebhookUrlVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
9387 echo '<p class="description">' . esc_html__('Enter your Slack webhook URL for live agent notifications.', 'mxchat') . '</p>';
9388 }
9389
9390 public function mxchat_live_agent_secret_key_callback() {
9391 printf(
9392 '<input type="password" id="live_agent_secret_key" name="live_agent_secret_key" value="%s" class="regular-text" />',
9393 isset($this->options['live_agent_secret_key']) ? esc_attr($this->options['live_agent_secret_key']) : ''
9394 );
9395 echo '<button type="button" id="toggleSecretKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
9396 echo '<p class="description">' . esc_html__('Secret key for validating Slack requests. Keep this secure.', 'mxchat') . '</p>';
9397 }
9398
9399 public function mxchat_live_agent_bot_token_callback() {
9400 printf(
9401 '<input type="password" id="live_agent_bot_token" name="live_agent_bot_token" value="%s" class="regular-text" />',
9402 isset($this->options['live_agent_bot_token']) ? esc_attr($this->options['live_agent_bot_token']) : ''
9403 );
9404 echo '<button type="button" id="toggleBotTokenVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
9405 echo '<p class="description">' . esc_html__('Your Slack Bot OAuth Token (starts with xoxb-). Keep this secure.', 'mxchat') . '</p>';
9406 }
9407
9408 public function mxchat_similarity_threshold_callback() {
9409 // Load from mxchat_options array
9410 $options = get_option('mxchat_options', []);
9411
9412 // Get value from options array with default of 80
9413 $threshold = isset($options['similarity_threshold']) ? $options['similarity_threshold'] : 35;
9414
9415 echo '<div class="slider-container">';
9416 echo sprintf(
9417 '<input type="range"
9418 id="similarity_threshold"
9419 name="similarity_threshold"
9420 min="20"
9421 max="85"
9422 step="1"
9423 value="%s"
9424 class="range-slider" />',
9425 esc_attr($threshold)
9426 );
9427 echo sprintf(
9428 '<span id="threshold_value" class="range-value">%s</span>',
9429 esc_html($threshold)
9430 );
9431 echo '</div>';
9432 echo '<p class="description">';
9433 echo sprintf(
9434 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'),
9435 '<a href="' . admin_url('admin.php?page=mxchat-addons') . '">',
9436 '</a>'
9437 );
9438 echo '</p>';
9439 }
9440
9441 public function mxchat_enqueue_admin_assets() {
9442 wp_enqueue_style('wp-color-picker');
9443
9444 // Get the plugin version or file modification time for cache busting
9445 $plugin_version = '2.2.1'; // Replace this with your plugin's version
9446
9447 // File paths
9448 $color_picker_js_path = plugin_dir_path(__FILE__) . '../js/my-color-picker.js';
9449 $embedding_check_js_path = plugin_dir_path(__FILE__) . '../js/embedding-check.js';
9450 $admin_css_path = plugin_dir_path(__FILE__) . '../css/admin-style.css';
9451 $knowledge_css_path = plugin_dir_path(__FILE__) . '../css/knowledge-style.css';
9452 $intent_css_path = plugin_dir_path(__FILE__) . '../css/intent-style.css';
9453 $transcripts_css_path = plugin_dir_path(__FILE__) . '../css/chat-transcripts.css';
9454 $transcripts_js_path = plugin_dir_path(__FILE__) . '../js/mxchat_transcripts.js';
9455 $activation_js_path = plugin_dir_path(__FILE__) . '../js/activation-script.js';
9456
9457 // Add the new content selector files
9458 $content_selector_css_path = plugin_dir_path(__FILE__) . '../css/content-selector.css';
9459 $content_selector_js_path = plugin_dir_path(__FILE__) . '../js/content-selector.js';
9460
9461 // Check if files exist and get modification times
9462 $color_picker_version = file_exists($color_picker_js_path) ? filemtime($color_picker_js_path) : $plugin_version;
9463 $embedding_check_version = file_exists($embedding_check_js_path) ? filemtime($embedding_check_js_path) : $plugin_version;
9464 $admin_css_version = file_exists($admin_css_path) ? filemtime($admin_css_path) : $plugin_version;
9465 $knowledge_css_version = file_exists($knowledge_css_path) ? filemtime($knowledge_css_path) : $plugin_version;
9466 $intent_css_version = file_exists($intent_css_path) ? filemtime($intent_css_path) : $plugin_version;
9467 $transcripts_css_version = file_exists($transcripts_css_path) ? filemtime($transcripts_css_path) : $plugin_version;
9468 $transcripts_js_version = file_exists($transcripts_js_path) ? filemtime($transcripts_js_path) : $plugin_version;
9469 $activation_js_version = file_exists($activation_js_path) ? filemtime($activation_js_path) : $plugin_version;
9470
9471 // Get versions for the new content selector files
9472 $content_selector_css_version = file_exists($content_selector_css_path) ? filemtime($content_selector_css_path) : $plugin_version;
9473 $content_selector_js_version = file_exists($content_selector_js_path) ? filemtime($content_selector_js_path) : $plugin_version;
9474
9475 $admin_status_js_path = plugin_dir_path(__FILE__) . '../js/admin-status.js';
9476 $admin_status_js_version = file_exists($admin_status_js_path) ? filemtime($admin_status_js_path) : $plugin_version;
9477
9478 // Check current admin page
9479 $current_page = isset($_GET['page']) ? $_GET['page'] : '';
9480
9481 // Only enqueue on the prompts page
9482 if ($current_page === 'mxchat-prompts') {
9483 wp_enqueue_script(
9484 'mxchat-status-updater',
9485 plugin_dir_url(__FILE__) . '../js/admin-status.js',
9486 array('jquery'),
9487 $admin_status_js_version,
9488 true
9489 );
9490
9491 // Add the nonce for the status updater
9492 wp_localize_script(
9493 'mxchat-status-updater',
9494 'mxchat_status_data',
9495 array(
9496 'ajax_url' => admin_url('admin-ajax.php'),
9497 'nonce' => wp_create_nonce('mxchat_status_nonce')
9498 )
9499 );
9500
9501 // Enqueue the content selector files only on the prompts page
9502 wp_enqueue_style(
9503 'mxchat-content-selector-css',
9504 plugin_dir_url(__FILE__) . '../css/content-selector.css',
9505 array(),
9506 $content_selector_css_version
9507 );
9508
9509 wp_enqueue_script(
9510 'mxchat-content-selector-js',
9511 plugin_dir_url(__FILE__) . '../js/content-selector.js',
9512 array('jquery'),
9513 $content_selector_js_version,
9514 true
9515 );
9516
9517 // Add the nonce for the content selector
9518 wp_localize_script(
9519 'mxchat-content-selector-js',
9520 'mxchatSelector',
9521 array(
9522 'ajaxurl' => admin_url('admin-ajax.php'),
9523 'nonce' => wp_create_nonce('mxchat_content_selector_nonce'),
9524 'i18n' => array(
9525 'searchPlaceholder' => __('Search posts and pages...', 'mxchat'),
9526 'selectAll' => __('Select All', 'mxchat'),
9527 'process' => __('Process Selected', 'mxchat'),
9528 'cancel' => __('Cancel', 'mxchat'),
9529 'noResults' => __('No content found.', 'mxchat')
9530 )
9531 )
9532 );
9533 }
9534
9535 // Enqueue activation script if on the activation page
9536 if ($current_page === 'mxchat-activation') {
9537 wp_enqueue_script(
9538 'mxchat-activation-js',
9539 plugin_dir_url(__FILE__) . '../js/activation-script.js',
9540 array('jquery'),
9541 $activation_js_version,
9542 true
9543 );
9544
9545 // Pass needed data to the activation script
9546 wp_localize_script(
9547 'mxchat-activation-js',
9548 'mxchatAdmin',
9549 array(
9550 'ajax_url' => admin_url('admin-ajax.php'),
9551 'license_nonce' => wp_create_nonce('mxchat_activate_license_nonce')
9552 )
9553 );
9554 }
9555
9556 // Enqueue scripts and styles with corrected paths
9557 wp_enqueue_script(
9558 'mxchat-color-picker',
9559 plugin_dir_url(__FILE__) . '../js/my-color-picker.js',
9560 array('wp-color-picker'),
9561 $color_picker_version,
9562 true
9563 );
9564
9565 wp_enqueue_script(
9566 'mxchat-embedding-check',
9567 plugin_dir_url(__FILE__) . '../js/embedding-check.js',
9568 array(),
9569 $embedding_check_version,
9570 true
9571 );
9572
9573 wp_enqueue_script(
9574 'mxchat-admin-js',
9575 plugin_dir_url(__FILE__) . '../js/mxchat-admin.js',
9576 array('jquery'),
9577 $plugin_version,
9578 true
9579 );
9580
9581 wp_localize_script('mxchat-admin-js', 'mxchatAdmin', array(
9582 'ajax_url' => admin_url('admin-ajax.php'),
9583 'license_nonce' => wp_create_nonce('mxchat_activate_license_nonce'),
9584 'inline_edit_nonce' => wp_create_nonce('mxchat_save_inline_nonce'),
9585 'setting_nonce' => wp_create_nonce('mxchat_save_setting_nonce'),
9586 'export_nonce' => wp_create_nonce('mxchat_export_transcripts'),
9587 'actions_nonce' => wp_create_nonce('mxchat_actions_nonce'),
9588 'add_intent_nonce' => wp_create_nonce('mxchat_add_intent_nonce'),
9589 'edit_intent_nonce' => wp_create_nonce('mxchat_edit_intent'),
9590 'toggle_action_nonce' => wp_create_nonce('mxchat_actions_nonce'),
9591 'is_activated' => $this->is_activated ? '1' : '0',
9592 // Add these two new lines
9593 'status_nonce' => wp_create_nonce('mxchat_status_nonce'),
9594 'status_refresh_interval' => 5000, // Update every 5 seconds
9595 ));
9596
9597 // Enqueue the admin CSS
9598 wp_enqueue_style(
9599 'mxchat-admin-css',
9600 plugin_dir_url(__FILE__) . '../css/admin-style.css',
9601 array(),
9602 $admin_css_version
9603 );
9604
9605 // Conditional enqueue for transcripts page
9606 if ($current_page === 'mxchat-transcripts') {
9607 wp_enqueue_style(
9608 'mxchat-chat-transcripts-css',
9609 plugin_dir_url(__FILE__) . '../css/chat-transcripts.css',
9610 array(),
9611 $transcripts_css_version
9612 );
9613
9614 wp_enqueue_script(
9615 'mxchat-transcripts-js',
9616 plugin_dir_url(__FILE__) . '../js/mxchat_transcripts.js',
9617 array('jquery'),
9618 $transcripts_js_version,
9619 true
9620 );
9621 }
9622
9623 // Only enqueue knowledge CSS on knowledge-related pages or all plugin pages
9624 if (strpos($current_page, 'mxchat') !== false) {
9625 wp_enqueue_style(
9626 'mxchat-knowledge-css',
9627 plugin_dir_url(__FILE__) . '../css/knowledge-style.css',
9628 array(),
9629 $knowledge_css_version
9630 );
9631 }
9632
9633 // Only enqueue intent-style.css on the mxchat-actions page
9634 if ($current_page === 'mxchat-actions') {
9635 wp_enqueue_style(
9636 'mxchat-intent-css',
9637 plugin_dir_url(__FILE__) . '../css/intent-style.css',
9638 array(),
9639 $intent_css_version
9640 );
9641 }
9642
9643 // IMPORTANT: Use the same script handle as above for localizing mxchatPromptsAdmin
9644 wp_localize_script('mxchat-admin-js', 'mxchatPromptsAdmin', array(
9645 'ajax_url' => admin_url('admin-ajax.php'),
9646 'prompts_setting_nonce' => wp_create_nonce('mxchat_prompts_setting_nonce'),
9647 ));
9648
9649 // Add this to the function that enqueues your admin scripts
9650 wp_localize_script('mxchat-admin-status', 'mxchat_status_data', array(
9651 'ajax_url' => admin_url('admin-ajax.php'),
9652 'nonce' => wp_create_nonce('mxchat_process_batch') // Make sure this matches what you check in the PHP
9653 ));
9654
9655 // Localize the script for color picker and settings
9656 wp_localize_script('mxchat-color-picker', 'mxchatStyleSettings', array(
9657 'ajax_url' => admin_url('admin-ajax.php'),
9658 'link_target_toggle' => $this->options['link_target_toggle'] ?? 'off',
9659 'user_message_bg_color' => $this->options['user_message_bg_color'] ?? '#fff',
9660 'user_message_font_color' => $this->options['user_message_font_color'] ?? '#212121',
9661 'bot_message_bg_color' => $this->options['bot_message_bg_color'] ?? '#212121',
9662 'bot_message_font_color' => $this->options['bot_message_font_color'] ?? '#fff',
9663 'top_bar_bg_color' => $this->options['top_bar_bg_color'] ?? '#212121',
9664 'send_button_font_color' => $this->options['send_button_font_color'] ?? '#212121',
9665 'close_button_color' => $this->options['close_button_color'] ?? '#fff',
9666 'chatbot_background_color' => $this->options['chatbot_background_color'] ?? '#212121',
9667 'icon_color' => $this->options['icon_color'] ?? '#fff',
9668 'chat_input_font_color' => $this->options['chat_input_font_color'] ?? '#212121',
9669 'pre_chat_message' => $this->options['pre_chat_message'] ?? esc_html__('Hey there! Ask me anything!', 'mxchat'),
9670 'rate_limit_message' => $this->options['rate_limit_message'] ?? esc_html__('Rate limit exceeded. Please try again later.', 'mxchat'),
9671 'loops_api_key' => $this->options['loops_api_key'] ?? '',
9672 'loops_mailing_list' => $this->options['loops_mailing_list'] ?? '',
9673 'triggered_phrase_response' => $this->options['triggered_phrase_response'] ?? esc_html__('Would you like to join our mailing list? Please provide your email below.', 'mxchat'),
9674 'email_capture_response' => $this->options['email_capture_response'] ?? esc_html__('Thank you for providing your email! You\'ve been added to our list.', 'mxchat'),
9675 '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'),
9676 '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'),
9677 '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'),
9678 'pdf_max_pages' => $this->options['pdf_max_pages'] ?? 69,
9679 'live_agent_webhook_url' => $this->options['live_agent_webhook_url'] ?? '',
9680 'live_agent_secret_key' => $this->options['live_agent_secret_key'] ?? '',
9681 'live_agent_bot_token' => $this->options['live_agent_bot_token'] ?? '',
9682 'live_agent_message_bg_color' => $this->options['live_agent_message_bg_color'] ?? '#ffffff',
9683 'live_agent_message_font_color' => $this->options['live_agent_message_font_color'] ?? '#333333',
9684 'chat_toolbar_toggle' => $this->options['chat_toolbar_toggle'] ?? 'off',
9685 'show_pdf_upload_button' => $this->options['show_pdf_upload_button'] ?? 'on',
9686 'show_word_upload_button' => $this->options['show_word_upload_button'] ?? 'on',
9687 'mode_indicator_bg_color' => $this->options['mode_indicator_bg_color'] ?? '#767676',
9688 'mode_indicator_font_color' => $this->options['mode_indicator_font_color'] ?? '#ffffff',
9689 'toolbar_icon_color' => $this->options['toolbar_icon_color'] ?? '#212121',
9690 ));
9691 }
9692
9693 public function mxchat_sanitize($input) {
9694 $new_input = array();
9695
9696 if (isset($input['api_key'])) {
9697 $new_input['api_key'] = sanitize_text_field($input['api_key']);
9698 }
9699
9700 if (isset($input['similarity_threshold'])) {
9701 $new_input['similarity_threshold'] = absint($input['similarity_threshold']); // Ensure it's an integer
9702 $new_input['similarity_threshold'] = min(max($new_input['similarity_threshold'], 20), 85); // Enforce range
9703 }
9704
9705 if (isset($input['xai_api_key'])) {
9706 $new_input['xai_api_key'] = sanitize_text_field($input['xai_api_key']);
9707 }
9708
9709 if (isset($input['claude_api_key'])) {
9710 $new_input['claude_api_key'] = sanitize_text_field($input['claude_api_key']);
9711 }
9712
9713 if (isset($input['deepseek_api_key'])) {
9714 $new_input['deepseek_api_key'] = sanitize_text_field($input['deepseek_api_key']);
9715 }
9716
9717 if (isset($input['gemini_api_key'])) {
9718 $new_input['gemini_api_key'] = sanitize_text_field($input['gemini_api_key']);
9719 }
9720
9721 if (isset($input['enable_woocommerce_integration'])) {
9722 $new_input['enable_woocommerce_integration'] = $input['enable_woocommerce_integration'] === 'on' ? 'on' : 'off';
9723 }
9724
9725 if (isset($input['privacy_toggle'])) {
9726 $new_input['privacy_toggle'] = $input['privacy_toggle'];
9727 }
9728
9729 if (isset($input['complianz_toggle'])) {
9730 $new_input['complianz_toggle'] = $input['complianz_toggle'];
9731 }
9732
9733 // Handle custom privacy text input
9734 if (isset($input['privacy_text'])) {
9735 // Allow basic HTML for links
9736 $new_input['privacy_text'] = wp_kses_post($input['privacy_text']);
9737 }
9738
9739 if (isset($input['system_prompt_instructions'])) {
9740 $new_input['system_prompt_instructions'] = sanitize_textarea_field($input['system_prompt_instructions']);
9741 }
9742
9743 if (isset($input['mxchat_pro_email'])) {
9744 $new_input['mxchat_pro_email'] = sanitize_email($input['mxchat_pro_email']);
9745 }
9746
9747 if (isset($input['mxchat_activation_key'])) {
9748 $new_input['mxchat_activation_key'] = sanitize_text_field($input['mxchat_activation_key']);
9749 }
9750
9751 if (isset($input['append_to_body'])) {
9752 $new_input['append_to_body'] = $input['append_to_body'] === 'on' ? 'on' : 'off';
9753 }
9754
9755 if (isset($input['top_bar_title'])) {
9756 $new_input['top_bar_title'] = sanitize_text_field($input['top_bar_title']);
9757 }
9758
9759 if (isset($input['ai_agent_text'])) {
9760 $new_input['ai_agent_text'] = sanitize_text_field($input['ai_agent_text']);
9761 }
9762
9763 if (isset($input['enable_email_block'])) {
9764 $new_input['enable_email_block'] = sanitize_text_field($input['enable_email_block']);
9765 }
9766
9767 if (isset($input['email_blocker_header_content'])) {
9768 // wp_kses_post() allows standard HTML tags permitted by WordPress
9769 $new_input['email_blocker_header_content'] = wp_kses_post($input['email_blocker_header_content']);
9770 }
9771 if (isset($input['email_blocker_button_text'])) {
9772 $new_input['email_blocker_button_text'] = sanitize_text_field($input['email_blocker_button_text']);
9773 }
9774
9775 if (isset($input['intro_message'])) {
9776 $new_input['intro_message'] = wp_kses_post($input['intro_message']); // Use wp_kses_post instead
9777 }
9778
9779 if (isset($input['input_copy'])) {
9780 $new_input['input_copy'] = sanitize_text_field($input['input_copy']);
9781 }
9782
9783 if (isset($input['rate_limit_message'])) {
9784 $new_input['rate_limit_message'] = sanitize_text_field($input['rate_limit_message']);
9785 }
9786
9787 // Handle the new rate limits format
9788 if (isset($input['rate_limits']) && is_array($input['rate_limits'])) {
9789 $new_input['rate_limits'] = array();
9790 $allowed_limits = array('1', '3', '5', '10', '15', '20', '50', '100', 'unlimited');
9791 $allowed_timeframes = array('hourly', 'daily', 'weekly', 'monthly');
9792
9793 foreach ($input['rate_limits'] as $role_id => $settings) {
9794 $new_input['rate_limits'][$role_id] = array();
9795
9796 // Sanitize limit
9797 if (isset($settings['limit'])) {
9798 $limit = sanitize_text_field($settings['limit']);
9799 if (in_array($limit, $allowed_limits, true)) {
9800 $new_input['rate_limits'][$role_id]['limit'] = $limit;
9801 } else {
9802 $new_input['rate_limits'][$role_id]['limit'] = ($role_id === 'logged_out') ? '10' : '100'; // Default
9803 }
9804 }
9805
9806 // Sanitize timeframe
9807 if (isset($settings['timeframe'])) {
9808 $timeframe = sanitize_text_field($settings['timeframe']);
9809 if (in_array($timeframe, $allowed_timeframes, true)) {
9810 $new_input['rate_limits'][$role_id]['timeframe'] = $timeframe;
9811 } else {
9812 $new_input['rate_limits'][$role_id]['timeframe'] = 'daily'; // Default
9813 }
9814 }
9815
9816 // Sanitize message
9817 if (isset($settings['message'])) {
9818 $new_input['rate_limits'][$role_id]['message'] = sanitize_textarea_field($settings['message']);
9819 }
9820 }
9821 }
9822
9823 if (isset($input['pre_chat_message'])) {
9824 $new_input['pre_chat_message'] = sanitize_textarea_field($input['pre_chat_message']);
9825 }
9826
9827 if (isset($input['voyage_api_key'])) {
9828 $new_input['voyage_api_key'] = sanitize_text_field($input['voyage_api_key']);
9829 }
9830
9831 // Add to your sanitize function
9832 if (isset($input['embedding_model'])) {
9833 $allowed_models = array(
9834 'text-embedding-ada-002',
9835 'text-embedding-3-small',
9836 'text-embedding-3-large',
9837 'voyage-3-large',
9838 'gemini-embedding-exp-03-07'
9839 );
9840 if (in_array($input['embedding_model'], $allowed_models)) {
9841 $new_input['embedding_model'] = sanitize_text_field($input['embedding_model']);
9842 }
9843 }
9844
9845 if (isset($input['model'])) {
9846 $allowed_models = array(
9847 'gemini-2.0-flash',
9848 'gemini-2.0-flash-lite',
9849 'gemini-1.5-pro',
9850 'gemini-1.5-flash',
9851 'grok-3-beta',
9852 'grok-3-fast-beta',
9853 'grok-3-mini-beta',
9854 'grok-3-mini-fast-beta',
9855 'grok-2',
9856 'deepseek-chat',
9857 'claude-opus-4-20250514',
9858 'claude-sonnet-4-20250514',
9859 'claude-3-7-sonnet-20250219',
9860 'claude-3-5-sonnet-20241022',
9861 'claude-3-opus-20240229',
9862 'claude-3-sonnet-20240229',
9863 'claude-3-haiku-20240307',
9864 'gpt-4o',
9865 'gpt-4.1-2025-04-14',
9866 'gpt-4o-mini',
9867 'gpt-4-turbo',
9868 'gpt-4',
9869 'gpt-3.5-turbo',
9870 );
9871 if (in_array($input['model'], $allowed_models)) {
9872 $new_input['model'] = sanitize_text_field($input['model']);
9873 }
9874 }
9875
9876 if (isset($input['close_button_color'])) {
9877 $new_input['close_button_color'] = sanitize_hex_color($input['close_button_color']);
9878 }
9879
9880 if (isset($input['chatbot_bg_color'])) {
9881 $new_input['chatbot_bg_color'] = sanitize_hex_color($input['chatbot_bg_color']);
9882 }
9883
9884 if (isset($input['woocommerce_consumer_key'])) {
9885 $new_input['woocommerce_consumer_key'] = sanitize_text_field($input['woocommerce_consumer_key']);
9886 }
9887
9888 if (isset($input['woocommerce_consumer_secret'])) {
9889 $new_input['woocommerce_consumer_secret'] = sanitize_text_field($input['woocommerce_consumer_secret']);
9890 }
9891
9892 if (isset($input['user_message_bg_color'])) {
9893 $new_input['user_message_bg_color'] = sanitize_hex_color($input['user_message_bg_color']);
9894 }
9895
9896 if (isset($input['user_message_font_color'])) {
9897 $new_input['user_message_font_color'] = sanitize_hex_color($input['user_message_font_color']);
9898 }
9899
9900 if (isset($input['bot_message_bg_color'])) {
9901 $new_input['bot_message_bg_color'] = sanitize_hex_color($input['bot_message_bg_color']);
9902 }
9903
9904 if (isset($input['bot_message_font_color'])) {
9905 $new_input['bot_message_font_color'] = sanitize_hex_color($input['bot_message_font_color']);
9906 }
9907
9908 if (isset($input['live_agent_message_bg_color'])) {
9909 $new_input['live_agent_message_bg_color'] = sanitize_hex_color($input['live_agent_message_bg_color']);
9910 }
9911
9912 if (isset($input['live_agent_message_font_color'])) {
9913 $new_input['live_agent_message_font_color'] = sanitize_hex_color($input['live_agent_message_font_color']);
9914 }
9915
9916 if (isset($input['mode_indicator_bg_color'])) {
9917 $new_input['mode_indicator_bg_color'] = sanitize_hex_color($input['mode_indicator_bg_color']);
9918 }
9919
9920 if (isset($input['mode_indicator_font_color'])) {
9921 $new_input['mode_indicator_font_color'] = sanitize_hex_color($input['mode_indicator_font_color']);
9922 }
9923
9924 if (isset($input['toolbar_icon_color'])) {
9925 $new_input['toolbar_icon_color'] = sanitize_hex_color($input['toolbar_icon_color']);
9926 }
9927
9928 if (isset($input['top_bar_bg_color'])) {
9929 $new_input['top_bar_bg_color'] = sanitize_hex_color($input['top_bar_bg_color']);
9930 }
9931
9932 if (isset($input['send_button_font_color'])) {
9933 $new_input['send_button_font_color'] = sanitize_hex_color($input['send_button_font_color']);
9934 }
9935
9936 if (isset($input['chatbot_background_color'])) {
9937 $new_input['chatbot_background_color'] = sanitize_hex_color($input['chatbot_background_color']);
9938 }
9939
9940 if (isset($input['icon_color'])) {
9941 $new_input['icon_color'] = sanitize_hex_color($input['icon_color']);
9942 }
9943
9944 if (isset($input['custom_icon'])) {
9945 $new_input['custom_icon'] = esc_url_raw($input['custom_icon']);
9946 }
9947
9948 if (isset($input['title_icon'])) {
9949 $new_input['title_icon'] = esc_url_raw($input['title_icon']);
9950 }
9951
9952 if (isset($input['chat_input_font_color'])) {
9953 $new_input['chat_input_font_color'] = sanitize_hex_color($input['chat_input_font_color']);
9954 }
9955
9956 // Sanitize link_target_toggle
9957 if (isset($input['link_target_toggle'])) {
9958 $new_input['link_target_toggle'] = $input['link_target_toggle'] === 'on' ? 'on' : 'off';
9959 }
9960
9961 // Sanitize Loops API Key
9962 if (isset($input['loops_api_key'])) {
9963 $new_input['loops_api_key'] = sanitize_text_field($input['loops_api_key']);
9964 }
9965
9966 if (isset($input['chat_persistence_toggle'])) {
9967 $new_input['chat_persistence_toggle'] = $input['chat_persistence_toggle'] === 'on' ? 'on' : 'off';
9968 }
9969
9970 if (isset($input['popular_question_1'])) {
9971 $new_input['popular_question_1'] = sanitize_text_field($input['popular_question_1']);
9972 }
9973
9974 if (isset($input['popular_question_2'])) {
9975 $new_input['popular_question_2'] = sanitize_text_field($input['popular_question_2']);
9976 }
9977
9978 if (isset($input['popular_question_3'])) {
9979 $new_input['popular_question_3'] = sanitize_text_field($input['popular_question_3']);
9980 }
9981
9982 if (isset($input['additional_popular_questions']) && is_array($input['additional_popular_questions'])) {
9983 $new_input['additional_popular_questions'] = array_map('sanitize_text_field', $input['additional_popular_questions']);
9984 }
9985
9986 // Sanitize Loops Mailing List
9987 if (isset($input['loops_mailing_list'])) {
9988 $new_input['loops_mailing_list'] = sanitize_text_field($input['loops_mailing_list']);
9989 }
9990
9991 // Sanitize Triggered Phrase Response
9992 if (isset($input['triggered_phrase_response'])) {
9993 $new_input['triggered_phrase_response'] = wp_kses_post($input['triggered_phrase_response']);
9994 }
9995
9996 if (isset($input['email_capture_response'])) {
9997 $new_input['email_capture_response'] = sanitize_textarea_field($input['email_capture_response']);
9998 }
9999
10000 // Sanitize Brave Search Settings
10001 if (isset($input['brave_api_key'])) {
10002 $new_input['brave_api_key'] = sanitize_text_field($input['brave_api_key']);
10003 }
10004
10005 if (isset($input['brave_image_count'])) {
10006 $image_count = intval($input['brave_image_count']);
10007 $new_input['brave_image_count'] = ($image_count >=1 && $image_count <=6) ? $image_count : 4;
10008 }
10009
10010 if (isset($input['brave_safe_search'])) {
10011 $allowed = array('strict', 'off');
10012 $new_input['brave_safe_search'] = in_array($input['brave_safe_search'], $allowed, true) ? $input['brave_safe_search'] : 'strict';
10013 }
10014
10015 if (isset($input['brave_news_count'])) {
10016 $news_count = intval($input['brave_news_count']);
10017 $new_input['brave_news_count'] = ($news_count >=1 && $news_count <=10) ? $news_count : 3;
10018 }
10019
10020 if (isset($input['brave_country'])) {
10021 $new_input['brave_country'] = sanitize_text_field($input['brave_country']);
10022 }
10023
10024 if (isset($input['brave_language'])) {
10025 $new_input['brave_language'] = sanitize_text_field($input['brave_language']);
10026 }
10027
10028 if (isset($input['chat_toolbar_toggle'])) {
10029 $new_input['chat_toolbar_toggle'] = $input['chat_toolbar_toggle'] === 'on' ? 'on' : 'off';
10030 }
10031
10032 // Sanitize PDF upload button toggle
10033 if (isset($input['show_pdf_upload_button'])) {
10034 $new_input['show_pdf_upload_button'] = $input['show_pdf_upload_button'] === 'on' ? 'on' : 'off';
10035 } else {
10036 $new_input['show_pdf_upload_button'] = 'off'; // If checkbox is unchecked
10037 }
10038
10039 // Sanitize Word upload button toggle
10040 if (isset($input['show_word_upload_button'])) {
10041 $new_input['show_word_upload_button'] = $input['show_word_upload_button'] === 'on' ? 'on' : 'off';
10042 } else {
10043 $new_input['show_word_upload_button'] = 'off'; // If checkbox is unchecked
10044 }
10045
10046 if (isset($input['pdf_intent_trigger_text'])) {
10047 $new_input['pdf_intent_trigger_text'] = sanitize_text_field($input['pdf_intent_trigger_text']);
10048 }
10049
10050 if (isset($input['pdf_intent_success_text'])) {
10051 $new_input['pdf_intent_success_text'] = sanitize_text_field($input['pdf_intent_success_text']);
10052 }
10053
10054 if (isset($input['pdf_intent_error_text'])) {
10055 $new_input['pdf_intent_error_text'] = sanitize_text_field($input['pdf_intent_error_text']);
10056 }
10057
10058 if (isset($input['pdf_max_pages'])) {
10059 $new_input['pdf_max_pages'] = intval($input['pdf_max_pages']);
10060 if ($new_input['pdf_max_pages'] < 1 || $new_input['pdf_max_pages'] > 69) {
10061 $new_input['pdf_max_pages'] = 69; // Default to 69 if out of range
10062 }
10063 }
10064
10065 if (isset($input['live_agent_webhook_url'])) {
10066 $new_input['live_agent_webhook_url'] = esc_url_raw($input['live_agent_webhook_url']);
10067 }
10068 if (isset($input['live_agent_secret_key'])) {
10069 $new_input['live_agent_secret_key'] = sanitize_text_field($input['live_agent_secret_key']);
10070 }
10071
10072 // Live Agent Integration
10073 if (isset($input['live_agent_bot_token'])) {
10074 $new_input['live_agent_bot_token'] = sanitize_text_field($input['live_agent_bot_token']);
10075 }
10076
10077 if (isset($input['live_agent_status'])) {
10078 $new_input['live_agent_status'] = ($input['live_agent_status'] === 'on') ? 'on' : 'off';
10079 }
10080 if (isset($input['live_agent_away_message'])) {
10081 $new_input['live_agent_away_message'] = sanitize_textarea_field($input['live_agent_away_message']);
10082 }
10083 if (isset($input['live_agent_notification_message'])) {
10084 $new_input['live_agent_notification_message'] = sanitize_textarea_field($input['live_agent_notification_message']);
10085 }
10086
10087 return $new_input;
10088 }
10089
10090
10091 // Method to append the chatbot to the body
10092 public function mxchat_append_chatbot_to_body() {
10093 $options = get_option('mxchat_options');
10094 if (isset($options['append_to_body']) && $options['append_to_body'] === 'on') {
10095 echo do_shortcode('[mxchat_chatbot floating="yes"]');
10096 }
10097 }
10098
10099
10100
10101
10102
10103 private function mxchat_fetch_loops_mailing_lists($api_key) {
10104 $url = 'https://app.loops.so/api/v1/lists';
10105 $response = wp_remote_get($url, array(
10106 'headers' => array(
10107 'Authorization' => 'Bearer ' . $api_key,
10108 'Content-Type' => 'application/json'
10109 )
10110 ));
10111
10112 if (is_wp_error($response)) {
10113 return array();
10114 }
10115
10116 $body = wp_remote_retrieve_body($response);
10117 $lists = json_decode($body, true);
10118
10119 return isset($lists) && is_array($lists) ? $lists : array();
10120 }
10121
10122 function mxchat_calculate_cosine_similarity($vec1, $vec2) {
10123 if (empty($vec1) || empty($vec2)) {
10124 return 0.0;
10125 }
10126
10127 $dot_product = 0.0;
10128 $norm_a = 0.0;
10129 $norm_b = 0.0;
10130
10131 for ($i = 0; $i < count($vec1); $i++) {
10132 $dot_product += $vec1[$i] * $vec2[$i];
10133 $norm_a += pow($vec1[$i], 2);
10134 $norm_b += pow($vec2[$i], 2);
10135 }
10136
10137 if ($norm_a == 0.0 || $norm_b == 0.0) {
10138 return 0.0;
10139 } else {
10140 return $dot_product / (sqrt($norm_a) * sqrt($norm_b));
10141 }
10142 }
10143
10144
10145 }
10146 ?>
10147