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

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

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