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

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