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
← All changes | includes/class-mxchat-admin.php +6389 -2087 2.0.42.2.1 View file →
@@ -37,17 +37,27 @@
37 37 add_action('wp_ajax_mxchat_save_inline_prompt', array($this, 'mxchat_save_inline_prompt'));
38 38 add_action('admin_post_mxchat_delete_all_prompts', array($this, 'mxchat_handle_delete_all_prompts'));
39 39 add_action('admin_post_mxchat_add_intent', array($this, 'mxchat_handle_add_intent'));
40 40 add_action('admin_post_mxchat_delete_intent', array($this, 'mxchat_handle_delete_intent'));
41 - add_action('admin_post_mxchat_update_intent_threshold', array($this, 'mxchat_handle_update_intent_threshold'));
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'));
42 43 add_action('admin_post_mxchat_stop_processing', array($this, 'mxchat_stop_processing'));
43 - add_action('admin_post_mxchat_edit_intent', array($this, 'handle_edit_intent'));
44 + add_action('admin_post_mxchat_edit_intent', array($this, 'mxchat_handle_edit_intent'));
44 45 add_action('save_post', array($this, 'handle_post_update'), 10, 3);
45 46 add_action('post_updated', array($this, 'handle_post_update'), 10, 3);
46 47 add_action('wp_ajax_mxchat_save_setting', array($this, 'mxchat_save_setting_callback'));
47 48 add_action('wp_trash_post', array($this, 'mxchat_handle_post_delete'));
48 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'));
49 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 +
50 60 if (isset($this->options['enable_woocommerce_integration']) &&
51 61 ($this->options['enable_woocommerce_integration'] === '1' ||
52 62 $this->options['enable_woocommerce_integration'] === 'on')) {
53 63
@@ -55,16 +65,23 @@
55 65 add_action('wp_insert_post', array($this, 'mxchat_handle_product_change'), 10, 3);
56 66 add_action('wp_trash_post', array($this, 'mxchat_handle_product_delete'));
57 67 add_action('before_delete_post', array($this, 'mxchat_handle_product_delete'));
58 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 +
59 73 }
60 74
61 - // Method to check if the license is active
62 75 private function is_license_active() {
63 - $license_status = get_option('mxchat_license_status', esc_html__('inactive', 'mxchat'));
64 - return $license_status === esc_html__('active', 'mxchat');
65 - }
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 +}
66 82
83 +
67 84 // Initialize default options
68 85 private function initialize_default_options() {
69 86 $default_options = array(
70 87 'api_key' => '',
@@ -70,23 +87,41 @@
70 87 'api_key' => '',
71 88 'xai_api_key' => '',
72 89 'claude_api_key' => '',
73 90 'deepseek_api_key' => '',
74 - 'system_prompt_instructions' => esc_html__('[EXAMPLE INSTRUCTIONS] You are an AI Chatbot assistant for this website. The primary subject you should focus on is [insert proper subject here]. Your main goal is to assist visitors with questions related to this specific topic. Here are some key things to keep in mind:
75 - - Your name is [Chatbot Name].
76 - - Stay focused on topics related to [insert proper subject here]. If a visitor asks about an unrelated topic, politely redirect the conversation to how you can assist them with this subject.
77 - - Do not make up links to pages. Only use specific links you see in your knowledgebase from [insert your domain]. Do not make assumptions or make up URLs.
78 - - Keep your responses short, concise, and to the point. Provide clear and direct answers suitable for a chatbot interaction.
79 - - Provide answers based on the knowledge available to you. If you do not have an answer to a specific question, let the visitor know that you don’t have the information.', 'mxchat'),
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',
80 114 'model' => esc_html__('gpt-4o', 'mxchat'),
81 115 'rate_limit_logged_out' => esc_html__('100', 'mxchat'),
82 116 'role_rate_limits' => array(),
83 117 'rate_limit_message' => esc_html__('Rate limit exceeded. Please try again later.', 'mxchat'),
84 118 'enable_email_block' => '',
85 - 'email_blocker_header_content' => esc_html__("<h2>Welcome to Our Chat!</h2>\n<p>Let's get started. Enter your email to begin chatting with us.</p>", 'mxchat'),
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'),
86 120 'email_blocker_button_text' => esc_html__('Start Chat', 'mxchat'),
87 121 'top_bar_title' => esc_html__('MxChat', 'mxchat'),
88 - 'intro_message' => esc_html__('Hello! How can I assist you today?', 'mxchat'),
122 + 'intro_message' => __('Hello! How can I assist you today?', 'mxchat'),
123 + 'ai_agent_text' => esc_html__('AI Agent', 'mxchat'),
89 124 'input_copy' => esc_html__('How can I assist?', 'mxchat'),
90 125 'append_to_body' => esc_html__('off', 'mxchat'),
91 126 'close_button_color' => esc_html__('#fff', 'mxchat'),
92 127 'chatbot_bg_color' => esc_html__('#fff', 'mxchat'),
@@ -105,17 +140,19 @@
105 140
106 141 // New fields for Loops Integration
107 142 'loops_api_key' => '',
108 143 'loops_mailing_list' => '',
109 - 'triggered_phrase_response' => esc_html__('Would you like to join our mailing list? Please provide your email below.', 'mxchat'),
110 - 'email_capture_response' => esc_html__('Thank you for providing your email! You\'ve been added to our list.', 'mxchat'),
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'),
111 146 'popular_question_1' => '',
112 147 'popular_question_2' => '',
113 148 'popular_question_3' => '',
114 - 'pdf_intent_trigger_text' => esc_html__("Please provide the URL to the PDF you'd like to discuss.", 'mxchat'),
115 - 'pdf_intent_success_text' => esc_html__("I've processed the PDF. What questions do you have about it?", 'mxchat'),
116 - 'pdf_intent_error_text' => esc_html__("Sorry, I couldn't process the PDF. Please ensure it's a valid file.", 'mxchat'),
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'),
117 152 'pdf_max_pages' => 69,
153 + 'show_pdf_upload_button' => 'on',
154 + 'show_word_upload_button' => 'on',
118 155
119 156 // Live Agent Integration
120 157 'live_agent_webhook_url' => '',
121 158 'live_agent_secret_key' => '',
@@ -173,26 +210,24 @@
173 210 'mxchat-prompts',
174 211 array($this, 'mxchat_create_prompts_page')
175 212 );
176 213
177 - // Submenu page for Chat Transcripts
178 214 add_submenu_page(
179 - 'mxchat-max', // Corrected parent slug to match the main menu
215 + 'mxchat-max',
180 216 esc_html__('Chat Transcripts', 'mxchat'),
181 217 esc_html__('Transcripts', 'mxchat'),
182 218 'manage_options',
183 219 'mxchat-transcripts',
184 - array($this, 'mxchat_create_transcripts_page') // Prefixed function name with mxchat_
220 + array($this, 'mxchat_create_transcripts_page')
185 221 );
186 222
187 - // Submenu page for Intents
188 223 add_submenu_page(
189 - 'mxchat-max', // Parent slug
190 - esc_html__('MxChat Intents', 'mxchat'), // Page title
191 - esc_html__('Intents', 'mxchat'), // Menu title
192 - 'manage_options', // Capability
193 - 'mxchat-intents', // Menu slug
194 - array($this, 'mxchat_intents_page_html') // Callback function
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')
195 230 );
196 231
197 232 add_submenu_page(
198 233 'mxchat-max',
@@ -222,28 +257,52 @@
222 257
223 258 public function mxchat_save_setting_callback() {
224 259 check_ajax_referer('mxchat_save_setting_nonce');
225 260 if (!current_user_can('manage_options')) {
261 + ('MXChat Save: Unauthorized access attempt');
226 262 wp_send_json_error(['message' => esc_html__('Unauthorized', 'mxchat')]);
227 263 }
264 +
228 265 $name = isset($_POST['name']) ? $_POST['name'] : '';
229 266 // Strip slashes from the value before saving
230 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 +
231 272 if (empty($name)) {
273 + //error_log('MXChat Save: Empty field name detected');
232 274 wp_send_json_error(['message' => esc_html__('Invalid field name', 'mxchat')]);
233 275 }
276 +
234 277 // Load the full options array
235 278 $options = get_option('mxchat_options', []);
279 + //error_log('MXChat Save: Current options array: ' . print_r($options, true));
280 +
236 281 // Handle special cases
237 282 switch ($name) {
238 283 case 'additional_popular_questions':
284 + //error_log('MXChat Save: Processing additional_popular_questions');
239 285 $questions = json_decode($value, true); // No need for stripslashes here
240 286 if (is_array($questions)) {
241 287 $options[$name] = $questions;
242 288 // Also update old option for backwards compatibility
243 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');
244 293 }
245 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;
246 305 case 'user_message_bg_color':
247 306 case 'user_message_font_color':
248 307 case 'bot_message_bg_color':
249 308 case 'bot_message_font_color':
@@ -256,119 +315,372 @@
256 315 case 'live_agent_message_font_color':
257 316 case 'mode_indicator_bg_color':
258 317 case 'mode_indicator_font_color':
259 318 case 'toolbar_icon_color':
319 + //error_log('MXChat Save: Processing color value: ' . $name);
260 320 // Store color values directly
261 321 $options[$name] = $value;
262 322 break;
263 323 case 'live_agent_status':
324 + //error_log('MXChat Save: Processing live_agent_status');
264 325 // Set the new value
265 326 $options[$name] = ($value === 'on') ? 'on' : 'off';
266 327 break;
267 328 case 'enable_woocommerce_integration':
329 + //error_log('MXChat Save: Processing enable_woocommerce_integration');
268 330 // Handle values that used to be 1/0
269 331 $options[$name] = ($value === 'on' || $value === '1') ? 'on' : 'off';
270 332 break;
271 333 default:
272 - // Handle role rate limits
273 - if (strpos($name, 'mxchat_options[role_rate_limits]') !== false) {
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);
274 374 // Extract role ID from the name
275 375 preg_match('/\[role_rate_limits\]\[(.*?)\]/', $name, $matches);
376 + //error_log('MXChat Save: Regex matches: ' . print_r($matches, true));
377 +
276 378 if (isset($matches[1])) {
277 379 $role_id = $matches[1];
278 380 // Initialize role_rate_limits if it doesn't exist
279 381 if (!isset($options['role_rate_limits'])) {
382 + //error_log('MXChat Save: Initializing role_rate_limits array');
280 383 $options['role_rate_limits'] = [];
281 384 }
282 385 // Update the specific role's rate limit
283 386 $options['role_rate_limits'][$role_id] = sanitize_text_field($value);
284 - break;
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);
285 390 }
286 391 }
287 392 // Handle toggles
288 - if (strpos($name, 'toggle') !== false || in_array($name, [
393 + else if (strpos($name, 'toggle') !== false || in_array($name, [
289 394 'chat_persistence_toggle',
290 395 'privacy_toggle',
291 396 'complianz_toggle',
292 - 'chat_toolbar_toggle' // Removed live_agent_status from here
397 + 'chat_toolbar_toggle',
398 + 'show_pdf_upload_button',
399 + 'show_word_upload_button'
293 400 ])) {
401 + //error_log('MXChat Save: Processing toggle: ' . $name);
294 402 $options[$name] = ($value === 'on') ? 'on' : 'off';
295 403 } else {
404 + //error_log('MXChat Save: Processing standard field: ' . $name);
296 405 // Store all other values directly
297 406 $options[$name] = $value;
298 407 }
299 408 break;
300 409 }
301 - // Save all updates
410 +
411 + // Save all updates to the options array
302 412 $updated = update_option('mxchat_options', $options);
303 - // Handle backwards compatibility for certain fields
304 - $legacy_fields = ['brave_api_key', 'brave_image_count', 'brave_safe_search', 'brave_news_count',
305 - 'brave_country', 'brave_language', 'similarity_threshold'];
306 - if (in_array($name, $legacy_fields)) {
307 - // Also update the individual option for backwards compatibility
308 - update_option($name, $value);
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;
309 429 }
310 - if ($updated) {
311 - wp_send_json_success(['message' => esc_html__('Setting saved', 'mxchat')]);
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 + ));
312 493 } else {
313 - wp_send_json_error(['message' => esc_html__('Update failed or no changes', 'mxchat')]);
494 + wp_send_json_success(array(
495 + 'migrated' => false,
496 + 'message' => 'Settings already exist in core plugin'
497 + ));
314 498 }
315 499 }
316 500
317 501 /**
318 - * Handles AJAX auto-save for prompts and auto-sync settings.
502 + * Fixed AJAX handler that bypasses the problematic sanitization
319 503 */
504 +/**
505 + * Fixed AJAX handler with improved verification
506 + */
320 507 public function mxchat_save_prompts_setting_callback() {
321 - // Check the correct nonce for this action.
322 - check_ajax_referer( 'mxchat_prompts_setting_nonce', '_ajax_nonce' );
323 -
324 - if ( ! current_user_can( 'manage_options' ) ) {
325 - wp_send_json_error( [ 'message' => esc_html__( 'Unauthorized', 'mxchat' ) ] );
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')]);
326 512 }
327 513
328 - $name = isset( $_POST['name'] ) ? sanitize_text_field( wp_unslash( $_POST['name'] ) ) : '';
329 - $value = isset( $_POST['value'] ) ? sanitize_text_field( wp_unslash( $_POST['value'] ) ) : '';
330 -
331 - if ( empty( $name ) ) {
332 - wp_send_json_error( [ 'message' => esc_html__( 'Invalid field name', 'mxchat' ) ] );
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')]);
333 521 }
334 -
335 - // Handle standalone auto-sync settings.
336 - if ( in_array( $name, [ 'mxchat_auto_sync_posts', 'mxchat_auto_sync_pages' ], true ) ) {
337 - // Convert checkbox value to "1" if checked, "0" if not.
338 - $value = ( $value === 'on' ) ? '1' : '0';
339 - update_option( $name, $value );
340 - wp_send_json_success( [ 'message' => esc_html__( 'Setting saved', 'mxchat' ) ] );
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
341 616 }
342 -
343 - // Handle fields stored in the 'mxchat_prompts_options' array.
344 -// Handle fields stored in the 'mxchat_prompts_options' array.
345 -if ( false !== strpos( $name, 'mxchat_prompts_options[' ) ) {
346 - // Extract the key name using regex.
347 - if ( preg_match( '/mxchat_prompts_options\[(.*?)\]/', $name, $matches ) && ! empty( $matches[1] ) ) {
348 - $key = sanitize_text_field( $matches[1] );
349 - $options = get_option( 'mxchat_prompts_options', [] );
350 -
351 - // Remove Pinecone-specific settings from the main plugin handling.
352 - if ( 'mxchat_use_pinecone' === $key ) {
353 - // Optionally, you could simply ignore this field or set a default value.
354 - // For example, ensure it's always 0 or remove it entirely:
355 - // $options[ $key ] = '0';
356 - // Or, if you prefer, just return an error so that the main plugin doesn't handle it:
357 - wp_send_json_error( [ 'message' => esc_html__( 'Pinecone settings are now handled in the add-on.', 'mxchat' ) ] );
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')]);
358 625 } else {
359 - // For all other fields, use the existing logic.
360 - $options[ $key ] = ( $value === 'on' || $value === '1' ) ? '1' : $value;
626 + wp_send_json_error(['message' => esc_html__('No changes detected', 'mxchat')]);
361 627 }
362 - update_option( 'mxchat_prompts_options', $options );
363 - wp_send_json_success( [ 'message' => esc_html__( 'Setting saved', 'mxchat' ) ] );
364 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 + }
365 640 }
366 641
367 642
368 - wp_send_json_error( [ 'message' => esc_html__( 'Field not recognized', 'mxchat' ) ] );
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 + );
369 662 }
370 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 +
371 683 public function mxchat_display_admin_notice() {
372 684 // Success notice
373 685 if ($message = get_transient('mxchat_admin_notice_success')) {
374 686 ?>
@@ -398,181 +710,260 @@
398 710
399 711 public function mxchat_create_admin_page() {
400 712
401 713 ?>
402 - <div class="wrap mxchat-admin">
403 - <?php if (!$this->is_activated): ?>
404 - <div class="mxchat-pro-banner">
405 - <p>
406 - <?php echo esc_html__('For a limited time, get lifetime access and save $20 on MxChat Pro!', 'mxchat'); ?>
407 - <a href="https://mxchat.ai/" target="_blank"><?php echo esc_html__('Upgrade to Pro today', 'mxchat'); ?></a>
408 - </p>
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>
409 723 </div>
410 - <?php endif; ?>
411 724
412 - <div class="mxchat-agents-banner">
413 - <p>
414 - <strong><?php echo esc_html__('The MxChat Add-Ons Platform', 'mxchat'); ?></strong><?php echo esc_html__(' is here! Pro users get unlimited access to our growing collection of powerful add-ons. ', 'mxchat'); ?><a href="<?php echo admin_url('admin.php?page=mxchat-addons'); ?>"><?php echo esc_html__('Explore all add-ons', 'mxchat'); ?></a><?php echo esc_html__(' including Forms Builder, Theme Customizer, Content Moderation, and more – all included with your lifetime Pro license!', 'mxchat'); ?>
415 - </p>
416 - </div>
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; ?>
417 739
418 - <h2 class="mxchat-nav-tab-wrapper">
419 - <a href="#chatbot" class="mxchat-nav-tab mxchat-nav-tab-active" data-tab="chatbot"><?php echo esc_html__('Chatbot', 'mxchat'); ?></a>
420 - <a href="#embed" class="mxchat-nav-tab" data-tab="embed"><?php echo esc_html__('Integrations', 'mxchat'); ?></a>
421 - <a href="#general" class="mxchat-nav-tab" data-tab="general"><?php echo esc_html__('FAQ', 'mxchat'); ?></a>
422 - </h2>
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>
423 746
424 - <div id="chatbot" class="mxchat-tab-content active">
425 - <div class="mxchat-autosave-section">
426 - <?php do_settings_sections('mxchat-chatbot'); ?>
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>
427 754 </div>
428 - </div>
429 755
430 - <div id="embed" class="mxchat-tab-content">
431 - <div class="mxchat-autosave-section">
432 -
433 - <div class="mxchat-settings-section">
434 - <h2><?php echo esc_html__('Loops Settings', 'mxchat'); ?></h2>
435 - <table class="form-table">
436 - <?php do_settings_fields('mxchat-embed', 'mxchat_loops_section'); ?>
437 - </table>
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>
438 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>
439 776
440 - <div class="section-divider"></div>
441 -
442 - <div class="mxchat-settings-section">
443 - <h2><?php echo esc_html__('Brave Search Settings', 'mxchat'); ?></h2>
444 - <table class="form-table">
445 - <?php do_settings_fields('mxchat-embed', 'mxchat_brave_section'); ?>
446 - </table>
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>
447 784 </div>
448 785
449 - <div class="section-divider"></div>
450 -
451 - <div class="mxchat-settings-section">
452 - <h2><?php echo esc_html__('Toolbar Settings & Intents', 'mxchat'); ?></h2>
453 - <table class="form-table">
454 - <?php do_settings_fields('mxchat-embed', 'mxchat_pdf_intent_section'); ?>
455 - </table>
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>
456 794 </div>
795 + </div>
457 796
458 - <div class="section-divider"></div>
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>
459 825
460 - <!-- Live Agent Settings Section -->
461 - <div class="mxchat-settings-section">
462 - <h2><?php echo esc_html__('Live Agent Settings', 'mxchat'); ?></h2>
463 - <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>
464 - <table class="form-table">
465 - <?php do_settings_fields('mxchat-embed', 'mxchat_live_agent_section'); ?>
466 - </table>
467 - </div>
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>
468 835 </div>
469 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>
470 848
471 -
472 - <div id="general" class="mxchat-tab-content">
473 - <?php do_settings_sections('mxchat-general'); ?>
474 - <p>
475 - <?php echo esc_html__('If you’re having trouble with setup or getting the responses you need, we encourage you to review our', 'mxchat'); ?>
476 - <a href="https://mxchat.ai/documentation/" target="_blank" rel="noopener noreferrer"><?php echo esc_html__('documentation', 'mxchat'); ?></a> <?php echo esc_html__('or', 'mxchat'); ?>
477 - <a href="https://wordpress.org/support/plugin/mxchat-basic/" target="_blank" rel="noopener noreferrer"><?php echo esc_html__('create a support ticket', 'mxchat'); ?></a>.
478 - </p>
479 - <p>
480 - <?php echo esc_html__('If you like our plugin, please consider', 'mxchat'); ?> <a href="https://wordpress.org/plugins/mxchat-basic/#reviews" target="_blank" rel="noopener noreferrer"><?php echo esc_html__('leaving us a review', 'mxchat'); ?></a>.
481 - </p>
482 -
483 - <div class="faq-item">
484 - <h3><?php echo esc_html__('How does the Claude API integration work?', 'mxchat'); ?></h3>
485 - <p>
486 - <?php echo esc_html__('The Claude API, provided by Anthropic, allows for intelligent, context-aware chatbot responses in MxChat. To use it, you will need both an OpenAI API key and a Claude API key. This is necessary because the system utilizes OpenAI for vector embedding in the Retrieval-Augmented Generation (RAG) process, as Claude does not currently offer an embedding API.', 'mxchat'); ?>
487 - </p>
488 - <p>
489 - <?php echo esc_html__('When using the Claude API, your custom content is sent to Claude for generating responses, while the embeddings are processed through OpenAI. This ensures your chatbot provides accurate and engaging responses while maintaining knowledge relevant to your website.', 'mxchat'); ?>
490 - </p>
491 - <p>
492 - <?php echo esc_html__('You can obtain your Claude API key by signing up on the', 'mxchat'); ?> <a href="https://www.anthropic.com/api" target="_blank" rel="noopener"><?php echo esc_html__('Anthropic API page', 'mxchat'); ?></a>.
493 - </p>
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>
494 857 </div>
495 -
496 - <div class="faq-item">
497 - <h3><?php echo esc_html__('How does the X.AI API integration work?', 'mxchat'); ?></h3>
498 - <p>
499 - <?php echo esc_html__('The X.AI API, released on 10.21.24, is currently in beta. To use it, you will need both an OpenAI API key and an X.AI API key. This is because OpenAI handles vector embeddings in the Retrieval-Augmented Generation (RAG) process, as X.AI does not yet provide an embedding API.', 'mxchat'); ?>
500 - </p>
501 - <p>
502 - <?php echo esc_html__('Custom content is sent to the X.AI API for generating responses, while OpenAI processes the embeddings. This allows the chatbot to provide advanced responses while maintaining context from your website. You can get your X.AI API key from the', 'mxchat'); ?> <a href="https://docs.x.ai/docs" target="_blank" rel="noopener"><?php echo esc_html__('X.AI API documentation', 'mxchat'); ?></a>.
503 - </p>
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>
504 868 </div>
869 + </div>
505 870
506 - <div class="faq-item">
507 - <h3><?php echo esc_html__('Do I need an OpenAI API key to use the chatbot?', 'mxchat'); ?></h3>
508 - <p>
509 - <?php echo esc_html__('Yes, you will need an OpenAI API key to power the chatbot. You can obtain an API key by signing up on the', 'mxchat'); ?> <a href="https://platform.openai.com/signup" target="_blank" rel="noopener"><?php echo esc_html__('OpenAI platform', 'mxchat'); ?></a>. <?php echo esc_html__('After signing up, you must add credits to your account—typically, $5 in credits is sufficient to get started.', 'mxchat'); ?>
510 - </p>
511 - <p>
512 - <?php echo esc_html__('Once you have your API key, simply enter it in the chatbot\'s settings to enable functionality. The chatbot relies on OpenAI’s models for generating responses, so having sufficient credits in your OpenAI account is essential for smooth operation.', 'mxchat'); ?>
513 - </p>
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>
514 879 </div>
880 + </div>
515 881
516 - <div class="faq-item">
517 - <h3><?php echo esc_html__('How do I add the chatbot to my site?', 'mxchat'); ?></h3>
518 - <p>
519 - <?php echo esc_html__('You can add the chatbot using the shortcode', 'mxchat'); ?> <code>[mxchat_chatbot floating="yes"]</code> <?php echo esc_html__('or', 'mxchat'); ?> <code>[mxchat_chatbot floating="no"]</code>. <?php echo esc_html__('For initial testing and styling, it\'s best to use the shortcode on a draft or non-public page. Once you’re ready to go live, enable the “Append Chat Widget to Body” option in the settings for site-wide integration (recommended) or add shortcode to the footer.', 'mxchat'); ?>
520 - </p>
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>
521 891 </div>
892 + </div>
522 893
523 - <div class="faq-item">
524 - <h3><?php echo esc_html__('How does the chatbot use my content to generate responses?', 'mxchat'); ?></h3>
525 - <p>
526 - <?php echo esc_html__('The chatbot uses AI and vector embeddings to connect users with relevant information. When you submit content, it converts it into a mathematical format. When a user asks a question, the bot matches it to your stored content and generates a response based on relevance. For example, submitting "Our phone number is 910-123-4567" allows the bot to retrieve this information when asked about contact details. To ensure related information is provided together, submit it in one entry.', 'mxchat'); ?>
527 - </p>
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>
528 902 </div>
529 -
530 - <div class="faq-item">
531 - <h3><?php echo esc_html__('Why does the chatbot sometimes make up links or information?', 'mxchat'); ?></h3>
532 - <p>
533 - <?php echo esc_html__('Occasionally, the chatbot may generate inaccurate links or information, known as "hallucinations." To minimize this, you can add system instructions to guide its behavior. For example, include an instruction like: "Only provide links you directly have access to or retrieve from the knowledge base. Do not make up links or information that you do not have direct access to." This helps the bot stay aligned with your content.', 'mxchat'); ?>
534 - </p>
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>
535 913 </div>
536 -
537 - <div class="faq-item">
538 - <h3><?php echo esc_html__('How do intents work in MxChat?', 'mxchat'); ?></h3>
539 - <p>
540 - <?php echo esc_html__('Intents allow MxChat to recognize user requests and trigger specific actions, such as capturing emails or displaying product information. This helps provide a more interactive and responsive experience. For a detailed explanation of each intent, please refer to our', 'mxchat'); ?> <a href="https://mxchat.ai/documentation/#intents" target="_blank" rel="noopener noreferrer"><?php echo esc_html__('Intents Documentation', 'mxchat'); ?></a>.
541 - </p>
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>
542 924 </div>
925 + </div>
543 926
544 - <div class="faq-item">
545 - <h3><?php echo esc_html__('How does the Complianz integration work?', 'mxchat'); ?></h3>
546 - <p>
547 - <?php echo esc_html__('The Pro version offers direct integration with the Complianz GDPR plugin, making it easy to stay compliant with GDPR. If Complianz is enabled on your website, users must accept consent before the chatbot widget appears. The chatbot will only display once the user has accepted, ensuring compliance with data privacy regulations.', 'mxchat'); ?>
548 - </p>
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>
549 935 </div>
936 + </div>
550 937
551 - <div class="faq-item">
552 - <h3><?php echo esc_html__('How does the WooCommerce integration work?', 'mxchat'); ?></h3>
553 - <p>
554 - <?php echo esc_html__('With WooCommerce integration, the chatbot automatically embeds new products and updates existing ones. For already-published products, you can click update or submit the product sitemap. The “Order History Access” setting allows the bot to access users\' order history (requires login) and assist with order inquiries. To enable the “Add to Cart” feature, add this system instruction: “After discussing a product, ask if the user wants to add it to their cart. The user must say \'Add to cart\' exactly.” Currently, this feature supports English only, with more languages coming soon.', 'mxchat'); ?>
555 - </p>
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>
556 947 </div>
948 + </div>
557 949
558 - <div class="faq-item">
559 - <h3><?php echo esc_html__('Why isn\'t my chatbot responding as expected?', 'mxchat'); ?></h3>
560 - <p>
561 - <?php echo esc_html__('AI chatbots can sometimes behave in unexpected ways, especially if you\'re new to configuring AI for specific tasks. We\'re committed to helping you get the most out of your chatbot experience. While we’re working on comprehensive guides and video tutorials, our team is here to assist you directly. If your chatbot isn\'t delivering the responses you need, please don’t hesitate to', 'mxchat'); ?> <a href="https://mxchat.ai/contact/" target="_blank" rel="noopener noreferrer"><?php echo esc_html__('contact us', 'mxchat'); ?></a> <?php echo esc_html__('for personalized support.', 'mxchat'); ?>
562 - </p>
563 - <p>
564 - <?php echo esc_html__('We’re dedicated to your success and ready to guide you in aligning the AI\'s behavior to meet your goals.', 'mxchat'); ?>
565 - </p>
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>
566 959 </div>
960 + </div>
961 + </div>
567 962
568 - <div class="faq-item">
569 - <h3><?php echo esc_html__('What is Loops, and how do I get an API key?', 'mxchat'); ?></h3>
570 - <p>
571 - <?php echo esc_html__('Loops is a powerful SaaS email service that helps you automate and enhance your email marketing campaigns, making it easy to reach and engage with your audience. To integrate Loops with MxChat, you’ll need an API key from Loops. You can obtain this key by logging into your Loops account and navigating to the API settings. Visit the', 'mxchat'); ?> <a href="https://loops.so" target="_blank" rel="noopener noreferrer"><?php echo esc_html__('Loops website', 'mxchat'); ?></a> <?php echo esc_html__('to get started or to sign up for an account.', 'mxchat'); ?>
572 - </p>
963 + </div>
964 + </div>
573 965 </div>
574 -
575 966 </div>
576 967 </div>
577 968 <?php
578 969 }
@@ -577,73 +968,554 @@
577 968 <?php
578 969 }
579 970
580 971
581 -
582 972 public function mxchat_create_transcripts_page() {
583 973 global $wpdb;
584 974 $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
585 -
975 +
586 976 // Get basic stats
587 977 $total_chats = $wpdb->get_var("SELECT COUNT(DISTINCT session_id) FROM $table_name");
588 978 $total_messages = $wpdb->get_var("SELECT COUNT(*) FROM $table_name");
589 - $total_users = $wpdb->get_var("SELECT COUNT(DISTINCT user_email) FROM $table_name WHERE user_email != ''");
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 + ");
590 1042 ?>
591 - <div class="wrap mxchat-admin">
592 - <!-- Stats Cards -->
593 - <div class="mxchat-stats-grid">
594 - <div class="mxchat-stat-card">
595 - <div class="stat-icon">💬</div>
596 - <div class="stat-content">
597 - <span class="stat-value"><?php echo esc_html($total_chats); ?></span>
598 - <span class="stat-label"><?php esc_html_e('Total Chats', 'mxchat'); ?></span>
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>
599 1062 </div>
600 - </div>
601 - <div class="mxchat-stat-card">
602 - <div class="stat-icon">📝</div>
603 - <div class="stat-content">
604 - <span class="stat-value"><?php echo esc_html($total_messages); ?></span>
605 - <span class="stat-label"><?php esc_html_e('Total Messages', 'mxchat'); ?></span>
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>
606 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>
607 1087 </div>
608 - <div class="mxchat-stat-card">
609 - <div class="stat-icon">👥</div>
610 - <div class="stat-content">
611 - <span class="stat-value"><?php echo esc_html($total_users); ?></span>
612 - <span class="stat-label"><?php esc_html_e('Unique Users', 'mxchat'); ?></span>
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">
613 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>
614 1117 </div>
1118 +
1119 + <!-- Transcripts Container -->
1120 + <div id="mxchat-transcripts"></div>
615 1121 </div>
616 -
617 - <!-- Search and Filter Controls -->
618 - <div class="mxchat-controls-wrapper">
619 - <div class="mxchat-search-box">
620 - <input type="text" id="mxchat-search-transcripts"
621 - placeholder="<?php esc_attr_e('Search transcripts...', 'mxchat'); ?>"
622 - class="regular-text">
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>
623 1130 </div>
624 -
625 - <form id="mxchat-delete-form" method="post">
626 - <?php wp_nonce_field('mxchat_delete_chat_history', 'mxchat_delete_chat_nonce'); ?>
627 - <div class="mxchat-controls">
628 - <button type="button" id="mxchat-select-all-transcripts" class="mxchat-select-button">
629 - <span class="dashicons dashicons-yes-alt"></span>
630 - <span class="button-text"><?php esc_html_e('Select All', 'mxchat'); ?></span>
631 - </button>
632 - <button type="submit" class="button delete-chats-button">
633 - <span class="dashicons dashicons-trash"></span>
634 - <?php esc_html_e('Delete Selected', 'mxchat'); ?>
635 - </button>
636 - </div>
637 - </form>
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>
638 1147 </div>
639 -
640 - <!-- Transcripts Container -->
641 - <div id="mxchat-transcripts"></div>
642 1148 </div>
643 1149 <?php
644 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 + }
645 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 +
646 1518 public function mxchat_create_prompts_page() {
647 1519 global $wpdb;
648 1520 $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
649 1521
@@ -656,43 +1528,72 @@
656 1528 $nonce = isset($_GET['_wpnonce']) ? sanitize_text_field($_GET['_wpnonce']) : '';
657 1529 $search_query = (!empty($nonce) && wp_verify_nonce($nonce, 'mxchat_prompts_search_nonce') && isset($_GET['search'])) ? sanitize_text_field($_GET['search']) : '';
658 1530 $current_page = isset($_GET['paged']) ? absint($_GET['paged']) : 1;
659 1531 $per_page = 10;
660 - $offset = ($current_page - 1) * $per_page;
661 1532
662 - // Modify query to handle search input
663 - $sql_search = "";
664 - if ($search_query) {
665 - $sql_search = $wpdb->prepare("WHERE article_content LIKE %s", '%' . $wpdb->esc_like($search_query) . '%');
666 - }
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';
667 1540
668 - // Retrieve total number of prompts, considering search filter
669 - $total_prompts = $wpdb->get_var("SELECT COUNT(*) FROM {$table_name} {$sql_search}");
670 - $total_pages = ceil($total_prompts / $per_page);
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;
671 1554
672 - // Retrieve prompts from the database
673 - $prompts = $wpdb->get_results(
674 - $wpdb->prepare(
675 - "SELECT * FROM {$table_name} {$sql_search} ORDER BY timestamp DESC LIMIT %d OFFSET %d",
676 - $per_page,
677 - $offset
678 - )
679 - );
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 + }
680 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 +
681 1575 // Add the pagination links generation here
682 - $page_links = paginate_links(array(
683 - 'base' => add_query_arg(array(
684 - 'paged' => '%#%',
685 - 'search' => urlencode($search_query),
686 - '_wpnonce' => wp_create_nonce('mxchat_prompts_search_nonce')
687 - ), admin_url('admin.php?page=mxchat-prompts')),
688 - 'format' => '',
689 - 'prev_text' => __('&laquo; Previous', 'mxchat'),
690 - 'next_text' => __('Next &raquo;', 'mxchat'),
691 - 'total' => $total_pages,
692 - 'current' => $current_page,
693 - ));
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 + }
694 1591
1592 + // ================================
1593 + // END OF REPLACEMENT SECTION
1594 + // ================================
1595 +
695 1596 // Retrieve processing statuses
696 1597 $pdf_url = get_transient('mxchat_last_pdf_url');
697 1598 $sitemap_url = get_transient('mxchat_last_sitemap_url');
698 1599 $pdf_status = $pdf_url ? $this->get_pdf_processing_status($pdf_url) : false;
@@ -709,9 +1610,10 @@
709 1610
710 1611 $is_processing = ($pdf_status && ($pdf_status['status'] === 'processing' || $pdf_status['status'] === 'error'))
711 1612 || ($sitemap_status && ($sitemap_status['status'] === 'processing' || $sitemap_status['status'] === 'error'));
712 1613 ?>
713 - <div class="wrap mxchat-wrapper">
1614 +
1615 +<div class="wrap mxchat-wrapper">
714 1616 <!-- Hero Section -->
715 1617 <div class="mxchat-hero">
716 1618 <h1 class="mxchat-main-title">
717 1619 <span class="mxchat-gradient-text">Knowledge Base</span> Manager
@@ -721,207 +1623,367 @@
721 1623 </p>
722 1624 </div>
723 1625
724 1626 <div class="mxchat-content">
725 - <!-- Import Settings Card -->
726 - <div class="mxchat-card">
727 - <h2><?php esc_html_e('Knowledge Import Settings', 'mxchat'); ?></h2>
728 - <p><?php esc_html_e('Using the free Pinecone DB Manager add-on is recommended for high performance and a knowledge database of more than 500.', 'mxchat'); ?></p>
1627 +
729 1628
730 - <!-- Tab Contents -->
731 - <div class="mxchat-tab-contents">
732 - <!-- Default Database Tab -->
733 - <div id="default-db" class="mxchat-tab-content active">
734 - <!-- Auto-Sync Settings -->
735 - <div class="mxchat-settings-section">
736 - <h3><?php esc_html_e('Auto-Sync Settings', 'mxchat'); ?></h3>
737 - <p class="mxchat-description">
738 - <?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'); ?>
739 - </p>
740 - <div class="mxchat-autosave-section"> <!-- Add this wrapper -->
741 - <div class="mxchat-toggle-group">
742 - <div class="mxchat-toggle-container">
743 - <label class="mxchat-toggle-switch">
744 - <input type="checkbox"
745 - name="mxchat_auto_sync_posts"
746 - class="mxchat-autosave-field"
747 - value="1"
748 - data-nonce="<?php echo wp_create_nonce('mxchat_prompts_setting_nonce'); ?>"
749 - <?php checked(get_option('mxchat_auto_sync_posts', '0'), '1'); ?>>
750 - <span class="mxchat-toggle-slider"></span>
751 - </label>
752 - <span class="mxchat-toggle-label">
753 - <?php esc_html_e('Auto-sync Posts', 'mxchat'); ?>
754 - </span>
755 - </div>
756 - <div class="mxchat-toggle-container">
757 - <label class="mxchat-toggle-switch">
758 - <input type="checkbox"
759 - name="mxchat_auto_sync_pages"
760 - class="mxchat-autosave-field"
761 - value="1"
762 - data-nonce="<?php echo wp_create_nonce('mxchat_prompts_setting_nonce'); ?>"
763 - <?php checked(get_option('mxchat_auto_sync_pages', '0'), '1'); ?>>
764 - <span class="mxchat-toggle-slider"></span>
765 - </label>
766 - <span class="mxchat-toggle-label">
767 - <?php esc_html_e('Auto-sync Pages', 'mxchat'); ?>
768 - </span>
769 - </div>
770 - </div>
771 - </div>
772 - </div>
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 +
773 1642
774 - <!-- Import Methods -->
775 - <div class="mxchat-import-methods">
776 - <div class="mxchat-method-card">
777 - <h4><?php esc_html_e('Sitemap Import', 'mxchat'); ?></h4>
778 - <p><?php esc_html_e('Import all content from your sitemap automatically.', 'mxchat'); ?></p>
779 - </div>
780 - <div class="mxchat-method-card">
781 - <h4><?php esc_html_e('PDF Import', 'mxchat'); ?></h4>
782 - <p><?php esc_html_e('Import knowledge directly from PDF documents.', 'mxchat'); ?></p>
783 - </div>
784 - <div class="mxchat-method-card">
785 - <h4><?php esc_html_e('Direct URL', 'mxchat'); ?></h4>
786 - <p><?php esc_html_e('Import content from any specific webpage.', 'mxchat'); ?></p>
787 - </div>
788 - <div class="mxchat-method-card">
789 - <h4><?php esc_html_e('Direct Content', 'mxchat'); ?></h4>
790 - <p><?php esc_html_e('Directly submit content to be vectorized.', 'mxchat'); ?></p>
791 - </div>
792 - </div>
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">
793 1647
794 - <!-- Import Form -->
795 - <?php if (!$is_processing) : ?>
796 - <div class="mxchat-import-form">
797 - <form id="mxchat-sitemap-form" method="post"
798 - action="<?php echo esc_url(admin_url('admin-post.php?action=mxchat_submit_sitemap')); ?>">
799 - <?php wp_nonce_field('mxchat_submit_sitemap_action', 'mxchat_submit_sitemap_nonce'); ?>
800 - <div class="mxchat-url-input-group">
801 - <input type="url"
802 - name="sitemap_url"
803 - id="sitemap_url"
804 - placeholder="<?php esc_attr_e('Enter Sitemap, PDF, or Webpage URL', 'mxchat'); ?>"
805 - required />
806 - <button type="submit"
807 - name="submit_sitemap"
808 - class="mxchat-button-primary">
809 - <?php esc_html_e('Import', 'mxchat'); ?>
810 - </button>
811 - </div>
812 - </form>
813 - </div>
814 - <?php endif; ?>
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']);
815 1656
816 - <!-- Processing Status -->
817 - <?php if ($pdf_status && $pdf_status['status'] !== 'complete') : ?>
818 - <div class="mxchat-status-card">
819 - <div class="mxchat-status-header">
820 - <h4><?php esc_html_e('PDF Processing Status', 'mxchat'); ?></h4>
821 - <?php if ($is_processing) : ?>
822 - <form method="post" class="mxchat-stop-form"
823 - action="<?php echo esc_url(admin_url('admin-post.php?action=mxchat_stop_processing')); ?>">
824 - <?php wp_nonce_field('mxchat_stop_processing_action', 'mxchat_stop_processing_nonce'); ?>
825 - <button type="submit" name="stop_processing" class="mxchat-button-secondary">
826 - <?php esc_html_e('Stop Processing', 'mxchat'); ?>
827 - </button>
828 - </form>
829 - <?php endif; ?>
830 - </div>
831 - <div class="mxchat-progress-bar">
832 - <div class="mxchat-progress-fill" style="width: <?php echo esc_attr($pdf_status['percentage']); ?>%"></div>
833 - </div>
834 - <div class="mxchat-status-details">
835 - <p><?php printf(
836 - esc_html__('Progress: %1$d of %2$d pages (%3$d%%)', 'mxchat'),
837 - absint($pdf_status['processed_pages']),
838 - absint($pdf_status['total_pages']),
839 - absint($pdf_status['percentage'])
840 - ); ?></p>
841 - <p><?php printf(
842 - esc_html__('Status: %s', 'mxchat'),
843 - esc_html(ucfirst($pdf_status['status']))
844 - ); ?></p>
845 - <p><?php printf(
846 - esc_html__('Last update: %s', 'mxchat'),
847 - esc_html($pdf_status['last_update'])
848 - ); ?></p>
849 - </div>
850 - </div>
851 - <?php endif; ?>
1657 +// Determine if they have the needed API key for their selected embedding model
1658 +$has_required_key = false;
1659 +$required_key_type = '';
852 1660
853 - <?php if ($sitemap_status && $sitemap_status['status'] !== 'complete') : ?>
854 - <div class="mxchat-status-card">
855 - <div class="mxchat-status-header">
856 - <h4><?php esc_html_e('Sitemap Processing Status (Refresh for update)', 'mxchat'); ?></h4>
857 - <?php if ($is_processing) : ?>
858 - <form method="post" class="mxchat-stop-form"
859 - action="<?php echo esc_url(admin_url('admin-post.php?action=mxchat_stop_processing')); ?>">
860 - <?php wp_nonce_field('mxchat_stop_processing_action', 'mxchat_stop_processing_nonce'); ?>
861 - <button type="submit" name="stop_processing" class="mxchat-button-secondary">
862 - <?php esc_html_e('Stop Processing', 'mxchat'); ?>
863 - </button>
864 - </form>
865 - <?php endif; ?>
866 - </div>
867 - <div class="mxchat-progress-bar">
868 - <div class="mxchat-progress-fill" style="width: <?php echo esc_attr($sitemap_status['percentage']); ?>%"></div>
869 - </div>
870 - <div class="mxchat-status-details">
871 - <p><?php printf(
872 - esc_html__('Progress: %1$d of %2$d URLs (%3$d%%)', 'mxchat'),
873 - absint($sitemap_status['processed_urls']),
874 - absint($sitemap_status['total_urls']),
875 - absint($sitemap_status['percentage'])
876 - ); ?></p>
877 - <?php if (!empty($sitemap_status['error']) || !empty($sitemap_status['last_error'])) : ?>
878 - <div class="mxchat-error-notice">
879 - <?php if (!empty($sitemap_status['error'])) : ?>
880 - <p class="error"><?php echo esc_html($sitemap_status['error']); ?></p>
881 - <?php endif; ?>
882 - <?php if (!empty($sitemap_status['last_error'])) : ?>
883 - <p class="last-error"><?php echo esc_html__('Last error:', 'mxchat') . ' ' . esc_html($sitemap_status['last_error']); ?></p>
884 - <?php endif; ?>
885 - </div>
886 - <?php endif; ?>
887 - </div>
888 - </div>
889 - <?php endif; ?>
890 - </div>
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 +?>
891 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>
892 1686
893 - </div>
894 - </div>
895 1687
896 - <!-- Direct Content Submission Card -->
897 -<div class="mxchat-card">
898 - <div class="mxchat-card-header">
899 - <h2><?php esc_html_e('Submit Direct Content', 'mxchat'); ?></h2>
900 - </div>
901 - <form id="mxchat-content-form" method="post"
902 - action="<?php echo esc_url(admin_url('admin-post.php?action=mxchat_submit_content')); ?>">
903 - <?php wp_nonce_field('mxchat_submit_content_action', 'mxchat_submit_content_nonce'); ?>
904 - <div class="mxchat-form-group">
905 - <label for="article_content"><?php esc_html_e('Content:', 'mxchat'); ?></label>
906 - <textarea name="article_content" id="article_content" required
907 - placeholder="<?php esc_attr_e('Enter your content here...', 'mxchat'); ?>"></textarea>
908 - </div>
909 - <div class="mxchat-form-group">
910 - <label for="article_url"><?php esc_html_e('Source URL (Optional):', 'mxchat'); ?></label>
911 - <input type="url" name="article_url" id="article_url"
912 - placeholder="<?php esc_attr_e('Enter source URL', 'mxchat'); ?>">
913 - </div>
914 - <button type="submit" name="submit_content" class="mxchat-button-primary">
915 - <?php esc_html_e('Submit Content', 'mxchat'); ?>
916 - </button>
917 - </form>
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; ?>
918 1964 </div>
919 1965
920 1966 <!-- Knowledge Base Table Card -->
921 1967 <div class="mxchat-card">
922 1968 <div class="mxchat-card-header">
923 - <h2><?php esc_html_e('Knowledge Base', 'mxchat'); ?></h2>
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>
924 1986 <div class="mxchat-header-actions">
925 1987 <!-- Search -->
926 1988 <form method="get" id="knowledge-search" class="mxchat-search-form">
927 1989 <?php wp_nonce_field('mxchat_prompts_search_nonce'); ?>
@@ -940,8 +2002,9 @@
940 2002 action="<?php echo esc_url(admin_url('admin-post.php?action=mxchat_delete_all_prompts')); ?>"
941 2003 class="mxchat-delete-form"
942 2004 onsubmit="return confirm('<?php esc_attr_e('Are you sure you want to delete all knowledge? This action cannot be undone.', 'mxchat'); ?>');">
943 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); ?>" />
944 2007 <button type="submit" name="delete_all_prompts" class="mxchat-button-danger">
945 2008 <span class="dashicons dashicons-trash"></span>
946 2009 <?php esc_html_e('Delete All', 'mxchat'); ?>
947 2010 </button>
@@ -948,8 +2011,21 @@
948 2011 </form>
949 2012 </div>
950 2013 </div>
951 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 +
952 2028 <!-- Table -->
953 2029 <div class="mxchat-table-wrapper">
954 2030 <table class="mxchat-records-table">
955 2031 <thead>
@@ -956,47 +2032,100 @@
956 2032 <tr>
957 2033 <th><?php esc_html_e('ID', 'mxchat'); ?></th>
958 2034 <th><?php esc_html_e('Content', 'mxchat'); ?></th>
959 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; ?>
960 2039 <th><?php esc_html_e('Actions', 'mxchat'); ?></th>
961 2040 </tr>
962 2041 </thead>
963 2042 <tbody>
964 2043 <?php if ($prompts) : ?>
965 - <?php foreach ($prompts as $prompt) : ?>
966 - <tr id="prompt-<?php echo esc_attr($prompt->id); ?>">
967 - <td><?php echo esc_html($prompt->id); ?></td>
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>
968 2057 <td class="mxchat-content-cell">
969 2058 <div class="content-view">
970 - <?php echo wp_kses_post(wpautop(esc_textarea($prompt->article_content))); ?>
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 + ?>
971 2071 </div>
972 - <textarea class="content-edit" style="display:none;">
973 - <?php echo esc_textarea($prompt->article_content); ?>
974 - </textarea>
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; ?>
975 2078 </td>
976 2079 <td class="mxchat-url-cell">
977 - <?php if (!empty($prompt->source_url)) : ?>
978 - <a href="<?php echo esc_url($prompt->source_url); ?>" target="_blank">
979 - <span class="dashicons dashicons-external"></span>
980 - <?php esc_html_e('View Source', 'mxchat'); ?>
981 - </a>
982 - <?php else : ?>
983 - <span class="mxchat-na"><?php esc_html_e('N/A', 'mxchat'); ?></span>
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); ?>" />
984 2093 <?php endif; ?>
985 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; ?>
986 2102 <td class="mxchat-actions-cell">
987 - <button class="mxchat-button-icon edit-button"
988 - data-id="<?php echo esc_attr($prompt->id); ?>">
989 - <span class="dashicons dashicons-edit"></span>
990 - </button>
991 - <button class="mxchat-button-icon save-button"
992 - data-id="<?php echo esc_attr($prompt->id); ?>"
993 - style="display:none;"
994 - data-nonce="<?php echo wp_create_nonce('mxchat_save_inline_nonce'); ?>">
995 - <span class="dashicons dashicons-saved"></span>
996 - </button>
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 -->
997 2125 <a href="<?php echo esc_url(admin_url(
998 2126 'admin-post.php?action=mxchat_delete_prompt&id=' . esc_attr($prompt->id)
2127 + . '&source=' . esc_attr($data_source)
999 2128 . '&_wpnonce=' . wp_create_nonce('mxchat_delete_prompt_nonce')
1000 2129 )); ?>"
1001 2130 class="mxchat-button-icon delete-button"
1002 2131 onclick="return confirm('<?php esc_attr_e('Are you sure you want to delete this entry?', 'mxchat'); ?>');">
@@ -1006,10 +2135,14 @@
1006 2135 </tr>
1007 2136 <?php endforeach; ?>
1008 2137 <?php else : ?>
1009 2138 <tr>
1010 - <td colspan="4" class="mxchat-no-records">
1011 - <?php esc_html_e('No knowledge base entries found.', 'mxchat'); ?>
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; ?>
1012 2145 </td>
1013 2146 </tr>
1014 2147 <?php endif; ?>
1015 2148 </tbody>
@@ -1021,14 +2154,609 @@
1021 2154 <?php echo wp_kses_post($page_links); ?>
1022 2155 </div>
1023 2156 <?php endif; ?>
1024 2157 </div>
2158 +
1025 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>
1026 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 +
1027 2512 <?php
1028 2513 }
1029 2514
1030 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 +}
1031 2759 public function mxchat_handle_delete_all_prompts() {
1032 2760 // Verify nonce
1033 2761 if (!isset($_POST['mxchat_delete_all_prompts_nonce']) || !wp_verify_nonce($_POST['mxchat_delete_all_prompts_nonce'], 'mxchat_delete_all_prompts_action')) {
1034 2762 wp_die(__('Nonce verification failed.', 'mxchat'));
@@ -1038,26 +2766,73 @@
1038 2766 if (!current_user_can('manage_options')) {
1039 2767 wp_die(__('You do not have sufficient permissions to delete all prompts.', 'mxchat'));
1040 2768 }
1041 2769
1042 - global $wpdb;
1043 - $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
2770 + $success = true;
2771 + $error_messages = array();
1044 2772
1045 - // Delete all prompts from the table
1046 - $wpdb->query("DELETE FROM {$table_name}");
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 + }
1047 2812
1048 2813 // Clear relevant cache
1049 2814 wp_cache_delete('all_prompts', 'mxchat_prompts');
1050 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 +
1051 2825 // Redirect back with a success message
1052 2826 $redirect_url = add_query_arg(array(
1053 2827 'page' => 'mxchat-prompts',
1054 - 'all_deleted' => 'true'
2828 + 'all_deleted' => $success ? 'true' : 'false'
1055 2829 ), admin_url('admin.php'));
1056 2830
1057 2831 wp_safe_redirect($redirect_url);
1058 2832 exit;
1059 2833 }
2834 +
1060 2835 public function mxchat_handle_delete_prompt() {
1061 2836 // Sanitize and validate nonce
1062 2837 $nonce = isset($_GET['_wpnonce']) ? sanitize_text_field(wp_unslash($_GET['_wpnonce'])) : '';
1063 2838 if (empty($nonce) || !wp_verify_nonce($nonce, 'mxchat_delete_prompt_nonce')) {
@@ -1062,242 +2837,260 @@
1062 2837 $nonce = isset($_GET['_wpnonce']) ? sanitize_text_field(wp_unslash($_GET['_wpnonce'])) : '';
1063 2838 if (empty($nonce) || !wp_verify_nonce($nonce, 'mxchat_delete_prompt_nonce')) {
1064 2839 wp_die(esc_html__('Nonce verification failed.', 'mxchat'));
1065 2840 }
1066 -
2841 +
1067 2842 // Check permissions
1068 2843 if (!current_user_can('manage_options')) {
1069 2844 wp_die(esc_html__('You do not have sufficient permissions to delete prompts.', 'mxchat'));
1070 2845 }
1071 -
1072 - // Validate and sanitize ID parameter
1073 - $id = isset($_GET['id']) ? intval($_GET['id']) : 0;
1074 - if ($id <= 0) {
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)) {
1075 2852 wp_die(esc_html__('Invalid prompt ID.', 'mxchat'));
1076 2853 }
1077 -
1078 - global $wpdb;
1079 - $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
1080 -
1081 - // Clear cache and delete prompt
1082 - wp_cache_delete('prompt_' . $id, 'mxchat_prompts');
1083 - $wpdb->delete($table_name, array('id' => $id), array('%d'));
1084 -
1085 - wp_safe_redirect(add_query_arg(array('page' => 'mxchat-prompts', 'deleted' => 'true'), admin_url('admin.php')));
1086 - exit;
1087 -}
1088 -public function mxchat_generate_embedding($text) {
1089 - $options = get_option('mxchat_options');
1090 - $api_key = $options['api_key'] ?? esc_html__('default_api_key', 'mxchat');
1091 -
1092 - $response = wp_remote_post('https://api.openai.com/v1/embeddings', array(
1093 - 'body' => wp_json_encode(array(
1094 - 'model' => esc_html__('text-embedding-ada-002', 'mxchat'),
1095 - 'input' => $text
1096 - )),
1097 - 'headers' => array(
1098 - 'Authorization' => 'Bearer ' . $api_key,
1099 - 'Content-Type' => esc_html__('application/json', 'mxchat')
1100 - ),
1101 - ));
1102 -
1103 - if (is_wp_error($response)) {
1104 - return null;
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'));
1105 2874 }
1106 -
1107 - $response_data = json_decode(wp_remote_retrieve_body($response), true);
1108 - return $response_data['data'][0]['embedding'] ?? null;
1109 - }
1110 -public function mxchat_delete_chat_history() {
1111 - if (!current_user_can('manage_options')) {
1112 - echo wp_json_encode(['error' => esc_html__('You do not have sufficient permissions.', 'mxchat')]);
1113 - wp_die();
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);
1114 2898 }
1115 -
1116 - check_ajax_referer('mxchat_delete_chat_history', 'security');
1117 -
2899 + } else {
2900 + error_log('[MXCHAT-DELETE] Deleting from WordPress database, ID: ' . $id);
2901 +
2902 + // Handle WordPress database deletion
1118 2903 global $wpdb;
1119 - $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
1120 -
1121 - if (isset($_POST['delete_session_ids']) && is_array($_POST['delete_session_ids'])) {
1122 - foreach ($_POST['delete_session_ids'] as $session_id) {
1123 - $session_id_sanitized = sanitize_text_field($session_id);
1124 -
1125 - // Clear relevant cache before deletion
1126 - $cache_key = 'chat_session_' . $session_id_sanitized;
1127 - wp_cache_delete($cache_key, 'mxchat_chat_sessions');
1128 -
1129 - // Perform the deletion
1130 - $wpdb->delete($table_name, ['session_id' => $session_id_sanitized]);
1131 - }
1132 -
1133 - // Optionally, clear a general cache if you have one
1134 - wp_cache_delete('all_chat_sessions', 'mxchat_chat_sessions');
1135 -
1136 - echo wp_json_encode(['success' => esc_html__('Selected chat sessions have been deleted.', 'mxchat')]);
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);
1137 2919 } else {
1138 - echo wp_json_encode(['error' => esc_html__('No chat sessions selected for deletion.', 'mxchat')]);
2920 + set_transient('mxchat_admin_notice_error',
2921 + esc_html__('Failed to delete entry from database.', 'mxchat'), 30);
1139 2922 }
1140 -
1141 - wp_die();
1142 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 +}
1143 2935
1144 -
1145 -public function mxchat_save_inline_prompt() {
1146 - // Check for nonce security
1147 - check_ajax_referer('mxchat_save_inline_nonce');
1148 -
1149 - // Verify permissions
1150 - if (!current_user_can('manage_options')) {
1151 - wp_send_json_error(esc_html__('Permission denied.', 'mxchat'));
1152 - return;
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 + );
1153 2951 }
1154 -
1155 - global $wpdb;
1156 - $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
1157 -
1158 - // Validate and sanitize input data
1159 - $prompt_id = isset($_POST['id']) ? absint($_POST['id']) : 0;
1160 - $article_content = isset($_POST['article_content']) ? sanitize_textarea_field($_POST['article_content']) : '';
1161 - $article_url = isset($_POST['article_url']) ? esc_url_raw($_POST['article_url']) : '';
1162 -
1163 - if ($prompt_id > 0 && !empty($article_content)) {
1164 - // Re-generate the embedding vector for the updated content
1165 - $embedding_vector = $this->mxchat_generate_embedding($article_content);
1166 -
1167 - if (is_array($embedding_vector)) {
1168 - // Serialize the embedding vector before storing it
1169 - $embedding_vector_serialized = serialize($embedding_vector);
1170 -
1171 - // Update the prompt in the database
1172 - $updated = $wpdb->update(
1173 - $table_name,
1174 - array(
1175 - 'article_content' => $article_content,
1176 - 'embedding_vector' => $embedding_vector_serialized,
1177 - 'source_url' => $article_url,
1178 - ),
1179 - array('id' => $prompt_id),
1180 - array('%s', '%s', '%s'),
1181 - array('%d')
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'
1182 2975 );
1183 -
1184 - if ($updated !== false) {
1185 - wp_send_json_success();
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);
1186 2988 } else {
1187 - wp_send_json_error(esc_html__('Database update failed.', 'mxchat'));
2989 + $failed_batches++;
2990 + error_log('Failed to delete Pinecone batch: ' . $result['message']);
1188 2991 }
1189 - } else {
1190 - wp_send_json_error(esc_html__('Embedding generation failed.', 'mxchat'));
1191 2992 }
1192 - } else {
1193 - wp_send_json_error(esc_html__('Invalid data.', 'mxchat'));
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 + );
1194 3012 }
1195 3013 }
1196 3014
1197 -
1198 -
1199 -// Add this method to handle post updates
1200 -public function handle_post_update($post_id, $post, $update) {
1201 - // Basic validation checks
1202 - if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE || wp_is_post_revision($post_id)) {
1203 - return;
1204 - }
1205 -
1206 - // Only process published content
1207 - if (!in_array($post->post_status, array('publish'))) {
1208 - return;
1209 - }
1210 -
1211 - // Check if sync is enabled for this post type
1212 - $post_type = $post->post_type;
1213 - if (!in_array($post_type, array('post', 'page'))) {
1214 - return;
1215 - }
1216 -
1217 - $sync_option = ($post_type === 'post') ? 'mxchat_auto_sync_posts' : 'mxchat_auto_sync_pages';
1218 - if (get_option($sync_option) != '1') {
1219 - return;
1220 - }
1221 -
1222 - // Prepare content and URL
1223 - $content = wp_strip_all_tags($post->post_content);
1224 - $url = get_permalink($post_id);
1225 -
1226 - // Generate embedding vector
1227 - $embedding_vector = $this->mxchat_generate_embedding($content);
1228 - if (!$embedding_vector) {
1229 - return;
1230 - }
1231 -
1232 - // Check for Pinecone addon and its settings
1233 - $pinecone_settings = get_option('mxchat_pinecone_addon_options');
1234 - $use_pinecone = false;
1235 -
1236 - if ($pinecone_settings && is_array($pinecone_settings)) {
1237 - // Check if Pinecone is enabled and all required settings are present
1238 - $use_pinecone = (
1239 - isset($pinecone_settings['mxchat_use_pinecone']) &&
1240 - $pinecone_settings['mxchat_use_pinecone'] === '1' &&
1241 - !empty($pinecone_settings['mxchat_pinecone_api_key']) &&
1242 - !empty($pinecone_settings['mxchat_pinecone_host']) &&
1243 - !empty($pinecone_settings['mxchat_pinecone_index']) &&
1244 - !empty($pinecone_settings['mxchat_pinecone_environment'])
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()
1245 3049 );
1246 3050 }
1247 -
1248 - if ($use_pinecone) {
1249 - // Use Pinecone
1250 - $pinecone_result = $this->store_in_pinecone_main(
1251 - $embedding_vector,
1252 - $content,
1253 - $url,
1254 - $pinecone_settings['mxchat_pinecone_api_key'],
1255 - $pinecone_settings['mxchat_pinecone_environment'],
1256 - $pinecone_settings['mxchat_pinecone_index']
1257 - );
1258 -
1259 - if (!$pinecone_result['success']) {
1260 - //error_log('MXChat: Pinecone sync failed - falling back to WordPress DB');
1261 - $this->store_in_wordpress_db($content, $url, $embedding_vector);
1262 - }
1263 - } else {
1264 - // Fallback to WordPress DB storage
1265 - $this->store_in_wordpress_db($content, $url, $embedding_vector);
1266 - }
1267 -}
1268 -// Modified storage function to add type field
1269 -private function store_in_pinecone_main($embedding_vector, $content, $url, $api_key, $environment, $index_name, $vector_id = null) {
1270 - $vector_id = $vector_id ?: md5($url);
1271 - $options = get_option('mxchat_pinecone_addon_options');
1272 - $host = $options['mxchat_pinecone_host'] ?? '';
1273 -
1274 - if (empty($host)) {
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);
1275 3059 return array(
1276 3060 'success' => false,
1277 - 'message' => 'Pinecone host is not configured. Please set the host in your settings.'
3061 + 'message' => sprintf(
3062 + 'Pinecone API error (HTTP %d): %s',
3063 + $response_code,
3064 + $response_body
3065 + )
1278 3066 );
1279 3067 }
3068 +
3069 + return array(
3070 + 'success' => true,
3071 + 'message' => 'Batch deleted successfully from Pinecone'
3072 + );
3073 +}
1280 3074
1281 - // Determine if this is a product URL
1282 - $is_product = (strpos($url, '/product/') !== false || strpos($url, '/shop/') !== false);
1283 -
1284 - $api_endpoint = "https://{$host}/vectors/upsert";
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
1285 3088 $request_body = array(
1286 - 'vectors' => array(
1287 - array(
1288 - 'id' => $vector_id,
1289 - 'values' => $embedding_vector,
1290 - 'metadata' => array(
1291 - 'text' => $content,
1292 - 'source_url' => $url,
1293 - 'type' => $is_product ? 'product' : 'content',
1294 - 'last_updated' => time()
1295 - )
1296 - )
1297 - )
3089 + 'ids' => array($vector_id)
1298 3090 );
1299 -
3091 +
3092 + // Make the deletion request
1300 3093 $response = wp_remote_post($api_endpoint, array(
1301 3094 'headers' => array(
1302 3095 'Api-Key' => $api_key,
1303 3096 'accept' => 'application/json',
@@ -1304,11 +3097,12 @@
1304 3097 'content-type' => 'application/json'
1305 3098 ),
1306 3099 'body' => wp_json_encode($request_body),
1307 3100 'timeout' => 30,
1308 - 'data_format' => 'body'
3101 + 'method' => 'POST'
1309 3102 ));
1310 -
3103 +
3104 + // Handle WordPress HTTP API errors
1311 3105 if (is_wp_error($response)) {
1312 3106 return array(
1313 3107 'success' => false,
1314 3108 'message' => $response->get_error_message()
@@ -1313,206 +3107,448 @@
1313 3107 'success' => false,
1314 3108 'message' => $response->get_error_message()
1315 3109 );
1316 3110 }
1317 -
3111 +
3112 + // Check response status
1318 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
1319 3117 if ($response_code !== 200) {
1320 - $body = wp_remote_retrieve_body($response);
3118 + error_log('Pinecone deletion failed: HTTP ' . $response_code . ' - ' . $response_body);
1321 3119 return array(
1322 3120 'success' => false,
1323 3121 'message' => sprintf(
1324 3122 'Pinecone API error (HTTP %d): %s',
1325 3123 $response_code,
1326 - $body
3124 + $response_body
1327 3125 )
1328 3126 );
1329 3127 }
1330 -
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 +
1331 3135 return array(
1332 3136 'success' => true,
1333 - 'message' => 'Successfully stored in Pinecone'
3137 + 'message' => 'Vector deleted successfully from Pinecone'
1334 3138 );
1335 3139 }
1336 -// Helper method for WordPress DB storage
1337 -private function store_in_wordpress_db($content, $url, $embedding_vector) {
1338 - global $wpdb;
1339 - $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
1340 3140
1341 - $existing_entry = $wpdb->get_row(
1342 - $wpdb->prepare("SELECT id FROM $table_name WHERE source_url = %s", $url)
1343 - );
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 +}
1344 3154
1345 - if ($existing_entry) {
1346 - $wpdb->update(
1347 - $table_name,
1348 - array(
1349 - 'article_content' => $content,
1350 - 'embedding_vector' => serialize($embedding_vector),
1351 - 'timestamp' => current_time('mysql')
1352 - ),
1353 - array('id' => $existing_entry->id),
1354 - array('%s', '%s', '%s')
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 + )
1355 3257 );
3258 +
3259 + // Set output dimensionality to 1536 for consistency with other models
3260 + $request_body['outputDimensionality'] = 1536;
1356 3261 } else {
1357 - $wpdb->insert(
1358 - $table_name,
1359 - array(
1360 - 'article_content' => $content,
1361 - 'source_url' => $url,
1362 - 'embedding_vector' => serialize($embedding_vector),
1363 - 'timestamp' => current_time('mysql')
1364 - ),
1365 - array('%s', '%s', '%s', '%s')
3262 + // OpenAI/Voyage API format
3263 + $request_body = array(
3264 + 'model' => $selected_model,
3265 + 'input' => $text
1366 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 + }
1367 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 + }
1368 3406 }
1369 -/**
1370 - * Handle deletion of posts and pages from both Pinecone and WordPress DB
1371 - *
1372 - * @param int $post_id The ID of the post being deleted
1373 - * @return void
1374 - */
1375 -public function mxchat_handle_post_delete($post_id) {
1376 - // Get post data before it's deleted
1377 - $post = get_post($post_id);
1378 3407
1379 - // Basic validation
1380 - if (!$post || wp_is_post_revision($post_id)) {
1381 - return;
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();
1382 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 +}
1383 3455
1384 - // Check post type
1385 - $post_type = $post->post_type;
1386 - if (!in_array($post_type, array('post', 'page'))) {
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'));
1387 3463 return;
1388 3464 }
1389 3465
1390 - // Check if sync is enabled for this post type
1391 - $sync_option = ($post_type === 'post') ? 'mxchat_auto_sync_posts' : 'mxchat_auto_sync_pages';
1392 - if (get_option($sync_option) != '1') {
1393 - return;
1394 - }
3466 + global $wpdb;
3467 + $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
1395 3468
1396 - // Get the URL before post is deleted
1397 - $source_url = get_permalink($post_id);
1398 - if (!$source_url) {
1399 - //error_log('MXChat: Failed to get permalink for post ' . $post_id);
1400 - return;
1401 - }
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']) : '';
1402 3473
1403 - // Check for Pinecone addon and its settings
1404 - $pinecone_settings = get_option('mxchat_pinecone_addon_options');
1405 - $use_pinecone = false;
1406 - if ($pinecone_settings && is_array($pinecone_settings)) {
1407 - $use_pinecone = (
1408 - isset($pinecone_settings['mxchat_use_pinecone']) &&
1409 - $pinecone_settings['mxchat_use_pinecone'] === '1' &&
1410 - !empty($pinecone_settings['mxchat_pinecone_api_key']) &&
1411 - !empty($pinecone_settings['mxchat_pinecone_host']) &&
1412 - !empty($pinecone_settings['mxchat_pinecone_index']) &&
1413 - !empty($pinecone_settings['mxchat_pinecone_environment'])
1414 - );
1415 - }
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);
1416 3477
1417 - $deletion_successful = false;
3478 + if (is_array($embedding_vector)) {
3479 + // Serialize the embedding vector before storing it
3480 + $embedding_vector_serialized = serialize($embedding_vector);
1418 3481
1419 - if ($use_pinecone) {
1420 - try {
1421 - $pinecone_result = $this->delete_from_pinecone(
1422 - array($source_url),
1423 - $pinecone_settings['mxchat_pinecone_api_key'],
1424 - $pinecone_settings['mxchat_pinecone_environment'],
1425 - $pinecone_settings['mxchat_pinecone_index']
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')
1426 3493 );
1427 3494
1428 - if (!$pinecone_result['success']) {
1429 - //error_log('MXChat: Pinecone deletion failed for URL: ' . $source_url . ' - ' . $pinecone_result['message']);
3495 + if ($updated !== false) {
3496 + wp_send_json_success();
1430 3497 } else {
1431 - $deletion_successful = true;
3498 + wp_send_json_error(esc_html__('Database update failed.', 'mxchat'));
1432 3499 }
1433 - } catch (Exception $e) {
1434 - //error_log('MXChat: Exception during Pinecone deletion - ' . $e->getMessage());
1435 - }
1436 - }
1437 -
1438 - // Always attempt WordPress DB deletion, regardless of Pinecone status
1439 - try {
1440 - global $wpdb;
1441 - $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
1442 -
1443 - $result = $wpdb->delete(
1444 - $table_name,
1445 - array('source_url' => $source_url),
1446 - array('%s')
1447 - );
1448 -
1449 - if ($result === false) {
1450 - //error_log('MXChat: WordPress DB deletion failed for URL: ' . $source_url);
1451 3500 } else {
1452 - $deletion_successful = true;
3501 + wp_send_json_error(esc_html__('Embedding generation failed.', 'mxchat'));
1453 3502 }
1454 - } catch (Exception $e) {
1455 - //error_log('MXChat: Exception during WordPress DB deletion - ' . $e->getMessage());
3503 + } else {
3504 + wp_send_json_error(esc_html__('Invalid data.', 'mxchat'));
1456 3505 }
1457 -
1458 - if (!$deletion_successful) {
1459 - //error_log('MXChat: Complete deletion failure for post ID: ' . $post_id . ' URL: ' . $source_url);
1460 - }
1461 3506 }
1462 3507
1463 -
1464 3508 public function mxchat_handle_content_submission() {
1465 3509 // Check if the form was submitted and the user has permission.
1466 3510 if (!isset($_POST['submit_content']) || !current_user_can('manage_options')) {
1467 3511 return;
1468 3512 }
1469 -
3513 +
1470 3514 // Verify the nonce.
1471 3515 $nonce = isset($_POST['mxchat_submit_content_nonce']) ? sanitize_text_field(wp_unslash($_POST['mxchat_submit_content_nonce'])) : '';
1472 3516 if (!wp_verify_nonce($nonce, 'mxchat_submit_content_action')) {
1473 3517 wp_die(esc_html__('Nonce verification failed.', 'mxchat'));
1474 3518 }
1475 -
3519 +
1476 3520 // Sanitize the inputs.
1477 3521 $article_content = sanitize_textarea_field($_POST['article_content']);
1478 3522 $article_url = isset($_POST['article_url']) ? esc_url_raw($_POST['article_url']) : '';
1479 -
1480 - // Generate the embedding vector.
1481 - $embedding_vector = $this->mxchat_generate_embedding($article_content);
1482 - if (!is_array($embedding_vector)) {
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)) {
1483 3537 set_transient('mxchat_admin_notice_error',
1484 - esc_html__('Embedding generation failed. Please ensure your API key is correct and try again.', 'mxchat'),
3538 + esc_html__('API key is not configured. Please add your API key in the settings before submitting content.', 'mxchat'),
1485 3539 30
1486 3540 );
1487 3541 wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
1488 3542 exit;
1489 3543 }
1490 -
1491 - // Store in the WordPress database.
1492 - global $wpdb;
1493 - $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
1494 -
1495 - // Ensure the source_url column exists.
1496 - if ($wpdb->get_var($wpdb->prepare("SHOW COLUMNS FROM {$table_name} LIKE %s", 'source_url')) != 'source_url') {
1497 - $wpdb->query($wpdb->prepare("ALTER TABLE {$table_name} ADD source_url VARCHAR(255) DEFAULT ''"));
1498 - }
1499 -
1500 - // Serialize and store the embedding vector.
1501 - $embedding_vector_serialized = serialize($embedding_vector);
1502 - $inserted = $wpdb->insert(
1503 - $table_name,
1504 - array(
1505 - 'article_content' => $article_content,
1506 - 'embedding_vector' => $embedding_vector_serialized,
1507 - 'source_url' => $article_url,
1508 - ),
1509 - array('%s', '%s', '%s')
1510 - );
1511 -
1512 - if ($inserted === false) {
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)) {
1513 3549 set_transient('mxchat_admin_notice_error',
1514 - esc_html__('Error inserting content into the database. Please try again.', 'mxchat'),
3550 + esc_html__('Error storing content: ', 'mxchat') . $result->get_error_message(),
1515 3551 30
1516 3552 );
1517 3553 } else {
1518 3554 set_transient('mxchat_admin_notice_success',
@@ -1519,9 +3555,9 @@
1519 3555 esc_html__('Content successfully submitted!', 'mxchat'),
1520 3556 30
1521 3557 );
1522 3558 }
1523 -
3559 +
1524 3560 wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
1525 3561 exit;
1526 3562 }
1527 3563
@@ -1633,31 +3669,57 @@
1633 3669 $start_page = absint($status['processed_pages']);
1634 3670 $end_page = min($start_page + $batch_size, $total_pages);
1635 3671
1636 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 + }
1637 3678
1638 3679 for ($i = $start_page; $i < $end_page; $i++) {
1639 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 +
1640 3688 $sanitized_content = $instance->mxchat_sanitize_content_for_api($text); // Call via instance
1641 3689
1642 - if (!empty($sanitized_content)) {
1643 - $embedding_vector = $instance->mxchat_generate_embedding($sanitized_content); // Call via instance
1644 - if (is_array($embedding_vector)) {
1645 - $metadata = array(
1646 - 'document_type' => 'pdf',
1647 - 'total_pages' => $total_pages,
1648 - 'current_page' => $i + 1,
1649 - 'prev_page' => $i > 0 ? $i : null,
1650 - 'next_page' => $i < ($total_pages - 1) ? $i + 2 : null,
1651 - 'source_url' => $pdf_url
1652 - );
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 + }
1653 3695
1654 - $content_with_metadata = wp_json_encode($metadata) . "\n---\n" . $sanitized_content;
1655 - $page_url = esc_url($pdf_url . "#page=" . ($i + 1));
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 + );
1656 3713
1657 - $options = get_option('mxchat_options');
1658 - MxChat_Utils::submit_content_to_db($content_with_metadata, $page_url, $options['api_key']);
1659 - }
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()));
1660 3722 }
1661 3723
1662 3724 // Update progress with sanitized data
1663 3725 $status['processed_pages'] = absint($i + 1);
@@ -1683,11 +3745,30 @@
1683 3745 }
1684 3746 }
1685 3747
1686 3748 } catch (\Exception $e) {
1687 - $status['status'] = 'error';
1688 - $status['error'] = sanitize_text_field($e->getMessage());
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 +
1689 3769 set_transient($status_key, array_map('sanitize_text_field', $status), DAY_IN_SECONDS);
3770 +
1690 3771 if (file_exists($pdf_path)) {
1691 3772 wp_delete_file($pdf_path);
1692 3773 }
1693 3774 }
@@ -1699,29 +3780,57 @@
1699 3780 if (!$status || !is_array($status)) {
1700 3781 return false;
1701 3782 }
1702 3783
1703 - return array(
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(
1704 3798 'total_pages' => absint($status['total_pages']),
1705 3799 'processed_pages' => absint($status['processed_pages']),
1706 3800 'percentage' => ($status['total_pages'] > 0)
1707 - ? round((absint($status['processed_pages']) / absint($status['total_pages'])) * 100)
1708 - : 0,
3801 + ? round((absint($status['processed_pages']) / absint($status['total_pages'])) * 100)
3802 + : 0,
1709 3803 'status' => sanitize_text_field($status['status']),
1710 3804 'last_update' => human_time_diff(absint($status['last_update']), time()) . ' ' . esc_html__('ago', 'mxchat')
1711 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;
1712 3813 }
3814 +
3815 +
1713 3816 public function mxchat_handle_sitemap_submission() {
3817 + // Start logging the submission process
3818 + //error_log('[MXCHAT-URL] ===== Starting URL submission process =====');
3819 +
1714 3820 // Check if the form was submitted and verify permissions
1715 3821 if (!isset($_POST['submit_sitemap']) || !current_user_can('manage_options')) {
3822 + //error_log('[MXCHAT-URL] Error: Unauthorized access or form not submitted properly');
1716 3823 wp_die(esc_html__('Unauthorized access', 'mxchat'));
1717 3824 }
1718 3825
1719 3826 // Verify nonce
3827 + //error_log('[MXCHAT-URL] Verifying nonce');
1720 3828 check_admin_referer('mxchat_submit_sitemap_action', 'mxchat_submit_sitemap_nonce');
1721 3829
1722 3830 // Validate URL
1723 3831 if (!isset($_POST['sitemap_url']) || empty($_POST['sitemap_url'])) {
3832 + //error_log('[MXCHAT-URL] Error: Empty or missing URL');
1724 3833 set_transient('mxchat_admin_notice_error',
1725 3834 esc_html__('Please provide a valid URL.', 'mxchat'),
1726 3835 30
1727 3836 );
@@ -1729,12 +3838,43 @@
1729 3838 exit;
1730 3839 }
1731 3840
1732 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');
1733 3872 $response = wp_remote_get($submitted_url, array('timeout' => 30));
1734 3873
1735 3874 if (is_wp_error($response) || wp_remote_retrieve_response_code($response) !== 200) {
1736 - $error_message = is_wp_error($response) ? $response->get_error_message() : __('Failed to fetch URL', 'mxchat');
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);
1737 3877 set_transient('mxchat_admin_notice_error',
1738 3878 sprintf(
1739 3879 esc_html__('Failed to fetch the URL: %s', 'mxchat'),
1740 3880 esc_html($error_message)
@@ -1745,11 +3885,13 @@
1745 3885 exit;
1746 3886 }
1747 3887
1748 3888 $content_type = wp_remote_retrieve_header($response, 'content-type');
3889 + //error_log('[MXCHAT-URL] Content type: ' . $content_type);
1749 3890 $body_content = wp_remote_retrieve_body($response);
1750 3891
1751 3892 if (empty($body_content)) {
3893 + //error_log('[MXCHAT-URL] Error: Empty response body');
1752 3894 set_transient('mxchat_admin_notice_error',
1753 3895 esc_html__('Empty response received from URL.', 'mxchat'),
1754 3896 30
1755 3897 );
@@ -1755,12 +3897,15 @@
1755 3897 );
1756 3898 wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
1757 3899 exit;
1758 3900 }
3901 + //error_log('[MXCHAT-URL] Retrieved body content length: ' . strlen($body_content) . ' bytes');
1759 3902
1760 3903 // Handle PDF URL
1761 3904 if ($this->is_pdf_url($submitted_url, $response)) {
3905 + //error_log('[MXCHAT-URL] Detected PDF URL, handling PDF for knowledge base');
1762 3906 $result = $this->handle_pdf_for_knowledge_base($submitted_url, $response);
3907 + //error_log('[MXCHAT-URL] PDF handling result: ' . $result);
1763 3908
1764 3909 if ($result === 'scheduled') {
1765 3910 set_transient(
1766 3911 'mxchat_last_pdf_url',
@@ -1772,9 +3917,9 @@
1772 3917 30
1773 3918 );
1774 3919 } else {
1775 3920 set_transient('mxchat_admin_notice_error',
1776 - esc_html__('Failed to start PDF processing. Please try again.', 'mxchat'),
3921 + esc_html__('Failed to start PDF processing: ', 'mxchat') . esc_html($result),
1777 3922 30
1778 3923 );
1779 3924 }
1780 3925
@@ -1783,8 +3928,9 @@
1783 3928 }
1784 3929
1785 3930 // Handle Sitemap XML
1786 3931 if (strpos($content_type, 'xml') !== false || strpos($body_content, '<urlset') !== false) {
3932 + //error_log('[MXCHAT-URL] Detected XML content, processing as sitemap');
1787 3933 libxml_use_internal_errors(true);
1788 3934 $xml = simplexml_load_string($body_content);
1789 3935 $xml_errors = libxml_get_errors();
1790 3936 libxml_clear_errors();
@@ -1789,8 +3935,15 @@
1789 3935 $xml_errors = libxml_get_errors();
1790 3936 libxml_clear_errors();
1791 3937
1792 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 +
1793 3946 set_transient('mxchat_admin_notice_error',
1794 3947 esc_html__('Invalid sitemap XML. Please provide a valid sitemap.', 'mxchat'),
1795 3948 30
1796 3949 );
@@ -1797,9 +3950,11 @@
1797 3950 wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
1798 3951 exit;
1799 3952 }
1800 3953
3954 + //error_log('[MXCHAT-URL] Valid XML found, handling sitemap for knowledge base');
1801 3955 $result = $this->handle_sitemap_for_knowledge_base($xml, $submitted_url);
3956 + //error_log('[MXCHAT-URL] Sitemap handling result: ' . $result);
1802 3957
1803 3958 if ($result === 'scheduled') {
1804 3959 set_transient(
1805 3960 'mxchat_last_sitemap_url',
@@ -1810,10 +3965,12 @@
1810 3965 esc_html__('Sitemap processing has started in the background. You can check the progress in the Knowledge Base section.', 'mxchat'),
1811 3966 30
1812 3967 );
1813 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
1814 3971 set_transient('mxchat_admin_notice_error',
1815 - esc_html__('Failed to start sitemap processing. Please try again.', 'mxchat'),
3972 + esc_html__('Failed to start sitemap processing. Please check the status below for details.', 'mxchat'),
1816 3973 30
1817 3974 );
1818 3975 }
1819 3976
@@ -1821,42 +3978,142 @@
1821 3978 exit;
1822 3979 }
1823 3980
1824 3981 // Handle Regular URL
3982 + //error_log('[MXCHAT-URL] Processing as regular webpage');
1825 3983 $page_content = $this->mxchat_extract_main_content($body_content);
3984 + //error_log('[MXCHAT-URL] Extracted content length: ' . strlen($page_content) . ' bytes');
3985 +
1826 3986 $sanitized_content = $this->mxchat_sanitize_content_for_api($page_content);
3987 + //error_log('[MXCHAT-URL] Sanitized content length: ' . strlen($sanitized_content) . ' bytes');
1827 3988
1828 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
1829 3993 set_transient('mxchat_admin_notice_error',
1830 3994 esc_html__('No valid content found on the provided URL.', 'mxchat'),
1831 3995 30
1832 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 +
1833 4006 wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
1834 4007 exit;
1835 4008 }
1836 4009
4010 + //error_log('[MXCHAT-URL] Generating embedding for content');
1837 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 +
1838 4034 if (is_array($embedding_vector)) {
1839 - MxChat_Utils::submit_content_to_db(
4035 + //error_log('[MXCHAT-URL] Successfully generated embedding with ' . count($embedding_vector) . ' dimensions');
4036 +
4037 + $db_result = MxChat_Utils::submit_content_to_db(
1840 4038 $sanitized_content,
1841 4039 $submitted_url,
1842 - $this->options['api_key']
4040 + $api_key
1843 4041 );
1844 - set_transient('mxchat_admin_notice_success',
1845 - esc_html__('URL content successfully submitted!', 'mxchat'),
1846 - 30
1847 - );
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 +
1848 4079 } else {
1849 - set_transient('mxchat_admin_notice_error',
1850 - esc_html__('Failed to generate embedding for the URL content. Please check your API key and try again.', 'mxchat'),
1851 - 30
1852 - );
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);
1853 4094 }
1854 4095
4096 + //error_log('[MXCHAT-URL] ===== Completed URL submission process =====');
1855 4097 wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
1856 4098 exit;
1857 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 +}
1858 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');
1859 4116 if (!current_user_can('manage_options')) {
1860 4117 //error_log(esc_html__('Unauthorized sitemap processing attempt', 'mxchat'));
1861 4118 return false;
1862 4119 }
@@ -1867,8 +4124,40 @@
1867 4124 if (!$xml || !is_object($xml)) {
1868 4125 throw new Exception(__('Invalid XML object provided', 'mxchat'));
1869 4126 }
1870 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 +
1871 4160 $urls = [];
1872 4161 foreach ($xml->url as $url_element) {
1873 4162 $url = esc_url_raw((string)$url_element->loc);
1874 4163 if ($url) {
@@ -1905,10 +4194,33 @@
1905 4194
1906 4195 return __('scheduled', 'mxchat');
1907 4196
1908 4197 } catch (\Exception $e) {
1909 - //error_log(sprintf(esc_html__('Error preparing sitemap for processing: %s', 'mxchat'), esc_html($e->getMessage())));
1910 - return false;
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;
1911 4223 }
1912 4224 }
1913 4225 public static function process_sitemap_urls_cron($urls, $sitemap_url, $total_urls, $batch_size, $batch_pause) {
1914 4226 // Validate inputs
@@ -1928,8 +4240,13 @@
1928 4240 if (!$status || !is_array($status)) {
1929 4241 throw new Exception('Invalid status data retrieved from transient');
1930 4242 }
1931 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 +
1932 4249 $start_url = absint($status['processed_urls']);
1933 4250 $end_url = min($start_url + $batch_size, $total_urls);
1934 4251 $instance = new self();
1935 4252
@@ -1936,11 +4253,38 @@
1936 4253 // Track failures in batch
1937 4254 $batch_stats = [
1938 4255 'processed' => 0,
1939 4256 'failed' => 0,
1940 - 'last_error' => ''
4257 + 'last_error' => '',
4258 + 'embedding_errors' => 0 // Track specifically embedding errors
1941 4259 ];
1942 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 +
1943 4287 for ($i = $start_url; $i < $end_url; $i++) {
1944 4288 $page_url = esc_url_raw($urls[$i]);
1945 4289 $page_response = wp_remote_get($page_url);
1946 4290
@@ -1945,8 +4289,18 @@
1945 4289 $page_response = wp_remote_get($page_url);
1946 4290
1947 4291 if (is_wp_error($page_response) || wp_remote_retrieve_response_code($page_response) !== 200) {
1948 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 +
1949 4303 continue;
1950 4304 }
1951 4305
1952 4306 $page_html = wp_remote_retrieve_body($page_response);
@@ -1955,8 +4309,29 @@
1955 4309
1956 4310 if (!empty($sanitized_content)) {
1957 4311 $embedding_vector = $instance->mxchat_generate_embedding($sanitized_content);
1958 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)
1959 4334 if (is_array($embedding_vector)) {
1960 4335 $options = get_option('mxchat_options');
1961 4336 $submission_result = MxChat_Utils::submit_content_to_db($sanitized_content, $page_url, $options['api_key']);
1962 4337
@@ -1962,8 +4337,16 @@
1962 4337
1963 4338 if (is_wp_error($submission_result)) {
1964 4339 $batch_stats['failed']++;
1965 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 +
1966 4349 continue;
1967 4350 }
1968 4351
1969 4352 $batch_stats['processed']++;
@@ -1968,9 +4351,22 @@
1968 4351
1969 4352 $batch_stats['processed']++;
1970 4353 } else {
1971 4354 $batch_stats['failed']++;
1972 - $batch_stats['last_error'] = 'Failed to generate embedding. Please check OpenAI API key.';
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 + }
1973 4369 }
1974 4370 }
1975 4371
1976 4372 $status['processed_urls'] = absint($i + 1);
@@ -1977,9 +4373,14 @@
1977 4373 $status['last_update'] = time();
1978 4374 $status['failed_urls'] = absint($status['failed_urls'] ?? 0) + $batch_stats['failed'];
1979 4375 $status['last_error'] = $batch_stats['last_error'];
1980 4376
1981 - set_transient($status_key, array_map('sanitize_text_field', $status), DAY_IN_SECONDS);
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);
1982 4383 }
1983 4384
1984 4385 // If all URLs in this batch failed, stop processing
1985 4386 if ($batch_stats['processed'] === 0 && $batch_stats['failed'] > 0) {
@@ -1988,53 +4389,384 @@
1988 4389 'Processing stopped: %d consecutive failures. Last error: %s',
1989 4390 $batch_stats['failed'],
1990 4391 $batch_stats['last_error']
1991 4392 );
1992 - set_transient($status_key, array_map('sanitize_text_field', $status), DAY_IN_SECONDS);
4393 + set_transient($status_key, $status, DAY_IN_SECONDS);
1993 4394 return;
1994 4395 }
1995 4396
1996 - if ($end_url < $total_urls) {
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
1997 4410 wp_schedule_single_event(time() + $batch_pause, 'mxchat_process_sitemap_urls', array(
1998 - 'urls' => array_map('esc_url_raw', $urls),
4411 + 'urls' => $urls,
1999 4412 'sitemap_url' => $sitemap_url,
2000 4413 'total_urls' => $total_urls,
2001 4414 'batch_size' => $batch_size,
2002 4415 'batch_pause' => $batch_pause,
2003 4416 ));
2004 - } else {
2005 - $status['status'] = 'complete';
2006 - set_transient($status_key, array_map('sanitize_text_field', $status), DAY_IN_SECONDS);
2007 4417 }
2008 4418 } catch (\Exception $e) {
2009 4419 $status['status'] = 'error';
2010 - $status['error'] = sanitize_text_field($e->getMessage());
2011 - set_transient($status_key, array_map('sanitize_text_field', $status), DAY_IN_SECONDS);
4420 + $status['error'] = $e->getMessage();
4421 + set_transient($status_key, $status, DAY_IN_SECONDS);
2012 4422 }
2013 4423 }
2014 -
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 +}
2015 4668 public function get_sitemap_processing_status($sitemap_url) {
2016 4669 $sitemap_url = esc_url_raw($sitemap_url);
2017 - $status = get_transient(sanitize_key('mxchat_sitemap_status_' . md5($sitemap_url)));
2018 -
4670 + $status_key = sanitize_key('mxchat_sitemap_status_' . md5($sitemap_url));
4671 + $status = get_transient($status_key);
4672 +
2019 4673 if (!$status || !is_array($status)) {
2020 4674 return false;
2021 4675 }
2022 -
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 +
2023 4691 return array(
2024 4692 'total_urls' => absint($status['total_urls']),
2025 4693 'processed_urls' => absint($status['processed_urls']),
2026 4694 'failed_urls' => absint($status['failed_urls'] ?? 0),
2027 4695 'percentage' => ($status['total_urls'] > 0)
2028 - ? round((absint($status['processed_urls']) / absint($status['total_urls'])) * 100)
2029 - : 0,
4696 + ? round((absint($status['processed_urls']) / absint($status['total_urls'])) * 100)
4697 + : 0,
2030 4698 'status' => sanitize_text_field($status['status']),
2031 4699 'last_update' => human_time_diff(absint($status['last_update']), time()) . ' ' . esc_html__('ago', 'mxchat'),
2032 4700 'error' => isset($status['error']) ? sanitize_text_field($status['error']) : '',
2033 - 'last_error' => isset($status['last_error']) ? sanitize_text_field($status['last_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()
2034 4703 );
2035 4704 }
2036 -
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 +}
2037 4769 public function mxchat_stop_processing() {
2038 4770 // Verify permissions
2039 4771 if (!current_user_can('manage_options')) {
2040 4772 wp_die(esc_html__('Unauthorized access', 'mxchat'));
@@ -2070,27 +4802,898 @@
2070 4802 );
2071 4803 wp_safe_redirect(admin_url('admin.php?page=mxchat-prompts'));
2072 4804 exit;
2073 4805 }
2074 -private function mxchat_sanitize_content_for_api($content) {
2075 - // Remove script, style tags, and HTML comments
2076 - $content = preg_replace('/<script\b[^>]*>(.*?)<\/script>/is', '', $content);
2077 - $content = preg_replace('/<style\b[^>]*>(.*?)<\/style>/is', '', $content);
2078 - $content = preg_replace('/<!--(.|\s)*?-->/', '', $content);
2079 4806
2080 - // Remove all HTML tags and decode HTML entities
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
2081 5519 $content = wp_strip_all_tags($content);
2082 - $content = html_entity_decode($content, ENT_QUOTES | ENT_HTML5);
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 +}
2083 5581
2084 - // Trim and normalize whitespace
2085 - $content = trim(preg_replace('/\s+/', ' ', $content));
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);
2086 5591
2087 - return $content;
2088 -}
5592 + // Basic validation
5593 + if (!$post || wp_is_post_revision($post_id)) {
5594 + return;
5595 + }
2089 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 + }
2090 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 + }
2091 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';
2092 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 + */
2093 5696 public function mxchat_handle_product_change($post_id, $post, $update) {
2094 5697 if ($post->post_type !== 'product') {
2095 5698 return;
2096 5699 }
@@ -2104,66 +5707,82 @@
2104 5707 });
2105 5708 }
2106 5709 }
2107 5710
5711 +/**
5712 + * Store WooCommerce product embeddings
5713 + */
2108 5714 private function mxchat_store_product_embedding($product) {
2109 5715 if (!isset($this->options['enable_woocommerce_integration']) ||
2110 5716 !in_array($this->options['enable_woocommerce_integration'], ['1', 'on'])) {
2111 5717 return;
2112 5718 }
2113 -
5719 +
2114 5720 $source_url = get_permalink($product->get_id());
2115 -
2116 - // Build description
5721 +
5722 + // Build product content
5723 + $title = $product->get_name();
5724 + $description = $product->get_description();
5725 + $short_description = $product->get_short_description();
2117 5726 $regular_price = $product->get_regular_price();
2118 5727 $sale_price = $product->get_sale_price();
2119 - $description = $product->get_description() . "\n\n" .
2120 - esc_html__('Short Description:', 'mxchat') . " " . $product->get_short_description() . "\n" .
2121 - esc_html__('Price:', 'mxchat') . " " . $regular_price . "\n" .
2122 - esc_html__('Sale Price:', 'mxchat') . " " . ($sale_price ?: esc_html__('N/A', 'mxchat')) . "\n" .
2123 - esc_html__('SKU:', 'mxchat') . " " . $product->get_sku();
2124 -
2125 - // Generate embedding vector
2126 - $embedding_vector = $this->mxchat_generate_embedding($description);
2127 - if (!is_array($embedding_vector)) {
2128 - return;
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";
2129 5735 }
2130 -
2131 - // Check for Pinecone addon and its settings
2132 - $pinecone_settings = get_option('mxchat_pinecone_addon_options');
2133 - $use_pinecone = false;
2134 -
2135 - if ($pinecone_settings && is_array($pinecone_settings)) {
2136 - $use_pinecone = (
2137 - isset($pinecone_settings['mxchat_use_pinecone']) &&
2138 - $pinecone_settings['mxchat_use_pinecone'] === '1' &&
2139 - !empty($pinecone_settings['mxchat_pinecone_api_key']) &&
2140 - !empty($pinecone_settings['mxchat_pinecone_host']) &&
2141 - !empty($pinecone_settings['mxchat_pinecone_index']) &&
2142 - !empty($pinecone_settings['mxchat_pinecone_environment'])
2143 - );
5736 +
5737 + if (!empty($short_description)) {
5738 + $content .= "Short Description: " . wp_strip_all_tags($short_description) . "\n\n";
2144 5739 }
2145 -
2146 - if ($use_pinecone) {
2147 - $result = $this->store_in_pinecone_main(
2148 - $embedding_vector,
2149 - $description,
2150 - $source_url,
2151 - $pinecone_settings['mxchat_pinecone_api_key'],
2152 - $pinecone_settings['mxchat_pinecone_environment'],
2153 - $pinecone_settings['mxchat_pinecone_index']
2154 - );
2155 -
2156 - if (!$result['success']) {
2157 - // Fallback to WordPress DB if Pinecone storage fails
2158 - $this->store_in_wordpress_db($description, $source_url, $embedding_vector);
2159 - }
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'] ?? '';
2160 5759 } else {
2161 - // Use WordPress DB storage
2162 - $this->store_in_wordpress_db($description, $source_url, $embedding_vector);
5760 + $api_key = $options['api_key'] ?? '';
2163 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 + }
2164 5779 }
2165 5780
5781 +
5782 +/**
5783 + * Handle WooCommerce product deletion
5784 + */
2166 5785 public function mxchat_handle_product_delete($post_id) {
2167 5786 if (get_post_type($post_id) !== 'product') {
2168 5787 return;
2169 5788 }
@@ -2169,39 +5788,28 @@
2169 5788 }
2170 5789
2171 5790 $source_url = get_permalink($post_id);
2172 5791
2173 - // Check for Pinecone addon and its settings
2174 - $pinecone_settings = get_option('mxchat_pinecone_addon_options');
2175 - $use_pinecone = false;
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';
2176 5795
2177 - if ($pinecone_settings && is_array($pinecone_settings)) {
2178 - $use_pinecone = (
2179 - isset($pinecone_settings['mxchat_use_pinecone']) &&
2180 - $pinecone_settings['mxchat_use_pinecone'] === '1' &&
2181 - !empty($pinecone_settings['mxchat_pinecone_api_key']) &&
2182 - !empty($pinecone_settings['mxchat_pinecone_host']) &&
2183 - !empty($pinecone_settings['mxchat_pinecone_index']) &&
2184 - !empty($pinecone_settings['mxchat_pinecone_environment'])
2185 - );
2186 - }
2187 -
2188 - if ($use_pinecone) {
2189 - $this->delete_from_pinecone(
2190 - array($source_url),
2191 - $pinecone_settings['mxchat_pinecone_api_key'],
2192 - $pinecone_settings['mxchat_pinecone_environment'],
2193 - $pinecone_settings['mxchat_pinecone_index']
2194 - );
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);
2195 5799 } else {
2196 - // Fallback to WordPress DB deletion
5800 + // Delete from WordPress DB
2197 5801 global $wpdb;
2198 5802 $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
2199 - $wpdb->delete($table_name, array('source_url' => $source_url), array('%s'));
5803 +
5804 + $wpdb->delete(
5805 + $table_name,
5806 + array('source_url' => $source_url),
5807 + array('%s')
5808 + );
2200 5809 }
2201 5810 }
2202 5811
2203 -
2204 5812 /**
2205 5813 * Delete vectors from Pinecone by source URL
2206 5814 *
2207 5815 * @param array $urls Array of source URLs to delete
@@ -2290,139 +5898,8 @@
2290 5898 );
2291 5899 }
2292 5900
2293 5901
2294 -public function mxchat_fetch_chat_history() {
2295 - global $wpdb;
2296 - $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
2297 -
2298 - if (!current_user_can('manage_options')) {
2299 - wp_die(esc_html__('You do not have sufficient permissions to view this page.', 'mxchat'));
2300 - }
2301 -
2302 - // First get unique session IDs ordered by most recent message in each session
2303 - $session_ids = $wpdb->get_col(
2304 - $wpdb->prepare(
2305 - "SELECT DISTINCT session_id
2306 - FROM {$table_name}
2307 - GROUP BY session_id
2308 - ORDER BY MAX(timestamp) DESC"
2309 - )
2310 - );
2311 -
2312 - if (empty($session_ids)) {
2313 - wp_die(esc_html__('No chat history available.', 'mxchat'));
2314 - }
2315 -
2316 - ob_start();
2317 - echo '<div class="mxchat-transcript">';
2318 -
2319 - // Iterate through sessions from newest to oldest
2320 - foreach ($session_ids as $session_id) {
2321 - // Get the email associated with this session (if available)
2322 - $email = $wpdb->get_var(
2323 - $wpdb->prepare(
2324 - "SELECT user_email
2325 - FROM {$table_name}
2326 - WHERE session_id = %s AND user_email != ''
2327 - ORDER BY timestamp ASC
2328 - LIMIT 1",
2329 - $session_id
2330 - )
2331 - );
2332 -
2333 - // Debug: Check email retrieval
2334 - if (empty($email)) {
2335 - //error_log("No email found for session {$session_id}");
2336 - } else {
2337 - //error_log("Email for session {$session_id}: " . $email);
2338 - }
2339 -
2340 - // Get messages for this session ordered by timestamp
2341 - $messages = $wpdb->get_results(
2342 - $wpdb->prepare(
2343 - "SELECT * FROM {$table_name}
2344 - WHERE session_id = %s
2345 - ORDER BY timestamp ASC",
2346 - $session_id
2347 - )
2348 - );
2349 -
2350 - // Start session block
2351 - echo '<div class="mxchat-session">';
2352 - echo '<div class="mxchat-session-header">';
2353 - // Wrap checkbox and session ID in one block
2354 - echo '<div class="mxchat-session-id">';
2355 - echo '<input type="checkbox" name="delete_session_ids[]" value="' . esc_attr($session_id) . '"> ';
2356 - echo '<strong>' . esc_html__('Session ID:', 'mxchat') . '</strong> ' . esc_html($session_id);
2357 - echo '</div>';
2358 -
2359 - // Place email directly below the session ID block
2360 - if (!empty($email)) {
2361 - echo '<div class="mxchat-session-email">';
2362 - echo '<strong>' . esc_html__('Email:', 'mxchat') . '</strong> ' . esc_html($email);
2363 - echo '</div>';
2364 - }
2365 - echo '</div>';
2366 -
2367 - echo '<div class="mxchat-messages">';
2368 -
2369 - // Display messages for this session
2370 - foreach ($messages as $transcript) {
2371 - $formatted_timestamp = date_i18n('F j, Y g:i a', strtotime($transcript->timestamp));
2372 -
2373 - // Determine message styling
2374 - switch ($transcript->role) {
2375 - case 'assistant':
2376 - case 'bot':
2377 - $message_class = 'bot-message';
2378 - $display_role = esc_html__('Chatbot', 'mxchat');
2379 - break;
2380 - case 'user':
2381 - $message_class = 'user-message';
2382 - $display_role = !empty($transcript->user_identifier)
2383 - ? sanitize_text_field($transcript->user_identifier)
2384 - : esc_html__('User', 'mxchat');
2385 - break;
2386 - case 'agent':
2387 - $message_class = 'agent-message';
2388 - $display_role = esc_html__('Agent', 'mxchat');
2389 - break;
2390 - default:
2391 - $message_class = 'unknown-message';
2392 - $display_role = esc_html__('Unknown', 'mxchat');
2393 - }
2394 -
2395 - // Process message content
2396 - $message_content = wp_kses(
2397 - stripslashes($transcript->message),
2398 - [
2399 - 'b' => [], 'strong' => [], 'i' => [], 'em' => [], 'u' => [],
2400 - 'br' => [], 'p' => [], 'ul' => [], 'ol' => [], 'li' => [],
2401 - 'a' => ['href' => [], 'title' => []]
2402 - ]
2403 - );
2404 - $message_content = nl2br($message_content);
2405 -
2406 - // Render message
2407 - echo '<div class="mxchat-message ' . esc_attr($message_class) . '">';
2408 - echo '<div class="mxchat-message-header">' . esc_html($display_role) . '</div>';
2409 - echo '<div class="mxchat-message-content">' . $message_content . '</div>';
2410 - echo '<div class="mxchat-timestamp">' . esc_html($formatted_timestamp) . '</div>';
2411 - echo '</div>';
2412 - }
2413 -
2414 - // Close session block
2415 - echo '</div></div>';
2416 - }
2417 -
2418 - echo '</div>';
2419 - $output = ob_get_clean();
2420 - echo $output;
2421 - wp_die();
2422 -}
2423 -
2424 -
2425 5902 public function mxchat_create_activation_page() {
2426 5903 $license_status = get_option('mxchat_license_status', 'inactive');
2427 5904 $license_error = get_option('mxchat_license_error', '');
2428 5905 ?>
@@ -2478,9 +5955,9 @@
2478 5955 </div>
2479 5956 <?php
2480 5957 }
2481 5958
2482 -public function mxchat_intents_page_html() {
5959 +public function mxchat_actions_page_html() {
2483 5960 if (!current_user_can('manage_options')) {
2484 5961 return;
2485 5962 }
2486 5963
@@ -2493,9 +5970,9 @@
2493 5970
2494 5971 // Success message
2495 5972 if (isset($_GET['updated']) && $_GET['updated'] === 'true') {
2496 5973 echo '<div class="notice notice-success is-dismissible"><p>' .
2497 - esc_html__('Intent updated successfully.', 'mxchat') .
5974 + esc_html__('Action updated successfully.', 'mxchat') .
2498 5975 '</p></div>';
2499 5976 }
2500 5977
2501 5978 // Filtering logic
@@ -2516,10 +5993,10 @@
2516 5993 // Pagination
2517 5994 $total_intents = $wpdb->get_var("SELECT COUNT(*) FROM $table_name WHERE $where");
2518 5995 $total_pages = ceil($total_intents / $per_page);
2519 5996
2520 - // Get intents
2521 - $intents = $wpdb->get_results($wpdb->prepare(
5997 + // Get intents (now called actions)
5998 + $actions = $wpdb->get_results($wpdb->prepare(
2522 5999 "SELECT * FROM $table_name WHERE $where LIMIT %d OFFSET %d",
2523 6000 $per_page, $offset
2524 6001 ));
2525 6002
@@ -2524,441 +6001,619 @@
2524 6001 ));
2525 6002
2526 6003 // Get callbacks
2527 6004 $available_callbacks = $this->mxchat_get_available_callbacks();
2528 -
6005 +
2529 6006 ?>
2530 6007 <div class="wrap mxchat-wrapper">
2531 6008 <!-- Hero Section -->
2532 6009 <div class="mxchat-hero">
2533 6010 <h1 class="mxchat-main-title">
2534 - <span class="mxchat-gradient-text">Intent</span> Manager
6011 + <span class="mxchat-gradient-text">Actions</span> Manager
2535 6012 </h1>
2536 6013 <p class="mxchat-hero-subtitle">
2537 - <?php esc_html_e('Create and manage custom intents to enhance your chatbots understanding and response capabilities.', 'mxchat'); ?>
6014 + <?php esc_html_e('Create and manage custom actions to enhance your chatbot\'s capabilities.', 'mxchat'); ?>
2538 6015 </p>
2539 6016 </div>
2540 6017
2541 - <div class="mxchat-content">
2542 - <!-- Add Intent Card -->
2543 - <div class="mxchat-card">
2544 - <div class="mxchat-card-header">
2545 - <h2><?php esc_html_e('Add New Intent', 'mxchat'); ?></h2>
2546 - </div>
2547 -
2548 - <div class="mxchat-intent-documentation">
2549 - <p>
2550 - <?php esc_html_e('We highly encourage users to quickly read our ', 'mxchat'); ?>
2551 - <a href="https://mxchat.ai/documentation/#intents" target="_blank" rel="noopener noreferrer">
2552 - <?php esc_html_e('documentation', 'mxchat'); ?>
2553 - </a>
2554 - <?php esc_html_e(' to better understand intents. Some intents require setup in the Integration tab. If your intent is not triggering lower similarity threshold test.', 'mxchat'); ?>
2555 - </p>
2556 - </div>
2557 -
2558 - <form id="mxchat-add-intent-form" method="post"
2559 - action="<?php echo esc_url(admin_url('admin-post.php')); ?>">
2560 - <input type="hidden" name="action" value="mxchat_add_intent">
2561 - <?php wp_nonce_field('mxchat_add_intent_nonce'); ?>
2562 -
2563 - <div class="mxchat-form-group">
2564 - <label for="intent_label">
2565 - <?php esc_html_e('Intent Label (For your reference only)', 'mxchat'); ?>
2566 - </label>
2567 - <input name="intent_label" type="text" id="intent_label" required
2568 - class="mxchat-intent-input"
2569 - placeholder="<?php esc_attr_e('Example Email Capture: Newsletter Signup', 'mxchat'); ?>">
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); ?>">
2570 6028 </div>
2571 -
2572 - <div class="mxchat-form-group">
2573 - <label for="phrases">
2574 - <?php esc_html_e('Phrases (comma-separated)', 'mxchat'); ?>
2575 - </label>
2576 - <textarea name="phrases" id="phrases" rows="5" required
2577 - class="mxchat-intent-textarea"
2578 - placeholder="<?php esc_attr_e('Example Email Capture: sign me up, subscribe me, I want to join, add me to the newsletter, send me updates, keep me informed', 'mxchat'); ?>"></textarea>
2579 - </div>
2580 -
2581 - <div class="mxchat-form-group">
2582 - <label for="callback_function">
2583 - <?php esc_html_e('Callback Function', 'mxchat'); ?>
2584 - </label>
2585 - <select name="callback_function" id="callback_function"
2586 - class="mxchat-intent-select" required>
2587 - <option value="">
2588 - <?php esc_html_e('Select a Callback', 'mxchat'); ?>
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); ?>
2589 6036 </option>
2590 - <?php
2591 - $groups = $this->mxchat_get_available_callbacks(true);
2592 - foreach ($groups as $group_label => $group_callbacks) :
2593 - echo '<optgroup label="' . esc_attr($group_label) . '">';
2594 - foreach ($group_callbacks as $function => $data) :
2595 - $label = $data['label'];
2596 - $pro_only = $data['pro_only'];
2597 - $disabled = (!$this->is_activated && $pro_only) ? 'disabled' : '';
2598 - $label_suffix = (!$this->is_activated && $pro_only) ? ' (Pro Only)' : '';
2599 - ?>
2600 - <option value="<?php echo esc_attr($function); ?>"
2601 - <?php echo $disabled; ?>>
2602 - <?php echo esc_html($label . $label_suffix); ?>
2603 - </option>
2604 - <?php
2605 - endforeach;
2606 - echo '</optgroup>';
2607 - endforeach;
2608 - ?>
2609 - </select>
2610 - </div>
2611 -
2612 - <button type="submit" class="mxchat-button-primary">
2613 - <?php esc_html_e('Add Intent', 'mxchat'); ?>
6037 + <?php endforeach; ?>
6038 + </select>
6039 + <button type="submit" class="mxchat-button-secondary">
6040 + <?php esc_html_e('Filter', 'mxchat'); ?>
2614 6041 </button>
2615 6042 </form>
2616 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>
2617 6051
2618 - <!-- Manage Intents Card -->
2619 - <div class="mxchat-card">
2620 - <div class="mxchat-card-header">
2621 - <h2><?php esc_html_e('Manage Intents', 'mxchat'); ?></h2>
2622 - <div class="mxchat-header-actions">
2623 - <form method="get" class="mxchat-search-form">
2624 - <input type="hidden" name="page" value="mxchat-intents">
2625 - <div class="mxchat-search-group">
2626 - <span class="dashicons dashicons-search"></span>
2627 - <input type="text" name="s"
2628 - placeholder="<?php esc_attr_e('Search Intents', 'mxchat'); ?>"
2629 - value="<?php echo esc_attr($search_term); ?>">
2630 - <select name="callback_filter" class="mxchat-intent-filter">
2631 - <option value="">
2632 - <?php esc_html_e('All Callbacks', 'mxchat'); ?>
2633 - </option>
2634 - <?php foreach ($available_callbacks as $function => $callback_data) :
2635 - $label = $callback_data['label']; ?>
2636 - <option value="<?php echo esc_attr($function); ?>"
2637 - <?php selected($callback_filter, $function); ?>>
2638 - <?php echo esc_html($label); ?>
2639 - </option>
2640 - <?php endforeach; ?>
2641 - </select>
2642 - <button type="submit" class="mxchat-button-secondary">
2643 - <?php esc_html_e('Filter', 'mxchat'); ?>
2644 - </button>
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>
2645 6082 </div>
2646 - </form>
2647 - </div>
2648 - </div>
2649 -
2650 - <div class="mxchat-table-wrapper">
2651 - <table class="mxchat-records-table">
2652 - <thead>
2653 - <tr>
2654 - <th><?php esc_html_e('Intent Label', 'mxchat'); ?></th>
2655 - <th><?php esc_html_e('Phrases', 'mxchat'); ?></th>
2656 - <th><?php esc_html_e('Callback Function', 'mxchat'); ?></th>
2657 - <th><?php esc_html_e('Similarity Threshold', 'mxchat'); ?></th>
2658 - <th><?php esc_html_e('Actions', 'mxchat'); ?></th>
2659 - </tr>
2660 - </thead>
2661 - <tbody>
2662 - <?php if ($intents) :
2663 - foreach ($intents as $intent) :
2664 - $callback_function = $intent->callback_function;
2665 - $callback_label = isset($available_callbacks[$callback_function]['label'])
2666 - ? $available_callbacks[$callback_function]['label']
2667 - : $callback_function;
2668 - $threshold_value = isset($intent->similarity_threshold)
2669 - ? round($intent->similarity_threshold * 100)
2670 - : 85;
2671 -
2672 - // Check if this is a form intent (its intent_label starts with "Form ")
2673 - $is_form_intent = strpos($intent->intent_label, 'Form ') === 0;
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 + }
2674 6102 ?>
2675 - <tr<?php echo $is_form_intent ? ' class="mxchat-form-intent"' : ''; ?>>
2676 - <td>
2677 - <?php
2678 - if ($is_form_intent) {
2679 - // Attempt to extract the form ID from the intent_label.
2680 - preg_match('/Form (\d+)/', $intent->intent_label, $matches);
2681 - $form_id = isset($matches[1]) ? intval($matches[1]) : 0;
2682 - if ($form_id) {
2683 - global $wpdb;
2684 - $forms_table = $wpdb->prefix . 'mxchat_forms';
2685 - $form = $wpdb->get_row($wpdb->prepare("SELECT title FROM $forms_table WHERE id = %d", $form_id));
2686 - if ($form && !empty($form->title)) {
2687 - echo esc_html($form->title);
2688 - } else {
2689 - echo esc_html($intent->intent_label);
2690 - }
2691 - } else {
2692 - echo esc_html($intent->intent_label);
2693 - }
2694 - echo ' <span class="mxchat-badge">Form Intent</span>';
2695 - } else {
2696 - echo esc_html($intent->intent_label);
2697 - }
2698 - ?>
2699 - </td>
2700 - <td class="mxchat-content-cell">
2701 - <?php echo esc_html($intent->phrases); ?>
2702 - </td>
2703 - <td><?php echo esc_html($callback_label); ?></td>
2704 - <td>
2705 - <!-- Similarity threshold adjustment (shown for all intents) -->
2706 - <form method="post"
2707 - action="<?php echo esc_url(admin_url('admin-post.php')); ?>"
2708 - class="mxchat-threshold-form">
2709 - <?php wp_nonce_field('mxchat_update_intent_threshold_nonce'); ?>
2710 - <input type="hidden" name="action"
2711 - value="mxchat_update_intent_threshold">
2712 - <input type="hidden" name="intent_id"
2713 - value="<?php echo esc_attr($intent->id); ?>">
2714 - <div class="mxchat-slider-group">
2715 - <input type="range"
2716 - name="intent_threshold"
2717 - id="intent_threshold_<?php echo esc_attr($intent->id); ?>"
2718 - min="70"
2719 - max="95"
2720 - value="<?php echo esc_attr($threshold_value); ?>"
2721 - class="mxchat-intent-slider"
2722 - oninput="document.getElementById('threshold_output_<?php echo esc_attr($intent->id); ?>').value = this.value + '%'">
2723 - <output id="threshold_output_<?php echo esc_attr($intent->id); ?>"
2724 - class="mxchat-intent-output">
2725 - <?php echo esc_html($threshold_value); ?>%
2726 - </output>
2727 - </div>
2728 - <button type="submit" class="mxchat-button-icon">
2729 - <span class="dashicons dashicons-saved"></span>
2730 - </button>
2731 - </form>
2732 - </td>
2733 - <td class="mxchat-actions-cell">
2734 - <?php if (!$is_form_intent) : ?>
2735 - <!-- Standard intents: allow edit and delete -->
2736 - <button type="button"
2737 - class="mxchat-button-icon mxchat-edit-button"
2738 - data-intent-id="<?php echo esc_attr($intent->id); ?>"
2739 - data-phrases="<?php echo esc_attr($intent->phrases); ?>">
2740 - <span class="dashicons dashicons-edit"></span>
2741 - </button>
2742 - <form method="post"
2743 - action="<?php echo esc_url(admin_url('admin-post.php')); ?>"
2744 - class="mxchat-delete-form"
2745 - onsubmit="return confirm('<?php esc_attr_e('Are you sure you want to delete this intent?', 'mxchat'); ?>');">
2746 - <?php wp_nonce_field('mxchat_delete_intent_nonce'); ?>
2747 - <input type="hidden" name="action" value="mxchat_delete_intent">
2748 - <input type="hidden" name="intent_id"
2749 - value="<?php echo esc_attr($intent->id); ?>">
2750 - <button type="submit" class="mxchat-button-icon">
2751 - <span class="dashicons dashicons-trash"></span>
2752 - </button>
2753 - </form>
2754 - <?php else : ?>
2755 - <!-- Form intents: show manage in forms button -->
2756 - <?php
2757 - preg_match('/Form (\d+)/', $intent->intent_label, $matches);
2758 - $form_id = isset($matches[1]) ? $matches[1] : '';
2759 - ?>
2760 - <a href="<?php echo esc_url(admin_url('admin.php?page=mxchat-forms&action=edit&form_id=' . $form_id)); ?>"
2761 - class="mxchat-button-secondary">
2762 - <?php esc_html_e('Manage in Forms', 'mxchat'); ?>
2763 - </a>
2764 - <?php endif; ?>
2765 - </td>
2766 - </tr>
2767 - <?php endforeach;
2768 - else : ?>
2769 - <tr>
2770 - <td colspan="5" class="mxchat-no-records">
2771 - <?php esc_html_e('No intents found.', 'mxchat'); ?>
2772 - </td>
2773 - </tr>
2774 - <?php endif; ?>
2775 - </tbody>
2776 - </table>
2777 - </div>
2778 -
2779 - <?php if ($total_pages > 1) : ?>
2780 - <div class="mxchat-pagination">
2781 - <?php
2782 - echo paginate_links(array(
2783 - 'base' => add_query_arg('paged', '%#%'),
2784 - 'format' => '',
2785 - 'prev_text' => __('&laquo; Previous', 'mxchat'),
2786 - 'next_text' => __('Next &raquo;', 'mxchat'),
2787 - 'total' => $total_pages,
2788 - 'current' => $page
2789 - ));
2790 - ?>
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>
2791 6177 </div>
2792 6178 <?php endif; ?>
2793 6179 </div>
2794 6180 </div>
2795 -
2796 - <!-- Edit Modal -->
2797 -<div id="mxchat-edit-modal" class="mxchat-modal" style="display: none;">
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;">
2798 6200 <div class="mxchat-modal-content">
2799 6201 <span class="mxchat-modal-close">&times;</span>
2800 - <div class="mxchat-card-header">
2801 - <h2><?php esc_html_e('Edit Intent Phrases', 'mxchat'); ?></h2>
2802 - </div>
2803 6202
2804 - <form id="mxchat-edit-intent-form" method="post"
2805 - action="<?php echo esc_url(admin_url('admin-post.php')); ?>">
2806 - <?php wp_nonce_field('mxchat_edit_intent_nonce'); ?>
2807 - <input type="hidden" name="action" value="mxchat_edit_intent">
2808 - <input type="hidden" name="intent_id" id="edit_intent_id">
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="">
2809 6211
2810 - <div class="mxchat-form-group">
2811 - <label for="edit_phrases">
2812 - <?php esc_html_e('Phrases (comma-separated)', 'mxchat'); ?>
2813 - </label>
2814 - <textarea name="phrases"
2815 - id="edit_phrases"
2816 - rows="5"
2817 - class="mxchat-intent-textarea"
2818 - required></textarea>
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>
2819 6318 </div>
2820 6319
2821 - <div class="mxchat-modal-actions">
2822 - <button type="submit" class="mxchat-button-primary">
2823 - <?php esc_html_e('Update Phrases', 'mxchat'); ?>
2824 - </button>
2825 - <button type="button" class="mxchat-button-secondary mxchat-modal-cancel">
2826 - <?php esc_html_e('Cancel', 'mxchat'); ?>
2827 - </button>
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>
2828 6389 </div>
2829 6390 </form>
2830 6391 </div>
2831 6392 </div>
2832 6393
2833 - </div><!-- .mxchat-content -->
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>
2834 6400 </div><!-- .mxchat-wrapper -->
6401 + <?php
6402 +}
2835 6403
2836 - <div id="mxchat-intent-loading" class="mxchat-intent-loading" style="display: none;">
2837 - <div class="mxchat-intent-loading-spinner"></div>
2838 - <div class="mxchat-intent-loading-text">
2839 - <?php esc_html_e('Saving intent, please wait...', 'mxchat'); ?>
2840 - </div>
2841 - </div>
2842 -<?php
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 . '...';
2843 6421 }
2844 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 +}
2845 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 +}
2846 6485
2847 6486 /**
2848 - * Handle editing of intent phrases
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
2849 6512 *
2850 6513 * @since 1.0.0
2851 6514 * @return void
2852 6515 */
2853 -public function handle_edit_intent() {
2854 - // Verify nonce and user capabilities
2855 - if (!isset($_POST['_wpnonce']) || !wp_verify_nonce($_POST['_wpnonce'], 'mxchat_edit_intent_nonce')) {
2856 - wp_die(esc_html__('Security check failed.', 'mxchat'));
2857 - }
2858 -
6516 +public function mxchat_handle_edit_intent() {
6517 + // Security checks (nonce and permissions)
2859 6518 if (!current_user_can('manage_options')) {
2860 - wp_die(esc_html__('You do not have permission to perform this action.', 'mxchat'));
6519 + wp_die(esc_html__('Unauthorized user', 'mxchat'));
2861 6520 }
6521 + check_admin_referer('mxchat_edit_intent');
2862 6522
2863 - // Validate and sanitize input
6523 + // Get POST data
2864 6524 $intent_id = isset($_POST['intent_id']) ? absint($_POST['intent_id']) : 0;
2865 - if (!$intent_id) {
2866 - wp_die(esc_html__('Invalid intent ID.', 'mxchat'));
2867 - }
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
2868 6529
2869 - $phrases_input = isset($_POST['phrases']) ? sanitize_textarea_field($_POST['phrases']) : '';
2870 - if (empty($phrases_input)) {
2871 - wp_die(esc_html__('Phrases cannot be empty.', 'mxchat'));
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;
2872 6534 }
2873 6535
2874 - // Process phrases the same way as in intent creation
2875 6536 $phrases_array = array_map('sanitize_text_field', array_filter(array_map('trim', explode(',', $phrases_input))));
2876 -
2877 6537 if (empty($phrases_array)) {
2878 - wp_die(esc_html__('Please enter at least one valid phrase.', 'mxchat'));
6538 + $this->handle_embedding_error(__('Please enter at least one valid phrase.', 'mxchat'));
6539 + return;
2879 6540 }
2880 6541
2881 - // Generate embeddings and combine them
6542 + // Generate embeddings with improved error handling
2882 6543 $vectors = [];
6544 + $failed_phrases = [];
6545 +
2883 6546 foreach ($phrases_array as $phrase) {
2884 6547 $embedding_vector = $this->mxchat_generate_embedding($phrase, $this->options['api_key']);
2885 6548 if (is_array($embedding_vector)) {
2886 6549 $vectors[] = $embedding_vector;
2887 6550 } else {
2888 - wp_die(esc_html__('Error generating embedding for phrase: ', 'mxchat') . esc_html($phrase));
6551 + $failed_phrases[] = $phrase;
2889 6552 }
2890 6553 }
2891 -
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 +
2892 6565 if (empty($vectors)) {
2893 - wp_die(esc_html__('No valid embeddings generated. Please check your phrases.', 'mxchat'));
6566 + $this->handle_embedding_error(__('No valid embeddings generated. Please check your phrases.', 'mxchat'));
6567 + return;
2894 6568 }
2895 6569
2896 - // Create combined vector just like in intent creation
2897 6570 $combined_vector = $this->mxchat_average_vectors($vectors);
2898 6571 $serialized_vector = maybe_serialize($combined_vector);
2899 6572
6573 + // Update the database
2900 6574 global $wpdb;
2901 6575 $table_name = $wpdb->prefix . 'mxchat_intents';
2902 6576
2903 - // Update the intent with both new phrases and the combined vector
2904 6577 $result = $wpdb->update(
2905 6578 $table_name,
2906 6579 array(
6580 + 'intent_label' => $intent_label,
2907 6581 'phrases' => implode(', ', $phrases_array),
2908 - 'embedding_vector' => $serialized_vector
6582 + 'embedding_vector' => $serialized_vector,
6583 + 'similarity_threshold' => $similarity_threshold
2909 6584 ),
2910 6585 array('id' => $intent_id),
2911 - array('%s', '%s'),
2912 - array('%d')
6586 + array('%s', '%s', '%s', '%f'), // Format: string, string, string, float
6587 + array('%d') // Where format: integer
2913 6588 );
2914 6589
2915 6590 if (false === $result) {
2916 - wp_die(esc_html__('Failed to update intent.', 'mxchat'));
6591 + $this->handle_embedding_error(__('Failed to update action in database.', 'mxchat'));
6592 + return;
2917 6593 }
2918 6594
2919 - // Redirect back to the intents page with a success message
6595 + // Set success message and redirect
6596 + set_transient('mxchat_admin_notice_success', __('Intent updated successfully!', 'mxchat'), 60);
6597 +
2920 6598 $redirect_url = add_query_arg(
2921 6599 array(
2922 - 'page' => 'mxchat-intents',
2923 - 'updated' => 'true'
6600 + 'page' => 'mxchat-actions'
2924 6601 ),
2925 6602 admin_url('admin.php')
2926 6603 );
2927 -
2928 6604 wp_safe_redirect($redirect_url);
2929 6605 exit;
2930 6606 }
2931 -public function mxchat_handle_update_intent_threshold() {
2932 - if ( ! current_user_can( 'manage_options' ) ) {
2933 - wp_die( esc_html__('Unauthorized user', 'mxchat') );
2934 - }
2935 6607
2936 - check_admin_referer('mxchat_update_intent_threshold_nonce');
2937 -
2938 - if (isset($_POST['intent_id'], $_POST['intent_threshold'])) {
2939 - global $wpdb;
2940 - $table_name = $wpdb->prefix . 'mxchat_intents';
2941 - $intent_id = intval($_POST['intent_id']);
2942 - $threshold_percentage = max(70, min(95, intval($_POST['intent_threshold'])));
2943 - $similarity_threshold = $threshold_percentage / 100;
2944 -
2945 - $wpdb->update(
2946 - $table_name,
2947 - ['similarity_threshold' => $similarity_threshold],
2948 - ['id' => $intent_id],
2949 - ['%f'],
2950 - ['%d']
2951 - );
2952 - }
2953 -
2954 - wp_safe_redirect(admin_url('admin.php?page=mxchat-intents'));
2955 - exit;
2956 -}
2957 -
6608 +/**
6609 + * Handle adding new intent - with improved error handling
6610 + *
6611 + * @return void
6612 + */
2958 6613 public function mxchat_handle_add_intent() {
2959 - if ( ! current_user_can( 'manage_options' ) ) {
2960 - wp_die( esc_html__('Unauthorized user', 'mxchat') );
6614 + if (!current_user_can('manage_options')) {
6615 + wp_die(esc_html__('Unauthorized user', 'mxchat'));
2961 6616 }
2962 6617
2963 6618 check_admin_referer('mxchat_add_intent_nonce');
2964 6619
@@ -2970,120 +6625,484 @@
2970 6625 $callback_function = isset($_POST['callback_function']) ? sanitize_text_field($_POST['callback_function']) : '';
2971 6626 $default_threshold = 0.85;
2972 6627
2973 6628 if (empty($intent_label) || empty($callback_function) || empty($phrases_input)) {
2974 - wp_die( esc_html__('Invalid input. Please ensure all fields are filled out.', 'mxchat') );
6629 + $this->handle_embedding_error(__('Invalid input. Please ensure all fields are filled out.', 'mxchat'));
6630 + return;
2975 6631 }
2976 6632
2977 6633 $available_callbacks = $this->mxchat_get_available_callbacks();
2978 6634
2979 6635 if (!array_key_exists($callback_function, $available_callbacks)) {
2980 - wp_die( esc_html__('Invalid callback function selected.', 'mxchat') );
6636 + $this->handle_embedding_error(__('Invalid callback function selected.', 'mxchat'));
6637 + return;
2981 6638 }
2982 6639
2983 6640 $is_pro_only = $available_callbacks[$callback_function]['pro_only'];
2984 6641 if ($is_pro_only && !$this->is_activated) {
2985 - wp_die( esc_html__('This callback function is available in the Pro version only.', 'mxchat') );
6642 + $this->handle_embedding_error(__('This callback function is available in the Pro version only.', 'mxchat'));
6643 + return;
2986 6644 }
2987 6645
2988 6646 $phrases_array = array_map('sanitize_text_field', array_filter(array_map('trim', explode(',', $phrases_input))));
2989 6647
2990 6648 if (empty($phrases_array)) {
2991 - wp_die( esc_html__('Please enter at least one valid phrase.', 'mxchat') );
6649 + $this->handle_embedding_error(__('Please enter at least one valid phrase.', 'mxchat'));
6650 + return;
2992 6651 }
2993 6652
6653 + // Generate embeddings with improved error handling
2994 6654 $vectors = [];
6655 + $failed_phrases = [];
6656 +
2995 6657 foreach ($phrases_array as $phrase) {
2996 6658 $embedding_vector = $this->mxchat_generate_embedding($phrase, $this->options['api_key']);
2997 6659 if (is_array($embedding_vector)) {
2998 6660 $vectors[] = $embedding_vector;
2999 6661 } else {
3000 - wp_die( esc_html__('Error generating embedding for phrase: ', 'mxchat') . esc_html($phrase) );
6662 + $failed_phrases[] = $phrase;
3001 6663 }
3002 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 + }
3003 6675
3004 - if (!empty($vectors)) {
3005 - $combined_vector = $this->mxchat_average_vectors($vectors);
3006 - $serialized_vector = maybe_serialize($combined_vector);
6676 + if (empty($vectors)) {
6677 + $this->handle_embedding_error(__('No valid embeddings generated. Please check your phrases.', 'mxchat'));
6678 + return;
6679 + }
3007 6680
3008 - $result = $wpdb->insert($table_name, [
3009 - 'intent_label' => $intent_label,
3010 - 'phrases' => implode(', ', $phrases_array),
3011 - 'embedding_vector' => $serialized_vector,
3012 - 'callback_function' => $callback_function,
3013 - 'similarity_threshold' => $default_threshold,
3014 - ]);
6681 + $combined_vector = $this->mxchat_average_vectors($vectors);
6682 + $serialized_vector = maybe_serialize($combined_vector);
3015 6683
3016 - if ($result === false) {
3017 - wp_die( esc_html__('Database error: ', 'mxchat') . esc_html($wpdb->last_error) );
3018 - }
3019 - } else {
3020 - wp_die( esc_html__('No valid embeddings generated. Please check your phrases.', 'mxchat') );
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;
3021 6695 }
3022 6696
3023 - wp_safe_redirect(admin_url('admin.php?page=mxchat-intents'));
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'));
3024 6701 exit;
3025 6702 }
3026 6703
3027 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 +}
3028 6752
3029 6753
3030 6754
3031 -
3032 -private function mxchat_get_available_callbacks($grouped = false) {
3033 - $callbacks = [
3034 - 'mxchat_handle_email_capture' => [
3035 - 'label' => __('Email Capture', 'mxchat'),
3036 - 'pro_only' => false,
3037 - 'group' => __('Customer Engagement', 'mxchat'),
3038 - ],
3039 - 'mxchat_generate_image' => [
3040 - 'label' => __('Generate Image', 'mxchat'),
3041 - 'pro_only' => true,
3042 - 'group' => __('Other Features', 'mxchat'),
3043 - ],
3044 - 'mxchat_handle_search_request' => [
3045 - 'label' => __('Brave Web Search', 'mxchat'),
3046 - 'pro_only' => false,
3047 - 'group' => __('Search Features', 'mxchat'),
3048 - ],
3049 - 'mxchat_handle_image_search_request' => [
3050 - 'label' => __('Brave Image Search', 'mxchat'),
3051 - 'pro_only' => false,
3052 - 'group' => __('Search Features', 'mxchat'),
3053 - ],
3054 - 'mxchat_handle_pdf_discussion' => [
3055 - 'label' => __('Chat with PDF', 'mxchat'),
3056 - 'pro_only' => true,
3057 - 'group' => __('Other Features', 'mxchat'),
3058 - ],
3059 - 'mxchat_live_agent_handover' => [
3060 - 'label' => __('Live Agent', 'mxchat'),
3061 - 'pro_only' => true,
3062 - 'group' => __('Customer Engagement', 'mxchat'),
3063 - ],
3064 - 'mxchat_handle_switch_to_chatbot_intent' => [
3065 - 'label' => __('Back to Chatbot', 'mxchat'),
3066 - 'pro_only' => true,
3067 - 'group' => __('Customer Engagement', 'mxchat'),
3068 - ],
3069 - ];
3070 -
3071 - // Allow other plugins to add their callbacks
3072 - $callbacks = apply_filters('mxchat_available_callbacks', $callbacks);
3073 -
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 +
3074 7066 // Return grouped structure if requested
3075 7067 if ($grouped) {
3076 - $grouped_callbacks = [];
7068 + $grouped_callbacks = array();
3077 7069 foreach ($callbacks as $key => $data) {
3078 - $group_label = $data['group'] ?? __('Other Features', 'mxchat');
3079 - $grouped_callbacks[$group_label][$key] = [
3080 - 'label' => $data['label'],
3081 - 'pro_only' => $data['pro_only'],
3082 - ];
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;
3083 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 +
3084 7102 return $grouped_callbacks;
3085 7103 }
7104 +
3086 7105 return $callbacks;
3087 7106 }
3088 7107
3089 7108
@@ -3120,15 +7139,14 @@
3120 7139
3121 7140 $wpdb->delete($table_name, ['id' => $intent_id], ['%d']);
3122 7141 }
3123 7142
3124 - wp_safe_redirect(admin_url('admin.php?page=mxchat-intents'));
7143 + wp_safe_redirect(admin_url('admin.php?page=mxchat-actions'));
3125 7144 exit;
3126 7145 }
3127 7146
3128 7147
3129 7148 public function mxchat_page_init() {
3130 - // Register settings
3131 7149 register_setting(
3132 7150 'mxchat_option_group',
3133 7151 'mxchat_options',
3134 7152 array($this, 'mxchat_sanitize')
@@ -3139,10 +7157,10 @@
3139 7157 'mxchat_similarity_threshold',
3140 7158 array(
3141 7159 'type' => 'number',
3142 7160 'sanitize_callback' => function($value) {
3143 - $value = absint($value); // Ensure it's an integer
3144 - return min(max($value, 70), 85); // Enforce range
7161 + $value = absint($value);
7162 + return min(max($value, 20), 95);
3145 7163 },
3146 7164 'default' => 80,
3147 7165 )
3148 7166 );
@@ -3171,43 +7189,98 @@
3171 7189 'mxchat-chatbot',
3172 7190 'mxchat_chatbot_section'
3173 7191 );
3174 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 + );
3175 7264
3176 - // Existing fields...
3177 7265 add_settings_field(
3178 - 'api_key',
3179 - esc_html__('OpenAI API Key', 'mxchat'),
3180 - array($this, 'api_key_callback'),
7266 + 'model',
7267 + esc_html__('Chat Model', 'mxchat'),
7268 + array($this, 'mxchat_model_callback'),
3181 7269 'mxchat-chatbot',
3182 7270 'mxchat_chatbot_section'
3183 7271 );
3184 -
7272 +
7273 + // Add the settings field
3185 7274 add_settings_field(
3186 - 'xai_api_key',
3187 - esc_html__('X.AI API Key', 'mxchat'),
3188 - array($this, 'xai_api_key_callback'),
7275 + 'embedding_model',
7276 + esc_html__('Embedding Model', 'mxchat'),
7277 + array($this, 'embedding_model_callback'),
3189 7278 'mxchat-chatbot',
3190 7279 'mxchat_chatbot_section'
3191 7280 );
3192 -
7281 +
3193 7282 add_settings_field(
3194 - 'claude_api_key',
3195 - esc_html__('Claude API Key', 'mxchat'),
3196 - array($this, 'claude_api_key_callback'),
3197 - 'mxchat-chatbot',
3198 - 'mxchat_chatbot_section'
3199 - );
3200 -
3201 - add_settings_field(
3202 - 'deepseek_api_key',
3203 - esc_html__('DeepSeek API Key', 'mxchat'),
3204 - array($this, 'deepseek_api_key_callback'),
3205 - 'mxchat-chatbot',
3206 - 'mxchat_chatbot_section'
3207 - );
3208 -
3209 - add_settings_field(
3210 7283 'system_prompt_instructions',
3211 7284 esc_html__('AI Instructions (Behavior)', 'mxchat'),
3212 7285 array($this, 'system_prompt_instructions_callback'),
3213 7286 'mxchat-chatbot',
@@ -3213,24 +7286,25 @@
3213 7286 'mxchat-chatbot',
3214 7287 'mxchat_chatbot_section'
3215 7288 );
3216 7289
7290 +
3217 7291 add_settings_field(
3218 - 'model',
3219 - esc_html__('Model', 'mxchat'),
3220 - array($this, 'mxchat_model_callback'),
7292 + 'top_bar_title',
7293 + esc_html__('Top Bar Title', 'mxchat'),
7294 + array($this, 'mxchat_top_bar_title_callback'),
3221 7295 'mxchat-chatbot',
3222 7296 'mxchat_chatbot_section'
3223 7297 );
3224 7298
3225 7299 add_settings_field(
3226 - 'top_bar_title',
3227 - esc_html__('Top Bar Title', 'mxchat'),
3228 - array($this, 'mxchat_top_bar_title_callback'),
7300 + 'ai_agent_text',
7301 + esc_html__('AI Agent Text', 'mxchat'),
7302 + array($this, 'mxchat_ai_agent_text_callback'),
3229 7303 'mxchat-chatbot',
3230 7304 'mxchat_chatbot_section'
3231 7305 );
3232 -
7306 +
3233 7307 add_settings_field(
3234 7308 'enable_email_block',
3235 7309 esc_html__('Require Email To Chat', 'mxchat'),
3236 7310 array($this, 'enable_email_block_callback'),
@@ -3270,32 +7344,8 @@
3270 7344 'mxchat_chatbot_section'
3271 7345 );
3272 7346
3273 7347 add_settings_field(
3274 - 'rate_limit_logged_out',
3275 - __('Rate Limit for Logged-out Users', 'mxchat'),
3276 - array($this, 'mxchat_rate_limit_logged_out_callback'),
3277 - 'mxchat-chatbot',
3278 - 'mxchat_chatbot_section'
3279 - );
3280 -
3281 - add_settings_field(
3282 - 'rate_limit_roles',
3283 - __('Rate Limits by User Role', 'mxchat'),
3284 - array($this, 'mxchat_rate_limit_roles_callback'),
3285 - 'mxchat-chatbot',
3286 - 'mxchat_chatbot_section'
3287 - );
3288 -
3289 - add_settings_field(
3290 - 'rate_limit_message',
3291 - esc_html__('Rate Limit Message', 'mxchat'),
3292 - array($this, 'mxchat_rate_limit_message_callback'),
3293 - 'mxchat-chatbot',
3294 - 'mxchat_chatbot_section'
3295 - );
3296 -
3297 - add_settings_field(
3298 7348 'pre_chat_message',
3299 7349 esc_html__('Chat Teaser Pop-up', 'mxchat'),
3300 7350 array($this, 'mxchat_pre_chat_message_callback'),
3301 7351 'mxchat-chatbot',
@@ -3335,9 +7385,9 @@
3335 7385 );
3336 7386
3337 7387 add_settings_field(
3338 7388 'popular_question_1',
3339 - esc_html__('Popular Question 1', 'mxchat'),
7389 + esc_html__('Quick Question 1', 'mxchat'),
3340 7390 array($this, 'mxchat_popular_question_1_callback'),
3341 7391 'mxchat-chatbot',
3342 7392 'mxchat_chatbot_section'
3343 7393 );
@@ -3343,9 +7393,9 @@
3343 7393 );
3344 7394
3345 7395 add_settings_field(
3346 7396 'popular_question_2',
3347 - esc_html__('Popular Question 2', 'mxchat'),
7397 + esc_html__('Quick Question 2', 'mxchat'),
3348 7398 array($this, 'mxchat_popular_question_2_callback'),
3349 7399 'mxchat-chatbot',
3350 7400 'mxchat_chatbot_section'
3351 7401 );
@@ -3351,9 +7401,9 @@
3351 7401 );
3352 7402
3353 7403 add_settings_field(
3354 7404 'popular_question_3',
3355 - esc_html__('Popular Question 3', 'mxchat'),
7405 + esc_html__('Quick Question 3', 'mxchat'),
3356 7406 array($this, 'mxchat_popular_question_3_callback'),
3357 7407 'mxchat-chatbot',
3358 7408 'mxchat_chatbot_section'
3359 7409 );
@@ -3359,9 +7409,9 @@
3359 7409 );
3360 7410
3361 7411 add_settings_field(
3362 7412 'additional_popular_questions',
3363 - esc_html__('Additional Popular Questions', 'mxchat'),
7413 + esc_html__('Additional Quick Questions', 'mxchat'),
3364 7414 array($this, 'mxchat_additional_popular_questions_callback'),
3365 7415 'mxchat-chatbot',
3366 7416 'mxchat_chatbot_section'
3367 7417 );
@@ -3366,8 +7416,16 @@
3366 7416 'mxchat_chatbot_section'
3367 7417 );
3368 7418
3369 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 +
3370 7428 // Loops Settings Section
3371 7429 add_settings_section(
3372 7430 'mxchat_loops_section',
3373 7431 esc_html__('Loops Settings', 'mxchat'),
@@ -3478,8 +7536,26 @@
3478 7536 array($this, 'mxchat_chat_toolbar_toggle_callback'),
3479 7537 'mxchat-embed',
3480 7538 'mxchat_pdf_intent_section'
3481 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 + );
3482 7558
3483 7559 add_settings_field(
3484 7560 'pdf_intent_trigger_text',
3485 7561 __('Intent Trigger Text', 'mxchat'),
@@ -3576,9 +7652,9 @@
3576 7652
3577 7653 // General Settings Section
3578 7654 add_settings_section(
3579 7655 'mxchat_general_section',
3580 - esc_html__('Frequently Asked Questions (FAQ)', 'mxchat'),
7656 + esc_html__('YouTube Tutorials', 'mxchat'),
3581 7657 null,
3582 7658 'mxchat-general'
3583 7659 );
3584 7660 }
@@ -3583,9 +7659,8 @@
3583 7659 );
3584 7660 }
3585 7661
3586 7662 public function mxchat_prompts_page_init() {
3587 - // Register all settings as a single array
3588 7663 register_setting(
3589 7664 'mxchat_prompts_options',
3590 7665 'mxchat_prompts_options',
3591 7666 array(
@@ -3597,18 +7672,57 @@
3597 7672 'mxchat_use_pinecone' => 0,
3598 7673 'mxchat_pinecone_api_key' => '',
3599 7674 'mxchat_pinecone_environment' => '',
3600 7675 'mxchat_pinecone_index' => '',
3601 - 'mxchat_pinecone_host' => '', // Add this line
7676 + 'mxchat_pinecone_host' => '',
3602 7677 ),
3603 7678 'sanitize_callback' => array($this, 'sanitize_prompts_options'),
3604 7679 )
3605 7680 );
3606 7681
3607 - // Add settings saved message
3608 7682 add_action('admin_notices', array($this, 'sync_settings_notice'));
3609 7683 }
3610 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 +
3611 7725 /**
3612 7726 * Sanitize all prompts options
3613 7727 *
3614 7728 * @param array $input The unsanitized options array
@@ -3747,82 +7861,143 @@
3747 7861 update_option('mxchat_license_error', $error_message);
3748 7862 wp_send_json_error($error_message);
3749 7863 }
3750 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 +}
3751 7890
3752 7891
3753 -public function mxchat_rate_limit_logged_out_callback() {
3754 - // Load the entire 'mxchat_options' array
3755 - $all_options = get_option('mxchat_options', []);
3756 7892
3757 - // Retrieve the saved rate limit or use the default value
3758 - $default_rate_limit = '10';
3759 - $selected_rate_limit = isset($all_options['rate_limit_logged_out']) ? $all_options['rate_limit_logged_out'] : $default_rate_limit;
3760 7893
7894 +public function mxchat_rate_limits_callback() {
7895 + $all_options = get_option('mxchat_options', []);
7896 +
3761 7897 // Define available rate limits
3762 - $rate_limits = array('1', '3', '5', '10', '15', '20', '100', 'unlimited');
3763 -
3764 - // Output the dropdown
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
3765 7913 echo '<div class="pro-feature-wrapper active">';
3766 - echo '<select id="rate_limit_logged_out" name="rate_limit_logged_out">';
3767 - foreach ($rate_limits as $limit) {
3768 - echo '<option value="' . esc_attr($limit) . '" ' . selected($selected_rate_limit, $limit, false) . '>' . esc_html($limit) . '</option>';
3769 - }
3770 - echo '</select>';
3771 - echo '<p class="description">' . esc_html__('Set the maximum number of messages a logged-out user can send within a 24-hour period.', 'mxchat') . '</p>';
3772 - echo '</div>';
3773 -}
7914 + echo '<div class="mxchat-rate-limits-container">';
3774 7915
3775 -// Add this new callback function
3776 -public function mxchat_rate_limit_roles_callback() {
3777 - $all_options = get_option('mxchat_options', []);
3778 - $roles = wp_roles()->get_names();
3779 - $rate_limits = array('1', '3', '5', '10', '15', '20', '100', 'unlimited');
3780 -
3781 - echo '<div class="pro-feature-wrapper active mxchat-autosave-section">';
3782 -
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
3783 7921 foreach ($roles as $role_id => $role_name) {
3784 - $default_rate_limit = '100';
3785 - $selected_rate_limit = isset($all_options['role_rate_limits'][$role_id])
3786 - ? $all_options['role_rate_limits'][$role_id]
3787 - : $default_rate_limit;
3788 -
3789 - echo '<div style="margin-bottom: 10px;">';
3790 - echo '<label style="display: inline-block; width: 150px;">' . esc_html($role_name) . ':</label>';
3791 - echo '<select
3792 - id="role_rate_limits_' . esc_attr($role_id) . '"
3793 - name="mxchat_options[role_rate_limits][' . esc_attr($role_id) . ']"
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]"
3794 7957 class="mxchat-autosave-field">';
3795 7958 foreach ($rate_limits as $limit) {
3796 - echo '<option value="' . esc_attr($limit) . '" ' . selected($selected_rate_limit, $limit, false) . '>' . esc_html($limit) . '</option>';
7959 + echo '<option value="' . esc_attr($limit) . '" ' . selected($selected_limit, $limit, false) . '>' . esc_html($limit) . '</option>';
3797 7960 }
3798 7961 echo '</select>';
3799 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
3800 7994 }
3801 -
3802 - echo '<p class="description">' . esc_html__('Set the maximum number of messages users in each role can send within a 24-hour period.', 'mxchat') . '</p>';
3803 - echo '</div>';
7995 +
7996 + echo '</div>'; // End container
7997 +
7998 + echo '</div>'; // End pro-feature-wrapper
3804 7999 }
3805 -
3806 -public function mxchat_rate_limit_message_callback() {
3807 - // Load the entire 'mxchat_options' array
3808 - $all_options = get_option('mxchat_options', []);
3809 -
3810 - // Retrieve the saved message or use the default value
3811 - $default_message = esc_html__('Rate limit exceeded. Please try again later.', 'mxchat');
3812 - $rate_limit_message = isset($all_options['rate_limit_message']) ? $all_options['rate_limit_message'] : $default_message;
3813 -
3814 - // Output the textarea
3815 - echo '<div class="pro-feature-wrapper active">';
3816 - printf(
3817 - '<textarea id="rate_limit_message" name="rate_limit_message" rows="3" cols="50">%s</textarea>',
3818 - esc_textarea($rate_limit_message)
3819 - );
3820 - echo '<p class="description">' . esc_html__('This message will be displayed when a user exceeds the rate limit.', 'mxchat') . '</p>';
3821 - echo '</div>';
3822 -}
3823 -
3824 -
3825 8000 private function mxchat_add_option_field($id, $title, $callback = '') {
3826 8001 add_settings_field(
3827 8002 $id,
3828 8003 __($title, 'mxchat'),
@@ -3831,114 +8006,113 @@
3831 8006 'mxchat_setting_section_id',
3832 8007 $id === 'model' ? ['label_for' => 'model'] : []
3833 8008 );
3834 8009 }
3835 -
8010 +
8011 +// OpenAI API Key
3836 8012 public function api_key_callback() {
3837 - // Retrieve from your stored 'api_key' in the mxchat_options array
3838 8013 $apiKey = isset($this->options['api_key']) ? esc_attr($this->options['api_key']) : '';
3839 -
3840 - // Notice we changed the name to "api_key" (no array notation).
3841 - echo '<input type="password" id="api_key" name="api_key" value="' . $apiKey . '" class="regular-text" />';
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" />';
3842 8017 echo '<button type="button" id="toggleApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
3843 - echo '<p class="description">' . esc_html__('The OpenAI API key is required even when using other models as it is used for vector embedding functionality. You must add credits to your OpenAI billing account before use.', 'mxchat') . '</p>';
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>';
3844 8020 }
8021 +
8022 +// X.AI API Key
3845 8023 public function xai_api_key_callback() {
3846 - // Check if the feature is activated (paid feature)
3847 - $disabled = $this->is_activated ? '' : 'disabled'; // Disable input if not activated
3848 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive'; // CSS class to style the wrapper based on activation status
3849 -
3850 - // Retrieve the X.AI API key value
3851 8024 $xaiApiKey = isset($this->options['xai_api_key']) ? esc_attr($this->options['xai_api_key']) : '';
3852 -
3853 - // Render the input field for the X.AI API key
3854 - echo '<div class="' . esc_attr($class) . '">';
3855 - printf(
3856 - '<input type="password" id="xai_api_key" name="xai_api_key" value="%s" class="regular-text" %s />',
3857 - $xaiApiKey,
3858 - $disabled
3859 - );
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" />';
3860 8028 echo '<button type="button" id="toggleXaiApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
3861 -
3862 - // If the feature is not activated, show the overlay with a "Pro Only" message
3863 - if (!$this->is_activated) {
3864 - echo '<div class="pro-feature-overlay">';
3865 - echo '<a href="https://mxchat.ai/" target="_blank"><img src="' . plugin_dir_url(__FILE__) . '../images/pro-only-dark.png" alt="' . esc_attr__('Pro Only', 'mxchat') . '" /></a>';
3866 - echo '</div>';
3867 - }
3868 -
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>';
3869 8030 echo '</div>';
3870 8031 }
8032 +// Claude API Key
3871 8033 public function claude_api_key_callback() {
3872 - // Check if the feature is activated (paid feature)
3873 - $disabled = $this->is_activated ? '' : 'disabled';
3874 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
3875 -
3876 - // Retrieve the Claude API key value directly like the others
3877 8034 $claudeApiKey = isset($this->options['claude_api_key']) ? esc_attr($this->options['claude_api_key']) : '';
3878 8035
3879 - // Use direct name field like the other working API keys
3880 - echo '<div class="' . esc_attr($class) . '">';
3881 - printf(
3882 - '<input type="password" id="claude_api_key" name="claude_api_key" value="%s" class="regular-text" %s />',
3883 - $claudeApiKey,
3884 - $disabled
3885 - );
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" />';
3886 8038 echo '<button type="button" id="toggleClaudeApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
3887 -
3888 - if (!$this->is_activated) {
3889 - echo '<div class="pro-feature-overlay">';
3890 - echo '<a href="https://mxchat.ai/" target="_blank"><img src="' . plugin_dir_url(__FILE__) . '../images/pro-only-dark.png" alt="' . esc_attr__('Pro Only', 'mxchat') . '" /></a>';
3891 - echo '</div>';
3892 - }
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>';
3893 8040 echo '</div>';
3894 8041 }
8042 +
8043 +// DeepSeek API Key
3895 8044 public function deepseek_api_key_callback() {
3896 - // Retrieve from your stored 'deepseek_api_key' in the mxchat_options array
3897 8045 $apiKey = isset($this->options['deepseek_api_key']) ? esc_attr($this->options['deepseek_api_key']) : '';
3898 -
3899 - // Notice we changed the name to "deepseek_api_key" (no array notation).
3900 - echo '<input type="password" id="deepseek_api_key" name="deepseek_api_key" value="' . $apiKey . '" class="regular-text" />';
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" />';
3901 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>';
3902 8052 }
3903 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 +}
3904 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 +}
3905 8075
3906 -
3907 -
3908 8076 public function mxchat_loops_api_key_callback() {
3909 - // Support both old and new format
3910 8077 $loops_api_key = isset($this->options['loops_api_key']) ? esc_attr($this->options['loops_api_key']) : '';
3911 -
3912 - echo '<div class="api-key-wrapper">';
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">';
3913 8084 echo sprintf(
3914 - '<input type="password" id="loops_api_key" name="loops_api_key" value="%s" class="regular-text" />',
8085 + '<input type="password" id="loops_api_key" name="loops_api_key" value="%s" class="regular-text" autocomplete="new-password" />',
3915 8086 $loops_api_key
3916 8087 );
3917 8088 echo '<button type="button" id="toggleLoopsApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
3918 8089 echo '</div>';
3919 - echo '<p class="description">' . esc_html__('Enter your Loops API Key here. (See FAQ for details)', 'mxchat') . '</p>';
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>';
3920 8091 }
3921 -
3922 8092 public function mxchat_loops_mailing_list_callback() {
3923 8093 // Add error handling and type checking
3924 8094 $loops_api_key = '';
3925 8095 $selected_list = '';
3926 -
8096 +
3927 8097 // Safely get the API key
3928 8098 if (isset($this->options['loops_api_key']) && is_string($this->options['loops_api_key'])) {
3929 8099 $loops_api_key = $this->options['loops_api_key'];
3930 8100 }
3931 -
8101 +
3932 8102 // Safely get the selected list
3933 8103 if (isset($this->options['loops_mailing_list']) && is_string($this->options['loops_mailing_list'])) {
3934 8104 $selected_list = $this->options['loops_mailing_list'];
3935 8105 }
3936 -
8106 +
3937 8107 if (!empty($loops_api_key)) {
3938 8108 $lists = $this->mxchat_fetch_loops_mailing_lists($loops_api_key);
3939 8109 if (is_array($lists) && !empty($lists)) {
3940 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 +
3941 8115 foreach ($lists as $list) {
3942 8116 if (is_array($list) && isset($list['id']) && isset($list['name'])) {
3943 8117 echo sprintf(
3944 8118 '<option value="%s" %s>%s</option>',
@@ -3948,8 +8122,9 @@
3948 8122 );
3949 8123 }
3950 8124 }
3951 8125 echo '</select>';
8126 + echo '<p class="description">' . esc_html__('Please select a mailing list to use with Loops.', 'mxchat') . '</p>';
3952 8127 } else {
3953 8128 echo '<p class="description">' . esc_html__('No lists found. Please verify your API Key.', 'mxchat') . '</p>';
3954 8129 }
3955 8130 } else {
@@ -3997,23 +8172,92 @@
3997 8172 );
3998 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>';
3999 8174 }
4000 8175
4001 -
4002 -
4003 8176 // Callback for AI Instructions textarea
4004 8177 public function system_prompt_instructions_callback() {
4005 8178 // Retrieve the current value of the system prompt instructions
4006 8179 $instructions = isset($this->options['system_prompt_instructions']) ? esc_textarea($this->options['system_prompt_instructions']) : '';
4007 -
4008 8180 // Render the textarea field
4009 8181 printf(
4010 8182 '<textarea id="system_prompt_instructions" name="system_prompt_instructions" rows="5" cols="50">%s</textarea>',
4011 8183 $instructions
4012 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 +}
4013 8200
4014 - // Provide a helpful description
4015 - 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>';
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>';
4016 8260 }
4017 8261
4018 8262
4019 8263 public function mxchat_model_callback() {
@@ -4018,10 +8262,19 @@
4018 8262
4019 8263 public function mxchat_model_callback() {
4020 8264 // Define available models grouped by provider
4021 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 + ),
4022 8272 esc_html__('X.AI Models', 'mxchat') => array(
4023 - 'grok-beta' => esc_html__('grok-beta (Early Beta)', 'mxchat'),
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'),
4024 8277 'grok-2' => esc_html__('Grok 2', 'mxchat')
4025 8278 ),
4026 8279 esc_html__('DeepSeek Models', 'mxchat') => array(
4027 8280 'deepseek-chat' => esc_html__('DeepSeek-V3', 'mxchat'),
@@ -4026,14 +8279,18 @@
4026 8279 esc_html__('DeepSeek Models', 'mxchat') => array(
4027 8280 'deepseek-chat' => esc_html__('DeepSeek-V3', 'mxchat'),
4028 8281 ),
4029 8282 esc_html__('Claude Models', 'mxchat') => array(
4030 - 'claude-3-5-sonnet-20241022' => esc_html__('Claude 3.5 Sonnet (Most Intelligent)', 'mxchat'),
4031 - 'claude-3-opus-20240229' => esc_html__('Claude 3 Opus (Highly Complex Tasks)', 'mxchat'),
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'),
4032 8288 'claude-3-sonnet-20240229' => esc_html__('Claude 3 Sonnet (Balanced)', 'mxchat'),
4033 8289 'claude-3-haiku-20240307' => esc_html__('Claude 3 Haiku (Fastest)', 'mxchat')
4034 8290 ),
4035 8291 esc_html__('OpenAI Models', 'mxchat') => array(
8292 + 'gpt-4.1-2025-04-14' => esc_html__('GPT-4.1 (Flagship for Complex Tasks)', 'mxchat'),
4036 8293 'gpt-4o' => esc_html__('GPT-4o (Recommended)', 'mxchat'),
4037 8294 'gpt-4o-mini' => esc_html__('GPT-4o Mini (Fast and Lightweight)', 'mxchat'),
4038 8295 'gpt-4-turbo' => esc_html__('GPT-4 Turbo (High-Performance)', 'mxchat'),
4039 8296 'gpt-4' => esc_html__('GPT-4 (High Intelligence)', 'mxchat'),
@@ -4039,40 +8296,63 @@
4039 8296 'gpt-4' => esc_html__('GPT-4 (High Intelligence)', 'mxchat'),
4040 8297 'gpt-3.5-turbo' => esc_html__('GPT-3.5 Turbo (Affordable and Fast)', 'mxchat')
4041 8298 )
4042 8299 );
4043 -
8300 +
4044 8301 // Retrieve the currently selected model from saved options
4045 8302 $selected_model = isset($this->options['model']) ? esc_attr($this->options['model']) : 'gpt-4o';
4046 -
8303 +
4047 8304 // Begin the select dropdown
4048 - echo '<select id="model" name="model">'; // No array notation in name attribute
4049 -
8305 + echo '<select id="model" name="model">';
8306 +
4050 8307 // Iterate over groups of models
4051 8308 foreach ($models as $group_label => $group_models) {
4052 8309 echo '<optgroup label="' . esc_attr($group_label) . '">';
4053 -
8310 +
4054 8311 foreach ($group_models as $model_value => $model_label) {
4055 - // Disable Pro-only models for non-activated users
4056 - $disabled = (!$this->is_activated && ($group_label === esc_html__('X.AI Models', 'mxchat') || $group_label === esc_html__('Claude Models', 'mxchat'))) ? 'disabled' : '';
4057 - $label_suffix = (!$this->is_activated && ($group_label === esc_html__('X.AI Models', 'mxchat') || $group_label === esc_html__('Claude Models', 'mxchat'))) ? esc_html__(' (Pro Only)', 'mxchat') : '';
4058 -
4059 - // Output the option element
4060 - echo '<option value="' . esc_attr($model_value) . '" ' . selected($selected_model, $model_value, false) . ' ' . $disabled . '>' . esc_html($model_label . $label_suffix) . '</option>';
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>';
4061 8314 }
4062 -
8315 +
4063 8316 echo '</optgroup>';
4064 8317 }
4065 -
8318 +
4066 8319 // Close the select dropdown
4067 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 +}
4068 8325
4069 - // Add a description below the dropdown
4070 - echo '<p class="description">' . esc_html__('Select the AI model to use for your chatbot. Pro-only models are marked accordingly.', 'mxchat') . '</p>';
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>';
4071 8352 }
4072 8353
4073 8354
4074 -
4075 8355 public function mxchat_top_bar_title_callback() {
4076 8356 // Retrieve the current value of the top bar title from saved options
4077 8357 $top_bar_title = isset($this->options['top_bar_title']) ? esc_attr($this->options['top_bar_title']) : '';
4078 8358
@@ -4081,9 +8361,16 @@
4081 8361
4082 8362 // Add a description
4083 8363 echo '<p class="description">' . esc_html__('Enter the title text that will appear on the top bar of the chatbot.', 'mxchat') . '</p>';
4084 8364 }
4085 -
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 +}
4086 8373 public function enable_email_block_callback() {
4087 8374 // Load full plugin options array
4088 8375 $all_options = get_option('mxchat_options', []);
4089 8376
@@ -4103,9 +8390,8 @@
4103 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>';
4104 8391 }
4105 8392
4106 8393
4107 -
4108 8394 public function email_blocker_header_content_callback() {
4109 8395 // Load the entire 'mxchat_options' array
4110 8396 $all_options = get_option('mxchat_options', []);
4111 8397
@@ -4113,14 +8399,15 @@
4113 8399 $content = isset($all_options['email_blocker_header_content'])
4114 8400 ? $all_options['email_blocker_header_content']
4115 8401 : '';
4116 8402
4117 - // Render the textarea
8403 + // Render the textarea - IMPORTANT: name should be just "email_blocker_header_content"
4118 8404 echo '<textarea
4119 8405 id="email_blocker_header_content"
4120 8406 name="email_blocker_header_content"
4121 8407 rows="5"
4122 8408 cols="70"
8409 + data-setting="email_blocker_header_content"
4123 8410 >' . esc_textarea($content) . '</textarea>';
4124 8411
4125 8412 echo '<p class="description">';
4126 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');
@@ -4126,9 +8413,8 @@
4126 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');
4127 8414 echo '</p>';
4128 8415 }
4129 8416
4130 -
4131 8417 public function email_blocker_button_text_callback() {
4132 8418 // Load the entire 'mxchat_options' array
4133 8419 $all_options = get_option('mxchat_options', []);
4134 8420
@@ -4149,23 +8435,20 @@
4149 8435
4150 8436 public function mxchat_intro_message_callback() {
4151 8437 // Load the entire 'mxchat_options' array
4152 8438 $all_options = get_option('mxchat_options', []);
4153 -
4154 8439 // Retrieve the saved intro message or use the default
4155 8440 $default_message = __('Hello! How can I assist you today?', 'mxchat');
4156 8441 $saved_message = isset($all_options['intro_message']) ? $all_options['intro_message'] : $default_message;
4157 -
4158 - // Output the textarea with the saved value
8442 + // Output the textarea with the saved value without escaping HTML
4159 8443 ?>
4160 - <textarea id="intro_message" name="intro_message" rows="5" cols="50"><?php echo esc_textarea($saved_message); ?></textarea>
8444 + <textarea id="intro_message" name="intro_message" rows="5" cols="50"><?php echo $saved_message; ?></textarea>
4161 8445 <p class="description">
4162 - <?php esc_html_e('Enter your message. Line breaks will be preserved.', 'mxchat'); ?>
8446 + <?php esc_html_e('Enter your message. HTML tags and line breaks will be preserved.', 'mxchat'); ?>
4163 8447 </p>
4164 8448 <?php
4165 8449 }
4166 8450
4167 -
4168 8451 public function mxchat_input_copy_callback() {
4169 8452 // Load the entire 'mxchat_options' array
4170 8453 $all_options = get_option('mxchat_options', []);
4171 8454
@@ -4735,13 +9018,13 @@
4735 9018 // Render the input field
4736 9019 printf(
4737 9020 '<input type="text" id="popular_question_1" name="popular_question_1" value="%s" placeholder="%s" class="regular-text" />',
4738 9021 esc_attr($popular_question_1),
4739 - esc_attr__('Enter Popular Question 1', 'mxchat')
9022 + esc_attr__('Enter Quick Question 1', 'mxchat')
4740 9023 );
4741 9024
4742 9025 // Add a description for the field
4743 - echo '<p class="description">' . esc_html__('This will be the first popular question in the chatbot.', 'mxchat') . '</p>';
9026 + echo '<p class="description">' . esc_html__('This will be the first Quick Question in the chatbot, displayed above the input field.', 'mxchat') . '</p>';
4744 9027 }
4745 9028
4746 9029
4747 9030 public function mxchat_popular_question_2_callback() {
@@ -4754,13 +9037,13 @@
4754 9037 // Render the input field
4755 9038 printf(
4756 9039 '<input type="text" id="popular_question_2" name="popular_question_2" value="%s" placeholder="%s" class="regular-text" />',
4757 9040 esc_attr($popular_question_2),
4758 - esc_attr__('Enter Popular Question 2', 'mxchat')
9041 + esc_attr__('Enter Quick Question 2', 'mxchat')
4759 9042 );
4760 9043
4761 9044 // Add a description for the field
4762 - echo '<p class="description">' . esc_html__('This will be the second popular question in the chatbot.', 'mxchat') . '</p>';
9045 + echo '<p class="description">' . esc_html__('This will be the second Quick Question in the chatbot.', 'mxchat') . '</p>';
4763 9046 }
4764 9047
4765 9048
4766 9049 public function mxchat_popular_question_3_callback() {
@@ -4773,13 +9056,13 @@
4773 9056 // Render the input field
4774 9057 printf(
4775 9058 '<input type="text" id="popular_question_3" name="popular_question_3" value="%s" placeholder="%s" class="regular-text" />',
4776 9059 esc_attr($popular_question_3),
4777 - esc_attr(__('Enter Popular Question 3', 'mxchat'))
9060 + esc_attr(__('Enter Quick Question 3', 'mxchat'))
4778 9061 );
4779 9062
4780 9063 // Add a description for the field
4781 - echo '<p class="description">' . esc_html__('This will be the third popular question in the chatbot.', 'mxchat') . '</p>';
9064 + echo '<p class="description">' . esc_html__('This will be the third Quick Question in the chatbot.', 'mxchat') . '</p>';
4782 9065 }
4783 9066
4784 9067 public function mxchat_additional_popular_questions_callback() {
4785 9068 $options = get_option('mxchat_options', []);
@@ -4800,9 +9083,9 @@
4800 9083 <button type="button" class="button mxchat-remove-question"
4801 9084 aria-label="%s">%s</button>
4802 9085 </div>',
4803 9086 esc_attr($question),
4804 - esc_attr(sprintf(__('Enter Additional Popular Question %d', 'mxchat'), $index + 4)),
9087 + esc_attr(sprintf(__('Enter Additional Quick Question %d', 'mxchat'), $index + 4)),
4805 9088 $index,
4806 9089 esc_attr(__('Remove question', 'mxchat')),
4807 9090 esc_html__('Remove', 'mxchat')
4808 9091 );
@@ -4817,9 +9100,9 @@
4817 9100 data-question-index="0" />
4818 9101 <button type="button" class="button mxchat-remove-question"
4819 9102 aria-label="%s">%s</button>
4820 9103 </div>',
4821 - esc_attr(__('Enter Additional Popular Question 4', 'mxchat')),
9104 + esc_attr(__('Enter Additional Quick Question 4', 'mxchat')),
4822 9105 esc_attr(__('Remove question', 'mxchat')),
4823 9106 esc_html__('Remove', 'mxchat')
4824 9107 );
4825 9108 }
@@ -4828,9 +9111,9 @@
4828 9111 '<button type="button" class="button mxchat-add-question" aria-label="%s">%s</button>',
4829 9112 esc_attr(__('Add question', 'mxchat')),
4830 9113 esc_html__('Add Question', 'mxchat')
4831 9114 );
4832 - echo '<p class="description">' . esc_html__('Add as many popular questions as you need.', 'mxchat') . '</p>';
9115 + echo '<p class="description">' . esc_html__('Add as many Quick Questions as you need.', 'mxchat') . '</p>';
4833 9116 echo '</div>';
4834 9117 }
4835 9118
4836 9119 public function mxchat_brave_api_key_callback() {
@@ -4929,141 +9212,116 @@
4929 9212 echo '<p>' . esc_html__('Configure the intent settings for the Chat with PDF feature.', 'mxchat') . '</p>';
4930 9213 }
4931 9214
4932 9215 public function mxchat_chat_toolbar_toggle_callback() {
4933 - // Get chat toolbar toggle value with fallback
4934 - $chat_toolbar_toggle = isset($this->options['chat_toolbar_toggle']) ? $this->options['chat_toolbar_toggle'] : 'off';
4935 - $checked = ($chat_toolbar_toggle === 'on') ? 'checked' : '';
4936 -
4937 - // Check if the plugin is activated (paid feature)
4938 - $disabled = $this->is_activated ? '' : 'disabled';
4939 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
4940 -
4941 - echo '<div class="' . esc_attr($class) . '">';
4942 -
4943 - // Output the toggle switch
4944 - echo '<label class="toggle-switch">';
4945 - echo sprintf(
4946 - '<input type="checkbox" id="chat_toolbar_toggle" name="chat_toolbar_toggle" value="on" %s %s />',
4947 - esc_attr($checked),
4948 - esc_attr($disabled)
4949 - );
4950 - echo '<span class="slider"></span>';
4951 - echo '</label>';
4952 -
4953 - // Pro feature overlay
4954 - if (!$this->is_activated) {
4955 - echo '<div class="pro-feature-overlay">';
4956 - echo '<a href="https://mxchat.ai/" target="_blank">';
4957 - echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
4958 - echo '</a>';
4959 - echo '</div>';
4960 - }
4961 -
4962 - echo '</div>';
4963 - 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>';
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>';
4964 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 +}
4965 9269
4966 -
4967 9270 public function mxchat_pdf_intent_trigger_text_callback() {
4968 - $disabled = $this->is_activated ? '' : 'disabled';
4969 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
4970 9271 $default_text = __("Please provide the URL to the PDF you'd like to discuss.", 'mxchat');
4971 9272
4972 - echo '<div class="' . esc_attr($class) . '">';
4973 9273 echo sprintf(
4974 9274 '<textarea id="pdf_intent_trigger_text"
4975 9275 name="pdf_intent_trigger_text"
4976 9276 rows="3"
4977 9277 cols="50"
4978 - placeholder="%s"
4979 - %s>%s</textarea>',
9278 + placeholder="%s">%s</textarea>',
4980 9279 esc_attr__('Enter trigger text', 'mxchat'),
4981 - esc_attr($disabled),
4982 9280 isset($this->options['pdf_intent_trigger_text'])
4983 9281 ? esc_textarea($this->options['pdf_intent_trigger_text'])
4984 9282 : esc_textarea($default_text)
4985 9283 );
4986 9284 echo '<p class="description">' . esc_html__('Text displayed when the intent is triggered.', 'mxchat') . '</p>';
4987 -
4988 - if (!$this->is_activated) {
4989 - echo '<div class="pro-feature-overlay">';
4990 - echo '<a href="https://mxchat.ai/" target="_blank">';
4991 - echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
4992 - echo '</a>';
4993 - echo '</div>';
4994 - }
4995 - echo '</div>';
4996 9285 }
4997 9286
4998 9287 public function mxchat_pdf_intent_success_text_callback() {
4999 - $disabled = $this->is_activated ? '' : 'disabled';
5000 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
5001 9288 $default_text = __("I've processed the PDF. What questions do you have about it?", 'mxchat');
5002 9289
5003 - echo '<div class="' . esc_attr($class) . '">';
5004 9290 echo sprintf(
5005 9291 '<textarea id="pdf_intent_success_text"
5006 9292 name="pdf_intent_success_text"
5007 9293 rows="3"
5008 9294 cols="50"
5009 - placeholder="%s"
5010 - %s>%s</textarea>',
9295 + placeholder="%s">%s</textarea>',
5011 9296 esc_attr__('Enter success text', 'mxchat'),
5012 - esc_attr($disabled),
5013 9297 isset($this->options['pdf_intent_success_text'])
5014 9298 ? esc_textarea($this->options['pdf_intent_success_text'])
5015 9299 : esc_textarea($default_text)
5016 9300 );
5017 9301 echo '<p class="description">' . esc_html__('Text displayed when the intent is successful.', 'mxchat') . '</p>';
5018 -
5019 - if (!$this->is_activated) {
5020 - echo '<div class="pro-feature-overlay">';
5021 - echo '<a href="https://mxchat.ai/" target="_blank">';
5022 - echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
5023 - echo '</a>';
5024 - echo '</div>';
5025 - }
5026 - echo '</div>';
5027 9302 }
5028 9303
5029 9304 public function mxchat_pdf_intent_error_text_callback() {
5030 - $disabled = $this->is_activated ? '' : 'disabled';
5031 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
5032 9305 $default_text = __("Sorry, I couldn't process the PDF. Please ensure it's a valid file.", 'mxchat');
5033 9306
5034 - echo '<div class="' . esc_attr($class) . '">';
5035 9307 echo sprintf(
5036 9308 '<textarea id="pdf_intent_error_text"
5037 9309 name="pdf_intent_error_text"
5038 9310 rows="3"
5039 9311 cols="50"
5040 - placeholder="%s"
5041 - %s>%s</textarea>',
9312 + placeholder="%s">%s</textarea>',
5042 9313 esc_attr__('Enter error text', 'mxchat'),
5043 - esc_attr($disabled),
5044 9314 isset($this->options['pdf_intent_error_text'])
5045 9315 ? esc_textarea($this->options['pdf_intent_error_text'])
5046 9316 : esc_textarea($default_text)
5047 9317 );
5048 9318 echo '<p class="description">' . esc_html__('Text displayed when an error occurs during the intent.', 'mxchat') . '</p>';
5049 -
5050 - if (!$this->is_activated) {
5051 - echo '<div class="pro-feature-overlay">';
5052 - echo '<a href="https://mxchat.ai/" target="_blank">';
5053 - echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
5054 - echo '</a>';
5055 - echo '</div>';
5056 - }
5057 - echo '</div>';
5058 9319 }
5059 9320
5060 9321 public function mxchat_pdf_max_pages_callback() {
5061 - $disabled = $this->is_activated ? '' : 'disabled';
5062 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
5063 9322 $max_pages = isset($this->options['pdf_max_pages']) ? intval($this->options['pdf_max_pages']) : 69;
5064 9323
5065 - echo '<div class="' . esc_attr($class) . '">';
5066 9324 echo sprintf(
5067 9325 '<input type="range"
5068 9326 id="pdf_max_pages"
5069 9327 name="pdf_max_pages"
@@ -5069,37 +9327,22 @@
5069 9327 name="pdf_max_pages"
5070 9328 min="1"
5071 9329 max="69"
5072 9330 value="%d"
5073 - %s
5074 9331 class="range-slider" />',
5075 - esc_attr($max_pages),
5076 - esc_attr($disabled)
9332 + esc_attr($max_pages)
5077 9333 );
5078 9334 echo '<span id="pdf_max_pages_output">' . esc_html($max_pages) . '</span>';
5079 9335 echo '<p class="description">' . esc_html__('Set the maximum number of document pages users can upload for processing. (1-69 pages)', 'mxchat') . '</p>';
5080 -
5081 - if (!$this->is_activated) {
5082 - echo '<div class="pro-feature-overlay">';
5083 - echo '<a href="https://mxchat.ai/" target="_blank">';
5084 - echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
5085 - echo '</a>';
5086 - echo '</div>';
5087 - }
5088 - echo '</div>';
5089 9336 }
5090 9337
5091 9338 public function mxchat_live_agent_status_callback() {
5092 - $disabled = $this->is_activated ? '' : 'disabled';
5093 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
5094 9339 $status = isset($this->options['live_agent_status']) ? $this->options['live_agent_status'] : 'off';
5095 9340
5096 - echo '<div class="' . esc_attr($class) . '">';
5097 9341 echo '<label class="toggle-switch">';
5098 9342 echo sprintf(
5099 - '<input type="checkbox" id="live_agent_status" name="live_agent_status" value="on" %s %s />',
5100 - checked($status, 'on', false),
5101 - esc_attr($disabled)
9343 + '<input type="checkbox" id="live_agent_status" name="live_agent_status" value="on" %s />',
9344 + checked($status, 'on', false)
5102 9345 );
5103 9346 echo '<span class="slider"></span>';
5104 9347 echo '</label>';
5105 9348 echo '<label for="live_agent_status" class="mxchat-status-label">';
@@ -5104,105 +9347,78 @@
5104 9347 echo '</label>';
5105 9348 echo '<label for="live_agent_status" class="mxchat-status-label">';
5106 9349 echo '<span class="status-text">' . ($status === 'on' ? esc_html__('Online', 'mxchat') : esc_html__('Offline', 'mxchat')) . '</span>';
5107 9350 echo '</label>';
5108 -
5109 - if (!$this->is_activated) {
5110 - echo '<div class="pro-feature-overlay">';
5111 - echo '<a href="https://mxchat.ai/" target="_blank"><img src="' . plugin_dir_url(__FILE__) . '../images/pro-only-dark.png" alt="' . esc_attr__('Pro Only', 'mxchat') . '" /></a>';
5112 - echo '</div>';
5113 - }
5114 - echo '</div>';
5115 9351 }
5116 9352
5117 -
5118 -// Away Message Callback
5119 9353 public function mxchat_live_agent_away_message_callback() {
5120 - $disabled = $this->is_activated ? '' : 'disabled';
5121 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
5122 9354 $message = isset($this->options['live_agent_away_message'])
5123 9355 ? $this->options['live_agent_away_message']
5124 9356 : __('Sorry, live agents are currently unavailable. I can continue helping you as an AI assistant.', 'mxchat');
5125 9357
5126 - echo '<div class="' . esc_attr($class) . '">';
5127 9358 printf(
5128 - '<textarea id="live_agent_away_message" name="live_agent_away_message" rows="3" cols="50" %s>%s</textarea>',
5129 - esc_attr($disabled),
9359 + '<textarea id="live_agent_away_message" name="live_agent_away_message" rows="3" cols="50">%s</textarea>',
5130 9360 esc_textarea($message)
5131 9361 );
5132 9362 echo '<p class="description">' . esc_html__('Message shown when live agents are offline.', 'mxchat') . '</p>';
5133 -
5134 - if (!$this->is_activated) {
5135 - echo '<div class="pro-feature-overlay">';
5136 - echo '<a href="https://mxchat.ai/" target="_blank"><img src="' . plugin_dir_url(__FILE__) . '../images/pro-only-dark.png" alt="' . esc_attr__('Pro Only', 'mxchat') . '" /></a>';
5137 - echo '</div>';
5138 - }
5139 - echo '</div>';
5140 9363 }
5141 9364
5142 9365 public function mxchat_live_agent_notification_message_callback() {
5143 - $disabled = $this->is_activated ? '' : 'disabled';
5144 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
5145 9366 $message = isset($this->options['live_agent_notification_message'])
5146 9367 ? $this->options['live_agent_notification_message']
5147 9368 : __('Live agent has been notified.', 'mxchat');
5148 9369
5149 - echo '<div class="' . esc_attr($class) . '">';
5150 9370 printf(
5151 - '<textarea id="live_agent_notification_message" name="live_agent_notification_message" rows="3" cols="50" %s>%s</textarea>',
5152 - esc_attr($disabled),
9371 + '<textarea id="live_agent_notification_message" name="live_agent_notification_message" rows="3" cols="50">%s</textarea>',
5153 9372 esc_textarea($message)
5154 9373 );
5155 9374 echo '<p class="description">' . esc_html__('Message shown when live transfer activated.', 'mxchat') . '</p>';
5156 -
5157 - if (!$this->is_activated) {
5158 - echo '<div class="pro-feature-overlay">';
5159 - echo '<a href="https://mxchat.ai/" target="_blank"><img src="' . plugin_dir_url(__FILE__) . '../images/pro-only-dark.png" alt="' . esc_attr__('Pro Only', 'mxchat') . '" /></a>';
5160 - echo '</div>';
5161 - }
5162 - echo '</div>';
5163 9375 }
5164 9376
5165 9377 public function mxchat_live_agent_webhook_url_callback() {
5166 - $disabled = $this->is_activated ? '' : 'disabled';
5167 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
5168 9378 $webhook_url = isset($this->options['live_agent_webhook_url'])
5169 9379 ? esc_url($this->options['live_agent_webhook_url'])
5170 9380 : esc_url(get_option('live_agent_webhook_url', ''));
5171 9381
5172 - echo '<div class="' . esc_attr($class) . '">';
5173 9382 printf(
5174 - '<input type="password" id="live_agent_webhook_url" name="live_agent_webhook_url" value="%s" class="regular-text" %s />',
5175 - $webhook_url,
5176 - esc_attr($disabled)
9383 + '<input type="password" id="live_agent_webhook_url" name="live_agent_webhook_url" value="%s" class="regular-text" />',
9384 + $webhook_url
5177 9385 );
5178 9386 echo '<button type="button" id="toggleWebhookUrlVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5179 9387 echo '<p class="description">' . esc_html__('Enter your Slack webhook URL for live agent notifications.', 'mxchat') . '</p>';
9388 +}
5180 9389
5181 - if (!$this->is_activated) {
5182 - echo '<div class="pro-feature-overlay">';
5183 - echo '<a href="https://mxchat.ai/" target="_blank"><img src="' . plugin_dir_url(__FILE__) . '../images/pro-only-dark.png" alt="' . esc_attr__('Pro Only', 'mxchat') . '" /></a>';
5184 - echo '</div>';
5185 - }
5186 - echo '</div>';
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>';
5187 9397 }
5188 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 +}
5189 9407
5190 9408 public function mxchat_similarity_threshold_callback() {
5191 9409 // Load from mxchat_options array
5192 9410 $options = get_option('mxchat_options', []);
5193 -
5194 - // Get value with backwards compatibility
5195 - $threshold = isset($options['similarity_threshold'])
5196 - ? $options['similarity_threshold']
5197 - : get_option('mxchat_similarity_threshold', 80);
5198 -
9411 +
9412 + // Get value from options array with default of 80
9413 + $threshold = isset($options['similarity_threshold']) ? $options['similarity_threshold'] : 35;
9414 +
5199 9415 echo '<div class="slider-container">';
5200 9416 echo sprintf(
5201 9417 '<input type="range"
5202 9418 id="similarity_threshold"
5203 9419 name="similarity_threshold"
5204 - min="70"
9420 + min="20"
5205 9421 max="85"
5206 9422 step="1"
5207 9423 value="%s"
5208 9424 class="range-slider" />',
@@ -5212,63 +9428,22 @@
5212 9428 '<span id="threshold_value" class="range-value">%s</span>',
5213 9429 esc_html($threshold)
5214 9430 );
5215 9431 echo '</div>';
5216 -
5217 9432 echo '<p class="description">';
5218 - echo esc_html__('Set the similarity threshold (recommended: 75) to balance accuracy; too high might limit your bot\'s ability to find relevant knowledge.', 'mxchat');
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 + );
5219 9438 echo '</p>';
5220 9439 }
5221 9440
5222 -
5223 -
5224 -public function mxchat_live_agent_secret_key_callback() {
5225 - $disabled = $this->is_activated ? '' : 'disabled';
5226 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
5227 -
5228 - echo '<div class="' . esc_attr($class) . '">';
5229 - printf(
5230 - '<input type="password" id="live_agent_secret_key" name="live_agent_secret_key" value="%s" class="regular-text" %s />',
5231 - isset($this->options['live_agent_secret_key']) ? esc_attr($this->options['live_agent_secret_key']) : '',
5232 - esc_attr($disabled)
5233 - );
5234 - echo '<button type="button" id="toggleSecretKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5235 - echo '<p class="description">' . esc_html__('Secret key for validating Slack requests. Keep this secure.', 'mxchat') . '</p>';
5236 -
5237 - if (!$this->is_activated) {
5238 - echo '<div class="pro-feature-overlay">';
5239 - echo '<a href="https://mxchat.ai/" target="_blank"><img src="' . plugin_dir_url(__FILE__) . '../images/pro-only-dark.png" alt="' . esc_attr__('Pro Only', 'mxchat') . '" /></a>';
5240 - echo '</div>';
5241 - }
5242 - echo '</div>';
5243 -}
5244 -
5245 -public function mxchat_live_agent_bot_token_callback() {
5246 - $disabled = $this->is_activated ? '' : 'disabled';
5247 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
5248 -
5249 - echo '<div class="' . esc_attr($class) . '">';
5250 - printf(
5251 - '<input type="password" id="live_agent_bot_token" name="live_agent_bot_token" value="%s" class="regular-text" %s />',
5252 - isset($this->options['live_agent_bot_token']) ? esc_attr($this->options['live_agent_bot_token']) : '',
5253 - esc_attr($disabled)
5254 - );
5255 - echo '<button type="button" id="toggleBotTokenVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5256 - echo '<p class="description">' . esc_html__('Your Slack Bot OAuth Token (starts with xoxb-). Keep this secure.', 'mxchat') . '</p>';
5257 -
5258 - if (!$this->is_activated) {
5259 - echo '<div class="pro-feature-overlay">';
5260 - echo '<a href="https://mxchat.ai/" target="_blank"><img src="' . plugin_dir_url(__FILE__) . '../images/pro-only-dark.png" alt="' . esc_attr__('Pro Only', 'mxchat') . '" /></a>';
5261 - echo '</div>';
5262 - }
5263 - echo '</div>';
5264 -}
5265 -
5266 9441 public function mxchat_enqueue_admin_assets() {
5267 9442 wp_enqueue_style('wp-color-picker');
5268 9443
5269 9444 // Get the plugin version or file modification time for cache busting
5270 - $plugin_version = '2.0.4'; // Replace this with your plugin's version
9445 + $plugin_version = '2.2.1'; // Replace this with your plugin's version
5271 9446
5272 9447 // File paths
5273 9448 $color_picker_js_path = plugin_dir_path(__FILE__) . '../js/my-color-picker.js';
5274 9449 $embedding_check_js_path = plugin_dir_path(__FILE__) . '../js/embedding-check.js';
@@ -5274,9 +9449,15 @@
5274 9449 $embedding_check_js_path = plugin_dir_path(__FILE__) . '../js/embedding-check.js';
5275 9450 $admin_css_path = plugin_dir_path(__FILE__) . '../css/admin-style.css';
5276 9451 $knowledge_css_path = plugin_dir_path(__FILE__) . '../css/knowledge-style.css';
5277 9452 $intent_css_path = plugin_dir_path(__FILE__) . '../css/intent-style.css';
9453 + $transcripts_css_path = plugin_dir_path(__FILE__) . '../css/chat-transcripts.css';
5278 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';
5279 9460
5280 9461 // Check if files exist and get modification times
5281 9462 $color_picker_version = file_exists($color_picker_js_path) ? filemtime($color_picker_js_path) : $plugin_version;
5282 9463 $embedding_check_version = file_exists($embedding_check_js_path) ? filemtime($embedding_check_js_path) : $plugin_version;
@@ -5282,10 +9463,97 @@
5282 9463 $embedding_check_version = file_exists($embedding_check_js_path) ? filemtime($embedding_check_js_path) : $plugin_version;
5283 9464 $admin_css_version = file_exists($admin_css_path) ? filemtime($admin_css_path) : $plugin_version;
5284 9465 $knowledge_css_version = file_exists($knowledge_css_path) ? filemtime($knowledge_css_path) : $plugin_version;
5285 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;
5286 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 + }
5287 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 +
5288 9556 // Enqueue scripts and styles with corrected paths
5289 9557 wp_enqueue_script(
5290 9558 'mxchat-color-picker',
5291 9559 plugin_dir_url(__FILE__) . '../js/my-color-picker.js',
@@ -5302,16 +9570,8 @@
5302 9570 true
5303 9571 );
5304 9572
5305 9573 wp_enqueue_script(
5306 - 'mxchat-transcripts-js',
5307 - plugin_dir_url(__FILE__) . '../js/mxchat_transcripts.js',
5308 - array('jquery'),
5309 - $transcripts_js_version,
5310 - true
5311 - );
5312 -
5313 - wp_enqueue_script(
5314 9574 'mxchat-admin-js',
5315 9575 plugin_dir_url(__FILE__) . '../js/mxchat-admin.js',
5316 9576 array('jquery'),
5317 9577 $plugin_version,
@@ -5322,8 +9582,17 @@
5322 9582 'ajax_url' => admin_url('admin-ajax.php'),
5323 9583 'license_nonce' => wp_create_nonce('mxchat_activate_license_nonce'),
5324 9584 'inline_edit_nonce' => wp_create_nonce('mxchat_save_inline_nonce'),
5325 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
5326 9595 ));
5327 9596
5328 9597 // Enqueue the admin CSS
5329 9598 wp_enqueue_style(
@@ -5331,30 +9600,59 @@
5331 9600 plugin_dir_url(__FILE__) . '../css/admin-style.css',
5332 9601 array(),
5333 9602 $admin_css_version
5334 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 + }
5335 9632
5336 - wp_enqueue_style(
5337 - 'mxchat-knowledge-css',
5338 - plugin_dir_url(__FILE__) . '../css/knowledge-style.css',
5339 - array(),
5340 - $knowledge_css_version
5341 - );
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 + }
5342 9642
5343 -
5344 - wp_enqueue_style(
5345 - 'mxchat-intent-css',
5346 - plugin_dir_url(__FILE__) . '../css/intent-style.css',
5347 - array(),
5348 - $intent_css_version
5349 - );
5350 -
5351 9643 // IMPORTANT: Use the same script handle as above for localizing mxchatPromptsAdmin
5352 - wp_localize_script( 'mxchat-admin-js', 'mxchatPromptsAdmin', array(
5353 - 'ajax_url' => admin_url( 'admin-ajax.php' ),
5354 - 'prompts_setting_nonce' => wp_create_nonce( 'mxchat_prompts_setting_nonce' ),
5355 - ) );
5356 -
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 +
5357 9655 // Localize the script for color picker and settings
5358 9656 wp_localize_script('mxchat-color-picker', 'mxchatStyleSettings', array(
5359 9657 'ajax_url' => admin_url('admin-ajax.php'),
5360 9658 'link_target_toggle' => $this->options['link_target_toggle'] ?? 'off',
@@ -5383,8 +9681,10 @@
5383 9681 'live_agent_bot_token' => $this->options['live_agent_bot_token'] ?? '',
5384 9682 'live_agent_message_bg_color' => $this->options['live_agent_message_bg_color'] ?? '#ffffff',
5385 9683 'live_agent_message_font_color' => $this->options['live_agent_message_font_color'] ?? '#333333',
5386 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',
5387 9687 'mode_indicator_bg_color' => $this->options['mode_indicator_bg_color'] ?? '#767676',
5388 9688 'mode_indicator_font_color' => $this->options['mode_indicator_font_color'] ?? '#ffffff',
5389 9689 'toolbar_icon_color' => $this->options['toolbar_icon_color'] ?? '#212121',
5390 9690 ));
@@ -5389,9 +9689,8 @@
5389 9689 'toolbar_icon_color' => $this->options['toolbar_icon_color'] ?? '#212121',
5390 9690 ));
5391 9691 }
5392 9692
5393 -
5394 9693 public function mxchat_sanitize($input) {
5395 9694 $new_input = array();
5396 9695
5397 9696 if (isset($input['api_key'])) {
@@ -5399,9 +9698,9 @@
5399 9698 }
5400 9699
5401 9700 if (isset($input['similarity_threshold'])) {
5402 9701 $new_input['similarity_threshold'] = absint($input['similarity_threshold']); // Ensure it's an integer
5403 - $new_input['similarity_threshold'] = min(max($new_input['similarity_threshold'], 70), 85); // Enforce range
9702 + $new_input['similarity_threshold'] = min(max($new_input['similarity_threshold'], 20), 85); // Enforce range
5404 9703 }
5405 9704
5406 9705 if (isset($input['xai_api_key'])) {
5407 9706 $new_input['xai_api_key'] = sanitize_text_field($input['xai_api_key']);
@@ -5413,8 +9712,12 @@
5413 9712
5414 9713 if (isset($input['deepseek_api_key'])) {
5415 9714 $new_input['deepseek_api_key'] = sanitize_text_field($input['deepseek_api_key']);
5416 9715 }
9716 +
9717 + if (isset($input['gemini_api_key'])) {
9718 + $new_input['gemini_api_key'] = sanitize_text_field($input['gemini_api_key']);
9719 + }
5417 9720
5418 9721 if (isset($input['enable_woocommerce_integration'])) {
5419 9722 $new_input['enable_woocommerce_integration'] = $input['enable_woocommerce_integration'] === 'on' ? 'on' : 'off';
5420 9723 }
@@ -5451,8 +9754,12 @@
5451 9754
5452 9755 if (isset($input['top_bar_title'])) {
5453 9756 $new_input['top_bar_title'] = sanitize_text_field($input['top_bar_title']);
5454 9757 }
9758 +
9759 + if (isset($input['ai_agent_text'])) {
9760 + $new_input['ai_agent_text'] = sanitize_text_field($input['ai_agent_text']);
9761 + }
5455 9762
5456 9763 if (isset($input['enable_email_block'])) {
5457 9764 $new_input['enable_email_block'] = sanitize_text_field($input['enable_email_block']);
5458 9765 }
@@ -5465,9 +9772,9 @@
5465 9772 $new_input['email_blocker_button_text'] = sanitize_text_field($input['email_blocker_button_text']);
5466 9773 }
5467 9774
5468 9775 if (isset($input['intro_message'])) {
5469 - $new_input['intro_message'] = sanitize_textarea_field($input['intro_message']); // Changed from sanitize_text_field
9776 + $new_input['intro_message'] = wp_kses_post($input['intro_message']); // Use wp_kses_post instead
5470 9777 }
5471 9778
5472 9779 if (isset($input['input_copy'])) {
5473 9780 $new_input['input_copy'] = sanitize_text_field($input['input_copy']);
@@ -5476,53 +9783,92 @@
5476 9783 if (isset($input['rate_limit_message'])) {
5477 9784 $new_input['rate_limit_message'] = sanitize_text_field($input['rate_limit_message']);
5478 9785 }
5479 9786
5480 - if (isset($input['rate_limit_logged_out'])) {
5481 - $allowed_limits = array('1', '3', '5', '10', '15', '20', '100', 'unlimited'); // Add 'unlimited' to allowed values
5482 - $rate_limit = sanitize_text_field($input['rate_limit_logged_out']);
5483 -
5484 - if (in_array($rate_limit, $allowed_limits, true)) {
5485 - $new_input['rate_limit_logged_out'] = $rate_limit;
5486 - } else {
5487 - $new_input['rate_limit_logged_out'] = '10'; // Default for logged-out users
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 + }
5488 9804 }
5489 - }
5490 -
5491 - // Handle role rate limits
5492 - if (isset($input['role_rate_limits']) && is_array($input['role_rate_limits'])) {
5493 - $allowed_limits = array('1', '3', '5', '10', '15', '20', '100', 'unlimited');
5494 - $new_input['role_rate_limits'] = array();
5495 -
5496 - foreach ($input['role_rate_limits'] as $role_id => $rate_limit) {
5497 - $rate_limit = sanitize_text_field($rate_limit);
5498 - if (in_array($rate_limit, $allowed_limits, true)) {
5499 - $new_input['role_rate_limits'][$role_id] = $rate_limit;
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;
5500 9811 } else {
5501 - $new_input['role_rate_limits'][$role_id] = '100'; // Default
9812 + $new_input['rate_limits'][$role_id]['timeframe'] = 'daily'; // Default
5502 9813 }
5503 9814 }
9815 +
9816 + // Sanitize message
9817 + if (isset($settings['message'])) {
9818 + $new_input['rate_limits'][$role_id]['message'] = sanitize_textarea_field($settings['message']);
9819 + }
5504 9820 }
9821 +}
5505 9822
5506 9823 if (isset($input['pre_chat_message'])) {
5507 9824 $new_input['pre_chat_message'] = sanitize_textarea_field($input['pre_chat_message']);
5508 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 + }
5509 9844
5510 9845 if (isset($input['model'])) {
5511 9846 $allowed_models = array(
5512 - 'grok-beta',
5513 - 'grok-2',
5514 - 'deepseek-chat',
5515 - 'claude-3-5-sonnet-20241022',
5516 - 'claude-3-opus-20240229',
5517 - 'claude-3-sonnet-20240229',
5518 - 'claude-3-haiku-20240307',
5519 - 'gpt-4o',
5520 - 'gpt-4o-mini',
5521 - 'gpt-4-turbo',
5522 - 'gpt-4',
5523 - 'gpt-3.5-turbo',
5524 - );
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 + );
5525 9871 if (in_array($input['model'], $allowed_models)) {
5526 9872 $new_input['model'] = sanitize_text_field($input['model']);
5527 9873 }
5528 9874 }
@@ -5681,8 +10027,22 @@
5681 10027
5682 10028 if (isset($input['chat_toolbar_toggle'])) {
5683 10029 $new_input['chat_toolbar_toggle'] = $input['chat_toolbar_toggle'] === 'on' ? 'on' : 'off';
5684 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 + }
5685 10045
5686 10046 if (isset($input['pdf_intent_trigger_text'])) {
5687 10047 $new_input['pdf_intent_trigger_text'] = sanitize_text_field($input['pdf_intent_trigger_text']);
5688 10048 }
@@ -5737,62 +10097,10 @@
5737 10097 }
5738 10098
5739 10099
5740 10100
5741 -private static function mxchat_extract_main_content($html) {
5742 - if (empty($html)) {
5743 - //error_log('mxchat_extract_main_content: Empty HTML content received');
5744 - return '';
5745 - }
5746 10101
5747 - try {
5748 - $dom = new DOMDocument;
5749 - libxml_use_internal_errors(true); // Suppress HTML parsing errors
5750 10102
5751 - // Simple load of HTML, with @ to suppress warnings
5752 - @$dom->loadHTML($html, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
5753 -
5754 - $xpath = new DOMXPath($dom);
5755 -
5756 - // Simplified selectors focusing on common content areas
5757 - $selectors = [
5758 - '//article',
5759 - '//*[@id="content"]',
5760 - '//*[@class="entry-content"]',
5761 - '//main'
5762 - ];
5763 -
5764 - foreach ($selectors as $selector) {
5765 - $nodes = $xpath->query($selector);
5766 - if ($nodes && $nodes->length > 0) {
5767 - $content = '';
5768 - foreach ($nodes as $node) {
5769 - $content .= $dom->saveHTML($node);
5770 - }
5771 - if (!empty($content)) {
5772 - return $content;
5773 - }
5774 - }
5775 - }
5776 -
5777 - // Fallback: Return the entire body content if no specific selector matches
5778 - $body = $dom->getElementsByTagName('body');
5779 - if ($body->length > 0) {
5780 - return $dom->saveHTML($body->item(0));
5781 - }
5782 -
5783 - // Last resort: return the original HTML
5784 - return $html;
5785 -
5786 - } catch (Exception $e) {
5787 - //error_log('mxchat_extract_main_content error: ' . $e->getMessage());
5788 - return $html; // Return original HTML if parsing fails
5789 - } finally {
5790 - libxml_clear_errors();
5791 - }
5792 -}
5793 -
5794 -
5795 10103 private function mxchat_fetch_loops_mailing_lists($api_key) {
5796 10104 $url = 'https://app.loops.so/api/v1/lists';
5797 10105 $response = wp_remote_get($url, array(
5798 10106 'headers' => array(
@@ -5810,12 +10118,8 @@
5810 10118
5811 10119 return isset($lists) && is_array($lists) ? $lists : array();
5812 10120 }
5813 10121
5814 -
5815 -
5816 -
5817 -
5818 10122 function mxchat_calculate_cosine_similarity($vec1, $vec2) {
5819 10123 if (empty($vec1) || empty($vec2)) {
5820 10124 return 0.0;
5821 10125 }
@@ -5835,10 +10139,8 @@
5835 10139 } else {
5836 10140 return $dot_product / (sqrt($norm_a) * sqrt($norm_b));
5837 10141 }
5838 10142 }
5839 -
5840 -
5841 10143
5842 10144
5843 10145 }
5844 10146 ?>