PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 3.0.8
MxChat – AI Chatbot & Content Generation for WordPress v3.0.8
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.8, at includes/class-mxchat-admin.php

8,709 lines 359.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 if (!defined('ABSPATH')) {
3 exit; // Exit if accessed directly
4 }
5
6 class MxChat_Admin {
7 private $options;
8 private $chat_count;
9 private $is_activated;
10 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' => 'off',
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 // Debug settings
203 'debug_mode' => 'off',
204 );
205
206
207 // Merge existing options with defaults
208 $existing_options = get_option('mxchat_options', array());
209 $merged_options = wp_parse_args($existing_options, $default_options);
210
211 // Update the options if they have changed
212 if ($existing_options !== $merged_options) {
213 update_option('mxchat_options', $merged_options);
214 }
215
216 // Add default limits for each role
217 $roles = wp_roles()->get_names();
218 foreach ($roles as $role_id => $role_name) {
219 $default_options['role_rate_limits'][$role_id] = esc_html__('100', 'mxchat');
220 }
221
222 return $default_options;
223
224 // Update the $this->options property
225 $this->options = $merged_options;
226 }
227
228 public function mxchat_add_plugin_page() {
229 // Main menu page
230 add_menu_page(
231 esc_html__('MxChat Settings', 'mxchat'),
232 esc_html__('MxChat', 'mxchat'),
233 'manage_options',
234 'mxchat-max',
235 array($this, 'mxchat_create_admin_page'),
236 'dashicons-testimonial',
237 6
238 );
239
240 // Rename the first submenu item from "MxChat" to "Settings"
241 add_submenu_page(
242 'mxchat-max',
243 esc_html__('MxChat Settings', 'mxchat'),
244 esc_html__('Settings', 'mxchat'),
245 'manage_options',
246 'mxchat-max',
247 array($this, 'mxchat_create_admin_page')
248 );
249
250 // Submenu page for Knowledge
251 add_submenu_page(
252 'mxchat-max',
253 esc_html__('Prompts', 'mxchat'),
254 esc_html__('Knowledge', 'mxchat'),
255 'manage_options',
256 'mxchat-prompts',
257 array($this, 'mxchat_create_prompts_page')
258 );
259
260 add_submenu_page(
261 'mxchat-max',
262 esc_html__('Chat Transcripts', 'mxchat'),
263 esc_html__('Transcripts', 'mxchat'),
264 'manage_options',
265 'mxchat-transcripts',
266 array($this, 'mxchat_create_transcripts_page')
267 );
268
269 add_submenu_page(
270 'mxchat-max',
271 esc_html__('MxChat Actions', 'mxchat'),
272 esc_html__('Actions', 'mxchat'),
273 'manage_options',
274 'mxchat-actions',
275 array($this, 'mxchat_actions_page_html')
276 );
277
278 // Consolidated Pro & Extensions page (replaces separate Add Ons and Pro Upgrade pages)
279 add_submenu_page(
280 'mxchat-max',
281 esc_html__('Pro & Extensions', 'mxchat'),
282 esc_html__('Pro & Extensions', 'mxchat'),
283 'manage_options',
284 'mxchat-activation',
285 array($this, 'mxchat_create_activation_page')
286 );
287 }
288
289 public function mxchat_create_addons_page() {
290 require_once plugin_dir_path(__FILE__) . 'class-mxchat-addons.php';
291 $addons_page = new MxChat_Addons();
292 $addons_page->render_page();
293 }
294
295 /**
296 * Test actual streaming functionality in the WordPress environment
297 */
298 public function mxchat_handle_test_streaming_actual() {
299 check_ajax_referer('mxchat_test_streaming_nonce', 'nonce');
300
301 // Check if headers have already been sent
302 if (headers_sent()) {
303 wp_send_json_error(['message' => 'Headers already sent - streaming not possible']);
304 return;
305 }
306
307 // Check for required functions
308 if (!function_exists('curl_init')) {
309 wp_send_json_error(['message' => 'cURL not available - streaming requires cURL']);
310 return;
311 }
312
313 // Get user's selected model and API key
314 $options = get_option('mxchat_options', []);
315 $selected_model = $options['model'] ?? 'gpt-5.1-chat-latest';
316
317 // Get the provider from the model
318 $model_parts = explode('-', $selected_model);
319 $provider = strtolower($model_parts[0]);
320
321 // Get the appropriate API key
322 $api_key = '';
323 switch ($provider) {
324 case 'gpt':
325 case 'o1':
326 $api_key = $options['api_key'] ?? '';
327 break;
328 case 'claude':
329 $api_key = $options['claude_api_key'] ?? '';
330 break;
331 case 'grok':
332 $api_key = $options['xai_api_key'] ?? '';
333 break;
334 case 'deepseek':
335 $api_key = $options['deepseek_api_key'] ?? '';
336 break;
337 case 'gemini':
338 $api_key = $options['gemini_api_key'] ?? '';
339 break;
340 default:
341 // Default to OpenAI for unknown models
342 $api_key = $options['api_key'] ?? '';
343 $provider = 'gpt';
344 break;
345 }
346
347 if (empty($api_key)) {
348 wp_send_json_error(['message' => "API key not configured for {$provider} provider"]);
349 return;
350 }
351
352 // Test streaming with the selected model and provider
353 try {
354 $this->perform_streaming_test($provider, $selected_model, $api_key);
355 } catch (Exception $e) {
356 wp_send_json_error(['message' => 'Streaming test exception: ' . $e->getMessage()]);
357 }
358 }
359
360 /**
361 * Perform the actual streaming test
362 */
363 private function perform_streaming_test($provider, $model, $api_key) {
364 // Set streaming headers
365 header('Content-Type: text/event-stream');
366 header('Cache-Control: no-cache');
367 header('X-Accel-Buffering: no'); // Disable nginx buffering
368
369 // Prepare test message
370 $test_message = "Please respond with exactly: 'Streaming test successful!' - send this as a short response for testing.";
371
372 // Configure API request based on provider
373 $url = '';
374 $headers = [];
375 $body = [];
376
377 switch ($provider) {
378 case 'gpt':
379 case 'o1':
380 $url = 'https://api.openai.com/v1/chat/completions';
381 $headers = [
382 'Content-Type: application/json',
383 'Authorization: Bearer ' . $api_key
384 ];
385 $body = [
386 'model' => $model,
387 'messages' => [['role' => 'user', 'content' => $test_message]],
388 'max_tokens' => 50,
389 'temperature' => 0.3,
390 'stream' => true
391 ];
392 break;
393
394 case 'claude':
395 $url = 'https://api.anthropic.com/v1/messages';
396 $headers = [
397 'Content-Type: application/json',
398 'x-api-key: ' . $api_key,
399 'anthropic-version: 2023-06-01'
400 ];
401 $body = [
402 'model' => $model,
403 'messages' => [['role' => 'user', 'content' => $test_message]],
404 'max_tokens' => 50,
405 'temperature' => 0.3,
406 'stream' => true
407 ];
408 break;
409
410 case 'grok':
411 $url = 'https://api.x.ai/v1/chat/completions';
412 $headers = [
413 'Content-Type: application/json',
414 'Authorization: Bearer ' . $api_key
415 ];
416 $body = [
417 'model' => $model,
418 'messages' => [['role' => 'user', 'content' => $test_message]],
419 'max_tokens' => 50,
420 'temperature' => 0.3,
421 'stream' => true
422 ];
423 break;
424
425 case 'deepseek':
426 $url = 'https://api.deepseek.com/v1/chat/completions';
427 $headers = [
428 'Content-Type: application/json',
429 'Authorization: Bearer ' . $api_key
430 ];
431 $body = [
432 'model' => $model,
433 'messages' => [['role' => 'user', 'content' => $test_message]],
434 'max_tokens' => 50,
435 'temperature' => 0.3,
436 'stream' => true
437 ];
438 break;
439
440 default:
441 echo "data: " . json_encode(['error' => 'Unsupported provider for streaming test: ' . $provider]) . "\n\n";
442 flush();
443 return;
444 }
445
446 // Initialize cURL
447 $ch = curl_init();
448 curl_setopt($ch, CURLOPT_URL, $url);
449 curl_setopt($ch, CURLOPT_RETURNTRANSFER, false);
450 curl_setopt($ch, CURLOPT_POST, true);
451 curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
452 curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
453 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
454 curl_setopt($ch, CURLOPT_TIMEOUT, 30);
455 curl_setopt($ch, CURLOPT_WRITEFUNCTION, function($ch, $data) use ($provider) {
456 return $this->process_streaming_test_data($data, $provider);
457 });
458
459 // Send initial test message
460 echo "data: " . json_encode(['content' => '[Starting streaming test...]']) . "\n\n";
461 flush();
462
463 $result = curl_exec($ch);
464 $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
465 $curl_error = curl_error($ch);
466 curl_close($ch);
467
468 if ($curl_error) {
469 echo "data: " . json_encode(['error' => 'cURL Error: ' . $curl_error]) . "\n\n";
470 flush();
471 return;
472 }
473
474 if ($http_code !== 200) {
475 echo "data: " . json_encode(['error' => 'API returned HTTP ' . $http_code]) . "\n\n";
476 flush();
477 return;
478 }
479
480 // Send completion signal
481 echo "data: [DONE]\n\n";
482 flush();
483 }
484
485 /**
486 * Process streaming data for the test
487 */
488 private function process_streaming_test_data($data, $provider) {
489 static $chunk_count = 0;
490
491 $lines = explode("\n", $data);
492
493 foreach ($lines as $line) {
494 if (trim($line) === '') {
495 continue;
496 }
497
498 // Handle different provider formats
499 if ($provider === 'claude') {
500 // Claude uses event: and data: format
501 if (strpos($line, 'data: ') === 0) {
502 $json_str = substr($line, 6);
503 $json = json_decode($json_str, true);
504
505 if (isset($json['type']) && $json['type'] === 'content_block_delta') {
506 if (isset($json['delta']['text'])) {
507 $chunk_count++;
508 echo "data: " . json_encode([
509 'content' => $json['delta']['text'],
510 'test_chunk' => $chunk_count
511 ]) . "\n\n";
512 flush();
513 }
514 }
515 }
516 } else {
517 // OpenAI, X.AI, DeepSeek format
518 if (strpos($line, 'data: ') === 0) {
519 $json_str = substr($line, 6);
520
521 if ($json_str === '[DONE]') {
522 // Don't echo [DONE] here, let the main function handle it
523 continue;
524 }
525
526 $json = json_decode($json_str, true);
527 if (isset($json['choices'][0]['delta']['content'])) {
528 $chunk_count++;
529 echo "data: " . json_encode([
530 'content' => $json['choices'][0]['delta']['content'],
531 'test_chunk' => $chunk_count
532 ]) . "\n\n";
533 flush();
534 }
535 }
536 }
537 }
538
539 return strlen($data);
540 }
541
542 /**
543 * Updated version of your existing test method (keep this as a fallback)
544 */
545 public function mxchat_handle_test_streaming() {
546 check_ajax_referer('mxchat_test_streaming_nonce', 'nonce');
547
548 // Use the actual streaming test instead
549 $this->mxchat_handle_test_streaming_actual();
550 }
551
552
553 public function register_pinecone_settings() {
554 register_setting(
555 'mxchat_pinecone_addon_options',
556 'mxchat_pinecone_addon_options',
557 array(
558 'type' => 'array',
559 'sanitize_callback' => array($this, 'sanitize_pinecone_settings'),
560 'default' => array(
561 'mxchat_use_pinecone' => '0',
562 'mxchat_pinecone_api_key' => '',
563 'mxchat_pinecone_host' => '',
564 'mxchat_pinecone_index' => '',
565 'mxchat_pinecone_environment' => ''
566 )
567 )
568 );
569 }
570
571 public function sanitize_pinecone_settings($input) {
572 $sanitized = array();
573
574 $sanitized['mxchat_use_pinecone'] = isset($input['mxchat_use_pinecone']) ? '1' : '0';
575 $sanitized['mxchat_pinecone_api_key'] = sanitize_text_field($input['mxchat_pinecone_api_key'] ?? '');
576 $sanitized['mxchat_pinecone_host'] = sanitize_text_field($input['mxchat_pinecone_host'] ?? '');
577 $sanitized['mxchat_pinecone_index'] = sanitize_text_field($input['mxchat_pinecone_index'] ?? '');
578 $sanitized['mxchat_pinecone_environment'] = sanitize_text_field($input['mxchat_pinecone_environment'] ?? '');
579
580 // Remove https:// from host if present
581 $sanitized['mxchat_pinecone_host'] = str_replace(['https://', 'http://'], '', $sanitized['mxchat_pinecone_host']);
582
583 return $sanitized;
584 }
585
586 public function register_openai_vectorstore_settings() {
587 register_setting(
588 'mxchat_openai_vectorstore_options',
589 'mxchat_openai_vectorstore_options',
590 array(
591 'type' => 'array',
592 'sanitize_callback' => array($this, 'sanitize_openai_vectorstore_settings'),
593 'default' => array(
594 'mxchat_use_openai_vectorstore' => '0',
595 'mxchat_vectorstore_ids' => '',
596 'mxchat_vectorstore_max_results' => 5
597 )
598 )
599 );
600 }
601
602 public function sanitize_openai_vectorstore_settings($input) {
603 $sanitized = array();
604
605 $sanitized['mxchat_use_openai_vectorstore'] = isset($input['mxchat_use_openai_vectorstore']) ? '1' : '0';
606 $sanitized['mxchat_vectorstore_ids'] = sanitize_text_field($input['mxchat_vectorstore_ids'] ?? '');
607 $sanitized['mxchat_vectorstore_max_results'] = absint($input['mxchat_vectorstore_max_results'] ?? 5);
608
609 // Ensure max results is within reasonable range
610 if ($sanitized['mxchat_vectorstore_max_results'] < 1) {
611 $sanitized['mxchat_vectorstore_max_results'] = 1;
612 }
613 if ($sanitized['mxchat_vectorstore_max_results'] > 20) {
614 $sanitized['mxchat_vectorstore_max_results'] = 20;
615 }
616
617 return $sanitized;
618 }
619
620 /**
621 * AJAX handler to test OpenAI Vector Store connection
622 */
623 public function mxchat_test_vectorstore_connection() {
624 check_ajax_referer('mxchat_admin_nonce', 'nonce');
625
626 if (!current_user_can('manage_options')) {
627 wp_send_json_error(array('message' => __('Permission denied.', 'mxchat')));
628 return;
629 }
630
631 $vectorstore_options = get_option('mxchat_openai_vectorstore_options', array());
632 $vectorstore_ids = $vectorstore_options['mxchat_vectorstore_ids'] ?? '';
633 $mxchat_options = get_option('mxchat_options', array());
634 $api_key = $mxchat_options['api_key'] ?? '';
635
636 if (empty($api_key)) {
637 wp_send_json_error(array('message' => __('OpenAI API key is not configured.', 'mxchat')));
638 return;
639 }
640
641 if (empty($vectorstore_ids)) {
642 wp_send_json_error(array('message' => __('No Vector Store ID configured.', 'mxchat')));
643 return;
644 }
645
646 // Get the first Vector Store ID for testing
647 $ids_array = array_map('trim', explode(',', $vectorstore_ids));
648 $test_id = $ids_array[0];
649
650 // Test by retrieving the Vector Store info
651 $response = wp_remote_get(
652 'https://api.openai.com/v1/vector_stores/' . $test_id,
653 array(
654 'headers' => array(
655 'Authorization' => 'Bearer ' . $api_key,
656 'Content-Type' => 'application/json',
657 'OpenAI-Beta' => 'assistants=v2'
658 ),
659 'timeout' => 30
660 )
661 );
662
663 if (is_wp_error($response)) {
664 wp_send_json_error(array('message' => __('Connection failed: ', 'mxchat') . $response->get_error_message()));
665 return;
666 }
667
668 $status_code = wp_remote_retrieve_response_code($response);
669 $body = json_decode(wp_remote_retrieve_body($response), true);
670
671 if ($status_code === 200 && isset($body['id'])) {
672 $file_count = $body['file_counts']['completed'] ?? 0;
673 $name = $body['name'] ?? $test_id;
674 wp_send_json_success(array(
675 'message' => sprintf(
676 __('Connected successfully! Vector Store: %s (%d files)', 'mxchat'),
677 esc_html($name),
678 $file_count
679 )
680 ));
681 } elseif ($status_code === 404) {
682 wp_send_json_error(array('message' => __('Vector Store not found. Please check the ID.', 'mxchat')));
683 } elseif ($status_code === 401) {
684 wp_send_json_error(array('message' => __('Invalid API key.', 'mxchat')));
685 } else {
686 $error_message = $body['error']['message'] ?? __('Unknown error occurred.', 'mxchat');
687 wp_send_json_error(array('message' => $error_message));
688 }
689 }
690
691 /**
692 * AJAX handler to test Slack connection and validate scopes
693 */
694 public function mxchat_test_slack_connection() {
695 check_ajax_referer('mxchat_admin_nonce', 'nonce');
696
697 if (!current_user_can('manage_options')) {
698 wp_send_json_error(array('message' => __('Permission denied.', 'mxchat')));
699 return;
700 }
701
702 $bot_token = $this->options['live_agent_bot_token'] ?? '';
703
704 if (empty($bot_token)) {
705 wp_send_json_error(array('message' => __('Slack Bot Token is not configured. Please enter your token and save settings first.', 'mxchat')));
706 return;
707 }
708
709 // Test 1: Validate bot token with auth.test
710 $auth_response = wp_remote_post('https://slack.com/api/auth.test', array(
711 'headers' => array(
712 'Authorization' => 'Bearer ' . $bot_token,
713 'Content-Type' => 'application/json'
714 ),
715 'timeout' => 15
716 ));
717
718 if (is_wp_error($auth_response)) {
719 wp_send_json_error(array('message' => __('Connection failed: ', 'mxchat') . $auth_response->get_error_message()));
720 return;
721 }
722
723 $auth_body = json_decode(wp_remote_retrieve_body($auth_response), true);
724
725 if (!isset($auth_body['ok']) || !$auth_body['ok']) {
726 $error = $auth_body['error'] ?? 'unknown_error';
727 $error_messages = array(
728 'invalid_auth' => __('Invalid bot token. Please check your token starts with xoxb-', 'mxchat'),
729 'not_authed' => __('No authentication token provided.', 'mxchat'),
730 'account_inactive' => __('The Slack workspace has been deactivated.', 'mxchat'),
731 'token_revoked' => __('The bot token has been revoked. Please generate a new one.', 'mxchat'),
732 );
733 $message = $error_messages[$error] ?? sprintf(__('Authentication failed: %s', 'mxchat'), $error);
734 wp_send_json_error(array('message' => $message));
735 return;
736 }
737
738 $team_name = $auth_body['team'] ?? 'Unknown Workspace';
739 $bot_name = $auth_body['user'] ?? 'Unknown Bot';
740
741 // Test 2: Check if we can list channels (tests channels:read scope)
742 $channels_response = wp_remote_post('https://slack.com/api/conversations.list', array(
743 'headers' => array(
744 'Authorization' => 'Bearer ' . $bot_token,
745 'Content-Type' => 'application/json'
746 ),
747 'body' => json_encode(array('limit' => 1)),
748 'timeout' => 15
749 ));
750
751 $channels_body = json_decode(wp_remote_retrieve_body($channels_response), true);
752 $can_read_channels = isset($channels_body['ok']) && $channels_body['ok'];
753
754 // Test 3: Check if we can create channels (tests channels:manage scope)
755 // We'll just check the error message without actually creating
756 $create_response = wp_remote_post('https://slack.com/api/conversations.create', array(
757 'headers' => array(
758 'Authorization' => 'Bearer ' . $bot_token,
759 'Content-Type' => 'application/json'
760 ),
761 'body' => json_encode(array('name' => 'mxchat-test-' . time(), 'is_private' => false)),
762 'timeout' => 15
763 ));
764
765 $create_body = json_decode(wp_remote_retrieve_body($create_response), true);
766
767 // If channel was created, delete it immediately
768 if (isset($create_body['ok']) && $create_body['ok'] && isset($create_body['channel']['id'])) {
769 wp_remote_post('https://slack.com/api/conversations.archive', array(
770 'headers' => array(
771 'Authorization' => 'Bearer ' . $bot_token,
772 'Content-Type' => 'application/json'
773 ),
774 'body' => json_encode(array('channel' => $create_body['channel']['id'])),
775 'timeout' => 15
776 ));
777 $can_create_channels = true;
778 } else {
779 $create_error = $create_body['error'] ?? '';
780 // name_taken means we have permission but channel exists
781 $can_create_channels = ($create_error === 'name_taken' || (isset($create_body['ok']) && $create_body['ok']));
782
783 // Check for missing scope errors
784 if ($create_error === 'missing_scope') {
785 $can_create_channels = false;
786 }
787 }
788
789 // Build result message
790 $results = array();
791 $results[] = sprintf(__('Workspace: %s', 'mxchat'), esc_html($team_name));
792 $results[] = sprintf(__('Bot: %s', 'mxchat'), esc_html($bot_name));
793 $results[] = '';
794 $results[] = ($can_read_channels ? '' : '') . ' ' . __('channels:read - List channels', 'mxchat');
795 $results[] = ($can_create_channels ? '' : '') . ' ' . __('channels:manage - Create channels', 'mxchat');
796
797 $missing_scopes = array();
798 if (!$can_read_channels) $missing_scopes[] = 'channels:read';
799 if (!$can_create_channels) $missing_scopes[] = 'channels:manage';
800
801 if (!empty($missing_scopes)) {
802 wp_send_json_error(array(
803 'message' => implode("\n", $results),
804 'missing_scopes' => $missing_scopes,
805 'partial' => true
806 ));
807 } else {
808 wp_send_json_success(array(
809 'message' => implode("\n", $results)
810 ));
811 }
812 }
813
814 public function mxchat_display_admin_notice() {
815 // Success notice
816 if ($message = get_transient('mxchat_admin_notice_success')) {
817 ?>
818 <div class="notice notice-success is-dismissible">
819 <p><?php echo esc_html($message); ?></p>
820 <button type="button" class="notice-dismiss"><span class="screen-reader-text"><?php echo esc_html__('Dismiss this notice.', 'mxchat'); ?></span></button>
821 </div>
822 <?php
823 delete_transient('mxchat_admin_notice_success'); // Clear the transient after displaying
824 }
825
826 // Error notice
827 if ($message = get_transient('mxchat_admin_notice_error')) {
828 ?>
829 <div class="notice notice-error is-dismissible">
830 <p><?php echo esc_html($message); ?></p>
831 <button type="button" class="notice-dismiss"><span class="screen-reader-text"><?php echo esc_html__('Dismiss this notice.', 'mxchat'); ?></span></button>
832 </div>
833 <?php
834 delete_transient('mxchat_admin_notice_error'); // Clear the transient after displaying
835 }
836 }
837
838 public function show_live_agent_disabled_banner() {
839 $show_disabled_notice = get_option('mxchat_show_live_agent_disabled_notice', false);
840
841 if ($show_disabled_notice) {
842 ?>
843 <div class="mxchat-live-agent-disabled-notice" id="mxchat-disabled-notice">
844 <div class="mxchat-pro-notification">
845 <button type="button" class="mxchat-dismiss-btn" onclick="dismissLiveAgentNotice()" aria-label="Dismiss notification">
846 <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
847 <line x1="18" y1="6" x2="6" y2="18"></line>
848 <line x1="6" y1="6" x2="18" y2="18"></line>
849 </svg>
850 </button>
851 <div class="mxchat-live-agent-content">
852 <h3>🔧 Live Agent Integration Updated!</h3>
853 <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>
854 </div>
855 </div>
856 </div>
857 <?php
858 }
859 }
860 public function mxchat_create_admin_page() {
861 $this->add_live_agent_nonce();
862 $this->add_theme_migration_nonce();
863
864 // Include and render the new sidebar-based settings page
865 require_once plugin_dir_path(__FILE__) . 'admin-settings-page.php';
866 mxchat_render_settings_page($this);
867 }
868
869 public function dismiss_live_agent_notice() {
870 // Add debugging
871 //error_log('dismiss_live_agent_notice called');
872 //error_log('POST data: ' . print_r($_POST, true));
873
874 // Verify nonce
875 if (!wp_verify_nonce($_POST['nonce'], 'dismiss_live_agent_notice')) {
876 //error_log('Nonce verification failed');
877 wp_die('Security check failed');
878 }
879
880 // Remove the notice flag
881 $deleted = delete_option('mxchat_show_live_agent_disabled_notice');
882 //error_log('Option deleted: ' . ($deleted ? 'yes' : 'no'));
883
884 wp_send_json_success();
885 }
886 public function add_live_agent_nonce() {
887 if (get_option('mxchat_show_live_agent_disabled_notice', false)) {
888 // Make sure your admin script is enqueued and localize the data
889 wp_localize_script('mxchat-admin-js', 'mxchatLiveAgent', array(
890 'nonce' => wp_create_nonce('dismiss_live_agent_notice'),
891 'ajaxurl' => admin_url('admin-ajax.php')
892 ));
893 }
894 }
895
896 /**
897 * Show theme migration notice for Pro users with AI-generated themes
898 * Only shown once - dismissible and stored in options
899 */
900 public function show_theme_migration_banner() {
901 // Only show if Pro is activated
902 if (!$this->is_activated) {
903 return;
904 }
905
906 // Check if notice should be shown
907 $show_notice = get_option('mxchat_show_theme_migration_notice', false);
908
909 if ($show_notice) {
910 ?>
911 <div class="mxchat-theme-migration-notice" id="mxchat-theme-migration-notice">
912 <div class="mxchat-pro-notification">
913 <button type="button" class="mxchat-dismiss-btn" onclick="dismissThemeMigrationNotice()" aria-label="Dismiss notification">
914 <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
915 <line x1="18" y1="6" x2="6" y2="18"></line>
916 <line x1="6" y1="6" x2="18" y2="18"></line>
917 </svg>
918 </button>
919 <div class="mxchat-theme-migration-content">
920 <h3>🎨 AI Theme Migration Required</h3>
921 <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>
922 </div>
923 </div>
924 </div>
925 <?php
926 }
927 }
928
929 /**
930 * Dismiss theme migration notice via AJAX
931 */
932 public function dismiss_theme_migration_notice() {
933 // Verify nonce
934 if (!wp_verify_nonce($_POST['nonce'], 'dismiss_theme_migration_notice')) {
935 wp_die('Security check failed');
936 }
937
938 // Remove the notice flag
939 delete_option('mxchat_show_theme_migration_notice');
940
941 wp_send_json_success();
942 }
943
944 /**
945 * Add nonce for theme migration notice dismiss
946 */
947 public function add_theme_migration_nonce() {
948 if (get_option('mxchat_show_theme_migration_notice', false) && $this->is_activated) {
949 wp_localize_script('mxchat-admin-js', 'mxchatThemeMigration', array(
950 'nonce' => wp_create_nonce('dismiss_theme_migration_notice'),
951 'ajaxurl' => admin_url('admin-ajax.php')
952 ));
953 }
954 }
955
956 public function mxchat_create_transcripts_page() {
957 global $wpdb;
958 $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
959
960 // Get basic stats
961 $total_chats = $wpdb->get_var("SELECT COUNT(DISTINCT session_id) FROM $table_name") ?: 0;
962 $total_messages = $wpdb->get_var("SELECT COUNT(*) FROM $table_name") ?: 0;
963
964 // Count unique users with detailed breakdown
965 $total_users = $wpdb->get_var("
966 SELECT COUNT(DISTINCT
967 CASE
968 WHEN user_email != '' AND user_email IS NOT NULL THEN user_email
969 WHEN user_id != 0 THEN CONCAT('user_', user_id)
970 WHEN user_identifier NOT LIKE 'Tech-Savvy User'
971 AND user_identifier NOT LIKE 'Detail-Oriented User'
972 AND user_identifier NOT LIKE 'Language Learner'
973 AND user_identifier NOT LIKE 'Casual Browser'
974 AND user_identifier NOT LIKE 'Policy Enforcer'
975 AND user_identifier NOT LIKE 'Researcher'
976 AND user_identifier NOT LIKE 'Loyalty Member'
977 AND user_identifier NOT LIKE 'Gift Buyer'
978 AND user_identifier NOT LIKE 'Parent or Caregiver'
979 THEN user_identifier
980 ELSE session_id
981 END
982 )
983 FROM $table_name
984 WHERE role != 'assistant'
985 ");
986
987 // Get user type breakdown
988 $registered_users = $wpdb->get_var("
989 SELECT COUNT(DISTINCT user_email)
990 FROM $table_name
991 WHERE user_email != '' AND user_email IS NOT NULL
992 ");
993
994 $guest_users = $wpdb->get_var("
995 SELECT COUNT(DISTINCT user_identifier)
996 FROM $table_name
997 WHERE (user_email = '' OR user_email IS NULL)
998 AND role != 'assistant'
999 AND user_identifier NOT LIKE 'Tech-Savvy User'
1000 AND user_identifier NOT LIKE 'Detail-Oriented User'
1001 AND user_identifier NOT LIKE 'Language Learner'
1002 AND user_identifier NOT LIKE 'Casual Browser'
1003 AND user_identifier NOT LIKE 'Policy Enforcer'
1004 AND user_identifier NOT LIKE 'Researcher'
1005 AND user_identifier NOT LIKE 'Loyalty Member'
1006 AND user_identifier NOT LIKE 'Gift Buyer'
1007 AND user_identifier NOT LIKE 'Parent or Caregiver'
1008 ");
1009
1010 // Get agent test messages count
1011 $agent_tests = $wpdb->get_var("
1012 SELECT COUNT(DISTINCT session_id)
1013 FROM $table_name
1014 WHERE user_identifier IN (
1015 'Tech-Savvy User',
1016 'Detail-Oriented User',
1017 'Language Learner',
1018 'Casual Browser',
1019 'Policy Enforcer',
1020 'Researcher',
1021 'Loyalty Member',
1022 'Gift Buyer',
1023 'Parent or Caregiver'
1024 )
1025 ");
1026 // Get activity metrics
1027 $today_chats = $wpdb->get_var("
1028 SELECT COUNT(DISTINCT session_id)
1029 FROM $table_name
1030 WHERE DATE(timestamp) = CURDATE()
1031 ");
1032
1033 $week_chats = $wpdb->get_var("
1034 SELECT COUNT(DISTINCT session_id)
1035 FROM $table_name
1036 WHERE timestamp >= DATE_SUB(NOW(), INTERVAL 7 DAY)
1037 ");
1038
1039 $month_chats = $wpdb->get_var("
1040 SELECT COUNT(DISTINCT session_id)
1041 FROM $table_name
1042 WHERE timestamp >= DATE_SUB(NOW(), INTERVAL 30 DAY)
1043 ");
1044
1045 // Get daily chat data for last 7 days
1046 $daily_stats = $wpdb->get_results("
1047 SELECT
1048 DATE(timestamp) as date,
1049 COUNT(DISTINCT session_id) as chats,
1050 COUNT(*) as messages
1051 FROM $table_name
1052 WHERE timestamp >= DATE_SUB(NOW(), INTERVAL 7 DAY)
1053 GROUP BY DATE(timestamp)
1054 ORDER BY date ASC
1055 ");
1056
1057 // Get average messages per chat
1058 $avg_messages = $wpdb->get_var("
1059 SELECT AVG(message_count)
1060 FROM (
1061 SELECT session_id, COUNT(*) as message_count
1062 FROM $table_name
1063 GROUP BY session_id
1064 ) as chat_counts
1065 ");
1066 $avg_messages = $avg_messages ? round($avg_messages, 1) : 0;
1067
1068 // Get busiest hour
1069 $busiest_hour = $wpdb->get_row("
1070 SELECT HOUR(timestamp) as hour, COUNT(DISTINCT session_id) as chat_count
1071 FROM $table_name
1072 GROUP BY HOUR(timestamp)
1073 ORDER BY chat_count DESC
1074 LIMIT 1
1075 ");
1076
1077 // Prepare chart data
1078 $chart_labels = array();
1079 $chart_chats = array();
1080 $chart_messages = array();
1081
1082 // Fill last 7 days with data
1083 for ($i = 6; $i >= 0; $i--) {
1084 $date = date('Y-m-d', strtotime("-$i days"));
1085 $day_name = date('D', strtotime("-$i days"));
1086 $chart_labels[] = $day_name;
1087
1088 $found = false;
1089 foreach ($daily_stats as $stat) {
1090 if ($stat->date === $date) {
1091 $chart_chats[] = (int)$stat->chats;
1092 $chart_messages[] = (int)$stat->messages;
1093 $found = true;
1094 break;
1095 }
1096 }
1097 if (!$found) {
1098 $chart_chats[] = 0;
1099 $chart_messages[] = 0;
1100 }
1101 }
1102
1103 // Prepare page data for the template
1104 $page_data = array(
1105 'total_chats' => $total_chats,
1106 'total_messages' => $total_messages,
1107 'total_users' => $total_users,
1108 'registered_users' => $registered_users,
1109 'guest_users' => $guest_users,
1110 'agent_tests' => $agent_tests,
1111 'today_chats' => $today_chats,
1112 'week_chats' => $week_chats,
1113 'month_chats' => $month_chats,
1114 'avg_messages' => $avg_messages,
1115 'busiest_hour' => $busiest_hour,
1116 'chart_labels' => $chart_labels,
1117 'chart_chats' => $chart_chats,
1118 'chart_messages' => $chart_messages,
1119 );
1120
1121 // Include and render the new template
1122 require_once plugin_dir_path(__FILE__) . 'admin-transcripts-page.php';
1123 mxchat_render_transcripts_page($this, $page_data);
1124 }
1125
1126 /**
1127 * Get chart data for transcripts page
1128 * Used by both page render and script localization
1129 *
1130 * @return array Chart data with labels, chats, and messages arrays
1131 */
1132 private function get_transcripts_chart_data() {
1133 global $wpdb;
1134 $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
1135
1136 // Get daily chat data for last 7 days - same query as mxchat_transcripts_page()
1137 $daily_stats = $wpdb->get_results("
1138 SELECT
1139 DATE(timestamp) as date,
1140 COUNT(DISTINCT session_id) as chats,
1141 COUNT(*) as messages
1142 FROM $table_name
1143 WHERE timestamp >= DATE_SUB(NOW(), INTERVAL 7 DAY)
1144 GROUP BY DATE(timestamp)
1145 ORDER BY date ASC
1146 ");
1147
1148 // Prepare chart data
1149 $chart_labels = array();
1150 $chart_chats = array();
1151 $chart_messages = array();
1152
1153 // Fill last 7 days with data - same logic as mxchat_transcripts_page()
1154 for ($i = 6; $i >= 0; $i--) {
1155 $date = date('Y-m-d', strtotime("-$i days"));
1156 $day_name = date('D', strtotime("-$i days"));
1157 $chart_labels[] = $day_name;
1158
1159 $found = false;
1160 if ($daily_stats) {
1161 foreach ($daily_stats as $stat) {
1162 if ($stat->date === $date) {
1163 $chart_chats[] = (int)$stat->chats;
1164 $chart_messages[] = (int)$stat->messages;
1165 $found = true;
1166 break;
1167 }
1168 }
1169 }
1170 if (!$found) {
1171 $chart_chats[] = 0;
1172 $chart_messages[] = 0;
1173 }
1174 }
1175
1176 return array(
1177 'labels' => $chart_labels,
1178 'chats' => $chart_chats,
1179 'messages' => $chart_messages
1180 );
1181 }
1182
1183 public function mxchat_transcripts_notification_section_callback() {
1184 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>';
1185 }
1186 public function mxchat_enable_notifications_callback() {
1187 $options = get_option('mxchat_transcripts_options', array());
1188 $enabled = isset($options['mxchat_enable_notifications']) ? $options['mxchat_enable_notifications'] : 0;
1189 ?>
1190 <label for="mxchat_enable_notifications">
1191 <input type="checkbox" id="mxchat_enable_notifications"
1192 name="mxchat_transcripts_options[mxchat_enable_notifications]"
1193 value="1" <?php checked(1, $enabled); ?>>
1194 <?php esc_html_e('Send email notification when a new chat session starts', 'mxchat'); ?>
1195 </label>
1196 <p class="description">
1197 <?php esc_html_e('Enable this option to receive email notifications for new chat sessions.', 'mxchat'); ?>
1198 </p>
1199 <?php
1200 }
1201 public function mxchat_notification_email_callback() {
1202 $options = get_option('mxchat_transcripts_options', array());
1203 $email = isset($options['mxchat_notification_email']) ? $options['mxchat_notification_email'] : get_option('admin_email');
1204 ?>
1205 <input type="email" id="mxchat_notification_email"
1206 name="mxchat_transcripts_options[mxchat_notification_email]"
1207 value="<?php echo esc_attr($email); ?>"
1208 class="regular-text">
1209 <p class="description">
1210 <?php esc_html_e('Enter the email address where notifications should be sent. Defaults to the admin email address.', 'mxchat'); ?>
1211 </p>
1212 <?php
1213 }
1214
1215 public function mxchat_auto_delete_transcripts_callback() {
1216 $options = get_option('mxchat_transcripts_options', array());
1217 $interval = isset($options['mxchat_auto_delete_transcripts']) ? $options['mxchat_auto_delete_transcripts'] : 'never';
1218 ?>
1219 <select id="mxchat_auto_delete_transcripts"
1220 name="mxchat_transcripts_options[mxchat_auto_delete_transcripts]">
1221 <option value="never" <?php selected($interval, 'never'); ?>>
1222 <?php esc_html_e('Never (Keep All Transcripts)', 'mxchat'); ?>
1223 </option>
1224 <option value="1week" <?php selected($interval, '1week'); ?>>
1225 <?php esc_html_e('After 1 Week', 'mxchat'); ?>
1226 </option>
1227 <option value="2weeks" <?php selected($interval, '2weeks'); ?>>
1228 <?php esc_html_e('After 2 Weeks', 'mxchat'); ?>
1229 </option>
1230 <option value="1month" <?php selected($interval, '1month'); ?>>
1231 <?php esc_html_e('After 1 Month', 'mxchat'); ?>
1232 </option>
1233 </select>
1234 <p class="description">
1235 <?php esc_html_e('Automatically delete old chat transcripts after the selected time period. This helps manage database size and privacy.', 'mxchat'); ?>
1236 </p>
1237 <?php
1238 }
1239
1240 public function mxchat_auto_email_transcript_callback() {
1241 $options = get_option('mxchat_transcripts_options', array());
1242 $enabled = isset($options['mxchat_auto_email_transcript_enabled']) ? $options['mxchat_auto_email_transcript_enabled'] : 0;
1243 $delay = isset($options['mxchat_auto_email_transcript_delay']) ? $options['mxchat_auto_email_transcript_delay'] : '30';
1244 $require_contact = isset($options['mxchat_auto_email_transcript_require_contact']) ? $options['mxchat_auto_email_transcript_require_contact'] : 0;
1245 ?>
1246 <label>
1247 <input type="checkbox"
1248 id="mxchat_auto_email_transcript_enabled"
1249 name="mxchat_transcripts_options[mxchat_auto_email_transcript_enabled]"
1250 value="1"
1251 <?php checked($enabled, 1); ?>>
1252 <?php esc_html_e('Enable Auto-Email of Full Transcript', 'mxchat'); ?>
1253 </label>
1254 <br><br>
1255 <label for="mxchat_auto_email_transcript_delay">
1256 <?php esc_html_e('Send transcript after:', 'mxchat'); ?>
1257 </label>
1258 <select id="mxchat_auto_email_transcript_delay"
1259 name="mxchat_transcripts_options[mxchat_auto_email_transcript_delay]">
1260 <option value="15" <?php selected($delay, '15'); ?>>
1261 <?php esc_html_e('15 minutes', 'mxchat'); ?>
1262 </option>
1263 <option value="30" <?php selected($delay, '30'); ?>>
1264 <?php esc_html_e('30 minutes', 'mxchat'); ?>
1265 </option>
1266 <option value="60" <?php selected($delay, '60'); ?>>
1267 <?php esc_html_e('1 hour', 'mxchat'); ?>
1268 </option>
1269 </select>
1270 <br><br>
1271 <label>
1272 <input type="checkbox"
1273 id="mxchat_auto_email_transcript_require_contact"
1274 name="mxchat_transcripts_options[mxchat_auto_email_transcript_require_contact]"
1275 value="1"
1276 <?php checked($require_contact, 1); ?>>
1277 <?php esc_html_e('Only send if visitor provided contact info', 'mxchat'); ?>
1278 </label>
1279 <p class="description">
1280 <?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'); ?>
1281 <br>
1282 <?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'); ?>
1283 </p>
1284 <?php
1285 }
1286
1287
1288
1289 public function sanitize_transcripts_options($input) {
1290 $sanitized = array();
1291
1292 $sanitized['mxchat_enable_notifications'] = isset($input['mxchat_enable_notifications']) ? 1 : 0;
1293
1294 if (isset($input['mxchat_notification_email'])) {
1295 $sanitized['mxchat_notification_email'] = sanitize_email($input['mxchat_notification_email']);
1296 if (!is_email($sanitized['mxchat_notification_email'])) {
1297 add_settings_error(
1298 'mxchat_transcripts_options',
1299 'invalid_email',
1300 __('Please enter a valid email address for notifications.', 'mxchat'),
1301 'error'
1302 );
1303 $sanitized['mxchat_notification_email'] = get_option('admin_email');
1304 }
1305 }
1306
1307 // Sanitize auto-delete setting
1308 $valid_intervals = array('never', '1week', '2weeks', '1month');
1309 if (isset($input['mxchat_auto_delete_transcripts'])) {
1310 $sanitized['mxchat_auto_delete_transcripts'] = in_array($input['mxchat_auto_delete_transcripts'], $valid_intervals)
1311 ? $input['mxchat_auto_delete_transcripts']
1312 : 'never';
1313 } else {
1314 $sanitized['mxchat_auto_delete_transcripts'] = 'never';
1315 }
1316
1317 // Get old value to check if auto-delete setting changed
1318 $old_options = get_option('mxchat_transcripts_options');
1319 $old_interval = isset($old_options['mxchat_auto_delete_transcripts']) ? $old_options['mxchat_auto_delete_transcripts'] : 'never';
1320
1321 // If the auto-delete setting changed, reschedule the cron job
1322 if ($old_interval !== $sanitized['mxchat_auto_delete_transcripts']) {
1323 $this->schedule_transcript_cleanup($sanitized['mxchat_auto_delete_transcripts']);
1324 }
1325
1326 // Sanitize auto-email transcript settings
1327 $sanitized['mxchat_auto_email_transcript_enabled'] = isset($input['mxchat_auto_email_transcript_enabled']) ? 1 : 0;
1328
1329 $valid_delays = array('15', '30', '60');
1330 if (isset($input['mxchat_auto_email_transcript_delay'])) {
1331 $sanitized['mxchat_auto_email_transcript_delay'] = in_array($input['mxchat_auto_email_transcript_delay'], $valid_delays)
1332 ? $input['mxchat_auto_email_transcript_delay']
1333 : '30';
1334 } else {
1335 $sanitized['mxchat_auto_email_transcript_delay'] = '30';
1336 }
1337
1338 // Sanitize require contact info setting
1339 $sanitized['mxchat_auto_email_transcript_require_contact'] = isset($input['mxchat_auto_email_transcript_require_contact']) ? 1 : 0;
1340
1341 return $sanitized;
1342 }
1343
1344 /**
1345 * Schedule or unschedule the transcript cleanup cron job
1346 */
1347 public function schedule_transcript_cleanup($interval) {
1348 // Clear any existing scheduled event
1349 $timestamp = wp_next_scheduled('mxchat_cleanup_old_transcripts');
1350 if ($timestamp) {
1351 wp_unschedule_event($timestamp, 'mxchat_cleanup_old_transcripts');
1352 }
1353
1354 // Schedule new event if not set to "never"
1355 if ($interval !== 'never') {
1356 // Schedule to run daily at 3 AM
1357 $next_run = strtotime('tomorrow 3:00 AM');
1358 wp_schedule_event($next_run, 'daily', 'mxchat_cleanup_old_transcripts');
1359 }
1360 }
1361
1362 /**
1363 * Delete old transcripts based on the configured interval
1364 */
1365 public function cleanup_old_transcripts() {
1366 $options = get_option('mxchat_transcripts_options', array());
1367 $interval = isset($options['mxchat_auto_delete_transcripts']) ? $options['mxchat_auto_delete_transcripts'] : 'never';
1368
1369 // If set to never, don't delete anything
1370 if ($interval === 'never') {
1371 return;
1372 }
1373
1374 // Calculate the cutoff date
1375 $days = 0;
1376 switch ($interval) {
1377 case '1week':
1378 $days = 7;
1379 break;
1380 case '2weeks':
1381 $days = 14;
1382 break;
1383 case '1month':
1384 $days = 30;
1385 break;
1386 default:
1387 return; // Invalid interval, don't delete anything
1388 }
1389
1390 global $wpdb;
1391 $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
1392 $translations_table = $wpdb->prefix . 'mxchat_transcript_translations';
1393
1394 // Calculate the cutoff timestamp
1395 $cutoff_date = date('Y-m-d H:i:s', strtotime("-{$days} days"));
1396
1397 // First, get the session IDs that will be deleted (to clean up translations too)
1398 $sessions_to_delete = $wpdb->get_col(
1399 $wpdb->prepare(
1400 "SELECT DISTINCT session_id FROM {$table_name} WHERE timestamp < %s",
1401 $cutoff_date
1402 )
1403 );
1404
1405 // Delete transcripts older than the cutoff date
1406 $deleted = $wpdb->query(
1407 $wpdb->prepare(
1408 "DELETE FROM {$table_name} WHERE timestamp < %s",
1409 $cutoff_date
1410 )
1411 );
1412
1413 // Also delete any translations for the deleted sessions
1414 if (!empty($sessions_to_delete) && $wpdb->get_var("SHOW TABLES LIKE '$translations_table'") === $translations_table) {
1415 $placeholders = implode(',', array_fill(0, count($sessions_to_delete), '%s'));
1416 $wpdb->query(
1417 $wpdb->prepare(
1418 "DELETE FROM {$translations_table} WHERE session_id IN ($placeholders)",
1419 $sessions_to_delete
1420 )
1421 );
1422 }
1423
1424 // Log the cleanup action
1425 if ($deleted !== false && $deleted > 0) {
1426 error_log(sprintf('MXChat: Auto-deleted %d transcripts older than %d days', $deleted, $days));
1427 }
1428 }
1429
1430 public function export_chat_transcripts() {
1431 if (!current_user_can('manage_options')) {
1432 wp_die(esc_html__('You do not have sufficient permissions to access this page.', 'mxchat'));
1433 }
1434
1435 check_ajax_referer('mxchat_export_transcripts', 'security');
1436
1437 global $wpdb;
1438 $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
1439
1440 // Get all transcripts ordered by session and timestamp
1441 $results = $wpdb->get_results(
1442 "SELECT session_id, user_email, user_identifier, role, message, timestamp
1443 FROM {$table_name}
1444 ORDER BY session_id, timestamp ASC"
1445 );
1446
1447 if (empty($results)) {
1448 wp_send_json_error(array('message' => 'No transcripts found.'));
1449 wp_die();
1450 }
1451
1452 // Set headers for CSV download
1453 header('Content-Type: text/csv');
1454 header('Content-Disposition: attachment; filename="chat-transcripts-' . date('Y-m-d') . '.csv"');
1455 header('Pragma: no-cache');
1456 header('Expires: 0');
1457
1458 // Create output stream
1459 $output = fopen('php://output', 'w');
1460
1461 // Add UTF-8 BOM for proper Excel encoding
1462 fputs($output, "\xEF\xBB\xBF");
1463
1464 // Add CSV headers
1465 fputcsv($output, array(
1466 'Session ID',
1467 'Email',
1468 'User Identifier',
1469 'Role',
1470 'Message',
1471 'Timestamp'
1472 ));
1473
1474 // Add data rows
1475 foreach ($results as $row) {
1476 fputcsv($output, array(
1477 $row->session_id,
1478 $row->user_email,
1479 $row->user_identifier,
1480 $row->role,
1481 $row->message,
1482 $row->timestamp
1483 ));
1484 }
1485
1486 fclose($output);
1487 wp_die();
1488 }
1489
1490 /**
1491 * Handle translation of chat messages via AJAX
1492 */
1493 public function mxchat_translate_messages() {
1494 if (!current_user_can('manage_options')) {
1495 wp_send_json_error(['error' => 'Insufficient permissions']);
1496 wp_die();
1497 }
1498
1499 $session_id = isset($_POST['session_id']) ? sanitize_text_field($_POST['session_id']) : '';
1500 $target_lang = isset($_POST['target_lang']) ? sanitize_text_field($_POST['target_lang']) : 'en';
1501 $messages_json = isset($_POST['messages']) ? wp_unslash($_POST['messages']) : '[]';
1502 $messages = json_decode($messages_json, true);
1503
1504 if (empty($session_id)) {
1505 wp_send_json_error(['error' => 'No session ID provided']);
1506 wp_die();
1507 }
1508
1509 if (empty($messages) || !is_array($messages)) {
1510 wp_send_json_error(['error' => 'No messages to translate']);
1511 wp_die();
1512 }
1513
1514 // Language names for prompting
1515 $languages = [
1516 'en' => 'English',
1517 'es' => 'Spanish',
1518 'fr' => 'French',
1519 'de' => 'German',
1520 'it' => 'Italian',
1521 'pt' => 'Portuguese',
1522 'nl' => 'Dutch',
1523 'ru' => 'Russian',
1524 'zh' => 'Chinese',
1525 'ja' => 'Japanese',
1526 'ko' => 'Korean',
1527 'ar' => 'Arabic',
1528 'hi' => 'Hindi',
1529 'tr' => 'Turkish',
1530 'pl' => 'Polish',
1531 'vi' => 'Vietnamese',
1532 'th' => 'Thai',
1533 'id' => 'Indonesian',
1534 'sv' => 'Swedish',
1535 'da' => 'Danish'
1536 ];
1537
1538 $target_lang_name = isset($languages[$target_lang]) ? $languages[$target_lang] : 'English';
1539
1540 // Build combined text for translation (numbered for parsing)
1541 $numbered_messages = [];
1542 foreach ($messages as $i => $msg) {
1543 $content = isset($msg['content']) ? trim($msg['content']) : '';
1544 if (!empty($content)) {
1545 $numbered_messages[] = "[MSG" . $i . "]" . $content . "[/MSG" . $i . "]";
1546 }
1547 }
1548
1549 if (empty($numbered_messages)) {
1550 wp_send_json_error(['error' => 'No valid messages to translate']);
1551 wp_die();
1552 }
1553
1554 $combined_text = implode("\n\n", $numbered_messages);
1555
1556 // Prepare the translation prompt
1557 $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.";
1558
1559 // Get user's selected model and determine provider
1560 $options = get_option('mxchat_options', []);
1561 $selected_model = $options['model'] ?? 'gpt-5.1-chat-latest';
1562
1563 // Check if using OpenRouter
1564 if ($selected_model === 'openrouter') {
1565 $provider = 'openrouter';
1566 $selected_model = $options['openrouter_selected_model'] ?? '';
1567 $api_key = $options['openrouter_api_key'] ?? '';
1568
1569 if (empty($selected_model)) {
1570 wp_send_json_error(['error' => 'No OpenRouter model selected']);
1571 wp_die();
1572 }
1573 } else {
1574 // Determine provider from model name
1575 $model_parts = explode('-', $selected_model);
1576 $provider = strtolower($model_parts[0]);
1577
1578 // Get the appropriate API key based on provider
1579 $api_key = '';
1580 switch ($provider) {
1581 case 'gpt':
1582 case 'o1':
1583 $api_key = $options['api_key'] ?? '';
1584 break;
1585 case 'claude':
1586 $api_key = $options['claude_api_key'] ?? '';
1587 break;
1588 case 'grok':
1589 $api_key = $options['xai_api_key'] ?? '';
1590 break;
1591 case 'deepseek':
1592 $api_key = $options['deepseek_api_key'] ?? '';
1593 break;
1594 case 'gemini':
1595 $api_key = $options['gemini_api_key'] ?? '';
1596 break;
1597 default:
1598 // Default to OpenAI
1599 $api_key = $options['api_key'] ?? '';
1600 $provider = 'gpt';
1601 break;
1602 }
1603 }
1604
1605 if (empty($api_key)) {
1606 wp_send_json_error(['error' => 'No API key configured for ' . $provider]);
1607 wp_die();
1608 }
1609
1610 // Make API request based on provider
1611 $response = $this->translate_with_provider($provider, $selected_model, $api_key, $system_prompt, $combined_text);
1612
1613 if (is_wp_error($response)) {
1614 wp_send_json_error(['error' => $response->get_error_message()]);
1615 wp_die();
1616 }
1617
1618 // Parse the response to extract translated messages
1619 $translations = [];
1620 foreach ($messages as $msg) {
1621 $index = $msg['index'];
1622 $pattern = '/\[MSG' . $index . '\](.*?)\[\/MSG' . $index . '\]/s';
1623 if (preg_match($pattern, $response, $matches)) {
1624 $translations[] = [
1625 'index' => $index,
1626 'translated' => trim($matches[1])
1627 ];
1628 }
1629 }
1630
1631 // Save translations to database
1632 if (!empty($translations)) {
1633 $this->save_transcript_translation($session_id, $target_lang, $translations);
1634 }
1635
1636 wp_send_json(['success' => true, 'translations' => $translations, 'language' => $target_lang]);
1637 wp_die();
1638 }
1639
1640 /**
1641 * Save transcript translation to database
1642 */
1643 private function save_transcript_translation($session_id, $language_code, $translations) {
1644 global $wpdb;
1645 $table_name = $wpdb->prefix . 'mxchat_transcript_translations';
1646
1647 // Check if table exists, create if not
1648 if ($wpdb->get_var("SHOW TABLES LIKE '$table_name'") !== $table_name) {
1649 mxchat_create_translations_table();
1650 }
1651
1652 $now = current_time('mysql');
1653 $translations_json = wp_json_encode($translations);
1654
1655 // Use REPLACE to insert or update
1656 $wpdb->query($wpdb->prepare(
1657 "REPLACE INTO $table_name (session_id, language_code, translations, created_at, updated_at)
1658 VALUES (%s, %s, %s, %s, %s)",
1659 $session_id,
1660 $language_code,
1661 $translations_json,
1662 $now,
1663 $now
1664 ));
1665 }
1666
1667 /**
1668 * Get saved translation for a session
1669 */
1670 public function mxchat_get_transcript_translation() {
1671 if (!current_user_can('manage_options')) {
1672 wp_send_json_error(['error' => 'Insufficient permissions']);
1673 wp_die();
1674 }
1675
1676 $session_id = isset($_POST['session_id']) ? sanitize_text_field($_POST['session_id']) : '';
1677
1678 if (empty($session_id)) {
1679 wp_send_json_error(['error' => 'No session ID provided']);
1680 wp_die();
1681 }
1682
1683 global $wpdb;
1684 $table_name = $wpdb->prefix . 'mxchat_transcript_translations';
1685
1686 // Check if table exists
1687 if ($wpdb->get_var("SHOW TABLES LIKE '$table_name'") !== $table_name) {
1688 wp_send_json(['success' => true, 'has_translation' => false]);
1689 wp_die();
1690 }
1691
1692 // Get the most recent translation for this session
1693 $result = $wpdb->get_row($wpdb->prepare(
1694 "SELECT language_code, translations FROM $table_name WHERE session_id = %s ORDER BY updated_at DESC LIMIT 1",
1695 $session_id
1696 ));
1697
1698 if ($result) {
1699 $translations = json_decode($result->translations, true);
1700 wp_send_json([
1701 'success' => true,
1702 'has_translation' => true,
1703 'language' => $result->language_code,
1704 'translations' => $translations
1705 ]);
1706 } else {
1707 wp_send_json(['success' => true, 'has_translation' => false]);
1708 }
1709 wp_die();
1710 }
1711
1712 /**
1713 * Translate text using the user's selected provider and model
1714 */
1715 private function translate_with_provider($provider, $model, $api_key, $system_prompt, $text) {
1716 switch ($provider) {
1717 case 'claude':
1718 return $this->translate_with_claude($api_key, $model, $system_prompt, $text);
1719 case 'grok':
1720 return $this->translate_with_xai($api_key, $model, $system_prompt, $text);
1721 case 'deepseek':
1722 return $this->translate_with_deepseek($api_key, $model, $system_prompt, $text);
1723 case 'gemini':
1724 return $this->translate_with_gemini($api_key, $model, $system_prompt, $text);
1725 case 'openrouter':
1726 return $this->translate_with_openrouter($api_key, $model, $system_prompt, $text);
1727 case 'gpt':
1728 case 'o1':
1729 default:
1730 return $this->translate_with_openai($api_key, $model, $system_prompt, $text);
1731 }
1732 }
1733
1734 /**
1735 * Translate text using OpenAI API
1736 */
1737 private function translate_with_openai($api_key, $model, $system_prompt, $text) {
1738 $response = wp_remote_post('https://api.openai.com/v1/chat/completions', [
1739 'timeout' => 60,
1740 'headers' => [
1741 'Authorization' => 'Bearer ' . $api_key,
1742 'Content-Type' => 'application/json'
1743 ],
1744 'body' => wp_json_encode([
1745 'model' => $model,
1746 'messages' => [
1747 ['role' => 'system', 'content' => $system_prompt],
1748 ['role' => 'user', 'content' => $text]
1749 ],
1750 'temperature' => 0.3
1751 ])
1752 ]);
1753
1754 if (is_wp_error($response)) {
1755 return $response;
1756 }
1757
1758 $body = json_decode(wp_remote_retrieve_body($response), true);
1759
1760 if (isset($body['error'])) {
1761 return new WP_Error('api_error', $body['error']['message']);
1762 }
1763
1764 if (isset($body['choices'][0]['message']['content'])) {
1765 return $body['choices'][0]['message']['content'];
1766 }
1767
1768 return new WP_Error('api_error', 'Invalid API response');
1769 }
1770
1771 /**
1772 * Translate text using Claude API
1773 */
1774 private function translate_with_claude($api_key, $model, $system_prompt, $text) {
1775 $response = wp_remote_post('https://api.anthropic.com/v1/messages', [
1776 'timeout' => 60,
1777 'headers' => [
1778 'x-api-key' => $api_key,
1779 'anthropic-version' => '2023-06-01',
1780 'Content-Type' => 'application/json'
1781 ],
1782 'body' => wp_json_encode([
1783 'model' => $model,
1784 'max_tokens' => 4096,
1785 'system' => $system_prompt,
1786 'messages' => [
1787 ['role' => 'user', 'content' => $text]
1788 ]
1789 ])
1790 ]);
1791
1792 if (is_wp_error($response)) {
1793 return $response;
1794 }
1795
1796 $body = json_decode(wp_remote_retrieve_body($response), true);
1797
1798 if (isset($body['error'])) {
1799 return new WP_Error('api_error', $body['error']['message']);
1800 }
1801
1802 if (isset($body['content'][0]['text'])) {
1803 return $body['content'][0]['text'];
1804 }
1805
1806 return new WP_Error('api_error', 'Invalid API response');
1807 }
1808
1809 /**
1810 * Translate text using xAI (Grok) API
1811 */
1812 private function translate_with_xai($api_key, $model, $system_prompt, $text) {
1813 $response = wp_remote_post('https://api.x.ai/v1/chat/completions', [
1814 'timeout' => 60,
1815 'headers' => [
1816 'Authorization' => 'Bearer ' . $api_key,
1817 'Content-Type' => 'application/json'
1818 ],
1819 'body' => wp_json_encode([
1820 'model' => $model,
1821 'messages' => [
1822 ['role' => 'system', 'content' => $system_prompt],
1823 ['role' => 'user', 'content' => $text]
1824 ],
1825 'temperature' => 0.3
1826 ])
1827 ]);
1828
1829 if (is_wp_error($response)) {
1830 return $response;
1831 }
1832
1833 $body = json_decode(wp_remote_retrieve_body($response), true);
1834
1835 if (isset($body['error'])) {
1836 return new WP_Error('api_error', $body['error']['message']);
1837 }
1838
1839 if (isset($body['choices'][0]['message']['content'])) {
1840 return $body['choices'][0]['message']['content'];
1841 }
1842
1843 return new WP_Error('api_error', 'Invalid API response');
1844 }
1845
1846 /**
1847 * Translate text using DeepSeek API
1848 */
1849 private function translate_with_deepseek($api_key, $model, $system_prompt, $text) {
1850 $response = wp_remote_post('https://api.deepseek.com/v1/chat/completions', [
1851 'timeout' => 60,
1852 'headers' => [
1853 'Authorization' => 'Bearer ' . $api_key,
1854 'Content-Type' => 'application/json'
1855 ],
1856 'body' => wp_json_encode([
1857 'model' => $model,
1858 'messages' => [
1859 ['role' => 'system', 'content' => $system_prompt],
1860 ['role' => 'user', 'content' => $text]
1861 ],
1862 'temperature' => 0.3
1863 ])
1864 ]);
1865
1866 if (is_wp_error($response)) {
1867 return $response;
1868 }
1869
1870 $body = json_decode(wp_remote_retrieve_body($response), true);
1871
1872 if (isset($body['error'])) {
1873 return new WP_Error('api_error', $body['error']['message']);
1874 }
1875
1876 if (isset($body['choices'][0]['message']['content'])) {
1877 return $body['choices'][0]['message']['content'];
1878 }
1879
1880 return new WP_Error('api_error', 'Invalid API response');
1881 }
1882
1883 /**
1884 * Translate text using Google Gemini API
1885 */
1886 private function translate_with_gemini($api_key, $model, $system_prompt, $text) {
1887 $url = 'https://generativelanguage.googleapis.com/v1beta/models/' . $model . ':generateContent?key=' . $api_key;
1888
1889 $response = wp_remote_post($url, [
1890 'timeout' => 60,
1891 'headers' => [
1892 'Content-Type' => 'application/json'
1893 ],
1894 'body' => wp_json_encode([
1895 'contents' => [
1896 [
1897 'parts' => [
1898 ['text' => $system_prompt . "\n\n" . $text]
1899 ]
1900 ]
1901 ],
1902 'generationConfig' => [
1903 'temperature' => 0.3
1904 ]
1905 ])
1906 ]);
1907
1908 if (is_wp_error($response)) {
1909 return $response;
1910 }
1911
1912 $body = json_decode(wp_remote_retrieve_body($response), true);
1913
1914 if (isset($body['error'])) {
1915 return new WP_Error('api_error', $body['error']['message']);
1916 }
1917
1918 if (isset($body['candidates'][0]['content']['parts'][0]['text'])) {
1919 return $body['candidates'][0]['content']['parts'][0]['text'];
1920 }
1921
1922 return new WP_Error('api_error', 'Invalid API response');
1923 }
1924
1925 /**
1926 * Translate text using OpenRouter API
1927 */
1928 private function translate_with_openrouter($api_key, $model, $system_prompt, $text) {
1929 $response = wp_remote_post('https://openrouter.ai/api/v1/chat/completions', [
1930 'timeout' => 60,
1931 'headers' => [
1932 'Authorization' => 'Bearer ' . $api_key,
1933 'Content-Type' => 'application/json',
1934 'HTTP-Referer' => home_url(),
1935 'X-Title' => 'MxChat Translation'
1936 ],
1937 'body' => wp_json_encode([
1938 'model' => $model,
1939 'messages' => [
1940 ['role' => 'system', 'content' => $system_prompt],
1941 ['role' => 'user', 'content' => $text]
1942 ],
1943 'temperature' => 0.3
1944 ])
1945 ]);
1946
1947 if (is_wp_error($response)) {
1948 return $response;
1949 }
1950
1951 $body = json_decode(wp_remote_retrieve_body($response), true);
1952
1953 if (isset($body['error'])) {
1954 return new WP_Error('api_error', $body['error']['message']);
1955 }
1956
1957 if (isset($body['choices'][0]['message']['content'])) {
1958 return $body['choices'][0]['message']['content'];
1959 }
1960
1961 return new WP_Error('api_error', 'Invalid API response');
1962 }
1963
1964 public function mxchat_fetch_chat_history() {
1965 global $wpdb;
1966 $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
1967 $url_clicks_table = $wpdb->prefix . 'mxchat_url_clicks';
1968
1969 if (!current_user_can('manage_options')) {
1970 wp_send_json_error(['message' => 'Insufficient permissions']);
1971 wp_die();
1972 }
1973
1974 $page = isset($_POST['page']) ? absint($_POST['page']) : 1;
1975 $per_page = isset($_POST['per_page']) ? absint($_POST['per_page']) : 50;
1976 $offset = ($page - 1) * $per_page;
1977 $search = isset($_POST['search']) ? sanitize_text_field($_POST['search']) : '';
1978 $sort_order = isset($_POST['sort_order']) && $_POST['sort_order'] === 'asc' ? 'ASC' : 'DESC';
1979
1980 // Build search condition
1981 $search_condition = '';
1982 $search_params = [];
1983 if (!empty($search)) {
1984 $search_condition = "WHERE (
1985 session_id LIKE %s
1986 OR user_email LIKE %s
1987 OR user_name LIKE %s
1988 OR user_identifier LIKE %s
1989 OR message LIKE %s
1990 )";
1991 $search_params = array_fill(0, 5, '%' . $wpdb->esc_like($search) . '%');
1992 }
1993
1994 // Get total count
1995 $count_query = !empty($search)
1996 ? $wpdb->prepare("SELECT COUNT(DISTINCT session_id) FROM {$table_name} {$search_condition}", $search_params)
1997 : "SELECT COUNT(DISTINCT session_id) FROM {$table_name}";
1998 $total_sessions = (int) $wpdb->get_var($count_query);
1999
2000 // Get session IDs for current page (sorted by newest or oldest)
2001 $session_query = !empty($search)
2002 ? $wpdb->prepare(
2003 "SELECT DISTINCT t.session_id FROM {$table_name} t {$search_condition}
2004 GROUP BY t.session_id ORDER BY MAX(t.timestamp) {$sort_order} LIMIT %d OFFSET %d",
2005 array_merge($search_params, [$per_page, $offset])
2006 )
2007 : $wpdb->prepare(
2008 "SELECT DISTINCT session_id FROM {$table_name}
2009 GROUP BY session_id ORDER BY MAX(timestamp) {$sort_order} LIMIT %d OFFSET %d",
2010 $per_page, $offset
2011 );
2012 $session_ids = $wpdb->get_col($session_query);
2013
2014 $total_pages = ceil($total_sessions / $per_page);
2015
2016 if (empty($session_ids)) {
2017 wp_send_json([
2018 'success' => true,
2019 'sessions' => [],
2020 'page' => $page,
2021 'total_pages' => 0,
2022 'total_sessions' => 0,
2023 'showing_start' => 0,
2024 'showing_end' => 0
2025 ]);
2026 wp_die();
2027 }
2028
2029 // Check for optional columns/tables
2030 $url_table_exists = $wpdb->get_var("SHOW TABLES LIKE '$url_clicks_table'") === $url_clicks_table;
2031 $originating_columns_exist = !empty($wpdb->get_results("SHOW COLUMNS FROM $table_name LIKE 'originating_page_url'"));
2032
2033 // Build session list data
2034 $sessions = [];
2035 foreach ($session_ids as $session_id) {
2036 // Get session metadata
2037 $session_data = $originating_columns_exist
2038 ? $wpdb->get_row($wpdb->prepare(
2039 "SELECT user_email, user_name, user_identifier, originating_page_url, originating_page_title, timestamp
2040 FROM {$table_name} WHERE session_id = %s ORDER BY timestamp ASC LIMIT 1",
2041 $session_id
2042 ))
2043 : $wpdb->get_row($wpdb->prepare(
2044 "SELECT user_email, user_name, user_identifier, timestamp FROM {$table_name}
2045 WHERE session_id = %s LIMIT 1",
2046 $session_id
2047 ));
2048
2049 // Get message count and latest timestamp
2050 $message_stats = $wpdb->get_row($wpdb->prepare(
2051 "SELECT COUNT(*) as count, MAX(timestamp) as latest FROM {$table_name} WHERE session_id = %s",
2052 $session_id
2053 ));
2054
2055 // Get first user message as preview
2056 $first_user_msg = $wpdb->get_var($wpdb->prepare(
2057 "SELECT message FROM {$table_name} WHERE session_id = %s AND role = 'user' ORDER BY timestamp ASC LIMIT 1",
2058 $session_id
2059 ));
2060 $preview = $first_user_msg ? wp_trim_words(wp_strip_all_tags(stripslashes($first_user_msg)), 12, '...') : 'No messages';
2061
2062 // Build display name
2063 $user_email = !empty($session_data->user_email) ? $session_data->user_email : '';
2064 $user_name = !empty($session_data->user_name) ? $session_data->user_name : '';
2065 $user_identifier = !empty($session_data->user_identifier) ? $session_data->user_identifier : 'Guest';
2066
2067 $display_name = $user_name ?: ($user_email ? explode('@', $user_email)[0] : $user_identifier);
2068 $display_sub = $user_email ?: ('ID: ' . $user_identifier);
2069
2070 // Format time - show relative for recent, date for older
2071 $timestamp = strtotime($message_stats->latest . ' UTC');
2072 $now = time();
2073 $diff = $now - $timestamp;
2074 if ($diff < 3600) {
2075 $time_display = floor($diff / 60) . 'm ago';
2076 } elseif ($diff < 86400) {
2077 $time_display = floor($diff / 3600) . 'h ago';
2078 } elseif ($diff < 604800) {
2079 $time_display = floor($diff / 86400) . 'd ago';
2080 } else {
2081 $time_display = wp_date('M j', $timestamp);
2082 }
2083
2084 // Get initials for avatar
2085 $initials = strtoupper(substr($display_name, 0, 2));
2086 if (strlen($display_name) > 2 && strpos($display_name, ' ') !== false) {
2087 $parts = explode(' ', $display_name);
2088 $initials = strtoupper(substr($parts[0], 0, 1) . substr(end($parts), 0, 1));
2089 }
2090
2091 $sessions[] = [
2092 'session_id' => $session_id,
2093 'display_name' => $display_name,
2094 'display_sub' => $display_sub,
2095 'initials' => $initials,
2096 'preview' => $preview,
2097 'message_count' => (int) $message_stats->count,
2098 'time_display' => $time_display,
2099 'timestamp' => $message_stats->latest
2100 ];
2101 }
2102
2103 wp_send_json([
2104 'success' => true,
2105 'sessions' => $sessions,
2106 'page' => $page,
2107 'total_pages' => $total_pages,
2108 'total_sessions' => $total_sessions,
2109 'showing_start' => $offset + 1,
2110 'showing_end' => min($offset + $per_page, $total_sessions)
2111 ]);
2112 wp_die();
2113 }
2114
2115 /**
2116 * Fetch single conversation details for split-panel view
2117 */
2118 public function mxchat_fetch_conversation() {
2119 global $wpdb;
2120 $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
2121 $url_clicks_table = $wpdb->prefix . 'mxchat_url_clicks';
2122
2123 if (!current_user_can('manage_options')) {
2124 wp_send_json_error(['message' => 'Insufficient permissions']);
2125 wp_die();
2126 }
2127
2128 $session_id = isset($_POST['session_id']) ? sanitize_text_field($_POST['session_id']) : '';
2129 if (empty($session_id)) {
2130 wp_send_json_error(['message' => 'No session ID provided']);
2131 wp_die();
2132 }
2133
2134 // Check for optional columns/tables
2135 $url_table_exists = $wpdb->get_var("SHOW TABLES LIKE '$url_clicks_table'") === $url_clicks_table;
2136 $originating_columns_exist = !empty($wpdb->get_results("SHOW COLUMNS FROM $table_name LIKE 'originating_page_url'"));
2137
2138 // Get session metadata
2139 $session_data = $originating_columns_exist
2140 ? $wpdb->get_row($wpdb->prepare(
2141 "SELECT user_email, user_name, user_identifier, originating_page_url, originating_page_title, timestamp
2142 FROM {$table_name} WHERE session_id = %s ORDER BY timestamp ASC LIMIT 1",
2143 $session_id
2144 ))
2145 : $wpdb->get_row($wpdb->prepare(
2146 "SELECT user_email, user_name, user_identifier, timestamp FROM {$table_name}
2147 WHERE session_id = %s LIMIT 1",
2148 $session_id
2149 ));
2150
2151 if (!$session_data) {
2152 wp_send_json_error(['message' => 'Session not found']);
2153 wp_die();
2154 }
2155
2156 // Get clicked URLs
2157 $clicked_urls = [];
2158 if ($url_table_exists) {
2159 $url_clicks = $wpdb->get_results($wpdb->prepare(
2160 "SELECT DISTINCT clicked_url FROM {$url_clicks_table}
2161 WHERE session_id = %s ORDER BY click_timestamp ASC",
2162 $session_id
2163 ));
2164 foreach ($url_clicks as $click) {
2165 $clicked_urls[] = $click->clicked_url;
2166 }
2167 }
2168
2169 // Get all messages
2170 $messages = $wpdb->get_results($wpdb->prepare(
2171 "SELECT * FROM {$table_name} WHERE session_id = %s ORDER BY timestamp ASC",
2172 $session_id
2173 ));
2174
2175 // Build user info
2176 $user_email = !empty($session_data->user_email) ? $session_data->user_email : '';
2177 $user_name = !empty($session_data->user_name) ? $session_data->user_name : '';
2178 $user_identifier = !empty($session_data->user_identifier) ? $session_data->user_identifier : 'Guest';
2179
2180 $display_name = $user_name ?: ($user_email ? explode('@', $user_email)[0] : $user_identifier);
2181 $display_sub = $user_email ?: ('ID: ' . $user_identifier);
2182
2183 // Get initials
2184 $initials = strtoupper(substr($display_name, 0, 2));
2185 if (strlen($display_name) > 2 && strpos($display_name, ' ') !== false) {
2186 $parts = explode(' ', $display_name);
2187 $initials = strtoupper(substr($parts[0], 0, 1) . substr(end($parts), 0, 1));
2188 }
2189
2190 // Page info
2191 $page_url = $originating_columns_exist && !empty($session_data->originating_page_url) ? $session_data->originating_page_url : '';
2192 $page_title = '';
2193 if ($page_url) {
2194 $page_title = !empty($session_data->originating_page_title) ? $session_data->originating_page_title : parse_url($page_url, PHP_URL_PATH);
2195 }
2196
2197 // Format messages for output
2198 $formatted_messages = [];
2199 foreach ($messages as $msg) {
2200 $is_user = ($msg->role === 'user');
2201 $is_bot = ($msg->role === 'bot' || $msg->role === 'assistant');
2202
2203 $content = wp_kses(
2204 stripslashes($msg->message),
2205 [
2206 'b' => [], 'strong' => [], 'i' => [], 'em' => [], 'u' => [],
2207 'br' => [], 'p' => [], 'ul' => [], 'ol' => [], 'li' => [],
2208 'a' => ['href' => [], 'title' => [], 'target' => []]
2209 ]
2210 );
2211 $formatted_content = $this->format_transcript_message($content);
2212
2213 $has_rag = $is_bot && !empty($msg->rag_context);
2214
2215 $formatted_messages[] = [
2216 'id' => $msg->id,
2217 'role' => $msg->role,
2218 'is_user' => $is_user,
2219 'is_bot' => $is_bot,
2220 'content' => $formatted_content,
2221 'timestamp' => wp_date('g:i A', strtotime($msg->timestamp . ' UTC')),
2222 'full_timestamp' => wp_date('F j, Y g:i A', strtotime($msg->timestamp . ' UTC')),
2223 'has_rag' => $has_rag
2224 ];
2225 }
2226
2227 // First message timestamp for "started" display
2228 $started = !empty($messages) ? wp_date('M j, Y g:i A', strtotime($messages[0]->timestamp . ' UTC')) : '-';
2229
2230 wp_send_json([
2231 'success' => true,
2232 'session_id' => $session_id,
2233 'user' => [
2234 'name' => $display_name,
2235 'sub' => $display_sub,
2236 'initials' => $initials,
2237 'email' => $user_email,
2238 'identifier' => $user_identifier
2239 ],
2240 'page' => [
2241 'url' => $page_url,
2242 'title' => $page_title
2243 ],
2244 'clicked_urls' => $clicked_urls,
2245 'messages' => $formatted_messages,
2246 'message_count' => count($messages),
2247 'started' => $started
2248 ]);
2249 wp_die();
2250 }
2251
2252
2253 /**
2254 * ALTERNATIVE: Simpler helper method using string replacement
2255 */
2256 private function highlight_clicked_links($message_content, $clicked_urls) {
2257 if (empty($clicked_urls)) {
2258 return wp_kses(
2259 $message_content,
2260 [
2261 'b' => [], 'strong' => [], 'i' => [], 'em' => [], 'u' => [],
2262 'br' => [], 'p' => [], 'ul' => [], 'ol' => [], 'li' => [],
2263 'a' => ['href' => [], 'title' => [], 'target' => [], 'class' => []]
2264 ]
2265 );
2266 }
2267
2268 // First apply standard sanitization
2269 $message_content = wp_kses(
2270 $message_content,
2271 [
2272 'b' => [], 'strong' => [], 'i' => [], 'em' => [], 'u' => [],
2273 'br' => [], 'p' => [], 'ul' => [], 'ol' => [], 'li' => [],
2274 'a' => ['href' => [], 'title' => [], 'target' => [], 'class' => []]
2275 ]
2276 );
2277
2278 // Process each clicked URL
2279 foreach ($clicked_urls as $clicked_url) {
2280 // Try multiple patterns to catch different link formats
2281 $patterns = [
2282 // Standard link format
2283 '/<a([^>]*href=["\']' . preg_quote($clicked_url, '/') . '["\'][^>]*)>/i',
2284 // Link with trailing slash
2285 '/<a([^>]*href=["\']' . preg_quote(rtrim($clicked_url, '/'), '/') . '\/?["\'][^>]*)>/i',
2286 // Encoded entities version
2287 '/<a([^>]*href=["\']' . preg_quote(htmlentities($clicked_url), '/') . '["\'][^>]*)>/i',
2288 ];
2289
2290 foreach ($patterns as $pattern) {
2291 if (preg_match($pattern, $message_content)) {
2292 $message_content = preg_replace_callback(
2293 $pattern,
2294 function($matches) {
2295 $full_match = $matches[0];
2296 $attributes = $matches[1];
2297
2298 // Check if it already has the class
2299 if (strpos($full_match, 'mxchat-clicked-link') !== false) {
2300 return $full_match;
2301 }
2302
2303 // Check if class attribute exists
2304 if (preg_match('/class=["\']([^"\']*)["\']/', $attributes, $class_matches)) {
2305 // Add to existing class
2306 $new_attributes = preg_replace(
2307 '/class=["\']([^"\']*)["\']/',
2308 'class="$1 mxchat-clicked-link"',
2309 $attributes
2310 );
2311 } else {
2312 // Add new class attribute
2313 $new_attributes = $attributes . ' class="mxchat-clicked-link"';
2314 }
2315
2316 // Add title if not present
2317 if (strpos($new_attributes, 'title=') === false) {
2318 $new_attributes .= ' title="User clicked this link"';
2319 }
2320
2321 return '<a' . $new_attributes . '>';
2322 },
2323 $message_content
2324 );
2325
2326 // If we found and replaced, break out of the patterns loop
2327 break;
2328 }
2329 }
2330 }
2331
2332 return $message_content;
2333 }
2334
2335 public function mxchat_create_prompts_page() {
2336 //error_log('=== DEBUG: mxchat_create_prompts_page started ===');
2337
2338 global $wpdb;
2339 $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
2340
2341 $knowledge_manager = MxChat_Knowledge_Manager::get_instance();
2342 $pinecone_manager = MxChat_Pinecone_Manager::get_instance();
2343
2344 // Display success message if all prompts were deleted
2345 if (isset($_GET['all_deleted']) && $_GET['all_deleted'] === 'true') {
2346 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>';
2347 }
2348
2349 // Set up pagination, search query, and content type filter
2350 $nonce = isset($_GET['_wpnonce']) ? sanitize_text_field($_GET['_wpnonce']) : '';
2351 $search_query = (!empty($nonce) && wp_verify_nonce($nonce, 'mxchat_prompts_search_nonce') && isset($_GET['search'])) ? sanitize_text_field($_GET['search']) : '';
2352 $content_type_filter = isset($_GET['content_type']) ? sanitize_key($_GET['content_type']) : ''; // ADDED 2.5.6
2353 $current_page = isset($_GET['paged']) ? absint($_GET['paged']) : 1;
2354 $per_page = 25;
2355
2356 //error_log('DEBUG: Search query: ' . $search_query);
2357 //error_log('DEBUG: Content type filter: ' . $content_type_filter);
2358 //error_log('DEBUG: Current page: ' . $current_page);
2359 //error_log('DEBUG: Per page: ' . $per_page);
2360
2361 // ================================
2362 // MULTI-BOT CONFIGURATION
2363 // ================================
2364
2365 // Check for multi-bot and set up bot selection
2366 if (class_exists('MxChat_Multi_Bot_Manager')) {
2367 $multi_bot_manager = MxChat_Multi_Bot_Core_Manager::get_instance();
2368 $available_bots = $multi_bot_manager->get_available_bots();
2369
2370 // Get saved bot selection (user-specific first, then site-wide default)
2371 $user_id = get_current_user_id();
2372 $saved_bot_id = get_user_meta($user_id, 'mxchat_selected_knowledge_bot', true);
2373 if (empty($saved_bot_id)) {
2374 $saved_bot_id = get_option('mxchat_current_knowledge_bot', 'default');
2375 }
2376
2377 // Allow URL override but default to saved selection
2378 $current_bot_id = isset($_GET['bot_id']) ? sanitize_key($_GET['bot_id']) : $saved_bot_id;
2379 $multibot_active = true;
2380 } else {
2381 $current_bot_id = 'default';
2382 $multibot_active = false;
2383 }
2384
2385 // ================================
2386 // DATA SOURCE CONFIGURATION
2387 // ================================
2388
2389 // Get bot-specific Pinecone settings
2390 $pinecone_options = $pinecone_manager->mxchat_get_bot_pinecone_options($current_bot_id);
2391 $use_pinecone = ($pinecone_options['mxchat_use_pinecone'] ?? '0') === '1';
2392 $pinecone_api_key = $pinecone_options['mxchat_pinecone_api_key'] ?? '';
2393
2394 // Get OpenAI Vector Store settings
2395 $vectorstore_options = get_option('mxchat_openai_vectorstore_options', array());
2396 $use_vectorstore = ($vectorstore_options['mxchat_use_openai_vectorstore'] ?? '0') === '1';
2397
2398 //error_log('DEBUG: Bot ' . $current_bot_id . ' - Use Pinecone: ' . ($use_pinecone ? 'YES' : 'NO'));
2399 //error_log('DEBUG: Bot ' . $current_bot_id . ' - Has API Key: ' . (!empty($pinecone_api_key) ? 'YES' : 'NO'));
2400 //error_log('DEBUG: Bot ' . $current_bot_id . ' - Host: ' . ($pinecone_options['mxchat_pinecone_host'] ?? 'NOT SET'));
2401 //error_log('DEBUG: Bot ' . $current_bot_id . ' - Namespace: ' . ($pinecone_options['mxchat_pinecone_namespace'] ?? 'NOT SET'));
2402
2403 if ($use_pinecone && !empty($pinecone_api_key)) {
2404 //error_log('DEBUG: Using PINECONE data source with bot-specific config');
2405 // PINECONE DATA SOURCE
2406 $data_source = 'pinecone';
2407
2408 // IMPORTANT: Pass the bot-specific $pinecone_options, not default options!
2409 // UPDATED 2.5.6: Added content_type_filter parameter
2410 $records = $pinecone_manager->mxchat_fetch_pinecone_records($pinecone_options, $search_query, $current_page, $per_page, $current_bot_id, $content_type_filter);
2411
2412 // TEMPORARY DEBUG - Add this right after the fetch call
2413 //error_log('=== DEBUG: Bot switching issue ===');
2414 //error_log('Current bot ID: ' . $current_bot_id);
2415 //error_log('Use Pinecone: ' . ($use_pinecone ? 'YES' : 'NO'));
2416 //error_log('Records returned: ' . count($records['data'] ?? []));
2417 //error_log('Total records: ' . ($records['total'] ?? 0));
2418
2419 // Check first few records to see their bot_id
2420 if (!empty($records['data'])) {
2421 foreach (array_slice($records['data'], 0, 3) as $i => $record) {
2422 $record_bot_id = $record->bot_id ?? 'NOT_SET';
2423 //error_log('Record ' . ($i+1) . ' bot_id: ' . $record_bot_id . ', content preview: ' . substr($record->article_content ?? '', 0, 30) . '...');
2424 }
2425 }
2426 //error_log('=== END DEBUG ===');
2427
2428 $total_records = $records['total'] ?? 0;
2429 $prompts = $records['data'] ?? array();
2430 $total_in_database = $records['total_in_database'] ?? 0;
2431 $showing_recent_only = $records['showing_recent_only'] ?? false;
2432
2433 $total_pages = ceil($total_records / $per_page);
2434
2435 } else {
2436 //error_log('DEBUG: Using WORDPRESS DB data source');
2437 // WORDPRESS DB DATA SOURCE (your existing logic)
2438 $data_source = 'wordpress';
2439
2440 // Initialize these variables for WordPress DB
2441 $total_in_database = 0;
2442 $showing_recent_only = false;
2443
2444 $offset = ($current_page - 1) * $per_page;
2445
2446 // UPDATED 2.5.6: Build WHERE clause for search and content type filtering
2447 $where_clauses = array();
2448 $where_values = array();
2449
2450 if ($search_query) {
2451 $where_clauses[] = "article_content LIKE %s";
2452 $where_values[] = '%' . $wpdb->esc_like($search_query) . '%';
2453 }
2454
2455 if ($content_type_filter) {
2456 $where_clauses[] = "content_type = %s";
2457 $where_values[] = $content_type_filter;
2458 }
2459
2460 $sql_where = "";
2461 if (!empty($where_clauses)) {
2462 $sql_where = "WHERE " . implode(" AND ", $where_clauses);
2463 }
2464
2465 // UPDATED 2.6.3: Count unique entries (by source_url) instead of individual rows
2466 // This ensures pagination shows X entries per page, not X chunks
2467 // Entries with empty source_url are counted individually
2468 if (!empty($where_values)) {
2469 // Count unique source_urls + count of rows with empty source_url
2470 $count_query = $wpdb->prepare(
2471 "SELECT (SELECT COUNT(DISTINCT source_url) FROM {$table_name} {$sql_where} AND source_url != '') +
2472 (SELECT COUNT(*) FROM {$table_name} {$sql_where} AND (source_url = '' OR source_url IS NULL))",
2473 array_merge($where_values, $where_values)
2474 );
2475 } else {
2476 $count_query = "SELECT (SELECT COUNT(DISTINCT source_url) FROM {$table_name} WHERE source_url != '') +
2477 (SELECT COUNT(*) FROM {$table_name} WHERE source_url = '' OR source_url IS NULL)";
2478 }
2479 $total_records = $wpdb->get_var($count_query);
2480 $total_pages = ceil($total_records / $per_page);
2481
2482 // UPDATED 2.6.3: Get unique source_urls for pagination, then fetch all their rows
2483 // Step 1: Get the source_urls for this page (distinct URLs ordered by latest timestamp)
2484 if (!empty($where_values)) {
2485 $urls_query = $wpdb->prepare(
2486 "SELECT source_url, MAX(timestamp) as latest_ts FROM {$table_name} {$sql_where}
2487 GROUP BY source_url ORDER BY latest_ts DESC LIMIT %d OFFSET %d",
2488 array_merge($where_values, array($per_page, $offset))
2489 );
2490 } else {
2491 $urls_query = $wpdb->prepare(
2492 "SELECT source_url, MAX(timestamp) as latest_ts FROM {$table_name}
2493 GROUP BY source_url ORDER BY latest_ts DESC LIMIT %d OFFSET %d",
2494 $per_page, $offset
2495 );
2496 }
2497 $page_urls = $wpdb->get_results($urls_query);
2498
2499 // Step 2: Fetch all rows for these source_urls
2500 $prompts = array();
2501 if (!empty($page_urls)) {
2502 // Build URL order map to preserve newest-first ordering from step 1
2503 $url_order_map = array();
2504 $url_list = array();
2505 $has_empty_url = false;
2506 $order_index = 0;
2507 foreach ($page_urls as $url_row) {
2508 if (empty($url_row->source_url)) {
2509 $has_empty_url = true;
2510 $url_order_map['__empty__'] = $order_index++;
2511 } else {
2512 $url_list[] = $url_row->source_url;
2513 $url_order_map[$url_row->source_url] = $order_index++;
2514 }
2515 }
2516
2517 // Build query to fetch all rows for these URLs
2518 $url_conditions = array();
2519 $url_values = array();
2520
2521 if (!empty($url_list)) {
2522 $placeholders = implode(',', array_fill(0, count($url_list), '%s'));
2523 $url_conditions[] = "source_url IN ($placeholders)";
2524 $url_values = array_merge($url_values, $url_list);
2525 }
2526
2527 if ($has_empty_url) {
2528 $url_conditions[] = "(source_url = '' OR source_url IS NULL)";
2529 }
2530
2531 if (!empty($url_conditions)) {
2532 $url_where = "WHERE (" . implode(" OR ", $url_conditions) . ")";
2533
2534 // Add original filters back
2535 if (!empty($where_clauses)) {
2536 $url_where .= " AND " . implode(" AND ", $where_clauses);
2537 $url_values = array_merge($url_values, $where_values);
2538 }
2539
2540 if (!empty($url_values)) {
2541 $prompts_query = $wpdb->prepare(
2542 "SELECT * FROM {$table_name} {$url_where} ORDER BY timestamp DESC",
2543 $url_values
2544 );
2545 } else {
2546 $prompts_query = "SELECT * FROM {$table_name} {$url_where} ORDER BY timestamp DESC";
2547 }
2548
2549 $prompts = $wpdb->get_results($prompts_query);
2550
2551 // Sort prompts by the original URL order (newest first), then by timestamp within each URL group
2552 usort($prompts, function($a, $b) use ($url_order_map) {
2553 $url_a = empty($a->source_url) ? '__empty__' : $a->source_url;
2554 $url_b = empty($b->source_url) ? '__empty__' : $b->source_url;
2555 $order_a = $url_order_map[$url_a] ?? PHP_INT_MAX;
2556 $order_b = $url_order_map[$url_b] ?? PHP_INT_MAX;
2557
2558 // First sort by URL order (newest URLs first)
2559 if ($order_a !== $order_b) {
2560 return $order_a - $order_b;
2561 }
2562
2563 // Within same URL, sort by timestamp DESC (newest chunks first)
2564 return strtotime($b->timestamp) - strtotime($a->timestamp);
2565 });
2566 }
2567 }
2568 }
2569
2570 // ================================
2571 // PAGINATION GENERATION
2572 // ================================
2573
2574 // Generate pagination links
2575 if ($total_pages > 1) {
2576 // Build a clean base URL with only necessary parameters
2577 $pagination_args = array('page' => 'mxchat-prompts');
2578
2579 // Preserve bot_id parameter if multi-bot is active
2580 if ($multibot_active && !empty($current_bot_id) && $current_bot_id !== 'default') {
2581 $pagination_args['bot_id'] = $current_bot_id;
2582 }
2583
2584 // Preserve search query and nonce if present
2585 if ($search_query) {
2586 $pagination_args['search'] = $search_query;
2587 if (!empty($nonce)) {
2588 $pagination_args['_wpnonce'] = $nonce;
2589 }
2590 }
2591
2592 // Preserve content type filter if present
2593 if ($content_type_filter) {
2594 $pagination_args['content_type'] = $content_type_filter;
2595 }
2596
2597 // Build clean base URL
2598 $base_url = add_query_arg($pagination_args, admin_url('admin.php'));
2599
2600 $page_links = paginate_links(array(
2601 'base' => $base_url . '%_%',
2602 'format' => '&paged=%#%',
2603 'prev_text' => __('&laquo; Previous', 'mxchat'),
2604 'next_text' => __('Next &raquo;', 'mxchat'),
2605 'total' => $total_pages,
2606 'current' => $current_page,
2607 'type' => 'plain',
2608 ));
2609 } else {
2610 $page_links = '';
2611 }
2612
2613 // ================================
2614 // PROCESSING STATUS RETRIEVAL
2615 // ================================
2616
2617 // Retrieve processing statuses using queue-based method
2618 $processing_statuses = $knowledge_manager->mxchat_get_processing_statuses();
2619 $pdf_status = $processing_statuses['pdf_status'];
2620 $sitemap_status = $processing_statuses['sitemap_status'];
2621 $is_processing = $processing_statuses['is_processing'];
2622
2623 //error_log('=== DEBUG: mxchat_create_prompts_page data preparation completed ===');
2624
2625 // ================================
2626 // RENDER PAGE WITH NEW SIDEBAR LAYOUT
2627 // ================================
2628
2629 // Include the new knowledge page template
2630 require_once plugin_dir_path(__FILE__) . 'admin-knowledge-page.php';
2631
2632 // Package all page data for the render function
2633 $page_data = array(
2634 'prompts' => $prompts,
2635 'total_records' => $total_records,
2636 'total_pages' => $total_pages,
2637 'current_page' => $current_page,
2638 'per_page' => $per_page,
2639 'page_links' => $page_links,
2640 'search_query' => $search_query,
2641 'content_type_filter' => $content_type_filter,
2642 'data_source' => $data_source,
2643 'use_pinecone' => $use_pinecone,
2644 'use_vectorstore' => $use_vectorstore,
2645 'multibot_active' => $multibot_active,
2646 'current_bot_id' => $current_bot_id,
2647 'pdf_status' => $pdf_status,
2648 'sitemap_status' => $sitemap_status,
2649 'is_processing' => $is_processing,
2650 'total_in_database' => $total_in_database ?? 0,
2651 'showing_recent_only' => $showing_recent_only ?? false,
2652 );
2653
2654 // Render the new sidebar-based page
2655 mxchat_render_knowledge_page($this, $knowledge_manager, $page_data);
2656 }
2657
2658 /**
2659 * Get bot-specific Pinecone configuration
2660 * Used in the knowledge retrieval functions
2661 */
2662 private function get_bot_pinecone_config($bot_id = 'default') {
2663 //error_log("MXCHAT DEBUG: get_bot_pinecone_config called for bot: " . $bot_id);
2664
2665 // If default bot or multi-bot add-on not active, use default Pinecone config
2666 if ($bot_id === 'default' || !class_exists('MxChat_Multi_Bot_Manager')) {
2667 //error_log("MXCHAT DEBUG: Using default Pinecone config (no multi-bot or bot is 'default')");
2668 $addon_options = get_option('mxchat_pinecone_addon_options', array());
2669 $config = array(
2670 'use_pinecone' => (isset($addon_options['mxchat_use_pinecone']) && $addon_options['mxchat_use_pinecone'] === '1'),
2671 'api_key' => $addon_options['mxchat_pinecone_api_key'] ?? '',
2672 'host' => $addon_options['mxchat_pinecone_host'] ?? '',
2673 'namespace' => $addon_options['mxchat_pinecone_namespace'] ?? ''
2674 );
2675 //error_log("MXCHAT DEBUG: Default config - use_pinecone: " . ($config['use_pinecone'] ? 'true' : 'false'));
2676 return $config;
2677 }
2678
2679 //error_log("MXCHAT DEBUG: Calling filter 'mxchat_get_bot_pinecone_config' for bot: " . $bot_id);
2680
2681 // Hook for multi-bot add-on to provide bot-specific Pinecone config
2682 $bot_pinecone_config = apply_filters('mxchat_get_bot_pinecone_config', array(), $bot_id);
2683
2684 if (!empty($bot_pinecone_config)) {
2685 //error_log("MXCHAT DEBUG: Got bot-specific config from filter");
2686 //error_log(" - use_pinecone: " . (isset($bot_pinecone_config['use_pinecone']) ? ($bot_pinecone_config['use_pinecone'] ? 'true' : 'false') : 'not set'));
2687 //error_log(" - host: " . ($bot_pinecone_config['host'] ?? 'not set'));
2688 //error_log(" - namespace: " . ($bot_pinecone_config['namespace'] ?? 'not set'));
2689 } else {
2690 //error_log("MXCHAT DEBUG: Filter returned empty config!");
2691 }
2692
2693 return is_array($bot_pinecone_config) ? $bot_pinecone_config : array();
2694 }
2695
2696 public function mxchat_delete_chat_history() {
2697 if (!current_user_can('manage_options')) {
2698 echo wp_json_encode(['error' => esc_html__('You do not have sufficient permissions.', 'mxchat')]);
2699 wp_die();
2700 }
2701 check_ajax_referer('mxchat_delete_chat_history', 'security');
2702 global $wpdb;
2703 $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
2704
2705 if (isset($_POST['delete_session_ids']) && is_array($_POST['delete_session_ids'])) {
2706 $deleted_count = 0;
2707 $translations_table = $wpdb->prefix . 'mxchat_transcript_translations';
2708
2709 foreach ($_POST['delete_session_ids'] as $session_id) {
2710 $session_id_sanitized = sanitize_text_field($session_id);
2711
2712 // Clear relevant cache before deletion
2713 $cache_key = 'chat_session_' . $session_id_sanitized;
2714 wp_cache_delete($cache_key, 'mxchat_chat_sessions');
2715
2716 // Perform the deletion from the database table
2717 $wpdb->delete($table_name, ['session_id' => $session_id_sanitized]);
2718
2719 // Delete any saved translations for this session
2720 if ($wpdb->get_var("SHOW TABLES LIKE '$translations_table'") === $translations_table) {
2721 $wpdb->delete($translations_table, ['session_id' => $session_id_sanitized]);
2722 }
2723
2724 // Delete the corresponding option entry from wp_options table
2725 delete_option("mxchat_history_" . $session_id_sanitized);
2726
2727 // Delete any associated metadata options
2728 delete_option("mxchat_email_" . $session_id_sanitized);
2729 delete_option("mxchat_agent_name_" . $session_id_sanitized);
2730
2731 $deleted_count++;
2732 }
2733
2734 // Optionally, clear a general cache if you have one
2735 wp_cache_delete('all_chat_sessions', 'mxchat_chat_sessions');
2736
2737 echo wp_json_encode([
2738 'success' => sprintf(
2739 esc_html__('%d chat session(s) have been deleted from all storage locations.', 'mxchat'),
2740 $deleted_count
2741 )
2742 ]);
2743 } else {
2744 echo wp_json_encode(['error' => esc_html__('No chat sessions selected for deletion.', 'mxchat')]);
2745 }
2746
2747 wp_die();
2748 }
2749
2750 /**
2751 * Format transcript message content with markdown processing
2752 * Converts markdown links, bold, italic, code blocks, and plain URLs to HTML
2753 */
2754 private function format_transcript_message($text) {
2755 if (empty($text)) {
2756 return '';
2757 }
2758
2759 // Normalize line endings and clean up excessive whitespace
2760 $text = str_replace("\r\n", "\n", $text);
2761 $text = str_replace("\r", "\n", $text);
2762
2763 // Clean up existing <br> tags that may have been saved (legacy data)
2764 // Convert <br>, <br/>, <br /> back to newlines for consistent processing
2765 $text = preg_replace('/<br\s*\/?>\s*/i', "\n", $text);
2766
2767 // Collapse 3+ consecutive newlines to just 2 (paragraph break)
2768 $text = preg_replace('/\n{3,}/', "\n\n", $text);
2769
2770 // Process markdown headers (# Header)
2771 $text = preg_replace_callback('/^(#{1,6})\s+(.+)$/m', function($matches) {
2772 $level = strlen($matches[1]);
2773 $content = esc_html(trim($matches[2]));
2774 return "<h{$level}>{$content}</h{$level}>";
2775 }, $text);
2776
2777 // Process code blocks with triple backticks
2778 $text = preg_replace_callback('/```(\w+)?\n?([\s\S]*?)```/', function($matches) {
2779 $language = !empty($matches[1]) ? ' class="language-' . esc_attr($matches[1]) . '"' : '';
2780 $code = esc_html($matches[2]);
2781 return "<pre><code{$language}>{$code}</code></pre>";
2782 }, $text);
2783
2784 // Process inline code with single backticks
2785 $text = preg_replace('/`([^`]+)`/', '<code>$1</code>', $text);
2786
2787 // Process bold text **text** or __text__
2788 $text = preg_replace('/\*\*(.+?)\*\*/', '<strong>$1</strong>', $text);
2789 $text = preg_replace('/__(.+?)__/', '<strong>$1</strong>', $text);
2790
2791 // Process italic text *text* or _text_ (but not if part of URL)
2792 $text = preg_replace('/(?<![*_\w])\*([^*]+)\*(?![*\w])/', '<em>$1</em>', $text);
2793 $text = preg_replace('/(?<![*_\w])_([^_]+)_(?![*\w])/', '<em>$1</em>', $text);
2794
2795 // Process markdown links [text](url)
2796 $text = preg_replace_callback('/\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/', function($matches) {
2797 $link_text = esc_html($matches[1]);
2798 $url = esc_url($matches[2]);
2799 return "<a href=\"{$url}\" target=\"_blank\" rel=\"noopener\">{$link_text}</a>";
2800 }, $text);
2801
2802 // Process citation-style brackets [URL]
2803 $text = preg_replace_callback('/\[(https?:\/\/[^\]]+)\]/', function($matches) {
2804 $url = esc_url($matches[1]);
2805 return "<a href=\"{$url}\" target=\"_blank\" rel=\"noopener\">{$url}</a>";
2806 }, $text);
2807
2808 // Process standalone URLs (not already in links)
2809 $text = preg_replace_callback(
2810 '/(?<!href="|">)(https?:\/\/[^\s<>"]+)(?![^<]*<\/a>)/',
2811 function($matches) {
2812 $url = esc_url($matches[1]);
2813 // Truncate display URL if too long
2814 $display = strlen($matches[1]) > 50 ? substr($matches[1], 0, 47) . '...' : $matches[1];
2815 return "<a href=\"{$url}\" target=\"_blank\" rel=\"noopener\">{$display}</a>";
2816 },
2817 $text
2818 );
2819
2820 // Process mailto links
2821 $text = preg_replace_callback('/\[([^\]]+)\]\((mailto:[^)]+)\)/', function($matches) {
2822 $link_text = esc_html($matches[1]);
2823 $mailto = esc_url($matches[2]);
2824 return "<a href=\"{$mailto}\">{$link_text}</a>";
2825 }, $text);
2826
2827 // Convert paragraphs: split by double newlines, wrap in <p> tags
2828 // This creates proper paragraph structure instead of excessive <br> tags
2829 $paragraphs = preg_split('/\n\n+/', $text);
2830
2831 // Filter out empty paragraphs but preserve content like "0"
2832 $paragraphs = array_values(array_filter(array_map('trim', $paragraphs), function($p) {
2833 return $p !== '';
2834 }));
2835
2836 if (empty($paragraphs)) {
2837 // No content after filtering
2838 return '';
2839 } elseif (count($paragraphs) > 1) {
2840 // Multiple paragraphs - wrap each in <p> tags, convert single newlines to <br>
2841 $formatted_paragraphs = array_map(function($p) {
2842 return nl2br($p);
2843 }, $paragraphs);
2844 $text = '<p>' . implode('</p><p>', $formatted_paragraphs) . '</p>';
2845 } else {
2846 // Single paragraph - just convert newlines to <br>
2847 $text = nl2br($paragraphs[0]);
2848 }
2849
2850 return $text;
2851 }
2852
2853 /**
2854 * AJAX handler to fetch RAG context for a specific message
2855 * Used by the transcript viewer to show retrieved documents
2856 */
2857 public function mxchat_get_rag_context() {
2858 // Check permissions
2859 if (!current_user_can('manage_options')) {
2860 wp_send_json_error(['message' => esc_html__('You do not have sufficient permissions.', 'mxchat')]);
2861 wp_die();
2862 }
2863
2864 // Validate message ID
2865 if (!isset($_POST['message_id']) || empty($_POST['message_id'])) {
2866 wp_send_json_error(['message' => esc_html__('Message ID is required.', 'mxchat')]);
2867 wp_die();
2868 }
2869
2870 $message_id = absint($_POST['message_id']);
2871
2872 global $wpdb;
2873 $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
2874
2875 // Fetch the RAG context for this message
2876 $result = $wpdb->get_row($wpdb->prepare(
2877 "SELECT rag_context FROM {$table_name} WHERE id = %d",
2878 $message_id
2879 ));
2880
2881 if (!$result || empty($result->rag_context)) {
2882 wp_send_json_error(['message' => esc_html__('No RAG context found for this message.', 'mxchat')]);
2883 wp_die();
2884 }
2885
2886 // Decode the JSON data
2887 $rag_context = json_decode($result->rag_context, true);
2888
2889 if (json_last_error() !== JSON_ERROR_NONE) {
2890 wp_send_json_error(['message' => esc_html__('Invalid RAG context data.', 'mxchat')]);
2891 wp_die();
2892 }
2893
2894 wp_send_json_success($rag_context);
2895 wp_die();
2896 }
2897
2898 public function display_admin_notices() {
2899 // Check if we're on a MXChat admin page
2900 $screen = get_current_screen();
2901 if (!$screen || strpos($screen->base, 'mxchat') === false) {
2902 return;
2903 }
2904
2905 $dismiss_button = '<button type="button" class="notice-dismiss"><span class="screen-reader-text">' . esc_html__('Dismiss this notice.', 'mxchat') . '</span></button>';
2906
2907 // Check for error notices
2908 $error_notice = get_transient('mxchat_admin_notice_error');
2909 if ($error_notice) {
2910 echo '<div class="notice notice-error is-dismissible"><p>' . wp_kses_post($error_notice) . '</p>' . $dismiss_button . '</div>';
2911 delete_transient('mxchat_admin_notice_error');
2912 }
2913
2914 // Check for success notices
2915 $success_notice = get_transient('mxchat_admin_notice_success');
2916 if ($success_notice) {
2917 echo '<div class="notice notice-success is-dismissible"><p>' . wp_kses_post($success_notice) . '</p>' . $dismiss_button . '</div>';
2918 delete_transient('mxchat_admin_notice_success');
2919 }
2920
2921 // Check for info notices
2922 $info_notice = get_transient('mxchat_admin_notice_info');
2923 if ($info_notice) {
2924 echo '<div class="notice notice-info is-dismissible"><p>' . wp_kses_post($info_notice) . '</p>' . $dismiss_button . '</div>';
2925 delete_transient('mxchat_admin_notice_info');
2926 }
2927 }
2928
2929
2930 public function mxchat_create_activation_page() {
2931 // Include the new Pro & Extensions page template
2932 require_once plugin_dir_path(__FILE__) . 'admin-pro-page.php';
2933
2934 // Get addons configuration from the MxChat_Addons class
2935 require_once plugin_dir_path(__FILE__) . 'class-mxchat-addons.php';
2936 $addons_instance = new MxChat_Addons();
2937 $addons_config = $addons_instance->get_addons_config();
2938
2939 // Render the consolidated Pro & Extensions page
2940 mxchat_render_pro_page($this, $addons_config);
2941 }
2942
2943 /**
2944 * Check if current activation is linked to a domain
2945 * This checks YOUR website's database, not the user's local database
2946 */
2947 public function is_current_activation_linked($domain) {
2948 $license_key = get_option('mxchat_activation_key');
2949 $email = get_option('mxchat_pro_email');
2950
2951 if (empty($license_key) || empty($email)) {
2952 return false;
2953 }
2954
2955 // Check with YOUR website's API
2956 $response = wp_remote_post('https://mxchat.ai/mxchat-api/check-domain', array(
2957 'body' => array(
2958 'license_key' => $license_key,
2959 'email' => $email,
2960 'domain' => $domain
2961 ),
2962 'timeout' => 10,
2963 'sslverify' => false
2964 ));
2965
2966 if (is_wp_error($response)) {
2967 return false;
2968 }
2969
2970 $body = json_decode(wp_remote_retrieve_body($response), true);
2971 return isset($body['success']) && $body['success'] && isset($body['data']['linked']) && $body['data']['linked'];
2972 }
2973
2974
2975 public function mxchat_actions_page_html() {
2976 if (!current_user_can('manage_options')) {
2977 return;
2978 }
2979
2980 global $wpdb;
2981 $table_name = $wpdb->prefix . 'mxchat_intents';
2982
2983 // Get stats for dashboard
2984 $total_actions = $wpdb->get_var("SELECT COUNT(*) FROM $table_name");
2985 $enabled_actions = $wpdb->get_var("SELECT COUNT(*) FROM $table_name WHERE enabled = 1");
2986 $disabled_actions = $total_actions - $enabled_actions;
2987
2988 // Get unique action types count
2989 $action_types_count = $wpdb->get_var("SELECT COUNT(DISTINCT callback_function) FROM $table_name");
2990
2991 // Get action type distribution
2992 $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");
2993 $available_callbacks = $this->mxchat_get_available_callbacks();
2994 $callback_groups = $this->mxchat_get_available_callbacks(true, true);
2995
2996 $action_type_distribution = array();
2997 foreach ($type_distribution_raw as $row) {
2998 $label = isset($available_callbacks[$row->callback_function]['label'])
2999 ? $available_callbacks[$row->callback_function]['label']
3000 : $row->callback_function;
3001 $action_type_distribution[$label] = $row->count;
3002 }
3003
3004 // Prepare page data
3005 $page_data = array(
3006 'total_actions' => $total_actions,
3007 'enabled_actions' => $enabled_actions,
3008 'disabled_actions' => $disabled_actions,
3009 'action_types_count' => $action_types_count,
3010 'action_type_distribution' => $action_type_distribution,
3011 'available_callbacks' => $available_callbacks,
3012 'callback_groups' => $callback_groups,
3013 );
3014
3015 // Include and render the new template
3016 require_once plugin_dir_path(__FILE__) . 'admin-actions-page.php';
3017 mxchat_render_actions_page($this, $page_data);
3018 }
3019
3020 /**
3021 * LEGACY HTML - Preserved below for reference, to be removed in future update
3022 */
3023 function mxchat_actions_page_legacy_html() {
3024 // This function is deprecated and no longer used
3025 // The new template is in includes/admin-actions-page.php
3026 ?>
3027 <div class="wrap mxchat-wrapper">
3028 <!-- Hero Section -->
3029 <div class="mxchat-hero">
3030 <h1 class="mxchat-main-title">
3031 <span class="mxchat-gradient-text">Actions</span> Manager
3032 </h1>
3033 <p class="mxchat-hero-subtitle">
3034 <?php esc_html_e('Create and manage custom actions to enhance your chatbot\'s capabilities.', 'mxchat'); ?>
3035 </p>
3036 </div>
3037
3038 <!-- Actions Header with Search and Filter -->
3039 <div class="mxchat-actions-header">
3040 <div class="mxchat-actions-filters">
3041 <form method="get" class="mxchat-search-form">
3042 <input type="hidden" name="page" value="mxchat-actions">
3043 <div class="mxchat-search-group">
3044 <span class="dashicons dashicons-search"></span>
3045 <input type="text" name="s" class="mxchat-search-input"
3046 placeholder="<?php esc_attr_e('Search Actions', 'mxchat'); ?>"
3047 value="<?php echo esc_attr($search_term); ?>">
3048 </div>
3049 <select name="callback_filter" class="mxchat-action-filter">
3050 <option value=""><?php esc_html_e('All Action Types', 'mxchat'); ?></option>
3051 <?php foreach ($available_callbacks as $function => $callback_data) :
3052 $label = $callback_data['label']; ?>
3053 <option value="<?php echo esc_attr($function); ?>"
3054 <?php selected($callback_filter, $function); ?>>
3055 <?php echo esc_html($label); ?>
3056 </option>
3057 <?php endforeach; ?>
3058 </select>
3059 <button type="submit" class="mxchat-button-secondary">
3060 <?php esc_html_e('Filter', 'mxchat'); ?>
3061 </button>
3062 </form>
3063 </div>
3064 <div class="mxchat-actions-controls">
3065 <button type="button" id="mxchat-add-action-btn" class="mxchat-button-primary">
3066 <span class="dashicons dashicons-plus-alt"></span>
3067 <?php esc_html_e('Add New Action', 'mxchat'); ?>
3068 </button>
3069 </div>
3070 </div>
3071
3072 <!-- Actions Grid Layout - All actions in a single grid -->
3073 <div class="mxchat-actions-grid">
3074 <div class="mxchat-cards-container">
3075 <?php if (!empty($actions)) : ?>
3076 <?php foreach ($actions as $action) :
3077 $callback_function = $action->callback_function;
3078 $callback_label = isset($available_callbacks[$callback_function]['label'])
3079 ? $available_callbacks[$callback_function]['label']
3080 : $callback_function;
3081 $threshold_value = isset($action->similarity_threshold)
3082 ? round($action->similarity_threshold * 100)
3083 : 85;
3084
3085 // Check if this is a form action
3086 $is_form_action = strpos($action->intent_label, 'Form ') === 0;
3087
3088 // Get action status (enabled/disabled) - default to true if column doesn't exist
3089 $is_enabled = isset($action->enabled) ? (bool)$action->enabled : true;
3090
3091 // Get enabled bots for display
3092 $enabled_bots = [];
3093 if (isset($action->enabled_bots) && !empty($action->enabled_bots)) {
3094 $enabled_bots = json_decode($action->enabled_bots, true);
3095 if (!is_array($enabled_bots)) {
3096 $enabled_bots = ['default'];
3097 }
3098 } else {
3099 $enabled_bots = ['default']; // Backward compatibility
3100 }
3101 ?>
3102 <div class="mxchat-action-card <?php echo $is_form_action ? 'mxchat-form-action' : ''; ?>">
3103 <div class="mxchat-card-header">
3104 <div class="mxchat-card-title"><?php echo esc_html($action->intent_label); ?></div>
3105 <div class="mxchat-card-toggle">
3106 <label class="mxchat-switch">
3107 <input type="checkbox" class="mxchat-action-toggle"
3108 data-action-id="<?php echo esc_attr($action->id); ?>"
3109 <?php checked($is_enabled); ?>>
3110 <span class="mxchat-slider round"></span>
3111 </label>
3112 </div>
3113 </div>
3114
3115 <div class="mxchat-card-body">
3116 <div class="mxchat-card-description">
3117 <strong><?php esc_html_e('Type:', 'mxchat'); ?></strong>
3118 <?php echo esc_html($callback_label); ?>
3119 </div>
3120
3121 <div class="mxchat-card-phrases">
3122 <strong><?php esc_html_e('Trigger phrases:', 'mxchat'); ?></strong>
3123 <div class="mxchat-phrases-preview">
3124 <?php
3125 // Check if the helper function exists, otherwise use a simple substring
3126 if (method_exists($this, 'get_trimmed_phrases')) {
3127 echo esc_html($this->get_trimmed_phrases($action->phrases));
3128 } else {
3129 echo esc_html(strlen($action->phrases) > 100 ?
3130 substr($action->phrases, 0, 97) . '...' :
3131 $action->phrases);
3132 }
3133 ?>
3134 </div>
3135 </div>
3136
3137 <div class="mxchat-threshold-control">
3138 <div class="mxchat-threshold-label">
3139 <?php esc_html_e('Similarity Threshold:', 'mxchat'); ?>
3140 <span class="mxchat-threshold-value"><?php echo esc_html($threshold_value); ?>%</span>
3141 </div>
3142 </div>
3143
3144 <!-- Bot Availability Display (Read-only) -->
3145 <div class="mxchat-card-bots">
3146 <strong><?php esc_html_e('Assigned bots:', 'mxchat'); ?></strong>
3147 <div class="mxchat-bot-badges">
3148 <?php
3149 foreach ($enabled_bots as $bot_id) {
3150 if ($bot_id === 'default') {
3151 echo '<span class="mxchat-bot-badge">' . esc_html__('Default Bot', 'mxchat') . '</span>';
3152 } else {
3153 // Try to get bot name from multi-bot manager
3154 if (class_exists('MxChat_Multi_Bot_Core_Manager')) {
3155 $multi_bot_manager = MxChat_Multi_Bot_Core_Manager::get_instance();
3156 $available_bots = $multi_bot_manager->get_available_bots();
3157 $bot_name = isset($available_bots[$bot_id]) ? $available_bots[$bot_id] : $bot_id;
3158 echo '<span class="mxchat-bot-badge">' . esc_html($bot_name) . '</span>';
3159 } else {
3160 echo '<span class="mxchat-bot-badge">' . esc_html($bot_id) . '</span>';
3161 }
3162 }
3163 }
3164 ?>
3165 </div>
3166 </div>
3167 </div>
3168
3169 <div class="mxchat-card-footer">
3170 <?php
3171 // Check if it's a form action
3172 $is_form_action = preg_match('/Form (\d+)/', $action->intent_label, $form_matches);
3173
3174 // Check if it's a recommendation flow action
3175 $is_flow_action = preg_match('/Recommendation Flow (\d+)/', $action->intent_label, $flow_matches);
3176
3177 if ($is_form_action) {
3178 $form_id = isset($form_matches[1]) ? $form_matches[1] : '';
3179 ?>
3180 <a href="<?php echo esc_url(admin_url('admin.php?page=mxchat-forms&action=edit&form_id=' . $form_id)); ?>"
3181 class="mxchat-button-primary">
3182 <span class="dashicons dashicons-feedback"></span>
3183 <?php esc_html_e('Edit Form', 'mxchat'); ?>
3184 </a>
3185 <?php } elseif ($is_flow_action) {
3186 ?>
3187 <a href="<?php echo esc_url(admin_url('admin.php?page=mxchat-smart-recommender')); ?>"
3188 class="mxchat-button-primary">
3189 <span class="dashicons dashicons-list-view"></span>
3190 <?php esc_html_e('Manage Flows', 'mxchat'); ?>
3191 </a>
3192 <?php } else { ?>
3193 <button type="button"
3194 class="mxchat-button-secondary mxchat-edit-button"
3195 data-action-id="<?php echo esc_attr($action->id); ?>"
3196 data-phrases="<?php echo esc_attr($action->phrases); ?>"
3197 data-label="<?php echo esc_attr($action->intent_label); ?>"
3198 data-threshold="<?php echo esc_attr(round($action->similarity_threshold * 100)); ?>"
3199 data-callback-function="<?php echo esc_attr($action->callback_function); ?>"
3200 data-enabled-bots="<?php echo esc_attr(json_encode($enabled_bots)); ?>">
3201 <span class="dashicons dashicons-edit"></span>
3202 <?php esc_html_e('Edit', 'mxchat'); ?>
3203 </button>
3204 <form method="post"
3205 action="<?php echo esc_url(admin_url('admin-post.php')); ?>"
3206 class="mxchat-delete-form"
3207 onsubmit="return confirm('<?php esc_attr_e('Are you sure you want to delete this action?', 'mxchat'); ?>');">
3208 <?php wp_nonce_field('mxchat_delete_intent_nonce'); ?>
3209 <input type="hidden" name="action" value="mxchat_delete_intent">
3210 <input type="hidden" name="intent_id" value="<?php echo esc_attr($action->id); ?>">
3211 <button type="submit" class="mxchat-button-text mxchat-delete-button">
3212 <span class="dashicons dashicons-trash"></span>
3213 <?php esc_html_e('Delete', 'mxchat'); ?>
3214 </button>
3215 </form>
3216 <?php } ?>
3217 </div>
3218 </div>
3219 <?php endforeach; ?>
3220 <?php else : ?>
3221 <!-- If no actions found -->
3222 <div class="mxchat-no-actions">
3223 <div class="mxchat-empty-state">
3224 <span class="dashicons dashicons-format-chat"></span>
3225 <h2><?php esc_html_e('No actions found', 'mxchat'); ?></h2>
3226 <p><?php esc_html_e('Get started by creating your first action to enhance your chatbot.', 'mxchat'); ?></p>
3227 <button type="button" id="mxchat-create-first-action" class="mxchat-button-primary">
3228 <?php esc_html_e('Create Your First Action', 'mxchat'); ?>
3229 </button>
3230 </div>
3231 </div>
3232 <?php endif; ?>
3233 </div>
3234 </div>
3235
3236 <?php if ($total_pages > 1) : ?>
3237 <div class="mxchat-pagination">
3238 <?php
3239 echo paginate_links(array(
3240 'base' => add_query_arg('paged', '%#%'),
3241 'format' => '',
3242 'prev_text' => __('&laquo; Previous', 'mxchat'),
3243 'next_text' => __('Next &raquo;', 'mxchat'),
3244 'total' => $total_pages,
3245 'current' => $page
3246 ));
3247 ?>
3248 </div>
3249 <?php endif; ?>
3250
3251 <!-- Add/Edit Action Modal with Step-Based Approach -->
3252 <!-- Complete Modal HTML with Defined Groups Variable -->
3253 <div id="mxchat-action-modal" class="mxchat-modal" style="display: none;">
3254 <div class="mxchat-modal-content">
3255 <span class="mxchat-modal-close">&times;</span>
3256
3257 <form id="mxchat-action-form" method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>">
3258 <!-- Dynamic nonce field -->
3259 <div id="action-nonce-container">
3260 <?php wp_nonce_field('mxchat_add_intent_nonce', 'add_intent_nonce'); ?>
3261 </div>
3262 <input type="hidden" name="action" id="form_action_type" value="mxchat_add_intent">
3263 <input type="hidden" name="intent_id" id="edit_action_id" value="">
3264 <input type="hidden" name="callback_function" id="callback_function" value="">
3265
3266 <!-- Step 1: Action Type Selection -->
3267 <div id="mxchat-action-step-1" class="mxchat-action-step active">
3268 <div class="mxchat-step-indicator">
3269 <div class="mxchat-step-number">1</div>
3270 <div class="mxchat-step-title"><?php esc_html_e('Select Action Type', 'mxchat'); ?></div>
3271 </div>
3272
3273 <div id="mxchat-action-type-selector" class="mxchat-action-type-selector">
3274 <div class="mxchat-action-type-search">
3275 <span class="dashicons dashicons-search"></span>
3276 <input type="text" id="action-type-search" placeholder="<?php esc_attr_e('Search action types...', 'mxchat'); ?>" class="mxchat-action-type-search-input">
3277 </div>
3278
3279 <?php
3280 // Get the callbacks - IMPORTANT: Define the $groups variable here
3281 $groups = $this->mxchat_get_available_callbacks(true, true);
3282 ?>
3283
3284 <div class="mxchat-action-type-categories">
3285 <button type="button" class="mxchat-category-button active" data-category="all"><?php esc_html_e('All', 'mxchat'); ?></button>
3286 <?php
3287 // Get unique categories from the defined groups
3288 foreach ($groups as $group_label => $group_callbacks) :
3289 $category_slug = sanitize_title($group_label);
3290 ?>
3291 <button type="button" class="mxchat-category-button" data-category="<?php echo esc_attr($category_slug); ?>"><?php echo esc_html($group_label); ?></button>
3292 <?php endforeach; ?>
3293 </div>
3294
3295 <div class="mxchat-action-types-grid">
3296 <?php
3297 // Generate action cards from available callbacks
3298 foreach ($groups as $group_label => $group_callbacks) :
3299 $category_slug = sanitize_title($group_label);
3300
3301 foreach ($group_callbacks as $function => $data) :
3302 $label = $data['label'];
3303 $pro_only = $data['pro_only'];
3304 $icon = isset($data['icon']) ? $data['icon'] : 'admin-generic';
3305 $description = isset($data['description']) ? $data['description'] : '';
3306 $is_addon = isset($data['addon']) && $data['addon'] !== false;
3307 $addon_name = isset($data['addon_name']) ? $data['addon_name'] : '';
3308 $is_installed = isset($data['installed']) ? $data['installed'] : true;
3309
3310 // Determine card status and styling
3311 $card_class = 'mxchat-action-type-card';
3312 $icon_class = 'mxchat-action-type-icon';
3313 $status_badge = '';
3314
3315 if ($pro_only && !$this->is_activated) {
3316 // Pro feature but no Pro license
3317 $icon_class .= ' pro-feature';
3318 $status_badge = '<span class="mxchat-pro-badge">' . esc_html__('Pro', 'mxchat') . '</span>';
3319 }
3320
3321 if ($is_addon && !$is_installed) {
3322 // Add-on not installed
3323 $card_class .= ' not-installed';
3324 $status_badge .= '<span class="mxchat-addon-badge">' . esc_html__('Add-on Required', 'mxchat') . '</span>';
3325 }
3326
3327 // Default description if none provided
3328 if (empty($description)) {
3329 $description = sprintf(
3330 esc_html__('Use the %s action in your chatbot', 'mxchat'),
3331 $label
3332 );
3333 }
3334 ?>
3335 <div class="<?php echo esc_attr($card_class); ?>"
3336 data-category="<?php echo esc_attr($category_slug); ?>"
3337 data-value="<?php echo esc_attr($function); ?>"
3338 data-label="<?php echo esc_attr($label); ?>"
3339 data-pro="<?php echo $pro_only ? 'true' : 'false'; ?>"
3340 data-addon="<?php echo esc_attr($is_addon ? $data['addon'] : ''); ?>"
3341 data-installed="<?php echo $is_installed ? 'true' : 'false'; ?>">
3342 <div class="<?php echo esc_attr($icon_class); ?>">
3343 <span class="dashicons dashicons-<?php echo esc_attr($icon); ?>"></span>
3344 </div>
3345 <div class="mxchat-action-type-info">
3346 <h4><?php echo esc_html($label); ?></h4>
3347 <p><?php echo esc_html($description); ?></p>
3348 <?php if (!empty($status_badge)) : ?>
3349 <?php echo $status_badge; ?>
3350 <?php endif; ?>
3351
3352 <?php if ($is_addon && !$is_installed) : ?>
3353 <div class="mxchat-addon-info">
3354 <?php echo esc_html(sprintf(
3355 __('Requires %s', 'mxchat'),
3356 $addon_name
3357 )); ?>
3358 </div>
3359 <?php endif; ?>
3360 </div>
3361 </div>
3362 <?php endforeach;
3363 endforeach; ?>
3364 </div>
3365 </div>
3366
3367 <div class="mxchat-modal-actions">
3368 <button type="button" class="mxchat-button-secondary mxchat-modal-cancel">
3369 <?php esc_html_e('Cancel', 'mxchat'); ?>
3370 </button>
3371 </div>
3372 </div>
3373
3374 <!-- Step 2: Action Configuration -->
3375 <div id="mxchat-action-step-2" class="mxchat-action-step">
3376 <div class="mxchat-step-indicator">
3377 <div class="mxchat-step-number">2</div>
3378 <div class="mxchat-step-title"><?php esc_html_e('Configure Action', 'mxchat'); ?></div>
3379 </div>
3380
3381 <div class="mxchat-selected-action">
3382 <button type="button" class="mxchat-back-button" id="mxchat-back-to-step-1">
3383 <span class="dashicons dashicons-arrow-left-alt"></span>
3384 <?php esc_html_e('Back to Action Types', 'mxchat'); ?>
3385 </button>
3386 <div class="mxchat-selected-action-info">
3387 <div id="selected-action-icon" class="mxchat-action-type-icon">
3388 <span class="dashicons dashicons-admin-generic"></span>
3389 </div>
3390 <div class="mxchat-selected-action-details">
3391 <h3 id="selected-action-title"><?php esc_html_e('Selected Action', 'mxchat'); ?></h3>
3392 <p id="selected-action-description"><?php esc_html_e('Configure this action for your chatbot', 'mxchat'); ?></p>
3393 </div>
3394 </div>
3395 </div>
3396
3397 <div class="mxchat-form-group">
3398 <label for="intent_label">
3399 <?php esc_html_e('Action Label (For your reference only)', 'mxchat'); ?>
3400 </label>
3401 <input name="intent_label" type="text" id="intent_label" required
3402 class="mxchat-intent-input"
3403 placeholder="<?php esc_attr_e('Example: Newsletter Signup', 'mxchat'); ?>">
3404 </div>
3405
3406 <div class="mxchat-form-group">
3407 <label for="phrases">
3408 <?php esc_html_e('Trigger Phrases (comma-separated)', 'mxchat'); ?>
3409 </label>
3410 <textarea name="phrases" id="action_phrases" rows="5" required
3411 class="mxchat-intent-textarea"
3412 placeholder="<?php esc_attr_e('Example: sign me up, subscribe me, I want to join, add me to the newsletter', 'mxchat'); ?>"></textarea>
3413 </div>
3414
3415 <div class="mxchat-form-group">
3416 <label for="similarity_threshold">
3417 <?php esc_html_e('Similarity Threshold', 'mxchat'); ?>
3418 <span class="mxchat-threshold-value-display">85%</span>
3419 </label>
3420 <div class="mxchat-slider-group modal-slider">
3421 <input type="range"
3422 name="similarity_threshold"
3423 id="similarity_threshold"
3424 min="10"
3425 max="95"
3426 value="85"
3427 class="mxchat-intent-slider"
3428 oninput="document.querySelector('.mxchat-threshold-value-display').textContent = this.value + '%'">
3429 </div>
3430 <div class="mxchat-threshold-hint">
3431 <?php esc_html_e('Lower values (10-30) make the action trigger more easily. Higher values (70-95) require more exact matches.', 'mxchat'); ?>
3432 </div>
3433 </div>
3434
3435 <!-- Bot Selection Section -->
3436 <div class="mxchat-form-group">
3437 <label for="enabled_bots">
3438 <?php esc_html_e('Which bots should we enable this action for?', 'mxchat'); ?>
3439 </label>
3440 <div class="mxchat-bot-selector">
3441 <div class="mxchat-bot-option">
3442 <label class="mxchat-checkbox-label">
3443 <input type="checkbox"
3444 name="enabled_bots[]"
3445 value="default"
3446 id="bot_default"
3447 checked="checked">
3448 <span class="mxchat-checkmark"></span>
3449 <?php esc_html_e('Default Bot', 'mxchat'); ?>
3450 </label>
3451 </div>
3452
3453 <?php if (class_exists('MxChat_Multi_Bot_Core_Manager')) :
3454 $multi_bot_manager = MxChat_Multi_Bot_Core_Manager::get_instance();
3455 $available_bots = $multi_bot_manager->get_available_bots();
3456
3457 foreach ($available_bots as $bot_id => $bot_name) :
3458 if ($bot_id === 'default') continue; // Skip default, already shown above
3459 ?>
3460 <div class="mxchat-bot-option">
3461 <label class="mxchat-checkbox-label">
3462 <input type="checkbox"
3463 name="enabled_bots[]"
3464 value="<?php echo esc_attr($bot_id); ?>"
3465 id="bot_<?php echo esc_attr($bot_id); ?>">
3466 <span class="mxchat-checkmark"></span>
3467 <?php echo esc_html($bot_name); ?>
3468 </label>
3469 </div>
3470 <?php
3471 endforeach;
3472 endif; ?>
3473 </div>
3474 </div>
3475
3476 <div class="mxchat-modal-actions">
3477 <button type="button" class="mxchat-button-secondary mxchat-modal-cancel">
3478 <?php esc_html_e('Cancel', 'mxchat'); ?>
3479 </button>
3480 <button type="submit" class="mxchat-button-primary" id="mxchat-save-action-btn">
3481 <?php esc_html_e('Save Action', 'mxchat'); ?>
3482 </button>
3483 </div>
3484 </div>
3485 </form>
3486 </div>
3487 </div>
3488
3489 <div id="mxchat-action-loading" class="mxchat-action-loading" style="display: none;">
3490 <div class="mxchat-action-loading-spinner"></div>
3491 <div class="mxchat-action-loading-text">
3492 <?php esc_html_e('Saving action, please wait...', 'mxchat'); ?>
3493 </div>
3494 </div>
3495 </div><!-- .mxchat-wrapper -->
3496 <?php
3497 }
3498 private function get_trimmed_phrases($phrases, $max_length = 100) {
3499 if (strlen($phrases) <= $max_length) {
3500 return $phrases;
3501 }
3502
3503 $trimmed = substr($phrases, 0, $max_length);
3504 $last_comma = strrpos($trimmed, ',');
3505
3506 if ($last_comma !== false) {
3507 $trimmed = substr($trimmed, 0, $last_comma);
3508 }
3509
3510 return $trimmed . '...';
3511 }
3512 public function mxchat_add_enabled_column_to_intents() {
3513 global $wpdb;
3514 $table_name = $wpdb->prefix . 'mxchat_intents';
3515
3516 // Check if the column already exists
3517 $columns = $wpdb->get_results("SHOW COLUMNS FROM $table_name LIKE 'enabled'");
3518
3519 if (empty($columns)) {
3520 // Add the column with default value of 1 (enabled)
3521 $wpdb->query("ALTER TABLE $table_name ADD COLUMN enabled TINYINT(1) NOT NULL DEFAULT 1");
3522 }
3523 }
3524
3525 public function mxchat_handle_delete_intent() {
3526 if ( ! current_user_can( 'manage_options' ) ) {
3527 wp_die( esc_html__('Unauthorized user', 'mxchat') );
3528 }
3529
3530 check_admin_referer('mxchat_delete_intent_nonce');
3531
3532 if (isset($_POST['intent_id'])) {
3533 global $wpdb;
3534 $table_name = $wpdb->prefix . 'mxchat_intents';
3535 $intent_id = intval($_POST['intent_id']);
3536
3537 $wpdb->delete($table_name, ['id' => $intent_id], ['%d']);
3538 }
3539
3540 wp_safe_redirect(admin_url('admin.php?page=mxchat-actions'));
3541 exit;
3542 }
3543 public function mxchat_handle_edit_intent() {
3544 // Security checks (nonce and permissions)
3545 if (!current_user_can('manage_options')) {
3546 wp_die(esc_html__('Unauthorized user', 'mxchat'));
3547 }
3548 check_admin_referer('mxchat_edit_intent');
3549
3550 // Get POST data
3551 $intent_id = isset($_POST['intent_id']) ? absint($_POST['intent_id']) : 0;
3552 $intent_label = isset($_POST['intent_label']) ? sanitize_text_field($_POST['intent_label']) : '';
3553 $phrases_input = isset($_POST['phrases']) ? sanitize_textarea_field($_POST['phrases']) : '';
3554 $threshold_percentage = isset($_POST['similarity_threshold']) ? intval($_POST['similarity_threshold']) : 85;
3555 $similarity_threshold = min(95, max(10, $threshold_percentage)) / 100; // Convert to 0.10–0.95
3556
3557 // Handle enabled_bots
3558 $enabled_bots = isset($_POST['enabled_bots']) ? $_POST['enabled_bots'] : array('default');
3559 $enabled_bots = array_map('sanitize_text_field', $enabled_bots);
3560
3561 // Ensure default is always included for backward compatibility
3562 if (!in_array('default', $enabled_bots)) {
3563 $enabled_bots[] = 'default';
3564 }
3565
3566 $enabled_bots_json = json_encode($enabled_bots);
3567
3568 // Validate inputs
3569 if (!$intent_id || empty($intent_label) || empty($phrases_input)) {
3570 $this->handle_embedding_error(__('Invalid input. Please ensure all fields are filled out.', 'mxchat'));
3571 return;
3572 }
3573
3574 $phrases_array = array_map('sanitize_text_field', array_filter(array_map('trim', explode(',', $phrases_input))));
3575 if (empty($phrases_array)) {
3576 $this->handle_embedding_error(__('Please enter at least one valid phrase.', 'mxchat'));
3577 return;
3578 }
3579
3580 // Generate embeddings with improved error handling
3581 $vectors = [];
3582 $failed_phrases = [];
3583
3584 foreach ($phrases_array as $phrase) {
3585 $embedding_vector = $this->mxchat_generate_embedding($phrase, $this->options['api_key']);
3586 if (is_array($embedding_vector)) {
3587 $vectors[] = $embedding_vector;
3588 } else {
3589 $failed_phrases[] = $phrase;
3590 }
3591 }
3592
3593 if (!empty($failed_phrases)) {
3594 $this->handle_embedding_error(
3595 sprintf(
3596 __('Error generating embeddings for phrases: %s. Check your embedding API.', 'mxchat'),
3597 implode(', ', $failed_phrases)
3598 )
3599 );
3600 return;
3601 }
3602
3603 if (empty($vectors)) {
3604 $this->handle_embedding_error(__('No valid embeddings generated. Please check your phrases.', 'mxchat'));
3605 return;
3606 }
3607
3608 $combined_vector = $this->mxchat_average_vectors($vectors);
3609 $serialized_vector = maybe_serialize($combined_vector);
3610
3611 // Update the database
3612 global $wpdb;
3613 $table_name = $wpdb->prefix . 'mxchat_intents';
3614
3615 $result = $wpdb->update(
3616 $table_name,
3617 array(
3618 'intent_label' => $intent_label,
3619 'phrases' => implode(', ', $phrases_array),
3620 'embedding_vector' => $serialized_vector,
3621 'similarity_threshold' => $similarity_threshold,
3622 'enabled_bots' => $enabled_bots_json // Include enabled_bots in update
3623 ),
3624 array('id' => $intent_id),
3625 array('%s', '%s', '%s', '%f', '%s'), // Format: string, string, string, float, string
3626 array('%d') // Where format: integer
3627 );
3628
3629 if (false === $result) {
3630 $this->handle_embedding_error(__('Failed to update action in database.', 'mxchat'));
3631 return;
3632 }
3633
3634 // Set success message and redirect
3635 set_transient('mxchat_admin_notice_success', __('Intent updated successfully!', 'mxchat'), 60);
3636
3637 $redirect_url = add_query_arg(
3638 array(
3639 'page' => 'mxchat-actions'
3640 ),
3641 admin_url('admin.php')
3642 );
3643 wp_safe_redirect($redirect_url);
3644 exit;
3645 }
3646 public function mxchat_handle_add_intent() {
3647 if (!current_user_can('manage_options')) {
3648 wp_die(esc_html__('Unauthorized user', 'mxchat'));
3649 }
3650 check_admin_referer('mxchat_add_intent_nonce');
3651 global $wpdb;
3652 $table_name = $wpdb->prefix . 'mxchat_intents';
3653
3654 // Sanitize and get form data
3655 $intent_label = isset($_POST['intent_label']) ? sanitize_text_field($_POST['intent_label']) : '';
3656 $phrases_input = isset($_POST['phrases']) ? sanitize_textarea_field($_POST['phrases']) : '';
3657 $callback_function = isset($_POST['callback_function']) ? sanitize_text_field($_POST['callback_function']) : '';
3658
3659 // Get similarity threshold from form (convert percentage to decimal)
3660 $similarity_threshold = isset($_POST['similarity_threshold']) ? floatval($_POST['similarity_threshold']) / 100 : 0.85;
3661
3662 // Handle enabled_bots
3663 $enabled_bots = isset($_POST['enabled_bots']) ? $_POST['enabled_bots'] : array('default');
3664 $enabled_bots = array_map('sanitize_text_field', $enabled_bots);
3665
3666 // Ensure default is always included for backward compatibility with existing actions
3667 if (!in_array('default', $enabled_bots)) {
3668 $enabled_bots[] = 'default';
3669 }
3670
3671 $enabled_bots_json = json_encode($enabled_bots);
3672
3673 // Validate required fields
3674 if (empty($intent_label) || empty($callback_function) || empty($phrases_input)) {
3675 $this->handle_embedding_error(__('Invalid input. Please ensure all fields are filled out.', 'mxchat'));
3676 return;
3677 }
3678
3679 // Validate callback function
3680 $available_callbacks = $this->mxchat_get_available_callbacks();
3681 if (!array_key_exists($callback_function, $available_callbacks)) {
3682 $this->handle_embedding_error(__('Invalid callback function selected.', 'mxchat'));
3683 return;
3684 }
3685
3686 // Check Pro requirements
3687 $is_pro_only = $available_callbacks[$callback_function]['pro_only'];
3688 if ($is_pro_only && !$this->is_activated) {
3689 $this->handle_embedding_error(__('This callback function is available in the Pro version only.', 'mxchat'));
3690 return;
3691 }
3692
3693 // Process phrases
3694 $phrases_array = array_map('sanitize_text_field', array_filter(array_map('trim', explode(',', $phrases_input))));
3695 if (empty($phrases_array)) {
3696 $this->handle_embedding_error(__('Please enter at least one valid phrase.', 'mxchat'));
3697 return;
3698 }
3699
3700 // Generate embeddings with improved error handling
3701 $vectors = [];
3702 $failed_phrases = [];
3703 foreach ($phrases_array as $phrase) {
3704 $embedding_vector = $this->mxchat_generate_embedding($phrase, $this->options['api_key']);
3705 if (is_array($embedding_vector)) {
3706 $vectors[] = $embedding_vector;
3707 } else {
3708 $failed_phrases[] = $phrase;
3709 }
3710 }
3711
3712 // Check for embedding failures
3713 if (!empty($failed_phrases)) {
3714 $this->handle_embedding_error(
3715 sprintf(
3716 __('Error generating embeddings for phrases: %s. Check your embedding API.', 'mxchat'),
3717 implode(', ', $failed_phrases)
3718 )
3719 );
3720 return;
3721 }
3722
3723 if (empty($vectors)) {
3724 $this->handle_embedding_error(__('No valid embeddings generated. Please check your phrases.', 'mxchat'));
3725 return;
3726 }
3727
3728 // Create combined vector and insert into database
3729 $combined_vector = $this->mxchat_average_vectors($vectors);
3730 $serialized_vector = maybe_serialize($combined_vector);
3731
3732 $result = $wpdb->insert($table_name, [
3733 'intent_label' => $intent_label,
3734 'phrases' => implode(', ', $phrases_array),
3735 'embedding_vector' => $serialized_vector,
3736 'callback_function' => $callback_function,
3737 'similarity_threshold' => $similarity_threshold,
3738 'enabled_bots' => $enabled_bots_json // NEW field
3739 ]);
3740
3741 if ($result === false) {
3742 $this->handle_embedding_error(__('Database error: ', 'mxchat') . $wpdb->last_error);
3743 return;
3744 }
3745
3746 // Set success message and redirect
3747 set_transient('mxchat_admin_notice_success', __('New intent added successfully!', 'mxchat'), 60);
3748 wp_safe_redirect(admin_url('admin.php?page=mxchat-actions'));
3749 exit;
3750 }
3751
3752
3753
3754
3755 private function handle_embedding_error($message, $redirect = true) {
3756 // Store the error message in the existing transient
3757 set_transient('mxchat_admin_notice_error', $message, 60);
3758
3759 if ($redirect) {
3760 // Redirect back to the actions page
3761 $redirect_url = add_query_arg(
3762 array(
3763 'page' => 'mxchat-actions'
3764 ),
3765 admin_url('admin.php')
3766 );
3767 wp_safe_redirect($redirect_url);
3768 exit;
3769 }
3770 }
3771
3772 /**
3773 * AJAX handler to fetch actions list for the new split-panel UI
3774 */
3775 public function mxchat_fetch_actions_list() {
3776 check_ajax_referer('mxchat_actions_nonce', 'security');
3777
3778 if (!current_user_can('manage_options')) {
3779 wp_send_json_error(__('Unauthorized', 'mxchat'));
3780 }
3781
3782 global $wpdb;
3783 $table_name = $wpdb->prefix . 'mxchat_intents';
3784
3785 $page = isset($_POST['page']) ? max(1, intval($_POST['page'])) : 1;
3786 $per_page = isset($_POST['per_page']) ? min(100, max(1, intval($_POST['per_page']))) : 50;
3787 $offset = ($page - 1) * $per_page;
3788 $search = isset($_POST['search']) ? sanitize_text_field($_POST['search']) : '';
3789 $callback_filter = isset($_POST['callback_filter']) ? sanitize_text_field($_POST['callback_filter']) : '';
3790 $sort_order = isset($_POST['sort_order']) && $_POST['sort_order'] === 'asc' ? 'ASC' : 'DESC';
3791
3792 // Build WHERE clause
3793 $where = '1=1';
3794 $params = array();
3795
3796 if ($search) {
3797 $search_like = '%' . $wpdb->esc_like($search) . '%';
3798 $where .= ' AND (intent_label LIKE %s OR phrases LIKE %s)';
3799 $params[] = $search_like;
3800 $params[] = $search_like;
3801 }
3802
3803 if ($callback_filter) {
3804 $where .= ' AND callback_function = %s';
3805 $params[] = $callback_filter;
3806 }
3807
3808 // Get total count
3809 $count_query = "SELECT COUNT(*) FROM $table_name WHERE $where";
3810 if (!empty($params)) {
3811 $count_query = $wpdb->prepare($count_query, $params);
3812 }
3813 $total_actions = $wpdb->get_var($count_query);
3814
3815 // Get actions
3816 $query = "SELECT * FROM $table_name WHERE $where ORDER BY id $sort_order LIMIT %d OFFSET %d";
3817 $all_params = array_merge($params, array($per_page, $offset));
3818 $actions = $wpdb->get_results($wpdb->prepare($query, $all_params));
3819
3820 // Get available callbacks for labels/icons
3821 $available_callbacks = $this->mxchat_get_available_callbacks();
3822
3823 // Format actions for response
3824 $formatted_actions = array();
3825 foreach ($actions as $action) {
3826 $callback_data = isset($available_callbacks[$action->callback_function])
3827 ? $available_callbacks[$action->callback_function]
3828 : array('label' => $action->callback_function, 'icon' => 'admin-generic');
3829
3830 $enabled_bots = json_decode($action->enabled_bots, true);
3831 if (!is_array($enabled_bots)) {
3832 $enabled_bots = array('default');
3833 }
3834
3835 $formatted_actions[] = array(
3836 'id' => intval($action->id),
3837 'label' => $action->intent_label,
3838 'phrases' => $action->phrases,
3839 'callback_function' => $action->callback_function,
3840 'callback_label' => $callback_data['label'],
3841 'icon' => isset($callback_data['icon']) ? $callback_data['icon'] : 'admin-generic',
3842 'threshold' => round($action->similarity_threshold * 100),
3843 'enabled' => (bool) $action->enabled,
3844 'enabled_bots' => $enabled_bots,
3845 );
3846 }
3847
3848 $total_pages = ceil($total_actions / $per_page);
3849 $showing_start = $total_actions > 0 ? $offset + 1 : 0;
3850 $showing_end = min($offset + $per_page, $total_actions);
3851
3852 wp_send_json_success(array(
3853 'actions' => $formatted_actions,
3854 'page' => $page,
3855 'per_page' => $per_page,
3856 'total_actions' => intval($total_actions),
3857 'total_pages' => $total_pages,
3858 'showing_start' => $showing_start,
3859 'showing_end' => $showing_end,
3860 ));
3861 }
3862
3863 /**
3864 * AJAX handler to toggle action enabled status
3865 */
3866 public function mxchat_toggle_action_status() {
3867 check_ajax_referer('mxchat_actions_nonce', 'security');
3868
3869 if (!current_user_can('manage_options')) {
3870 wp_send_json_error(__('Unauthorized', 'mxchat'));
3871 }
3872
3873 $action_id = isset($_POST['action_id']) ? intval($_POST['action_id']) : 0;
3874 $enabled = isset($_POST['enabled']) ? intval($_POST['enabled']) : 0;
3875
3876 if (!$action_id) {
3877 wp_send_json_error(__('Invalid action ID', 'mxchat'));
3878 }
3879
3880 global $wpdb;
3881 $table_name = $wpdb->prefix . 'mxchat_intents';
3882
3883 $result = $wpdb->update(
3884 $table_name,
3885 array('enabled' => $enabled ? 1 : 0),
3886 array('id' => $action_id),
3887 array('%d'),
3888 array('%d')
3889 );
3890
3891 if ($result === false) {
3892 wp_send_json_error(__('Failed to update action status', 'mxchat'));
3893 }
3894
3895 wp_send_json_success(array('enabled' => (bool) $enabled));
3896 }
3897
3898 /**
3899 * AJAX handler to bulk delete actions
3900 */
3901 public function mxchat_bulk_delete_actions() {
3902 check_ajax_referer('mxchat_delete_intent_nonce', 'security');
3903
3904 if (!current_user_can('manage_options')) {
3905 wp_send_json_error(__('Unauthorized', 'mxchat'));
3906 }
3907
3908 $action_ids = isset($_POST['action_ids']) ? array_map('intval', (array) $_POST['action_ids']) : array();
3909
3910 if (empty($action_ids)) {
3911 wp_send_json_error(__('No actions selected', 'mxchat'));
3912 }
3913
3914 global $wpdb;
3915 $table_name = $wpdb->prefix . 'mxchat_intents';
3916
3917 $placeholders = implode(',', array_fill(0, count($action_ids), '%d'));
3918 $query = $wpdb->prepare("DELETE FROM $table_name WHERE id IN ($placeholders)", $action_ids);
3919 $result = $wpdb->query($query);
3920
3921 if ($result === false) {
3922 wp_send_json_error(__('Failed to delete actions', 'mxchat'));
3923 }
3924
3925 wp_send_json_success(array('deleted' => $result));
3926 }
3927
3928 /**
3929 * AJAX handler to add a new intent/action
3930 */
3931 public function mxchat_add_intent_ajax() {
3932 check_ajax_referer('mxchat_add_intent_nonce', 'security');
3933
3934 if (!current_user_can('manage_options')) {
3935 wp_send_json_error(__('Unauthorized', 'mxchat'));
3936 }
3937
3938 global $wpdb;
3939 $table_name = $wpdb->prefix . 'mxchat_intents';
3940
3941 // Sanitize input
3942 $intent_label = isset($_POST['intent_label']) ? sanitize_text_field($_POST['intent_label']) : '';
3943 $phrases_input = isset($_POST['phrases']) ? sanitize_textarea_field($_POST['phrases']) : '';
3944 $callback_function = isset($_POST['callback_function']) ? sanitize_text_field($_POST['callback_function']) : '';
3945 $similarity_threshold = isset($_POST['similarity_threshold']) ? floatval($_POST['similarity_threshold']) / 100 : 0.85;
3946 $enabled_bots = isset($_POST['enabled_bots']) ? array_map('sanitize_text_field', (array) $_POST['enabled_bots']) : array('default');
3947
3948 // Validate
3949 if (empty($intent_label) || empty($phrases_input) || empty($callback_function)) {
3950 wp_send_json_error(__('Please fill in all required fields.', 'mxchat'));
3951 }
3952
3953 // Process phrases
3954 $phrases_array = array_filter(array_map('trim', preg_split('/[\n,]+/', $phrases_input)));
3955 if (empty($phrases_array)) {
3956 wp_send_json_error(__('Please provide at least one trigger phrase.', 'mxchat'));
3957 }
3958
3959 // Generate embedding (combine phrases into single string for embedding)
3960 $embedding_vector = $this->mxchat_generate_embedding(implode(' ', $phrases_array));
3961 if (is_wp_error($embedding_vector)) {
3962 // Fallback: store without embedding
3963 $serialized_vector = null;
3964 } else {
3965 $serialized_vector = maybe_serialize($embedding_vector);
3966 }
3967
3968 // Ensure default bot is included
3969 if (!in_array('default', $enabled_bots)) {
3970 $enabled_bots[] = 'default';
3971 }
3972 $enabled_bots_json = json_encode($enabled_bots);
3973
3974 // Insert
3975 $result = $wpdb->insert(
3976 $table_name,
3977 array(
3978 'intent_label' => $intent_label,
3979 'phrases' => implode(', ', $phrases_array),
3980 'embedding_vector' => $serialized_vector,
3981 'similarity_threshold' => $similarity_threshold,
3982 'callback_function' => $callback_function,
3983 'enabled' => 1,
3984 'enabled_bots' => $enabled_bots_json,
3985 )
3986 );
3987
3988 if ($result === false) {
3989 wp_send_json_error(__('Failed to add action to database.', 'mxchat'));
3990 }
3991
3992 wp_send_json_success(array('id' => $wpdb->insert_id));
3993 }
3994
3995 /**
3996 * AJAX handler to edit an existing intent/action
3997 */
3998 public function mxchat_edit_intent_ajax() {
3999 check_ajax_referer('mxchat_edit_intent', 'security');
4000
4001 if (!current_user_can('manage_options')) {
4002 wp_send_json_error(__('Unauthorized', 'mxchat'));
4003 }
4004
4005 global $wpdb;
4006 $table_name = $wpdb->prefix . 'mxchat_intents';
4007
4008 // Sanitize input
4009 $intent_id = isset($_POST['intent_id']) ? intval($_POST['intent_id']) : 0;
4010 $intent_label = isset($_POST['intent_label']) ? sanitize_text_field($_POST['intent_label']) : '';
4011 $phrases_input = isset($_POST['phrases']) ? sanitize_textarea_field($_POST['phrases']) : '';
4012 $callback_function = isset($_POST['callback_function']) ? sanitize_text_field($_POST['callback_function']) : '';
4013 $similarity_threshold = isset($_POST['similarity_threshold']) ? floatval($_POST['similarity_threshold']) / 100 : 0.85;
4014 $enabled_bots = isset($_POST['enabled_bots']) ? array_map('sanitize_text_field', (array) $_POST['enabled_bots']) : array('default');
4015
4016 // Validate
4017 if (!$intent_id || empty($intent_label) || empty($phrases_input)) {
4018 wp_send_json_error(__('Please fill in all required fields.', 'mxchat'));
4019 }
4020
4021 // Process phrases
4022 $phrases_array = array_filter(array_map('trim', preg_split('/[\n,]+/', $phrases_input)));
4023 if (empty($phrases_array)) {
4024 wp_send_json_error(__('Please provide at least one trigger phrase.', 'mxchat'));
4025 }
4026
4027 // Generate new embedding (combine phrases into single string for embedding)
4028 $embedding_vector = $this->mxchat_generate_embedding(implode(' ', $phrases_array));
4029 if (is_wp_error($embedding_vector)) {
4030 // Keep existing embedding
4031 $serialized_vector = null;
4032 $update_data = array(
4033 'intent_label' => $intent_label,
4034 'phrases' => implode(', ', $phrases_array),
4035 'similarity_threshold' => $similarity_threshold,
4036 'callback_function' => $callback_function,
4037 'enabled_bots' => json_encode($enabled_bots),
4038 );
4039 } else {
4040 $serialized_vector = maybe_serialize($embedding_vector);
4041 $update_data = array(
4042 'intent_label' => $intent_label,
4043 'phrases' => implode(', ', $phrases_array),
4044 'embedding_vector' => $serialized_vector,
4045 'similarity_threshold' => $similarity_threshold,
4046 'callback_function' => $callback_function,
4047 'enabled_bots' => json_encode($enabled_bots),
4048 );
4049 }
4050
4051 // Ensure default bot is included
4052 if (!in_array('default', $enabled_bots)) {
4053 $enabled_bots[] = 'default';
4054 }
4055 $update_data['enabled_bots'] = json_encode($enabled_bots);
4056
4057 $result = $wpdb->update(
4058 $table_name,
4059 $update_data,
4060 array('id' => $intent_id),
4061 null,
4062 array('%d')
4063 );
4064
4065 if ($result === false) {
4066 wp_send_json_error(__('Failed to update action.', 'mxchat'));
4067 }
4068
4069 wp_send_json_success(array('updated' => true));
4070 }
4071
4072 /**
4073 * AJAX handler to delete a single intent/action
4074 */
4075 public function mxchat_delete_intent_ajax() {
4076 check_ajax_referer('mxchat_delete_intent_nonce', 'security');
4077
4078 if (!current_user_can('manage_options')) {
4079 wp_send_json_error(__('Unauthorized', 'mxchat'));
4080 }
4081
4082 $intent_id = isset($_POST['intent_id']) ? intval($_POST['intent_id']) : 0;
4083
4084 if (!$intent_id) {
4085 wp_send_json_error(__('Invalid action ID', 'mxchat'));
4086 }
4087
4088 global $wpdb;
4089 $table_name = $wpdb->prefix . 'mxchat_intents';
4090
4091 $result = $wpdb->delete($table_name, array('id' => $intent_id), array('%d'));
4092
4093 if ($result === false) {
4094 wp_send_json_error(__('Failed to delete action', 'mxchat'));
4095 }
4096
4097 wp_send_json_success(array('deleted' => true));
4098 }
4099
4100
4101 /**
4102 * Enhanced get_available_callbacks function with form action exclusion
4103 *
4104 * @param bool $grouped Whether to return callbacks grouped by category
4105 * @param bool $include_all Whether to include all potential actions (even if add-on not installed)
4106 * @return array Callbacks data with icons, descriptions and availability status
4107 */
4108 private function mxchat_get_available_callbacks($grouped = false, $include_all = true) {
4109 // Load WordPress plugin functions if needed
4110 if (!function_exists('get_plugins')) {
4111 require_once ABSPATH . 'wp-admin/includes/plugin.php';
4112 }
4113
4114 // Get active plugins
4115 $active_plugins = get_option('active_plugins', array());
4116
4117 // Functions to exclude from the action selector only if Pro is activated
4118 // If user doesn't have Pro, show these so they can see what they're missing
4119 $excluded_when_pro_active_functions = array(
4120 'mxchat_handle_form_collection' // Forms add-on action
4121 );
4122
4123 // Always excluded functions (regardless of Pro status)
4124 $always_excluded_functions = array();
4125
4126 // Combine exclusion lists based on Pro activation status
4127 $excluded_functions = $always_excluded_functions;
4128 if ($this->is_activated) {
4129 // Only exclude add-on managed functions if Pro is active
4130 $excluded_functions = array_merge($excluded_functions, $excluded_when_pro_active_functions);
4131 }
4132
4133 // Define add-on plugin files and their corresponding action functions
4134 $addon_plugins = array(
4135 'mxchat-woo/mxchat-woo.php' => array(
4136 'functions' => array(
4137 'mxchat_handle_product_recommendations',
4138 'mxchat_handle_order_history',
4139 'mxchat_show_product_card',
4140 'mxchat_add_to_cart',
4141 'mxchat_checkout_redirect'
4142 ),
4143 'name' => __('WooCommerce Add-on', 'mxchat'),
4144 'pro_required' => true
4145 ),
4146 'mxchat-perplexity/mxchat-perplexity.php' => array(
4147 'functions' => array('mxchat_perplexity_research'),
4148 'name' => __('Perplexity Add-on', 'mxchat'),
4149 'pro_required' => true
4150 ),
4151 'mxchat-forms/mxchat-forms.php' => array(
4152 'functions' => array('mxchat_handle_form_collection'),
4153 'name' => __('Forms Add-on', 'mxchat'),
4154 'pro_required' => true
4155 ),
4156 // Add other add-ons and their functions here
4157 );
4158
4159 // Get the functions that are provided by active add-ons
4160 $addon_provided_functions = array();
4161 $addon_function_mapping = array(); // Maps functions to their add-on info
4162
4163 // Check which add-ons are active
4164 foreach ($addon_plugins as $plugin_file => $addon_info) {
4165 $is_active = in_array($plugin_file, $active_plugins);
4166
4167 // For each function in this addon
4168 foreach ($addon_info['functions'] as $function) {
4169 // Consider a function installed only if:
4170 // 1. The add-on is active AND
4171 // 2. Either it doesn't require Pro OR Pro is activated
4172 $is_installed = $is_active && (!$addon_info['pro_required'] || $this->is_activated);
4173
4174 // If the add-on is installed, mark this function as provided by an add-on
4175 if ($is_installed) {
4176 $addon_provided_functions[] = $function;
4177 }
4178
4179 // Store addon info for this function regardless of installation status
4180 $addon_function_mapping[$function] = array(
4181 'addon' => basename(dirname($plugin_file)),
4182 'addon_name' => $addon_info['name'],
4183 'pro_required' => $addon_info['pro_required'],
4184 'is_active' => $is_active,
4185 'is_installed' => $is_installed
4186 );
4187 }
4188 }
4189
4190 // Core callbacks - always available in the base plugin
4191 $core_callbacks = array(
4192 'mxchat_handle_email_capture' => array(
4193 'label' => __('Loops Email Capture', 'mxchat'),
4194 'pro_only' => false,
4195 'group' => __('Customer Engagement', 'mxchat'),
4196 'icon' => 'email-alt',
4197 'description' => __('Collect visitor emails for your mailing list in Loops', 'mxchat'),
4198 'addon' => false, // Not from an add-on
4199 'installed' => true // Always installed with base plugin
4200 ),
4201 'mxchat_handle_search_request' => array(
4202 'label' => __('Brave Web Search', 'mxchat'),
4203 'pro_only' => false,
4204 'group' => __('Search Features', 'mxchat'),
4205 'icon' => 'search',
4206 'description' => __('Let users search the web directly from the chat', 'mxchat'),
4207 'addon' => false,
4208 'installed' => true
4209 ),
4210 'mxchat_handle_image_search_request' => array(
4211 'label' => __('Brave Image Search', 'mxchat'),
4212 'pro_only' => false,
4213 'group' => __('Search Features', 'mxchat'),
4214 'icon' => 'format-image',
4215 'description' => __('Search and display images in the chat conversation', 'mxchat'),
4216 'addon' => false,
4217 'installed' => true
4218 ),
4219 // Pro core features - check is_activated property
4220 'mxchat_generate_image' => array(
4221 'label' => __('Generate Image', 'mxchat'),
4222 'pro_only' => false,
4223 'group' => __('Other Features', 'mxchat'),
4224 'icon' => 'art',
4225 'description' => __('Create images with DALL-E 3 from OpenAI (requires OpenAI API key)', 'mxchat'),
4226 'addon' => false,
4227 'installed' => true
4228 ),
4229 'mxchat_handle_pdf_discussion' => array(
4230 'label' => __('Chat with PDF', 'mxchat'),
4231 'pro_only' => false,
4232 'group' => __('Other Features', 'mxchat'),
4233 'icon' => 'media-document',
4234 'description' => __('Answer questions about uploaded PDF documents', 'mxchat'),
4235 'addon' => false,
4236 'installed' => true
4237 ),
4238 'mxchat_live_agent_handover' => array(
4239 'label' => __('Slack Live Agent', 'mxchat'),
4240 'pro_only' => false,
4241 'group' => __('Customer Engagement', 'mxchat'),
4242 'icon' => 'admin-users',
4243 'description' => __('Transfer conversation to a human support agent on Slack', 'mxchat'),
4244 'addon' => false,
4245 'installed' => true
4246 ),
4247 'mxchat_telegram_live_agent_handover' => array(
4248 'label' => __('Telegram Live Agent', 'mxchat'),
4249 'pro_only' => false,
4250 'group' => __('Customer Engagement', 'mxchat'),
4251 'icon' => 'format-chat',
4252 'description' => __('Transfer conversation to a human support agent on Telegram', 'mxchat'),
4253 'addon' => false,
4254 'installed' => true
4255 ),
4256 'mxchat_handle_switch_to_chatbot_intent' => array(
4257 'label' => __('Back to Chatbot', 'mxchat'),
4258 'pro_only' => false,
4259 'group' => __('Customer Engagement', 'mxchat'),
4260 'icon' => 'backup',
4261 'description' => __('Return from live agent mode to AI chatbot', 'mxchat'),
4262 'addon' => false,
4263 'installed' => true
4264 ),
4265 );
4266
4267 // Add-on callbacks with placeholders - only include if the add-on is NOT active
4268 $addon_callbacks = array(
4269 // WooCommerce Add-on
4270 'mxchat_handle_product_recommendations' => array(
4271 'label' => __('Product Recommendations', 'mxchat'),
4272 'pro_only' => true,
4273 'group' => __('WooCommerce Features', 'mxchat'),
4274 'icon' => 'cart',
4275 'description' => __('Suggest products based on customer preferences', 'mxchat'),
4276 ),
4277 'mxchat_handle_order_history' => array(
4278 'label' => __('Order History', 'mxchat'),
4279 'pro_only' => true,
4280 'group' => __('WooCommerce Features', 'mxchat'),
4281 'icon' => 'clipboard',
4282 'description' => __('Allow customers to check their order status', 'mxchat'),
4283 ),
4284 'mxchat_show_product_card' => array(
4285 'label' => __('Show Product Card', 'mxchat'),
4286 'pro_only' => true,
4287 'group' => __('WooCommerce Features', 'mxchat'),
4288 'icon' => 'products',
4289 'description' => __('Display product information in the chat', 'mxchat'),
4290 ),
4291 'mxchat_add_to_cart' => array(
4292 'label' => __('Add to Cart', 'mxchat'),
4293 'pro_only' => true,
4294 'group' => __('WooCommerce Features', 'mxchat'),
4295 'icon' => 'plus-alt',
4296 'description' => __('Add products to cart directly from chat', 'mxchat'),
4297 ),
4298 'mxchat_checkout_redirect' => array(
4299 'label' => __('Proceed to Checkout', 'mxchat'),
4300 'pro_only' => true,
4301 'group' => __('WooCommerce Features', 'mxchat'),
4302 'icon' => 'arrow-right-alt',
4303 'description' => __('Redirect customer to checkout page', 'mxchat'),
4304 ),
4305
4306 // Perplexity Add-on
4307 'mxchat_perplexity_research' => array(
4308 'label' => __('Perplexity Research', 'mxchat'),
4309 'pro_only' => true,
4310 'group' => __('Search Features', 'mxchat'),
4311 'icon' => 'book-alt',
4312 'description' => __('Allows the chatbot to search the web for accurate, up-to-date answers', 'mxchat'),
4313 ),
4314
4315 // Forms Add-on (only shown when Pro is not activated)
4316 'mxchat_handle_form_collection' => array(
4317 'label' => __('Form Collection', 'mxchat'),
4318 'pro_only' => true,
4319 'group' => __('Form Features', 'mxchat'),
4320 'icon' => 'feedback',
4321 'description' => __('Collect user information through custom forms in chat', 'mxchat'),
4322 ),
4323 );
4324
4325 // Enhance add-on callbacks with installation status and addon info
4326 foreach ($addon_callbacks as $function => $data) {
4327 if (isset($addon_function_mapping[$function])) {
4328 $addon_info = $addon_function_mapping[$function];
4329
4330 $addon_callbacks[$function]['addon'] = $addon_info['addon'];
4331 $addon_callbacks[$function]['addon_name'] = $addon_info['addon_name'];
4332 $addon_callbacks[$function]['installed'] = $addon_info['is_installed'];
4333
4334 // Set pro_only based on add-on configuration
4335 $addon_callbacks[$function]['pro_only'] = $addon_info['pro_required'];
4336 } else {
4337 $addon_callbacks[$function]['addon'] = 'unknown';
4338 $addon_callbacks[$function]['addon_name'] = __('Unknown Add-on', 'mxchat');
4339 $addon_callbacks[$function]['installed'] = false;
4340 }
4341 }
4342
4343 // Initialize callbacks with core features
4344 $callbacks = $core_callbacks;
4345
4346 // Get callbacks from active add-ons
4347 $active_addon_callbacks = apply_filters('mxchat_available_callbacks', array());
4348
4349 // Add placeholder callbacks only for add-ons that aren't active
4350 if ($include_all) {
4351 foreach ($addon_callbacks as $function => $data) {
4352 // Skip placeholders for functions provided by active add-ons
4353 if (in_array($function, $addon_provided_functions)) {
4354 continue;
4355 }
4356
4357 // Skip excluded functions
4358 if (in_array($function, $excluded_functions)) {
4359 continue;
4360 }
4361
4362 // Add the placeholder
4363 $callbacks[$function] = $data;
4364 }
4365 }
4366
4367 // Add callbacks from active add-ons (will override placeholders)
4368 foreach ($active_addon_callbacks as $function => $data) {
4369 // Skip excluded functions
4370 if (in_array($function, $excluded_functions)) {
4371 continue;
4372 }
4373
4374 // Always include callbacks from add-ons
4375 $callbacks[$function] = $data;
4376
4377 // Ensure they have the proper add-on info
4378 if (isset($addon_function_mapping[$function])) {
4379 $addon_info = $addon_function_mapping[$function];
4380 $callbacks[$function]['addon'] = $addon_info['addon'];
4381 $callbacks[$function]['addon_name'] = $addon_info['addon_name'];
4382 $callbacks[$function]['installed'] = $addon_info['is_installed'];
4383 $callbacks[$function]['pro_only'] = $addon_info['pro_required'];
4384 }
4385 }
4386
4387 // Just before returning callbacks, sort them to prioritize free features
4388 if (!$grouped) {
4389 // Create temporary arrays for sorting
4390 $free_callbacks = array();
4391 $pro_callbacks = array();
4392
4393 // Split callbacks into free and pro
4394 foreach ($callbacks as $key => $data) {
4395 if (isset($data['pro_only']) && $data['pro_only']) {
4396 $pro_callbacks[$key] = $data;
4397 } else {
4398 $free_callbacks[$key] = $data;
4399 }
4400 }
4401
4402 // Merge with free callbacks first
4403 $callbacks = array_merge($free_callbacks, $pro_callbacks);
4404 }
4405
4406 // Return grouped structure if requested
4407 if ($grouped) {
4408 $grouped_callbacks = array();
4409 foreach ($callbacks as $key => $data) {
4410 $group_label = isset($data['group']) ? $data['group'] : __('Other Features', 'mxchat');
4411
4412 // Ensure we carry forward all the new fields in grouped mode
4413 $callback_data = array(
4414 'label' => $data['label'],
4415 'pro_only' => isset($data['pro_only']) ? $data['pro_only'] : false,
4416 'icon' => isset($data['icon']) ? $data['icon'] : 'admin-generic',
4417 'description' => isset($data['description']) ? $data['description'] : __('Custom action for your chatbot', 'mxchat'),
4418 'addon' => isset($data['addon']) ? $data['addon'] : false,
4419 'addon_name' => isset($data['addon_name']) ? $data['addon_name'] : '',
4420 'installed' => isset($data['installed']) ? $data['installed'] : true
4421 );
4422
4423 $grouped_callbacks[$group_label][$key] = $callback_data;
4424 }
4425
4426 // Sort within each group to prioritize free features
4427 foreach ($grouped_callbacks as $group => $items) {
4428 $free_items = array();
4429 $pro_items = array();
4430
4431 foreach ($items as $key => $data) {
4432 if (isset($data['pro_only']) && $data['pro_only']) {
4433 $pro_items[$key] = $data;
4434 } else {
4435 $free_items[$key] = $data;
4436 }
4437 }
4438
4439 $grouped_callbacks[$group] = array_merge($free_items, $pro_items);
4440 }
4441
4442 return $grouped_callbacks;
4443 }
4444
4445 return $callbacks;
4446 }
4447
4448 public function mxchat_page_init() {
4449 register_setting(
4450 'mxchat_option_group',
4451 'mxchat_options',
4452 array($this, 'mxchat_sanitize')
4453 );
4454
4455 register_setting(
4456 'mxchat_option_group',
4457 'mxchat_similarity_threshold',
4458 array(
4459 'type' => 'number',
4460 'sanitize_callback' => function($value) {
4461 $value = absint($value);
4462 return min(max($value, 20), 95);
4463 },
4464 'default' => 80,
4465 )
4466 );
4467
4468 // Chatbot Settings Section
4469 add_settings_section(
4470 'mxchat_chatbot_section',
4471 esc_html__('Chatbot Settings', 'mxchat'),
4472 null,
4473 'mxchat-chatbot'
4474 );
4475
4476 // API Keys Settings Section
4477 add_settings_section(
4478 'mxchat_api_keys_section',
4479 esc_html__('API Keys', 'mxchat'),
4480 array($this, 'mxchat_api_keys_section_callback'),
4481 'mxchat-api-keys'
4482 );
4483
4484 // OpenAI API Key
4485 add_settings_field(
4486 'api_key',
4487 esc_html__('OpenAI API Key', 'mxchat'),
4488 array($this, 'api_key_callback'),
4489 'mxchat-api-keys',
4490 'mxchat_api_keys_section'
4491 );
4492
4493 // X.AI API Key
4494 add_settings_field(
4495 'xai_api_key',
4496 esc_html__('X.AI API Key', 'mxchat'),
4497 array($this, 'xai_api_key_callback'),
4498 'mxchat-api-keys',
4499 'mxchat_api_keys_section'
4500 );
4501
4502 // Claude API Key
4503 add_settings_field(
4504 'claude_api_key',
4505 esc_html__('Claude API Key', 'mxchat'),
4506 array($this, 'claude_api_key_callback'),
4507 'mxchat-api-keys',
4508 'mxchat_api_keys_section'
4509 );
4510
4511 // DeepSeek API Key
4512 add_settings_field(
4513 'deepseek_api_key',
4514 esc_html__('DeepSeek API Key', 'mxchat'),
4515 array($this, 'deepseek_api_key_callback'),
4516 'mxchat-api-keys',
4517 'mxchat_api_keys_section'
4518 );
4519
4520 // Google Gemini API Key
4521 add_settings_field(
4522 'gemini_api_key',
4523 esc_html__('Google Gemini API Key', 'mxchat'),
4524 array($this, 'gemini_api_key_callback'),
4525 'mxchat-api-keys',
4526 'mxchat_api_keys_section'
4527 );
4528
4529 // Voyage AI API Key
4530 add_settings_field(
4531 'voyage_api_key',
4532 esc_html__('Voyage AI API Key', 'mxchat'),
4533 array($this, 'voyage_api_key_callback'),
4534 'mxchat-api-keys',
4535 'mxchat_api_keys_section'
4536 );
4537
4538 // OpenRouter API Key
4539 add_settings_field(
4540 'openrouter_api_key',
4541 esc_html__('OpenRouter API Key', 'mxchat'),
4542 array($this, 'openrouter_api_key_callback'),
4543 'mxchat-api-keys',
4544 'mxchat_api_keys_section'
4545 );
4546
4547 // Loops API Key
4548 add_settings_field(
4549 'loops_api_key',
4550 esc_html__('Loops API Key', 'mxchat'),
4551 array($this, 'mxchat_loops_api_key_callback'),
4552 'mxchat-api-keys',
4553 'mxchat_api_keys_section'
4554 );
4555
4556 // Brave Search API Key
4557 add_settings_field(
4558 'brave_api_key',
4559 __('Brave API Key', 'mxchat'),
4560 array($this, 'mxchat_brave_api_key_callback'),
4561 'mxchat-api-keys',
4562 'mxchat_api_keys_section'
4563 );
4564
4565 // Frontend Debugger Toggle
4566 add_settings_field(
4567 'show_frontend_debugger',
4568 esc_html__('Frontend Debugger', 'mxchat'),
4569 array($this, 'mxchat_frontend_debugger_callback'),
4570 'mxchat-chatbot',
4571 'mxchat_chatbot_section'
4572 );
4573
4574 // Similarity Threshold Slider
4575 add_settings_field(
4576 'similarity_threshold', // Field ID
4577 esc_html__('Similarity Threshold', 'mxchat'), // Field title
4578 array($this, 'mxchat_similarity_threshold_callback'), // Callback function
4579 'mxchat-chatbot', // Page
4580 'mxchat_chatbot_section' // Section
4581 );
4582
4583 // RAG Sources Limit Slider
4584 add_settings_field(
4585 'rag_sources_limit', // Field ID
4586 esc_html__('RAG Sources Limit', 'mxchat'), // Field title
4587 array($this, 'mxchat_rag_sources_limit_callback'), // Callback function
4588 'mxchat-chatbot', // Page
4589 'mxchat_chatbot_section' // Section
4590 );
4591
4592 add_settings_field(
4593 'append_to_body',
4594 esc_html__('Auto-Display Chatbot', 'mxchat'),
4595 array($this, 'mxchat_append_to_body_callback'),
4596 'mxchat-chatbot',
4597 'mxchat_chatbot_section'
4598 );
4599
4600 add_settings_field(
4601 'contextual_awareness_toggle',
4602 esc_html__('Contextual Awareness', 'mxchat'),
4603 array($this, 'mxchat_contextual_awareness_callback'),
4604 'mxchat-chatbot',
4605 'mxchat_chatbot_section'
4606 );
4607
4608 add_settings_field(
4609 'citation_links_toggle',
4610 esc_html__('Citation Links', 'mxchat'),
4611 array($this, 'mxchat_citation_links_toggle_callback'),
4612 'mxchat-chatbot',
4613 'mxchat_chatbot_section'
4614 );
4615
4616 add_settings_field(
4617 'enable_streaming_toggle',
4618 esc_html__('Enable Streaming', 'mxchat'),
4619 array($this, 'enable_streaming_toggle_callback'),
4620 'mxchat-chatbot',
4621 'mxchat_chatbot_section', // Same section as your working toggle
4622 array(
4623 'class' => 'mxchat-setting-row streaming-setting',
4624 'style' => 'display: none;' // Hidden by default, shown when OpenAI/Claude selected
4625 )
4626 );
4627
4628
4629 add_settings_field(
4630 'model',
4631 esc_html__('Chat Model', 'mxchat'),
4632 array($this, 'mxchat_model_callback'),
4633 'mxchat-chatbot',
4634 'mxchat_chatbot_section'
4635 );
4636
4637 add_settings_field(
4638 'embedding_model',
4639 esc_html__('Embedding Model', 'mxchat'),
4640 array($this, 'embedding_model_callback'),
4641 'mxchat-chatbot',
4642 'mxchat_chatbot_section'
4643 );
4644
4645 add_settings_field(
4646 'system_prompt_instructions',
4647 esc_html__('AI Instructions (Behavior)', 'mxchat'),
4648 array($this, 'system_prompt_instructions_callback'),
4649 'mxchat-chatbot',
4650 'mxchat_chatbot_section'
4651 );
4652
4653
4654 add_settings_field(
4655 'top_bar_title',
4656 esc_html__('Top Bar Title', 'mxchat'),
4657 array($this, 'mxchat_top_bar_title_callback'),
4658 'mxchat-chatbot',
4659 'mxchat_chatbot_section'
4660 );
4661
4662 add_settings_field(
4663 'ai_agent_text',
4664 esc_html__('AI Agent Text', 'mxchat'),
4665 array($this, 'mxchat_ai_agent_text_callback'),
4666 'mxchat-chatbot',
4667 'mxchat_chatbot_section'
4668 );
4669
4670 add_settings_field(
4671 'enable_email_block',
4672 esc_html__('Require Email To Chat', 'mxchat'),
4673 array($this, 'enable_email_block_callback'),
4674 'mxchat-chatbot',
4675 'mxchat_chatbot_section'
4676 );
4677
4678 add_settings_field(
4679 'email_blocker_header_content',
4680 esc_html__('Require Email Chat Content', 'mxchat'),
4681 array($this, 'email_blocker_header_content_callback'),
4682 'mxchat-chatbot',
4683 'mxchat_chatbot_section'
4684 );
4685
4686 add_settings_field(
4687 'email_blocker_button_text',
4688 esc_html__('Require Email Chat Button Text', 'mxchat'),
4689 [$this, 'email_blocker_button_text_callback'],
4690 'mxchat-chatbot',
4691 'mxchat_chatbot_section'
4692 );
4693
4694 add_settings_field(
4695 'enable_name_field',
4696 esc_html__('Require Name Field', 'mxchat'),
4697 array($this, 'enable_name_field_callback'),
4698 'mxchat-chatbot',
4699 'mxchat_chatbot_section'
4700 );
4701
4702 add_settings_field(
4703 'name_field_placeholder',
4704 esc_html__('Name Field Placeholder', 'mxchat'),
4705 array($this, 'name_field_placeholder_callback'),
4706 'mxchat-chatbot',
4707 'mxchat_chatbot_section'
4708 );
4709
4710 add_settings_field(
4711 'intro_message',
4712 esc_html__('Introductory Message', 'mxchat'),
4713 array($this, 'mxchat_intro_message_callback'),
4714 'mxchat-chatbot',
4715 'mxchat_chatbot_section'
4716 );
4717
4718 add_settings_field(
4719 'input_copy',
4720 esc_html__('Input Copy', 'mxchat'),
4721 array($this, 'mxchat_input_copy_callback'),
4722 'mxchat-chatbot',
4723 'mxchat_chatbot_section'
4724 );
4725
4726 add_settings_field(
4727 'pre_chat_message',
4728 esc_html__('Chat Teaser Pop-up', 'mxchat'),
4729 array($this, 'mxchat_pre_chat_message_callback'),
4730 'mxchat-chatbot',
4731 'mxchat_chatbot_section'
4732 );
4733
4734 add_settings_field(
4735 'privacy_toggle',
4736 esc_html__('Toggle Privacy Notice', 'mxchat'),
4737 array($this, 'mxchat_privacy_toggle_callback'),
4738 'mxchat-chatbot',
4739 'mxchat_chatbot_section'
4740 );
4741
4742 add_settings_field(
4743 'complianz_toggle',
4744 esc_html__('Enable Complianz', 'mxchat'),
4745 array($this, 'mxchat_complianz_toggle_callback'),
4746 'mxchat-chatbot',
4747 'mxchat_chatbot_section'
4748 );
4749
4750 add_settings_field(
4751 'link_target_toggle',
4752 esc_html__('Open Links in a New Tab', 'mxchat'),
4753 array($this, 'mxchat_link_target_toggle_callback'),
4754 'mxchat-chatbot',
4755 'mxchat_chatbot_section'
4756 );
4757
4758 add_settings_field(
4759 'chat_persistence_toggle',
4760 esc_html__('Enable Chat Persistence', 'mxchat'),
4761 array($this, 'mxchat_chat_persistence_toggle_callback'),
4762 'mxchat-chatbot',
4763 'mxchat_chatbot_section'
4764 );
4765
4766 add_settings_field(
4767 'popular_question_1',
4768 esc_html__('Quick Question 1', 'mxchat'),
4769 array($this, 'mxchat_popular_question_1_callback'),
4770 'mxchat-chatbot',
4771 'mxchat_chatbot_section'
4772 );
4773
4774 add_settings_field(
4775 'popular_question_2',
4776 esc_html__('Quick Question 2', 'mxchat'),
4777 array($this, 'mxchat_popular_question_2_callback'),
4778 'mxchat-chatbot',
4779 'mxchat_chatbot_section'
4780 );
4781
4782 add_settings_field(
4783 'popular_question_3',
4784 esc_html__('Quick Question 3', 'mxchat'),
4785 array($this, 'mxchat_popular_question_3_callback'),
4786 'mxchat-chatbot',
4787 'mxchat_chatbot_section'
4788 );
4789
4790 add_settings_field(
4791 'additional_popular_questions',
4792 esc_html__('Additional Quick Questions', 'mxchat'),
4793 array($this, 'mxchat_additional_popular_questions_callback'),
4794 'mxchat-chatbot',
4795 'mxchat_chatbot_section'
4796 );
4797
4798
4799 add_settings_field(
4800 'rate_limits',
4801 __('Rate Limits Settings', 'mxchat'),
4802 array($this, 'mxchat_rate_limits_callback'),
4803 'mxchat-chatbot',
4804 'mxchat_chatbot_section'
4805 );
4806
4807 // Loops Settings Section
4808 add_settings_section(
4809 'mxchat_loops_section',
4810 esc_html__('Loops Settings', 'mxchat'),
4811 null,
4812 'mxchat-embed'
4813 );
4814
4815 // Loops Settings Fields (API Key moved to API Keys tab)
4816 add_settings_field(
4817 'loops_mailing_list',
4818 esc_html__('Loops Mailing List', 'mxchat'),
4819 array($this, 'mxchat_loops_mailing_list_callback'),
4820 'mxchat-embed',
4821 'mxchat_loops_section'
4822 );
4823
4824 add_settings_field(
4825 'triggered_phrase_response',
4826 esc_html__('Triggered Phrase Response', 'mxchat'),
4827 array($this, 'mxchat_triggered_phrase_response_callback'),
4828 'mxchat-embed',
4829 'mxchat_loops_section'
4830 );
4831
4832 add_settings_field(
4833 'email_capture_response',
4834 esc_html__('Email Capture Response', 'mxchat'),
4835 array($this, 'mxchat_email_capture_response_callback'),
4836 'mxchat-embed',
4837 'mxchat_loops_section'
4838 );
4839
4840 // Brave Search Settings Fields
4841 add_settings_section(
4842 'mxchat_brave_section',
4843 __('Brave Search Settings', 'mxchat'),
4844 array($this, 'mxchat_brave_section_callback'),
4845 'mxchat-embed'
4846 );
4847
4848 // Brave API Key moved to API Keys tab
4849 add_settings_field(
4850 'brave_image_count',
4851 __('Number of Images to Return', 'mxchat'),
4852 array($this, 'mxchat_brave_image_count_callback'),
4853 'mxchat-embed',
4854 'mxchat_brave_section'
4855 );
4856
4857 add_settings_field(
4858 'brave_safe_search',
4859 __('Safe Search', 'mxchat'),
4860 array($this, 'mxchat_brave_safe_search_callback'),
4861 'mxchat-embed',
4862 'mxchat_brave_section'
4863 );
4864
4865 add_settings_field(
4866 'brave_news_count',
4867 __('Number of News Articles', 'mxchat'),
4868 array($this, 'mxchat_brave_news_count_callback'),
4869 'mxchat-embed',
4870 'mxchat_brave_section'
4871 );
4872
4873 add_settings_field(
4874 'brave_country',
4875 __('Country', 'mxchat'),
4876 array($this, 'mxchat_brave_country_callback'),
4877 'mxchat-embed',
4878 'mxchat_brave_section'
4879 );
4880
4881 add_settings_field(
4882 'brave_language',
4883 __('Language', 'mxchat'),
4884 array($this, 'mxchat_brave_language_callback'),
4885 'mxchat-embed',
4886 'mxchat_brave_section'
4887 );
4888
4889 // Chat with PDF Intent Settings Fields
4890 add_settings_section(
4891 'mxchat_pdf_intent_section',
4892 __('Toolbar Settings & Intents', 'mxchat'),
4893 array($this, 'mxchat_pdf_intent_section_callback'),
4894 'mxchat-embed'
4895 );
4896
4897 add_settings_field(
4898 'chat_toolbar_toggle',
4899 __('Show Chat Toolbar', 'mxchat'),
4900 array($this, 'mxchat_chat_toolbar_toggle_callback'),
4901 'mxchat-embed',
4902 'mxchat_pdf_intent_section'
4903 );
4904
4905 // PDF Upload Button Toggle
4906 add_settings_field(
4907 'show_pdf_upload_button',
4908 __('Show PDF Upload Button', 'mxchat'),
4909 array($this, 'mxchat_show_pdf_upload_button_callback'),
4910 'mxchat-embed',
4911 'mxchat_pdf_intent_section'
4912 );
4913
4914 // Word Upload Button Toggle
4915 add_settings_field(
4916 'show_word_upload_button',
4917 __('Show Word Upload Button', 'mxchat'),
4918 array($this, 'mxchat_show_word_upload_button_callback'),
4919 'mxchat-embed',
4920 'mxchat_pdf_intent_section'
4921 );
4922
4923 add_settings_field(
4924 'pdf_intent_trigger_text',
4925 __('Intent Trigger Text', 'mxchat'),
4926 array($this, 'mxchat_pdf_intent_trigger_text_callback'),
4927 'mxchat-embed',
4928 'mxchat_pdf_intent_section'
4929 );
4930
4931 add_settings_field(
4932 'pdf_intent_success_text',
4933 __('Success Text', 'mxchat'),
4934 array($this, 'mxchat_pdf_intent_success_text_callback'),
4935 'mxchat-embed',
4936 'mxchat_pdf_intent_section'
4937 );
4938
4939 add_settings_field(
4940 'pdf_intent_error_text',
4941 __('Error Text', 'mxchat'),
4942 array($this, 'mxchat_pdf_intent_error_text_callback'),
4943 'mxchat-embed',
4944 'mxchat_pdf_intent_section'
4945 );
4946
4947 // Add PDF Maximum Pages Field
4948 add_settings_field(
4949 'pdf_max_pages',
4950 __('Maximum Document Pages', 'mxchat'),
4951 array($this, 'mxchat_pdf_max_pages_callback'),
4952 'mxchat-embed',
4953 'mxchat_pdf_intent_section'
4954 );
4955
4956 // Live Agent Settings Fields
4957 add_settings_section(
4958 'mxchat_live_agent_section',
4959 __('Live Agent Settings', 'mxchat'),
4960 array($this, 'mxchat_live_agent_section_callback'),
4961 'mxchat-embed'
4962 );
4963
4964 // Live Agent Status Fields (add at top of live agent settings)
4965 add_settings_field(
4966 'live_agent_status',
4967 __('Live Agent Status', 'mxchat'),
4968 array($this, 'mxchat_live_agent_status_callback'),
4969 'mxchat-embed',
4970 'mxchat_live_agent_section'
4971 );
4972
4973 add_settings_field(
4974 'live_agent_notification_message',
4975 __('Notification Message', 'mxchat'),
4976 array($this, 'mxchat_live_agent_notification_message_callback'),
4977 'mxchat-embed',
4978 'mxchat_live_agent_section'
4979 );
4980
4981 add_settings_field(
4982 'live_agent_away_message',
4983 __('Away Message', 'mxchat'),
4984 array($this, 'mxchat_live_agent_away_message_callback'),
4985 'mxchat-embed',
4986 'mxchat_live_agent_section'
4987 );
4988
4989 add_settings_field(
4990 'live_agent_user_ids',
4991 __('Slack Agent User IDs', 'mxchat'),
4992 array($this, 'mxchat_live_agent_user_ids_callback'),
4993 'mxchat-embed',
4994 'mxchat_live_agent_section'
4995 );
4996
4997 add_settings_field(
4998 'live_agent_webhook_url',
4999 __('Slack Webhook URL', 'mxchat'),
5000 array($this, 'mxchat_live_agent_webhook_url_callback'),
5001 'mxchat-embed',
5002 'mxchat_live_agent_section'
5003 );
5004
5005 add_settings_field(
5006 'live_agent_secret_key',
5007 __('Slack Secret Key', 'mxchat'),
5008 array($this, 'mxchat_live_agent_secret_key_callback'),
5009 'mxchat-embed',
5010 'mxchat_live_agent_section'
5011 );
5012
5013 // Live Agent Integration Fields
5014 add_settings_field(
5015 'live_agent_bot_token',
5016 __('Slack Bot OAuth Token', 'mxchat'),
5017 array($this, 'mxchat_live_agent_bot_token_callback'),
5018 'mxchat-embed',
5019 'mxchat_live_agent_section'
5020 );
5021
5022 // Telegram Integration Section
5023 add_settings_section(
5024 'mxchat_telegram_section',
5025 __('Telegram Settings', 'mxchat'),
5026 array($this, 'mxchat_telegram_section_callback'),
5027 'mxchat-embed'
5028 );
5029
5030 add_settings_field(
5031 'telegram_status',
5032 __('Live Agent Status', 'mxchat'),
5033 array($this, 'mxchat_telegram_status_callback'),
5034 'mxchat-embed',
5035 'mxchat_telegram_section'
5036 );
5037
5038 add_settings_field(
5039 'telegram_notification_message',
5040 __('Notification Message', 'mxchat'),
5041 array($this, 'mxchat_telegram_notification_message_callback'),
5042 'mxchat-embed',
5043 'mxchat_telegram_section'
5044 );
5045
5046 add_settings_field(
5047 'telegram_away_message',
5048 __('Away Message', 'mxchat'),
5049 array($this, 'mxchat_telegram_away_message_callback'),
5050 'mxchat-embed',
5051 'mxchat_telegram_section'
5052 );
5053
5054 add_settings_field(
5055 'telegram_bot_token',
5056 __('Telegram Bot Token', 'mxchat'),
5057 array($this, 'mxchat_telegram_bot_token_callback'),
5058 'mxchat-embed',
5059 'mxchat_telegram_section'
5060 );
5061
5062 add_settings_field(
5063 'telegram_group_id',
5064 __('Telegram Group ID', 'mxchat'),
5065 array($this, 'mxchat_telegram_group_id_callback'),
5066 'mxchat-embed',
5067 'mxchat_telegram_section'
5068 );
5069
5070 add_settings_field(
5071 'telegram_webhook_secret',
5072 __('Webhook Secret Token', 'mxchat'),
5073 array($this, 'mxchat_telegram_webhook_secret_callback'),
5074 'mxchat-embed',
5075 'mxchat_telegram_section'
5076 );
5077
5078 // General Settings Section
5079 add_settings_section(
5080 'mxchat_general_section',
5081 esc_html__('YouTube Tutorials', 'mxchat'),
5082 null,
5083 'mxchat-general'
5084 );
5085 }
5086
5087 public function mxchat_prompts_page_init() {
5088 register_setting(
5089 'mxchat_prompts_options',
5090 'mxchat_prompts_options',
5091 array(
5092 'type' => 'array',
5093 'description' => __('MXChat Knowledge Base Settings', 'mxchat'),
5094 'default' => array(
5095 'mxchat_auto_sync_posts' => 0,
5096 'mxchat_auto_sync_pages' => 0,
5097 'mxchat_use_pinecone' => 0,
5098 'mxchat_pinecone_api_key' => '',
5099 'mxchat_pinecone_environment' => '',
5100 'mxchat_pinecone_index' => '',
5101 'mxchat_pinecone_host' => '',
5102 ),
5103 'sanitize_callback' => array($this, 'sanitize_prompts_options'),
5104 )
5105 );
5106
5107 add_action('admin_notices', array($this, 'sync_settings_notice'));
5108 }
5109
5110 public function mxchat_transcripts_page_init() {
5111 register_setting(
5112 'mxchat_transcripts_options',
5113 'mxchat_transcripts_options',
5114 array(
5115 'type' => 'array',
5116 'description' => __('MXChat Transcripts Notification Settings', 'mxchat'),
5117 'default' => array(
5118 'mxchat_enable_notifications' => 0,
5119 'mxchat_notification_email' => get_option('admin_email'),
5120 'mxchat_auto_delete_transcripts' => 'never',
5121 ),
5122 'sanitize_callback' => array($this, 'sanitize_transcripts_options'),
5123 )
5124 );
5125
5126 add_settings_section(
5127 'mxchat_transcripts_notification_section',
5128 esc_html__('Chat Notification Settings', 'mxchat'),
5129 array($this, 'mxchat_transcripts_notification_section_callback'),
5130 'mxchat-transcripts'
5131 );
5132
5133 add_settings_field(
5134 'mxchat_enable_notifications',
5135 esc_html__('Enable Chat Notifications', 'mxchat'),
5136 array($this, 'mxchat_enable_notifications_callback'),
5137 'mxchat-transcripts',
5138 'mxchat_transcripts_notification_section'
5139 );
5140
5141 add_settings_field(
5142 'mxchat_notification_email',
5143 esc_html__('Notification Email Address', 'mxchat'),
5144 array($this, 'mxchat_notification_email_callback'),
5145 'mxchat-transcripts',
5146 'mxchat_transcripts_notification_section'
5147 );
5148
5149 add_settings_field(
5150 'mxchat_auto_delete_transcripts',
5151 esc_html__('Auto-Delete Old Transcripts', 'mxchat'),
5152 array($this, 'mxchat_auto_delete_transcripts_callback'),
5153 'mxchat-transcripts',
5154 'mxchat_transcripts_notification_section'
5155 );
5156
5157 add_settings_field(
5158 'mxchat_auto_email_transcript',
5159 esc_html__('Auto-Email Full Transcript', 'mxchat'),
5160 array($this, 'mxchat_auto_email_transcript_callback'),
5161 'mxchat-transcripts',
5162 'mxchat_transcripts_notification_section'
5163 );
5164 }
5165
5166
5167 /**
5168 * Sanitize all prompts options
5169 *
5170 * @param array $input The unsanitized options array
5171 * @return array The sanitized options array
5172 */
5173 public function sanitize_prompts_options($input) {
5174 // Log the incoming input.
5175 //error_log('Sanitizing inputs: ' . print_r($input, true));
5176
5177 $sanitized = array();
5178
5179 // Boolean options
5180 $sanitized['mxchat_auto_sync_posts'] = isset($input['mxchat_auto_sync_posts']) ? 1 : 0;
5181 $sanitized['mxchat_auto_sync_pages'] = isset($input['mxchat_auto_sync_pages']) ? 1 : 0;
5182 $sanitized['mxchat_use_pinecone'] = !empty($input['mxchat_use_pinecone']) ? 1 : 0;
5183
5184 // API Key: if less than 32 characters, flag as invalid.
5185 $api_key = sanitize_text_field($input['mxchat_pinecone_api_key'] ?? '');
5186 if (!empty($api_key) && strlen($api_key) < 32) {
5187 add_settings_error(
5188 'mxchat_prompts_options',
5189 'invalid_api_key',
5190 __('The Pinecone API key appears to be invalid. Please check your API key.', 'mxchat')
5191 );
5192 $existing_options = get_option('mxchat_prompts_options', array());
5193 $sanitized['mxchat_pinecone_api_key'] = $existing_options['mxchat_pinecone_api_key'] ?? '';
5194 } else {
5195 $sanitized['mxchat_pinecone_api_key'] = $api_key;
5196 }
5197
5198 // Environment and Index Name
5199 $sanitized['mxchat_pinecone_environment'] = sanitize_text_field($input['mxchat_pinecone_environment'] ?? '');
5200 $sanitized['mxchat_pinecone_index'] = sanitize_text_field($input['mxchat_pinecone_index'] ?? '');
5201
5202 // Host: Remove protocol and validate format.
5203 $host = sanitize_text_field($input['mxchat_pinecone_host'] ?? '');
5204 $host = preg_replace('#^https?://#', '', $host);
5205 //error_log('Host after removing protocol: ' . $host);
5206 if (!empty($host)) {
5207 if (!preg_match('/^[\w-]+\.svc\.[\w-]+\.pinecone\.io$/', $host)) {
5208 add_settings_error(
5209 'mxchat_prompts_options',
5210 'invalid_host',
5211 __('The Pinecone host appears to be invalid. It should look like "mxchat-vectors-zrmsquq.svc.aped-4627-b74a.pinecone.io"', 'mxchat')
5212 );
5213 $existing_options = get_option('mxchat_prompts_options', array());
5214 $sanitized['mxchat_pinecone_host'] = $existing_options['mxchat_pinecone_host'] ?? '';
5215 } else {
5216 $sanitized['mxchat_pinecone_host'] = $host;
5217 }
5218 } else {
5219 $sanitized['mxchat_pinecone_host'] = '';
5220 }
5221
5222 //error_log('Final sanitized array: ' . print_r($sanitized, true));
5223
5224 return $sanitized;
5225 }
5226
5227 public function sync_settings_notice() {
5228 // Only show notice on our plugin page
5229 if (!isset($_GET['page']) || $_GET['page'] !== 'mxchat-prompts') {
5230 return;
5231 }
5232
5233 // Check if settings were updated
5234 if (isset($_GET['settings-updated'])) {
5235
5236 ?>
5237 <div class="notice notice-success is-dismissible">
5238 <p><?php esc_html_e('Sync settings updated successfully.', 'mxchat'); ?></p>
5239 <button type="button" class="notice-dismiss"><span class="screen-reader-text"><?php esc_html_e('Dismiss this notice.', 'mxchat'); ?></span></button>
5240 </div>
5241 <?php
5242
5243 }
5244 }
5245 // Add this sanitization function to your class
5246 public function sanitize_sync_setting($input) {
5247 return (bool)$input ? __('1', 'mxchat') : __('', 'mxchat');
5248 }
5249
5250 public function mxchat_rate_limits_callback() {
5251 $all_options = get_option('mxchat_options', []);
5252
5253 // Define available rate limits
5254 $rate_limits = array('1', '3', '5', '10', '15', '20', '50', '100', 'unlimited');
5255
5256 // Define available timeframes
5257 $timeframes = array(
5258 'hourly' => __('Per Hour', 'mxchat'),
5259 'daily' => __('Per Day', 'mxchat'),
5260 'weekly' => __('Per Week', 'mxchat'),
5261 'monthly' => __('Per Month', 'mxchat')
5262 );
5263
5264 // Get all roles plus a "logged_out" pseudo-role
5265 $roles = wp_roles()->get_names();
5266 $roles['logged_out'] = __('Logged Out Users', 'mxchat');
5267
5268 // Start the wrapper
5269 echo '<div class="pro-feature-wrapper active">';
5270 echo '<div class="mxchat-rate-limits-container">';
5271
5272 echo '<p class="description" style="margin-bottom: 20px;">' .
5273 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') .
5274 '</p>';
5275
5276 // Add markdown link documentation
5277 echo '<div class="notice notice-info inline" style="margin-bottom: 20px; padding: 10px;">';
5278 echo '<p><strong>' . esc_html__('Markdown Links Supported:', 'mxchat') . '</strong></p>';
5279 echo '<p>' . esc_html__('You can include clickable links in your custom messages using markdown syntax:', 'mxchat') . '</p>';
5280 echo '<ul style="margin-left: 20px;">';
5281 echo '<li><code>[Link text](https://example.com)</code> - Creates a clickable link</li>';
5282 echo '<li><code>[Visit our pricing](https://example.com/pricing)</code> - Link with custom text</li>';
5283 echo '<li><code>Plain URLs like https://example.com will also become clickable</code></li>';
5284 echo '</ul>';
5285 echo '</div>';
5286
5287 // Output the controls for each role
5288 foreach ($roles as $role_id => $role_name) {
5289 // Get saved options or defaults
5290 $default_limit = ($role_id === 'logged_out') ? '10' : '100';
5291 $default_timeframe = 'daily';
5292 $default_message = __('Rate limit exceeded. Please try again later.', 'mxchat');
5293
5294 $selected_limit = isset($all_options['rate_limits'][$role_id]['limit'])
5295 ? $all_options['rate_limits'][$role_id]['limit']
5296 : $default_limit;
5297
5298 $selected_timeframe = isset($all_options['rate_limits'][$role_id]['timeframe'])
5299 ? $all_options['rate_limits'][$role_id]['timeframe']
5300 : $default_timeframe;
5301
5302 $custom_message = isset($all_options['rate_limits'][$role_id]['message'])
5303 ? $all_options['rate_limits'][$role_id]['message']
5304 : $default_message;
5305
5306 // Output the row
5307 echo '<div class="mxchat-rate-limit-row mxchat-autosave-section">';
5308
5309 // Role label
5310 echo '<div class="mxchat-rate-limit-role">' . esc_html($role_name) . '</div>';
5311
5312 // Controls section
5313 echo '<div class="mxchat-rate-limit-controls-wrapper">';
5314
5315 // Rate limit and timeframe controls
5316 echo '<div class="mxchat-rate-limit-controls">';
5317
5318 // Limit dropdown
5319 echo '<div>';
5320 echo '<label for="rate_limits_' . esc_attr($role_id) . '_limit">' . esc_html__('Limit:', 'mxchat') . '</label>';
5321 echo '<select
5322 id="rate_limits_' . esc_attr($role_id) . '_limit"
5323 name="mxchat_options[rate_limits][' . esc_attr($role_id) . '][limit]"
5324 class="mxchat-autosave-field">';
5325 foreach ($rate_limits as $limit) {
5326 echo '<option value="' . esc_attr($limit) . '" ' . selected($selected_limit, $limit, false) . '>' . esc_html($limit) . '</option>';
5327 }
5328 echo '</select>';
5329 echo '</div>';
5330
5331 // Timeframe dropdown
5332 echo '<div>';
5333 echo '<label for="rate_limits_' . esc_attr($role_id) . '_timeframe">' . esc_html__('Timeframe:', 'mxchat') . '</label>';
5334 echo '<select
5335 id="rate_limits_' . esc_attr($role_id) . '_timeframe"
5336 name="mxchat_options[rate_limits][' . esc_attr($role_id) . '][timeframe]"
5337 class="mxchat-autosave-field">';
5338 foreach ($timeframes as $value => $label) {
5339 echo '<option value="' . esc_attr($value) . '" ' . selected($selected_timeframe, $value, false) . '>' . esc_html($label) . '</option>';
5340 }
5341 echo '</select>';
5342 echo '</div>';
5343
5344 echo '</div>'; // End controls
5345
5346 // Custom message textarea
5347 echo '<div class="mxchat-rate-limit-message">';
5348 echo '<label for="rate_limits_' . esc_attr($role_id) . '_message">' . esc_html__('Custom Message:', 'mxchat') . '</label>';
5349 echo '<textarea
5350 id="rate_limits_' . esc_attr($role_id) . '_message"
5351 name="mxchat_options[rate_limits][' . esc_attr($role_id) . '][message]"
5352 class="mxchat-autosave-field"
5353 placeholder="' . esc_attr__('Enter custom message when rate limit is exceeded', 'mxchat') . '">' .
5354 esc_textarea($custom_message) .
5355 '</textarea>';
5356 echo '<p class="description">' .
5357 esc_html__('Example: Rate limit reached! [Visit our pricing page](https://example.com/pricing) to upgrade.', 'mxchat') .
5358 '</p>';
5359 echo '</div>'; // End message
5360
5361 echo '</div>'; // End controls wrapper
5362
5363 echo '</div>'; // End row
5364 }
5365
5366 echo '</div>'; // End container
5367
5368 echo '</div>'; // End pro-feature-wrapper
5369 }
5370
5371 private function mxchat_add_option_field($id, $title, $callback = '') {
5372 add_settings_field(
5373 $id,
5374 __($title, 'mxchat'),
5375 $callback ? array($this, $callback) : array($this, $id . '_callback'),
5376 'mxchat-max',
5377 'mxchat_setting_section_id',
5378 $id === 'model' ? ['label_for' => 'model'] : []
5379 );
5380 }
5381
5382 // API Keys Section Callback
5383 public function mxchat_api_keys_section_callback() {
5384 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>';
5385 }
5386
5387 // OpenAI API Key
5388 public function api_key_callback() {
5389 $apiKey = isset($this->options['api_key']) ? esc_attr($this->options['api_key']) : '';
5390 $nonce = wp_create_nonce('mxchat_autosave_nonce');
5391
5392 echo '<div class="api-key-wrapper">';
5393 echo '<input type="text" id="api_key" name="api_key" value="' . $apiKey . '" class="regular-text mxchat-autosave-field mxchat-api-key-field" autocomplete="new-password" data-lpignore="true" data-form-type="other" data-nonce="' . $nonce . '" />';
5394 echo '<button type="button" id="toggleApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5395 echo '<p class="description">' . esc_html__('Required for OpenAI GPT models and OpenAI embeddings. Get your API key from OpenAI Platform.', 'mxchat') . '</p>';
5396 echo '</div>';
5397 }
5398
5399 // X.AI API Key
5400 public function xai_api_key_callback() {
5401 $xaiApiKey = isset($this->options['xai_api_key']) ? esc_attr($this->options['xai_api_key']) : '';
5402 $nonce = wp_create_nonce('mxchat_autosave_nonce');
5403
5404 echo '<div class="api-key-wrapper">';
5405 echo '<input type="text" id="xai_api_key" name="xai_api_key" value="' . $xaiApiKey . '" class="regular-text mxchat-autosave-field mxchat-api-key-field" autocomplete="new-password" data-lpignore="true" data-form-type="other" data-nonce="' . $nonce . '" />';
5406 echo '<button type="button" id="toggleXaiApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5407 echo '<p class="description">' . esc_html__('Required for X.AI Grok models. Get your API key from X.AI Console.', 'mxchat') . '</p>';
5408 echo '</div>';
5409 }
5410 // Claude API Key
5411 public function claude_api_key_callback() {
5412 $claudeApiKey = isset($this->options['claude_api_key']) ? esc_attr($this->options['claude_api_key']) : '';
5413 $nonce = wp_create_nonce('mxchat_autosave_nonce');
5414
5415 echo '<div class="api-key-wrapper">';
5416 echo '<input type="text" id="claude_api_key" name="claude_api_key" value="' . $claudeApiKey . '" class="regular-text mxchat-autosave-field mxchat-api-key-field" autocomplete="new-password" data-lpignore="true" data-form-type="other" data-nonce="' . $nonce . '" />';
5417 echo '<button type="button" id="toggleClaudeApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5418 echo '<p class="description">' . esc_html__('Required for Anthropic Claude models. Get your API key from Anthropic Console.', 'mxchat') . '</p>';
5419 echo '</div>';
5420 }
5421
5422 // DeepSeek API Key
5423 public function deepseek_api_key_callback() {
5424 $apiKey = isset($this->options['deepseek_api_key']) ? esc_attr($this->options['deepseek_api_key']) : '';
5425 $nonce = wp_create_nonce('mxchat_autosave_nonce');
5426
5427 echo '<div class="api-key-wrapper">';
5428 echo '<input type="text" id="deepseek_api_key" name="deepseek_api_key" value="' . $apiKey . '" class="regular-text mxchat-autosave-field mxchat-api-key-field" autocomplete="new-password" data-lpignore="true" data-form-type="other" data-nonce="' . $nonce . '" />';
5429 echo '<button type="button" id="toggleDeepSeekApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5430 echo '<p class="description">' . esc_html__('Required for DeepSeek models. Get your API key from DeepSeek Platform.', 'mxchat') . '</p>';
5431 echo '</div>';
5432 }
5433
5434 // Gemini API Key
5435 public function gemini_api_key_callback() {
5436 $geminiApiKey = isset($this->options['gemini_api_key']) ? esc_attr($this->options['gemini_api_key']) : '';
5437 $nonce = wp_create_nonce('mxchat_autosave_nonce');
5438
5439 echo '<div class="api-key-wrapper">';
5440 echo '<input type="text" id="gemini_api_key" name="gemini_api_key" value="' . $geminiApiKey . '" class="regular-text mxchat-autosave-field mxchat-api-key-field" autocomplete="new-password" data-lpignore="true" data-form-type="other" data-nonce="' . $nonce . '" />';
5441 echo '<button type="button" id="toggleGeminiApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5442 echo '<p class="description">' . esc_html__('Required for Google Gemini models and embeddings. Get your API key from Google AI Studio.', 'mxchat') . '</p>';
5443 echo '</div>';
5444 }
5445
5446
5447 // OpenRouter API Key
5448 public function openrouter_api_key_callback() {
5449 $openrouterApiKey = isset($this->options['openrouter_api_key']) ? esc_attr($this->options['openrouter_api_key']) : '';
5450 $nonce = wp_create_nonce('mxchat_autosave_nonce');
5451
5452 echo '<div class="api-key-wrapper">';
5453 echo '<input type="text" id="openrouter_api_key" name="openrouter_api_key" value="' . $openrouterApiKey . '" class="regular-text mxchat-autosave-field mxchat-api-key-field" autocomplete="new-password" data-lpignore="true" data-form-type="other" data-nonce="' . $nonce . '" />';
5454 echo '<button type="button" id="toggleOpenRouterApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5455 echo '<p class="description">' . esc_html__('Required for OpenRouter models. Get your API key from OpenRouter.ai', 'mxchat') . '</p>';
5456 echo '</div>';
5457 }
5458
5459 // Voyage API Key
5460 public function voyage_api_key_callback() {
5461 $apiKey = isset($this->options['voyage_api_key']) ? esc_attr($this->options['voyage_api_key']) : '';
5462 $nonce = wp_create_nonce('mxchat_autosave_nonce');
5463
5464 echo '<div class="api-key-wrapper">';
5465 echo '<input type="text" id="voyage_api_key" name="voyage_api_key" value="' . $apiKey . '" class="regular-text mxchat-autosave-field mxchat-api-key-field" autocomplete="new-password" data-lpignore="true" data-form-type="other" data-nonce="' . $nonce . '" />';
5466 echo '<button type="button" id="toggleVoyageAPIKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5467 echo '<p class="description">' . esc_html__('Required for Voyage AI embedding models. Get your API key from Voyage AI.', 'mxchat') . '</p>';
5468 echo '</div>';
5469 }
5470
5471 public function mxchat_loops_api_key_callback() {
5472 $loops_api_key = isset($this->options['loops_api_key']) ? esc_attr($this->options['loops_api_key']) : '';
5473 $nonce = wp_create_nonce('mxchat_autosave_nonce');
5474
5475 echo '<div class="api-key-wrapper">';
5476 echo sprintf(
5477 '<input type="text" id="loops_api_key" name="loops_api_key" value="%s" class="regular-text mxchat-autosave-field mxchat-api-key-field" autocomplete="new-password" data-lpignore="true" data-form-type="other" data-nonce="%s" />',
5478 $loops_api_key,
5479 $nonce
5480 );
5481 echo '<button type="button" id="toggleLoopsApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5482 echo '</div>';
5483 echo '<p class="description">' . esc_html__('Required for Loops email integration. Get your API key from Loops.so', 'mxchat') . '</p>';
5484 }
5485 public function mxchat_loops_mailing_list_callback() {
5486 // Add error handling and type checking
5487 $loops_api_key = '';
5488 $selected_list = '';
5489 $nonce = wp_create_nonce('mxchat_autosave_nonce');
5490
5491 // Safely get the API key
5492 if (isset($this->options['loops_api_key']) && is_string($this->options['loops_api_key'])) {
5493 $loops_api_key = $this->options['loops_api_key'];
5494 }
5495
5496 // Safely get the selected list
5497 if (isset($this->options['loops_mailing_list']) && is_string($this->options['loops_mailing_list'])) {
5498 $selected_list = $this->options['loops_mailing_list'];
5499 }
5500
5501 if (!empty($loops_api_key)) {
5502 $lists = $this->mxchat_fetch_loops_mailing_lists($loops_api_key);
5503 if (is_array($lists) && !empty($lists)) {
5504 echo '<div class="mxchat-field-wrapper">';
5505 echo '<select id="loops_mailing_list" name="loops_mailing_list" class="mxchat-autosave-field" data-nonce="' . $nonce . '">';
5506
5507 // Add a default "Select a list" option
5508 echo '<option value="" ' . selected($selected_list, '', false) . '>' . esc_html__('Select a list', 'mxchat') . '</option>';
5509
5510 foreach ($lists as $list) {
5511 if (is_array($list) && isset($list['id']) && isset($list['name'])) {
5512 echo sprintf(
5513 '<option value="%s" %s>%s</option>',
5514 esc_attr($list['id']),
5515 selected($selected_list, $list['id'], false),
5516 esc_html($list['name'])
5517 );
5518 }
5519 }
5520 echo '</select>';
5521 echo '</div>';
5522 echo '<p class="description">' . esc_html__('Please select a mailing list to use with Loops.', 'mxchat') . '</p>';
5523 } else {
5524 echo '<p class="description">' . esc_html__('No lists found. Please verify your API Key.', 'mxchat') . '</p>';
5525 }
5526 } else {
5527 echo '<p class="description">' . esc_html__('Enter a valid Loops API Key to load mailing lists.', 'mxchat') . '</p>';
5528 }
5529 }
5530 public function mxchat_triggered_phrase_response_callback() {
5531 $default_response = __('Would you like to join our mailing list? Please provide your email below.', 'mxchat');
5532 $triggered_response = isset($this->options['triggered_phrase_response'])
5533 ? $this->options['triggered_phrase_response']
5534 : $default_response;
5535 $nonce = wp_create_nonce('mxchat_autosave_nonce');
5536
5537 echo '<div class="mxchat-field-wrapper">';
5538 echo sprintf(
5539 '<textarea id="triggered_phrase_response" name="triggered_phrase_response" rows="3" cols="50" class="mxchat-autosave-field" data-nonce="%s">%s</textarea>',
5540 $nonce,
5541 esc_textarea($triggered_response)
5542 );
5543 echo '</div>';
5544 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>';
5545 }
5546
5547 public function mxchat_email_capture_response_callback() {
5548 $default_response = __('Thank you for providing your email! You\'ve been added to our list.', 'mxchat');
5549 $email_capture_response = isset($this->options['email_capture_response'])
5550 ? $this->options['email_capture_response']
5551 : $default_response;
5552 $nonce = wp_create_nonce('mxchat_autosave_nonce');
5553
5554 echo '<div class="mxchat-field-wrapper">';
5555 echo sprintf(
5556 '<textarea id="email_capture_response" name="email_capture_response" rows="3" cols="50" class="mxchat-autosave-field" data-nonce="%s">%s</textarea>',
5557 $nonce,
5558 esc_textarea($email_capture_response)
5559 );
5560 echo '</div>';
5561 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>';
5562 }
5563 public function mxchat_pre_chat_message_callback() {
5564 // Load the entire 'mxchat_options' array
5565 $all_options = get_option('mxchat_options', []);
5566
5567 // Retrieve the saved message or use the default value
5568 $default_message = __('Hey there! Ask me anything!', 'mxchat');
5569 $pre_chat_message = isset($all_options['pre_chat_message']) ? $all_options['pre_chat_message'] : $default_message;
5570
5571 // Output the textarea
5572 printf(
5573 '<textarea id="pre_chat_message" name="pre_chat_message" rows="5" cols="50">%s</textarea>',
5574 esc_textarea($pre_chat_message)
5575 );
5576 }
5577
5578 // Callback for AI Instructions textarea
5579 public function system_prompt_instructions_callback() {
5580 // Retrieve the current value of the system prompt instructions
5581 $instructions = isset($this->options['system_prompt_instructions']) ? esc_textarea($this->options['system_prompt_instructions']) : '';
5582 // Render the textarea field
5583 printf(
5584 '<textarea id="system_prompt_instructions" name="system_prompt_instructions" rows="5" cols="50">%s</textarea>',
5585 $instructions
5586 );
5587 // Personalization hint
5588 echo '<p class="description" style="margin-top: 8px;">';
5589 echo esc_html__('Use {visitor_name} to personalize AI responses when lead capture is enabled.', 'mxchat') . '<br>';
5590 echo '<code style="font-size: 12px;">' . esc_html__('Example: The visitor\'s name is {visitor_name}. Address them by name.', 'mxchat') . '</code>';
5591 echo '</p>';
5592 // Sample instructions button
5593 echo '<div class="mxchat-instructions-container">';
5594 echo '<button type="button" class="mxchat-instructions-btn" id="mxchatViewSampleBtn">';
5595 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">';
5596 echo '<path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/>';
5597 echo '<path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/>';
5598 echo '</svg>';
5599 echo esc_html__('View Sample Instructions', 'mxchat');
5600 echo '</button>';
5601 echo '</div>';
5602
5603 // Add modal to WordPress admin footer instead of inline
5604 add_action('admin_footer', array($this, 'render_sample_instructions_modal'));
5605 }
5606
5607 // New method to render modal in admin footer
5608 public function render_sample_instructions_modal() {
5609 static $modal_rendered = false;
5610 if ($modal_rendered) return; // Prevent duplicate modals
5611 $modal_rendered = true;
5612
5613 echo '<div class="mxchat-instructions-modal-overlay" id="mxchatSampleModal">';
5614 echo '<div class="mxchat-instructions-modal-content">';
5615 echo '<div class="mxchat-instructions-modal-header">';
5616 echo '<h3 class="mxchat-instructions-modal-title">';
5617 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">';
5618 echo '<path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/>';
5619 echo '<path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/>';
5620 echo '</svg>';
5621 echo esc_html__('Sample AI Instructions', 'mxchat');
5622 echo '</h3>';
5623 echo '<button type="button" class="mxchat-instructions-modal-close" id="mxchatModalClose">';
5624 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">';
5625 echo '<line x1="18" y1="6" x2="6" y2="18"/>';
5626 echo '<line x1="6" y1="6" x2="18" y2="18"/>';
5627 echo '</svg>';
5628 echo '</button>';
5629 echo '</div>';
5630 echo '<div class="mxchat-instructions-modal-body">';
5631 echo '<div class="mxchat-instructions-content">';
5632 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:
5633
5634 # Response Style - CRITICALLY IMPORTANT
5635 - MAXIMUM LENGTH: 1-3 short sentences per response
5636 - Ultra-concise: Get straight to the answer with no filler
5637 - No introductions like "Sure!" or "I\'d be happy to help"
5638 - No phrases like "based on my knowledge" or "according to information"
5639 - No explanatory text before giving the answer
5640 - No summaries or repetition
5641 - Hyperlink all URLs
5642 - Respond in user\'s language
5643 - Minor chit chat or conversation is okay, but try to keep it focused on [insert topic]
5644
5645 # Knowledge Base Requirements - PREVENT HALLUCINATIONS
5646 - ONLY answer using information explicitly provided in OFFICIAL KNOWLEDGE DATABASE CONTENT sections marked with ===== delimiters
5647 - If required information is NOT in the knowledge database: "I don\'t have enough information in my knowledge base to answer that question accurately."
5648 - NEVER invent or hallucinate URLs, links, product specs, procedures, dates, statistics, names, contacts, or company information
5649 - When knowledge base information is unclear or contradictory, acknowledge the limitation rather than guessing
5650 - Better to admit insufficient information than provide inaccurate answers');
5651 echo '</div>';
5652 echo '<button type="button" class="mxchat-instructions-copy-btn" id="mxchatCopyBtn">';
5653 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">';
5654 echo '<rect x="9" y="9" width="13" height="13" rx="2" ry="2"/>';
5655 echo '<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/>';
5656 echo '</svg>';
5657 echo esc_html__('Copy Instructions', 'mxchat');
5658 echo '</button>';
5659 echo '</div>';
5660 echo '<div class="mxchat-instructions-modal-footer">';
5661 echo '<button type="button" class="mxchat-instructions-btn-secondary" id="mxchatCloseBtn">' . esc_html__('Close', 'mxchat') . '</button>';
5662 echo '</div>';
5663 echo '</div>';
5664 echo '</div>';
5665 }
5666
5667
5668 public function mxchat_model_callback() {
5669 // Define available models grouped by provider
5670 $models = array(
5671 esc_html__('OpenRouter (100+ Models)', 'mxchat') => array(
5672 'openrouter' => esc_html__('OpenRouter - Select after entering API key', 'mxchat'),
5673 ),
5674 esc_html__('Google Gemini Models', 'mxchat') => array(
5675 'gemini-3-pro-preview' => esc_html__('Gemini 3 Pro (Most Intelligent, Multimodal)', 'mxchat'),
5676 'gemini-3-flash-preview' => esc_html__('Gemini 3 Flash (Balanced Speed & Scale)', 'mxchat'),
5677 'gemini-2.5-pro' => esc_html__('Gemini 2.5 Pro (Advanced Thinking)', 'mxchat'),
5678 'gemini-2.5-flash' => esc_html__('Gemini 2.5 Flash (Best Price-Performance)', 'mxchat'),
5679 'gemini-2.5-flash-lite' => esc_html__('Gemini 2.5 Flash-Lite (Ultra Fast)', 'mxchat'),
5680 'gemini-2.0-flash' => esc_html__('Gemini 2.0 Flash (Deprecated Mar 2026)', 'mxchat'),
5681 'gemini-2.0-flash-lite' => esc_html__('Gemini 2.0 Flash-Lite (Deprecated Mar 2026)', 'mxchat'),
5682 'gemini-1.5-pro' => esc_html__('Gemini 1.5 Pro (Deprecated Sep 2025)', 'mxchat'),
5683 'gemini-1.5-flash' => esc_html__('Gemini 1.5 Flash (Deprecated Sep 2025)', 'mxchat'),
5684 ),
5685 esc_html__('X.AI Models', 'mxchat') => array(
5686 'grok-3-beta' => esc_html__('Grok-3 (Powerful)', 'mxchat'),
5687 'grok-3-fast-beta' => esc_html__('Grok-3 Fast (High Performance)', 'mxchat'),
5688 'grok-3-mini-beta' => esc_html__('Grok-3 Mini (Affordable)', 'mxchat'),
5689 'grok-3-mini-fast-beta' => esc_html__('Grok-3 Mini Fast (Quick Response)', 'mxchat'),
5690 'grok-2' => esc_html__('Grok 2', 'mxchat'),
5691 'grok-4-0709' => esc_html__('Grok 4 (Latest Flagship)', 'mxchat'),
5692 'grok-4-1-fast-reasoning' => esc_html__('Grok 4.1 Fast (Reasoning)', 'mxchat'),
5693 'grok-4-1-fast-non-reasoning' => esc_html__('Grok 4.1 Fast (Non-Reasoning)', 'mxchat'),
5694 ),
5695 esc_html__('DeepSeek Models', 'mxchat') => array(
5696 'deepseek-chat' => esc_html__('DeepSeek-V3', 'mxchat'),
5697 ),
5698 esc_html__('Claude Models', 'mxchat') => array(
5699 'claude-opus-4-6' => esc_html__('Claude Opus 4.6 (Most Capable - Recommended)', 'mxchat'),
5700 'claude-sonnet-4-6' => esc_html__('Claude Sonnet 4.6 (Latest Sonnet - Fast & Capable)', '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="text" id="brave_api_key" name="brave_api_key" value="%s" class="regular-text mxchat-autosave-field mxchat-api-key-field" autocomplete="new-password" data-lpignore="true" data-form-type="other" 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-sonnet-4-6',
7780 'claude-opus-4-5',
7781 'claude-sonnet-4-5-20250929',
7782 'claude-opus-4-1-20250805',
7783 'claude-haiku-4-5-20251001',
7784 'claude-opus-4-20250514',
7785 'claude-sonnet-4-20250514',
7786 'gpt-5.2',
7787 'gpt-5.1-chat-latest',
7788 'gpt-5.1-2025-11-13',
7789 'gpt-5',
7790 'gpt-5-mini',
7791 'gpt-5-nano',
7792 );
7793
7794 if (in_array($input['model'], $allowed_models)) {
7795 $new_input['model'] = sanitize_text_field($input['model']);
7796 } else {
7797 // Fallback for any deprecated model
7798 $new_input['model'] = 'gpt-5.1-chat-latest';
7799 }
7800 }
7801 }
7802
7803 if (isset($input['openrouter_selected_model'])) {
7804 // Just sanitize it, don't validate against a whitelist
7805 $new_input['openrouter_selected_model'] = sanitize_text_field($input['openrouter_selected_model']);
7806 }
7807
7808 // ADD THIS:
7809 if (isset($input['openrouter_selected_model_name'])) {
7810 $new_input['openrouter_selected_model_name'] = sanitize_text_field($input['openrouter_selected_model_name']);
7811 }
7812
7813 if (isset($input['openrouter_api_key'])) {
7814 $new_input['openrouter_api_key'] = sanitize_text_field($input['openrouter_api_key']);
7815 }
7816
7817
7818 if (isset($input['close_button_color'])) {
7819 $new_input['close_button_color'] = sanitize_hex_color($input['close_button_color']);
7820 }
7821
7822 if (isset($input['chatbot_bg_color'])) {
7823 $new_input['chatbot_bg_color'] = sanitize_hex_color($input['chatbot_bg_color']);
7824 }
7825
7826 if (isset($input['woocommerce_consumer_key'])) {
7827 $new_input['woocommerce_consumer_key'] = sanitize_text_field($input['woocommerce_consumer_key']);
7828 }
7829
7830 if (isset($input['woocommerce_consumer_secret'])) {
7831 $new_input['woocommerce_consumer_secret'] = sanitize_text_field($input['woocommerce_consumer_secret']);
7832 }
7833
7834 if (isset($input['user_message_bg_color'])) {
7835 $new_input['user_message_bg_color'] = sanitize_hex_color($input['user_message_bg_color']);
7836 }
7837
7838 if (isset($input['user_message_font_color'])) {
7839 $new_input['user_message_font_color'] = sanitize_hex_color($input['user_message_font_color']);
7840 }
7841
7842 if (isset($input['bot_message_bg_color'])) {
7843 $new_input['bot_message_bg_color'] = sanitize_hex_color($input['bot_message_bg_color']);
7844 }
7845
7846 if (isset($input['bot_message_font_color'])) {
7847 $new_input['bot_message_font_color'] = sanitize_hex_color($input['bot_message_font_color']);
7848 }
7849
7850 if (isset($input['live_agent_message_bg_color'])) {
7851 $new_input['live_agent_message_bg_color'] = sanitize_hex_color($input['live_agent_message_bg_color']);
7852 }
7853
7854 if (isset($input['live_agent_message_font_color'])) {
7855 $new_input['live_agent_message_font_color'] = sanitize_hex_color($input['live_agent_message_font_color']);
7856 }
7857
7858 if (isset($input['mode_indicator_bg_color'])) {
7859 $new_input['mode_indicator_bg_color'] = sanitize_hex_color($input['mode_indicator_bg_color']);
7860 }
7861
7862 if (isset($input['mode_indicator_font_color'])) {
7863 $new_input['mode_indicator_font_color'] = sanitize_hex_color($input['mode_indicator_font_color']);
7864 }
7865
7866 if (isset($input['toolbar_icon_color'])) {
7867 $new_input['toolbar_icon_color'] = sanitize_hex_color($input['toolbar_icon_color']);
7868 }
7869
7870 if (isset($input['top_bar_bg_color'])) {
7871 $new_input['top_bar_bg_color'] = sanitize_hex_color($input['top_bar_bg_color']);
7872 }
7873
7874 if (isset($input['quick_questions_toggle_color'])) {
7875 $new_input['quick_questions_toggle_color'] = sanitize_hex_color($input['quick_questions_toggle_color']);
7876 }
7877
7878 if (isset($input['send_button_font_color'])) {
7879 $new_input['send_button_font_color'] = sanitize_hex_color($input['send_button_font_color']);
7880 }
7881
7882 if (isset($input['chatbot_background_color'])) {
7883 $new_input['chatbot_background_color'] = sanitize_hex_color($input['chatbot_background_color']);
7884 }
7885
7886 if (isset($input['icon_color'])) {
7887 $new_input['icon_color'] = sanitize_hex_color($input['icon_color']);
7888 }
7889
7890 if (isset($input['custom_icon'])) {
7891 $new_input['custom_icon'] = esc_url_raw($input['custom_icon']);
7892 }
7893
7894 if (isset($input['title_icon'])) {
7895 $new_input['title_icon'] = esc_url_raw($input['title_icon']);
7896 }
7897
7898 if (isset($input['chat_input_font_color'])) {
7899 $new_input['chat_input_font_color'] = sanitize_hex_color($input['chat_input_font_color']);
7900 }
7901
7902 // Sanitize link_target_toggle
7903 if (isset($input['link_target_toggle'])) {
7904 $new_input['link_target_toggle'] = $input['link_target_toggle'] === 'on' ? 'on' : 'off';
7905 }
7906
7907 // Sanitize Loops API Key
7908 if (isset($input['loops_api_key'])) {
7909 $new_input['loops_api_key'] = sanitize_text_field($input['loops_api_key']);
7910 }
7911
7912 if (isset($input['chat_persistence_toggle'])) {
7913 $new_input['chat_persistence_toggle'] = $input['chat_persistence_toggle'] === 'on' ? 'on' : 'off';
7914 }
7915
7916 if (isset($input['popular_question_1'])) {
7917 $new_input['popular_question_1'] = sanitize_text_field($input['popular_question_1']);
7918 }
7919
7920 if (isset($input['popular_question_2'])) {
7921 $new_input['popular_question_2'] = sanitize_text_field($input['popular_question_2']);
7922 }
7923
7924 if (isset($input['popular_question_3'])) {
7925 $new_input['popular_question_3'] = sanitize_text_field($input['popular_question_3']);
7926 }
7927
7928 if (isset($input['additional_popular_questions']) && is_array($input['additional_popular_questions'])) {
7929 $new_input['additional_popular_questions'] = array_map('sanitize_text_field', $input['additional_popular_questions']);
7930 }
7931
7932 // Sanitize Loops Mailing List
7933 if (isset($input['loops_mailing_list'])) {
7934 $new_input['loops_mailing_list'] = sanitize_text_field($input['loops_mailing_list']);
7935 }
7936
7937 // Sanitize Triggered Phrase Response
7938 if (isset($input['triggered_phrase_response'])) {
7939 $new_input['triggered_phrase_response'] = wp_kses_post($input['triggered_phrase_response']);
7940 }
7941 if (isset($input['email_capture_response'])) {
7942 $new_input['email_capture_response'] = wp_kses_post($input['email_capture_response']);
7943 }
7944
7945 // Sanitize Brave Search Settings
7946 if (isset($input['brave_api_key'])) {
7947 $new_input['brave_api_key'] = sanitize_text_field($input['brave_api_key']);
7948 }
7949
7950 if (isset($input['brave_image_count'])) {
7951 $image_count = intval($input['brave_image_count']);
7952 $new_input['brave_image_count'] = ($image_count >=1 && $image_count <=6) ? $image_count : 4;
7953 }
7954
7955 if (isset($input['brave_safe_search'])) {
7956 $allowed = array('strict', 'off');
7957 $new_input['brave_safe_search'] = in_array($input['brave_safe_search'], $allowed, true) ? $input['brave_safe_search'] : 'strict';
7958 }
7959
7960 if (isset($input['brave_news_count'])) {
7961 $news_count = intval($input['brave_news_count']);
7962 $new_input['brave_news_count'] = ($news_count >=1 && $news_count <=10) ? $news_count : 3;
7963 }
7964
7965 if (isset($input['brave_country'])) {
7966 $new_input['brave_country'] = sanitize_text_field($input['brave_country']);
7967 }
7968
7969 if (isset($input['brave_language'])) {
7970 $new_input['brave_language'] = sanitize_text_field($input['brave_language']);
7971 }
7972
7973 if (isset($input['chat_toolbar_toggle'])) {
7974 $new_input['chat_toolbar_toggle'] = $input['chat_toolbar_toggle'] === 'on' ? 'on' : 'off';
7975 }
7976
7977 // Sanitize PDF upload button toggle
7978 if (isset($input['show_pdf_upload_button'])) {
7979 $new_input['show_pdf_upload_button'] = $input['show_pdf_upload_button'] === 'on' ? 'on' : 'off';
7980 } else {
7981 $new_input['show_pdf_upload_button'] = 'off'; // If checkbox is unchecked
7982 }
7983
7984 // Sanitize Word upload button toggle
7985 if (isset($input['show_word_upload_button'])) {
7986 $new_input['show_word_upload_button'] = $input['show_word_upload_button'] === 'on' ? 'on' : 'off';
7987 } else {
7988 $new_input['show_word_upload_button'] = 'off'; // If checkbox is unchecked
7989 }
7990
7991 if (isset($input['pdf_intent_trigger_text'])) {
7992 $new_input['pdf_intent_trigger_text'] = sanitize_text_field($input['pdf_intent_trigger_text']);
7993 }
7994
7995 if (isset($input['pdf_intent_success_text'])) {
7996 $new_input['pdf_intent_success_text'] = sanitize_text_field($input['pdf_intent_success_text']);
7997 }
7998
7999 if (isset($input['pdf_intent_error_text'])) {
8000 $new_input['pdf_intent_error_text'] = sanitize_text_field($input['pdf_intent_error_text']);
8001 }
8002
8003 if (isset($input['pdf_max_pages'])) {
8004 $new_input['pdf_max_pages'] = intval($input['pdf_max_pages']);
8005 if ($new_input['pdf_max_pages'] < 1 || $new_input['pdf_max_pages'] > 69) {
8006 $new_input['pdf_max_pages'] = 69; // Default to 69 if out of range
8007 }
8008 }
8009
8010 if (isset($input['live_agent_webhook_url'])) {
8011 $new_input['live_agent_webhook_url'] = esc_url_raw($input['live_agent_webhook_url']);
8012 }
8013 if (isset($input['live_agent_secret_key'])) {
8014 $new_input['live_agent_secret_key'] = sanitize_text_field($input['live_agent_secret_key']);
8015 }
8016
8017 // Live Agent Integration
8018 if (isset($input['live_agent_bot_token'])) {
8019 $new_input['live_agent_bot_token'] = sanitize_text_field($input['live_agent_bot_token']);
8020 }
8021
8022 if (isset($input['live_agent_user_ids'])) {
8023 $new_input['live_agent_user_ids'] = sanitize_textarea_field($input['live_agent_user_ids']);
8024 }
8025
8026 if (isset($input['live_agent_status'])) {
8027 $new_input['live_agent_status'] = ($input['live_agent_status'] === 'on') ? 'on' : 'off';
8028 }
8029 if (isset($input['live_agent_away_message'])) {
8030 $new_input['live_agent_away_message'] = sanitize_textarea_field($input['live_agent_away_message']);
8031 }
8032 if (isset($input['live_agent_notification_message'])) {
8033 $new_input['live_agent_notification_message'] = sanitize_textarea_field($input['live_agent_notification_message']);
8034 }
8035
8036 // Telegram Integration
8037 if (isset($input['telegram_status'])) {
8038 $new_input['telegram_status'] = ($input['telegram_status'] === 'on') ? 'on' : 'off';
8039 }
8040 if (isset($input['telegram_bot_token'])) {
8041 $new_input['telegram_bot_token'] = sanitize_text_field($input['telegram_bot_token']);
8042 }
8043 if (isset($input['telegram_group_id'])) {
8044 $new_input['telegram_group_id'] = sanitize_text_field($input['telegram_group_id']);
8045 }
8046 if (isset($input['telegram_webhook_secret'])) {
8047 $new_input['telegram_webhook_secret'] = sanitize_text_field($input['telegram_webhook_secret']);
8048 }
8049 if (isset($input['telegram_notification_message'])) {
8050 $new_input['telegram_notification_message'] = sanitize_textarea_field($input['telegram_notification_message']);
8051 }
8052 if (isset($input['telegram_away_message'])) {
8053 $new_input['telegram_away_message'] = sanitize_textarea_field($input['telegram_away_message']);
8054 }
8055
8056 // Sanitize script loading strategy
8057 if (isset($input['script_loading_strategy'])) {
8058 $allowed_strategies = array('default', 'defer', 'delay_1s', 'delay_3s', 'delay_5s', 'on_interaction');
8059 $new_input['script_loading_strategy'] = in_array($input['script_loading_strategy'], $allowed_strategies)
8060 ? $input['script_loading_strategy']
8061 : 'default';
8062 }
8063
8064 // Sanitize debug mode
8065 if (isset($input['debug_mode'])) {
8066 $new_input['debug_mode'] = ($input['debug_mode'] === 'on') ? 'on' : 'off';
8067 }
8068
8069 return $new_input;
8070 }
8071
8072 /**
8073 * Log a debug message if debug mode is enabled
8074 *
8075 * @param string $type The type of log entry (settings_save, api_error, activation, etc.)
8076 * @param string $message The log message
8077 * @param array $data Optional additional data to log
8078 * @return bool Whether the message was logged
8079 */
8080 public static function mxchat_log_debug( $type, $message, $data = array() ) {
8081 $options = get_option( 'mxchat_options', array() );
8082
8083 // Check if debug mode is enabled
8084 if ( ! isset( $options['debug_mode'] ) || $options['debug_mode'] !== 'on' ) {
8085 return false;
8086 }
8087
8088 // Get current log
8089 $log = get_option( 'mxchat_debug_log', array() );
8090 if ( ! is_array( $log ) ) {
8091 $log = array();
8092 }
8093
8094 // Add new entry
8095 $entry = array(
8096 'time' => current_time( 'Y-m-d H:i:s' ),
8097 'type' => sanitize_key( $type ),
8098 'message' => sanitize_text_field( $message ),
8099 );
8100
8101 if ( ! empty( $data ) ) {
8102 $entry['data'] = $data;
8103 }
8104
8105 // Add to beginning of array (newest first)
8106 array_unshift( $log, $entry );
8107
8108 // Keep only last 100 entries
8109 if ( count( $log ) > 100 ) {
8110 $log = array_slice( $log, 0, 100 );
8111 }
8112
8113 // Save log
8114 update_option( 'mxchat_debug_log', $log, false );
8115
8116 return true;
8117 }
8118
8119 /**
8120 * Get the debug log entries
8121 *
8122 * @return array The debug log entries
8123 */
8124 public static function mxchat_get_debug_log() {
8125 $log = get_option( 'mxchat_debug_log', array() );
8126 return is_array( $log ) ? $log : array();
8127 }
8128
8129 /**
8130 * Clear the debug log
8131 *
8132 * @return bool Whether the log was cleared
8133 */
8134 public static function mxchat_clear_debug_log() {
8135 return delete_option( 'mxchat_debug_log' );
8136 }
8137
8138 /**
8139 * Export settings as JSON with masked API keys
8140 *
8141 * @return array The sanitized settings array
8142 */
8143 public static function mxchat_export_settings() {
8144 $options = get_option( 'mxchat_options', array() );
8145
8146 if ( ! is_array( $options ) ) {
8147 return array();
8148 }
8149
8150 // List of API key fields to mask
8151 $api_key_fields = array(
8152 'api_key',
8153 'xai_api_key',
8154 'claude_api_key',
8155 'deepseek_api_key',
8156 'voyage_api_key',
8157 'gemini_api_key',
8158 'openrouter_api_key',
8159 'loops_api_key',
8160 'brave_api_key',
8161 'live_agent_secret_key',
8162 'live_agent_bot_token',
8163 'telegram_bot_token',
8164 'telegram_webhook_secret',
8165 'woocommerce_consumer_key',
8166 'woocommerce_consumer_secret',
8167 );
8168
8169 // Mask API keys (show only last 4 characters)
8170 foreach ( $api_key_fields as $field ) {
8171 if ( isset( $options[ $field ] ) && ! empty( $options[ $field ] ) ) {
8172 $value = $options[ $field ];
8173 if ( strlen( $value ) > 4 ) {
8174 $options[ $field ] = str_repeat( '*', strlen( $value ) - 4 ) . substr( $value, -4 );
8175 } else {
8176 $options[ $field ] = '****';
8177 }
8178 }
8179 }
8180
8181 // Add metadata
8182 $export = array(
8183 'plugin_version' => defined( 'MXCHAT_VERSION' ) ? MXCHAT_VERSION : 'unknown',
8184 'export_date' => current_time( 'Y-m-d H:i:s' ),
8185 'wordpress_version' => get_bloginfo( 'version' ),
8186 'php_version' => phpversion(),
8187 'settings' => $options,
8188 );
8189
8190 return $export;
8191 }
8192
8193 /**
8194 * Reset all settings to defaults
8195 *
8196 * @return bool Whether the reset was successful
8197 */
8198 public static function mxchat_reset_all_settings() {
8199 // Delete main options
8200 $deleted = delete_option( 'mxchat_options' );
8201
8202 // Also clear the debug log
8203 delete_option( 'mxchat_debug_log' );
8204
8205 // Log the reset (will create new log since we just cleared it)
8206 // We need to temporarily enable debug mode to log this
8207 $temp_options = array( 'debug_mode' => 'on' );
8208 update_option( 'mxchat_options', $temp_options );
8209
8210 self::mxchat_log_debug( 'reset', 'All settings have been reset to defaults' );
8211
8212 // Now delete again to trigger re-initialization
8213 delete_option( 'mxchat_options' );
8214
8215 return $deleted;
8216 }
8217
8218 // Method to append the chatbot to the body
8219 public function mxchat_append_chatbot_to_body() {
8220 $options = get_option('mxchat_options');
8221 if (isset($options['append_to_body']) && $options['append_to_body'] === 'on') {
8222 echo do_shortcode('[mxchat_chatbot floating="yes"]');
8223 }
8224 }
8225
8226
8227
8228
8229
8230 private function mxchat_fetch_loops_mailing_lists($api_key) {
8231 $url = 'https://app.loops.so/api/v1/lists';
8232 $response = wp_remote_get($url, array(
8233 'headers' => array(
8234 'Authorization' => 'Bearer ' . $api_key,
8235 'Content-Type' => 'application/json'
8236 )
8237 ));
8238
8239 if (is_wp_error($response)) {
8240 return array();
8241 }
8242
8243 $body = wp_remote_retrieve_body($response);
8244 $lists = json_decode($body, true);
8245
8246 return isset($lists) && is_array($lists) ? $lists : array();
8247 }
8248
8249 function mxchat_calculate_cosine_similarity($vec1, $vec2) {
8250 if (empty($vec1) || empty($vec2)) {
8251 return 0.0;
8252 }
8253
8254 $dot_product = 0.0;
8255 $norm_a = 0.0;
8256 $norm_b = 0.0;
8257
8258 for ($i = 0; $i < count($vec1); $i++) {
8259 $dot_product += $vec1[$i] * $vec2[$i];
8260 $norm_a += pow($vec1[$i], 2);
8261 $norm_b += pow($vec2[$i], 2);
8262 }
8263
8264 if ($norm_a == 0.0 || $norm_b == 0.0) {
8265 return 0.0;
8266 } else {
8267 return $dot_product / (sqrt($norm_a) * sqrt($norm_b));
8268 }
8269 }
8270
8271 /**
8272 * Validates nonce and deletes all chat prompts
8273 */
8274 public function mxchat_handle_delete_all_prompts() {
8275 //error_log('=== DELETE ALL DEBUG START ===');
8276
8277 // Verify nonce
8278 if (!isset($_POST['mxchat_delete_all_prompts_nonce']) || !wp_verify_nonce($_POST['mxchat_delete_all_prompts_nonce'], 'mxchat_delete_all_prompts_action')) {
8279 //error_log('DEBUG: Nonce verification failed');
8280 wp_die(__('Nonce verification failed.', 'mxchat'));
8281 }
8282
8283 // Check permissions
8284 if (!current_user_can('manage_options')) {
8285 //error_log('DEBUG: Permission check failed');
8286 wp_die(__('You do not have sufficient permissions to delete all prompts.', 'mxchat'));
8287 }
8288
8289 // Get bot_id from POST data
8290 $bot_id = isset($_POST['bot_id']) ? sanitize_text_field($_POST['bot_id']) : 'default';
8291 //error_log('DEBUG: Bot ID from POST: ' . $bot_id);
8292
8293 $success = true;
8294 $error_messages = array();
8295
8296 // Get bot-specific Pinecone configuration
8297 $pinecone_manager = MxChat_Pinecone_Manager::get_instance();
8298 $pinecone_options = $pinecone_manager->mxchat_get_bot_pinecone_options($bot_id);
8299
8300 //error_log('DEBUG: Pinecone options retrieved:');
8301 //error_log(' - Use Pinecone: ' . ($pinecone_options['mxchat_use_pinecone'] ?? 'NOT SET'));
8302 //error_log(' - API Key: ' . (empty($pinecone_options['mxchat_pinecone_api_key']) ? 'EMPTY' : 'SET'));
8303 //error_log(' - Host: ' . ($pinecone_options['mxchat_pinecone_host'] ?? 'NOT SET'));
8304
8305 $use_pinecone = ($pinecone_options['mxchat_use_pinecone'] ?? '0') === '1';
8306
8307 if ($use_pinecone && !empty($pinecone_options['mxchat_pinecone_api_key'])) {
8308 //error_log('[MXCHAT-DELETE] Pinecone is enabled for bot ' . $bot_id . ' - deleting all from Pinecone');
8309
8310 // Delete from bot-specific Pinecone index
8311 $result = $pinecone_manager->mxchat_delete_all_from_pinecone($pinecone_options);
8312
8313 //error_log('DEBUG: Delete all result: ' . ($result['success'] ? 'SUCCESS' : 'FAILED'));
8314 if (!$result['success']) {
8315 //error_log('DEBUG: Delete all error: ' . $result['message']);
8316 }
8317
8318 if (!$result['success']) {
8319 $success = false;
8320 $error_messages[] = $result['message'];
8321 }
8322
8323 // No cache clearing needed since we removed caching
8324
8325 } else {
8326 //error_log('[MXCHAT-DELETE] Pinecone not enabled for bot ' . $bot_id . ' - deleting from WordPress database');
8327
8328 // Delete from WordPress database
8329 global $wpdb;
8330 $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
8331
8332 // If multi-bot is active and we have a specific bot, delete only that bot's content
8333 if ($bot_id !== 'default' && class_exists('MxChat_Multi_Bot_Manager')) {
8334 $result = $wpdb->delete(
8335 $table_name,
8336 array('bot_id' => $bot_id),
8337 array('%s')
8338 );
8339 } else {
8340 // Delete all content for default bot
8341 $result = $wpdb->query("DELETE FROM {$table_name}");
8342 }
8343
8344 if ($result === false) {
8345 $success = false;
8346 $error_messages[] = 'Failed to delete from WordPress database';
8347 }
8348 }
8349
8350 // No cache clearing needed since we removed caching
8351
8352 //error_log('=== DELETE ALL DEBUG END ===');
8353
8354 // Redirect back with a success message and bot_id
8355 $redirect_url = add_query_arg(array(
8356 'page' => 'mxchat-prompts',
8357 'bot_id' => $bot_id,
8358 'all_deleted' => $success ? 'true' : 'false'
8359 ), admin_url('admin.php'));
8360
8361 wp_safe_redirect($redirect_url);
8362 exit;
8363 }
8364
8365
8366
8367 /**
8368 * Handles deletion prompt with nonce validation
8369 */
8370 public function mxchat_handle_delete_prompt() {
8371 // Sanitize and validate nonce
8372 $nonce = isset($_GET['_wpnonce']) ? sanitize_text_field(wp_unslash($_GET['_wpnonce'])) : '';
8373 if (empty($nonce) || !wp_verify_nonce($nonce, 'mxchat_delete_prompt_nonce')) {
8374 wp_die(esc_html__('Nonce verification failed.', 'mxchat'));
8375 }
8376
8377 // Check permissions
8378 if (!current_user_can('manage_options')) {
8379 wp_die(esc_html__('You do not have sufficient permissions to delete prompts.', 'mxchat'));
8380 }
8381
8382 // Get ID and source parameters
8383 $id = isset($_GET['id']) ? sanitize_text_field($_GET['id']) : '';
8384 $source = isset($_GET['source']) ? sanitize_text_field($_GET['source']) : '';
8385
8386 if (empty($id)) {
8387 wp_die(esc_html__('Invalid prompt ID.', 'mxchat'));
8388 }
8389
8390 $success = false;
8391 $error_message = '';
8392
8393 // Check if Pinecone is enabled and determine source automatically if not specified
8394 $pinecone_options = get_option('mxchat_pinecone_addon_options', array());
8395 $use_pinecone = ($pinecone_options['mxchat_use_pinecone'] ?? '0') === '1';
8396
8397 // If source is not specified, determine based on Pinecone configuration
8398 if (empty($source)) {
8399 $source = $use_pinecone ? 'pinecone' : 'wordpress';
8400 }
8401
8402 if ($source === 'pinecone' || $use_pinecone) {
8403 //error_log('[MXCHAT-DELETE] Deleting from Pinecone, ID: ' . $id);
8404
8405 // Handle Pinecone deletion
8406 if (empty($pinecone_options['mxchat_pinecone_host']) ||
8407 empty($pinecone_options['mxchat_pinecone_api_key'])) {
8408 wp_die(esc_html__('Pinecone configuration is missing.', 'mxchat'));
8409 }
8410
8411 // Delete from Pinecone using the vector ID directly
8412 $pinecone_manager = MxChat_Pinecone_Manager::get_instance();
8413 $result = $pinecone_manager->mxchat_delete_from_pinecone_by_vector_id(
8414 $id,
8415 $pinecone_options['mxchat_pinecone_api_key'],
8416 $pinecone_options['mxchat_pinecone_host']
8417 );
8418 if ($result['success']) {
8419 $success = true;
8420
8421 // Remove from vector cache
8422 $pinecone_manager->mxchat_remove_from_pinecone_vector_cache($id);
8423 $pinecone_manager->mxchat_remove_from_processed_content_caches($id);
8424
8425 set_transient('mxchat_admin_notice_success',
8426 esc_html__('Vector deleted successfully from Pinecone.', 'mxchat'), 30);
8427 } else {
8428 $error_message = $result['message'];
8429 set_transient('mxchat_admin_notice_error',
8430 esc_html__('Failed to delete from Pinecone: ', 'mxchat') . esc_html($error_message), 30);
8431 }
8432 } else {
8433 //error_log('[MXCHAT-DELETE] Deleting from WordPress database, ID: ' . $id);
8434
8435 // Handle WordPress database deletion
8436 global $wpdb;
8437 $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
8438
8439 // Clear cache and delete prompt
8440 wp_cache_delete('prompt_' . $id, 'mxchat_prompts');
8441
8442 $result = $wpdb->delete(
8443 $table_name,
8444 array('id' => intval($id)),
8445 array('%d')
8446 );
8447
8448 if ($result !== false) {
8449 $success = true;
8450 set_transient('mxchat_admin_notice_success',
8451 esc_html__('Entry deleted successfully.', 'mxchat'), 30);
8452 } else {
8453 set_transient('mxchat_admin_notice_error',
8454 esc_html__('Failed to delete entry from database.', 'mxchat'), 30);
8455 }
8456 }
8457
8458 // Redirect back to the prompts page
8459 wp_safe_redirect(add_query_arg(
8460 array(
8461 'page' => 'mxchat-prompts',
8462 'deleted' => $success ? 'true' : 'false'
8463 ),
8464 admin_url('admin.php')
8465 ));
8466 exit;
8467 }
8468
8469
8470
8471
8472 /**
8473 * Averages multiple vectors into a single vector
8474 */
8475 private function mxchat_average_vectors($vectors) {
8476 $vector_length = count($vectors[0]);
8477 $sum_vector = array_fill(0, $vector_length, 0);
8478
8479 foreach ($vectors as $vector) {
8480 for ($i = 0; $i < $vector_length; $i++) {
8481 $sum_vector[$i] += $vector[$i];
8482 }
8483 }
8484
8485 // Divide each component by the number of vectors to get the average
8486 $num_vectors = count($vectors);
8487 for ($i = 0; $i < $vector_length; $i++) {
8488 $sum_vector[$i] /= $num_vectors;
8489 }
8490
8491 return $sum_vector;
8492 }
8493
8494
8495
8496
8497 /**
8498 * Generates embeddings from input text for MXChat
8499 */
8500 public function mxchat_generate_embedding($text) {
8501 // Enable detailed logging for debugging
8502 //error_log('[MXCHAT-EMBED] Starting embedding generation. Text length: ' . strlen($text) . ' bytes');
8503 //error_log('[MXCHAT-EMBED] Text preview: ' . substr($text, 0, 100) . '...');
8504
8505 $options = get_option('mxchat_options');
8506 $selected_model = $options['embedding_model'] ?? 'text-embedding-ada-002';
8507 //error_log('[MXCHAT-EMBED] Selected embedding model: ' . $selected_model);
8508
8509 // Determine provider and endpoint
8510 if (strpos($selected_model, 'voyage') === 0) {
8511 $api_key = $options['voyage_api_key'] ?? '';
8512 $endpoint = 'https://api.voyageai.com/v1/embeddings';
8513 $provider_name = 'Voyage AI';
8514 //error_log('[MXCHAT-EMBED] Using Voyage AI API');
8515 } elseif (strpos($selected_model, 'gemini-embedding') === 0) {
8516 $api_key = $options['gemini_api_key'] ?? '';
8517 $endpoint = 'https://generativelanguage.googleapis.com/v1beta/models/' . $selected_model . ':embedContent';
8518 $provider_name = 'Google Gemini';
8519 //error_log('[MXCHAT-EMBED] Using Google Gemini API');
8520 } else {
8521 $api_key = $options['api_key'] ?? '';
8522 $endpoint = 'https://api.openai.com/v1/embeddings';
8523 $provider_name = 'OpenAI';
8524 //error_log('[MXCHAT-EMBED] Using OpenAI API');
8525 }
8526
8527 //error_log('[MXCHAT-EMBED] Using endpoint: ' . $endpoint);
8528
8529 if (empty($api_key)) {
8530 $error_message = sprintf('Missing %s API key. Please configure your API key in the MxChat settings.', $provider_name);
8531 //error_log('[MXCHAT-EMBED] Error: ' . $error_message);
8532 return $error_message;
8533 }
8534
8535 // Check if text is too long (for OpenAI, roughly estimate tokens as words/0.75)
8536 $estimated_tokens = ceil(str_word_count($text) / 0.75);
8537 //error_log('[MXCHAT-EMBED] Estimated token count: ~' . $estimated_tokens);
8538
8539 if ($estimated_tokens > 8000 && strpos($selected_model, 'voyage') === false && strpos($selected_model, 'gemini-embedding') === false) {
8540 //error_log('[MXCHAT-EMBED] Warning: Text may exceed OpenAI token limits (8K for most models)');
8541 // Consider truncating text here
8542 }
8543
8544 // Prepare request body based on provider
8545 if (strpos($selected_model, 'gemini-embedding') === 0) {
8546 // Gemini API format
8547 $request_body = array(
8548 'model' => 'models/' . $selected_model,
8549 'content' => array(
8550 'parts' => array(
8551 array('text' => $text)
8552 )
8553 )
8554 );
8555
8556 // Set output dimensionality to 1536 for consistency with other models
8557 $request_body['outputDimensionality'] = 1536;
8558 } else {
8559 // OpenAI/Voyage API format
8560 $request_body = array(
8561 'model' => $selected_model,
8562 'input' => $text
8563 );
8564
8565 // Add output_dimension for voyage-3-large model
8566 if ($selected_model === 'voyage-3-large') {
8567 $request_body['output_dimension'] = 2048;
8568 }
8569 }
8570
8571 //error_log('[MXCHAT-EMBED] Request prepared with model: ' . $selected_model);
8572
8573 // Prepare headers based on provider
8574 if (strpos($selected_model, 'gemini-embedding') === 0) {
8575 // Gemini uses API key as query parameter
8576 $endpoint .= '?key=' . $api_key;
8577 $headers = array(
8578 'Content-Type' => 'application/json'
8579 );
8580 } else {
8581 // OpenAI/Voyage use Bearer token
8582 $headers = array(
8583 'Authorization' => 'Bearer ' . $api_key,
8584 'Content-Type' => 'application/json'
8585 );
8586 }
8587
8588 // Make API request
8589 //error_log('[MXCHAT-EMBED] Sending API request to: ' . $endpoint);
8590 $response = wp_remote_post($endpoint, array(
8591 'body' => wp_json_encode($request_body),
8592 'headers' => $headers,
8593 'timeout' => 60 // Increased timeout for large inputs
8594 ));
8595
8596 // Handle wp_remote_post errors
8597 if (is_wp_error($response)) {
8598 $error_message = $response->get_error_message();
8599 //error_log('[MXCHAT-EMBED] WP Remote Post Error: ' . $error_message);
8600 return 'Connection error: ' . $error_message;
8601 }
8602
8603 // Get and check HTTP response code
8604 $http_code = wp_remote_retrieve_response_code($response);
8605 //error_log('[MXCHAT-EMBED] API Response Code: ' . $http_code);
8606
8607 if ($http_code !== 200) {
8608 $error_body = wp_remote_retrieve_body($response);
8609 //error_log('[MXCHAT-EMBED] API Error Response Body: ' . $error_body);
8610
8611 // Try to parse error for more details
8612 $error_json = json_decode($error_body, true);
8613 if (json_last_error() === JSON_ERROR_NONE && isset($error_json['error'])) {
8614 $error_type = $error_json['error']['type'] ?? 'unknown';
8615 $error_message = $error_json['error']['message'] ?? 'No message';
8616 //error_log('[MXCHAT-EMBED] API Error Type: ' . $error_type);
8617 //error_log('[MXCHAT-EMBED] API Error Message: ' . $error_message);
8618
8619 // Customize error message for common API errors
8620 if ($error_type === 'invalid_request_error' && strpos($error_message, 'API key') !== false) {
8621 $error_message = sprintf('Invalid %s API key. Please check your API key in the MxChat settings.', $provider_name);
8622 } elseif ($error_type === 'authentication_error') {
8623 $error_message = sprintf('%s authentication failed. Please verify your API key in the MxChat settings.', $provider_name);
8624 }
8625
8626 //error_log('[MXCHAT-EMBED] Returning error: ' . $error_message);
8627 return $error_message;
8628 }
8629
8630 $error_message = sprintf("API Error (HTTP %d): Unable to generate embedding", $http_code);
8631 //error_log('[MXCHAT-EMBED] Returning error: ' . $error_message);
8632 return $error_message;
8633 }
8634
8635 // Parse response body
8636 $response_body = wp_remote_retrieve_body($response);
8637 //error_log('[MXCHAT-EMBED] Received response length: ' . strlen($response_body) . ' bytes');
8638
8639 $response_data = json_decode($response_body, true);
8640
8641 if (json_last_error() !== JSON_ERROR_NONE) {
8642 $error = json_last_error_msg();
8643 //error_log('[MXCHAT-EMBED] JSON Parse Error: ' . $error);
8644 //error_log('[MXCHAT-EMBED] Response preview: ' . substr($response_body, 0, 200));
8645 return "Failed to parse API response: $error";
8646 }
8647
8648 // Handle different response formats based on provider
8649 if (strpos($selected_model, 'gemini-embedding') === 0) {
8650 // Gemini API response format
8651 if (isset($response_data['embedding']['values'])) {
8652 $embedding_dimensions = count($response_data['embedding']['values']);
8653 //error_log('[MXCHAT-EMBED] Successfully extracted Gemini embedding with ' . $embedding_dimensions . ' dimensions');
8654
8655 // Check if embedding dimensions are as expected (should be 1536)
8656 if ($embedding_dimensions !== 1536) {
8657 //error_log('[MXCHAT-EMBED] Warning: Unexpected Gemini embedding dimensions: ' . $embedding_dimensions);
8658 }
8659
8660 return $response_data['embedding']['values'];
8661 } else {
8662 //error_log('[MXCHAT-EMBED] Error: No embedding found in Gemini response');
8663 //error_log('[MXCHAT-EMBED] Response structure: ' . wp_json_encode(array_keys($response_data)));
8664
8665 if (isset($response_data['error'])) {
8666 $error_message = "Gemini API Error in response: " . wp_json_encode($response_data['error']);
8667 //error_log('[MXCHAT-EMBED] ' . $error_message);
8668 return $error_message;
8669 }
8670
8671 $error_message = "Invalid Gemini API response format: No embedding found";
8672 //error_log('[MXCHAT-EMBED] ' . $error_message);
8673 return $error_message;
8674 }
8675 } else {
8676 // OpenAI/Voyage API response format
8677 if (isset($response_data['data'][0]['embedding'])) {
8678 $embedding_dimensions = count($response_data['data'][0]['embedding']);
8679 //error_log('[MXCHAT-EMBED] Successfully extracted embedding with ' . $embedding_dimensions . ' dimensions');
8680
8681 // Check if embedding dimensions are as expected
8682 if (($selected_model === 'text-embedding-ada-002' && $embedding_dimensions !== 1536) ||
8683 ($selected_model === 'voyage-3-large' && $embedding_dimensions !== 2048)) {
8684 //error_log('[MXCHAT-EMBED] Warning: Unexpected embedding dimensions');
8685 }
8686
8687 return $response_data['data'][0]['embedding'];
8688 } else {
8689 //error_log('[MXCHAT-EMBED] Error: No embedding found in response');
8690 //error_log('[MXCHAT-EMBED] Response structure: ' . wp_json_encode(array_keys($response_data)));
8691
8692 if (isset($response_data['error'])) {
8693 $error_message = "API Error in response: " . wp_json_encode($response_data['error']);
8694 //error_log('[MXCHAT-EMBED] ' . $error_message);
8695 return $error_message;
8696 }
8697
8698 $error_message = "Invalid API response format: No embedding found";
8699 //error_log('[MXCHAT-EMBED] ' . $error_message);
8700 return $error_message;
8701 }
8702 }
8703 }
8704
8705
8706
8707 }
8708 ?>
8709