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

8,520 lines 353.8 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 $nonce = wp_create_nonce('mxchat_autosave_nonce');
5376
5377 echo '<div class="api-key-wrapper">';
5378 echo '<input type="password" id="api_key" name="api_key" value="' . $apiKey . '" class="regular-text mxchat-autosave-field" autocomplete="off" data-nonce="' . $nonce . '" />';
5379 echo '<button type="button" id="toggleApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5380 echo '<p class="description">' . esc_html__('Required for OpenAI GPT models and OpenAI embeddings. Get your API key from OpenAI Platform.', 'mxchat') . '</p>';
5381 echo '</div>';
5382 }
5383
5384 // X.AI API Key
5385 public function xai_api_key_callback() {
5386 $xaiApiKey = isset($this->options['xai_api_key']) ? esc_attr($this->options['xai_api_key']) : '';
5387 $nonce = wp_create_nonce('mxchat_autosave_nonce');
5388
5389 echo '<div class="api-key-wrapper">';
5390 echo '<input type="password" id="xai_api_key" name="xai_api_key" value="' . $xaiApiKey . '" class="regular-text mxchat-autosave-field" autocomplete="off" data-nonce="' . $nonce . '" />';
5391 echo '<button type="button" id="toggleXaiApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5392 echo '<p class="description">' . esc_html__('Required for X.AI Grok models. Get your API key from X.AI Console.', 'mxchat') . '</p>';
5393 echo '</div>';
5394 }
5395 // Claude API Key
5396 public function claude_api_key_callback() {
5397 $claudeApiKey = isset($this->options['claude_api_key']) ? esc_attr($this->options['claude_api_key']) : '';
5398 $nonce = wp_create_nonce('mxchat_autosave_nonce');
5399
5400 echo '<div class="api-key-wrapper">';
5401 echo '<input type="password" id="claude_api_key" name="claude_api_key" value="' . $claudeApiKey . '" class="regular-text mxchat-autosave-field" autocomplete="off" data-nonce="' . $nonce . '" />';
5402 echo '<button type="button" id="toggleClaudeApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5403 echo '<p class="description">' . esc_html__('Required for Anthropic Claude models. Get your API key from Anthropic Console.', 'mxchat') . '</p>';
5404 echo '</div>';
5405 }
5406
5407 // DeepSeek API Key
5408 public function deepseek_api_key_callback() {
5409 $apiKey = isset($this->options['deepseek_api_key']) ? esc_attr($this->options['deepseek_api_key']) : '';
5410 $nonce = wp_create_nonce('mxchat_autosave_nonce');
5411
5412 echo '<div class="api-key-wrapper">';
5413 echo '<input type="password" id="deepseek_api_key" name="deepseek_api_key" value="' . $apiKey . '" class="regular-text mxchat-autosave-field" autocomplete="off" data-nonce="' . $nonce . '" />';
5414 echo '<button type="button" id="toggleDeepSeekApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5415 echo '<p class="description">' . esc_html__('Required for DeepSeek models. Get your API key from DeepSeek Platform.', 'mxchat') . '</p>';
5416 echo '</div>';
5417 }
5418
5419 // Gemini API Key
5420 public function gemini_api_key_callback() {
5421 $geminiApiKey = isset($this->options['gemini_api_key']) ? esc_attr($this->options['gemini_api_key']) : '';
5422 $nonce = wp_create_nonce('mxchat_autosave_nonce');
5423
5424 echo '<div class="api-key-wrapper">';
5425 echo '<input type="password" id="gemini_api_key" name="gemini_api_key" value="' . $geminiApiKey . '" class="regular-text mxchat-autosave-field" autocomplete="off" data-nonce="' . $nonce . '" />';
5426 echo '<button type="button" id="toggleGeminiApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5427 echo '<p class="description">' . esc_html__('Required for Google Gemini models and embeddings. Get your API key from Google AI Studio.', 'mxchat') . '</p>';
5428 echo '</div>';
5429 }
5430
5431
5432 // OpenRouter API Key
5433 public function openrouter_api_key_callback() {
5434 $openrouterApiKey = isset($this->options['openrouter_api_key']) ? esc_attr($this->options['openrouter_api_key']) : '';
5435 $nonce = wp_create_nonce('mxchat_autosave_nonce');
5436
5437 echo '<div class="api-key-wrapper">';
5438 echo '<input type="password" id="openrouter_api_key" name="openrouter_api_key" value="' . $openrouterApiKey . '" class="regular-text mxchat-autosave-field" autocomplete="off" data-nonce="' . $nonce . '" />';
5439 echo '<button type="button" id="toggleOpenRouterApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5440 echo '<p class="description">' . esc_html__('Required for OpenRouter models. Get your API key from OpenRouter.ai', 'mxchat') . '</p>';
5441 echo '</div>';
5442 }
5443
5444 // Voyage API Key
5445 public function voyage_api_key_callback() {
5446 $apiKey = isset($this->options['voyage_api_key']) ? esc_attr($this->options['voyage_api_key']) : '';
5447 $nonce = wp_create_nonce('mxchat_autosave_nonce');
5448
5449 echo '<div class="api-key-wrapper">';
5450 echo '<input type="password" id="voyage_api_key" name="voyage_api_key" value="' . $apiKey . '" class="regular-text mxchat-autosave-field" autocomplete="off" data-nonce="' . $nonce . '" />';
5451 echo '<button type="button" id="toggleVoyageAPIKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5452 echo '<p class="description">' . esc_html__('Required for Voyage AI embedding models. Get your API key from Voyage AI.', 'mxchat') . '</p>';
5453 echo '</div>';
5454 }
5455
5456 public function mxchat_loops_api_key_callback() {
5457 $loops_api_key = isset($this->options['loops_api_key']) ? esc_attr($this->options['loops_api_key']) : '';
5458 $nonce = wp_create_nonce('mxchat_autosave_nonce');
5459
5460 // Hidden fields to "trap" autofill
5461 echo '<input type="text" style="display:none" autocomplete="username" />';
5462 echo '<input type="password" style="display:none" autocomplete="current-password" />';
5463
5464 echo '<div class="api-key-wrapper">';
5465 echo sprintf(
5466 '<input type="password" id="loops_api_key" name="loops_api_key" value="%s" class="regular-text mxchat-autosave-field" autocomplete="new-password" data-nonce="%s" />',
5467 $loops_api_key,
5468 $nonce
5469 );
5470 echo '<button type="button" id="toggleLoopsApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5471 echo '</div>';
5472 echo '<p class="description">' . esc_html__('Required for Loops email integration. Get your API key from Loops.so', 'mxchat') . '</p>';
5473 }
5474 public function mxchat_loops_mailing_list_callback() {
5475 // Add error handling and type checking
5476 $loops_api_key = '';
5477 $selected_list = '';
5478 $nonce = wp_create_nonce('mxchat_autosave_nonce');
5479
5480 // Safely get the API key
5481 if (isset($this->options['loops_api_key']) && is_string($this->options['loops_api_key'])) {
5482 $loops_api_key = $this->options['loops_api_key'];
5483 }
5484
5485 // Safely get the selected list
5486 if (isset($this->options['loops_mailing_list']) && is_string($this->options['loops_mailing_list'])) {
5487 $selected_list = $this->options['loops_mailing_list'];
5488 }
5489
5490 if (!empty($loops_api_key)) {
5491 $lists = $this->mxchat_fetch_loops_mailing_lists($loops_api_key);
5492 if (is_array($lists) && !empty($lists)) {
5493 echo '<div class="mxchat-field-wrapper">';
5494 echo '<select id="loops_mailing_list" name="loops_mailing_list" class="mxchat-autosave-field" data-nonce="' . $nonce . '">';
5495
5496 // Add a default "Select a list" option
5497 echo '<option value="" ' . selected($selected_list, '', false) . '>' . esc_html__('Select a list', 'mxchat') . '</option>';
5498
5499 foreach ($lists as $list) {
5500 if (is_array($list) && isset($list['id']) && isset($list['name'])) {
5501 echo sprintf(
5502 '<option value="%s" %s>%s</option>',
5503 esc_attr($list['id']),
5504 selected($selected_list, $list['id'], false),
5505 esc_html($list['name'])
5506 );
5507 }
5508 }
5509 echo '</select>';
5510 echo '</div>';
5511 echo '<p class="description">' . esc_html__('Please select a mailing list to use with Loops.', 'mxchat') . '</p>';
5512 } else {
5513 echo '<p class="description">' . esc_html__('No lists found. Please verify your API Key.', 'mxchat') . '</p>';
5514 }
5515 } else {
5516 echo '<p class="description">' . esc_html__('Enter a valid Loops API Key to load mailing lists.', 'mxchat') . '</p>';
5517 }
5518 }
5519 public function mxchat_triggered_phrase_response_callback() {
5520 $default_response = __('Would you like to join our mailing list? Please provide your email below.', 'mxchat');
5521 $triggered_response = isset($this->options['triggered_phrase_response'])
5522 ? $this->options['triggered_phrase_response']
5523 : $default_response;
5524 $nonce = wp_create_nonce('mxchat_autosave_nonce');
5525
5526 echo '<div class="mxchat-field-wrapper">';
5527 echo sprintf(
5528 '<textarea id="triggered_phrase_response" name="triggered_phrase_response" rows="3" cols="50" class="mxchat-autosave-field" data-nonce="%s">%s</textarea>',
5529 $nonce,
5530 esc_textarea($triggered_response)
5531 );
5532 echo '</div>';
5533 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>';
5534 }
5535
5536 public function mxchat_email_capture_response_callback() {
5537 $default_response = __('Thank you for providing your email! You\'ve been added to our list.', 'mxchat');
5538 $email_capture_response = isset($this->options['email_capture_response'])
5539 ? $this->options['email_capture_response']
5540 : $default_response;
5541 $nonce = wp_create_nonce('mxchat_autosave_nonce');
5542
5543 echo '<div class="mxchat-field-wrapper">';
5544 echo sprintf(
5545 '<textarea id="email_capture_response" name="email_capture_response" rows="3" cols="50" class="mxchat-autosave-field" data-nonce="%s">%s</textarea>',
5546 $nonce,
5547 esc_textarea($email_capture_response)
5548 );
5549 echo '</div>';
5550 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>';
5551 }
5552 public function mxchat_pre_chat_message_callback() {
5553 // Load the entire 'mxchat_options' array
5554 $all_options = get_option('mxchat_options', []);
5555
5556 // Retrieve the saved message or use the default value
5557 $default_message = __('Hey there! Ask me anything!', 'mxchat');
5558 $pre_chat_message = isset($all_options['pre_chat_message']) ? $all_options['pre_chat_message'] : $default_message;
5559
5560 // Output the textarea
5561 printf(
5562 '<textarea id="pre_chat_message" name="pre_chat_message" rows="5" cols="50">%s</textarea>',
5563 esc_textarea($pre_chat_message)
5564 );
5565 }
5566
5567 // Callback for AI Instructions textarea
5568 public function system_prompt_instructions_callback() {
5569 // Retrieve the current value of the system prompt instructions
5570 $instructions = isset($this->options['system_prompt_instructions']) ? esc_textarea($this->options['system_prompt_instructions']) : '';
5571 // Render the textarea field
5572 printf(
5573 '<textarea id="system_prompt_instructions" name="system_prompt_instructions" rows="5" cols="50">%s</textarea>',
5574 $instructions
5575 );
5576 // Sample instructions button
5577 echo '<div class="mxchat-instructions-container">';
5578 echo '<button type="button" class="mxchat-instructions-btn" id="mxchatViewSampleBtn">';
5579 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">';
5580 echo '<path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/>';
5581 echo '<path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/>';
5582 echo '</svg>';
5583 echo esc_html__('View Sample Instructions', 'mxchat');
5584 echo '</button>';
5585 echo '</div>';
5586
5587 // Add modal to WordPress admin footer instead of inline
5588 add_action('admin_footer', array($this, 'render_sample_instructions_modal'));
5589 }
5590
5591 // New method to render modal in admin footer
5592 public function render_sample_instructions_modal() {
5593 static $modal_rendered = false;
5594 if ($modal_rendered) return; // Prevent duplicate modals
5595 $modal_rendered = true;
5596
5597 echo '<div class="mxchat-instructions-modal-overlay" id="mxchatSampleModal">';
5598 echo '<div class="mxchat-instructions-modal-content">';
5599 echo '<div class="mxchat-instructions-modal-header">';
5600 echo '<h3 class="mxchat-instructions-modal-title">';
5601 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">';
5602 echo '<path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/>';
5603 echo '<path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/>';
5604 echo '</svg>';
5605 echo esc_html__('Sample AI Instructions', 'mxchat');
5606 echo '</h3>';
5607 echo '<button type="button" class="mxchat-instructions-modal-close" id="mxchatModalClose">';
5608 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">';
5609 echo '<line x1="18" y1="6" x2="6" y2="18"/>';
5610 echo '<line x1="6" y1="6" x2="18" y2="18"/>';
5611 echo '</svg>';
5612 echo '</button>';
5613 echo '</div>';
5614 echo '<div class="mxchat-instructions-modal-body">';
5615 echo '<div class="mxchat-instructions-content">';
5616 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:
5617
5618 # Response Style - CRITICALLY IMPORTANT
5619 - MAXIMUM LENGTH: 1-3 short sentences per response
5620 - Ultra-concise: Get straight to the answer with no filler
5621 - No introductions like "Sure!" or "I\'d be happy to help"
5622 - No phrases like "based on my knowledge" or "according to information"
5623 - No explanatory text before giving the answer
5624 - No summaries or repetition
5625 - Hyperlink all URLs
5626 - Respond in user\'s language
5627 - Minor chit chat or conversation is okay, but try to keep it focused on [insert topic]
5628
5629 # Knowledge Base Requirements - PREVENT HALLUCINATIONS
5630 - ONLY answer using information explicitly provided in OFFICIAL KNOWLEDGE DATABASE CONTENT sections marked with ===== delimiters
5631 - If required information is NOT in the knowledge database: "I don\'t have enough information in my knowledge base to answer that question accurately."
5632 - NEVER invent or hallucinate URLs, links, product specs, procedures, dates, statistics, names, contacts, or company information
5633 - When knowledge base information is unclear or contradictory, acknowledge the limitation rather than guessing
5634 - Better to admit insufficient information than provide inaccurate answers');
5635 echo '</div>';
5636 echo '<button type="button" class="mxchat-instructions-copy-btn" id="mxchatCopyBtn">';
5637 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">';
5638 echo '<rect x="9" y="9" width="13" height="13" rx="2" ry="2"/>';
5639 echo '<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/>';
5640 echo '</svg>';
5641 echo esc_html__('Copy Instructions', 'mxchat');
5642 echo '</button>';
5643 echo '</div>';
5644 echo '<div class="mxchat-instructions-modal-footer">';
5645 echo '<button type="button" class="mxchat-instructions-btn-secondary" id="mxchatCloseBtn">' . esc_html__('Close', 'mxchat') . '</button>';
5646 echo '</div>';
5647 echo '</div>';
5648 echo '</div>';
5649 }
5650
5651
5652 public function mxchat_model_callback() {
5653 // Define available models grouped by provider
5654 $models = array(
5655 esc_html__('OpenRouter (100+ Models)', 'mxchat') => array(
5656 'openrouter' => esc_html__('OpenRouter - Select after entering API key', 'mxchat'),
5657 ),
5658 esc_html__('Google Gemini Models', 'mxchat') => array(
5659 'gemini-3-pro-preview' => esc_html__('Gemini 3 Pro (Most Intelligent, Multimodal)', 'mxchat'),
5660 'gemini-3-flash-preview' => esc_html__('Gemini 3 Flash (Balanced Speed & Scale)', 'mxchat'),
5661 'gemini-2.5-pro' => esc_html__('Gemini 2.5 Pro (Advanced Thinking)', 'mxchat'),
5662 'gemini-2.5-flash' => esc_html__('Gemini 2.5 Flash (Best Price-Performance)', 'mxchat'),
5663 'gemini-2.5-flash-lite' => esc_html__('Gemini 2.5 Flash-Lite (Ultra Fast)', 'mxchat'),
5664 'gemini-2.0-flash' => esc_html__('Gemini 2.0 Flash (Deprecated Mar 2026)', 'mxchat'),
5665 'gemini-2.0-flash-lite' => esc_html__('Gemini 2.0 Flash-Lite (Deprecated Mar 2026)', 'mxchat'),
5666 'gemini-1.5-pro' => esc_html__('Gemini 1.5 Pro (Deprecated Sep 2025)', 'mxchat'),
5667 'gemini-1.5-flash' => esc_html__('Gemini 1.5 Flash (Deprecated Sep 2025)', 'mxchat'),
5668 ),
5669 esc_html__('X.AI Models', 'mxchat') => array(
5670 'grok-3-beta' => esc_html__('Grok-3 (Powerful)', 'mxchat'),
5671 'grok-3-fast-beta' => esc_html__('Grok-3 Fast (High Performance)', 'mxchat'),
5672 'grok-3-mini-beta' => esc_html__('Grok-3 Mini (Affordable)', 'mxchat'),
5673 'grok-3-mini-fast-beta' => esc_html__('Grok-3 Mini Fast (Quick Response)', 'mxchat'),
5674 'grok-2' => esc_html__('Grok 2', 'mxchat'),
5675 'grok-4-0709' => esc_html__('Grok 4 (Latest Flagship)', 'mxchat'),
5676 'grok-4-1-fast-reasoning' => esc_html__('Grok 4.1 Fast (Reasoning)', 'mxchat'),
5677 'grok-4-1-fast-non-reasoning' => esc_html__('Grok 4.1 Fast (Non-Reasoning)', 'mxchat'),
5678 ),
5679 esc_html__('DeepSeek Models', 'mxchat') => array(
5680 'deepseek-chat' => esc_html__('DeepSeek-V3', 'mxchat'),
5681 ),
5682 esc_html__('Claude Models', 'mxchat') => array(
5683 'claude-sonnet-4-5-20250929' => esc_html__('Claude Sonnet 4.5 (Best for Agents & Coding)', 'mxchat'),
5684 'claude-opus-4-1-20250805' => esc_html__('Claude Opus 4.1 (Exceptional for Complex Tasks)', 'mxchat'),
5685 'claude-haiku-4-5-20251001' => esc_html__('Claude Haiku 4.5 (Fastest & Most Intelligent)', 'mxchat'),
5686 'claude-opus-4-20250514' => esc_html__('Claude 4 Opus (Most Capable)', 'mxchat'),
5687 'claude-sonnet-4-20250514' => esc_html__('Claude 4 Sonnet (High Performance)', 'mxchat'),
5688 'claude-3-7-sonnet-20250219' => esc_html__('Claude 3.7 Sonnet (High Intelligence)', 'mxchat'),
5689 'claude-3-opus-20240229' => esc_html__('Claude 3 Opus (Complex Tasks)', 'mxchat'),
5690 'claude-3-sonnet-20240229' => esc_html__('Claude 3 Sonnet (Balanced)', 'mxchat'),
5691 'claude-3-haiku-20240307' => esc_html__('Claude 3 Haiku (Fastest)', 'mxchat'),
5692 ),
5693 esc_html__('OpenAI Models', 'mxchat') => array(
5694 // GPT-5 family
5695 'gpt-5.2' => esc_html__('GPT-5.2 (Best General-Purpose & Agentic Model)', 'mxchat'),
5696 'gpt-5.1-2025-11-13' => esc_html__('GPT-5.1 (Flagship for Coding & Agentic Tasks)', 'mxchat'),
5697 'gpt-5' => esc_html__('GPT-5 (Flagship for Coding, Reasoning & Agents)', 'mxchat'),
5698 'gpt-5-mini' => esc_html__('GPT-5 Mini (Faster, Cost-Efficient for Precise Prompts)', 'mxchat'),
5699 'gpt-5-nano' => esc_html__('GPT-5 Nano (Fastest & Cheapest for Summarization/Classification)', 'mxchat'),
5700
5701 // Existing OpenAI models
5702 'gpt-4.1-2025-04-14' => esc_html__('GPT-4.1 (Flagship for Complex Tasks)', 'mxchat'),
5703 'gpt-4o' => esc_html__('GPT-4o (Recommended)', 'mxchat'),
5704 'gpt-4o-mini' => esc_html__('GPT-4o Mini (Fast and Lightweight)', 'mxchat'),
5705 'gpt-4-turbo' => esc_html__('GPT-4 Turbo (High-Performance)', 'mxchat'),
5706 'gpt-4' => esc_html__('GPT-4 (High Intelligence)', 'mxchat'),
5707 'gpt-3.5-turbo' => esc_html__('GPT-3.5 Turbo (Affordable and Fast)', 'mxchat'),
5708 ),
5709 );
5710
5711 // Retrieve the currently selected model from saved options
5712 $selected_model = isset($this->options['model']) ? esc_attr($this->options['model']) : 'gpt-4o';
5713
5714 // Begin the select dropdown
5715 echo '<select id="model" name="model">';
5716
5717 // Iterate over groups of models
5718 foreach ($models as $group_label => $group_models) {
5719 echo '<optgroup label="' . esc_attr($group_label) . '">';
5720
5721 foreach ($group_models as $model_value => $model_label) {
5722 echo '<option value="' . esc_attr($model_value) . '" ' . selected($selected_model, $model_value, false) . '>' . esc_html($model_label) . '</option>';
5723 }
5724
5725 echo '</optgroup>';
5726 }
5727
5728 echo '</select>';
5729
5730 // Add a note for OpenRouter
5731 echo '<p class="description" id="openrouter-model-note" style="display:none; color: #d63638; font-weight: 500;">';
5732 echo '<span class="dashicons dashicons-info" style="font-size: 16px; vertical-align: middle;"></span> ';
5733 echo esc_html__('After entering your OpenRouter API key above, click the button below to load available models.', 'mxchat');
5734 echo '</p>';
5735
5736 // API Key Status Messages (hidden by default, shown by JS based on selected model)
5737 $has_openai_key = !empty($this->options['api_key']);
5738 $has_claude_key = !empty($this->options['claude_api_key']);
5739 $has_xai_key = !empty($this->options['xai_api_key']);
5740 $has_deepseek_key = !empty($this->options['deepseek_api_key']);
5741 $has_gemini_key = !empty($this->options['gemini_api_key']);
5742 $has_openrouter_key = !empty($this->options['openrouter_api_key']);
5743
5744 // OpenAI/GPT models
5745 echo '<p class="mxchat-api-status" data-provider="openai" style="display:none;">';
5746 if ($has_openai_key) {
5747 echo '<span style="color: #00a32a;">✓ ' . esc_html__('API key for OpenAI detected', 'mxchat') . '</span>';
5748 } else {
5749 echo '<span style="color: #d63638;">⚠ ' . esc_html__('No API key for OpenAI detected. Please enter API key in API Keys tab.', 'mxchat') . '</span>';
5750 }
5751 echo '</p>';
5752
5753 // Claude models
5754 echo '<p class="mxchat-api-status" data-provider="claude" style="display:none;">';
5755 if ($has_claude_key) {
5756 echo '<span style="color: #00a32a;">✓ ' . esc_html__('API key for Anthropic (Claude) detected', 'mxchat') . '</span>';
5757 } else {
5758 echo '<span style="color: #d63638;">⚠ ' . esc_html__('No API key for Anthropic (Claude) detected. Please enter API key in API Keys tab.', 'mxchat') . '</span>';
5759 }
5760 echo '</p>';
5761
5762 // X.AI models
5763 echo '<p class="mxchat-api-status" data-provider="xai" style="display:none;">';
5764 if ($has_xai_key) {
5765 echo '<span style="color: #00a32a;">✓ ' . esc_html__('API key for X.AI (Grok) detected', 'mxchat') . '</span>';
5766 } else {
5767 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>';
5768 }
5769 echo '</p>';
5770
5771 // DeepSeek models
5772 echo '<p class="mxchat-api-status" data-provider="deepseek" style="display:none;">';
5773 if ($has_deepseek_key) {
5774 echo '<span style="color: #00a32a;">✓ ' . esc_html__('API key for DeepSeek detected', 'mxchat') . '</span>';
5775 } else {
5776 echo '<span style="color: #d63638;">⚠ ' . esc_html__('No API key for DeepSeek detected. Please enter API key in API Keys tab.', 'mxchat') . '</span>';
5777 }
5778 echo '</p>';
5779
5780 // Gemini models
5781 echo '<p class="mxchat-api-status" data-provider="gemini" style="display:none;">';
5782 if ($has_gemini_key) {
5783 echo '<span style="color: #00a32a;">✓ ' . esc_html__('API key for Google Gemini detected', 'mxchat') . '</span>';
5784 } else {
5785 echo '<span style="color: #d63638;">⚠ ' . esc_html__('No API key for Google Gemini detected. Please enter API key in API Keys tab.', 'mxchat') . '</span>';
5786 }
5787 echo '</p>';
5788
5789 // OpenRouter models
5790 echo '<p class="mxchat-api-status" data-provider="openrouter" style="display:none;">';
5791 if ($has_openrouter_key) {
5792 echo '<span style="color: #00a32a;">✓ ' . esc_html__('API key for OpenRouter detected', 'mxchat') . '</span>';
5793 } else {
5794 echo '<span style="color: #d63638;">⚠ ' . esc_html__('No API key for OpenRouter detected. Please enter API key in API Keys tab.', 'mxchat') . '</span>';
5795 }
5796 echo '</p>';
5797
5798 // ADD THESE HIDDEN FIELDS RIGHT HERE:
5799 $openrouter_model = isset($this->options['openrouter_selected_model']) ? esc_attr($this->options['openrouter_selected_model']) : '';
5800 $openrouter_model_name = isset($this->options['openrouter_selected_model_name']) ? esc_attr($this->options['openrouter_selected_model_name']) : '';
5801
5802 echo '<input type="hidden" id="openrouter_selected_model" name="openrouter_selected_model" value="' . $openrouter_model . '" />';
5803 echo '<input type="hidden" id="openrouter_selected_model_name" name="openrouter_selected_model_name" value="' . $openrouter_model_name . '" />';
5804 }
5805
5806 // Update your existing callback method
5807 public function enable_streaming_toggle_callback() {
5808 // Get value from options array, default to 'on'
5809 $enabled = isset($this->options['enable_streaming_toggle']) ? $this->options['enable_streaming_toggle'] : 'on';
5810 $checked = ($enabled === 'on') ? 'checked' : '';
5811
5812 echo '<label class="toggle-switch">';
5813 echo sprintf(
5814 '<input type="checkbox" id="enable_streaming_toggle" name="enable_streaming_toggle" value="on" %s />',
5815 esc_attr($checked)
5816 );
5817 echo '<span class="slider"></span>';
5818 echo '</label>';
5819
5820 // Test button with better styling
5821 echo '<div style="margin-top: 15px;">';
5822 echo '<button type="button" id="mxchat-test-streaming-btn" class="button button-secondary">';
5823 echo '<span class="dashicons dashicons-admin-tools" style="vertical-align: middle; margin-right: 5px;"></span>';
5824 echo esc_html__('Test Streaming Compatibility', 'mxchat');
5825 echo '</button>';
5826 echo '<p id="mxchat-test-streaming-result" style="margin-top:10px; font-weight: bold;"></p>';
5827 echo '</div>';
5828 }
5829
5830 // Web Search toggle callback
5831 public function enable_web_search_toggle_callback() {
5832 // Get value from options array, default to 'off'
5833 $enabled = isset($this->options['enable_web_search']) ? $this->options['enable_web_search'] : 'off';
5834 $checked = ($enabled === 'on') ? 'checked' : '';
5835
5836 // Get current model to determine if we should show/enable the toggle
5837 $current_model = isset($this->options['model']) ? $this->options['model'] : 'gpt-4o';
5838
5839 // Models that DON'T support web search (per OpenAI docs)
5840 // gpt-5 with minimal reasoning is handled at API level, gpt-4.1-nano doesn't support it
5841 $unsupported_models = array('gpt-4.1-nano');
5842
5843 // Check if current model is an OpenAI model that supports web search
5844 $openai_models = array(
5845 'gpt-5.2', 'gpt-5.1-2025-11-13', 'gpt-5', 'gpt-5-mini', 'gpt-5-nano',
5846 'gpt-4.1-2025-04-14', 'gpt-4o', 'gpt-4o-mini', 'gpt-4-turbo', 'gpt-4', 'gpt-3.5-turbo'
5847 );
5848
5849 $is_openai = in_array($current_model, $openai_models);
5850 $is_supported = $is_openai && !in_array($current_model, $unsupported_models);
5851
5852 // Wrapper div with data attributes for JS to show/hide
5853 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;"' : '') . '>';
5854
5855 echo '<label class="toggle-switch">';
5856 echo sprintf(
5857 '<input type="checkbox" id="enable_web_search" name="enable_web_search" value="on" %s />',
5858 esc_attr($checked)
5859 );
5860 echo '<span class="slider"></span>';
5861 echo '</label>';
5862
5863 echo '</div>';
5864
5865 // Message shown when non-OpenAI model is selected
5866 echo '<p id="web-search-unavailable-message" class="description" style="color: #666;' . ($is_supported ? ' display:none;' : '') . '">';
5867 echo '<span class="dashicons dashicons-info" style="font-size: 16px; vertical-align: middle; margin-right: 4px;"></span>';
5868 echo esc_html__('Web search is only available for OpenAI models.', 'mxchat');
5869 echo '</p>';
5870 }
5871
5872 // AJAX handler to fetch OpenRouter models
5873 public function fetch_openrouter_models() {
5874 check_ajax_referer('mxchat_fetch_openrouter_models', 'nonce');
5875
5876 $api_key = isset($_POST['api_key']) ? sanitize_text_field($_POST['api_key']) : '';
5877
5878 if (empty($api_key)) {
5879 wp_send_json_error(array('message' => 'API key is required'));
5880 }
5881
5882 $response = wp_remote_get('https://openrouter.ai/api/v1/models', array(
5883 'headers' => array(
5884 'Authorization' => 'Bearer ' . $api_key,
5885 'Content-Type' => 'application/json',
5886 ),
5887 'timeout' => 15,
5888 ));
5889
5890 if (is_wp_error($response)) {
5891 wp_send_json_error(array('message' => $response->get_error_message()));
5892 }
5893
5894 $body = wp_remote_retrieve_body($response);
5895 $data = json_decode($body, true);
5896
5897 if (isset($data['data']) && is_array($data['data'])) {
5898 // Format the models for the frontend
5899 $models = array_map(function($model) {
5900 return array(
5901 'id' => $model['id'],
5902 'name' => $model['name'] ?? $model['id'],
5903 'description' => $model['description'] ?? '',
5904 'context_length' => $model['context_length'] ?? 0,
5905 'pricing' => array(
5906 'prompt' => $model['pricing']['prompt'] ?? 0,
5907 'completion' => $model['pricing']['completion'] ?? 0,
5908 ),
5909 );
5910 }, $data['data']);
5911
5912 wp_send_json_success(array('models' => $models));
5913 } else {
5914 wp_send_json_error(array('message' => 'Invalid response from OpenRouter'));
5915 }
5916 }
5917
5918 // Callback function for embedding model selection
5919 public function embedding_model_callback() {
5920 $models = array(
5921 esc_html__('OpenAI Embeddings', 'mxchat') => array(
5922 'text-embedding-3-small' => esc_html__('TE3 Small (1536, Efficient)', 'mxchat'),
5923 'text-embedding-ada-002' => esc_html__('Ada 2 (1536, Recommended)', 'mxchat'),
5924 'text-embedding-3-large' => esc_html__('TE3 Large (3072, Powerful)', 'mxchat'),
5925 ),
5926 esc_html__('Voyage AI Embeddings', 'mxchat') => array(
5927 'voyage-3-large' => esc_html__('Voyage-3 Large (2048, Most Capable)', 'mxchat'),
5928 ),
5929 esc_html__('Google Gemini Embeddings', 'mxchat') => array(
5930 'gemini-embedding-001' => esc_html__('Gemini Embedding (1536, Stable)', 'mxchat'),
5931 )
5932 );
5933 $selected_model = isset($this->options['embedding_model']) ? esc_attr($this->options['embedding_model']) : 'text-embedding-ada-002';
5934 echo '<select id="embedding_model" name="embedding_model">';
5935 foreach ($models as $group_label => $group_models) {
5936 echo '<optgroup label="' . esc_attr($group_label) . '">';
5937 foreach ($group_models as $model_value => $model_label) {
5938 echo '<option value="' . esc_attr($model_value) . '" ' . selected($selected_model, $model_value, false) . '>' . esc_html($model_label) . '</option>';
5939 }
5940 echo '</optgroup>';
5941 }
5942 echo '</select>';
5943
5944 // API Key Status Messages for Embedding Models
5945 $has_openai_key = !empty($this->options['api_key']);
5946 $has_voyage_key = !empty($this->options['voyage_api_key']);
5947 $has_gemini_key = !empty($this->options['gemini_api_key']);
5948
5949 // OpenAI Embeddings
5950 echo '<p class="mxchat-embedding-api-status" data-provider="openai" style="display:none;">';
5951 if ($has_openai_key) {
5952 echo '<span style="color: #00a32a;">✓ ' . esc_html__('API key for OpenAI detected', 'mxchat') . '</span>';
5953 } else {
5954 echo '<span style="color: #d63638;">⚠ ' . esc_html__('No API key for OpenAI detected. Please enter API key in API Keys tab.', 'mxchat') . '</span>';
5955 }
5956 echo '</p>';
5957
5958 // Voyage AI Embeddings
5959 echo '<p class="mxchat-embedding-api-status" data-provider="voyage" style="display:none;">';
5960 if ($has_voyage_key) {
5961 echo '<span style="color: #00a32a;">✓ ' . esc_html__('API key for Voyage AI detected', 'mxchat') . '</span>';
5962 } else {
5963 echo '<span style="color: #d63638;">⚠ ' . esc_html__('No API key for Voyage AI detected. Please enter API key in API Keys tab.', 'mxchat') . '</span>';
5964 }
5965 echo '</p>';
5966
5967 // Gemini Embeddings
5968 echo '<p class="mxchat-embedding-api-status" data-provider="gemini" style="display:none;">';
5969 if ($has_gemini_key) {
5970 echo '<span style="color: #00a32a;">✓ ' . esc_html__('API key for Google Gemini detected', 'mxchat') . '</span>';
5971 } else {
5972 echo '<span style="color: #d63638;">⚠ ' . esc_html__('No API key for Google Gemini detected. Please enter API key in API Keys tab.', 'mxchat') . '</span>';
5973 }
5974 echo '</p>';
5975 }
5976
5977
5978 public function mxchat_top_bar_title_callback() {
5979 // Retrieve the current value of the top bar title from saved options
5980 $top_bar_title = isset($this->options['top_bar_title']) ? esc_attr($this->options['top_bar_title']) : '';
5981
5982 // Render the input field
5983 echo '<input type="text" id="top_bar_title" name="top_bar_title" value="' . $top_bar_title . '" />';
5984 }
5985 public function mxchat_ai_agent_text_callback() {
5986 // Retrieve the current value of the AI agent text from saved options
5987 $ai_agent_text = isset($this->options['ai_agent_text']) ? esc_attr($this->options['ai_agent_text']) : '';
5988 // Render the input field
5989 echo '<input type="text" id="ai_agent_text" name="ai_agent_text" value="' . $ai_agent_text . '" />';
5990 }
5991
5992
5993 public function enable_email_block_callback() {
5994 // Load full plugin options array
5995 $all_options = get_option('mxchat_options', []);
5996
5997 // Get the value, default to 'off'
5998 $enable_email_block = isset($all_options['enable_email_block']) ? $all_options['enable_email_block'] : 'off';
5999
6000 // Check if it's 'on'
6001 $checked = ($enable_email_block === 'on') ? 'checked' : '';
6002
6003 echo '<label class="toggle-switch">';
6004 echo sprintf(
6005 '<input type="checkbox" id="enable_email_block" name="enable_email_block" value="on" %s />',
6006 esc_attr($checked)
6007 );
6008 echo '<span class="slider"></span>';
6009 echo '</label>';
6010 }
6011
6012 public function email_blocker_header_content_callback() {
6013 // Load the entire 'mxchat_options' array
6014 $all_options = get_option('mxchat_options', []);
6015
6016 // Retrieve the saved content or default to empty
6017 $content = isset($all_options['email_blocker_header_content'])
6018 ? $all_options['email_blocker_header_content']
6019 : '';
6020
6021 // Render the textarea - IMPORTANT: name should be just "email_blocker_header_content"
6022 echo '<textarea
6023 id="email_blocker_header_content"
6024 name="email_blocker_header_content"
6025 rows="5"
6026 cols="70"
6027 data-setting="email_blocker_header_content"
6028 >' . esc_textarea($content) . '</textarea>';
6029 }
6030
6031 public function email_blocker_button_text_callback() {
6032 // Load the entire 'mxchat_options' array
6033 $all_options = get_option('mxchat_options', []);
6034
6035 // Retrieve the saved button text or default to empty
6036 $button_text = isset($all_options['email_blocker_button_text'])
6037 ? $all_options['email_blocker_button_text']
6038 : '';
6039
6040 // Use esc_attr to safely render the existing text
6041 echo '<input type="text" id="email_blocker_button_text" name="email_blocker_button_text" value="' . esc_attr($button_text) . '" style="width: 300px;" />';
6042 }
6043
6044 //Enable name field callback
6045 public function enable_name_field_callback() {
6046 // Load full plugin options array
6047 $all_options = get_option('mxchat_options', []);
6048 // Get the value, default to 'off'
6049 $enable_name_field = isset($all_options['enable_name_field']) ? $all_options['enable_name_field'] : 'off';
6050 // Check if it's 'on'
6051 $checked = ($enable_name_field === 'on') ? 'checked' : '';
6052 echo '<label class="toggle-switch">';
6053 echo sprintf(
6054 '<input type="checkbox" id="enable_name_field" name="enable_name_field" value="on" %s />',
6055 esc_attr($checked)
6056 );
6057 echo '<span class="slider"></span>';
6058 echo '</label>';
6059 }
6060 //Name field placeholder callback
6061 public function name_field_placeholder_callback() {
6062 $all_options = get_option('mxchat_options', []);
6063 $placeholder = isset($all_options['name_field_placeholder'])
6064 ? $all_options['name_field_placeholder']
6065 : esc_html__('Enter your name', 'mxchat');
6066
6067 echo '<input type="text" id="name_field_placeholder" name="name_field_placeholder" value="' . esc_attr($placeholder) . '" style="width: 300px;" />';
6068 }
6069
6070
6071
6072 public function mxchat_intro_message_callback() {
6073 // Load the entire 'mxchat_options' array
6074 $all_options = get_option('mxchat_options', []);
6075 // Retrieve the saved intro message or use the default
6076 $default_message = __('Hello! How can I assist you today?', 'mxchat');
6077 $saved_message = isset($all_options['intro_message']) ? $all_options['intro_message'] : $default_message;
6078 // Output the textarea with the saved value without escaping HTML
6079 ?>
6080 <textarea id="intro_message" name="intro_message" rows="5" cols="50"><?php echo $saved_message; ?></textarea>
6081 <?php
6082 }
6083
6084 public function mxchat_input_copy_callback() {
6085 // Load the entire 'mxchat_options' array
6086 $all_options = get_option('mxchat_options', []);
6087
6088 // Retrieve the saved input copy or use the default value
6089 $default_copy = __('How can I assist?', 'mxchat');
6090 $input_copy = isset($all_options['input_copy']) ? $all_options['input_copy'] : $default_copy;
6091
6092 // Output the input field with the saved value
6093 printf(
6094 '<input type="text" id="input_copy" name="input_copy" value="%s" placeholder="%s" />',
6095 esc_attr($input_copy),
6096 esc_attr__('How can I assist?', 'mxchat')
6097 );
6098 }
6099
6100
6101
6102 public function mxchat_user_message_font_color_callback() {
6103 $disabled = $this->is_activated ? '' : 'disabled';
6104 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6105
6106 echo '<div class="' . esc_attr($class) . '">';
6107 echo sprintf(
6108 '<input type="text"
6109 id="user_message_font_color"
6110 name="user_message_font_color"
6111 value="%s"
6112 class="my-color-field"
6113 data-default-color="#ffffff"
6114 %s />',
6115 isset($this->options['user_message_font_color']) ? esc_attr($this->options['user_message_font_color']) : '#ffffff',
6116 esc_attr($disabled)
6117 );
6118
6119 if (!$this->is_activated) {
6120 echo '<div class="pro-feature-overlay">';
6121 echo '<a href="https://mxchat.ai/" target="_blank">';
6122 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6123 echo '</a>';
6124 echo '</div>';
6125 }
6126 echo '</div>';
6127 }
6128
6129 public function mxchat_bot_message_bg_color_callback() {
6130 $disabled = $this->is_activated ? '' : 'disabled';
6131 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6132
6133 echo '<div class="' . esc_attr($class) . '">';
6134 echo sprintf(
6135 '<input type="text"
6136 id="bot_message_bg_color"
6137 name="bot_message_bg_color"
6138 value="%s"
6139 class="my-color-field"
6140 data-default-color="#e1e1e1"
6141 %s />',
6142 isset($this->options['bot_message_bg_color']) ? esc_attr($this->options['bot_message_bg_color']) : '#e1e1e1',
6143 esc_attr($disabled)
6144 );
6145
6146 if (!$this->is_activated) {
6147 echo '<div class="pro-feature-overlay">';
6148 echo '<a href="https://mxchat.ai/" target="_blank">';
6149 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6150 echo '</a>';
6151 echo '</div>';
6152 }
6153 echo '</div>';
6154 }
6155
6156 public function mxchat_bot_message_font_color_callback() {
6157 $disabled = $this->is_activated ? '' : 'disabled';
6158 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6159
6160 echo '<div class="' . esc_attr($class) . '">';
6161 echo sprintf(
6162 '<input type="text"
6163 id="bot_message_font_color"
6164 name="bot_message_font_color"
6165 value="%s"
6166 class="my-color-field"
6167 data-default-color="#333333"
6168 %s />',
6169 isset($this->options['bot_message_font_color']) ? esc_attr($this->options['bot_message_font_color']) : '#333333',
6170 esc_attr($disabled)
6171 );
6172
6173 if (!$this->is_activated) {
6174 echo '<div class="pro-feature-overlay">';
6175 echo '<a href="https://mxchat.ai/" target="_blank">';
6176 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6177 echo '</a>';
6178 echo '</div>';
6179 }
6180 echo '</div>';
6181 }
6182
6183 public function mxchat_live_agent_message_bg_color_callback() {
6184 $disabled = $this->is_activated ? '' : 'disabled';
6185 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6186
6187 echo '<div class="' . esc_attr($class) . '">';
6188 echo sprintf(
6189 '<input type="text"
6190 id="live_agent_message_bg_color"
6191 name="live_agent_message_bg_color"
6192 value="%s"
6193 class="my-color-field"
6194 data-default-color="#ffffff"
6195 %s />',
6196 isset($this->options['live_agent_message_bg_color']) ? esc_attr($this->options['live_agent_message_bg_color']) : '#ffffff',
6197 esc_attr($disabled)
6198 );
6199
6200 if (!$this->is_activated) {
6201 echo '<div class="pro-feature-overlay">';
6202 echo '<a href="https://mxchat.ai/" target="_blank">';
6203 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6204 echo '</a>';
6205 echo '</div>';
6206 }
6207 echo '</div>';
6208 }
6209
6210 public function mxchat_live_agent_message_font_color_callback() {
6211 $disabled = $this->is_activated ? '' : 'disabled';
6212 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6213
6214 echo '<div class="' . esc_attr($class) . '">';
6215 echo sprintf(
6216 '<input type="text"
6217 id="live_agent_message_font_color"
6218 name="live_agent_message_font_color"
6219 value="%s"
6220 class="my-color-field"
6221 data-default-color="#333333"
6222 %s />',
6223 isset($this->options['live_agent_message_font_color']) ? esc_attr($this->options['live_agent_message_font_color']) : '#333333',
6224 esc_attr($disabled)
6225 );
6226
6227 if (!$this->is_activated) {
6228 echo '<div class="pro-feature-overlay">';
6229 echo '<a href="https://mxchat.ai/" target="_blank">';
6230 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6231 echo '</a>';
6232 echo '</div>';
6233 }
6234 echo '</div>';
6235 }
6236
6237 public function mxchat_mode_indicator_bg_color_callback() {
6238 $disabled = $this->is_activated ? '' : 'disabled';
6239 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6240
6241 echo '<div class="' . esc_attr($class) . '">';
6242 echo sprintf(
6243 '<input type="text"
6244 id="mode_indicator_bg_color"
6245 name="mode_indicator_bg_color"
6246 value="%s"
6247 class="my-color-field"
6248 data-default-color="#767676"
6249 %s />',
6250 isset($this->options['mode_indicator_bg_color']) ? esc_attr($this->options['mode_indicator_bg_color']) : '#767676',
6251 esc_attr($disabled)
6252 );
6253
6254 if (!$this->is_activated) {
6255 echo '<div class="pro-feature-overlay">';
6256 echo '<a href="https://mxchat.ai/" target="_blank">';
6257 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6258 echo '</a>';
6259 echo '</div>';
6260 }
6261 echo '</div>';
6262 }
6263
6264 public function mxchat_mode_indicator_font_color_callback() {
6265 $disabled = $this->is_activated ? '' : 'disabled';
6266 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6267
6268 echo '<div class="' . esc_attr($class) . '">';
6269 echo sprintf(
6270 '<input type="text"
6271 id="mode_indicator_font_color"
6272 name="mode_indicator_font_color"
6273 value="%s"
6274 class="my-color-field"
6275 data-default-color="#ffffff"
6276 %s />',
6277 isset($this->options['mode_indicator_font_color']) ? esc_attr($this->options['mode_indicator_font_color']) : '#ffffff',
6278 esc_attr($disabled)
6279 );
6280
6281 if (!$this->is_activated) {
6282 echo '<div class="pro-feature-overlay">';
6283 echo '<a href="https://mxchat.ai/" target="_blank">';
6284 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6285 echo '</a>';
6286 echo '</div>';
6287 }
6288 echo '</div>';
6289 }
6290
6291 public function mxchat_toolbar_icon_color_callback() {
6292 $disabled = $this->is_activated ? '' : 'disabled';
6293 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6294
6295 echo '<div class="' . esc_attr($class) . '">';
6296 echo sprintf(
6297 '<input type="text"
6298 id="toolbar_icon_color"
6299 name="toolbar_icon_color"
6300 value="%s"
6301 class="my-color-field"
6302 data-default-color="#212121"
6303 %s />',
6304 isset($this->options['toolbar_icon_color']) ? esc_attr($this->options['toolbar_icon_color']) : '#212121',
6305 esc_attr($disabled)
6306 );
6307
6308 if (!$this->is_activated) {
6309 echo '<div class="pro-feature-overlay">';
6310 echo '<a href="https://mxchat.ai/" target="_blank">';
6311 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6312 echo '</a>';
6313 echo '</div>';
6314 }
6315 echo '</div>';
6316 }
6317
6318 public function mxchat_top_bar_bg_color_callback() {
6319 $disabled = $this->is_activated ? '' : 'disabled';
6320 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6321
6322 echo '<div class="' . esc_attr($class) . '">';
6323 echo sprintf(
6324 '<input type="text"
6325 id="top_bar_bg_color"
6326 name="top_bar_bg_color"
6327 value="%s"
6328 class="my-color-field"
6329 data-default-color="#00b294"
6330 %s />',
6331 isset($this->options['top_bar_bg_color']) ? esc_attr($this->options['top_bar_bg_color']) : '#00b294',
6332 esc_attr($disabled)
6333 );
6334
6335 if (!$this->is_activated) {
6336 echo '<div class="pro-feature-overlay">';
6337 echo '<a href="https://mxchat.ai/" target="_blank">';
6338 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6339 echo '</a>';
6340 echo '</div>';
6341 }
6342 echo '</div>';
6343 }
6344
6345 public function mxchat_send_button_font_color_callback() {
6346 $disabled = $this->is_activated ? '' : 'disabled';
6347 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6348
6349 echo '<div class="' . esc_attr($class) . '">';
6350 echo sprintf(
6351 '<input type="text"
6352 id="send_button_font_color"
6353 name="send_button_font_color"
6354 value="%s"
6355 class="my-color-field"
6356 data-default-color="#ffffff"
6357 %s />',
6358 isset($this->options['send_button_font_color']) ? esc_attr($this->options['send_button_font_color']) : '#ffffff',
6359 esc_attr($disabled)
6360 );
6361
6362 if (!$this->is_activated) {
6363 echo '<div class="pro-feature-overlay">';
6364 echo '<a href="https://mxchat.ai/" target="_blank">';
6365 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6366 echo '</a>';
6367 echo '</div>';
6368 }
6369 echo '</div>';
6370 }
6371
6372 public function mxchat_chatbot_background_color_callback() {
6373 $disabled = $this->is_activated ? '' : 'disabled';
6374 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6375
6376 echo '<div class="' . esc_attr($class) . '">';
6377 echo sprintf(
6378 '<input type="text"
6379 id="chatbot_background_color"
6380 name="chatbot_background_color"
6381 value="%s"
6382 class="my-color-field"
6383 data-default-color="#000000"
6384 %s />',
6385 isset($this->options['chatbot_background_color']) ? esc_attr($this->options['chatbot_background_color']) : '#000000',
6386 esc_attr($disabled)
6387 );
6388
6389 if (!$this->is_activated) {
6390 echo '<div class="pro-feature-overlay">';
6391 echo '<a href="https://mxchat.ai/" target="_blank">';
6392 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6393 echo '</a>';
6394 echo '</div>';
6395 }
6396 echo '</div>';
6397 }
6398
6399 public function mxchat_icon_color_callback() {
6400 $disabled = $this->is_activated ? '' : 'disabled';
6401 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6402
6403 echo '<div class="' . esc_attr($class) . '">';
6404 echo sprintf(
6405 '<input type="text"
6406 id="icon_color"
6407 name="icon_color"
6408 value="%s"
6409 class="my-color-field"
6410 data-default-color="#ffffff"
6411 %s />',
6412 isset($this->options['icon_color']) ? esc_attr($this->options['icon_color']) : '#ffffff',
6413 esc_attr($disabled)
6414 );
6415
6416 if (!$this->is_activated) {
6417 echo '<div class="pro-feature-overlay">';
6418 echo '<a href="https://mxchat.ai/" target="_blank">';
6419 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6420 echo '</a>';
6421 echo '</div>';
6422 }
6423 echo '</div>';
6424 }
6425
6426 public function mxchat_custom_icon_callback() {
6427 $disabled = $this->is_activated ? '' : 'disabled';
6428 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6429 $custom_icon_url = isset($this->options['custom_icon']) ? esc_url($this->options['custom_icon']) : '';
6430
6431 echo '<div class="' . esc_attr($class) . '">';
6432 echo sprintf(
6433 '<input type="url"
6434 id="custom_icon"
6435 name="custom_icon"
6436 value="%s"
6437 placeholder="%s"
6438 class="regular-text"
6439 %s />',
6440 $custom_icon_url,
6441 esc_attr__('Enter PNG URL', 'mxchat'),
6442 esc_attr($disabled)
6443 );
6444
6445 // Preview container for the icon
6446 if (!empty($custom_icon_url)) {
6447 echo '<div class="icon-preview" style="margin-top: 10px;">';
6448 echo '<img src="' . esc_url($custom_icon_url) . '" alt="' . esc_attr__('Custom Icon Preview', 'mxchat') . '" style="max-width: 48px; height: auto;" />';
6449 echo '</div>';
6450 }
6451
6452 echo '<p class="description">' . esc_html__('Upload your PNG icon and paste the URL here. Recommended size: 48x48 pixels.', 'mxchat') . '</p>';
6453
6454 if (!$this->is_activated) {
6455 echo '<div class="pro-feature-overlay">';
6456 echo '<a href="https://mxchat.ai/" target="_blank">';
6457 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6458 echo '</a>';
6459 echo '</div>';
6460 }
6461 echo '</div>';
6462 }
6463
6464 public function mxchat_title_icon_callback() {
6465 $disabled = $this->is_activated ? '' : 'disabled';
6466 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6467 // Fixed the variable reference - it was using custom_icon instead of title_icon
6468 $title_icon_url = isset($this->options['title_icon']) ? esc_url($this->options['title_icon']) : '';
6469
6470 echo '<div class="' . esc_attr($class) . '">';
6471 echo sprintf(
6472 '<input type="url"
6473 id="title_icon"
6474 name="title_icon"
6475 value="%s"
6476 placeholder="%s"
6477 class="regular-text"
6478 %s />',
6479 $title_icon_url,
6480 esc_attr__('Enter PNG URL', 'mxchat'),
6481 esc_attr($disabled)
6482 );
6483
6484 // Preview container for the icon
6485 if (!empty($title_icon_url)) {
6486 echo '<div class="icon-preview" style="margin-top: 10px;">';
6487 echo '<img src="' . esc_url($title_icon_url) . '" alt="' . esc_attr__('Title Icon Preview', 'mxchat') . '" style="max-width: 48px; height: auto;" />';
6488 echo '</div>';
6489 }
6490
6491 echo '<p class="description">' . esc_html__('Upload your PNG icon and paste the URL here. Recommended size: 48x48 pixels.', 'mxchat') . '</p>';
6492
6493 if (!$this->is_activated) {
6494 echo '<div class="pro-feature-overlay">';
6495 echo '<a href="https://mxchat.ai/" target="_blank">';
6496 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6497 echo '</a>';
6498 echo '</div>';
6499 }
6500 echo '</div>';
6501 }
6502
6503 public function mxchat_chat_input_font_color_callback() {
6504 $disabled = $this->is_activated ? '' : 'disabled';
6505 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6506
6507 echo '<div class="' . esc_attr($class) . '">';
6508 echo sprintf(
6509 '<input type="text"
6510 id="chat_input_font_color"
6511 name="chat_input_font_color"
6512 value="%s"
6513 class="my-color-field"
6514 data-default-color="#555555"
6515 %s />',
6516 isset($this->options['chat_input_font_color']) ? esc_attr($this->options['chat_input_font_color']) : '#555555',
6517 esc_attr($disabled)
6518 );
6519
6520 if (!$this->is_activated) {
6521 echo '<div class="pro-feature-overlay">';
6522 echo '<a href="https://mxchat.ai/" target="_blank">';
6523 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6524 echo '</a>';
6525 echo '</div>';
6526 }
6527 echo '</div>';
6528 }
6529
6530 public function mxchat_append_to_body_callback() {
6531 // Fetch fresh options to ensure we have the latest saved values
6532 $options = get_option('mxchat_options', array());
6533
6534 // Get value from options array, default to 'off'
6535 $append_to_body = isset($options['append_to_body']) ? $options['append_to_body'] : 'off';
6536 $checked = ($append_to_body === 'on') ? 'checked' : '';
6537
6538 // Get post type visibility settings
6539 $visibility_mode = isset($options['post_type_visibility_mode']) ? $options['post_type_visibility_mode'] : 'all';
6540 $visibility_list = isset($options['post_type_visibility_list']) ? $options['post_type_visibility_list'] : array();
6541 if (!is_array($visibility_list)) {
6542 $visibility_list = array();
6543 }
6544
6545 echo '<div class="mxchat-autosave-section">';
6546
6547 // Main toggle
6548 echo '<label class="toggle-switch">';
6549 echo sprintf(
6550 '<input type="checkbox" id="append_to_body" name="append_to_body" value="on" %s />',
6551 esc_attr($checked)
6552 );
6553 echo '<span class="slider"></span>';
6554 echo '</label>';
6555
6556 // Post Type Visibility Options (only visible when auto-display is ON)
6557 $display_style = ($append_to_body === 'on') ? '' : 'display: none;';
6558 echo '<div id="post-type-visibility-options" class="mxchat-sub-options" style="' . esc_attr($display_style) . '">';
6559
6560 echo '<div class="mxchat-post-type-visibility-header">';
6561 echo '<h4>' . esc_html__('Post Type Visibility', 'mxchat') . '</h4>';
6562 echo '</div>';
6563
6564 // Mode selector (radio buttons)
6565 echo '<div class="mxchat-visibility-mode">';
6566
6567 echo '<label class="mxchat-radio-label">';
6568 echo '<input type="radio" name="post_type_visibility_mode" value="all" ' . checked($visibility_mode, 'all', false) . ' />';
6569 echo '<span>' . esc_html__('Show on all post types', 'mxchat') . '</span>';
6570 echo '</label>';
6571
6572 echo '<label class="mxchat-radio-label">';
6573 echo '<input type="radio" name="post_type_visibility_mode" value="include" ' . checked($visibility_mode, 'include', false) . ' />';
6574 echo '<span>' . esc_html__('Only show on selected post types', 'mxchat') . '</span>';
6575 echo '</label>';
6576
6577 echo '<label class="mxchat-radio-label">';
6578 echo '<input type="radio" name="post_type_visibility_mode" value="exclude" ' . checked($visibility_mode, 'exclude', false) . ' />';
6579 echo '<span>' . esc_html__('Hide on selected post types', 'mxchat') . '</span>';
6580 echo '</label>';
6581
6582 echo '</div>';
6583
6584 // Post type checkboxes (only visible when mode is include or exclude)
6585 $list_display = ($visibility_mode !== 'all') ? '' : 'display: none;';
6586 echo '<div id="post-type-list" class="mxchat-post-type-list" style="' . esc_attr($list_display) . '">';
6587
6588 // Get all public post types
6589 $post_types = get_post_types(array('public' => true), 'objects');
6590
6591 foreach ($post_types as $post_type) {
6592 // Skip attachments
6593 if ($post_type->name === 'attachment') {
6594 continue;
6595 }
6596
6597 $is_checked = in_array($post_type->name, $visibility_list) ? 'checked' : '';
6598
6599 echo '<label class="mxchat-checkbox-label">';
6600 echo '<input type="checkbox" name="post_type_visibility_list[]" value="' . esc_attr($post_type->name) . '" ' . $is_checked . ' />';
6601 echo '<span>' . esc_html($post_type->label) . '</span>';
6602 echo '</label>';
6603 }
6604
6605 echo '</div>'; // End post-type-list
6606 echo '</div>'; // End post-type-visibility-options
6607 echo '</div>'; // End mxchat-autosave-section
6608 }
6609
6610 public function mxchat_frontend_debugger_callback() {
6611 $show_debugger = isset($this->options['show_frontend_debugger']) ? $this->options['show_frontend_debugger'] : 'on';
6612 $checked = ($show_debugger === 'on') ? 'checked' : '';
6613 echo '<label class="toggle-switch">';
6614 echo sprintf(
6615 '<input type="checkbox" id="show_frontend_debugger" name="show_frontend_debugger" value="on" %s />',
6616 esc_attr($checked)
6617 );
6618 echo '<span class="slider"></span>';
6619 echo '</label>';
6620 }
6621
6622 public function mxchat_contextual_awareness_callback() {
6623 // Get value from options array, default to 'off'
6624 $contextual_awareness = isset($this->options['contextual_awareness_toggle']) ? $this->options['contextual_awareness_toggle'] : 'off';
6625 $checked = ($contextual_awareness === 'on') ? 'checked' : '';
6626 echo '<label class="toggle-switch">';
6627 echo sprintf(
6628 '<input type="checkbox" id="contextual_awareness_toggle" name="contextual_awareness_toggle" value="on" %s />',
6629 esc_attr($checked)
6630 );
6631 echo '<span class="slider"></span>';
6632 echo '</label>';
6633 }
6634
6635 public function mxchat_citation_links_toggle_callback() {
6636 // Get value from options array, default to 'on' (enabled by default)
6637 $citation_links = isset($this->options['citation_links_toggle']) ? $this->options['citation_links_toggle'] : 'on';
6638 $checked = ($citation_links === 'on') ? 'checked' : '';
6639 echo '<label class="toggle-switch">';
6640 echo sprintf(
6641 '<input type="checkbox" id="citation_links_toggle" name="citation_links_toggle" value="on" %s />',
6642 esc_attr($checked)
6643 );
6644 echo '<span class="slider"></span>';
6645 echo '</label>';
6646 }
6647
6648 public function mxchat_privacy_toggle_callback() {
6649 // Load from mxchat_options array
6650 $options = get_option('mxchat_options', []);
6651
6652 // Get privacy toggle value with fallback
6653 $privacy_toggle = isset($options['privacy_toggle']) ? $options['privacy_toggle'] : 'off';
6654 $checked = ($privacy_toggle === 'on') ? 'checked' : '';
6655
6656 // Get privacy text with fallback
6657 $privacy_text = isset($options['privacy_text'])
6658 ? $options['privacy_text']
6659 : __('By chatting, you agree to our <a href="https://example.com/privacy-policy" target="_blank">privacy policy</a>.', 'mxchat');
6660
6661 // Output the toggle switch
6662 echo '<label class="toggle-switch">';
6663 echo sprintf(
6664 '<input type="checkbox" id="privacy_toggle" name="privacy_toggle" value="on" %s />',
6665 esc_attr($checked)
6666 );
6667 echo '<span class="slider"></span>';
6668 echo '</label>';
6669
6670 // Output the custom text input field
6671 echo sprintf(
6672 '<textarea id="privacy_text" name="privacy_text" rows="5" cols="50" class="regular-text">%s</textarea>',
6673 esc_textarea($privacy_text)
6674 );
6675 }
6676
6677
6678 public function mxchat_complianz_toggle_callback() {
6679 // Load from mxchat_options array
6680 $options = get_option('mxchat_options', []);
6681
6682 // Get complianz toggle value with fallback
6683 $complianz_toggle = isset($options['complianz_toggle']) ? $options['complianz_toggle'] : 'off';
6684 $checked = ($complianz_toggle === 'on') ? 'checked' : '';
6685
6686 // Output the toggle switch
6687 echo '<label class="toggle-switch">';
6688 echo sprintf(
6689 '<input type="checkbox" id="complianz_toggle" name="complianz_toggle" value="on" %s />',
6690 esc_attr($checked)
6691 );
6692 echo '<span class="slider"></span>';
6693 echo '</label>';
6694 }
6695
6696 public function mxchat_link_target_toggle_callback() {
6697 // Load from mxchat_options array
6698 $options = get_option('mxchat_options', []);
6699
6700 // Get link target toggle value with fallback
6701 $link_target_toggle = isset($options['link_target_toggle']) ? $options['link_target_toggle'] : 'off';
6702 $checked = ($link_target_toggle === 'on') ? 'checked' : '';
6703
6704 // Output the toggle switch
6705 echo '<label class="toggle-switch">';
6706 echo sprintf(
6707 '<input type="checkbox" id="link_target_toggle" name="link_target_toggle" value="on" %s />',
6708 esc_attr($checked)
6709 );
6710 echo '<span class="slider"></span>';
6711 echo '</label>';
6712 }
6713
6714 public function mxchat_chat_persistence_toggle_callback() {
6715 // Load from mxchat_options array
6716 $options = get_option('mxchat_options', []);
6717
6718 // Get chat persistence toggle value with fallback
6719 $chat_persistence_toggle = isset($options['chat_persistence_toggle']) ? $options['chat_persistence_toggle'] : 'off';
6720 $checked = ($chat_persistence_toggle === 'on') ? 'checked' : '';
6721
6722 // Output the toggle switch
6723 echo '<label class="toggle-switch">';
6724 echo sprintf(
6725 '<input type="checkbox" id="chat_persistence_toggle" name="chat_persistence_toggle" value="on" %s />',
6726 esc_attr($checked)
6727 );
6728 echo '<span class="slider"></span>';
6729 echo '</label>';
6730 }
6731
6732 public function mxchat_popular_question_1_callback() {
6733 // Load the full plugin options array
6734 $all_options = get_option('mxchat_options', []);
6735
6736 // Retrieve the specific option for popular_question_1
6737 $popular_question_1 = isset($all_options['popular_question_1']) ? $all_options['popular_question_1'] : '';
6738
6739 // Render the input field
6740 printf(
6741 '<input type="text" id="popular_question_1" name="popular_question_1" value="%s" placeholder="%s" class="regular-text" />',
6742 esc_attr($popular_question_1),
6743 esc_attr__('Enter Quick Question 1', 'mxchat')
6744 );
6745 }
6746
6747
6748 public function mxchat_popular_question_2_callback() {
6749 // Load the full plugin options array
6750 $all_options = get_option('mxchat_options', []);
6751
6752 // Retrieve the specific option for popular_question_2
6753 $popular_question_2 = isset($all_options['popular_question_2']) ? $all_options['popular_question_2'] : '';
6754
6755 // Render the input field
6756 printf(
6757 '<input type="text" id="popular_question_2" name="popular_question_2" value="%s" placeholder="%s" class="regular-text" />',
6758 esc_attr($popular_question_2),
6759 esc_attr__('Enter Quick Question 2', 'mxchat')
6760 );
6761 }
6762
6763
6764 public function mxchat_popular_question_3_callback() {
6765 // Load the full plugin options array
6766 $all_options = get_option('mxchat_options', []);
6767
6768 // Retrieve the specific option for popular_question_3
6769 $popular_question_3 = isset($all_options['popular_question_3']) ? $all_options['popular_question_3'] : '';
6770
6771 // Render the input field
6772 printf(
6773 '<input type="text" id="popular_question_3" name="popular_question_3" value="%s" placeholder="%s" class="regular-text" />',
6774 esc_attr($popular_question_3),
6775 esc_attr(__('Enter Quick Question 3', 'mxchat'))
6776 );
6777 }
6778
6779 public function mxchat_additional_popular_questions_callback() {
6780 $options = get_option('mxchat_options', []);
6781 $additional_questions = isset($options['additional_popular_questions'])
6782 ? $options['additional_popular_questions']
6783 : get_option('additional_popular_questions', array());
6784
6785 echo '<div id="mxchat-additional-questions-container">';
6786 if (!empty($additional_questions)) {
6787 foreach ($additional_questions as $index => $question) {
6788 printf(
6789 '<div class="mxchat-question-row">
6790 <input type="text" name="additional_popular_questions[]"
6791 value="%s"
6792 placeholder="%s"
6793 class="regular-text mxchat-question-input"
6794 data-question-index="%d" />
6795 <button type="button" class="button mxchat-remove-question"
6796 aria-label="%s">%s</button>
6797 </div>',
6798 esc_attr($question),
6799 esc_attr(sprintf(__('Enter Additional Quick Question %d', 'mxchat'), $index + 4)),
6800 $index,
6801 esc_attr(__('Remove question', 'mxchat')),
6802 esc_html__('Remove', 'mxchat')
6803 );
6804 }
6805 } else {
6806 printf(
6807 '<div class="mxchat-question-row">
6808 <input type="text" name="additional_popular_questions[]"
6809 value=""
6810 placeholder="%s"
6811 class="regular-text mxchat-question-input"
6812 data-question-index="0" />
6813 <button type="button" class="button mxchat-remove-question"
6814 aria-label="%s">%s</button>
6815 </div>',
6816 esc_attr(__('Enter Additional Quick Question 4', 'mxchat')),
6817 esc_attr(__('Remove question', 'mxchat')),
6818 esc_html__('Remove', 'mxchat')
6819 );
6820 }
6821 echo '</div>';
6822 printf(
6823 '<button type="button" class="button mxchat-add-question" aria-label="%s">%s</button>',
6824 esc_attr(__('Add question', 'mxchat')),
6825 esc_html__('Add Question', 'mxchat')
6826 );
6827 }
6828
6829 public function mxchat_brave_api_key_callback() {
6830 $brave_api_key = isset($this->options['brave_api_key']) ? esc_attr($this->options['brave_api_key']) : '';
6831 $nonce = wp_create_nonce('mxchat_autosave_nonce');
6832
6833 echo '<div class="api-key-wrapper">';
6834 echo sprintf(
6835 '<input type="password" id="brave_api_key" name="brave_api_key" value="%s" class="regular-text mxchat-autosave-field" data-nonce="%s" />',
6836 $brave_api_key,
6837 $nonce
6838 );
6839 echo '<button type="button" id="toggleBraveApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
6840 echo '</div>';
6841 echo '<p class="description">' . __('Required for Brave Search integration. Get your API key from Brave Search API.', 'mxchat') . '</p>';
6842 }
6843
6844 public function mxchat_brave_image_count_callback() {
6845 $brave_image_count = isset($this->options['brave_image_count'])
6846 ? intval($this->options['brave_image_count'])
6847 : 4;
6848 $nonce = wp_create_nonce('mxchat_autosave_nonce');
6849
6850 echo '<div class="mxchat-field-wrapper">';
6851 echo sprintf(
6852 '<input type="number" id="brave_image_count" name="brave_image_count"
6853 value="%d" min="1" max="6" class="small-text mxchat-autosave-field" data-nonce="%s" />',
6854 $brave_image_count,
6855 $nonce
6856 );
6857 echo '</div>';
6858 echo '<p class="description">' . __('Select the number of images to return (1-6).', 'mxchat') . '</p>';
6859 }
6860
6861 public function mxchat_brave_safe_search_callback() {
6862 $brave_safe_search = isset($this->options['brave_safe_search'])
6863 ? esc_attr($this->options['brave_safe_search'])
6864 : 'strict';
6865 $nonce = wp_create_nonce('mxchat_autosave_nonce');
6866
6867 echo '<div class="mxchat-field-wrapper">';
6868 echo '<select id="brave_safe_search" name="brave_safe_search" class="mxchat-autosave-field" data-nonce="' . $nonce . '">';
6869 echo sprintf(
6870 '<option value="strict" %s>%s</option>',
6871 selected($brave_safe_search, 'strict', false),
6872 __('Strict', 'mxchat')
6873 );
6874 echo sprintf(
6875 '<option value="off" %s>%s</option>',
6876 selected($brave_safe_search, 'off', false),
6877 __('Off', 'mxchat')
6878 );
6879 echo '</select>';
6880 echo '</div>';
6881 echo '<p class="description">' .
6882 esc_html__('Set the Safe Search level for image searches. Brave Search only supports "Strict" and "Off" options.', 'mxchat') .
6883 '</p>';
6884 }
6885
6886 public function mxchat_brave_news_count_callback() {
6887 $brave_news_count = isset($this->options['brave_news_count'])
6888 ? intval($this->options['brave_news_count'])
6889 : 3;
6890 $nonce = wp_create_nonce('mxchat_autosave_nonce');
6891
6892 echo '<div class="mxchat-field-wrapper">';
6893 echo sprintf(
6894 '<input type="number" id="brave_news_count" name="brave_news_count"
6895 value="%d" min="1" max="10" class="small-text mxchat-autosave-field" data-nonce="%s" />',
6896 $brave_news_count,
6897 $nonce
6898 );
6899 echo '</div>';
6900 echo '<p class="description">' . esc_html__('Select the number of news articles to retrieve (1-10).', 'mxchat') . '</p>';
6901 }
6902
6903 public function mxchat_brave_country_callback() {
6904 $brave_country = isset($this->options['brave_country'])
6905 ? esc_attr($this->options['brave_country'])
6906 : 'us';
6907 $nonce = wp_create_nonce('mxchat_autosave_nonce');
6908
6909 echo '<div class="mxchat-field-wrapper">';
6910 echo sprintf(
6911 '<input type="text" id="brave_country" name="brave_country"
6912 value="%s" maxlength="2" class="small-text mxchat-autosave-field" data-nonce="%s" />',
6913 $brave_country,
6914 $nonce
6915 );
6916 echo '</div>';
6917 echo '<p class="description">' . esc_html__('Enter the country code (e.g., "us" for United States).', 'mxchat') . '</p>';
6918 }
6919
6920 public function mxchat_brave_language_callback() {
6921 $brave_language = isset($this->options['brave_language'])
6922 ? esc_attr($this->options['brave_language'])
6923 : 'en';
6924 $nonce = wp_create_nonce('mxchat_autosave_nonce');
6925
6926 echo '<div class="mxchat-field-wrapper">';
6927 echo sprintf(
6928 '<input type="text" id="brave_language" name="brave_language"
6929 value="%s" maxlength="2" class="small-text mxchat-autosave-field" data-nonce="%s" />',
6930 $brave_language,
6931 $nonce
6932 );
6933 echo '</div>';
6934 echo '<p class="description">' . esc_html__('Enter the language code (e.g., "en" for English).', 'mxchat') . '</p>';
6935 }
6936
6937
6938
6939
6940
6941 // Section Callback
6942 public function mxchat_pdf_intent_section_callback() {
6943 echo '<p>' . esc_html__('Configure the intent settings for the Chat with PDF feature.', 'mxchat') . '</p>';
6944 }
6945
6946 public function mxchat_chat_toolbar_toggle_callback() {
6947 // Get chat toolbar toggle value with fallback
6948 $chat_toolbar_toggle = isset($this->options['chat_toolbar_toggle']) ? $this->options['chat_toolbar_toggle'] : 'off';
6949 $checked = ($chat_toolbar_toggle === 'on') ? 'checked' : '';
6950
6951 // Output the toggle switch
6952 echo '<label class="toggle-switch">';
6953 echo sprintf(
6954 '<input type="checkbox" id="chat_toolbar_toggle" name="chat_toolbar_toggle" value="on" %s />',
6955 esc_attr($checked)
6956 );
6957 echo '<span class="slider"></span>';
6958 echo '</label>';
6959
6960 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>';
6961 }
6962
6963 /**
6964 * Callback for PDF upload button toggle setting
6965 */
6966 public function mxchat_show_pdf_upload_button_callback() {
6967 // Get toggle value with fallback
6968 $show_pdf_button = isset($this->options['show_pdf_upload_button']) ? $this->options['show_pdf_upload_button'] : 'on';
6969 $checked = ($show_pdf_button === 'on') ? 'checked' : '';
6970
6971 // Output the toggle switch
6972 echo '<label class="toggle-switch">';
6973 echo sprintf(
6974 '<input type="checkbox" id="show_pdf_upload_button" name="show_pdf_upload_button" value="on" %s />',
6975 esc_attr($checked)
6976 );
6977 echo '<span class="slider"></span>';
6978 echo '</label>';
6979
6980 echo '<p class="description">' . esc_html__('Enable to show the PDF upload button in the chatbot toolbar. Disable to hide it.', 'mxchat') . '</p>';
6981 }
6982
6983 /**
6984 * Callback for Word upload button toggle setting
6985 */
6986 public function mxchat_show_word_upload_button_callback() {
6987 // Get toggle value with fallback
6988 $show_word_button = isset($this->options['show_word_upload_button']) ? $this->options['show_word_upload_button'] : 'on';
6989 $checked = ($show_word_button === 'on') ? 'checked' : '';
6990
6991 // Output the toggle switch
6992 echo '<label class="toggle-switch">';
6993 echo sprintf(
6994 '<input type="checkbox" id="show_word_upload_button" name="show_word_upload_button" value="on" %s />',
6995 esc_attr($checked)
6996 );
6997 echo '<span class="slider"></span>';
6998 echo '</label>';
6999
7000 echo '<p class="description">' . esc_html__('Enable to show the Word document upload button in the chatbot toolbar. Disable to hide it.', 'mxchat') . '</p>';
7001 }
7002
7003 public function mxchat_pdf_intent_trigger_text_callback() {
7004 $default_text = __("Please provide the URL to the PDF you'd like to discuss.", 'mxchat');
7005
7006 echo sprintf(
7007 '<textarea id="pdf_intent_trigger_text"
7008 name="pdf_intent_trigger_text"
7009 rows="3"
7010 cols="50"
7011 placeholder="%s">%s</textarea>',
7012 esc_attr__('Enter trigger text', 'mxchat'),
7013 isset($this->options['pdf_intent_trigger_text'])
7014 ? esc_textarea($this->options['pdf_intent_trigger_text'])
7015 : esc_textarea($default_text)
7016 );
7017 echo '<p class="description">' . esc_html__('Text displayed when the intent is triggered.', 'mxchat') . '</p>';
7018 }
7019
7020 public function mxchat_pdf_intent_success_text_callback() {
7021 $default_text = __("I've processed the PDF. What questions do you have about it?", 'mxchat');
7022
7023 echo sprintf(
7024 '<textarea id="pdf_intent_success_text"
7025 name="pdf_intent_success_text"
7026 rows="3"
7027 cols="50"
7028 placeholder="%s">%s</textarea>',
7029 esc_attr__('Enter success text', 'mxchat'),
7030 isset($this->options['pdf_intent_success_text'])
7031 ? esc_textarea($this->options['pdf_intent_success_text'])
7032 : esc_textarea($default_text)
7033 );
7034 echo '<p class="description">' . esc_html__('Text displayed when the intent is successful.', 'mxchat') . '</p>';
7035 }
7036
7037 public function mxchat_pdf_intent_error_text_callback() {
7038 $default_text = __("Sorry, I couldn't process the PDF. Please ensure it's a valid file.", 'mxchat');
7039
7040 echo sprintf(
7041 '<textarea id="pdf_intent_error_text"
7042 name="pdf_intent_error_text"
7043 rows="3"
7044 cols="50"
7045 placeholder="%s">%s</textarea>',
7046 esc_attr__('Enter error text', 'mxchat'),
7047 isset($this->options['pdf_intent_error_text'])
7048 ? esc_textarea($this->options['pdf_intent_error_text'])
7049 : esc_textarea($default_text)
7050 );
7051 echo '<p class="description">' . esc_html__('Text displayed when an error occurs during the intent.', 'mxchat') . '</p>';
7052 }
7053
7054 public function mxchat_pdf_max_pages_callback() {
7055 $max_pages = isset($this->options['pdf_max_pages']) ? intval($this->options['pdf_max_pages']) : 69;
7056
7057 echo sprintf(
7058 '<input type="range"
7059 id="pdf_max_pages"
7060 name="pdf_max_pages"
7061 min="1"
7062 max="69"
7063 value="%d"
7064 class="range-slider" />',
7065 esc_attr($max_pages)
7066 );
7067 echo '<span id="pdf_max_pages_output">' . esc_html($max_pages) . '</span>';
7068 echo '<p class="description">' . esc_html__('Set the maximum number of document pages users can upload for processing. (1-69 pages)', 'mxchat') . '</p>';
7069 }
7070
7071 public function mxchat_live_agent_status_callback() {
7072 // Always get fresh options instead of using cached $this->options
7073 $fresh_options = get_option('mxchat_options');
7074 $status = isset($fresh_options['live_agent_status']) ? $fresh_options['live_agent_status'] : 'off';
7075
7076 echo '<label class="toggle-switch">';
7077 echo sprintf(
7078 '<input type="checkbox" id="live_agent_status" name="live_agent_status" value="on" %s />',
7079 checked($status, 'on', false)
7080 );
7081 echo '<span class="slider"></span>';
7082 echo '</label>';
7083 echo '<label for="live_agent_status" class="mxchat-status-label">';
7084 echo '<span class="status-text">' . ($status === 'on' ? esc_html__('Online', 'mxchat') : esc_html__('Offline', 'mxchat')) . '</span>';
7085 echo '</label>';
7086 }
7087
7088 public function mxchat_live_agent_away_message_callback() {
7089 $message = isset($this->options['live_agent_away_message'])
7090 ? $this->options['live_agent_away_message']
7091 : __('Sorry, live agents are currently unavailable. I can continue helping you as an AI assistant.', 'mxchat');
7092
7093 printf(
7094 '<textarea id="live_agent_away_message" name="live_agent_away_message" rows="3" cols="50">%s</textarea>',
7095 esc_textarea($message)
7096 );
7097 echo '<p class="description">' . esc_html__('Message shown when live agents are offline.', 'mxchat') . '</p>';
7098 }
7099
7100 public function mxchat_live_agent_notification_message_callback() {
7101 $message = isset($this->options['live_agent_notification_message'])
7102 ? $this->options['live_agent_notification_message']
7103 : __('Live agent has been notified.', 'mxchat');
7104
7105 printf(
7106 '<textarea id="live_agent_notification_message" name="live_agent_notification_message" rows="3" cols="50">%s</textarea>',
7107 esc_textarea($message)
7108 );
7109 echo '<p class="description">' . esc_html__('Message shown when live transfer activated.', 'mxchat') . '</p>';
7110 }
7111
7112 public function mxchat_live_agent_webhook_url_callback() {
7113 $webhook_url = isset($this->options['live_agent_webhook_url'])
7114 ? esc_url($this->options['live_agent_webhook_url'])
7115 : esc_url(get_option('live_agent_webhook_url', ''));
7116
7117 printf(
7118 '<input type="password" id="live_agent_webhook_url" name="live_agent_webhook_url" value="%s" class="regular-text" />',
7119 $webhook_url
7120 );
7121 echo '<button type="button" id="toggleWebhookUrlVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
7122 echo '<p class="description">' . esc_html__('Enter your Slack webhook URL for live agent notifications.', 'mxchat') . '</p>';
7123 }
7124
7125 public function mxchat_live_agent_secret_key_callback() {
7126 printf(
7127 '<input type="password" id="live_agent_secret_key" name="live_agent_secret_key" value="%s" class="regular-text" />',
7128 isset($this->options['live_agent_secret_key']) ? esc_attr($this->options['live_agent_secret_key']) : ''
7129 );
7130 echo '<button type="button" id="toggleSecretKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
7131 echo '<p class="description">' . esc_html__('Secret key for validating Slack requests. Keep this secure.', 'mxchat') . '</p>';
7132 }
7133
7134 public function mxchat_live_agent_bot_token_callback() {
7135 printf(
7136 '<input type="password" id="live_agent_bot_token" name="live_agent_bot_token" value="%s" class="regular-text" />',
7137 isset($this->options['live_agent_bot_token']) ? esc_attr($this->options['live_agent_bot_token']) : ''
7138 );
7139 echo '<button type="button" id="toggleBotTokenVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
7140 echo '<p class="description">' . esc_html__('Your Slack Bot OAuth Token (starts with xoxb-). Keep this secure.', 'mxchat') . '</p>';
7141 }
7142
7143 public function mxchat_live_agent_user_ids_callback() {
7144 $user_ids = isset($this->options['live_agent_user_ids'])
7145 ? esc_textarea($this->options['live_agent_user_ids'])
7146 : '';
7147
7148 printf(
7149 '<textarea id="live_agent_user_ids" name="live_agent_user_ids" rows="4" class="large-text">%s</textarea>',
7150 $user_ids
7151 );
7152 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>';
7153 }
7154
7155 /**
7156 * Telegram Integration Callbacks
7157 */
7158 public function mxchat_telegram_section_callback() {
7159 echo '<p>' . esc_html__('Configure Telegram integration for live agent support.', 'mxchat') . '</p>';
7160 }
7161
7162 public function mxchat_telegram_status_callback() {
7163 $fresh_options = get_option('mxchat_options');
7164 $status = isset($fresh_options['telegram_status']) ? $fresh_options['telegram_status'] : 'off';
7165
7166 echo '<label class="toggle-switch">';
7167 echo sprintf(
7168 '<input type="checkbox" id="telegram_status" name="telegram_status" value="on" %s />',
7169 checked($status, 'on', false)
7170 );
7171 echo '<span class="slider"></span>';
7172 echo '</label>';
7173 echo '<label for="telegram_status" class="mxchat-status-label">';
7174 echo '<span class="status-text">' . ($status === 'on' ? esc_html__('Online', 'mxchat') : esc_html__('Offline', 'mxchat')) . '</span>';
7175 echo '</label>';
7176 }
7177
7178 public function mxchat_telegram_notification_message_callback() {
7179 $message = isset($this->options['telegram_notification_message'])
7180 ? $this->options['telegram_notification_message']
7181 : __("I've notified a support agent. Please allow a moment for them to respond.", 'mxchat');
7182
7183 printf(
7184 '<textarea id="telegram_notification_message" name="telegram_notification_message" rows="3" cols="50">%s</textarea>',
7185 esc_textarea($message)
7186 );
7187 echo '<p class="description">' . esc_html__('Message shown when live transfer activated.', 'mxchat') . '</p>';
7188 }
7189
7190 public function mxchat_telegram_away_message_callback() {
7191 $message = isset($this->options['telegram_away_message'])
7192 ? $this->options['telegram_away_message']
7193 : __('Sorry, live agents are currently unavailable. I can continue helping you as an AI assistant.', 'mxchat');
7194
7195 printf(
7196 '<textarea id="telegram_away_message" name="telegram_away_message" rows="3" cols="50">%s</textarea>',
7197 esc_textarea($message)
7198 );
7199 echo '<p class="description">' . esc_html__('Message shown when live agents are offline.', 'mxchat') . '</p>';
7200 }
7201
7202 public function mxchat_telegram_bot_token_callback() {
7203 printf(
7204 '<input type="password" id="telegram_bot_token" name="telegram_bot_token" value="%s" class="regular-text" />',
7205 isset($this->options['telegram_bot_token']) ? esc_attr($this->options['telegram_bot_token']) : ''
7206 );
7207 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>';
7208 echo '<p class="description">' . esc_html__('Your Telegram Bot Token from @BotFather (e.g., 123456789:ABCdefGhIJKlmNoPQRsTUVwxyZ).', 'mxchat') . '</p>';
7209 }
7210
7211 public function mxchat_telegram_group_id_callback() {
7212 printf(
7213 '<input type="text" id="telegram_group_id" name="telegram_group_id" value="%s" class="regular-text" />',
7214 isset($this->options['telegram_group_id']) ? esc_attr($this->options['telegram_group_id']) : ''
7215 );
7216 echo '<p class="description">' . esc_html__('Your Telegram supergroup ID (starts with -100). The group must have forum topics enabled.', 'mxchat') . '</p>';
7217 }
7218
7219 public function mxchat_telegram_webhook_secret_callback() {
7220 $secret = isset($this->options['telegram_webhook_secret']) ? $this->options['telegram_webhook_secret'] : '';
7221
7222 // Auto-generate secret if empty
7223 if (empty($secret)) {
7224 $secret = wp_generate_password(64, false, false);
7225 $options = get_option('mxchat_options', []);
7226 $options['telegram_webhook_secret'] = $secret;
7227 update_option('mxchat_options', $options);
7228 $this->options['telegram_webhook_secret'] = $secret;
7229 }
7230
7231 printf(
7232 '<input type="password" id="telegram_webhook_secret" name="telegram_webhook_secret" value="%s" class="regular-text" />',
7233 esc_attr($secret)
7234 );
7235 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>';
7236 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>';
7237 }
7238
7239 public function mxchat_similarity_threshold_callback() {
7240 // Load from mxchat_options array
7241 $options = get_option('mxchat_options', []);
7242
7243 // Get value from options array with default of 80
7244 $threshold = isset($options['similarity_threshold']) ? $options['similarity_threshold'] : 35;
7245
7246 echo '<div class="slider-container">';
7247 echo sprintf(
7248 '<input type="range"
7249 id="similarity_threshold"
7250 name="similarity_threshold"
7251 min="20"
7252 max="85"
7253 step="1"
7254 value="%s"
7255 class="range-slider" />',
7256 esc_attr($threshold)
7257 );
7258 echo sprintf(
7259 '<span id="threshold_value" class="range-value">%s</span>',
7260 esc_html($threshold)
7261 );
7262 echo '</div>';
7263 echo '<p class="description">';
7264 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');
7265 echo '</p>';
7266 }
7267
7268 public function mxchat_enqueue_admin_assets() {
7269 // Get plugin version
7270 $version = MXCHAT_VERSION;
7271
7272 // Use file modification time for development (remove in production)
7273 if (defined('WP_DEBUG') && WP_DEBUG) {
7274 $version = filemtime(plugin_dir_path(__FILE__) . '../mxchat-basic.php');
7275 }
7276
7277 $current_page = isset($_GET['page']) ? $_GET['page'] : '';
7278 $plugin_url = plugin_dir_url(__FILE__) . '../';
7279
7280 // Only load on MxChat pages
7281 if (strpos($current_page, 'mxchat') === false) {
7282 return;
7283 }
7284
7285 // Always load these on all MxChat pages
7286 $this->enqueue_core_admin_assets($plugin_url, $version);
7287 $this->enqueue_page_specific_assets($current_page, $plugin_url, $version);
7288 $this->localize_admin_scripts($current_page);
7289 }
7290 private function enqueue_core_admin_assets($plugin_url, $version) {
7291 // Core admin styles
7292 wp_enqueue_style('mxchat-admin-css', $plugin_url . 'css/admin-style.css', array(), $version);
7293 wp_enqueue_style('mxchat-knowledge-css', $plugin_url . 'css/knowledge-style.css', array(), $version);
7294
7295 // New sidebar navigation styles (for main settings page)
7296 wp_enqueue_style('mxchat-admin-sidebar-css', $plugin_url . 'css/admin-sidebar.css', array(), $version);
7297
7298 // Core admin scripts
7299 wp_enqueue_script('mxchat-admin-js', $plugin_url . 'js/mxchat-admin.js', array('jquery'), $version, true);
7300 }
7301 private function enqueue_page_specific_assets($current_page, $plugin_url, $version) {
7302 switch ($current_page) {
7303 case 'mxchat-prompts':
7304 // Knowledge processing page assets
7305 wp_enqueue_style('mxchat-content-selector-css', $plugin_url . 'css/content-selector.css', array(), $version);
7306 wp_enqueue_script('mxchat-content-selector-js', $plugin_url . 'js/content-selector.js', array('jquery'), $version, true);
7307 // Add the knowledge processing script (common script needed for WordPress dismiss functionality)
7308 wp_enqueue_script('mxchat-knowledge-processing', $plugin_url . 'js/knowledge-processing.js', array('jquery', 'common'), $version, true);
7309 break;
7310
7311 case 'mxchat-transcripts':
7312 // Load admin sidebar CSS first (shared styles for sidebar navigation)
7313 wp_enqueue_style('mxchat-admin-sidebar-css', $plugin_url . 'css/admin-sidebar.css', array(), $version);
7314 // Load transcripts-specific styles
7315 wp_enqueue_style('mxchat-chat-transcripts-css', $plugin_url . 'css/chat-transcripts.css', array('mxchat-admin-sidebar-css'), $version);
7316 wp_enqueue_script('mxchat-transcripts-js', $plugin_url . 'js/mxchat_transcripts.js', array('jquery'), $version, true);
7317 break;
7318
7319 case 'mxchat-actions':
7320 // Load admin sidebar CSS first (shared styles for sidebar navigation)
7321 wp_enqueue_style('mxchat-admin-sidebar-css', $plugin_url . 'css/admin-sidebar.css', array(), $version);
7322 // Load actions-specific styles
7323 wp_enqueue_style('mxchat-actions-css', $plugin_url . 'css/actions.css', array('mxchat-admin-sidebar-css'), $version);
7324 wp_enqueue_script('mxchat-actions-js', $plugin_url . 'js/mxchat_actions.js', array('jquery'), $version, true);
7325
7326 // Localize script data for actions page
7327 $is_activated = get_option('mxchat_pro_license_status') === 'active';
7328 wp_localize_script('mxchat-actions-js', 'mxchActionsData', array(
7329 'ajaxUrl' => admin_url('admin-ajax.php'),
7330 'nonce' => wp_create_nonce('mxchat_actions_nonce'),
7331 'addNonce' => wp_create_nonce('mxchat_add_intent_nonce'),
7332 'editNonce' => wp_create_nonce('mxchat_edit_intent'),
7333 'deleteNonce' => wp_create_nonce('mxchat_delete_intent_nonce'),
7334 'toggleNonce' => wp_create_nonce('mxchat_actions_nonce'),
7335 'isActivated' => $is_activated,
7336 'i18n' => array(
7337 'confirmDelete' => __('Are you sure you want to delete this action?', 'mxchat'),
7338 'confirmBulkDelete' => __('Are you sure you want to delete the selected actions?', 'mxchat'),
7339 'saving' => __('Saving...', 'mxchat'),
7340 'saved' => __('Saved successfully!', 'mxchat'),
7341 'error' => __('An error occurred. Please try again.', 'mxchat'),
7342 'proRequired' => __('This feature requires MxChat Pro.', 'mxchat'),
7343 'addonRequired' => __('This feature requires an add-on.', 'mxchat')
7344 )
7345 ));
7346 break;
7347
7348 case 'mxchat-activation':
7349 // Load admin sidebar CSS first (shared styles for sidebar navigation)
7350 wp_enqueue_style('mxchat-admin-sidebar-css', $plugin_url . 'css/admin-sidebar.css', array(), $version);
7351 // Load pro page-specific styles
7352 wp_enqueue_style('mxchat-pro-css', $plugin_url . 'css/admin-pro.css', array('mxchat-admin-sidebar-css'), $version);
7353 // Load pro page JavaScript
7354 wp_enqueue_script('mxchat-pro-js', $plugin_url . 'js/mxchat_pro.js', array('jquery'), $version, true);
7355 // Load activation script for license activation/deactivation
7356 wp_enqueue_script('mxchat-activation-js', $plugin_url . 'js/activation-script.js', array('jquery'), $version, true);
7357 break;
7358 default:
7359 wp_enqueue_script(
7360 'mxchat-test-streaming-js',
7361 $plugin_url . 'js/mxchat-test-streaming.js',
7362 ['jquery'],
7363 $version,
7364 true
7365 );
7366
7367 wp_localize_script('mxchat-test-streaming-js', 'mxchatTestStreamingAjax', [
7368 'ajax_url' => admin_url('admin-ajax.php'),
7369 'nonce' => wp_create_nonce('mxchat_test_streaming_nonce'),
7370 'settings_nonce' => wp_create_nonce('mxchat_save_setting_nonce')
7371 ]);
7372 break;
7373 }
7374 }
7375 private function localize_admin_scripts($current_page) {
7376 // Base localization data for main admin script
7377 $base_data = array(
7378 'ajax_url' => admin_url('admin-ajax.php'),
7379 'nonce' => wp_create_nonce('mxchat_admin_nonce'),
7380 'admin_url' => admin_url(),
7381 'license_nonce' => wp_create_nonce('mxchat_activate_license_nonce'),
7382 'inline_edit_nonce' => wp_create_nonce('mxchat_save_inline_nonce'),
7383 'setting_nonce' => wp_create_nonce('mxchat_save_setting_nonce'),
7384 'export_nonce' => wp_create_nonce('mxchat_export_transcripts'),
7385 'actions_nonce' => wp_create_nonce('mxchat_actions_nonce'),
7386 'add_intent_nonce' => wp_create_nonce('mxchat_add_intent_nonce'),
7387 'edit_intent_nonce' => wp_create_nonce('mxchat_edit_intent'),
7388 'toggle_action_nonce' => wp_create_nonce('mxchat_actions_nonce'),
7389 'fetch_openrouter_models_nonce' => wp_create_nonce('mxchat_fetch_openrouter_models'),
7390 'is_activated' => $this->is_activated ? '1' : '0',
7391 'status_refresh_interval' => 5000,
7392 'prompts_setting_nonce' => wp_create_nonce('mxchat_prompts_setting_nonce'),
7393 'ajaxurl' => admin_url('admin-ajax.php')
7394 );
7395
7396 // Localize main admin script with base data
7397 wp_localize_script('mxchat-admin-js', 'mxchatAdmin', $base_data);
7398
7399 // Page-specific localizations
7400 $this->localize_page_specific_scripts($current_page);
7401 }
7402 private function localize_page_specific_scripts($current_page) {
7403 switch ($current_page) {
7404 case 'mxchat-prompts':
7405 // Status updater localization
7406 wp_localize_script('mxchat-status-updater', 'mxchat_status_data', array(
7407 'ajax_url' => admin_url('admin-ajax.php'),
7408 'nonce' => wp_create_nonce('mxchat_status_nonce')
7409 ));
7410
7411 // Content selector localization
7412 wp_localize_script('mxchat-content-selector-js', 'mxchatSelector', array(
7413 'ajaxurl' => admin_url('admin-ajax.php'),
7414 'nonce' => wp_create_nonce('mxchat_content_selector_nonce'),
7415 'i18n' => array(
7416 'searchPlaceholder' => __('Search posts and pages...', 'mxchat'),
7417 'selectAll' => __('Select All', 'mxchat'),
7418 'process' => __('Process Selected', 'mxchat'),
7419 'cancel' => __('Cancel', 'mxchat'),
7420 'noResults' => __('No content found.', 'mxchat')
7421 )
7422 ));
7423
7424 wp_localize_script('mxchat-knowledge-processing', 'mxchatAdmin', array(
7425 'ajax_url' => admin_url('admin-ajax.php'),
7426 'status_nonce' => wp_create_nonce('mxchat_status_nonce'),
7427 'queue_nonce' => wp_create_nonce('mxchat_queue_nonce'),
7428 'stop_nonce' => wp_create_nonce('mxchat_stop_processing_action'),
7429 'settings_nonce' => wp_create_nonce('mxchat_prompts_setting_nonce'),
7430 'setting_nonce' => wp_create_nonce('mxchat_save_setting_nonce'),
7431 'entries_nonce' => wp_create_nonce('mxchat_entries_nonce'),
7432 'admin_url' => admin_url(),
7433 'ajaxurl' => admin_url('admin-ajax.php'),
7434 'status_refresh_interval' => 2000,
7435 'bot_id' => isset($_GET['bot_id']) ? sanitize_text_field($_GET['bot_id']) : 'default'
7436 ));
7437 break;
7438
7439 case 'mxchat-activation':
7440 wp_localize_script('mxchat-activation-js', 'mxchatAdmin', array(
7441 'ajax_url' => admin_url('admin-ajax.php'),
7442 'license_nonce' => wp_create_nonce('mxchat_activate_license_nonce')
7443 ));
7444 break;
7445
7446 case 'mxchat-transcripts':
7447 // Get chart data for localization
7448 $chart_data = $this->get_transcripts_chart_data();
7449
7450 wp_localize_script('mxchat-transcripts-js', 'mxchatAdmin', array(
7451 'ajax_url' => admin_url('admin-ajax.php'),
7452 'export_nonce' => wp_create_nonce('mxchat_export_transcripts'),
7453 'delete_nonce' => wp_create_nonce('mxchat_delete_chat_history'),
7454 'setting_nonce' => wp_create_nonce('mxchat_save_setting_nonce'),
7455 'translate_nonce' => wp_create_nonce('mxchat_translate_messages')
7456 ));
7457
7458 // Localize chart data separately - use array_values to ensure proper JSON array encoding
7459 wp_localize_script('mxchat-transcripts-js', 'mxchatChartData', array(
7460 'labels' => array_values($chart_data['labels']),
7461 'chats' => array_values($chart_data['chats']),
7462 'messages' => array_values($chart_data['messages'])
7463 ));
7464 break;
7465
7466 case 'mxchat-settings':
7467 default:
7468 // Color picker and settings localization
7469 wp_localize_script('mxchat-color-picker', 'mxchatStyleSettings', array(
7470 'ajax_url' => admin_url('admin-ajax.php'),
7471 'link_target_toggle' => $this->options['link_target_toggle'] ?? 'off',
7472 'user_message_bg_color' => $this->options['user_message_bg_color'] ?? '#fff',
7473 'user_message_font_color' => $this->options['user_message_font_color'] ?? '#212121',
7474 'bot_message_bg_color' => $this->options['bot_message_bg_color'] ?? '#212121',
7475 'bot_message_font_color' => $this->options['bot_message_font_color'] ?? '#fff',
7476 'top_bar_bg_color' => $this->options['top_bar_bg_color'] ?? '#212121',
7477 'send_button_font_color' => $this->options['send_button_font_color'] ?? '#212121',
7478 'close_button_color' => $this->options['close_button_color'] ?? '#fff',
7479 'chatbot_background_color' => $this->options['chatbot_background_color'] ?? '#212121',
7480 'icon_color' => $this->options['icon_color'] ?? '#fff',
7481 'chat_input_font_color' => $this->options['chat_input_font_color'] ?? '#212121',
7482 'pre_chat_message' => $this->options['pre_chat_message'] ?? esc_html__('Hey there! Ask me anything!', 'mxchat'),
7483 'rate_limit_message' => $this->options['rate_limit_message'] ?? esc_html__('Rate limit exceeded. Please try again later.', 'mxchat'),
7484 'loops_api_key' => $this->options['loops_api_key'] ?? '',
7485 'loops_mailing_list' => $this->options['loops_mailing_list'] ?? '',
7486 'triggered_phrase_response' => $this->options['triggered_phrase_response'] ?? esc_html__('Would you like to join our mailing list? Please provide your email below.', 'mxchat'),
7487 'email_capture_response' => $this->options['email_capture_response'] ?? esc_html__('Thank you for providing your email! You\'ve been added to our list.', 'mxchat'),
7488 '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'),
7489 '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'),
7490 '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'),
7491 'pdf_max_pages' => $this->options['pdf_max_pages'] ?? 69,
7492 'live_agent_webhook_url' => $this->options['live_agent_webhook_url'] ?? '',
7493 'live_agent_secret_key' => $this->options['live_agent_secret_key'] ?? '',
7494 'live_agent_bot_token' => $this->options['live_agent_bot_token'] ?? '',
7495 'live_agent_message_bg_color' => $this->options['live_agent_message_bg_color'] ?? '#ffffff',
7496 'live_agent_message_font_color' => $this->options['live_agent_message_font_color'] ?? '#333333',
7497 'chat_toolbar_toggle' => $this->options['chat_toolbar_toggle'] ?? 'off',
7498 'show_pdf_upload_button' => $this->options['show_pdf_upload_button'] ?? 'on',
7499 'show_word_upload_button' => $this->options['show_word_upload_button'] ?? 'on',
7500 'mode_indicator_bg_color' => $this->options['mode_indicator_bg_color'] ?? '#767676',
7501 'mode_indicator_font_color' => $this->options['mode_indicator_font_color'] ?? '#ffffff',
7502 'toolbar_icon_color' => $this->options['toolbar_icon_color'] ?? '#212121',
7503 ));
7504 break;
7505 }
7506
7507 // Additional localization that was in the original code
7508 wp_localize_script('mxchat-admin-js', 'mxchatPromptsAdmin', array(
7509 'ajax_url' => admin_url('admin-ajax.php'),
7510 'prompts_setting_nonce' => wp_create_nonce('mxchat_prompts_setting_nonce'),
7511 ));
7512 }
7513
7514 public function mxchat_sanitize($input) {
7515 $new_input = array();
7516
7517 if (isset($input['api_key'])) {
7518 $new_input['api_key'] = sanitize_text_field($input['api_key']);
7519 }
7520
7521 if (isset($input['similarity_threshold'])) {
7522 $new_input['similarity_threshold'] = absint($input['similarity_threshold']); // Ensure it's an integer
7523 $new_input['similarity_threshold'] = min(max($new_input['similarity_threshold'], 20), 85); // Enforce range
7524 }
7525
7526 if (isset($input['xai_api_key'])) {
7527 $new_input['xai_api_key'] = sanitize_text_field($input['xai_api_key']);
7528 }
7529
7530 if (isset($input['claude_api_key'])) {
7531 $new_input['claude_api_key'] = sanitize_text_field($input['claude_api_key']);
7532 }
7533
7534 if (isset($input['enable_streaming_toggle'])) {
7535 $new_input['enable_streaming_toggle'] = ($input['enable_streaming_toggle'] === 'on') ? 'on' : 'off';
7536 } else {
7537 // If checkbox not checked, it won't be in $input, so set to 'off'
7538 $new_input['enable_streaming_toggle'] = 'off';
7539 }
7540
7541 if (isset($input['enable_web_search'])) {
7542 $new_input['enable_web_search'] = ($input['enable_web_search'] === 'on') ? 'on' : 'off';
7543 } else {
7544 // If checkbox not checked, it won't be in $input, so set to 'off'
7545 $new_input['enable_web_search'] = 'off';
7546 }
7547
7548 if (isset($input['deepseek_api_key'])) {
7549 $new_input['deepseek_api_key'] = sanitize_text_field($input['deepseek_api_key']);
7550 }
7551
7552 if (isset($input['gemini_api_key'])) {
7553 $new_input['gemini_api_key'] = sanitize_text_field($input['gemini_api_key']);
7554 }
7555
7556 if (isset($input['enable_woocommerce_integration'])) {
7557 $new_input['enable_woocommerce_integration'] = $input['enable_woocommerce_integration'] === 'on' ? 'on' : 'off';
7558 }
7559
7560 if (isset($input['privacy_toggle'])) {
7561 $new_input['privacy_toggle'] = $input['privacy_toggle'];
7562 }
7563
7564 if (isset($input['complianz_toggle'])) {
7565 $new_input['complianz_toggle'] = $input['complianz_toggle'];
7566 }
7567
7568 // Handle custom privacy text input
7569 if (isset($input['privacy_text'])) {
7570 // Allow basic HTML for links
7571 $new_input['privacy_text'] = wp_kses_post($input['privacy_text']);
7572 }
7573
7574 if (isset($input['system_prompt_instructions'])) {
7575 $new_input['system_prompt_instructions'] = sanitize_textarea_field($input['system_prompt_instructions']);
7576 }
7577
7578 if (isset($input['mxchat_pro_email'])) {
7579 $new_input['mxchat_pro_email'] = sanitize_email($input['mxchat_pro_email']);
7580 }
7581
7582 if (isset($input['mxchat_activation_key'])) {
7583 $new_input['mxchat_activation_key'] = sanitize_text_field($input['mxchat_activation_key']);
7584 }
7585
7586 if (isset($input['append_to_body'])) {
7587 $new_input['append_to_body'] = $input['append_to_body'] === 'on' ? 'on' : 'off';
7588 }
7589
7590 // Post type visibility settings
7591 if (isset($input['post_type_visibility_mode'])) {
7592 $allowed_modes = array('all', 'include', 'exclude');
7593 $new_input['post_type_visibility_mode'] = in_array($input['post_type_visibility_mode'], $allowed_modes)
7594 ? $input['post_type_visibility_mode']
7595 : 'all';
7596 }
7597
7598 if (isset($input['post_type_visibility_list'])) {
7599 if (is_array($input['post_type_visibility_list'])) {
7600 $new_input['post_type_visibility_list'] = array_map('sanitize_key', $input['post_type_visibility_list']);
7601 } else {
7602 $new_input['post_type_visibility_list'] = array();
7603 }
7604 }
7605
7606 if (isset($input['contextual_awareness_toggle'])) {
7607 $new_input['contextual_awareness_toggle'] = $input['contextual_awareness_toggle'] === 'on' ? 'on' : 'off';
7608 }
7609
7610 if (isset($input['show_frontend_debugger'])) {
7611 $new_input['show_frontend_debugger'] = $input['show_frontend_debugger'] === 'on' ? 'on' : 'off';
7612 }
7613
7614 if (isset($input['citation_links_toggle'])) {
7615 $new_input['citation_links_toggle'] = $input['citation_links_toggle'] === 'on' ? 'on' : 'off';
7616 }
7617
7618 if (isset($input['top_bar_title'])) {
7619 $new_input['top_bar_title'] = sanitize_text_field($input['top_bar_title']);
7620 }
7621
7622 if (isset($input['ai_agent_text'])) {
7623 $new_input['ai_agent_text'] = sanitize_text_field($input['ai_agent_text']);
7624 }
7625
7626 if (isset($input['enable_email_block'])) {
7627 $new_input['enable_email_block'] = sanitize_text_field($input['enable_email_block']);
7628 }
7629
7630 if (isset($input['email_blocker_header_content'])) {
7631 // wp_kses_post() allows standard HTML tags permitted by WordPress
7632 $new_input['email_blocker_header_content'] = wp_kses_post($input['email_blocker_header_content']);
7633 }
7634 if (isset($input['email_blocker_button_text'])) {
7635 $new_input['email_blocker_button_text'] = sanitize_text_field($input['email_blocker_button_text']);
7636 }
7637 // Sanitize name field toggle
7638 if (isset($input['enable_name_field'])) {
7639 $new_input['enable_name_field'] = ($input['enable_name_field'] === 'on') ? 'on' : 'off';
7640 } else {
7641 $new_input['enable_name_field'] = 'off';
7642 }
7643 // Sanitize name field placeholder
7644 if (isset($input['name_field_placeholder'])) {
7645 $new_input['name_field_placeholder'] = sanitize_text_field($input['name_field_placeholder']);
7646 }
7647 if (isset($input['intro_message'])) {
7648 $new_input['intro_message'] = wp_kses_post($input['intro_message']); // Use wp_kses_post instead
7649 }
7650
7651 if (isset($input['input_copy'])) {
7652 $new_input['input_copy'] = sanitize_text_field($input['input_copy']);
7653 }
7654
7655 if (isset($input['rate_limit_message'])) {
7656 $new_input['rate_limit_message'] = sanitize_text_field($input['rate_limit_message']);
7657 }
7658
7659 // Handle the new rate limits format
7660 if (isset($input['rate_limits']) && is_array($input['rate_limits'])) {
7661 $new_input['rate_limits'] = array();
7662 $allowed_limits = array('1', '3', '5', '10', '15', '20', '50', '100', 'unlimited');
7663 $allowed_timeframes = array('hourly', 'daily', 'weekly', 'monthly');
7664
7665 foreach ($input['rate_limits'] as $role_id => $settings) {
7666 $new_input['rate_limits'][$role_id] = array();
7667
7668 // Sanitize limit
7669 if (isset($settings['limit'])) {
7670 $limit = sanitize_text_field($settings['limit']);
7671 if (in_array($limit, $allowed_limits, true)) {
7672 $new_input['rate_limits'][$role_id]['limit'] = $limit;
7673 } else {
7674 $new_input['rate_limits'][$role_id]['limit'] = ($role_id === 'logged_out') ? '10' : '100'; // Default
7675 }
7676 }
7677
7678 // Sanitize timeframe
7679 if (isset($settings['timeframe'])) {
7680 $timeframe = sanitize_text_field($settings['timeframe']);
7681 if (in_array($timeframe, $allowed_timeframes, true)) {
7682 $new_input['rate_limits'][$role_id]['timeframe'] = $timeframe;
7683 } else {
7684 $new_input['rate_limits'][$role_id]['timeframe'] = 'daily'; // Default
7685 }
7686 }
7687
7688 // Sanitize message
7689 if (isset($settings['message'])) {
7690 $new_input['rate_limits'][$role_id]['message'] = sanitize_textarea_field($settings['message']);
7691 }
7692 }
7693 }
7694
7695 if (isset($input['pre_chat_message'])) {
7696 $new_input['pre_chat_message'] = sanitize_textarea_field($input['pre_chat_message']);
7697 }
7698
7699 if (isset($input['voyage_api_key'])) {
7700 $new_input['voyage_api_key'] = sanitize_text_field($input['voyage_api_key']);
7701 }
7702
7703 // Add to your sanitize function
7704 if (isset($input['embedding_model'])) {
7705 $allowed_models = array(
7706 'text-embedding-ada-002',
7707 'text-embedding-3-small',
7708 'text-embedding-3-large',
7709 'voyage-3-large',
7710 'gemini-embedding-001'
7711 );
7712 if (in_array($input['embedding_model'], $allowed_models)) {
7713 $new_input['embedding_model'] = sanitize_text_field($input['embedding_model']);
7714 }
7715 }
7716
7717 if (isset($input['model'])) {
7718 if ($input['model'] === 'openrouter') {
7719 $new_input['model'] = 'openrouter';
7720 } else {
7721 $allowed_models = array(
7722 'gemini-3-pro-preview',
7723 'gemini-3-flash-preview',
7724 'gemini-2.5-pro',
7725 'gemini-2.5-flash',
7726 'gemini-2.5-flash-lite',
7727 'gemini-2.0-flash',
7728 'gemini-2.0-flash-lite',
7729 'gemini-1.5-pro',
7730 'gemini-1.5-flash',
7731 'grok-4-0709',
7732 'grok-4-1-fast-reasoning',
7733 'grok-4-1-fast-non-reasoning',
7734 'grok-3-beta',
7735 'grok-3-fast-beta',
7736 'grok-3-mini-beta',
7737 'grok-3-mini-fast-beta',
7738 'grok-2',
7739 'deepseek-chat',
7740 'claude-sonnet-4-5-20250929',
7741 'claude-opus-4-1-20250805',
7742 'claude-haiku-4-5-20251001',
7743 'claude-opus-4-20250514',
7744 'claude-sonnet-4-20250514',
7745 'claude-3-7-sonnet-20250219',
7746 // REMOVED: 'claude-3-5-sonnet-20241022',
7747 'claude-3-opus-20240229',
7748 'claude-3-sonnet-20240229',
7749 'claude-3-haiku-20240307',
7750 'gpt-5.2',
7751 'gpt-5.1-2025-11-13',
7752 'gpt-5',
7753 'gpt-5-mini',
7754 'gpt-5-nano',
7755 'gpt-4.1-2025-04-14',
7756 'gpt-4o',
7757 'gpt-4o-mini',
7758 'gpt-4-turbo',
7759 'gpt-4',
7760 'gpt-3.5-turbo',
7761 );
7762
7763 if (in_array($input['model'], $allowed_models)) {
7764 $new_input['model'] = sanitize_text_field($input['model']);
7765 } else {
7766 // Fallback for any deprecated model
7767 $new_input['model'] = 'claude-3-7-sonnet-20250219';
7768 }
7769 }
7770 }
7771
7772 if (isset($input['openrouter_selected_model'])) {
7773 // Just sanitize it, don't validate against a whitelist
7774 $new_input['openrouter_selected_model'] = sanitize_text_field($input['openrouter_selected_model']);
7775 }
7776
7777 // ADD THIS:
7778 if (isset($input['openrouter_selected_model_name'])) {
7779 $new_input['openrouter_selected_model_name'] = sanitize_text_field($input['openrouter_selected_model_name']);
7780 }
7781
7782 if (isset($input['openrouter_api_key'])) {
7783 $new_input['openrouter_api_key'] = sanitize_text_field($input['openrouter_api_key']);
7784 }
7785
7786
7787 if (isset($input['close_button_color'])) {
7788 $new_input['close_button_color'] = sanitize_hex_color($input['close_button_color']);
7789 }
7790
7791 if (isset($input['chatbot_bg_color'])) {
7792 $new_input['chatbot_bg_color'] = sanitize_hex_color($input['chatbot_bg_color']);
7793 }
7794
7795 if (isset($input['woocommerce_consumer_key'])) {
7796 $new_input['woocommerce_consumer_key'] = sanitize_text_field($input['woocommerce_consumer_key']);
7797 }
7798
7799 if (isset($input['woocommerce_consumer_secret'])) {
7800 $new_input['woocommerce_consumer_secret'] = sanitize_text_field($input['woocommerce_consumer_secret']);
7801 }
7802
7803 if (isset($input['user_message_bg_color'])) {
7804 $new_input['user_message_bg_color'] = sanitize_hex_color($input['user_message_bg_color']);
7805 }
7806
7807 if (isset($input['user_message_font_color'])) {
7808 $new_input['user_message_font_color'] = sanitize_hex_color($input['user_message_font_color']);
7809 }
7810
7811 if (isset($input['bot_message_bg_color'])) {
7812 $new_input['bot_message_bg_color'] = sanitize_hex_color($input['bot_message_bg_color']);
7813 }
7814
7815 if (isset($input['bot_message_font_color'])) {
7816 $new_input['bot_message_font_color'] = sanitize_hex_color($input['bot_message_font_color']);
7817 }
7818
7819 if (isset($input['live_agent_message_bg_color'])) {
7820 $new_input['live_agent_message_bg_color'] = sanitize_hex_color($input['live_agent_message_bg_color']);
7821 }
7822
7823 if (isset($input['live_agent_message_font_color'])) {
7824 $new_input['live_agent_message_font_color'] = sanitize_hex_color($input['live_agent_message_font_color']);
7825 }
7826
7827 if (isset($input['mode_indicator_bg_color'])) {
7828 $new_input['mode_indicator_bg_color'] = sanitize_hex_color($input['mode_indicator_bg_color']);
7829 }
7830
7831 if (isset($input['mode_indicator_font_color'])) {
7832 $new_input['mode_indicator_font_color'] = sanitize_hex_color($input['mode_indicator_font_color']);
7833 }
7834
7835 if (isset($input['toolbar_icon_color'])) {
7836 $new_input['toolbar_icon_color'] = sanitize_hex_color($input['toolbar_icon_color']);
7837 }
7838
7839 if (isset($input['top_bar_bg_color'])) {
7840 $new_input['top_bar_bg_color'] = sanitize_hex_color($input['top_bar_bg_color']);
7841 }
7842
7843 if (isset($input['quick_questions_toggle_color'])) {
7844 $new_input['quick_questions_toggle_color'] = sanitize_hex_color($input['quick_questions_toggle_color']);
7845 }
7846
7847 if (isset($input['send_button_font_color'])) {
7848 $new_input['send_button_font_color'] = sanitize_hex_color($input['send_button_font_color']);
7849 }
7850
7851 if (isset($input['chatbot_background_color'])) {
7852 $new_input['chatbot_background_color'] = sanitize_hex_color($input['chatbot_background_color']);
7853 }
7854
7855 if (isset($input['icon_color'])) {
7856 $new_input['icon_color'] = sanitize_hex_color($input['icon_color']);
7857 }
7858
7859 if (isset($input['custom_icon'])) {
7860 $new_input['custom_icon'] = esc_url_raw($input['custom_icon']);
7861 }
7862
7863 if (isset($input['title_icon'])) {
7864 $new_input['title_icon'] = esc_url_raw($input['title_icon']);
7865 }
7866
7867 if (isset($input['chat_input_font_color'])) {
7868 $new_input['chat_input_font_color'] = sanitize_hex_color($input['chat_input_font_color']);
7869 }
7870
7871 // Sanitize link_target_toggle
7872 if (isset($input['link_target_toggle'])) {
7873 $new_input['link_target_toggle'] = $input['link_target_toggle'] === 'on' ? 'on' : 'off';
7874 }
7875
7876 // Sanitize Loops API Key
7877 if (isset($input['loops_api_key'])) {
7878 $new_input['loops_api_key'] = sanitize_text_field($input['loops_api_key']);
7879 }
7880
7881 if (isset($input['chat_persistence_toggle'])) {
7882 $new_input['chat_persistence_toggle'] = $input['chat_persistence_toggle'] === 'on' ? 'on' : 'off';
7883 }
7884
7885 if (isset($input['popular_question_1'])) {
7886 $new_input['popular_question_1'] = sanitize_text_field($input['popular_question_1']);
7887 }
7888
7889 if (isset($input['popular_question_2'])) {
7890 $new_input['popular_question_2'] = sanitize_text_field($input['popular_question_2']);
7891 }
7892
7893 if (isset($input['popular_question_3'])) {
7894 $new_input['popular_question_3'] = sanitize_text_field($input['popular_question_3']);
7895 }
7896
7897 if (isset($input['additional_popular_questions']) && is_array($input['additional_popular_questions'])) {
7898 $new_input['additional_popular_questions'] = array_map('sanitize_text_field', $input['additional_popular_questions']);
7899 }
7900
7901 // Sanitize Loops Mailing List
7902 if (isset($input['loops_mailing_list'])) {
7903 $new_input['loops_mailing_list'] = sanitize_text_field($input['loops_mailing_list']);
7904 }
7905
7906 // Sanitize Triggered Phrase Response
7907 if (isset($input['triggered_phrase_response'])) {
7908 $new_input['triggered_phrase_response'] = wp_kses_post($input['triggered_phrase_response']);
7909 }
7910 if (isset($input['email_capture_response'])) {
7911 $new_input['email_capture_response'] = wp_kses_post($input['email_capture_response']);
7912 }
7913
7914 // Sanitize Brave Search Settings
7915 if (isset($input['brave_api_key'])) {
7916 $new_input['brave_api_key'] = sanitize_text_field($input['brave_api_key']);
7917 }
7918
7919 if (isset($input['brave_image_count'])) {
7920 $image_count = intval($input['brave_image_count']);
7921 $new_input['brave_image_count'] = ($image_count >=1 && $image_count <=6) ? $image_count : 4;
7922 }
7923
7924 if (isset($input['brave_safe_search'])) {
7925 $allowed = array('strict', 'off');
7926 $new_input['brave_safe_search'] = in_array($input['brave_safe_search'], $allowed, true) ? $input['brave_safe_search'] : 'strict';
7927 }
7928
7929 if (isset($input['brave_news_count'])) {
7930 $news_count = intval($input['brave_news_count']);
7931 $new_input['brave_news_count'] = ($news_count >=1 && $news_count <=10) ? $news_count : 3;
7932 }
7933
7934 if (isset($input['brave_country'])) {
7935 $new_input['brave_country'] = sanitize_text_field($input['brave_country']);
7936 }
7937
7938 if (isset($input['brave_language'])) {
7939 $new_input['brave_language'] = sanitize_text_field($input['brave_language']);
7940 }
7941
7942 if (isset($input['chat_toolbar_toggle'])) {
7943 $new_input['chat_toolbar_toggle'] = $input['chat_toolbar_toggle'] === 'on' ? 'on' : 'off';
7944 }
7945
7946 // Sanitize PDF upload button toggle
7947 if (isset($input['show_pdf_upload_button'])) {
7948 $new_input['show_pdf_upload_button'] = $input['show_pdf_upload_button'] === 'on' ? 'on' : 'off';
7949 } else {
7950 $new_input['show_pdf_upload_button'] = 'off'; // If checkbox is unchecked
7951 }
7952
7953 // Sanitize Word upload button toggle
7954 if (isset($input['show_word_upload_button'])) {
7955 $new_input['show_word_upload_button'] = $input['show_word_upload_button'] === 'on' ? 'on' : 'off';
7956 } else {
7957 $new_input['show_word_upload_button'] = 'off'; // If checkbox is unchecked
7958 }
7959
7960 if (isset($input['pdf_intent_trigger_text'])) {
7961 $new_input['pdf_intent_trigger_text'] = sanitize_text_field($input['pdf_intent_trigger_text']);
7962 }
7963
7964 if (isset($input['pdf_intent_success_text'])) {
7965 $new_input['pdf_intent_success_text'] = sanitize_text_field($input['pdf_intent_success_text']);
7966 }
7967
7968 if (isset($input['pdf_intent_error_text'])) {
7969 $new_input['pdf_intent_error_text'] = sanitize_text_field($input['pdf_intent_error_text']);
7970 }
7971
7972 if (isset($input['pdf_max_pages'])) {
7973 $new_input['pdf_max_pages'] = intval($input['pdf_max_pages']);
7974 if ($new_input['pdf_max_pages'] < 1 || $new_input['pdf_max_pages'] > 69) {
7975 $new_input['pdf_max_pages'] = 69; // Default to 69 if out of range
7976 }
7977 }
7978
7979 if (isset($input['live_agent_webhook_url'])) {
7980 $new_input['live_agent_webhook_url'] = esc_url_raw($input['live_agent_webhook_url']);
7981 }
7982 if (isset($input['live_agent_secret_key'])) {
7983 $new_input['live_agent_secret_key'] = sanitize_text_field($input['live_agent_secret_key']);
7984 }
7985
7986 // Live Agent Integration
7987 if (isset($input['live_agent_bot_token'])) {
7988 $new_input['live_agent_bot_token'] = sanitize_text_field($input['live_agent_bot_token']);
7989 }
7990
7991 if (isset($input['live_agent_user_ids'])) {
7992 $new_input['live_agent_user_ids'] = sanitize_textarea_field($input['live_agent_user_ids']);
7993 }
7994
7995 if (isset($input['live_agent_status'])) {
7996 $new_input['live_agent_status'] = ($input['live_agent_status'] === 'on') ? 'on' : 'off';
7997 }
7998 if (isset($input['live_agent_away_message'])) {
7999 $new_input['live_agent_away_message'] = sanitize_textarea_field($input['live_agent_away_message']);
8000 }
8001 if (isset($input['live_agent_notification_message'])) {
8002 $new_input['live_agent_notification_message'] = sanitize_textarea_field($input['live_agent_notification_message']);
8003 }
8004
8005 // Telegram Integration
8006 if (isset($input['telegram_status'])) {
8007 $new_input['telegram_status'] = ($input['telegram_status'] === 'on') ? 'on' : 'off';
8008 }
8009 if (isset($input['telegram_bot_token'])) {
8010 $new_input['telegram_bot_token'] = sanitize_text_field($input['telegram_bot_token']);
8011 }
8012 if (isset($input['telegram_group_id'])) {
8013 $new_input['telegram_group_id'] = sanitize_text_field($input['telegram_group_id']);
8014 }
8015 if (isset($input['telegram_webhook_secret'])) {
8016 $new_input['telegram_webhook_secret'] = sanitize_text_field($input['telegram_webhook_secret']);
8017 }
8018 if (isset($input['telegram_notification_message'])) {
8019 $new_input['telegram_notification_message'] = sanitize_textarea_field($input['telegram_notification_message']);
8020 }
8021 if (isset($input['telegram_away_message'])) {
8022 $new_input['telegram_away_message'] = sanitize_textarea_field($input['telegram_away_message']);
8023 }
8024
8025 return $new_input;
8026 }
8027
8028
8029 // Method to append the chatbot to the body
8030 public function mxchat_append_chatbot_to_body() {
8031 $options = get_option('mxchat_options');
8032 if (isset($options['append_to_body']) && $options['append_to_body'] === 'on') {
8033 echo do_shortcode('[mxchat_chatbot floating="yes"]');
8034 }
8035 }
8036
8037
8038
8039
8040
8041 private function mxchat_fetch_loops_mailing_lists($api_key) {
8042 $url = 'https://app.loops.so/api/v1/lists';
8043 $response = wp_remote_get($url, array(
8044 'headers' => array(
8045 'Authorization' => 'Bearer ' . $api_key,
8046 'Content-Type' => 'application/json'
8047 )
8048 ));
8049
8050 if (is_wp_error($response)) {
8051 return array();
8052 }
8053
8054 $body = wp_remote_retrieve_body($response);
8055 $lists = json_decode($body, true);
8056
8057 return isset($lists) && is_array($lists) ? $lists : array();
8058 }
8059
8060 function mxchat_calculate_cosine_similarity($vec1, $vec2) {
8061 if (empty($vec1) || empty($vec2)) {
8062 return 0.0;
8063 }
8064
8065 $dot_product = 0.0;
8066 $norm_a = 0.0;
8067 $norm_b = 0.0;
8068
8069 for ($i = 0; $i < count($vec1); $i++) {
8070 $dot_product += $vec1[$i] * $vec2[$i];
8071 $norm_a += pow($vec1[$i], 2);
8072 $norm_b += pow($vec2[$i], 2);
8073 }
8074
8075 if ($norm_a == 0.0 || $norm_b == 0.0) {
8076 return 0.0;
8077 } else {
8078 return $dot_product / (sqrt($norm_a) * sqrt($norm_b));
8079 }
8080 }
8081
8082 /**
8083 * Validates nonce and deletes all chat prompts
8084 */
8085 public function mxchat_handle_delete_all_prompts() {
8086 //error_log('=== DELETE ALL DEBUG START ===');
8087
8088 // Verify nonce
8089 if (!isset($_POST['mxchat_delete_all_prompts_nonce']) || !wp_verify_nonce($_POST['mxchat_delete_all_prompts_nonce'], 'mxchat_delete_all_prompts_action')) {
8090 //error_log('DEBUG: Nonce verification failed');
8091 wp_die(__('Nonce verification failed.', 'mxchat'));
8092 }
8093
8094 // Check permissions
8095 if (!current_user_can('manage_options')) {
8096 //error_log('DEBUG: Permission check failed');
8097 wp_die(__('You do not have sufficient permissions to delete all prompts.', 'mxchat'));
8098 }
8099
8100 // Get bot_id from POST data
8101 $bot_id = isset($_POST['bot_id']) ? sanitize_text_field($_POST['bot_id']) : 'default';
8102 //error_log('DEBUG: Bot ID from POST: ' . $bot_id);
8103
8104 $success = true;
8105 $error_messages = array();
8106
8107 // Get bot-specific Pinecone configuration
8108 $pinecone_manager = MxChat_Pinecone_Manager::get_instance();
8109 $pinecone_options = $pinecone_manager->mxchat_get_bot_pinecone_options($bot_id);
8110
8111 //error_log('DEBUG: Pinecone options retrieved:');
8112 //error_log(' - Use Pinecone: ' . ($pinecone_options['mxchat_use_pinecone'] ?? 'NOT SET'));
8113 //error_log(' - API Key: ' . (empty($pinecone_options['mxchat_pinecone_api_key']) ? 'EMPTY' : 'SET'));
8114 //error_log(' - Host: ' . ($pinecone_options['mxchat_pinecone_host'] ?? 'NOT SET'));
8115
8116 $use_pinecone = ($pinecone_options['mxchat_use_pinecone'] ?? '0') === '1';
8117
8118 if ($use_pinecone && !empty($pinecone_options['mxchat_pinecone_api_key'])) {
8119 //error_log('[MXCHAT-DELETE] Pinecone is enabled for bot ' . $bot_id . ' - deleting all from Pinecone');
8120
8121 // Delete from bot-specific Pinecone index
8122 $result = $pinecone_manager->mxchat_delete_all_from_pinecone($pinecone_options);
8123
8124 //error_log('DEBUG: Delete all result: ' . ($result['success'] ? 'SUCCESS' : 'FAILED'));
8125 if (!$result['success']) {
8126 //error_log('DEBUG: Delete all error: ' . $result['message']);
8127 }
8128
8129 if (!$result['success']) {
8130 $success = false;
8131 $error_messages[] = $result['message'];
8132 }
8133
8134 // No cache clearing needed since we removed caching
8135
8136 } else {
8137 //error_log('[MXCHAT-DELETE] Pinecone not enabled for bot ' . $bot_id . ' - deleting from WordPress database');
8138
8139 // Delete from WordPress database
8140 global $wpdb;
8141 $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
8142
8143 // If multi-bot is active and we have a specific bot, delete only that bot's content
8144 if ($bot_id !== 'default' && class_exists('MxChat_Multi_Bot_Manager')) {
8145 $result = $wpdb->delete(
8146 $table_name,
8147 array('bot_id' => $bot_id),
8148 array('%s')
8149 );
8150 } else {
8151 // Delete all content for default bot
8152 $result = $wpdb->query("DELETE FROM {$table_name}");
8153 }
8154
8155 if ($result === false) {
8156 $success = false;
8157 $error_messages[] = 'Failed to delete from WordPress database';
8158 }
8159 }
8160
8161 // No cache clearing needed since we removed caching
8162
8163 //error_log('=== DELETE ALL DEBUG END ===');
8164
8165 // Redirect back with a success message and bot_id
8166 $redirect_url = add_query_arg(array(
8167 'page' => 'mxchat-prompts',
8168 'bot_id' => $bot_id,
8169 'all_deleted' => $success ? 'true' : 'false'
8170 ), admin_url('admin.php'));
8171
8172 wp_safe_redirect($redirect_url);
8173 exit;
8174 }
8175
8176
8177
8178 /**
8179 * Handles deletion prompt with nonce validation
8180 */
8181 public function mxchat_handle_delete_prompt() {
8182 // Sanitize and validate nonce
8183 $nonce = isset($_GET['_wpnonce']) ? sanitize_text_field(wp_unslash($_GET['_wpnonce'])) : '';
8184 if (empty($nonce) || !wp_verify_nonce($nonce, 'mxchat_delete_prompt_nonce')) {
8185 wp_die(esc_html__('Nonce verification failed.', 'mxchat'));
8186 }
8187
8188 // Check permissions
8189 if (!current_user_can('manage_options')) {
8190 wp_die(esc_html__('You do not have sufficient permissions to delete prompts.', 'mxchat'));
8191 }
8192
8193 // Get ID and source parameters
8194 $id = isset($_GET['id']) ? sanitize_text_field($_GET['id']) : '';
8195 $source = isset($_GET['source']) ? sanitize_text_field($_GET['source']) : '';
8196
8197 if (empty($id)) {
8198 wp_die(esc_html__('Invalid prompt ID.', 'mxchat'));
8199 }
8200
8201 $success = false;
8202 $error_message = '';
8203
8204 // Check if Pinecone is enabled and determine source automatically if not specified
8205 $pinecone_options = get_option('mxchat_pinecone_addon_options', array());
8206 $use_pinecone = ($pinecone_options['mxchat_use_pinecone'] ?? '0') === '1';
8207
8208 // If source is not specified, determine based on Pinecone configuration
8209 if (empty($source)) {
8210 $source = $use_pinecone ? 'pinecone' : 'wordpress';
8211 }
8212
8213 if ($source === 'pinecone' || $use_pinecone) {
8214 //error_log('[MXCHAT-DELETE] Deleting from Pinecone, ID: ' . $id);
8215
8216 // Handle Pinecone deletion
8217 if (empty($pinecone_options['mxchat_pinecone_host']) ||
8218 empty($pinecone_options['mxchat_pinecone_api_key'])) {
8219 wp_die(esc_html__('Pinecone configuration is missing.', 'mxchat'));
8220 }
8221
8222 // Delete from Pinecone using the vector ID directly
8223 $pinecone_manager = MxChat_Pinecone_Manager::get_instance();
8224 $result = $pinecone_manager->mxchat_delete_from_pinecone_by_vector_id(
8225 $id,
8226 $pinecone_options['mxchat_pinecone_api_key'],
8227 $pinecone_options['mxchat_pinecone_host']
8228 );
8229 if ($result['success']) {
8230 $success = true;
8231
8232 // Remove from vector cache
8233 $pinecone_manager->mxchat_remove_from_pinecone_vector_cache($id);
8234 $pinecone_manager->mxchat_remove_from_processed_content_caches($id);
8235
8236 set_transient('mxchat_admin_notice_success',
8237 esc_html__('Vector deleted successfully from Pinecone.', 'mxchat'), 30);
8238 } else {
8239 $error_message = $result['message'];
8240 set_transient('mxchat_admin_notice_error',
8241 esc_html__('Failed to delete from Pinecone: ', 'mxchat') . esc_html($error_message), 30);
8242 }
8243 } else {
8244 //error_log('[MXCHAT-DELETE] Deleting from WordPress database, ID: ' . $id);
8245
8246 // Handle WordPress database deletion
8247 global $wpdb;
8248 $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
8249
8250 // Clear cache and delete prompt
8251 wp_cache_delete('prompt_' . $id, 'mxchat_prompts');
8252
8253 $result = $wpdb->delete(
8254 $table_name,
8255 array('id' => intval($id)),
8256 array('%d')
8257 );
8258
8259 if ($result !== false) {
8260 $success = true;
8261 set_transient('mxchat_admin_notice_success',
8262 esc_html__('Entry deleted successfully.', 'mxchat'), 30);
8263 } else {
8264 set_transient('mxchat_admin_notice_error',
8265 esc_html__('Failed to delete entry from database.', 'mxchat'), 30);
8266 }
8267 }
8268
8269 // Redirect back to the prompts page
8270 wp_safe_redirect(add_query_arg(
8271 array(
8272 'page' => 'mxchat-prompts',
8273 'deleted' => $success ? 'true' : 'false'
8274 ),
8275 admin_url('admin.php')
8276 ));
8277 exit;
8278 }
8279
8280
8281
8282
8283 /**
8284 * Averages multiple vectors into a single vector
8285 */
8286 private function mxchat_average_vectors($vectors) {
8287 $vector_length = count($vectors[0]);
8288 $sum_vector = array_fill(0, $vector_length, 0);
8289
8290 foreach ($vectors as $vector) {
8291 for ($i = 0; $i < $vector_length; $i++) {
8292 $sum_vector[$i] += $vector[$i];
8293 }
8294 }
8295
8296 // Divide each component by the number of vectors to get the average
8297 $num_vectors = count($vectors);
8298 for ($i = 0; $i < $vector_length; $i++) {
8299 $sum_vector[$i] /= $num_vectors;
8300 }
8301
8302 return $sum_vector;
8303 }
8304
8305
8306
8307
8308 /**
8309 * Generates embeddings from input text for MXChat
8310 */
8311 public function mxchat_generate_embedding($text) {
8312 // Enable detailed logging for debugging
8313 //error_log('[MXCHAT-EMBED] Starting embedding generation. Text length: ' . strlen($text) . ' bytes');
8314 //error_log('[MXCHAT-EMBED] Text preview: ' . substr($text, 0, 100) . '...');
8315
8316 $options = get_option('mxchat_options');
8317 $selected_model = $options['embedding_model'] ?? 'text-embedding-ada-002';
8318 //error_log('[MXCHAT-EMBED] Selected embedding model: ' . $selected_model);
8319
8320 // Determine provider and endpoint
8321 if (strpos($selected_model, 'voyage') === 0) {
8322 $api_key = $options['voyage_api_key'] ?? '';
8323 $endpoint = 'https://api.voyageai.com/v1/embeddings';
8324 $provider_name = 'Voyage AI';
8325 //error_log('[MXCHAT-EMBED] Using Voyage AI API');
8326 } elseif (strpos($selected_model, 'gemini-embedding') === 0) {
8327 $api_key = $options['gemini_api_key'] ?? '';
8328 $endpoint = 'https://generativelanguage.googleapis.com/v1beta/models/' . $selected_model . ':embedContent';
8329 $provider_name = 'Google Gemini';
8330 //error_log('[MXCHAT-EMBED] Using Google Gemini API');
8331 } else {
8332 $api_key = $options['api_key'] ?? '';
8333 $endpoint = 'https://api.openai.com/v1/embeddings';
8334 $provider_name = 'OpenAI';
8335 //error_log('[MXCHAT-EMBED] Using OpenAI API');
8336 }
8337
8338 //error_log('[MXCHAT-EMBED] Using endpoint: ' . $endpoint);
8339
8340 if (empty($api_key)) {
8341 $error_message = sprintf('Missing %s API key. Please configure your API key in the MxChat settings.', $provider_name);
8342 //error_log('[MXCHAT-EMBED] Error: ' . $error_message);
8343 return $error_message;
8344 }
8345
8346 // Check if text is too long (for OpenAI, roughly estimate tokens as words/0.75)
8347 $estimated_tokens = ceil(str_word_count($text) / 0.75);
8348 //error_log('[MXCHAT-EMBED] Estimated token count: ~' . $estimated_tokens);
8349
8350 if ($estimated_tokens > 8000 && strpos($selected_model, 'voyage') === false && strpos($selected_model, 'gemini-embedding') === false) {
8351 //error_log('[MXCHAT-EMBED] Warning: Text may exceed OpenAI token limits (8K for most models)');
8352 // Consider truncating text here
8353 }
8354
8355 // Prepare request body based on provider
8356 if (strpos($selected_model, 'gemini-embedding') === 0) {
8357 // Gemini API format
8358 $request_body = array(
8359 'model' => 'models/' . $selected_model,
8360 'content' => array(
8361 'parts' => array(
8362 array('text' => $text)
8363 )
8364 )
8365 );
8366
8367 // Set output dimensionality to 1536 for consistency with other models
8368 $request_body['outputDimensionality'] = 1536;
8369 } else {
8370 // OpenAI/Voyage API format
8371 $request_body = array(
8372 'model' => $selected_model,
8373 'input' => $text
8374 );
8375
8376 // Add output_dimension for voyage-3-large model
8377 if ($selected_model === 'voyage-3-large') {
8378 $request_body['output_dimension'] = 2048;
8379 }
8380 }
8381
8382 //error_log('[MXCHAT-EMBED] Request prepared with model: ' . $selected_model);
8383
8384 // Prepare headers based on provider
8385 if (strpos($selected_model, 'gemini-embedding') === 0) {
8386 // Gemini uses API key as query parameter
8387 $endpoint .= '?key=' . $api_key;
8388 $headers = array(
8389 'Content-Type' => 'application/json'
8390 );
8391 } else {
8392 // OpenAI/Voyage use Bearer token
8393 $headers = array(
8394 'Authorization' => 'Bearer ' . $api_key,
8395 'Content-Type' => 'application/json'
8396 );
8397 }
8398
8399 // Make API request
8400 //error_log('[MXCHAT-EMBED] Sending API request to: ' . $endpoint);
8401 $response = wp_remote_post($endpoint, array(
8402 'body' => wp_json_encode($request_body),
8403 'headers' => $headers,
8404 'timeout' => 60 // Increased timeout for large inputs
8405 ));
8406
8407 // Handle wp_remote_post errors
8408 if (is_wp_error($response)) {
8409 $error_message = $response->get_error_message();
8410 //error_log('[MXCHAT-EMBED] WP Remote Post Error: ' . $error_message);
8411 return 'Connection error: ' . $error_message;
8412 }
8413
8414 // Get and check HTTP response code
8415 $http_code = wp_remote_retrieve_response_code($response);
8416 //error_log('[MXCHAT-EMBED] API Response Code: ' . $http_code);
8417
8418 if ($http_code !== 200) {
8419 $error_body = wp_remote_retrieve_body($response);
8420 //error_log('[MXCHAT-EMBED] API Error Response Body: ' . $error_body);
8421
8422 // Try to parse error for more details
8423 $error_json = json_decode($error_body, true);
8424 if (json_last_error() === JSON_ERROR_NONE && isset($error_json['error'])) {
8425 $error_type = $error_json['error']['type'] ?? 'unknown';
8426 $error_message = $error_json['error']['message'] ?? 'No message';
8427 //error_log('[MXCHAT-EMBED] API Error Type: ' . $error_type);
8428 //error_log('[MXCHAT-EMBED] API Error Message: ' . $error_message);
8429
8430 // Customize error message for common API errors
8431 if ($error_type === 'invalid_request_error' && strpos($error_message, 'API key') !== false) {
8432 $error_message = sprintf('Invalid %s API key. Please check your API key in the MxChat settings.', $provider_name);
8433 } elseif ($error_type === 'authentication_error') {
8434 $error_message = sprintf('%s authentication failed. Please verify your API key in the MxChat settings.', $provider_name);
8435 }
8436
8437 //error_log('[MXCHAT-EMBED] Returning error: ' . $error_message);
8438 return $error_message;
8439 }
8440
8441 $error_message = sprintf("API Error (HTTP %d): Unable to generate embedding", $http_code);
8442 //error_log('[MXCHAT-EMBED] Returning error: ' . $error_message);
8443 return $error_message;
8444 }
8445
8446 // Parse response body
8447 $response_body = wp_remote_retrieve_body($response);
8448 //error_log('[MXCHAT-EMBED] Received response length: ' . strlen($response_body) . ' bytes');
8449
8450 $response_data = json_decode($response_body, true);
8451
8452 if (json_last_error() !== JSON_ERROR_NONE) {
8453 $error = json_last_error_msg();
8454 //error_log('[MXCHAT-EMBED] JSON Parse Error: ' . $error);
8455 //error_log('[MXCHAT-EMBED] Response preview: ' . substr($response_body, 0, 200));
8456 return "Failed to parse API response: $error";
8457 }
8458
8459 // Handle different response formats based on provider
8460 if (strpos($selected_model, 'gemini-embedding') === 0) {
8461 // Gemini API response format
8462 if (isset($response_data['embedding']['values'])) {
8463 $embedding_dimensions = count($response_data['embedding']['values']);
8464 //error_log('[MXCHAT-EMBED] Successfully extracted Gemini embedding with ' . $embedding_dimensions . ' dimensions');
8465
8466 // Check if embedding dimensions are as expected (should be 1536)
8467 if ($embedding_dimensions !== 1536) {
8468 //error_log('[MXCHAT-EMBED] Warning: Unexpected Gemini embedding dimensions: ' . $embedding_dimensions);
8469 }
8470
8471 return $response_data['embedding']['values'];
8472 } else {
8473 //error_log('[MXCHAT-EMBED] Error: No embedding found in Gemini response');
8474 //error_log('[MXCHAT-EMBED] Response structure: ' . wp_json_encode(array_keys($response_data)));
8475
8476 if (isset($response_data['error'])) {
8477 $error_message = "Gemini API Error in response: " . wp_json_encode($response_data['error']);
8478 //error_log('[MXCHAT-EMBED] ' . $error_message);
8479 return $error_message;
8480 }
8481
8482 $error_message = "Invalid Gemini API response format: No embedding found";
8483 //error_log('[MXCHAT-EMBED] ' . $error_message);
8484 return $error_message;
8485 }
8486 } else {
8487 // OpenAI/Voyage API response format
8488 if (isset($response_data['data'][0]['embedding'])) {
8489 $embedding_dimensions = count($response_data['data'][0]['embedding']);
8490 //error_log('[MXCHAT-EMBED] Successfully extracted embedding with ' . $embedding_dimensions . ' dimensions');
8491
8492 // Check if embedding dimensions are as expected
8493 if (($selected_model === 'text-embedding-ada-002' && $embedding_dimensions !== 1536) ||
8494 ($selected_model === 'voyage-3-large' && $embedding_dimensions !== 2048)) {
8495 //error_log('[MXCHAT-EMBED] Warning: Unexpected embedding dimensions');
8496 }
8497
8498 return $response_data['data'][0]['embedding'];
8499 } else {
8500 //error_log('[MXCHAT-EMBED] Error: No embedding found in response');
8501 //error_log('[MXCHAT-EMBED] Response structure: ' . wp_json_encode(array_keys($response_data)));
8502
8503 if (isset($response_data['error'])) {
8504 $error_message = "API Error in response: " . wp_json_encode($response_data['error']);
8505 //error_log('[MXCHAT-EMBED] ' . $error_message);
8506 return $error_message;
8507 }
8508
8509 $error_message = "Invalid API response format: No embedding found";
8510 //error_log('[MXCHAT-EMBED] ' . $error_message);
8511 return $error_message;
8512 }
8513 }
8514 }
8515
8516
8517
8518 }
8519 ?>
8520