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

8,547 lines 354.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 if (!defined('ABSPATH')) {
3 exit; // Exit if accessed directly
4 }
5
6 class MxChat_Admin {
7 private $options;
8 private $chat_count;
9 private $is_activated;
10 private $knowledge_manager;
11
12 /**
13 * Get whether the license is activated
14 * @return bool
15 */
16 public function is_activated() {
17 return $this->is_activated;
18 }
19
20 /**
21 * Get plugin options
22 * @return array
23 */
24 public function get_options() {
25 return $this->options;
26 }
27
28 public function __construct($knowledge_manager = null) {
29 $this->options = get_option('mxchat_options');
30 $this->chat_count = get_option('mxchat_chat_count', 0);
31 $this->is_activated = $this->is_license_active();
32 $this->knowledge_manager = $knowledge_manager;
33
34 // Initialize default options if they are not set
35 if (!$this->options) {
36 $this->initialize_default_options();
37 }
38
39 // Add admin menu and initialize settings
40 add_action('admin_menu', array($this, 'mxchat_add_plugin_page'));
41 add_action('admin_init', array($this, 'mxchat_page_init'));
42 add_action('admin_init', array($this, 'mxchat_prompts_page_init'));
43 add_action('admin_enqueue_scripts', array($this, 'mxchat_enqueue_admin_assets'));
44 add_action('wp_ajax_mxchat_delete_chat_history', array($this, 'mxchat_delete_chat_history'));
45 add_action('admin_post_mxchat_delete_prompt', array($this, 'mxchat_handle_delete_prompt'));
46 add_action('wp_ajax_mxchat_fetch_chat_history', array($this, 'mxchat_fetch_chat_history'));
47 add_action('wp_ajax_nopriv_mxchat_fetch_chat_history', array($this, 'mxchat_fetch_chat_history'));
48 add_action('wp_ajax_mxchat_fetch_conversation', array($this, 'mxchat_fetch_conversation'));
49 add_action('wp_footer', array($this, 'mxchat_append_chatbot_to_body'));
50 add_action('admin_head-mxchat-prompts', array($this, 'mxchat_enqueue_admin_assets'));
51 add_action('admin_head-toplevel_page_mxchat-max', array($this, 'mxchat_enqueue_admin_assets'));
52 add_action('admin_notices', array($this, 'mxchat_display_admin_notice'));
53 add_action('admin_post_mxchat_delete_all_prompts', array($this, 'mxchat_handle_delete_all_prompts'));
54 add_action('admin_post_mxchat_add_intent', array($this, 'mxchat_handle_add_intent'));
55 add_action('admin_post_mxchat_delete_intent', array($this, 'mxchat_handle_delete_intent'));
56 add_action('admin_post_mxchat_edit_intent', array($this, 'mxchat_handle_edit_intent'));
57 add_action('wp_ajax_mxchat_export_transcripts', array($this, 'export_chat_transcripts'));
58 add_action('admin_init', array($this, 'mxchat_transcripts_page_init'));
59 add_action('wp_ajax_dismiss_live_agent_notice', array($this, 'dismiss_live_agent_notice'));
60 add_action('wp_ajax_dismiss_theme_migration_notice', array($this, 'dismiss_theme_migration_notice'));
61 add_action('mxchat_cleanup_old_transcripts', array($this, 'cleanup_old_transcripts'));
62
63 add_action('admin_init', array($this, 'register_pinecone_settings'));
64 add_action('admin_init', array($this, 'register_openai_vectorstore_settings'));
65
66 add_action('admin_notices', array($this, 'display_admin_notices'));
67
68 add_action('wp_ajax_mxchat_test_streaming_actual', [$this, 'mxchat_handle_test_streaming_actual']);
69 add_action('wp_ajax_mxchat_test_streaming', [$this, 'mxchat_handle_test_streaming']); // Keep existing as fallback
70 add_action('wp_ajax_mxchat_test_vectorstore_connection', array($this, 'mxchat_test_vectorstore_connection'));
71
72 add_action('wp_ajax_mxchat_save_selected_bot', array($this, 'mxchat_save_selected_bot'));
73 add_action('wp_ajax_mxchat_fetch_openrouter_models', array($this, 'fetch_openrouter_models'));
74 add_action('wp_ajax_mxchat_get_rag_context', array($this, 'mxchat_get_rag_context'));
75
76 // Actions page AJAX handlers
77 add_action('wp_ajax_mxchat_fetch_actions_list', array($this, 'mxchat_fetch_actions_list'));
78 add_action('wp_ajax_mxchat_toggle_action_status', array($this, 'mxchat_toggle_action_status'));
79 add_action('wp_ajax_mxchat_bulk_delete_actions', array($this, 'mxchat_bulk_delete_actions'));
80 add_action('wp_ajax_mxchat_add_intent_ajax', array($this, 'mxchat_add_intent_ajax'));
81 add_action('wp_ajax_mxchat_edit_intent_ajax', array($this, 'mxchat_edit_intent_ajax'));
82 add_action('wp_ajax_mxchat_delete_intent_ajax', array($this, 'mxchat_delete_intent_ajax'));
83
84 // Slack test connection
85 add_action('wp_ajax_mxchat_test_slack_connection', array($this, 'mxchat_test_slack_connection'));
86
87 // Translation handlers
88 add_action('wp_ajax_mxchat_translate_messages', array($this, 'mxchat_translate_messages'));
89 add_action('wp_ajax_mxchat_get_transcript_translation', array($this, 'mxchat_get_transcript_translation'));
90 }
91
92 private function is_license_active() {
93 // Get the raw value without translation
94 $license_status = get_option('mxchat_license_status', 'inactive');
95
96 // Check against multiple possible values, bypassing translation issues
97 return ($license_status === 'active' || $license_status === esc_html__('active', 'mxchat'));
98 }
99
100 private function initialize_default_options() {
101 $default_options = array(
102 'api_key' => '',
103 'xai_api_key' => '',
104 'claude_api_key' => '',
105 'deepseek_api_key' => '',
106 'voyage_api_key' => '',
107 'gemini_api_key' => '',
108 'enable_streaming_toggle' => 'on',
109 'enable_web_search' => 'off',
110 'embedding_model' => 'text-embedding-ada-002',
111 'system_prompt_instructions' => 'You are an AI Chatbot assistant for this website. Your main goal is to assist visitors with questions and provide helpful information. Here are your key guidelines:
112
113 # Response Style - CRITICALLY IMPORTANT
114 - MAXIMUM LENGTH: 1-3 short sentences per response
115 - Ultra-concise: Get straight to the answer with no filler
116 - No introductions like "Sure!" or "I\'d be happy to help"
117 - No phrases like "based on my knowledge" or "according to information"
118 - No explanatory text before giving the answer
119 - No summaries or repetition
120 - Hyperlink all URLs
121 - Respond in user\'s language
122 - Minor chit chat or conversation is okay, but try to keep it focused on [insert topic]
123
124 # Knowledge Base Requirements - PREVENT HALLUCINATIONS
125 - ONLY answer questions using information explicitly provided in OFFICIAL KNOWLEDGE DATABASE CONTENT sections marked with ===== delimiters
126 - If required information is NOT in the knowledge database: "I don\'t have enough information in my knowledge base to answer that question accurately."
127 - NEVER invent or hallucinate URLs, links, product specs, procedures, dates, statistics, names, contacts, or company information
128 - When knowledge base information is unclear or contradictory, acknowledge the limitation rather than guessing
129 - Better to admit insufficient information than provide inaccurate answers',
130 'model' => esc_html__('gpt-5.1-chat-latest', 'mxchat'),
131 'rate_limit_logged_out' => esc_html__('100', 'mxchat'),
132 'role_rate_limits' => array(),
133 'rate_limit_message' => esc_html__('Rate limit exceeded. Please try again later.', 'mxchat'),
134 'enable_email_block' => '',
135 'email_blocker_header_content' => __("<h2>Welcome to Our Chat!</h2>\n<p>Let's get started. Enter your email to begin chatting with us.</p>", 'mxchat'),
136 'email_blocker_button_text' => esc_html__('Start Chat', 'mxchat'),
137 'enable_name_field' => 'off', // NEW
138 'name_field_placeholder' => esc_html__('Enter your name', 'mxchat'), // NEW
139 'top_bar_title' => esc_html__('MxChat', 'mxchat'),
140 'intro_message' => __('Hello! How can I assist you today?', 'mxchat'),
141 'ai_agent_text' => esc_html__('AI Agent', 'mxchat'),
142 'input_copy' => esc_html__('How can I assist?', 'mxchat'),
143 'append_to_body' => esc_html__('off', 'mxchat'),
144 'post_type_visibility_mode' => 'all', // 'all', 'include', 'exclude'
145 'post_type_visibility_list' => array(), // Array of post type slugs
146 'contextual_awareness_toggle' => 'off',
147 'citation_links_toggle' => 'on',
148 'show_frontend_debugger' => 'on',
149 'close_button_color' => esc_html__('#fff', 'mxchat'),
150 'chatbot_bg_color' => esc_html__('#fff', 'mxchat'),
151 'user_message_bg_color' => esc_html__('#fff', 'mxchat'),
152 'user_message_font_color' => esc_html__('#212121', 'mxchat'),
153 'bot_message_bg_color' => esc_html__('#212121', 'mxchat'),
154 'bot_message_font_color' => esc_html__('#fff', 'mxchat'),
155 'top_bar_bg_color' => esc_html__('#212121', 'mxchat'),
156 'send_button_font_color' => esc_html__('#212121', 'mxchat'),
157 'chat_input_font_color' => esc_html__('#212121', 'mxchat'),
158 'chatbot_background_color' => esc_html__('#212121', 'mxchat'),
159 'icon_color' => esc_html__('#fff', 'mxchat'),
160 'enable_woocommerce_integration' => esc_html__('0', 'mxchat'),
161 'link_target_toggle' => esc_html__('off', 'mxchat'),
162 'pre_chat_message' => esc_html__('Hey there! Ask me anything!', 'mxchat'),
163
164 // New fields for Loops Integration
165 'loops_api_key' => '',
166 'loops_mailing_list' => '',
167 'triggered_phrase_response' => __('Would you like to join our mailing list? Please provide your email below.', 'mxchat'),
168 'email_capture_response' => __('Thank you for providing your email! You\'ve been added to our list.', 'mxchat'),
169 'popular_question_1' => '',
170 'popular_question_2' => '',
171 'popular_question_3' => '',
172 'pdf_intent_trigger_text' => __("Please provide the URL to the PDF you'd like to discuss.", 'mxchat'),
173 'pdf_intent_success_text' => __("I've processed the PDF. What questions do you have about it?", 'mxchat'),
174 'pdf_intent_error_text' => __("Sorry, I couldn't process the PDF. Please ensure it's a valid file.", 'mxchat'),
175 'pdf_max_pages' => 69,
176 'show_pdf_upload_button' => 'on',
177 'show_word_upload_button' => 'on',
178
179 // Live Agent Integration (Slack)
180 'live_agent_webhook_url' => '',
181 'live_agent_secret_key' => '',
182 'live_agent_bot_token' => '',
183 'live_agent_message_bg_color' => esc_html__('#ffffff', 'mxchat'),
184 'live_agent_message_font_color' => esc_html__('#333333', 'mxchat'),
185
186 // Telegram Integration
187 'telegram_status' => 'off',
188 'telegram_bot_token' => '',
189 'telegram_group_id' => '',
190 'telegram_webhook_secret' => '',
191 'telegram_notification_message' => __("I've notified a support agent. Please allow a moment for them to respond. If you'd like to continue with AI, just type \"Switch to AI\" at any time.", 'mxchat'),
192 'telegram_away_message' => __("I just checked, and it looks like our support team isn't personally available at the moment. If you'd like, you can leave your email address, and they'll get back to you as soon as possible. In the meantime, you can keep chatting with me — just let me know how I can help!", 'mxchat'),
193
194 'chat_toolbar_toggle' => esc_html__('off', 'mxchat'),
195 'mode_indicator_bg_color' => esc_html__('#767676', 'mxchat'),
196 'mode_indicator_font_color' => esc_html__('#ffffff', 'mxchat'),
197 'toolbar_icon_color' => esc_html__('#212121', 'mxchat'),
198 );
199
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-5.1-chat-latest';
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-5.1-chat-latest';
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 // RAG Sources Limit Slider
4578 add_settings_field(
4579 'rag_sources_limit', // Field ID
4580 esc_html__('RAG Sources Limit', 'mxchat'), // Field title
4581 array($this, 'mxchat_rag_sources_limit_callback'), // Callback function
4582 'mxchat-chatbot', // Page
4583 'mxchat_chatbot_section' // Section
4584 );
4585
4586 add_settings_field(
4587 'append_to_body',
4588 esc_html__('Auto-Display Chatbot', 'mxchat'),
4589 array($this, 'mxchat_append_to_body_callback'),
4590 'mxchat-chatbot',
4591 'mxchat_chatbot_section'
4592 );
4593
4594 add_settings_field(
4595 'contextual_awareness_toggle',
4596 esc_html__('Contextual Awareness', 'mxchat'),
4597 array($this, 'mxchat_contextual_awareness_callback'),
4598 'mxchat-chatbot',
4599 'mxchat_chatbot_section'
4600 );
4601
4602 add_settings_field(
4603 'citation_links_toggle',
4604 esc_html__('Citation Links', 'mxchat'),
4605 array($this, 'mxchat_citation_links_toggle_callback'),
4606 'mxchat-chatbot',
4607 'mxchat_chatbot_section'
4608 );
4609
4610 add_settings_field(
4611 'enable_streaming_toggle',
4612 esc_html__('Enable Streaming', 'mxchat'),
4613 array($this, 'enable_streaming_toggle_callback'),
4614 'mxchat-chatbot',
4615 'mxchat_chatbot_section', // Same section as your working toggle
4616 array(
4617 'class' => 'mxchat-setting-row streaming-setting',
4618 'style' => 'display: none;' // Hidden by default, shown when OpenAI/Claude selected
4619 )
4620 );
4621
4622
4623 add_settings_field(
4624 'model',
4625 esc_html__('Chat Model', 'mxchat'),
4626 array($this, 'mxchat_model_callback'),
4627 'mxchat-chatbot',
4628 'mxchat_chatbot_section'
4629 );
4630
4631 add_settings_field(
4632 'embedding_model',
4633 esc_html__('Embedding Model', 'mxchat'),
4634 array($this, 'embedding_model_callback'),
4635 'mxchat-chatbot',
4636 'mxchat_chatbot_section'
4637 );
4638
4639 add_settings_field(
4640 'system_prompt_instructions',
4641 esc_html__('AI Instructions (Behavior)', 'mxchat'),
4642 array($this, 'system_prompt_instructions_callback'),
4643 'mxchat-chatbot',
4644 'mxchat_chatbot_section'
4645 );
4646
4647
4648 add_settings_field(
4649 'top_bar_title',
4650 esc_html__('Top Bar Title', 'mxchat'),
4651 array($this, 'mxchat_top_bar_title_callback'),
4652 'mxchat-chatbot',
4653 'mxchat_chatbot_section'
4654 );
4655
4656 add_settings_field(
4657 'ai_agent_text',
4658 esc_html__('AI Agent Text', 'mxchat'),
4659 array($this, 'mxchat_ai_agent_text_callback'),
4660 'mxchat-chatbot',
4661 'mxchat_chatbot_section'
4662 );
4663
4664 add_settings_field(
4665 'enable_email_block',
4666 esc_html__('Require Email To Chat', 'mxchat'),
4667 array($this, 'enable_email_block_callback'),
4668 'mxchat-chatbot',
4669 'mxchat_chatbot_section'
4670 );
4671
4672 add_settings_field(
4673 'email_blocker_header_content',
4674 esc_html__('Require Email Chat Content', 'mxchat'),
4675 array($this, 'email_blocker_header_content_callback'),
4676 'mxchat-chatbot',
4677 'mxchat_chatbot_section'
4678 );
4679
4680 add_settings_field(
4681 'email_blocker_button_text',
4682 esc_html__('Require Email Chat Button Text', 'mxchat'),
4683 [$this, 'email_blocker_button_text_callback'],
4684 'mxchat-chatbot',
4685 'mxchat_chatbot_section'
4686 );
4687
4688 add_settings_field(
4689 'enable_name_field',
4690 esc_html__('Require Name Field', 'mxchat'),
4691 array($this, 'enable_name_field_callback'),
4692 'mxchat-chatbot',
4693 'mxchat_chatbot_section'
4694 );
4695
4696 add_settings_field(
4697 'name_field_placeholder',
4698 esc_html__('Name Field Placeholder', 'mxchat'),
4699 array($this, 'name_field_placeholder_callback'),
4700 'mxchat-chatbot',
4701 'mxchat_chatbot_section'
4702 );
4703
4704 add_settings_field(
4705 'intro_message',
4706 esc_html__('Introductory Message', 'mxchat'),
4707 array($this, 'mxchat_intro_message_callback'),
4708 'mxchat-chatbot',
4709 'mxchat_chatbot_section'
4710 );
4711
4712 add_settings_field(
4713 'input_copy',
4714 esc_html__('Input Copy', 'mxchat'),
4715 array($this, 'mxchat_input_copy_callback'),
4716 'mxchat-chatbot',
4717 'mxchat_chatbot_section'
4718 );
4719
4720 add_settings_field(
4721 'pre_chat_message',
4722 esc_html__('Chat Teaser Pop-up', 'mxchat'),
4723 array($this, 'mxchat_pre_chat_message_callback'),
4724 'mxchat-chatbot',
4725 'mxchat_chatbot_section'
4726 );
4727
4728 add_settings_field(
4729 'privacy_toggle',
4730 esc_html__('Toggle Privacy Notice', 'mxchat'),
4731 array($this, 'mxchat_privacy_toggle_callback'),
4732 'mxchat-chatbot',
4733 'mxchat_chatbot_section'
4734 );
4735
4736 add_settings_field(
4737 'complianz_toggle',
4738 esc_html__('Enable Complianz', 'mxchat'),
4739 array($this, 'mxchat_complianz_toggle_callback'),
4740 'mxchat-chatbot',
4741 'mxchat_chatbot_section'
4742 );
4743
4744 add_settings_field(
4745 'link_target_toggle',
4746 esc_html__('Open Links in a New Tab', 'mxchat'),
4747 array($this, 'mxchat_link_target_toggle_callback'),
4748 'mxchat-chatbot',
4749 'mxchat_chatbot_section'
4750 );
4751
4752 add_settings_field(
4753 'chat_persistence_toggle',
4754 esc_html__('Enable Chat Persistence', 'mxchat'),
4755 array($this, 'mxchat_chat_persistence_toggle_callback'),
4756 'mxchat-chatbot',
4757 'mxchat_chatbot_section'
4758 );
4759
4760 add_settings_field(
4761 'popular_question_1',
4762 esc_html__('Quick Question 1', 'mxchat'),
4763 array($this, 'mxchat_popular_question_1_callback'),
4764 'mxchat-chatbot',
4765 'mxchat_chatbot_section'
4766 );
4767
4768 add_settings_field(
4769 'popular_question_2',
4770 esc_html__('Quick Question 2', 'mxchat'),
4771 array($this, 'mxchat_popular_question_2_callback'),
4772 'mxchat-chatbot',
4773 'mxchat_chatbot_section'
4774 );
4775
4776 add_settings_field(
4777 'popular_question_3',
4778 esc_html__('Quick Question 3', 'mxchat'),
4779 array($this, 'mxchat_popular_question_3_callback'),
4780 'mxchat-chatbot',
4781 'mxchat_chatbot_section'
4782 );
4783
4784 add_settings_field(
4785 'additional_popular_questions',
4786 esc_html__('Additional Quick Questions', 'mxchat'),
4787 array($this, 'mxchat_additional_popular_questions_callback'),
4788 'mxchat-chatbot',
4789 'mxchat_chatbot_section'
4790 );
4791
4792
4793 add_settings_field(
4794 'rate_limits',
4795 __('Rate Limits Settings', 'mxchat'),
4796 array($this, 'mxchat_rate_limits_callback'),
4797 'mxchat-chatbot',
4798 'mxchat_chatbot_section'
4799 );
4800
4801 // Loops Settings Section
4802 add_settings_section(
4803 'mxchat_loops_section',
4804 esc_html__('Loops Settings', 'mxchat'),
4805 null,
4806 'mxchat-embed'
4807 );
4808
4809 // Loops Settings Fields (API Key moved to API Keys tab)
4810 add_settings_field(
4811 'loops_mailing_list',
4812 esc_html__('Loops Mailing List', 'mxchat'),
4813 array($this, 'mxchat_loops_mailing_list_callback'),
4814 'mxchat-embed',
4815 'mxchat_loops_section'
4816 );
4817
4818 add_settings_field(
4819 'triggered_phrase_response',
4820 esc_html__('Triggered Phrase Response', 'mxchat'),
4821 array($this, 'mxchat_triggered_phrase_response_callback'),
4822 'mxchat-embed',
4823 'mxchat_loops_section'
4824 );
4825
4826 add_settings_field(
4827 'email_capture_response',
4828 esc_html__('Email Capture Response', 'mxchat'),
4829 array($this, 'mxchat_email_capture_response_callback'),
4830 'mxchat-embed',
4831 'mxchat_loops_section'
4832 );
4833
4834 // Brave Search Settings Fields
4835 add_settings_section(
4836 'mxchat_brave_section',
4837 __('Brave Search Settings', 'mxchat'),
4838 array($this, 'mxchat_brave_section_callback'),
4839 'mxchat-embed'
4840 );
4841
4842 // Brave API Key moved to API Keys tab
4843 add_settings_field(
4844 'brave_image_count',
4845 __('Number of Images to Return', 'mxchat'),
4846 array($this, 'mxchat_brave_image_count_callback'),
4847 'mxchat-embed',
4848 'mxchat_brave_section'
4849 );
4850
4851 add_settings_field(
4852 'brave_safe_search',
4853 __('Safe Search', 'mxchat'),
4854 array($this, 'mxchat_brave_safe_search_callback'),
4855 'mxchat-embed',
4856 'mxchat_brave_section'
4857 );
4858
4859 add_settings_field(
4860 'brave_news_count',
4861 __('Number of News Articles', 'mxchat'),
4862 array($this, 'mxchat_brave_news_count_callback'),
4863 'mxchat-embed',
4864 'mxchat_brave_section'
4865 );
4866
4867 add_settings_field(
4868 'brave_country',
4869 __('Country', 'mxchat'),
4870 array($this, 'mxchat_brave_country_callback'),
4871 'mxchat-embed',
4872 'mxchat_brave_section'
4873 );
4874
4875 add_settings_field(
4876 'brave_language',
4877 __('Language', 'mxchat'),
4878 array($this, 'mxchat_brave_language_callback'),
4879 'mxchat-embed',
4880 'mxchat_brave_section'
4881 );
4882
4883 // Chat with PDF Intent Settings Fields
4884 add_settings_section(
4885 'mxchat_pdf_intent_section',
4886 __('Toolbar Settings & Intents', 'mxchat'),
4887 array($this, 'mxchat_pdf_intent_section_callback'),
4888 'mxchat-embed'
4889 );
4890
4891 add_settings_field(
4892 'chat_toolbar_toggle',
4893 __('Show Chat Toolbar', 'mxchat'),
4894 array($this, 'mxchat_chat_toolbar_toggle_callback'),
4895 'mxchat-embed',
4896 'mxchat_pdf_intent_section'
4897 );
4898
4899 // PDF Upload Button Toggle
4900 add_settings_field(
4901 'show_pdf_upload_button',
4902 __('Show PDF Upload Button', 'mxchat'),
4903 array($this, 'mxchat_show_pdf_upload_button_callback'),
4904 'mxchat-embed',
4905 'mxchat_pdf_intent_section'
4906 );
4907
4908 // Word Upload Button Toggle
4909 add_settings_field(
4910 'show_word_upload_button',
4911 __('Show Word Upload Button', 'mxchat'),
4912 array($this, 'mxchat_show_word_upload_button_callback'),
4913 'mxchat-embed',
4914 'mxchat_pdf_intent_section'
4915 );
4916
4917 add_settings_field(
4918 'pdf_intent_trigger_text',
4919 __('Intent Trigger Text', 'mxchat'),
4920 array($this, 'mxchat_pdf_intent_trigger_text_callback'),
4921 'mxchat-embed',
4922 'mxchat_pdf_intent_section'
4923 );
4924
4925 add_settings_field(
4926 'pdf_intent_success_text',
4927 __('Success Text', 'mxchat'),
4928 array($this, 'mxchat_pdf_intent_success_text_callback'),
4929 'mxchat-embed',
4930 'mxchat_pdf_intent_section'
4931 );
4932
4933 add_settings_field(
4934 'pdf_intent_error_text',
4935 __('Error Text', 'mxchat'),
4936 array($this, 'mxchat_pdf_intent_error_text_callback'),
4937 'mxchat-embed',
4938 'mxchat_pdf_intent_section'
4939 );
4940
4941 // Add PDF Maximum Pages Field
4942 add_settings_field(
4943 'pdf_max_pages',
4944 __('Maximum Document Pages', 'mxchat'),
4945 array($this, 'mxchat_pdf_max_pages_callback'),
4946 'mxchat-embed',
4947 'mxchat_pdf_intent_section'
4948 );
4949
4950 // Live Agent Settings Fields
4951 add_settings_section(
4952 'mxchat_live_agent_section',
4953 __('Live Agent Settings', 'mxchat'),
4954 array($this, 'mxchat_live_agent_section_callback'),
4955 'mxchat-embed'
4956 );
4957
4958 // Live Agent Status Fields (add at top of live agent settings)
4959 add_settings_field(
4960 'live_agent_status',
4961 __('Live Agent Status', 'mxchat'),
4962 array($this, 'mxchat_live_agent_status_callback'),
4963 'mxchat-embed',
4964 'mxchat_live_agent_section'
4965 );
4966
4967 add_settings_field(
4968 'live_agent_notification_message',
4969 __('Notification Message', 'mxchat'),
4970 array($this, 'mxchat_live_agent_notification_message_callback'),
4971 'mxchat-embed',
4972 'mxchat_live_agent_section'
4973 );
4974
4975 add_settings_field(
4976 'live_agent_away_message',
4977 __('Away Message', 'mxchat'),
4978 array($this, 'mxchat_live_agent_away_message_callback'),
4979 'mxchat-embed',
4980 'mxchat_live_agent_section'
4981 );
4982
4983 add_settings_field(
4984 'live_agent_user_ids',
4985 __('Slack Agent User IDs', 'mxchat'),
4986 array($this, 'mxchat_live_agent_user_ids_callback'),
4987 'mxchat-embed',
4988 'mxchat_live_agent_section'
4989 );
4990
4991 add_settings_field(
4992 'live_agent_webhook_url',
4993 __('Slack Webhook URL', 'mxchat'),
4994 array($this, 'mxchat_live_agent_webhook_url_callback'),
4995 'mxchat-embed',
4996 'mxchat_live_agent_section'
4997 );
4998
4999 add_settings_field(
5000 'live_agent_secret_key',
5001 __('Slack Secret Key', 'mxchat'),
5002 array($this, 'mxchat_live_agent_secret_key_callback'),
5003 'mxchat-embed',
5004 'mxchat_live_agent_section'
5005 );
5006
5007 // Live Agent Integration Fields
5008 add_settings_field(
5009 'live_agent_bot_token',
5010 __('Slack Bot OAuth Token', 'mxchat'),
5011 array($this, 'mxchat_live_agent_bot_token_callback'),
5012 'mxchat-embed',
5013 'mxchat_live_agent_section'
5014 );
5015
5016 // Telegram Integration Section
5017 add_settings_section(
5018 'mxchat_telegram_section',
5019 __('Telegram Settings', 'mxchat'),
5020 array($this, 'mxchat_telegram_section_callback'),
5021 'mxchat-embed'
5022 );
5023
5024 add_settings_field(
5025 'telegram_status',
5026 __('Live Agent Status', 'mxchat'),
5027 array($this, 'mxchat_telegram_status_callback'),
5028 'mxchat-embed',
5029 'mxchat_telegram_section'
5030 );
5031
5032 add_settings_field(
5033 'telegram_notification_message',
5034 __('Notification Message', 'mxchat'),
5035 array($this, 'mxchat_telegram_notification_message_callback'),
5036 'mxchat-embed',
5037 'mxchat_telegram_section'
5038 );
5039
5040 add_settings_field(
5041 'telegram_away_message',
5042 __('Away Message', 'mxchat'),
5043 array($this, 'mxchat_telegram_away_message_callback'),
5044 'mxchat-embed',
5045 'mxchat_telegram_section'
5046 );
5047
5048 add_settings_field(
5049 'telegram_bot_token',
5050 __('Telegram Bot Token', 'mxchat'),
5051 array($this, 'mxchat_telegram_bot_token_callback'),
5052 'mxchat-embed',
5053 'mxchat_telegram_section'
5054 );
5055
5056 add_settings_field(
5057 'telegram_group_id',
5058 __('Telegram Group ID', 'mxchat'),
5059 array($this, 'mxchat_telegram_group_id_callback'),
5060 'mxchat-embed',
5061 'mxchat_telegram_section'
5062 );
5063
5064 add_settings_field(
5065 'telegram_webhook_secret',
5066 __('Webhook Secret Token', 'mxchat'),
5067 array($this, 'mxchat_telegram_webhook_secret_callback'),
5068 'mxchat-embed',
5069 'mxchat_telegram_section'
5070 );
5071
5072 // General Settings Section
5073 add_settings_section(
5074 'mxchat_general_section',
5075 esc_html__('YouTube Tutorials', 'mxchat'),
5076 null,
5077 'mxchat-general'
5078 );
5079 }
5080
5081 public function mxchat_prompts_page_init() {
5082 register_setting(
5083 'mxchat_prompts_options',
5084 'mxchat_prompts_options',
5085 array(
5086 'type' => 'array',
5087 'description' => __('MXChat Knowledge Base Settings', 'mxchat'),
5088 'default' => array(
5089 'mxchat_auto_sync_posts' => 0,
5090 'mxchat_auto_sync_pages' => 0,
5091 'mxchat_use_pinecone' => 0,
5092 'mxchat_pinecone_api_key' => '',
5093 'mxchat_pinecone_environment' => '',
5094 'mxchat_pinecone_index' => '',
5095 'mxchat_pinecone_host' => '',
5096 ),
5097 'sanitize_callback' => array($this, 'sanitize_prompts_options'),
5098 )
5099 );
5100
5101 add_action('admin_notices', array($this, 'sync_settings_notice'));
5102 }
5103
5104 public function mxchat_transcripts_page_init() {
5105 register_setting(
5106 'mxchat_transcripts_options',
5107 'mxchat_transcripts_options',
5108 array(
5109 'type' => 'array',
5110 'description' => __('MXChat Transcripts Notification Settings', 'mxchat'),
5111 'default' => array(
5112 'mxchat_enable_notifications' => 0,
5113 'mxchat_notification_email' => get_option('admin_email'),
5114 'mxchat_auto_delete_transcripts' => 'never',
5115 ),
5116 'sanitize_callback' => array($this, 'sanitize_transcripts_options'),
5117 )
5118 );
5119
5120 add_settings_section(
5121 'mxchat_transcripts_notification_section',
5122 esc_html__('Chat Notification Settings', 'mxchat'),
5123 array($this, 'mxchat_transcripts_notification_section_callback'),
5124 'mxchat-transcripts'
5125 );
5126
5127 add_settings_field(
5128 'mxchat_enable_notifications',
5129 esc_html__('Enable Chat Notifications', 'mxchat'),
5130 array($this, 'mxchat_enable_notifications_callback'),
5131 'mxchat-transcripts',
5132 'mxchat_transcripts_notification_section'
5133 );
5134
5135 add_settings_field(
5136 'mxchat_notification_email',
5137 esc_html__('Notification Email Address', 'mxchat'),
5138 array($this, 'mxchat_notification_email_callback'),
5139 'mxchat-transcripts',
5140 'mxchat_transcripts_notification_section'
5141 );
5142
5143 add_settings_field(
5144 'mxchat_auto_delete_transcripts',
5145 esc_html__('Auto-Delete Old Transcripts', 'mxchat'),
5146 array($this, 'mxchat_auto_delete_transcripts_callback'),
5147 'mxchat-transcripts',
5148 'mxchat_transcripts_notification_section'
5149 );
5150
5151 add_settings_field(
5152 'mxchat_auto_email_transcript',
5153 esc_html__('Auto-Email Full Transcript', 'mxchat'),
5154 array($this, 'mxchat_auto_email_transcript_callback'),
5155 'mxchat-transcripts',
5156 'mxchat_transcripts_notification_section'
5157 );
5158 }
5159
5160
5161 /**
5162 * Sanitize all prompts options
5163 *
5164 * @param array $input The unsanitized options array
5165 * @return array The sanitized options array
5166 */
5167 public function sanitize_prompts_options($input) {
5168 // Log the incoming input.
5169 //error_log('Sanitizing inputs: ' . print_r($input, true));
5170
5171 $sanitized = array();
5172
5173 // Boolean options
5174 $sanitized['mxchat_auto_sync_posts'] = isset($input['mxchat_auto_sync_posts']) ? 1 : 0;
5175 $sanitized['mxchat_auto_sync_pages'] = isset($input['mxchat_auto_sync_pages']) ? 1 : 0;
5176 $sanitized['mxchat_use_pinecone'] = !empty($input['mxchat_use_pinecone']) ? 1 : 0;
5177
5178 // API Key: if less than 32 characters, flag as invalid.
5179 $api_key = sanitize_text_field($input['mxchat_pinecone_api_key'] ?? '');
5180 if (!empty($api_key) && strlen($api_key) < 32) {
5181 add_settings_error(
5182 'mxchat_prompts_options',
5183 'invalid_api_key',
5184 __('The Pinecone API key appears to be invalid. Please check your API key.', 'mxchat')
5185 );
5186 $existing_options = get_option('mxchat_prompts_options', array());
5187 $sanitized['mxchat_pinecone_api_key'] = $existing_options['mxchat_pinecone_api_key'] ?? '';
5188 } else {
5189 $sanitized['mxchat_pinecone_api_key'] = $api_key;
5190 }
5191
5192 // Environment and Index Name
5193 $sanitized['mxchat_pinecone_environment'] = sanitize_text_field($input['mxchat_pinecone_environment'] ?? '');
5194 $sanitized['mxchat_pinecone_index'] = sanitize_text_field($input['mxchat_pinecone_index'] ?? '');
5195
5196 // Host: Remove protocol and validate format.
5197 $host = sanitize_text_field($input['mxchat_pinecone_host'] ?? '');
5198 $host = preg_replace('#^https?://#', '', $host);
5199 //error_log('Host after removing protocol: ' . $host);
5200 if (!empty($host)) {
5201 if (!preg_match('/^[\w-]+\.svc\.[\w-]+\.pinecone\.io$/', $host)) {
5202 add_settings_error(
5203 'mxchat_prompts_options',
5204 'invalid_host',
5205 __('The Pinecone host appears to be invalid. It should look like "mxchat-vectors-zrmsquq.svc.aped-4627-b74a.pinecone.io"', 'mxchat')
5206 );
5207 $existing_options = get_option('mxchat_prompts_options', array());
5208 $sanitized['mxchat_pinecone_host'] = $existing_options['mxchat_pinecone_host'] ?? '';
5209 } else {
5210 $sanitized['mxchat_pinecone_host'] = $host;
5211 }
5212 } else {
5213 $sanitized['mxchat_pinecone_host'] = '';
5214 }
5215
5216 //error_log('Final sanitized array: ' . print_r($sanitized, true));
5217
5218 return $sanitized;
5219 }
5220
5221 public function sync_settings_notice() {
5222 // Only show notice on our plugin page
5223 if (!isset($_GET['page']) || $_GET['page'] !== 'mxchat-prompts') {
5224 return;
5225 }
5226
5227 // Check if settings were updated
5228 if (isset($_GET['settings-updated'])) {
5229
5230 ?>
5231 <div class="notice notice-success is-dismissible">
5232 <p><?php esc_html_e('Sync settings updated successfully.', 'mxchat'); ?></p>
5233 <button type="button" class="notice-dismiss"><span class="screen-reader-text"><?php esc_html_e('Dismiss this notice.', 'mxchat'); ?></span></button>
5234 </div>
5235 <?php
5236
5237 }
5238 }
5239 // Add this sanitization function to your class
5240 public function sanitize_sync_setting($input) {
5241 return (bool)$input ? __('1', 'mxchat') : __('', 'mxchat');
5242 }
5243
5244 public function mxchat_rate_limits_callback() {
5245 $all_options = get_option('mxchat_options', []);
5246
5247 // Define available rate limits
5248 $rate_limits = array('1', '3', '5', '10', '15', '20', '50', '100', 'unlimited');
5249
5250 // Define available timeframes
5251 $timeframes = array(
5252 'hourly' => __('Per Hour', 'mxchat'),
5253 'daily' => __('Per Day', 'mxchat'),
5254 'weekly' => __('Per Week', 'mxchat'),
5255 'monthly' => __('Per Month', 'mxchat')
5256 );
5257
5258 // Get all roles plus a "logged_out" pseudo-role
5259 $roles = wp_roles()->get_names();
5260 $roles['logged_out'] = __('Logged Out Users', 'mxchat');
5261
5262 // Start the wrapper
5263 echo '<div class="pro-feature-wrapper active">';
5264 echo '<div class="mxchat-rate-limits-container">';
5265
5266 echo '<p class="description" style="margin-bottom: 20px;">' .
5267 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') .
5268 '</p>';
5269
5270 // Add markdown link documentation
5271 echo '<div class="notice notice-info inline" style="margin-bottom: 20px; padding: 10px;">';
5272 echo '<p><strong>' . esc_html__('Markdown Links Supported:', 'mxchat') . '</strong></p>';
5273 echo '<p>' . esc_html__('You can include clickable links in your custom messages using markdown syntax:', 'mxchat') . '</p>';
5274 echo '<ul style="margin-left: 20px;">';
5275 echo '<li><code>[Link text](https://example.com)</code> - Creates a clickable link</li>';
5276 echo '<li><code>[Visit our pricing](https://example.com/pricing)</code> - Link with custom text</li>';
5277 echo '<li><code>Plain URLs like https://example.com will also become clickable</code></li>';
5278 echo '</ul>';
5279 echo '</div>';
5280
5281 // Output the controls for each role
5282 foreach ($roles as $role_id => $role_name) {
5283 // Get saved options or defaults
5284 $default_limit = ($role_id === 'logged_out') ? '10' : '100';
5285 $default_timeframe = 'daily';
5286 $default_message = __('Rate limit exceeded. Please try again later.', 'mxchat');
5287
5288 $selected_limit = isset($all_options['rate_limits'][$role_id]['limit'])
5289 ? $all_options['rate_limits'][$role_id]['limit']
5290 : $default_limit;
5291
5292 $selected_timeframe = isset($all_options['rate_limits'][$role_id]['timeframe'])
5293 ? $all_options['rate_limits'][$role_id]['timeframe']
5294 : $default_timeframe;
5295
5296 $custom_message = isset($all_options['rate_limits'][$role_id]['message'])
5297 ? $all_options['rate_limits'][$role_id]['message']
5298 : $default_message;
5299
5300 // Output the row
5301 echo '<div class="mxchat-rate-limit-row mxchat-autosave-section">';
5302
5303 // Role label
5304 echo '<div class="mxchat-rate-limit-role">' . esc_html($role_name) . '</div>';
5305
5306 // Controls section
5307 echo '<div class="mxchat-rate-limit-controls-wrapper">';
5308
5309 // Rate limit and timeframe controls
5310 echo '<div class="mxchat-rate-limit-controls">';
5311
5312 // Limit dropdown
5313 echo '<div>';
5314 echo '<label for="rate_limits_' . esc_attr($role_id) . '_limit">' . esc_html__('Limit:', 'mxchat') . '</label>';
5315 echo '<select
5316 id="rate_limits_' . esc_attr($role_id) . '_limit"
5317 name="mxchat_options[rate_limits][' . esc_attr($role_id) . '][limit]"
5318 class="mxchat-autosave-field">';
5319 foreach ($rate_limits as $limit) {
5320 echo '<option value="' . esc_attr($limit) . '" ' . selected($selected_limit, $limit, false) . '>' . esc_html($limit) . '</option>';
5321 }
5322 echo '</select>';
5323 echo '</div>';
5324
5325 // Timeframe dropdown
5326 echo '<div>';
5327 echo '<label for="rate_limits_' . esc_attr($role_id) . '_timeframe">' . esc_html__('Timeframe:', 'mxchat') . '</label>';
5328 echo '<select
5329 id="rate_limits_' . esc_attr($role_id) . '_timeframe"
5330 name="mxchat_options[rate_limits][' . esc_attr($role_id) . '][timeframe]"
5331 class="mxchat-autosave-field">';
5332 foreach ($timeframes as $value => $label) {
5333 echo '<option value="' . esc_attr($value) . '" ' . selected($selected_timeframe, $value, false) . '>' . esc_html($label) . '</option>';
5334 }
5335 echo '</select>';
5336 echo '</div>';
5337
5338 echo '</div>'; // End controls
5339
5340 // Custom message textarea
5341 echo '<div class="mxchat-rate-limit-message">';
5342 echo '<label for="rate_limits_' . esc_attr($role_id) . '_message">' . esc_html__('Custom Message:', 'mxchat') . '</label>';
5343 echo '<textarea
5344 id="rate_limits_' . esc_attr($role_id) . '_message"
5345 name="mxchat_options[rate_limits][' . esc_attr($role_id) . '][message]"
5346 class="mxchat-autosave-field"
5347 placeholder="' . esc_attr__('Enter custom message when rate limit is exceeded', 'mxchat') . '">' .
5348 esc_textarea($custom_message) .
5349 '</textarea>';
5350 echo '<p class="description">' .
5351 esc_html__('Example: Rate limit reached! [Visit our pricing page](https://example.com/pricing) to upgrade.', 'mxchat') .
5352 '</p>';
5353 echo '</div>'; // End message
5354
5355 echo '</div>'; // End controls wrapper
5356
5357 echo '</div>'; // End row
5358 }
5359
5360 echo '</div>'; // End container
5361
5362 echo '</div>'; // End pro-feature-wrapper
5363 }
5364
5365 private function mxchat_add_option_field($id, $title, $callback = '') {
5366 add_settings_field(
5367 $id,
5368 __($title, 'mxchat'),
5369 $callback ? array($this, $callback) : array($this, $id . '_callback'),
5370 'mxchat-max',
5371 'mxchat_setting_section_id',
5372 $id === 'model' ? ['label_for' => 'model'] : []
5373 );
5374 }
5375
5376 // API Keys Section Callback
5377 public function mxchat_api_keys_section_callback() {
5378 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>';
5379 }
5380
5381 // OpenAI API Key
5382 public function api_key_callback() {
5383 $apiKey = isset($this->options['api_key']) ? esc_attr($this->options['api_key']) : '';
5384 $nonce = wp_create_nonce('mxchat_autosave_nonce');
5385
5386 echo '<div class="api-key-wrapper">';
5387 echo '<input type="password" id="api_key" name="api_key" value="' . $apiKey . '" class="regular-text mxchat-autosave-field" autocomplete="off" data-nonce="' . $nonce . '" />';
5388 echo '<button type="button" id="toggleApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5389 echo '<p class="description">' . esc_html__('Required for OpenAI GPT models and OpenAI embeddings. Get your API key from OpenAI Platform.', 'mxchat') . '</p>';
5390 echo '</div>';
5391 }
5392
5393 // X.AI API Key
5394 public function xai_api_key_callback() {
5395 $xaiApiKey = isset($this->options['xai_api_key']) ? esc_attr($this->options['xai_api_key']) : '';
5396 $nonce = wp_create_nonce('mxchat_autosave_nonce');
5397
5398 echo '<div class="api-key-wrapper">';
5399 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 . '" />';
5400 echo '<button type="button" id="toggleXaiApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5401 echo '<p class="description">' . esc_html__('Required for X.AI Grok models. Get your API key from X.AI Console.', 'mxchat') . '</p>';
5402 echo '</div>';
5403 }
5404 // Claude API Key
5405 public function claude_api_key_callback() {
5406 $claudeApiKey = isset($this->options['claude_api_key']) ? esc_attr($this->options['claude_api_key']) : '';
5407 $nonce = wp_create_nonce('mxchat_autosave_nonce');
5408
5409 echo '<div class="api-key-wrapper">';
5410 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 . '" />';
5411 echo '<button type="button" id="toggleClaudeApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5412 echo '<p class="description">' . esc_html__('Required for Anthropic Claude models. Get your API key from Anthropic Console.', 'mxchat') . '</p>';
5413 echo '</div>';
5414 }
5415
5416 // DeepSeek API Key
5417 public function deepseek_api_key_callback() {
5418 $apiKey = isset($this->options['deepseek_api_key']) ? esc_attr($this->options['deepseek_api_key']) : '';
5419 $nonce = wp_create_nonce('mxchat_autosave_nonce');
5420
5421 echo '<div class="api-key-wrapper">';
5422 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 . '" />';
5423 echo '<button type="button" id="toggleDeepSeekApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5424 echo '<p class="description">' . esc_html__('Required for DeepSeek models. Get your API key from DeepSeek Platform.', 'mxchat') . '</p>';
5425 echo '</div>';
5426 }
5427
5428 // Gemini API Key
5429 public function gemini_api_key_callback() {
5430 $geminiApiKey = isset($this->options['gemini_api_key']) ? esc_attr($this->options['gemini_api_key']) : '';
5431 $nonce = wp_create_nonce('mxchat_autosave_nonce');
5432
5433 echo '<div class="api-key-wrapper">';
5434 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 . '" />';
5435 echo '<button type="button" id="toggleGeminiApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5436 echo '<p class="description">' . esc_html__('Required for Google Gemini models and embeddings. Get your API key from Google AI Studio.', 'mxchat') . '</p>';
5437 echo '</div>';
5438 }
5439
5440
5441 // OpenRouter API Key
5442 public function openrouter_api_key_callback() {
5443 $openrouterApiKey = isset($this->options['openrouter_api_key']) ? esc_attr($this->options['openrouter_api_key']) : '';
5444 $nonce = wp_create_nonce('mxchat_autosave_nonce');
5445
5446 echo '<div class="api-key-wrapper">';
5447 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 . '" />';
5448 echo '<button type="button" id="toggleOpenRouterApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5449 echo '<p class="description">' . esc_html__('Required for OpenRouter models. Get your API key from OpenRouter.ai', 'mxchat') . '</p>';
5450 echo '</div>';
5451 }
5452
5453 // Voyage API Key
5454 public function voyage_api_key_callback() {
5455 $apiKey = isset($this->options['voyage_api_key']) ? esc_attr($this->options['voyage_api_key']) : '';
5456 $nonce = wp_create_nonce('mxchat_autosave_nonce');
5457
5458 echo '<div class="api-key-wrapper">';
5459 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 . '" />';
5460 echo '<button type="button" id="toggleVoyageAPIKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5461 echo '<p class="description">' . esc_html__('Required for Voyage AI embedding models. Get your API key from Voyage AI.', 'mxchat') . '</p>';
5462 echo '</div>';
5463 }
5464
5465 public function mxchat_loops_api_key_callback() {
5466 $loops_api_key = isset($this->options['loops_api_key']) ? esc_attr($this->options['loops_api_key']) : '';
5467 $nonce = wp_create_nonce('mxchat_autosave_nonce');
5468
5469 // Hidden fields to "trap" autofill
5470 echo '<input type="text" style="display:none" autocomplete="username" />';
5471 echo '<input type="password" style="display:none" autocomplete="current-password" />';
5472
5473 echo '<div class="api-key-wrapper">';
5474 echo sprintf(
5475 '<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" />',
5476 $loops_api_key,
5477 $nonce
5478 );
5479 echo '<button type="button" id="toggleLoopsApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5480 echo '</div>';
5481 echo '<p class="description">' . esc_html__('Required for Loops email integration. Get your API key from Loops.so', 'mxchat') . '</p>';
5482 }
5483 public function mxchat_loops_mailing_list_callback() {
5484 // Add error handling and type checking
5485 $loops_api_key = '';
5486 $selected_list = '';
5487 $nonce = wp_create_nonce('mxchat_autosave_nonce');
5488
5489 // Safely get the API key
5490 if (isset($this->options['loops_api_key']) && is_string($this->options['loops_api_key'])) {
5491 $loops_api_key = $this->options['loops_api_key'];
5492 }
5493
5494 // Safely get the selected list
5495 if (isset($this->options['loops_mailing_list']) && is_string($this->options['loops_mailing_list'])) {
5496 $selected_list = $this->options['loops_mailing_list'];
5497 }
5498
5499 if (!empty($loops_api_key)) {
5500 $lists = $this->mxchat_fetch_loops_mailing_lists($loops_api_key);
5501 if (is_array($lists) && !empty($lists)) {
5502 echo '<div class="mxchat-field-wrapper">';
5503 echo '<select id="loops_mailing_list" name="loops_mailing_list" class="mxchat-autosave-field" data-nonce="' . $nonce . '">';
5504
5505 // Add a default "Select a list" option
5506 echo '<option value="" ' . selected($selected_list, '', false) . '>' . esc_html__('Select a list', 'mxchat') . '</option>';
5507
5508 foreach ($lists as $list) {
5509 if (is_array($list) && isset($list['id']) && isset($list['name'])) {
5510 echo sprintf(
5511 '<option value="%s" %s>%s</option>',
5512 esc_attr($list['id']),
5513 selected($selected_list, $list['id'], false),
5514 esc_html($list['name'])
5515 );
5516 }
5517 }
5518 echo '</select>';
5519 echo '</div>';
5520 echo '<p class="description">' . esc_html__('Please select a mailing list to use with Loops.', 'mxchat') . '</p>';
5521 } else {
5522 echo '<p class="description">' . esc_html__('No lists found. Please verify your API Key.', 'mxchat') . '</p>';
5523 }
5524 } else {
5525 echo '<p class="description">' . esc_html__('Enter a valid Loops API Key to load mailing lists.', 'mxchat') . '</p>';
5526 }
5527 }
5528 public function mxchat_triggered_phrase_response_callback() {
5529 $default_response = __('Would you like to join our mailing list? Please provide your email below.', 'mxchat');
5530 $triggered_response = isset($this->options['triggered_phrase_response'])
5531 ? $this->options['triggered_phrase_response']
5532 : $default_response;
5533 $nonce = wp_create_nonce('mxchat_autosave_nonce');
5534
5535 echo '<div class="mxchat-field-wrapper">';
5536 echo sprintf(
5537 '<textarea id="triggered_phrase_response" name="triggered_phrase_response" rows="3" cols="50" class="mxchat-autosave-field" data-nonce="%s">%s</textarea>',
5538 $nonce,
5539 esc_textarea($triggered_response)
5540 );
5541 echo '</div>';
5542 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>';
5543 }
5544
5545 public function mxchat_email_capture_response_callback() {
5546 $default_response = __('Thank you for providing your email! You\'ve been added to our list.', 'mxchat');
5547 $email_capture_response = isset($this->options['email_capture_response'])
5548 ? $this->options['email_capture_response']
5549 : $default_response;
5550 $nonce = wp_create_nonce('mxchat_autosave_nonce');
5551
5552 echo '<div class="mxchat-field-wrapper">';
5553 echo sprintf(
5554 '<textarea id="email_capture_response" name="email_capture_response" rows="3" cols="50" class="mxchat-autosave-field" data-nonce="%s">%s</textarea>',
5555 $nonce,
5556 esc_textarea($email_capture_response)
5557 );
5558 echo '</div>';
5559 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>';
5560 }
5561 public function mxchat_pre_chat_message_callback() {
5562 // Load the entire 'mxchat_options' array
5563 $all_options = get_option('mxchat_options', []);
5564
5565 // Retrieve the saved message or use the default value
5566 $default_message = __('Hey there! Ask me anything!', 'mxchat');
5567 $pre_chat_message = isset($all_options['pre_chat_message']) ? $all_options['pre_chat_message'] : $default_message;
5568
5569 // Output the textarea
5570 printf(
5571 '<textarea id="pre_chat_message" name="pre_chat_message" rows="5" cols="50">%s</textarea>',
5572 esc_textarea($pre_chat_message)
5573 );
5574 }
5575
5576 // Callback for AI Instructions textarea
5577 public function system_prompt_instructions_callback() {
5578 // Retrieve the current value of the system prompt instructions
5579 $instructions = isset($this->options['system_prompt_instructions']) ? esc_textarea($this->options['system_prompt_instructions']) : '';
5580 // Render the textarea field
5581 printf(
5582 '<textarea id="system_prompt_instructions" name="system_prompt_instructions" rows="5" cols="50">%s</textarea>',
5583 $instructions
5584 );
5585 // Personalization hint
5586 echo '<p class="description" style="margin-top: 8px;">';
5587 echo esc_html__('Use {visitor_name} to personalize AI responses when lead capture is enabled.', 'mxchat') . '<br>';
5588 echo '<code style="font-size: 12px;">' . esc_html__('Example: The visitor\'s name is {visitor_name}. Address them by name.', 'mxchat') . '</code>';
5589 echo '</p>';
5590 // Sample instructions button
5591 echo '<div class="mxchat-instructions-container">';
5592 echo '<button type="button" class="mxchat-instructions-btn" id="mxchatViewSampleBtn">';
5593 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">';
5594 echo '<path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/>';
5595 echo '<path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/>';
5596 echo '</svg>';
5597 echo esc_html__('View Sample Instructions', 'mxchat');
5598 echo '</button>';
5599 echo '</div>';
5600
5601 // Add modal to WordPress admin footer instead of inline
5602 add_action('admin_footer', array($this, 'render_sample_instructions_modal'));
5603 }
5604
5605 // New method to render modal in admin footer
5606 public function render_sample_instructions_modal() {
5607 static $modal_rendered = false;
5608 if ($modal_rendered) return; // Prevent duplicate modals
5609 $modal_rendered = true;
5610
5611 echo '<div class="mxchat-instructions-modal-overlay" id="mxchatSampleModal">';
5612 echo '<div class="mxchat-instructions-modal-content">';
5613 echo '<div class="mxchat-instructions-modal-header">';
5614 echo '<h3 class="mxchat-instructions-modal-title">';
5615 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">';
5616 echo '<path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/>';
5617 echo '<path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/>';
5618 echo '</svg>';
5619 echo esc_html__('Sample AI Instructions', 'mxchat');
5620 echo '</h3>';
5621 echo '<button type="button" class="mxchat-instructions-modal-close" id="mxchatModalClose">';
5622 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">';
5623 echo '<line x1="18" y1="6" x2="6" y2="18"/>';
5624 echo '<line x1="6" y1="6" x2="18" y2="18"/>';
5625 echo '</svg>';
5626 echo '</button>';
5627 echo '</div>';
5628 echo '<div class="mxchat-instructions-modal-body">';
5629 echo '<div class="mxchat-instructions-content">';
5630 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:
5631
5632 # Response Style - CRITICALLY IMPORTANT
5633 - MAXIMUM LENGTH: 1-3 short sentences per response
5634 - Ultra-concise: Get straight to the answer with no filler
5635 - No introductions like "Sure!" or "I\'d be happy to help"
5636 - No phrases like "based on my knowledge" or "according to information"
5637 - No explanatory text before giving the answer
5638 - No summaries or repetition
5639 - Hyperlink all URLs
5640 - Respond in user\'s language
5641 - Minor chit chat or conversation is okay, but try to keep it focused on [insert topic]
5642
5643 # Knowledge Base Requirements - PREVENT HALLUCINATIONS
5644 - ONLY answer using information explicitly provided in OFFICIAL KNOWLEDGE DATABASE CONTENT sections marked with ===== delimiters
5645 - If required information is NOT in the knowledge database: "I don\'t have enough information in my knowledge base to answer that question accurately."
5646 - NEVER invent or hallucinate URLs, links, product specs, procedures, dates, statistics, names, contacts, or company information
5647 - When knowledge base information is unclear or contradictory, acknowledge the limitation rather than guessing
5648 - Better to admit insufficient information than provide inaccurate answers');
5649 echo '</div>';
5650 echo '<button type="button" class="mxchat-instructions-copy-btn" id="mxchatCopyBtn">';
5651 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">';
5652 echo '<rect x="9" y="9" width="13" height="13" rx="2" ry="2"/>';
5653 echo '<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/>';
5654 echo '</svg>';
5655 echo esc_html__('Copy Instructions', 'mxchat');
5656 echo '</button>';
5657 echo '</div>';
5658 echo '<div class="mxchat-instructions-modal-footer">';
5659 echo '<button type="button" class="mxchat-instructions-btn-secondary" id="mxchatCloseBtn">' . esc_html__('Close', 'mxchat') . '</button>';
5660 echo '</div>';
5661 echo '</div>';
5662 echo '</div>';
5663 }
5664
5665
5666 public function mxchat_model_callback() {
5667 // Define available models grouped by provider
5668 $models = array(
5669 esc_html__('OpenRouter (100+ Models)', 'mxchat') => array(
5670 'openrouter' => esc_html__('OpenRouter - Select after entering API key', 'mxchat'),
5671 ),
5672 esc_html__('Google Gemini Models', 'mxchat') => array(
5673 'gemini-3-pro-preview' => esc_html__('Gemini 3 Pro (Most Intelligent, Multimodal)', 'mxchat'),
5674 'gemini-3-flash-preview' => esc_html__('Gemini 3 Flash (Balanced Speed & Scale)', 'mxchat'),
5675 'gemini-2.5-pro' => esc_html__('Gemini 2.5 Pro (Advanced Thinking)', 'mxchat'),
5676 'gemini-2.5-flash' => esc_html__('Gemini 2.5 Flash (Best Price-Performance)', 'mxchat'),
5677 'gemini-2.5-flash-lite' => esc_html__('Gemini 2.5 Flash-Lite (Ultra Fast)', 'mxchat'),
5678 'gemini-2.0-flash' => esc_html__('Gemini 2.0 Flash (Deprecated Mar 2026)', 'mxchat'),
5679 'gemini-2.0-flash-lite' => esc_html__('Gemini 2.0 Flash-Lite (Deprecated Mar 2026)', 'mxchat'),
5680 'gemini-1.5-pro' => esc_html__('Gemini 1.5 Pro (Deprecated Sep 2025)', 'mxchat'),
5681 'gemini-1.5-flash' => esc_html__('Gemini 1.5 Flash (Deprecated Sep 2025)', 'mxchat'),
5682 ),
5683 esc_html__('X.AI Models', 'mxchat') => array(
5684 'grok-3-beta' => esc_html__('Grok-3 (Powerful)', 'mxchat'),
5685 'grok-3-fast-beta' => esc_html__('Grok-3 Fast (High Performance)', 'mxchat'),
5686 'grok-3-mini-beta' => esc_html__('Grok-3 Mini (Affordable)', 'mxchat'),
5687 'grok-3-mini-fast-beta' => esc_html__('Grok-3 Mini Fast (Quick Response)', 'mxchat'),
5688 'grok-2' => esc_html__('Grok 2', 'mxchat'),
5689 'grok-4-0709' => esc_html__('Grok 4 (Latest Flagship)', 'mxchat'),
5690 'grok-4-1-fast-reasoning' => esc_html__('Grok 4.1 Fast (Reasoning)', 'mxchat'),
5691 'grok-4-1-fast-non-reasoning' => esc_html__('Grok 4.1 Fast (Non-Reasoning)', 'mxchat'),
5692 ),
5693 esc_html__('DeepSeek Models', 'mxchat') => array(
5694 'deepseek-chat' => esc_html__('DeepSeek-V3', 'mxchat'),
5695 ),
5696 esc_html__('Claude Models', 'mxchat') => array(
5697 'claude-opus-4-6' => esc_html__('Claude Opus 4.6 (Most Capable - Recommended)', 'mxchat'),
5698 'claude-opus-4-5' => esc_html__('Claude Opus 4.5 (Highly Capable)', 'mxchat'),
5699 'claude-sonnet-4-5-20250929' => esc_html__('Claude Sonnet 4.5 (Best for Agents & Coding)', 'mxchat'),
5700 'claude-opus-4-1-20250805' => esc_html__('Claude Opus 4.1 (Exceptional for Complex Tasks)', 'mxchat'),
5701 'claude-haiku-4-5-20251001' => esc_html__('Claude Haiku 4.5 (Fastest & Most Intelligent)', 'mxchat'),
5702 'claude-opus-4-20250514' => esc_html__('Claude 4 Opus (Complex Tasks)', 'mxchat'),
5703 'claude-sonnet-4-20250514' => esc_html__('Claude 4 Sonnet (High Performance)', 'mxchat'),
5704 ),
5705 esc_html__('OpenAI Models', 'mxchat') => array(
5706 'gpt-5.2' => esc_html__('GPT-5.2 (Best General-Purpose & Agentic Model)', 'mxchat'),
5707 'gpt-5.1-chat-latest' => esc_html__('GPT-5.1 Chat Latest (Recommended)', 'mxchat'),
5708 'gpt-5.1-2025-11-13' => esc_html__('GPT-5.1 (Flagship for Coding & Agentic Tasks)', 'mxchat'),
5709 'gpt-5' => esc_html__('GPT-5 (Flagship for Coding, Reasoning & Agents)', 'mxchat'),
5710 'gpt-5-mini' => esc_html__('GPT-5 Mini (Fast and Lightweight)', 'mxchat'),
5711 'gpt-5-nano' => esc_html__('GPT-5 Nano (Fastest & Cheapest for Summarization/Classification)', 'mxchat'),
5712 ),
5713 );
5714
5715 // Retrieve the currently selected model from saved options
5716 $selected_model = isset($this->options['model']) ? esc_attr($this->options['model']) : 'gpt-5.1-chat-latest';
5717
5718 // Begin the select dropdown
5719 echo '<select id="model" name="model">';
5720
5721 // Iterate over groups of models
5722 foreach ($models as $group_label => $group_models) {
5723 echo '<optgroup label="' . esc_attr($group_label) . '">';
5724
5725 foreach ($group_models as $model_value => $model_label) {
5726 echo '<option value="' . esc_attr($model_value) . '" ' . selected($selected_model, $model_value, false) . '>' . esc_html($model_label) . '</option>';
5727 }
5728
5729 echo '</optgroup>';
5730 }
5731
5732 echo '</select>';
5733
5734 // Add a note for OpenRouter
5735 echo '<p class="description" id="openrouter-model-note" style="display:none; color: #d63638; font-weight: 500;">';
5736 echo '<span class="dashicons dashicons-info" style="font-size: 16px; vertical-align: middle;"></span> ';
5737 echo esc_html__('After entering your OpenRouter API key above, click the button below to load available models.', 'mxchat');
5738 echo '</p>';
5739
5740 // API Key Status Messages (hidden by default, shown by JS based on selected model)
5741 $has_openai_key = !empty($this->options['api_key']);
5742 $has_claude_key = !empty($this->options['claude_api_key']);
5743 $has_xai_key = !empty($this->options['xai_api_key']);
5744 $has_deepseek_key = !empty($this->options['deepseek_api_key']);
5745 $has_gemini_key = !empty($this->options['gemini_api_key']);
5746 $has_openrouter_key = !empty($this->options['openrouter_api_key']);
5747
5748 // OpenAI/GPT models
5749 echo '<p class="mxchat-api-status" data-provider="openai" style="display:none;">';
5750 if ($has_openai_key) {
5751 echo '<span style="color: #00a32a;">✓ ' . esc_html__('API key for OpenAI detected', 'mxchat') . '</span>';
5752 } else {
5753 echo '<span style="color: #d63638;">⚠ ' . esc_html__('No API key for OpenAI detected. Please enter API key in API Keys tab.', 'mxchat') . '</span>';
5754 }
5755 echo '</p>';
5756
5757 // Claude models
5758 echo '<p class="mxchat-api-status" data-provider="claude" style="display:none;">';
5759 if ($has_claude_key) {
5760 echo '<span style="color: #00a32a;">✓ ' . esc_html__('API key for Anthropic (Claude) detected', 'mxchat') . '</span>';
5761 } else {
5762 echo '<span style="color: #d63638;">⚠ ' . esc_html__('No API key for Anthropic (Claude) detected. Please enter API key in API Keys tab.', 'mxchat') . '</span>';
5763 }
5764 echo '</p>';
5765
5766 // X.AI models
5767 echo '<p class="mxchat-api-status" data-provider="xai" style="display:none;">';
5768 if ($has_xai_key) {
5769 echo '<span style="color: #00a32a;">✓ ' . esc_html__('API key for X.AI (Grok) detected', 'mxchat') . '</span>';
5770 } else {
5771 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>';
5772 }
5773 echo '</p>';
5774
5775 // DeepSeek models
5776 echo '<p class="mxchat-api-status" data-provider="deepseek" style="display:none;">';
5777 if ($has_deepseek_key) {
5778 echo '<span style="color: #00a32a;">✓ ' . esc_html__('API key for DeepSeek detected', 'mxchat') . '</span>';
5779 } else {
5780 echo '<span style="color: #d63638;">⚠ ' . esc_html__('No API key for DeepSeek detected. Please enter API key in API Keys tab.', 'mxchat') . '</span>';
5781 }
5782 echo '</p>';
5783
5784 // Gemini models
5785 echo '<p class="mxchat-api-status" data-provider="gemini" style="display:none;">';
5786 if ($has_gemini_key) {
5787 echo '<span style="color: #00a32a;">✓ ' . esc_html__('API key for Google Gemini detected', 'mxchat') . '</span>';
5788 } else {
5789 echo '<span style="color: #d63638;">⚠ ' . esc_html__('No API key for Google Gemini detected. Please enter API key in API Keys tab.', 'mxchat') . '</span>';
5790 }
5791 echo '</p>';
5792
5793 // OpenRouter models
5794 echo '<p class="mxchat-api-status" data-provider="openrouter" style="display:none;">';
5795 if ($has_openrouter_key) {
5796 echo '<span style="color: #00a32a;">✓ ' . esc_html__('API key for OpenRouter detected', 'mxchat') . '</span>';
5797 } else {
5798 echo '<span style="color: #d63638;">⚠ ' . esc_html__('No API key for OpenRouter detected. Please enter API key in API Keys tab.', 'mxchat') . '</span>';
5799 }
5800 echo '</p>';
5801
5802 // ADD THESE HIDDEN FIELDS RIGHT HERE:
5803 $openrouter_model = isset($this->options['openrouter_selected_model']) ? esc_attr($this->options['openrouter_selected_model']) : '';
5804 $openrouter_model_name = isset($this->options['openrouter_selected_model_name']) ? esc_attr($this->options['openrouter_selected_model_name']) : '';
5805
5806 echo '<input type="hidden" id="openrouter_selected_model" name="openrouter_selected_model" value="' . $openrouter_model . '" />';
5807 echo '<input type="hidden" id="openrouter_selected_model_name" name="openrouter_selected_model_name" value="' . $openrouter_model_name . '" />';
5808 }
5809
5810 // Update your existing callback method
5811 public function enable_streaming_toggle_callback() {
5812 // Get value from options array, default to 'on'
5813 $enabled = isset($this->options['enable_streaming_toggle']) ? $this->options['enable_streaming_toggle'] : 'on';
5814 $checked = ($enabled === 'on') ? 'checked' : '';
5815
5816 echo '<label class="toggle-switch">';
5817 echo sprintf(
5818 '<input type="checkbox" id="enable_streaming_toggle" name="enable_streaming_toggle" value="on" %s />',
5819 esc_attr($checked)
5820 );
5821 echo '<span class="slider"></span>';
5822 echo '</label>';
5823
5824 // Test button with better styling
5825 echo '<div style="margin-top: 15px;">';
5826 echo '<button type="button" id="mxchat-test-streaming-btn" class="button button-secondary">';
5827 echo '<span class="dashicons dashicons-admin-tools" style="vertical-align: middle; margin-right: 5px;"></span>';
5828 echo esc_html__('Test Streaming Compatibility', 'mxchat');
5829 echo '</button>';
5830 echo '<p id="mxchat-test-streaming-result" style="margin-top:10px; font-weight: bold;"></p>';
5831 echo '</div>';
5832 }
5833
5834 // Web Search toggle callback
5835 public function enable_web_search_toggle_callback() {
5836 // Get value from options array, default to 'off'
5837 $enabled = isset($this->options['enable_web_search']) ? $this->options['enable_web_search'] : 'off';
5838 $checked = ($enabled === 'on') ? 'checked' : '';
5839
5840 // Get current model to determine if we should show/enable the toggle
5841 $current_model = isset($this->options['model']) ? $this->options['model'] : 'gpt-5.1-chat-latest';
5842
5843 // Models that DON'T support web search (per OpenAI docs)
5844 // gpt-5 with minimal reasoning is handled at API level, gpt-4.1-nano doesn't support it
5845 $unsupported_models = array('gpt-4.1-nano');
5846
5847 // Check if current model is an OpenAI model that supports web search
5848 $openai_models = array(
5849 'gpt-5.2', 'gpt-5.1-chat-latest', 'gpt-5.1-2025-11-13', 'gpt-5', 'gpt-5-mini', 'gpt-5-nano'
5850 );
5851
5852 $is_openai = in_array($current_model, $openai_models);
5853 $is_supported = $is_openai && !in_array($current_model, $unsupported_models);
5854
5855 // Wrapper div with data attributes for JS to show/hide
5856 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;"' : '') . '>';
5857
5858 echo '<label class="toggle-switch">';
5859 echo sprintf(
5860 '<input type="checkbox" id="enable_web_search" name="enable_web_search" value="on" %s />',
5861 esc_attr($checked)
5862 );
5863 echo '<span class="slider"></span>';
5864 echo '</label>';
5865
5866 echo '</div>';
5867
5868 // Message shown when non-OpenAI model is selected
5869 echo '<p id="web-search-unavailable-message" class="description" style="color: #666;' . ($is_supported ? ' display:none;' : '') . '">';
5870 echo '<span class="dashicons dashicons-info" style="font-size: 16px; vertical-align: middle; margin-right: 4px;"></span>';
5871 echo esc_html__('Web search is only available for OpenAI models.', 'mxchat');
5872 echo '</p>';
5873 }
5874
5875 // AJAX handler to fetch OpenRouter models
5876 public function fetch_openrouter_models() {
5877 check_ajax_referer('mxchat_fetch_openrouter_models', 'nonce');
5878
5879 $api_key = isset($_POST['api_key']) ? sanitize_text_field($_POST['api_key']) : '';
5880
5881 if (empty($api_key)) {
5882 wp_send_json_error(array('message' => 'API key is required'));
5883 }
5884
5885 $response = wp_remote_get('https://openrouter.ai/api/v1/models', array(
5886 'headers' => array(
5887 'Authorization' => 'Bearer ' . $api_key,
5888 'Content-Type' => 'application/json',
5889 ),
5890 'timeout' => 15,
5891 ));
5892
5893 if (is_wp_error($response)) {
5894 wp_send_json_error(array('message' => $response->get_error_message()));
5895 }
5896
5897 $body = wp_remote_retrieve_body($response);
5898 $data = json_decode($body, true);
5899
5900 if (isset($data['data']) && is_array($data['data'])) {
5901 // Format the models for the frontend
5902 $models = array_map(function($model) {
5903 return array(
5904 'id' => $model['id'],
5905 'name' => $model['name'] ?? $model['id'],
5906 'description' => $model['description'] ?? '',
5907 'context_length' => $model['context_length'] ?? 0,
5908 'pricing' => array(
5909 'prompt' => $model['pricing']['prompt'] ?? 0,
5910 'completion' => $model['pricing']['completion'] ?? 0,
5911 ),
5912 );
5913 }, $data['data']);
5914
5915 wp_send_json_success(array('models' => $models));
5916 } else {
5917 wp_send_json_error(array('message' => 'Invalid response from OpenRouter'));
5918 }
5919 }
5920
5921 // Callback function for embedding model selection
5922 public function embedding_model_callback() {
5923 $models = array(
5924 esc_html__('OpenAI Embeddings', 'mxchat') => array(
5925 'text-embedding-3-small' => esc_html__('TE3 Small (1536, Efficient)', 'mxchat'),
5926 'text-embedding-ada-002' => esc_html__('Ada 2 (1536, Recommended)', 'mxchat'),
5927 'text-embedding-3-large' => esc_html__('TE3 Large (3072, Powerful)', 'mxchat'),
5928 ),
5929 esc_html__('Voyage AI Embeddings', 'mxchat') => array(
5930 'voyage-3-large' => esc_html__('Voyage-3 Large (2048, Most Capable)', 'mxchat'),
5931 ),
5932 esc_html__('Google Gemini Embeddings', 'mxchat') => array(
5933 'gemini-embedding-001' => esc_html__('Gemini Embedding (1536, Stable)', 'mxchat'),
5934 )
5935 );
5936 $selected_model = isset($this->options['embedding_model']) ? esc_attr($this->options['embedding_model']) : 'text-embedding-ada-002';
5937 echo '<select id="embedding_model" name="embedding_model">';
5938 foreach ($models as $group_label => $group_models) {
5939 echo '<optgroup label="' . esc_attr($group_label) . '">';
5940 foreach ($group_models as $model_value => $model_label) {
5941 echo '<option value="' . esc_attr($model_value) . '" ' . selected($selected_model, $model_value, false) . '>' . esc_html($model_label) . '</option>';
5942 }
5943 echo '</optgroup>';
5944 }
5945 echo '</select>';
5946
5947 // API Key Status Messages for Embedding Models
5948 $has_openai_key = !empty($this->options['api_key']);
5949 $has_voyage_key = !empty($this->options['voyage_api_key']);
5950 $has_gemini_key = !empty($this->options['gemini_api_key']);
5951
5952 // OpenAI Embeddings
5953 echo '<p class="mxchat-embedding-api-status" data-provider="openai" style="display:none;">';
5954 if ($has_openai_key) {
5955 echo '<span style="color: #00a32a;">✓ ' . esc_html__('API key for OpenAI detected', 'mxchat') . '</span>';
5956 } else {
5957 echo '<span style="color: #d63638;">⚠ ' . esc_html__('No API key for OpenAI detected. Please enter API key in API Keys tab.', 'mxchat') . '</span>';
5958 }
5959 echo '</p>';
5960
5961 // Voyage AI Embeddings
5962 echo '<p class="mxchat-embedding-api-status" data-provider="voyage" style="display:none;">';
5963 if ($has_voyage_key) {
5964 echo '<span style="color: #00a32a;">✓ ' . esc_html__('API key for Voyage AI detected', 'mxchat') . '</span>';
5965 } else {
5966 echo '<span style="color: #d63638;">⚠ ' . esc_html__('No API key for Voyage AI detected. Please enter API key in API Keys tab.', 'mxchat') . '</span>';
5967 }
5968 echo '</p>';
5969
5970 // Gemini Embeddings
5971 echo '<p class="mxchat-embedding-api-status" data-provider="gemini" style="display:none;">';
5972 if ($has_gemini_key) {
5973 echo '<span style="color: #00a32a;">✓ ' . esc_html__('API key for Google Gemini detected', 'mxchat') . '</span>';
5974 } else {
5975 echo '<span style="color: #d63638;">⚠ ' . esc_html__('No API key for Google Gemini detected. Please enter API key in API Keys tab.', 'mxchat') . '</span>';
5976 }
5977 echo '</p>';
5978 }
5979
5980
5981 public function mxchat_top_bar_title_callback() {
5982 // Retrieve the current value of the top bar title from saved options
5983 $top_bar_title = isset($this->options['top_bar_title']) ? esc_attr($this->options['top_bar_title']) : '';
5984
5985 // Render the input field
5986 echo '<input type="text" id="top_bar_title" name="top_bar_title" value="' . $top_bar_title . '" />';
5987 }
5988 public function mxchat_ai_agent_text_callback() {
5989 // Retrieve the current value of the AI agent text from saved options
5990 $ai_agent_text = isset($this->options['ai_agent_text']) ? esc_attr($this->options['ai_agent_text']) : '';
5991 // Render the input field
5992 echo '<input type="text" id="ai_agent_text" name="ai_agent_text" value="' . $ai_agent_text . '" />';
5993 }
5994
5995
5996 public function enable_email_block_callback() {
5997 // Load full plugin options array
5998 $all_options = get_option('mxchat_options', []);
5999
6000 // Get the value, default to 'off'
6001 $enable_email_block = isset($all_options['enable_email_block']) ? $all_options['enable_email_block'] : 'off';
6002
6003 // Check if it's 'on'
6004 $checked = ($enable_email_block === 'on') ? 'checked' : '';
6005
6006 echo '<label class="toggle-switch">';
6007 echo sprintf(
6008 '<input type="checkbox" id="enable_email_block" name="enable_email_block" value="on" %s />',
6009 esc_attr($checked)
6010 );
6011 echo '<span class="slider"></span>';
6012 echo '</label>';
6013 }
6014
6015 public function email_blocker_header_content_callback() {
6016 // Load the entire 'mxchat_options' array
6017 $all_options = get_option('mxchat_options', []);
6018
6019 // Retrieve the saved content or default to empty
6020 $content = isset($all_options['email_blocker_header_content'])
6021 ? $all_options['email_blocker_header_content']
6022 : '';
6023
6024 // Render the textarea - IMPORTANT: name should be just "email_blocker_header_content"
6025 echo '<textarea
6026 id="email_blocker_header_content"
6027 name="email_blocker_header_content"
6028 rows="5"
6029 cols="70"
6030 data-setting="email_blocker_header_content"
6031 >' . esc_textarea($content) . '</textarea>';
6032 }
6033
6034 public function email_blocker_button_text_callback() {
6035 // Load the entire 'mxchat_options' array
6036 $all_options = get_option('mxchat_options', []);
6037
6038 // Retrieve the saved button text or default to empty
6039 $button_text = isset($all_options['email_blocker_button_text'])
6040 ? $all_options['email_blocker_button_text']
6041 : '';
6042
6043 // Use esc_attr to safely render the existing text
6044 echo '<input type="text" id="email_blocker_button_text" name="email_blocker_button_text" value="' . esc_attr($button_text) . '" style="width: 300px;" />';
6045 }
6046
6047 //Enable name field callback
6048 public function enable_name_field_callback() {
6049 // Load full plugin options array
6050 $all_options = get_option('mxchat_options', []);
6051 // Get the value, default to 'off'
6052 $enable_name_field = isset($all_options['enable_name_field']) ? $all_options['enable_name_field'] : 'off';
6053 // Check if it's 'on'
6054 $checked = ($enable_name_field === 'on') ? 'checked' : '';
6055 echo '<label class="toggle-switch">';
6056 echo sprintf(
6057 '<input type="checkbox" id="enable_name_field" name="enable_name_field" value="on" %s />',
6058 esc_attr($checked)
6059 );
6060 echo '<span class="slider"></span>';
6061 echo '</label>';
6062 }
6063 //Name field placeholder callback
6064 public function name_field_placeholder_callback() {
6065 $all_options = get_option('mxchat_options', []);
6066 $placeholder = isset($all_options['name_field_placeholder'])
6067 ? $all_options['name_field_placeholder']
6068 : esc_html__('Enter your name', 'mxchat');
6069
6070 echo '<input type="text" id="name_field_placeholder" name="name_field_placeholder" value="' . esc_attr($placeholder) . '" style="width: 300px;" />';
6071 }
6072
6073
6074
6075 public function mxchat_intro_message_callback() {
6076 // Load the entire 'mxchat_options' array
6077 $all_options = get_option('mxchat_options', []);
6078 // Retrieve the saved intro message or use the default
6079 $default_message = __('Hello! How can I assist you today?', 'mxchat');
6080 $saved_message = isset($all_options['intro_message']) ? $all_options['intro_message'] : $default_message;
6081 // Output the textarea with the saved value without escaping HTML
6082 ?>
6083 <textarea id="intro_message" name="intro_message" rows="5" cols="50"><?php echo $saved_message; ?></textarea>
6084 <p class="description" style="margin-top: 8px;">
6085 <?php esc_html_e('Use {visitor_name} to personalize greetings when lead capture is enabled.', 'mxchat'); ?><br>
6086 <code style="font-size: 12px;"><?php esc_html_e('Example: Hello {visitor_name}! How can I help you today?', 'mxchat'); ?></code>
6087 </p>
6088 <?php
6089 }
6090
6091 public function mxchat_input_copy_callback() {
6092 // Load the entire 'mxchat_options' array
6093 $all_options = get_option('mxchat_options', []);
6094
6095 // Retrieve the saved input copy or use the default value
6096 $default_copy = __('How can I assist?', 'mxchat');
6097 $input_copy = isset($all_options['input_copy']) ? $all_options['input_copy'] : $default_copy;
6098
6099 // Output the input field with the saved value
6100 printf(
6101 '<input type="text" id="input_copy" name="input_copy" value="%s" placeholder="%s" />',
6102 esc_attr($input_copy),
6103 esc_attr__('How can I assist?', 'mxchat')
6104 );
6105 }
6106
6107
6108
6109 public function mxchat_user_message_font_color_callback() {
6110 $disabled = $this->is_activated ? '' : 'disabled';
6111 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6112
6113 echo '<div class="' . esc_attr($class) . '">';
6114 echo sprintf(
6115 '<input type="text"
6116 id="user_message_font_color"
6117 name="user_message_font_color"
6118 value="%s"
6119 class="my-color-field"
6120 data-default-color="#ffffff"
6121 %s />',
6122 isset($this->options['user_message_font_color']) ? esc_attr($this->options['user_message_font_color']) : '#ffffff',
6123 esc_attr($disabled)
6124 );
6125
6126 if (!$this->is_activated) {
6127 echo '<div class="pro-feature-overlay">';
6128 echo '<a href="https://mxchat.ai/" target="_blank">';
6129 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6130 echo '</a>';
6131 echo '</div>';
6132 }
6133 echo '</div>';
6134 }
6135
6136 public function mxchat_bot_message_bg_color_callback() {
6137 $disabled = $this->is_activated ? '' : 'disabled';
6138 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6139
6140 echo '<div class="' . esc_attr($class) . '">';
6141 echo sprintf(
6142 '<input type="text"
6143 id="bot_message_bg_color"
6144 name="bot_message_bg_color"
6145 value="%s"
6146 class="my-color-field"
6147 data-default-color="#e1e1e1"
6148 %s />',
6149 isset($this->options['bot_message_bg_color']) ? esc_attr($this->options['bot_message_bg_color']) : '#e1e1e1',
6150 esc_attr($disabled)
6151 );
6152
6153 if (!$this->is_activated) {
6154 echo '<div class="pro-feature-overlay">';
6155 echo '<a href="https://mxchat.ai/" target="_blank">';
6156 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6157 echo '</a>';
6158 echo '</div>';
6159 }
6160 echo '</div>';
6161 }
6162
6163 public function mxchat_bot_message_font_color_callback() {
6164 $disabled = $this->is_activated ? '' : 'disabled';
6165 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6166
6167 echo '<div class="' . esc_attr($class) . '">';
6168 echo sprintf(
6169 '<input type="text"
6170 id="bot_message_font_color"
6171 name="bot_message_font_color"
6172 value="%s"
6173 class="my-color-field"
6174 data-default-color="#333333"
6175 %s />',
6176 isset($this->options['bot_message_font_color']) ? esc_attr($this->options['bot_message_font_color']) : '#333333',
6177 esc_attr($disabled)
6178 );
6179
6180 if (!$this->is_activated) {
6181 echo '<div class="pro-feature-overlay">';
6182 echo '<a href="https://mxchat.ai/" target="_blank">';
6183 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6184 echo '</a>';
6185 echo '</div>';
6186 }
6187 echo '</div>';
6188 }
6189
6190 public function mxchat_live_agent_message_bg_color_callback() {
6191 $disabled = $this->is_activated ? '' : 'disabled';
6192 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6193
6194 echo '<div class="' . esc_attr($class) . '">';
6195 echo sprintf(
6196 '<input type="text"
6197 id="live_agent_message_bg_color"
6198 name="live_agent_message_bg_color"
6199 value="%s"
6200 class="my-color-field"
6201 data-default-color="#ffffff"
6202 %s />',
6203 isset($this->options['live_agent_message_bg_color']) ? esc_attr($this->options['live_agent_message_bg_color']) : '#ffffff',
6204 esc_attr($disabled)
6205 );
6206
6207 if (!$this->is_activated) {
6208 echo '<div class="pro-feature-overlay">';
6209 echo '<a href="https://mxchat.ai/" target="_blank">';
6210 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6211 echo '</a>';
6212 echo '</div>';
6213 }
6214 echo '</div>';
6215 }
6216
6217 public function mxchat_live_agent_message_font_color_callback() {
6218 $disabled = $this->is_activated ? '' : 'disabled';
6219 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6220
6221 echo '<div class="' . esc_attr($class) . '">';
6222 echo sprintf(
6223 '<input type="text"
6224 id="live_agent_message_font_color"
6225 name="live_agent_message_font_color"
6226 value="%s"
6227 class="my-color-field"
6228 data-default-color="#333333"
6229 %s />',
6230 isset($this->options['live_agent_message_font_color']) ? esc_attr($this->options['live_agent_message_font_color']) : '#333333',
6231 esc_attr($disabled)
6232 );
6233
6234 if (!$this->is_activated) {
6235 echo '<div class="pro-feature-overlay">';
6236 echo '<a href="https://mxchat.ai/" target="_blank">';
6237 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6238 echo '</a>';
6239 echo '</div>';
6240 }
6241 echo '</div>';
6242 }
6243
6244 public function mxchat_mode_indicator_bg_color_callback() {
6245 $disabled = $this->is_activated ? '' : 'disabled';
6246 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6247
6248 echo '<div class="' . esc_attr($class) . '">';
6249 echo sprintf(
6250 '<input type="text"
6251 id="mode_indicator_bg_color"
6252 name="mode_indicator_bg_color"
6253 value="%s"
6254 class="my-color-field"
6255 data-default-color="#767676"
6256 %s />',
6257 isset($this->options['mode_indicator_bg_color']) ? esc_attr($this->options['mode_indicator_bg_color']) : '#767676',
6258 esc_attr($disabled)
6259 );
6260
6261 if (!$this->is_activated) {
6262 echo '<div class="pro-feature-overlay">';
6263 echo '<a href="https://mxchat.ai/" target="_blank">';
6264 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6265 echo '</a>';
6266 echo '</div>';
6267 }
6268 echo '</div>';
6269 }
6270
6271 public function mxchat_mode_indicator_font_color_callback() {
6272 $disabled = $this->is_activated ? '' : 'disabled';
6273 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6274
6275 echo '<div class="' . esc_attr($class) . '">';
6276 echo sprintf(
6277 '<input type="text"
6278 id="mode_indicator_font_color"
6279 name="mode_indicator_font_color"
6280 value="%s"
6281 class="my-color-field"
6282 data-default-color="#ffffff"
6283 %s />',
6284 isset($this->options['mode_indicator_font_color']) ? esc_attr($this->options['mode_indicator_font_color']) : '#ffffff',
6285 esc_attr($disabled)
6286 );
6287
6288 if (!$this->is_activated) {
6289 echo '<div class="pro-feature-overlay">';
6290 echo '<a href="https://mxchat.ai/" target="_blank">';
6291 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6292 echo '</a>';
6293 echo '</div>';
6294 }
6295 echo '</div>';
6296 }
6297
6298 public function mxchat_toolbar_icon_color_callback() {
6299 $disabled = $this->is_activated ? '' : 'disabled';
6300 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6301
6302 echo '<div class="' . esc_attr($class) . '">';
6303 echo sprintf(
6304 '<input type="text"
6305 id="toolbar_icon_color"
6306 name="toolbar_icon_color"
6307 value="%s"
6308 class="my-color-field"
6309 data-default-color="#212121"
6310 %s />',
6311 isset($this->options['toolbar_icon_color']) ? esc_attr($this->options['toolbar_icon_color']) : '#212121',
6312 esc_attr($disabled)
6313 );
6314
6315 if (!$this->is_activated) {
6316 echo '<div class="pro-feature-overlay">';
6317 echo '<a href="https://mxchat.ai/" target="_blank">';
6318 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6319 echo '</a>';
6320 echo '</div>';
6321 }
6322 echo '</div>';
6323 }
6324
6325 public function mxchat_top_bar_bg_color_callback() {
6326 $disabled = $this->is_activated ? '' : 'disabled';
6327 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6328
6329 echo '<div class="' . esc_attr($class) . '">';
6330 echo sprintf(
6331 '<input type="text"
6332 id="top_bar_bg_color"
6333 name="top_bar_bg_color"
6334 value="%s"
6335 class="my-color-field"
6336 data-default-color="#00b294"
6337 %s />',
6338 isset($this->options['top_bar_bg_color']) ? esc_attr($this->options['top_bar_bg_color']) : '#00b294',
6339 esc_attr($disabled)
6340 );
6341
6342 if (!$this->is_activated) {
6343 echo '<div class="pro-feature-overlay">';
6344 echo '<a href="https://mxchat.ai/" target="_blank">';
6345 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6346 echo '</a>';
6347 echo '</div>';
6348 }
6349 echo '</div>';
6350 }
6351
6352 public function mxchat_send_button_font_color_callback() {
6353 $disabled = $this->is_activated ? '' : 'disabled';
6354 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6355
6356 echo '<div class="' . esc_attr($class) . '">';
6357 echo sprintf(
6358 '<input type="text"
6359 id="send_button_font_color"
6360 name="send_button_font_color"
6361 value="%s"
6362 class="my-color-field"
6363 data-default-color="#ffffff"
6364 %s />',
6365 isset($this->options['send_button_font_color']) ? esc_attr($this->options['send_button_font_color']) : '#ffffff',
6366 esc_attr($disabled)
6367 );
6368
6369 if (!$this->is_activated) {
6370 echo '<div class="pro-feature-overlay">';
6371 echo '<a href="https://mxchat.ai/" target="_blank">';
6372 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6373 echo '</a>';
6374 echo '</div>';
6375 }
6376 echo '</div>';
6377 }
6378
6379 public function mxchat_chatbot_background_color_callback() {
6380 $disabled = $this->is_activated ? '' : 'disabled';
6381 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6382
6383 echo '<div class="' . esc_attr($class) . '">';
6384 echo sprintf(
6385 '<input type="text"
6386 id="chatbot_background_color"
6387 name="chatbot_background_color"
6388 value="%s"
6389 class="my-color-field"
6390 data-default-color="#000000"
6391 %s />',
6392 isset($this->options['chatbot_background_color']) ? esc_attr($this->options['chatbot_background_color']) : '#000000',
6393 esc_attr($disabled)
6394 );
6395
6396 if (!$this->is_activated) {
6397 echo '<div class="pro-feature-overlay">';
6398 echo '<a href="https://mxchat.ai/" target="_blank">';
6399 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6400 echo '</a>';
6401 echo '</div>';
6402 }
6403 echo '</div>';
6404 }
6405
6406 public function mxchat_icon_color_callback() {
6407 $disabled = $this->is_activated ? '' : 'disabled';
6408 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6409
6410 echo '<div class="' . esc_attr($class) . '">';
6411 echo sprintf(
6412 '<input type="text"
6413 id="icon_color"
6414 name="icon_color"
6415 value="%s"
6416 class="my-color-field"
6417 data-default-color="#ffffff"
6418 %s />',
6419 isset($this->options['icon_color']) ? esc_attr($this->options['icon_color']) : '#ffffff',
6420 esc_attr($disabled)
6421 );
6422
6423 if (!$this->is_activated) {
6424 echo '<div class="pro-feature-overlay">';
6425 echo '<a href="https://mxchat.ai/" target="_blank">';
6426 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6427 echo '</a>';
6428 echo '</div>';
6429 }
6430 echo '</div>';
6431 }
6432
6433 public function mxchat_custom_icon_callback() {
6434 $disabled = $this->is_activated ? '' : 'disabled';
6435 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6436 $custom_icon_url = isset($this->options['custom_icon']) ? esc_url($this->options['custom_icon']) : '';
6437
6438 echo '<div class="' . esc_attr($class) . '">';
6439 echo sprintf(
6440 '<input type="url"
6441 id="custom_icon"
6442 name="custom_icon"
6443 value="%s"
6444 placeholder="%s"
6445 class="regular-text"
6446 %s />',
6447 $custom_icon_url,
6448 esc_attr__('Enter PNG URL', 'mxchat'),
6449 esc_attr($disabled)
6450 );
6451
6452 // Preview container for the icon
6453 if (!empty($custom_icon_url)) {
6454 echo '<div class="icon-preview" style="margin-top: 10px;">';
6455 echo '<img src="' . esc_url($custom_icon_url) . '" alt="' . esc_attr__('Custom Icon Preview', 'mxchat') . '" style="max-width: 48px; height: auto;" />';
6456 echo '</div>';
6457 }
6458
6459 echo '<p class="description">' . esc_html__('Upload your PNG icon and paste the URL here. Recommended size: 48x48 pixels.', 'mxchat') . '</p>';
6460
6461 if (!$this->is_activated) {
6462 echo '<div class="pro-feature-overlay">';
6463 echo '<a href="https://mxchat.ai/" target="_blank">';
6464 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6465 echo '</a>';
6466 echo '</div>';
6467 }
6468 echo '</div>';
6469 }
6470
6471 public function mxchat_title_icon_callback() {
6472 $disabled = $this->is_activated ? '' : 'disabled';
6473 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6474 // Fixed the variable reference - it was using custom_icon instead of title_icon
6475 $title_icon_url = isset($this->options['title_icon']) ? esc_url($this->options['title_icon']) : '';
6476
6477 echo '<div class="' . esc_attr($class) . '">';
6478 echo sprintf(
6479 '<input type="url"
6480 id="title_icon"
6481 name="title_icon"
6482 value="%s"
6483 placeholder="%s"
6484 class="regular-text"
6485 %s />',
6486 $title_icon_url,
6487 esc_attr__('Enter PNG URL', 'mxchat'),
6488 esc_attr($disabled)
6489 );
6490
6491 // Preview container for the icon
6492 if (!empty($title_icon_url)) {
6493 echo '<div class="icon-preview" style="margin-top: 10px;">';
6494 echo '<img src="' . esc_url($title_icon_url) . '" alt="' . esc_attr__('Title Icon Preview', 'mxchat') . '" style="max-width: 48px; height: auto;" />';
6495 echo '</div>';
6496 }
6497
6498 echo '<p class="description">' . esc_html__('Upload your PNG icon and paste the URL here. Recommended size: 48x48 pixels.', 'mxchat') . '</p>';
6499
6500 if (!$this->is_activated) {
6501 echo '<div class="pro-feature-overlay">';
6502 echo '<a href="https://mxchat.ai/" target="_blank">';
6503 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6504 echo '</a>';
6505 echo '</div>';
6506 }
6507 echo '</div>';
6508 }
6509
6510 public function mxchat_chat_input_font_color_callback() {
6511 $disabled = $this->is_activated ? '' : 'disabled';
6512 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6513
6514 echo '<div class="' . esc_attr($class) . '">';
6515 echo sprintf(
6516 '<input type="text"
6517 id="chat_input_font_color"
6518 name="chat_input_font_color"
6519 value="%s"
6520 class="my-color-field"
6521 data-default-color="#555555"
6522 %s />',
6523 isset($this->options['chat_input_font_color']) ? esc_attr($this->options['chat_input_font_color']) : '#555555',
6524 esc_attr($disabled)
6525 );
6526
6527 if (!$this->is_activated) {
6528 echo '<div class="pro-feature-overlay">';
6529 echo '<a href="https://mxchat.ai/" target="_blank">';
6530 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6531 echo '</a>';
6532 echo '</div>';
6533 }
6534 echo '</div>';
6535 }
6536
6537 public function mxchat_append_to_body_callback() {
6538 // Fetch fresh options to ensure we have the latest saved values
6539 $options = get_option('mxchat_options', array());
6540
6541 // Get value from options array, default to 'off'
6542 $append_to_body = isset($options['append_to_body']) ? $options['append_to_body'] : 'off';
6543 $checked = ($append_to_body === 'on') ? 'checked' : '';
6544
6545 // Get post type visibility settings
6546 $visibility_mode = isset($options['post_type_visibility_mode']) ? $options['post_type_visibility_mode'] : 'all';
6547 $visibility_list = isset($options['post_type_visibility_list']) ? $options['post_type_visibility_list'] : array();
6548 if (!is_array($visibility_list)) {
6549 $visibility_list = array();
6550 }
6551
6552 echo '<div class="mxchat-autosave-section">';
6553
6554 // Main toggle
6555 echo '<label class="toggle-switch">';
6556 echo sprintf(
6557 '<input type="checkbox" id="append_to_body" name="append_to_body" value="on" %s />',
6558 esc_attr($checked)
6559 );
6560 echo '<span class="slider"></span>';
6561 echo '</label>';
6562
6563 // Post Type Visibility Options (only visible when auto-display is ON)
6564 $display_style = ($append_to_body === 'on') ? '' : 'display: none;';
6565 echo '<div id="post-type-visibility-options" class="mxchat-sub-options" style="' . esc_attr($display_style) . '">';
6566
6567 echo '<div class="mxchat-post-type-visibility-header">';
6568 echo '<h4>' . esc_html__('Post Type Visibility', 'mxchat') . '</h4>';
6569 echo '</div>';
6570
6571 // Mode selector (radio buttons)
6572 echo '<div class="mxchat-visibility-mode">';
6573
6574 echo '<label class="mxchat-radio-label">';
6575 echo '<input type="radio" name="post_type_visibility_mode" value="all" ' . checked($visibility_mode, 'all', false) . ' />';
6576 echo '<span>' . esc_html__('Show on all post types', 'mxchat') . '</span>';
6577 echo '</label>';
6578
6579 echo '<label class="mxchat-radio-label">';
6580 echo '<input type="radio" name="post_type_visibility_mode" value="include" ' . checked($visibility_mode, 'include', false) . ' />';
6581 echo '<span>' . esc_html__('Only show on selected post types', 'mxchat') . '</span>';
6582 echo '</label>';
6583
6584 echo '<label class="mxchat-radio-label">';
6585 echo '<input type="radio" name="post_type_visibility_mode" value="exclude" ' . checked($visibility_mode, 'exclude', false) . ' />';
6586 echo '<span>' . esc_html__('Hide on selected post types', 'mxchat') . '</span>';
6587 echo '</label>';
6588
6589 echo '</div>';
6590
6591 // Post type checkboxes (only visible when mode is include or exclude)
6592 $list_display = ($visibility_mode !== 'all') ? '' : 'display: none;';
6593 echo '<div id="post-type-list" class="mxchat-post-type-list" style="' . esc_attr($list_display) . '">';
6594
6595 // Get all public post types
6596 $post_types = get_post_types(array('public' => true), 'objects');
6597
6598 foreach ($post_types as $post_type) {
6599 // Skip attachments
6600 if ($post_type->name === 'attachment') {
6601 continue;
6602 }
6603
6604 $is_checked = in_array($post_type->name, $visibility_list) ? 'checked' : '';
6605
6606 echo '<label class="mxchat-checkbox-label">';
6607 echo '<input type="checkbox" name="post_type_visibility_list[]" value="' . esc_attr($post_type->name) . '" ' . $is_checked . ' />';
6608 echo '<span>' . esc_html($post_type->label) . '</span>';
6609 echo '</label>';
6610 }
6611
6612 echo '</div>'; // End post-type-list
6613 echo '</div>'; // End post-type-visibility-options
6614 echo '</div>'; // End mxchat-autosave-section
6615 }
6616
6617 public function mxchat_frontend_debugger_callback() {
6618 $show_debugger = isset($this->options['show_frontend_debugger']) ? $this->options['show_frontend_debugger'] : 'on';
6619 $checked = ($show_debugger === 'on') ? 'checked' : '';
6620 echo '<label class="toggle-switch">';
6621 echo sprintf(
6622 '<input type="checkbox" id="show_frontend_debugger" name="show_frontend_debugger" value="on" %s />',
6623 esc_attr($checked)
6624 );
6625 echo '<span class="slider"></span>';
6626 echo '</label>';
6627 }
6628
6629 public function mxchat_contextual_awareness_callback() {
6630 // Get value from options array, default to 'off'
6631 $contextual_awareness = isset($this->options['contextual_awareness_toggle']) ? $this->options['contextual_awareness_toggle'] : 'off';
6632 $checked = ($contextual_awareness === 'on') ? 'checked' : '';
6633 echo '<label class="toggle-switch">';
6634 echo sprintf(
6635 '<input type="checkbox" id="contextual_awareness_toggle" name="contextual_awareness_toggle" value="on" %s />',
6636 esc_attr($checked)
6637 );
6638 echo '<span class="slider"></span>';
6639 echo '</label>';
6640 }
6641
6642 public function mxchat_citation_links_toggle_callback() {
6643 // Get value from options array, default to 'on' (enabled by default)
6644 $citation_links = isset($this->options['citation_links_toggle']) ? $this->options['citation_links_toggle'] : 'on';
6645 $checked = ($citation_links === 'on') ? 'checked' : '';
6646 echo '<label class="toggle-switch">';
6647 echo sprintf(
6648 '<input type="checkbox" id="citation_links_toggle" name="citation_links_toggle" value="on" %s />',
6649 esc_attr($checked)
6650 );
6651 echo '<span class="slider"></span>';
6652 echo '</label>';
6653 }
6654
6655 public function mxchat_privacy_toggle_callback() {
6656 // Load from mxchat_options array
6657 $options = get_option('mxchat_options', []);
6658
6659 // Get privacy toggle value with fallback
6660 $privacy_toggle = isset($options['privacy_toggle']) ? $options['privacy_toggle'] : 'off';
6661 $checked = ($privacy_toggle === 'on') ? 'checked' : '';
6662
6663 // Get privacy text with fallback
6664 $privacy_text = isset($options['privacy_text'])
6665 ? $options['privacy_text']
6666 : __('By chatting, you agree to our <a href="https://example.com/privacy-policy" target="_blank">privacy policy</a>.', 'mxchat');
6667
6668 // Output the toggle switch
6669 echo '<label class="toggle-switch">';
6670 echo sprintf(
6671 '<input type="checkbox" id="privacy_toggle" name="privacy_toggle" value="on" %s />',
6672 esc_attr($checked)
6673 );
6674 echo '<span class="slider"></span>';
6675 echo '</label>';
6676
6677 // Output the custom text input field
6678 echo sprintf(
6679 '<textarea id="privacy_text" name="privacy_text" rows="5" cols="50" class="regular-text">%s</textarea>',
6680 esc_textarea($privacy_text)
6681 );
6682 }
6683
6684
6685 public function mxchat_complianz_toggle_callback() {
6686 // Load from mxchat_options array
6687 $options = get_option('mxchat_options', []);
6688
6689 // Get complianz toggle value with fallback
6690 $complianz_toggle = isset($options['complianz_toggle']) ? $options['complianz_toggle'] : 'off';
6691 $checked = ($complianz_toggle === 'on') ? 'checked' : '';
6692
6693 // Output the toggle switch
6694 echo '<label class="toggle-switch">';
6695 echo sprintf(
6696 '<input type="checkbox" id="complianz_toggle" name="complianz_toggle" value="on" %s />',
6697 esc_attr($checked)
6698 );
6699 echo '<span class="slider"></span>';
6700 echo '</label>';
6701 }
6702
6703 public function mxchat_link_target_toggle_callback() {
6704 // Load from mxchat_options array
6705 $options = get_option('mxchat_options', []);
6706
6707 // Get link target toggle value with fallback
6708 $link_target_toggle = isset($options['link_target_toggle']) ? $options['link_target_toggle'] : 'off';
6709 $checked = ($link_target_toggle === 'on') ? 'checked' : '';
6710
6711 // Output the toggle switch
6712 echo '<label class="toggle-switch">';
6713 echo sprintf(
6714 '<input type="checkbox" id="link_target_toggle" name="link_target_toggle" value="on" %s />',
6715 esc_attr($checked)
6716 );
6717 echo '<span class="slider"></span>';
6718 echo '</label>';
6719 }
6720
6721 public function mxchat_chat_persistence_toggle_callback() {
6722 // Load from mxchat_options array
6723 $options = get_option('mxchat_options', []);
6724
6725 // Get chat persistence toggle value with fallback
6726 $chat_persistence_toggle = isset($options['chat_persistence_toggle']) ? $options['chat_persistence_toggle'] : 'off';
6727 $checked = ($chat_persistence_toggle === 'on') ? 'checked' : '';
6728
6729 // Output the toggle switch
6730 echo '<label class="toggle-switch">';
6731 echo sprintf(
6732 '<input type="checkbox" id="chat_persistence_toggle" name="chat_persistence_toggle" value="on" %s />',
6733 esc_attr($checked)
6734 );
6735 echo '<span class="slider"></span>';
6736 echo '</label>';
6737 }
6738
6739 public function mxchat_popular_question_1_callback() {
6740 // Load the full plugin options array
6741 $all_options = get_option('mxchat_options', []);
6742
6743 // Retrieve the specific option for popular_question_1
6744 $popular_question_1 = isset($all_options['popular_question_1']) ? $all_options['popular_question_1'] : '';
6745
6746 // Render the input field
6747 printf(
6748 '<input type="text" id="popular_question_1" name="popular_question_1" value="%s" placeholder="%s" class="regular-text" />',
6749 esc_attr($popular_question_1),
6750 esc_attr__('Enter Quick Question 1', 'mxchat')
6751 );
6752 }
6753
6754
6755 public function mxchat_popular_question_2_callback() {
6756 // Load the full plugin options array
6757 $all_options = get_option('mxchat_options', []);
6758
6759 // Retrieve the specific option for popular_question_2
6760 $popular_question_2 = isset($all_options['popular_question_2']) ? $all_options['popular_question_2'] : '';
6761
6762 // Render the input field
6763 printf(
6764 '<input type="text" id="popular_question_2" name="popular_question_2" value="%s" placeholder="%s" class="regular-text" />',
6765 esc_attr($popular_question_2),
6766 esc_attr__('Enter Quick Question 2', 'mxchat')
6767 );
6768 }
6769
6770
6771 public function mxchat_popular_question_3_callback() {
6772 // Load the full plugin options array
6773 $all_options = get_option('mxchat_options', []);
6774
6775 // Retrieve the specific option for popular_question_3
6776 $popular_question_3 = isset($all_options['popular_question_3']) ? $all_options['popular_question_3'] : '';
6777
6778 // Render the input field
6779 printf(
6780 '<input type="text" id="popular_question_3" name="popular_question_3" value="%s" placeholder="%s" class="regular-text" />',
6781 esc_attr($popular_question_3),
6782 esc_attr(__('Enter Quick Question 3', 'mxchat'))
6783 );
6784 }
6785
6786 public function mxchat_additional_popular_questions_callback() {
6787 $options = get_option('mxchat_options', []);
6788 $additional_questions = isset($options['additional_popular_questions'])
6789 ? $options['additional_popular_questions']
6790 : get_option('additional_popular_questions', array());
6791
6792 echo '<div id="mxchat-additional-questions-container">';
6793 if (!empty($additional_questions)) {
6794 foreach ($additional_questions as $index => $question) {
6795 printf(
6796 '<div class="mxchat-question-row">
6797 <input type="text" name="additional_popular_questions[]"
6798 value="%s"
6799 placeholder="%s"
6800 class="regular-text mxchat-question-input"
6801 data-question-index="%d" />
6802 <button type="button" class="button mxchat-remove-question"
6803 aria-label="%s">%s</button>
6804 </div>',
6805 esc_attr($question),
6806 esc_attr(sprintf(__('Enter Additional Quick Question %d', 'mxchat'), $index + 4)),
6807 $index,
6808 esc_attr(__('Remove question', 'mxchat')),
6809 esc_html__('Remove', 'mxchat')
6810 );
6811 }
6812 } else {
6813 printf(
6814 '<div class="mxchat-question-row">
6815 <input type="text" name="additional_popular_questions[]"
6816 value=""
6817 placeholder="%s"
6818 class="regular-text mxchat-question-input"
6819 data-question-index="0" />
6820 <button type="button" class="button mxchat-remove-question"
6821 aria-label="%s">%s</button>
6822 </div>',
6823 esc_attr(__('Enter Additional Quick Question 4', 'mxchat')),
6824 esc_attr(__('Remove question', 'mxchat')),
6825 esc_html__('Remove', 'mxchat')
6826 );
6827 }
6828 echo '</div>';
6829 printf(
6830 '<button type="button" class="button mxchat-add-question" aria-label="%s">%s</button>',
6831 esc_attr(__('Add question', 'mxchat')),
6832 esc_html__('Add Question', 'mxchat')
6833 );
6834 }
6835
6836 public function mxchat_brave_api_key_callback() {
6837 $brave_api_key = isset($this->options['brave_api_key']) ? esc_attr($this->options['brave_api_key']) : '';
6838 $nonce = wp_create_nonce('mxchat_autosave_nonce');
6839
6840 echo '<div class="api-key-wrapper">';
6841 echo sprintf(
6842 '<input type="password" id="brave_api_key" name="brave_api_key" value="%s" class="regular-text mxchat-autosave-field" data-nonce="%s" />',
6843 $brave_api_key,
6844 $nonce
6845 );
6846 echo '<button type="button" id="toggleBraveApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
6847 echo '</div>';
6848 echo '<p class="description">' . __('Required for Brave Search integration. Get your API key from Brave Search API.', 'mxchat') . '</p>';
6849 }
6850
6851 public function mxchat_brave_image_count_callback() {
6852 $brave_image_count = isset($this->options['brave_image_count'])
6853 ? intval($this->options['brave_image_count'])
6854 : 4;
6855 $nonce = wp_create_nonce('mxchat_autosave_nonce');
6856
6857 echo '<div class="mxchat-field-wrapper">';
6858 echo sprintf(
6859 '<input type="number" id="brave_image_count" name="brave_image_count"
6860 value="%d" min="1" max="6" class="small-text mxchat-autosave-field" data-nonce="%s" />',
6861 $brave_image_count,
6862 $nonce
6863 );
6864 echo '</div>';
6865 echo '<p class="description">' . __('Select the number of images to return (1-6).', 'mxchat') . '</p>';
6866 }
6867
6868 public function mxchat_brave_safe_search_callback() {
6869 $brave_safe_search = isset($this->options['brave_safe_search'])
6870 ? esc_attr($this->options['brave_safe_search'])
6871 : 'strict';
6872 $nonce = wp_create_nonce('mxchat_autosave_nonce');
6873
6874 echo '<div class="mxchat-field-wrapper">';
6875 echo '<select id="brave_safe_search" name="brave_safe_search" class="mxchat-autosave-field" data-nonce="' . $nonce . '">';
6876 echo sprintf(
6877 '<option value="strict" %s>%s</option>',
6878 selected($brave_safe_search, 'strict', false),
6879 __('Strict', 'mxchat')
6880 );
6881 echo sprintf(
6882 '<option value="off" %s>%s</option>',
6883 selected($brave_safe_search, 'off', false),
6884 __('Off', 'mxchat')
6885 );
6886 echo '</select>';
6887 echo '</div>';
6888 echo '<p class="description">' .
6889 esc_html__('Set the Safe Search level for image searches. Brave Search only supports "Strict" and "Off" options.', 'mxchat') .
6890 '</p>';
6891 }
6892
6893 public function mxchat_brave_news_count_callback() {
6894 $brave_news_count = isset($this->options['brave_news_count'])
6895 ? intval($this->options['brave_news_count'])
6896 : 3;
6897 $nonce = wp_create_nonce('mxchat_autosave_nonce');
6898
6899 echo '<div class="mxchat-field-wrapper">';
6900 echo sprintf(
6901 '<input type="number" id="brave_news_count" name="brave_news_count"
6902 value="%d" min="1" max="10" class="small-text mxchat-autosave-field" data-nonce="%s" />',
6903 $brave_news_count,
6904 $nonce
6905 );
6906 echo '</div>';
6907 echo '<p class="description">' . esc_html__('Select the number of news articles to retrieve (1-10).', 'mxchat') . '</p>';
6908 }
6909
6910 public function mxchat_brave_country_callback() {
6911 $brave_country = isset($this->options['brave_country'])
6912 ? esc_attr($this->options['brave_country'])
6913 : 'us';
6914 $nonce = wp_create_nonce('mxchat_autosave_nonce');
6915
6916 echo '<div class="mxchat-field-wrapper">';
6917 echo sprintf(
6918 '<input type="text" id="brave_country" name="brave_country"
6919 value="%s" maxlength="2" class="small-text mxchat-autosave-field" data-nonce="%s" />',
6920 $brave_country,
6921 $nonce
6922 );
6923 echo '</div>';
6924 echo '<p class="description">' . esc_html__('Enter the country code (e.g., "us" for United States).', 'mxchat') . '</p>';
6925 }
6926
6927 public function mxchat_brave_language_callback() {
6928 $brave_language = isset($this->options['brave_language'])
6929 ? esc_attr($this->options['brave_language'])
6930 : 'en';
6931 $nonce = wp_create_nonce('mxchat_autosave_nonce');
6932
6933 echo '<div class="mxchat-field-wrapper">';
6934 echo sprintf(
6935 '<input type="text" id="brave_language" name="brave_language"
6936 value="%s" maxlength="2" class="small-text mxchat-autosave-field" data-nonce="%s" />',
6937 $brave_language,
6938 $nonce
6939 );
6940 echo '</div>';
6941 echo '<p class="description">' . esc_html__('Enter the language code (e.g., "en" for English).', 'mxchat') . '</p>';
6942 }
6943
6944
6945
6946
6947
6948 // Section Callback
6949 public function mxchat_pdf_intent_section_callback() {
6950 echo '<p>' . esc_html__('Configure the intent settings for the Chat with PDF feature.', 'mxchat') . '</p>';
6951 }
6952
6953 public function mxchat_chat_toolbar_toggle_callback() {
6954 // Get chat toolbar toggle value with fallback
6955 $chat_toolbar_toggle = isset($this->options['chat_toolbar_toggle']) ? $this->options['chat_toolbar_toggle'] : 'off';
6956 $checked = ($chat_toolbar_toggle === 'on') ? 'checked' : '';
6957
6958 // Output the toggle switch
6959 echo '<label class="toggle-switch">';
6960 echo sprintf(
6961 '<input type="checkbox" id="chat_toolbar_toggle" name="chat_toolbar_toggle" value="on" %s />',
6962 esc_attr($checked)
6963 );
6964 echo '<span class="slider"></span>';
6965 echo '</label>';
6966
6967 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>';
6968 }
6969
6970 /**
6971 * Callback for PDF upload button toggle setting
6972 */
6973 public function mxchat_show_pdf_upload_button_callback() {
6974 // Get toggle value with fallback
6975 $show_pdf_button = isset($this->options['show_pdf_upload_button']) ? $this->options['show_pdf_upload_button'] : 'on';
6976 $checked = ($show_pdf_button === 'on') ? 'checked' : '';
6977
6978 // Output the toggle switch
6979 echo '<label class="toggle-switch">';
6980 echo sprintf(
6981 '<input type="checkbox" id="show_pdf_upload_button" name="show_pdf_upload_button" value="on" %s />',
6982 esc_attr($checked)
6983 );
6984 echo '<span class="slider"></span>';
6985 echo '</label>';
6986
6987 echo '<p class="description">' . esc_html__('Enable to show the PDF upload button in the chatbot toolbar. Disable to hide it.', 'mxchat') . '</p>';
6988 }
6989
6990 /**
6991 * Callback for Word upload button toggle setting
6992 */
6993 public function mxchat_show_word_upload_button_callback() {
6994 // Get toggle value with fallback
6995 $show_word_button = isset($this->options['show_word_upload_button']) ? $this->options['show_word_upload_button'] : 'on';
6996 $checked = ($show_word_button === 'on') ? 'checked' : '';
6997
6998 // Output the toggle switch
6999 echo '<label class="toggle-switch">';
7000 echo sprintf(
7001 '<input type="checkbox" id="show_word_upload_button" name="show_word_upload_button" value="on" %s />',
7002 esc_attr($checked)
7003 );
7004 echo '<span class="slider"></span>';
7005 echo '</label>';
7006
7007 echo '<p class="description">' . esc_html__('Enable to show the Word document upload button in the chatbot toolbar. Disable to hide it.', 'mxchat') . '</p>';
7008 }
7009
7010 public function mxchat_pdf_intent_trigger_text_callback() {
7011 $default_text = __("Please provide the URL to the PDF you'd like to discuss.", 'mxchat');
7012
7013 echo sprintf(
7014 '<textarea id="pdf_intent_trigger_text"
7015 name="pdf_intent_trigger_text"
7016 rows="3"
7017 cols="50"
7018 placeholder="%s">%s</textarea>',
7019 esc_attr__('Enter trigger text', 'mxchat'),
7020 isset($this->options['pdf_intent_trigger_text'])
7021 ? esc_textarea($this->options['pdf_intent_trigger_text'])
7022 : esc_textarea($default_text)
7023 );
7024 echo '<p class="description">' . esc_html__('Text displayed when the intent is triggered.', 'mxchat') . '</p>';
7025 }
7026
7027 public function mxchat_pdf_intent_success_text_callback() {
7028 $default_text = __("I've processed the PDF. What questions do you have about it?", 'mxchat');
7029
7030 echo sprintf(
7031 '<textarea id="pdf_intent_success_text"
7032 name="pdf_intent_success_text"
7033 rows="3"
7034 cols="50"
7035 placeholder="%s">%s</textarea>',
7036 esc_attr__('Enter success text', 'mxchat'),
7037 isset($this->options['pdf_intent_success_text'])
7038 ? esc_textarea($this->options['pdf_intent_success_text'])
7039 : esc_textarea($default_text)
7040 );
7041 echo '<p class="description">' . esc_html__('Text displayed when the intent is successful.', 'mxchat') . '</p>';
7042 }
7043
7044 public function mxchat_pdf_intent_error_text_callback() {
7045 $default_text = __("Sorry, I couldn't process the PDF. Please ensure it's a valid file.", 'mxchat');
7046
7047 echo sprintf(
7048 '<textarea id="pdf_intent_error_text"
7049 name="pdf_intent_error_text"
7050 rows="3"
7051 cols="50"
7052 placeholder="%s">%s</textarea>',
7053 esc_attr__('Enter error text', 'mxchat'),
7054 isset($this->options['pdf_intent_error_text'])
7055 ? esc_textarea($this->options['pdf_intent_error_text'])
7056 : esc_textarea($default_text)
7057 );
7058 echo '<p class="description">' . esc_html__('Text displayed when an error occurs during the intent.', 'mxchat') . '</p>';
7059 }
7060
7061 public function mxchat_pdf_max_pages_callback() {
7062 $max_pages = isset($this->options['pdf_max_pages']) ? intval($this->options['pdf_max_pages']) : 69;
7063
7064 echo sprintf(
7065 '<input type="range"
7066 id="pdf_max_pages"
7067 name="pdf_max_pages"
7068 min="1"
7069 max="69"
7070 value="%d"
7071 class="range-slider" />',
7072 esc_attr($max_pages)
7073 );
7074 echo '<span id="pdf_max_pages_output">' . esc_html($max_pages) . '</span>';
7075 echo '<p class="description">' . esc_html__('Set the maximum number of document pages users can upload for processing. (1-69 pages)', 'mxchat') . '</p>';
7076 }
7077
7078 public function mxchat_live_agent_status_callback() {
7079 // Always get fresh options instead of using cached $this->options
7080 $fresh_options = get_option('mxchat_options');
7081 $status = isset($fresh_options['live_agent_status']) ? $fresh_options['live_agent_status'] : 'off';
7082
7083 echo '<label class="toggle-switch">';
7084 echo sprintf(
7085 '<input type="checkbox" id="live_agent_status" name="live_agent_status" value="on" %s />',
7086 checked($status, 'on', false)
7087 );
7088 echo '<span class="slider"></span>';
7089 echo '</label>';
7090 echo '<label for="live_agent_status" class="mxchat-status-label">';
7091 echo '<span class="status-text">' . ($status === 'on' ? esc_html__('Online', 'mxchat') : esc_html__('Offline', 'mxchat')) . '</span>';
7092 echo '</label>';
7093 }
7094
7095 public function mxchat_live_agent_away_message_callback() {
7096 $message = isset($this->options['live_agent_away_message'])
7097 ? $this->options['live_agent_away_message']
7098 : __('Sorry, live agents are currently unavailable. I can continue helping you as an AI assistant.', 'mxchat');
7099
7100 printf(
7101 '<textarea id="live_agent_away_message" name="live_agent_away_message" rows="3" cols="50">%s</textarea>',
7102 esc_textarea($message)
7103 );
7104 echo '<p class="description">' . esc_html__('Message shown when live agents are offline.', 'mxchat') . '</p>';
7105 }
7106
7107 public function mxchat_live_agent_notification_message_callback() {
7108 $message = isset($this->options['live_agent_notification_message'])
7109 ? $this->options['live_agent_notification_message']
7110 : __('Live agent has been notified.', 'mxchat');
7111
7112 printf(
7113 '<textarea id="live_agent_notification_message" name="live_agent_notification_message" rows="3" cols="50">%s</textarea>',
7114 esc_textarea($message)
7115 );
7116 echo '<p class="description">' . esc_html__('Message shown when live transfer activated.', 'mxchat') . '</p>';
7117 }
7118
7119 public function mxchat_live_agent_webhook_url_callback() {
7120 $webhook_url = isset($this->options['live_agent_webhook_url'])
7121 ? esc_url($this->options['live_agent_webhook_url'])
7122 : esc_url(get_option('live_agent_webhook_url', ''));
7123
7124 printf(
7125 '<input type="password" id="live_agent_webhook_url" name="live_agent_webhook_url" value="%s" class="regular-text" />',
7126 $webhook_url
7127 );
7128 echo '<button type="button" id="toggleWebhookUrlVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
7129 echo '<p class="description">' . esc_html__('Enter your Slack webhook URL for live agent notifications.', 'mxchat') . '</p>';
7130 }
7131
7132 public function mxchat_live_agent_secret_key_callback() {
7133 printf(
7134 '<input type="password" id="live_agent_secret_key" name="live_agent_secret_key" value="%s" class="regular-text" />',
7135 isset($this->options['live_agent_secret_key']) ? esc_attr($this->options['live_agent_secret_key']) : ''
7136 );
7137 echo '<button type="button" id="toggleSecretKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
7138 echo '<p class="description">' . esc_html__('Secret key for validating Slack requests. Keep this secure.', 'mxchat') . '</p>';
7139 }
7140
7141 public function mxchat_live_agent_bot_token_callback() {
7142 printf(
7143 '<input type="password" id="live_agent_bot_token" name="live_agent_bot_token" value="%s" class="regular-text" />',
7144 isset($this->options['live_agent_bot_token']) ? esc_attr($this->options['live_agent_bot_token']) : ''
7145 );
7146 echo '<button type="button" id="toggleBotTokenVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
7147 echo '<p class="description">' . esc_html__('Your Slack Bot OAuth Token (starts with xoxb-). Keep this secure.', 'mxchat') . '</p>';
7148 }
7149
7150 public function mxchat_live_agent_user_ids_callback() {
7151 $user_ids = isset($this->options['live_agent_user_ids'])
7152 ? esc_textarea($this->options['live_agent_user_ids'])
7153 : '';
7154
7155 printf(
7156 '<textarea id="live_agent_user_ids" name="live_agent_user_ids" rows="4" class="large-text">%s</textarea>',
7157 $user_ids
7158 );
7159 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>';
7160 }
7161
7162 /**
7163 * Telegram Integration Callbacks
7164 */
7165 public function mxchat_telegram_section_callback() {
7166 echo '<p>' . esc_html__('Configure Telegram integration for live agent support.', 'mxchat') . '</p>';
7167 }
7168
7169 public function mxchat_telegram_status_callback() {
7170 $fresh_options = get_option('mxchat_options');
7171 $status = isset($fresh_options['telegram_status']) ? $fresh_options['telegram_status'] : 'off';
7172
7173 echo '<label class="toggle-switch">';
7174 echo sprintf(
7175 '<input type="checkbox" id="telegram_status" name="telegram_status" value="on" %s />',
7176 checked($status, 'on', false)
7177 );
7178 echo '<span class="slider"></span>';
7179 echo '</label>';
7180 echo '<label for="telegram_status" class="mxchat-status-label">';
7181 echo '<span class="status-text">' . ($status === 'on' ? esc_html__('Online', 'mxchat') : esc_html__('Offline', 'mxchat')) . '</span>';
7182 echo '</label>';
7183 }
7184
7185 public function mxchat_telegram_notification_message_callback() {
7186 $message = isset($this->options['telegram_notification_message'])
7187 ? $this->options['telegram_notification_message']
7188 : __("I've notified a support agent. Please allow a moment for them to respond.", 'mxchat');
7189
7190 printf(
7191 '<textarea id="telegram_notification_message" name="telegram_notification_message" rows="3" cols="50">%s</textarea>',
7192 esc_textarea($message)
7193 );
7194 echo '<p class="description">' . esc_html__('Message shown when live transfer activated.', 'mxchat') . '</p>';
7195 }
7196
7197 public function mxchat_telegram_away_message_callback() {
7198 $message = isset($this->options['telegram_away_message'])
7199 ? $this->options['telegram_away_message']
7200 : __('Sorry, live agents are currently unavailable. I can continue helping you as an AI assistant.', 'mxchat');
7201
7202 printf(
7203 '<textarea id="telegram_away_message" name="telegram_away_message" rows="3" cols="50">%s</textarea>',
7204 esc_textarea($message)
7205 );
7206 echo '<p class="description">' . esc_html__('Message shown when live agents are offline.', 'mxchat') . '</p>';
7207 }
7208
7209 public function mxchat_telegram_bot_token_callback() {
7210 printf(
7211 '<input type="password" id="telegram_bot_token" name="telegram_bot_token" value="%s" class="regular-text" />',
7212 isset($this->options['telegram_bot_token']) ? esc_attr($this->options['telegram_bot_token']) : ''
7213 );
7214 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>';
7215 echo '<p class="description">' . esc_html__('Your Telegram Bot Token from @BotFather (e.g., 123456789:ABCdefGhIJKlmNoPQRsTUVwxyZ).', 'mxchat') . '</p>';
7216 }
7217
7218 public function mxchat_telegram_group_id_callback() {
7219 printf(
7220 '<input type="text" id="telegram_group_id" name="telegram_group_id" value="%s" class="regular-text" />',
7221 isset($this->options['telegram_group_id']) ? esc_attr($this->options['telegram_group_id']) : ''
7222 );
7223 echo '<p class="description">' . esc_html__('Your Telegram supergroup ID (starts with -100). The group must have forum topics enabled.', 'mxchat') . '</p>';
7224 }
7225
7226 public function mxchat_telegram_webhook_secret_callback() {
7227 $secret = isset($this->options['telegram_webhook_secret']) ? $this->options['telegram_webhook_secret'] : '';
7228
7229 // Auto-generate secret if empty
7230 if (empty($secret)) {
7231 $secret = wp_generate_password(64, false, false);
7232 $options = get_option('mxchat_options', []);
7233 $options['telegram_webhook_secret'] = $secret;
7234 update_option('mxchat_options', $options);
7235 $this->options['telegram_webhook_secret'] = $secret;
7236 }
7237
7238 printf(
7239 '<input type="password" id="telegram_webhook_secret" name="telegram_webhook_secret" value="%s" class="regular-text" />',
7240 esc_attr($secret)
7241 );
7242 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>';
7243 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>';
7244 }
7245
7246 public function mxchat_similarity_threshold_callback() {
7247 // Load from mxchat_options array
7248 $options = get_option('mxchat_options', []);
7249
7250 // Get value from options array with default of 35
7251 $threshold = isset($options['similarity_threshold']) ? $options['similarity_threshold'] : 35;
7252
7253 echo '<div class="slider-container">';
7254 echo sprintf(
7255 '<input type="range"
7256 id="similarity_threshold"
7257 name="similarity_threshold"
7258 min="20"
7259 max="85"
7260 step="1"
7261 value="%s"
7262 class="range-slider" />',
7263 esc_attr($threshold)
7264 );
7265 echo sprintf(
7266 '<span id="threshold_value" class="range-value">%s</span>',
7267 esc_html($threshold)
7268 );
7269 echo '</div>';
7270 }
7271
7272 public function mxchat_rag_sources_limit_callback() {
7273 // Load from mxchat_options array
7274 $options = get_option('mxchat_options', []);
7275
7276 // Get value from options array with default of 6
7277 $rag_sources_limit = isset($options['rag_sources_limit']) ? intval($options['rag_sources_limit']) : 6;
7278
7279 echo '<div class="slider-container">';
7280 echo sprintf(
7281 '<input type="range"
7282 id="rag_sources_limit"
7283 name="rag_sources_limit"
7284 min="3"
7285 max="10"
7286 step="1"
7287 value="%s"
7288 class="range-slider" />',
7289 esc_attr($rag_sources_limit)
7290 );
7291 echo sprintf(
7292 '<span id="rag_sources_limit_value" class="range-value">%s</span>',
7293 esc_html($rag_sources_limit)
7294 );
7295 echo '</div>';
7296 }
7297
7298 public function mxchat_enqueue_admin_assets() {
7299 // Get plugin version
7300 $version = MXCHAT_VERSION;
7301
7302 // Use file modification time for development (remove in production)
7303 if (defined('WP_DEBUG') && WP_DEBUG) {
7304 $version = filemtime(plugin_dir_path(__FILE__) . '../mxchat-basic.php');
7305 }
7306
7307 $current_page = isset($_GET['page']) ? $_GET['page'] : '';
7308 $plugin_url = plugin_dir_url(__FILE__) . '../';
7309
7310 // Only load on MxChat pages
7311 if (strpos($current_page, 'mxchat') === false) {
7312 return;
7313 }
7314
7315 // Always load these on all MxChat pages
7316 $this->enqueue_core_admin_assets($plugin_url, $version);
7317 $this->enqueue_page_specific_assets($current_page, $plugin_url, $version);
7318 $this->localize_admin_scripts($current_page);
7319 }
7320 private function enqueue_core_admin_assets($plugin_url, $version) {
7321 // Core admin styles
7322 wp_enqueue_style('mxchat-admin-css', $plugin_url . 'css/admin-style.css', array(), $version);
7323 wp_enqueue_style('mxchat-knowledge-css', $plugin_url . 'css/knowledge-style.css', array(), $version);
7324
7325 // New sidebar navigation styles (for main settings page)
7326 wp_enqueue_style('mxchat-admin-sidebar-css', $plugin_url . 'css/admin-sidebar.css', array(), $version);
7327
7328 // Core admin scripts
7329 wp_enqueue_script('mxchat-admin-js', $plugin_url . 'js/mxchat-admin.js', array('jquery'), $version, true);
7330 }
7331 private function enqueue_page_specific_assets($current_page, $plugin_url, $version) {
7332 switch ($current_page) {
7333 case 'mxchat-prompts':
7334 // Knowledge processing page assets
7335 wp_enqueue_style('mxchat-content-selector-css', $plugin_url . 'css/content-selector.css', array(), $version);
7336 wp_enqueue_script('mxchat-content-selector-js', $plugin_url . 'js/content-selector.js', array('jquery'), $version, true);
7337 // Add the knowledge processing script (common script needed for WordPress dismiss functionality)
7338 wp_enqueue_script('mxchat-knowledge-processing', $plugin_url . 'js/knowledge-processing.js', array('jquery', 'common'), $version, true);
7339 break;
7340
7341 case 'mxchat-transcripts':
7342 // Load admin sidebar CSS first (shared styles for sidebar navigation)
7343 wp_enqueue_style('mxchat-admin-sidebar-css', $plugin_url . 'css/admin-sidebar.css', array(), $version);
7344 // Load transcripts-specific styles
7345 wp_enqueue_style('mxchat-chat-transcripts-css', $plugin_url . 'css/chat-transcripts.css', array('mxchat-admin-sidebar-css'), $version);
7346 wp_enqueue_script('mxchat-transcripts-js', $plugin_url . 'js/mxchat_transcripts.js', array('jquery'), $version, true);
7347 break;
7348
7349 case 'mxchat-actions':
7350 // Load admin sidebar CSS first (shared styles for sidebar navigation)
7351 wp_enqueue_style('mxchat-admin-sidebar-css', $plugin_url . 'css/admin-sidebar.css', array(), $version);
7352 // Load actions-specific styles
7353 wp_enqueue_style('mxchat-actions-css', $plugin_url . 'css/actions.css', array('mxchat-admin-sidebar-css'), $version);
7354 wp_enqueue_script('mxchat-actions-js', $plugin_url . 'js/mxchat_actions.js', array('jquery'), $version, true);
7355
7356 // Localize script data for actions page
7357 $is_activated = get_option('mxchat_pro_license_status') === 'active';
7358 wp_localize_script('mxchat-actions-js', 'mxchActionsData', array(
7359 'ajaxUrl' => admin_url('admin-ajax.php'),
7360 'nonce' => wp_create_nonce('mxchat_actions_nonce'),
7361 'addNonce' => wp_create_nonce('mxchat_add_intent_nonce'),
7362 'editNonce' => wp_create_nonce('mxchat_edit_intent'),
7363 'deleteNonce' => wp_create_nonce('mxchat_delete_intent_nonce'),
7364 'toggleNonce' => wp_create_nonce('mxchat_actions_nonce'),
7365 'isActivated' => $is_activated,
7366 'i18n' => array(
7367 'confirmDelete' => __('Are you sure you want to delete this action?', 'mxchat'),
7368 'confirmBulkDelete' => __('Are you sure you want to delete the selected actions?', 'mxchat'),
7369 'saving' => __('Saving...', 'mxchat'),
7370 'saved' => __('Saved successfully!', 'mxchat'),
7371 'error' => __('An error occurred. Please try again.', 'mxchat'),
7372 'proRequired' => __('This feature requires MxChat Pro.', 'mxchat'),
7373 'addonRequired' => __('This feature requires an add-on.', 'mxchat')
7374 )
7375 ));
7376 break;
7377
7378 case 'mxchat-activation':
7379 // Load admin sidebar CSS first (shared styles for sidebar navigation)
7380 wp_enqueue_style('mxchat-admin-sidebar-css', $plugin_url . 'css/admin-sidebar.css', array(), $version);
7381 // Load pro page-specific styles
7382 wp_enqueue_style('mxchat-pro-css', $plugin_url . 'css/admin-pro.css', array('mxchat-admin-sidebar-css'), $version);
7383 // Load pro page JavaScript
7384 wp_enqueue_script('mxchat-pro-js', $plugin_url . 'js/mxchat_pro.js', array('jquery'), $version, true);
7385 // Load activation script for license activation/deactivation
7386 wp_enqueue_script('mxchat-activation-js', $plugin_url . 'js/activation-script.js', array('jquery'), $version, true);
7387 break;
7388 default:
7389 wp_enqueue_script(
7390 'mxchat-test-streaming-js',
7391 $plugin_url . 'js/mxchat-test-streaming.js',
7392 ['jquery'],
7393 $version,
7394 true
7395 );
7396
7397 wp_localize_script('mxchat-test-streaming-js', 'mxchatTestStreamingAjax', [
7398 'ajax_url' => admin_url('admin-ajax.php'),
7399 'nonce' => wp_create_nonce('mxchat_test_streaming_nonce'),
7400 'settings_nonce' => wp_create_nonce('mxchat_save_setting_nonce')
7401 ]);
7402 break;
7403 }
7404 }
7405 private function localize_admin_scripts($current_page) {
7406 // Base localization data for main admin script
7407 $base_data = array(
7408 'ajax_url' => admin_url('admin-ajax.php'),
7409 'nonce' => wp_create_nonce('mxchat_admin_nonce'),
7410 'admin_url' => admin_url(),
7411 'license_nonce' => wp_create_nonce('mxchat_activate_license_nonce'),
7412 'inline_edit_nonce' => wp_create_nonce('mxchat_save_inline_nonce'),
7413 'setting_nonce' => wp_create_nonce('mxchat_save_setting_nonce'),
7414 'export_nonce' => wp_create_nonce('mxchat_export_transcripts'),
7415 'actions_nonce' => wp_create_nonce('mxchat_actions_nonce'),
7416 'add_intent_nonce' => wp_create_nonce('mxchat_add_intent_nonce'),
7417 'edit_intent_nonce' => wp_create_nonce('mxchat_edit_intent'),
7418 'toggle_action_nonce' => wp_create_nonce('mxchat_actions_nonce'),
7419 'fetch_openrouter_models_nonce' => wp_create_nonce('mxchat_fetch_openrouter_models'),
7420 'is_activated' => $this->is_activated ? '1' : '0',
7421 'status_refresh_interval' => 5000,
7422 'prompts_setting_nonce' => wp_create_nonce('mxchat_prompts_setting_nonce'),
7423 'ajaxurl' => admin_url('admin-ajax.php')
7424 );
7425
7426 // Localize main admin script with base data
7427 wp_localize_script('mxchat-admin-js', 'mxchatAdmin', $base_data);
7428
7429 // Page-specific localizations
7430 $this->localize_page_specific_scripts($current_page);
7431 }
7432 private function localize_page_specific_scripts($current_page) {
7433 switch ($current_page) {
7434 case 'mxchat-prompts':
7435 // Status updater localization
7436 wp_localize_script('mxchat-status-updater', 'mxchat_status_data', array(
7437 'ajax_url' => admin_url('admin-ajax.php'),
7438 'nonce' => wp_create_nonce('mxchat_status_nonce')
7439 ));
7440
7441 // Content selector localization
7442 wp_localize_script('mxchat-content-selector-js', 'mxchatSelector', array(
7443 'ajaxurl' => admin_url('admin-ajax.php'),
7444 'nonce' => wp_create_nonce('mxchat_content_selector_nonce'),
7445 'i18n' => array(
7446 'searchPlaceholder' => __('Search posts and pages...', 'mxchat'),
7447 'selectAll' => __('Select All', 'mxchat'),
7448 'process' => __('Process Selected', 'mxchat'),
7449 'cancel' => __('Cancel', 'mxchat'),
7450 'noResults' => __('No content found.', 'mxchat')
7451 )
7452 ));
7453
7454 wp_localize_script('mxchat-knowledge-processing', 'mxchatAdmin', array(
7455 'ajax_url' => admin_url('admin-ajax.php'),
7456 'status_nonce' => wp_create_nonce('mxchat_status_nonce'),
7457 'queue_nonce' => wp_create_nonce('mxchat_queue_nonce'),
7458 'stop_nonce' => wp_create_nonce('mxchat_stop_processing_action'),
7459 'settings_nonce' => wp_create_nonce('mxchat_prompts_setting_nonce'),
7460 'setting_nonce' => wp_create_nonce('mxchat_save_setting_nonce'),
7461 'entries_nonce' => wp_create_nonce('mxchat_entries_nonce'),
7462 'admin_url' => admin_url(),
7463 'ajaxurl' => admin_url('admin-ajax.php'),
7464 'status_refresh_interval' => 2000,
7465 'bot_id' => isset($_GET['bot_id']) ? sanitize_text_field($_GET['bot_id']) : 'default'
7466 ));
7467 break;
7468
7469 case 'mxchat-activation':
7470 wp_localize_script('mxchat-activation-js', 'mxchatAdmin', array(
7471 'ajax_url' => admin_url('admin-ajax.php'),
7472 'license_nonce' => wp_create_nonce('mxchat_activate_license_nonce')
7473 ));
7474 break;
7475
7476 case 'mxchat-transcripts':
7477 // Get chart data for localization
7478 $chart_data = $this->get_transcripts_chart_data();
7479
7480 wp_localize_script('mxchat-transcripts-js', 'mxchatAdmin', array(
7481 'ajax_url' => admin_url('admin-ajax.php'),
7482 'export_nonce' => wp_create_nonce('mxchat_export_transcripts'),
7483 'delete_nonce' => wp_create_nonce('mxchat_delete_chat_history'),
7484 'setting_nonce' => wp_create_nonce('mxchat_save_setting_nonce'),
7485 'translate_nonce' => wp_create_nonce('mxchat_translate_messages')
7486 ));
7487
7488 // Localize chart data separately - use array_values to ensure proper JSON array encoding
7489 wp_localize_script('mxchat-transcripts-js', 'mxchatChartData', array(
7490 'labels' => array_values($chart_data['labels']),
7491 'chats' => array_values($chart_data['chats']),
7492 'messages' => array_values($chart_data['messages'])
7493 ));
7494 break;
7495
7496 case 'mxchat-settings':
7497 default:
7498 // Color picker and settings localization
7499 wp_localize_script('mxchat-color-picker', 'mxchatStyleSettings', array(
7500 'ajax_url' => admin_url('admin-ajax.php'),
7501 'link_target_toggle' => $this->options['link_target_toggle'] ?? 'off',
7502 'user_message_bg_color' => $this->options['user_message_bg_color'] ?? '#fff',
7503 'user_message_font_color' => $this->options['user_message_font_color'] ?? '#212121',
7504 'bot_message_bg_color' => $this->options['bot_message_bg_color'] ?? '#212121',
7505 'bot_message_font_color' => $this->options['bot_message_font_color'] ?? '#fff',
7506 'top_bar_bg_color' => $this->options['top_bar_bg_color'] ?? '#212121',
7507 'send_button_font_color' => $this->options['send_button_font_color'] ?? '#212121',
7508 'close_button_color' => $this->options['close_button_color'] ?? '#fff',
7509 'chatbot_background_color' => $this->options['chatbot_background_color'] ?? '#212121',
7510 'icon_color' => $this->options['icon_color'] ?? '#fff',
7511 'chat_input_font_color' => $this->options['chat_input_font_color'] ?? '#212121',
7512 'pre_chat_message' => $this->options['pre_chat_message'] ?? esc_html__('Hey there! Ask me anything!', 'mxchat'),
7513 'rate_limit_message' => $this->options['rate_limit_message'] ?? esc_html__('Rate limit exceeded. Please try again later.', 'mxchat'),
7514 'loops_api_key' => $this->options['loops_api_key'] ?? '',
7515 'loops_mailing_list' => $this->options['loops_mailing_list'] ?? '',
7516 'triggered_phrase_response' => $this->options['triggered_phrase_response'] ?? esc_html__('Would you like to join our mailing list? Please provide your email below.', 'mxchat'),
7517 'email_capture_response' => $this->options['email_capture_response'] ?? esc_html__('Thank you for providing your email! You\'ve been added to our list.', 'mxchat'),
7518 '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'),
7519 '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'),
7520 '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'),
7521 'pdf_max_pages' => $this->options['pdf_max_pages'] ?? 69,
7522 'live_agent_webhook_url' => $this->options['live_agent_webhook_url'] ?? '',
7523 'live_agent_secret_key' => $this->options['live_agent_secret_key'] ?? '',
7524 'live_agent_bot_token' => $this->options['live_agent_bot_token'] ?? '',
7525 'live_agent_message_bg_color' => $this->options['live_agent_message_bg_color'] ?? '#ffffff',
7526 'live_agent_message_font_color' => $this->options['live_agent_message_font_color'] ?? '#333333',
7527 'chat_toolbar_toggle' => $this->options['chat_toolbar_toggle'] ?? 'off',
7528 'show_pdf_upload_button' => $this->options['show_pdf_upload_button'] ?? 'on',
7529 'show_word_upload_button' => $this->options['show_word_upload_button'] ?? 'on',
7530 'mode_indicator_bg_color' => $this->options['mode_indicator_bg_color'] ?? '#767676',
7531 'mode_indicator_font_color' => $this->options['mode_indicator_font_color'] ?? '#ffffff',
7532 'toolbar_icon_color' => $this->options['toolbar_icon_color'] ?? '#212121',
7533 ));
7534 break;
7535 }
7536
7537 // Additional localization that was in the original code
7538 wp_localize_script('mxchat-admin-js', 'mxchatPromptsAdmin', array(
7539 'ajax_url' => admin_url('admin-ajax.php'),
7540 'prompts_setting_nonce' => wp_create_nonce('mxchat_prompts_setting_nonce'),
7541 ));
7542 }
7543
7544 public function mxchat_sanitize($input) {
7545 $new_input = array();
7546
7547 if (isset($input['api_key'])) {
7548 $new_input['api_key'] = sanitize_text_field($input['api_key']);
7549 }
7550
7551 if (isset($input['similarity_threshold'])) {
7552 $new_input['similarity_threshold'] = absint($input['similarity_threshold']); // Ensure it's an integer
7553 $new_input['similarity_threshold'] = min(max($new_input['similarity_threshold'], 20), 85); // Enforce range
7554 }
7555
7556 if (isset($input['rag_sources_limit'])) {
7557 $new_input['rag_sources_limit'] = absint($input['rag_sources_limit']); // Ensure it's an integer
7558 $new_input['rag_sources_limit'] = min(max($new_input['rag_sources_limit'], 3), 10); // Enforce range 3-10
7559 }
7560
7561 if (isset($input['xai_api_key'])) {
7562 $new_input['xai_api_key'] = sanitize_text_field($input['xai_api_key']);
7563 }
7564
7565 if (isset($input['claude_api_key'])) {
7566 $new_input['claude_api_key'] = sanitize_text_field($input['claude_api_key']);
7567 }
7568
7569 if (isset($input['enable_streaming_toggle'])) {
7570 $new_input['enable_streaming_toggle'] = ($input['enable_streaming_toggle'] === 'on') ? 'on' : 'off';
7571 } else {
7572 // If checkbox not checked, it won't be in $input, so set to 'off'
7573 $new_input['enable_streaming_toggle'] = 'off';
7574 }
7575
7576 if (isset($input['enable_web_search'])) {
7577 $new_input['enable_web_search'] = ($input['enable_web_search'] === 'on') ? 'on' : 'off';
7578 } else {
7579 // If checkbox not checked, it won't be in $input, so set to 'off'
7580 $new_input['enable_web_search'] = 'off';
7581 }
7582
7583 if (isset($input['deepseek_api_key'])) {
7584 $new_input['deepseek_api_key'] = sanitize_text_field($input['deepseek_api_key']);
7585 }
7586
7587 if (isset($input['gemini_api_key'])) {
7588 $new_input['gemini_api_key'] = sanitize_text_field($input['gemini_api_key']);
7589 }
7590
7591 if (isset($input['enable_woocommerce_integration'])) {
7592 $new_input['enable_woocommerce_integration'] = $input['enable_woocommerce_integration'] === 'on' ? 'on' : 'off';
7593 }
7594
7595 if (isset($input['privacy_toggle'])) {
7596 $new_input['privacy_toggle'] = $input['privacy_toggle'];
7597 }
7598
7599 if (isset($input['complianz_toggle'])) {
7600 $new_input['complianz_toggle'] = $input['complianz_toggle'];
7601 }
7602
7603 // Handle custom privacy text input
7604 if (isset($input['privacy_text'])) {
7605 // Allow basic HTML for links
7606 $new_input['privacy_text'] = wp_kses_post($input['privacy_text']);
7607 }
7608
7609 if (isset($input['system_prompt_instructions'])) {
7610 $new_input['system_prompt_instructions'] = sanitize_textarea_field($input['system_prompt_instructions']);
7611 }
7612
7613 if (isset($input['mxchat_pro_email'])) {
7614 $new_input['mxchat_pro_email'] = sanitize_email($input['mxchat_pro_email']);
7615 }
7616
7617 if (isset($input['mxchat_activation_key'])) {
7618 $new_input['mxchat_activation_key'] = sanitize_text_field($input['mxchat_activation_key']);
7619 }
7620
7621 if (isset($input['append_to_body'])) {
7622 $new_input['append_to_body'] = $input['append_to_body'] === 'on' ? 'on' : 'off';
7623 }
7624
7625 // Post type visibility settings
7626 if (isset($input['post_type_visibility_mode'])) {
7627 $allowed_modes = array('all', 'include', 'exclude');
7628 $new_input['post_type_visibility_mode'] = in_array($input['post_type_visibility_mode'], $allowed_modes)
7629 ? $input['post_type_visibility_mode']
7630 : 'all';
7631 }
7632
7633 if (isset($input['post_type_visibility_list'])) {
7634 if (is_array($input['post_type_visibility_list'])) {
7635 $new_input['post_type_visibility_list'] = array_map('sanitize_key', $input['post_type_visibility_list']);
7636 } else {
7637 $new_input['post_type_visibility_list'] = array();
7638 }
7639 }
7640
7641 if (isset($input['contextual_awareness_toggle'])) {
7642 $new_input['contextual_awareness_toggle'] = $input['contextual_awareness_toggle'] === 'on' ? 'on' : 'off';
7643 }
7644
7645 if (isset($input['show_frontend_debugger'])) {
7646 $new_input['show_frontend_debugger'] = $input['show_frontend_debugger'] === 'on' ? 'on' : 'off';
7647 }
7648
7649 if (isset($input['citation_links_toggle'])) {
7650 $new_input['citation_links_toggle'] = $input['citation_links_toggle'] === 'on' ? 'on' : 'off';
7651 }
7652
7653 if (isset($input['top_bar_title'])) {
7654 $new_input['top_bar_title'] = sanitize_text_field($input['top_bar_title']);
7655 }
7656
7657 if (isset($input['ai_agent_text'])) {
7658 $new_input['ai_agent_text'] = sanitize_text_field($input['ai_agent_text']);
7659 }
7660
7661 if (isset($input['enable_email_block'])) {
7662 $new_input['enable_email_block'] = sanitize_text_field($input['enable_email_block']);
7663 }
7664
7665 if (isset($input['email_blocker_header_content'])) {
7666 // wp_kses_post() allows standard HTML tags permitted by WordPress
7667 $new_input['email_blocker_header_content'] = wp_kses_post($input['email_blocker_header_content']);
7668 }
7669 if (isset($input['email_blocker_button_text'])) {
7670 $new_input['email_blocker_button_text'] = sanitize_text_field($input['email_blocker_button_text']);
7671 }
7672 // Sanitize name field toggle
7673 if (isset($input['enable_name_field'])) {
7674 $new_input['enable_name_field'] = ($input['enable_name_field'] === 'on') ? 'on' : 'off';
7675 } else {
7676 $new_input['enable_name_field'] = 'off';
7677 }
7678 // Sanitize name field placeholder
7679 if (isset($input['name_field_placeholder'])) {
7680 $new_input['name_field_placeholder'] = sanitize_text_field($input['name_field_placeholder']);
7681 }
7682 if (isset($input['intro_message'])) {
7683 $new_input['intro_message'] = wp_kses_post($input['intro_message']); // Use wp_kses_post instead
7684 }
7685
7686 if (isset($input['input_copy'])) {
7687 $new_input['input_copy'] = sanitize_text_field($input['input_copy']);
7688 }
7689
7690 if (isset($input['rate_limit_message'])) {
7691 $new_input['rate_limit_message'] = sanitize_text_field($input['rate_limit_message']);
7692 }
7693
7694 // Handle the new rate limits format
7695 if (isset($input['rate_limits']) && is_array($input['rate_limits'])) {
7696 $new_input['rate_limits'] = array();
7697 $allowed_limits = array('1', '3', '5', '10', '15', '20', '50', '100', 'unlimited');
7698 $allowed_timeframes = array('hourly', 'daily', 'weekly', 'monthly');
7699
7700 foreach ($input['rate_limits'] as $role_id => $settings) {
7701 $new_input['rate_limits'][$role_id] = array();
7702
7703 // Sanitize limit
7704 if (isset($settings['limit'])) {
7705 $limit = sanitize_text_field($settings['limit']);
7706 if (in_array($limit, $allowed_limits, true)) {
7707 $new_input['rate_limits'][$role_id]['limit'] = $limit;
7708 } else {
7709 $new_input['rate_limits'][$role_id]['limit'] = ($role_id === 'logged_out') ? '10' : '100'; // Default
7710 }
7711 }
7712
7713 // Sanitize timeframe
7714 if (isset($settings['timeframe'])) {
7715 $timeframe = sanitize_text_field($settings['timeframe']);
7716 if (in_array($timeframe, $allowed_timeframes, true)) {
7717 $new_input['rate_limits'][$role_id]['timeframe'] = $timeframe;
7718 } else {
7719 $new_input['rate_limits'][$role_id]['timeframe'] = 'daily'; // Default
7720 }
7721 }
7722
7723 // Sanitize message
7724 if (isset($settings['message'])) {
7725 $new_input['rate_limits'][$role_id]['message'] = sanitize_textarea_field($settings['message']);
7726 }
7727 }
7728 }
7729
7730 if (isset($input['pre_chat_message'])) {
7731 $new_input['pre_chat_message'] = sanitize_textarea_field($input['pre_chat_message']);
7732 }
7733
7734 if (isset($input['voyage_api_key'])) {
7735 $new_input['voyage_api_key'] = sanitize_text_field($input['voyage_api_key']);
7736 }
7737
7738 // Add to your sanitize function
7739 if (isset($input['embedding_model'])) {
7740 $allowed_models = array(
7741 'text-embedding-ada-002',
7742 'text-embedding-3-small',
7743 'text-embedding-3-large',
7744 'voyage-3-large',
7745 'gemini-embedding-001'
7746 );
7747 if (in_array($input['embedding_model'], $allowed_models)) {
7748 $new_input['embedding_model'] = sanitize_text_field($input['embedding_model']);
7749 }
7750 }
7751
7752 if (isset($input['model'])) {
7753 if ($input['model'] === 'openrouter') {
7754 $new_input['model'] = 'openrouter';
7755 } else {
7756 $allowed_models = array(
7757 'gemini-3-pro-preview',
7758 'gemini-3-flash-preview',
7759 'gemini-2.5-pro',
7760 'gemini-2.5-flash',
7761 'gemini-2.5-flash-lite',
7762 'gemini-2.0-flash',
7763 'gemini-2.0-flash-lite',
7764 'gemini-1.5-pro',
7765 'gemini-1.5-flash',
7766 'grok-4-0709',
7767 'grok-4-1-fast-reasoning',
7768 'grok-4-1-fast-non-reasoning',
7769 'grok-3-beta',
7770 'grok-3-fast-beta',
7771 'grok-3-mini-beta',
7772 'grok-3-mini-fast-beta',
7773 'grok-2',
7774 'deepseek-chat',
7775 'claude-opus-4-6',
7776 'claude-opus-4-5',
7777 'claude-sonnet-4-5-20250929',
7778 'claude-opus-4-1-20250805',
7779 'claude-haiku-4-5-20251001',
7780 'claude-opus-4-20250514',
7781 'claude-sonnet-4-20250514',
7782 'gpt-5.2',
7783 'gpt-5.1-chat-latest',
7784 'gpt-5.1-2025-11-13',
7785 'gpt-5',
7786 'gpt-5-mini',
7787 'gpt-5-nano',
7788 );
7789
7790 if (in_array($input['model'], $allowed_models)) {
7791 $new_input['model'] = sanitize_text_field($input['model']);
7792 } else {
7793 // Fallback for any deprecated model
7794 $new_input['model'] = 'gpt-5.1-chat-latest';
7795 }
7796 }
7797 }
7798
7799 if (isset($input['openrouter_selected_model'])) {
7800 // Just sanitize it, don't validate against a whitelist
7801 $new_input['openrouter_selected_model'] = sanitize_text_field($input['openrouter_selected_model']);
7802 }
7803
7804 // ADD THIS:
7805 if (isset($input['openrouter_selected_model_name'])) {
7806 $new_input['openrouter_selected_model_name'] = sanitize_text_field($input['openrouter_selected_model_name']);
7807 }
7808
7809 if (isset($input['openrouter_api_key'])) {
7810 $new_input['openrouter_api_key'] = sanitize_text_field($input['openrouter_api_key']);
7811 }
7812
7813
7814 if (isset($input['close_button_color'])) {
7815 $new_input['close_button_color'] = sanitize_hex_color($input['close_button_color']);
7816 }
7817
7818 if (isset($input['chatbot_bg_color'])) {
7819 $new_input['chatbot_bg_color'] = sanitize_hex_color($input['chatbot_bg_color']);
7820 }
7821
7822 if (isset($input['woocommerce_consumer_key'])) {
7823 $new_input['woocommerce_consumer_key'] = sanitize_text_field($input['woocommerce_consumer_key']);
7824 }
7825
7826 if (isset($input['woocommerce_consumer_secret'])) {
7827 $new_input['woocommerce_consumer_secret'] = sanitize_text_field($input['woocommerce_consumer_secret']);
7828 }
7829
7830 if (isset($input['user_message_bg_color'])) {
7831 $new_input['user_message_bg_color'] = sanitize_hex_color($input['user_message_bg_color']);
7832 }
7833
7834 if (isset($input['user_message_font_color'])) {
7835 $new_input['user_message_font_color'] = sanitize_hex_color($input['user_message_font_color']);
7836 }
7837
7838 if (isset($input['bot_message_bg_color'])) {
7839 $new_input['bot_message_bg_color'] = sanitize_hex_color($input['bot_message_bg_color']);
7840 }
7841
7842 if (isset($input['bot_message_font_color'])) {
7843 $new_input['bot_message_font_color'] = sanitize_hex_color($input['bot_message_font_color']);
7844 }
7845
7846 if (isset($input['live_agent_message_bg_color'])) {
7847 $new_input['live_agent_message_bg_color'] = sanitize_hex_color($input['live_agent_message_bg_color']);
7848 }
7849
7850 if (isset($input['live_agent_message_font_color'])) {
7851 $new_input['live_agent_message_font_color'] = sanitize_hex_color($input['live_agent_message_font_color']);
7852 }
7853
7854 if (isset($input['mode_indicator_bg_color'])) {
7855 $new_input['mode_indicator_bg_color'] = sanitize_hex_color($input['mode_indicator_bg_color']);
7856 }
7857
7858 if (isset($input['mode_indicator_font_color'])) {
7859 $new_input['mode_indicator_font_color'] = sanitize_hex_color($input['mode_indicator_font_color']);
7860 }
7861
7862 if (isset($input['toolbar_icon_color'])) {
7863 $new_input['toolbar_icon_color'] = sanitize_hex_color($input['toolbar_icon_color']);
7864 }
7865
7866 if (isset($input['top_bar_bg_color'])) {
7867 $new_input['top_bar_bg_color'] = sanitize_hex_color($input['top_bar_bg_color']);
7868 }
7869
7870 if (isset($input['quick_questions_toggle_color'])) {
7871 $new_input['quick_questions_toggle_color'] = sanitize_hex_color($input['quick_questions_toggle_color']);
7872 }
7873
7874 if (isset($input['send_button_font_color'])) {
7875 $new_input['send_button_font_color'] = sanitize_hex_color($input['send_button_font_color']);
7876 }
7877
7878 if (isset($input['chatbot_background_color'])) {
7879 $new_input['chatbot_background_color'] = sanitize_hex_color($input['chatbot_background_color']);
7880 }
7881
7882 if (isset($input['icon_color'])) {
7883 $new_input['icon_color'] = sanitize_hex_color($input['icon_color']);
7884 }
7885
7886 if (isset($input['custom_icon'])) {
7887 $new_input['custom_icon'] = esc_url_raw($input['custom_icon']);
7888 }
7889
7890 if (isset($input['title_icon'])) {
7891 $new_input['title_icon'] = esc_url_raw($input['title_icon']);
7892 }
7893
7894 if (isset($input['chat_input_font_color'])) {
7895 $new_input['chat_input_font_color'] = sanitize_hex_color($input['chat_input_font_color']);
7896 }
7897
7898 // Sanitize link_target_toggle
7899 if (isset($input['link_target_toggle'])) {
7900 $new_input['link_target_toggle'] = $input['link_target_toggle'] === 'on' ? 'on' : 'off';
7901 }
7902
7903 // Sanitize Loops API Key
7904 if (isset($input['loops_api_key'])) {
7905 $new_input['loops_api_key'] = sanitize_text_field($input['loops_api_key']);
7906 }
7907
7908 if (isset($input['chat_persistence_toggle'])) {
7909 $new_input['chat_persistence_toggle'] = $input['chat_persistence_toggle'] === 'on' ? 'on' : 'off';
7910 }
7911
7912 if (isset($input['popular_question_1'])) {
7913 $new_input['popular_question_1'] = sanitize_text_field($input['popular_question_1']);
7914 }
7915
7916 if (isset($input['popular_question_2'])) {
7917 $new_input['popular_question_2'] = sanitize_text_field($input['popular_question_2']);
7918 }
7919
7920 if (isset($input['popular_question_3'])) {
7921 $new_input['popular_question_3'] = sanitize_text_field($input['popular_question_3']);
7922 }
7923
7924 if (isset($input['additional_popular_questions']) && is_array($input['additional_popular_questions'])) {
7925 $new_input['additional_popular_questions'] = array_map('sanitize_text_field', $input['additional_popular_questions']);
7926 }
7927
7928 // Sanitize Loops Mailing List
7929 if (isset($input['loops_mailing_list'])) {
7930 $new_input['loops_mailing_list'] = sanitize_text_field($input['loops_mailing_list']);
7931 }
7932
7933 // Sanitize Triggered Phrase Response
7934 if (isset($input['triggered_phrase_response'])) {
7935 $new_input['triggered_phrase_response'] = wp_kses_post($input['triggered_phrase_response']);
7936 }
7937 if (isset($input['email_capture_response'])) {
7938 $new_input['email_capture_response'] = wp_kses_post($input['email_capture_response']);
7939 }
7940
7941 // Sanitize Brave Search Settings
7942 if (isset($input['brave_api_key'])) {
7943 $new_input['brave_api_key'] = sanitize_text_field($input['brave_api_key']);
7944 }
7945
7946 if (isset($input['brave_image_count'])) {
7947 $image_count = intval($input['brave_image_count']);
7948 $new_input['brave_image_count'] = ($image_count >=1 && $image_count <=6) ? $image_count : 4;
7949 }
7950
7951 if (isset($input['brave_safe_search'])) {
7952 $allowed = array('strict', 'off');
7953 $new_input['brave_safe_search'] = in_array($input['brave_safe_search'], $allowed, true) ? $input['brave_safe_search'] : 'strict';
7954 }
7955
7956 if (isset($input['brave_news_count'])) {
7957 $news_count = intval($input['brave_news_count']);
7958 $new_input['brave_news_count'] = ($news_count >=1 && $news_count <=10) ? $news_count : 3;
7959 }
7960
7961 if (isset($input['brave_country'])) {
7962 $new_input['brave_country'] = sanitize_text_field($input['brave_country']);
7963 }
7964
7965 if (isset($input['brave_language'])) {
7966 $new_input['brave_language'] = sanitize_text_field($input['brave_language']);
7967 }
7968
7969 if (isset($input['chat_toolbar_toggle'])) {
7970 $new_input['chat_toolbar_toggle'] = $input['chat_toolbar_toggle'] === 'on' ? 'on' : 'off';
7971 }
7972
7973 // Sanitize PDF upload button toggle
7974 if (isset($input['show_pdf_upload_button'])) {
7975 $new_input['show_pdf_upload_button'] = $input['show_pdf_upload_button'] === 'on' ? 'on' : 'off';
7976 } else {
7977 $new_input['show_pdf_upload_button'] = 'off'; // If checkbox is unchecked
7978 }
7979
7980 // Sanitize Word upload button toggle
7981 if (isset($input['show_word_upload_button'])) {
7982 $new_input['show_word_upload_button'] = $input['show_word_upload_button'] === 'on' ? 'on' : 'off';
7983 } else {
7984 $new_input['show_word_upload_button'] = 'off'; // If checkbox is unchecked
7985 }
7986
7987 if (isset($input['pdf_intent_trigger_text'])) {
7988 $new_input['pdf_intent_trigger_text'] = sanitize_text_field($input['pdf_intent_trigger_text']);
7989 }
7990
7991 if (isset($input['pdf_intent_success_text'])) {
7992 $new_input['pdf_intent_success_text'] = sanitize_text_field($input['pdf_intent_success_text']);
7993 }
7994
7995 if (isset($input['pdf_intent_error_text'])) {
7996 $new_input['pdf_intent_error_text'] = sanitize_text_field($input['pdf_intent_error_text']);
7997 }
7998
7999 if (isset($input['pdf_max_pages'])) {
8000 $new_input['pdf_max_pages'] = intval($input['pdf_max_pages']);
8001 if ($new_input['pdf_max_pages'] < 1 || $new_input['pdf_max_pages'] > 69) {
8002 $new_input['pdf_max_pages'] = 69; // Default to 69 if out of range
8003 }
8004 }
8005
8006 if (isset($input['live_agent_webhook_url'])) {
8007 $new_input['live_agent_webhook_url'] = esc_url_raw($input['live_agent_webhook_url']);
8008 }
8009 if (isset($input['live_agent_secret_key'])) {
8010 $new_input['live_agent_secret_key'] = sanitize_text_field($input['live_agent_secret_key']);
8011 }
8012
8013 // Live Agent Integration
8014 if (isset($input['live_agent_bot_token'])) {
8015 $new_input['live_agent_bot_token'] = sanitize_text_field($input['live_agent_bot_token']);
8016 }
8017
8018 if (isset($input['live_agent_user_ids'])) {
8019 $new_input['live_agent_user_ids'] = sanitize_textarea_field($input['live_agent_user_ids']);
8020 }
8021
8022 if (isset($input['live_agent_status'])) {
8023 $new_input['live_agent_status'] = ($input['live_agent_status'] === 'on') ? 'on' : 'off';
8024 }
8025 if (isset($input['live_agent_away_message'])) {
8026 $new_input['live_agent_away_message'] = sanitize_textarea_field($input['live_agent_away_message']);
8027 }
8028 if (isset($input['live_agent_notification_message'])) {
8029 $new_input['live_agent_notification_message'] = sanitize_textarea_field($input['live_agent_notification_message']);
8030 }
8031
8032 // Telegram Integration
8033 if (isset($input['telegram_status'])) {
8034 $new_input['telegram_status'] = ($input['telegram_status'] === 'on') ? 'on' : 'off';
8035 }
8036 if (isset($input['telegram_bot_token'])) {
8037 $new_input['telegram_bot_token'] = sanitize_text_field($input['telegram_bot_token']);
8038 }
8039 if (isset($input['telegram_group_id'])) {
8040 $new_input['telegram_group_id'] = sanitize_text_field($input['telegram_group_id']);
8041 }
8042 if (isset($input['telegram_webhook_secret'])) {
8043 $new_input['telegram_webhook_secret'] = sanitize_text_field($input['telegram_webhook_secret']);
8044 }
8045 if (isset($input['telegram_notification_message'])) {
8046 $new_input['telegram_notification_message'] = sanitize_textarea_field($input['telegram_notification_message']);
8047 }
8048 if (isset($input['telegram_away_message'])) {
8049 $new_input['telegram_away_message'] = sanitize_textarea_field($input['telegram_away_message']);
8050 }
8051
8052 return $new_input;
8053 }
8054
8055
8056 // Method to append the chatbot to the body
8057 public function mxchat_append_chatbot_to_body() {
8058 $options = get_option('mxchat_options');
8059 if (isset($options['append_to_body']) && $options['append_to_body'] === 'on') {
8060 echo do_shortcode('[mxchat_chatbot floating="yes"]');
8061 }
8062 }
8063
8064
8065
8066
8067
8068 private function mxchat_fetch_loops_mailing_lists($api_key) {
8069 $url = 'https://app.loops.so/api/v1/lists';
8070 $response = wp_remote_get($url, array(
8071 'headers' => array(
8072 'Authorization' => 'Bearer ' . $api_key,
8073 'Content-Type' => 'application/json'
8074 )
8075 ));
8076
8077 if (is_wp_error($response)) {
8078 return array();
8079 }
8080
8081 $body = wp_remote_retrieve_body($response);
8082 $lists = json_decode($body, true);
8083
8084 return isset($lists) && is_array($lists) ? $lists : array();
8085 }
8086
8087 function mxchat_calculate_cosine_similarity($vec1, $vec2) {
8088 if (empty($vec1) || empty($vec2)) {
8089 return 0.0;
8090 }
8091
8092 $dot_product = 0.0;
8093 $norm_a = 0.0;
8094 $norm_b = 0.0;
8095
8096 for ($i = 0; $i < count($vec1); $i++) {
8097 $dot_product += $vec1[$i] * $vec2[$i];
8098 $norm_a += pow($vec1[$i], 2);
8099 $norm_b += pow($vec2[$i], 2);
8100 }
8101
8102 if ($norm_a == 0.0 || $norm_b == 0.0) {
8103 return 0.0;
8104 } else {
8105 return $dot_product / (sqrt($norm_a) * sqrt($norm_b));
8106 }
8107 }
8108
8109 /**
8110 * Validates nonce and deletes all chat prompts
8111 */
8112 public function mxchat_handle_delete_all_prompts() {
8113 //error_log('=== DELETE ALL DEBUG START ===');
8114
8115 // Verify nonce
8116 if (!isset($_POST['mxchat_delete_all_prompts_nonce']) || !wp_verify_nonce($_POST['mxchat_delete_all_prompts_nonce'], 'mxchat_delete_all_prompts_action')) {
8117 //error_log('DEBUG: Nonce verification failed');
8118 wp_die(__('Nonce verification failed.', 'mxchat'));
8119 }
8120
8121 // Check permissions
8122 if (!current_user_can('manage_options')) {
8123 //error_log('DEBUG: Permission check failed');
8124 wp_die(__('You do not have sufficient permissions to delete all prompts.', 'mxchat'));
8125 }
8126
8127 // Get bot_id from POST data
8128 $bot_id = isset($_POST['bot_id']) ? sanitize_text_field($_POST['bot_id']) : 'default';
8129 //error_log('DEBUG: Bot ID from POST: ' . $bot_id);
8130
8131 $success = true;
8132 $error_messages = array();
8133
8134 // Get bot-specific Pinecone configuration
8135 $pinecone_manager = MxChat_Pinecone_Manager::get_instance();
8136 $pinecone_options = $pinecone_manager->mxchat_get_bot_pinecone_options($bot_id);
8137
8138 //error_log('DEBUG: Pinecone options retrieved:');
8139 //error_log(' - Use Pinecone: ' . ($pinecone_options['mxchat_use_pinecone'] ?? 'NOT SET'));
8140 //error_log(' - API Key: ' . (empty($pinecone_options['mxchat_pinecone_api_key']) ? 'EMPTY' : 'SET'));
8141 //error_log(' - Host: ' . ($pinecone_options['mxchat_pinecone_host'] ?? 'NOT SET'));
8142
8143 $use_pinecone = ($pinecone_options['mxchat_use_pinecone'] ?? '0') === '1';
8144
8145 if ($use_pinecone && !empty($pinecone_options['mxchat_pinecone_api_key'])) {
8146 //error_log('[MXCHAT-DELETE] Pinecone is enabled for bot ' . $bot_id . ' - deleting all from Pinecone');
8147
8148 // Delete from bot-specific Pinecone index
8149 $result = $pinecone_manager->mxchat_delete_all_from_pinecone($pinecone_options);
8150
8151 //error_log('DEBUG: Delete all result: ' . ($result['success'] ? 'SUCCESS' : 'FAILED'));
8152 if (!$result['success']) {
8153 //error_log('DEBUG: Delete all error: ' . $result['message']);
8154 }
8155
8156 if (!$result['success']) {
8157 $success = false;
8158 $error_messages[] = $result['message'];
8159 }
8160
8161 // No cache clearing needed since we removed caching
8162
8163 } else {
8164 //error_log('[MXCHAT-DELETE] Pinecone not enabled for bot ' . $bot_id . ' - deleting from WordPress database');
8165
8166 // Delete from WordPress database
8167 global $wpdb;
8168 $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
8169
8170 // If multi-bot is active and we have a specific bot, delete only that bot's content
8171 if ($bot_id !== 'default' && class_exists('MxChat_Multi_Bot_Manager')) {
8172 $result = $wpdb->delete(
8173 $table_name,
8174 array('bot_id' => $bot_id),
8175 array('%s')
8176 );
8177 } else {
8178 // Delete all content for default bot
8179 $result = $wpdb->query("DELETE FROM {$table_name}");
8180 }
8181
8182 if ($result === false) {
8183 $success = false;
8184 $error_messages[] = 'Failed to delete from WordPress database';
8185 }
8186 }
8187
8188 // No cache clearing needed since we removed caching
8189
8190 //error_log('=== DELETE ALL DEBUG END ===');
8191
8192 // Redirect back with a success message and bot_id
8193 $redirect_url = add_query_arg(array(
8194 'page' => 'mxchat-prompts',
8195 'bot_id' => $bot_id,
8196 'all_deleted' => $success ? 'true' : 'false'
8197 ), admin_url('admin.php'));
8198
8199 wp_safe_redirect($redirect_url);
8200 exit;
8201 }
8202
8203
8204
8205 /**
8206 * Handles deletion prompt with nonce validation
8207 */
8208 public function mxchat_handle_delete_prompt() {
8209 // Sanitize and validate nonce
8210 $nonce = isset($_GET['_wpnonce']) ? sanitize_text_field(wp_unslash($_GET['_wpnonce'])) : '';
8211 if (empty($nonce) || !wp_verify_nonce($nonce, 'mxchat_delete_prompt_nonce')) {
8212 wp_die(esc_html__('Nonce verification failed.', 'mxchat'));
8213 }
8214
8215 // Check permissions
8216 if (!current_user_can('manage_options')) {
8217 wp_die(esc_html__('You do not have sufficient permissions to delete prompts.', 'mxchat'));
8218 }
8219
8220 // Get ID and source parameters
8221 $id = isset($_GET['id']) ? sanitize_text_field($_GET['id']) : '';
8222 $source = isset($_GET['source']) ? sanitize_text_field($_GET['source']) : '';
8223
8224 if (empty($id)) {
8225 wp_die(esc_html__('Invalid prompt ID.', 'mxchat'));
8226 }
8227
8228 $success = false;
8229 $error_message = '';
8230
8231 // Check if Pinecone is enabled and determine source automatically if not specified
8232 $pinecone_options = get_option('mxchat_pinecone_addon_options', array());
8233 $use_pinecone = ($pinecone_options['mxchat_use_pinecone'] ?? '0') === '1';
8234
8235 // If source is not specified, determine based on Pinecone configuration
8236 if (empty($source)) {
8237 $source = $use_pinecone ? 'pinecone' : 'wordpress';
8238 }
8239
8240 if ($source === 'pinecone' || $use_pinecone) {
8241 //error_log('[MXCHAT-DELETE] Deleting from Pinecone, ID: ' . $id);
8242
8243 // Handle Pinecone deletion
8244 if (empty($pinecone_options['mxchat_pinecone_host']) ||
8245 empty($pinecone_options['mxchat_pinecone_api_key'])) {
8246 wp_die(esc_html__('Pinecone configuration is missing.', 'mxchat'));
8247 }
8248
8249 // Delete from Pinecone using the vector ID directly
8250 $pinecone_manager = MxChat_Pinecone_Manager::get_instance();
8251 $result = $pinecone_manager->mxchat_delete_from_pinecone_by_vector_id(
8252 $id,
8253 $pinecone_options['mxchat_pinecone_api_key'],
8254 $pinecone_options['mxchat_pinecone_host']
8255 );
8256 if ($result['success']) {
8257 $success = true;
8258
8259 // Remove from vector cache
8260 $pinecone_manager->mxchat_remove_from_pinecone_vector_cache($id);
8261 $pinecone_manager->mxchat_remove_from_processed_content_caches($id);
8262
8263 set_transient('mxchat_admin_notice_success',
8264 esc_html__('Vector deleted successfully from Pinecone.', 'mxchat'), 30);
8265 } else {
8266 $error_message = $result['message'];
8267 set_transient('mxchat_admin_notice_error',
8268 esc_html__('Failed to delete from Pinecone: ', 'mxchat') . esc_html($error_message), 30);
8269 }
8270 } else {
8271 //error_log('[MXCHAT-DELETE] Deleting from WordPress database, ID: ' . $id);
8272
8273 // Handle WordPress database deletion
8274 global $wpdb;
8275 $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
8276
8277 // Clear cache and delete prompt
8278 wp_cache_delete('prompt_' . $id, 'mxchat_prompts');
8279
8280 $result = $wpdb->delete(
8281 $table_name,
8282 array('id' => intval($id)),
8283 array('%d')
8284 );
8285
8286 if ($result !== false) {
8287 $success = true;
8288 set_transient('mxchat_admin_notice_success',
8289 esc_html__('Entry deleted successfully.', 'mxchat'), 30);
8290 } else {
8291 set_transient('mxchat_admin_notice_error',
8292 esc_html__('Failed to delete entry from database.', 'mxchat'), 30);
8293 }
8294 }
8295
8296 // Redirect back to the prompts page
8297 wp_safe_redirect(add_query_arg(
8298 array(
8299 'page' => 'mxchat-prompts',
8300 'deleted' => $success ? 'true' : 'false'
8301 ),
8302 admin_url('admin.php')
8303 ));
8304 exit;
8305 }
8306
8307
8308
8309
8310 /**
8311 * Averages multiple vectors into a single vector
8312 */
8313 private function mxchat_average_vectors($vectors) {
8314 $vector_length = count($vectors[0]);
8315 $sum_vector = array_fill(0, $vector_length, 0);
8316
8317 foreach ($vectors as $vector) {
8318 for ($i = 0; $i < $vector_length; $i++) {
8319 $sum_vector[$i] += $vector[$i];
8320 }
8321 }
8322
8323 // Divide each component by the number of vectors to get the average
8324 $num_vectors = count($vectors);
8325 for ($i = 0; $i < $vector_length; $i++) {
8326 $sum_vector[$i] /= $num_vectors;
8327 }
8328
8329 return $sum_vector;
8330 }
8331
8332
8333
8334
8335 /**
8336 * Generates embeddings from input text for MXChat
8337 */
8338 public function mxchat_generate_embedding($text) {
8339 // Enable detailed logging for debugging
8340 //error_log('[MXCHAT-EMBED] Starting embedding generation. Text length: ' . strlen($text) . ' bytes');
8341 //error_log('[MXCHAT-EMBED] Text preview: ' . substr($text, 0, 100) . '...');
8342
8343 $options = get_option('mxchat_options');
8344 $selected_model = $options['embedding_model'] ?? 'text-embedding-ada-002';
8345 //error_log('[MXCHAT-EMBED] Selected embedding model: ' . $selected_model);
8346
8347 // Determine provider and endpoint
8348 if (strpos($selected_model, 'voyage') === 0) {
8349 $api_key = $options['voyage_api_key'] ?? '';
8350 $endpoint = 'https://api.voyageai.com/v1/embeddings';
8351 $provider_name = 'Voyage AI';
8352 //error_log('[MXCHAT-EMBED] Using Voyage AI API');
8353 } elseif (strpos($selected_model, 'gemini-embedding') === 0) {
8354 $api_key = $options['gemini_api_key'] ?? '';
8355 $endpoint = 'https://generativelanguage.googleapis.com/v1beta/models/' . $selected_model . ':embedContent';
8356 $provider_name = 'Google Gemini';
8357 //error_log('[MXCHAT-EMBED] Using Google Gemini API');
8358 } else {
8359 $api_key = $options['api_key'] ?? '';
8360 $endpoint = 'https://api.openai.com/v1/embeddings';
8361 $provider_name = 'OpenAI';
8362 //error_log('[MXCHAT-EMBED] Using OpenAI API');
8363 }
8364
8365 //error_log('[MXCHAT-EMBED] Using endpoint: ' . $endpoint);
8366
8367 if (empty($api_key)) {
8368 $error_message = sprintf('Missing %s API key. Please configure your API key in the MxChat settings.', $provider_name);
8369 //error_log('[MXCHAT-EMBED] Error: ' . $error_message);
8370 return $error_message;
8371 }
8372
8373 // Check if text is too long (for OpenAI, roughly estimate tokens as words/0.75)
8374 $estimated_tokens = ceil(str_word_count($text) / 0.75);
8375 //error_log('[MXCHAT-EMBED] Estimated token count: ~' . $estimated_tokens);
8376
8377 if ($estimated_tokens > 8000 && strpos($selected_model, 'voyage') === false && strpos($selected_model, 'gemini-embedding') === false) {
8378 //error_log('[MXCHAT-EMBED] Warning: Text may exceed OpenAI token limits (8K for most models)');
8379 // Consider truncating text here
8380 }
8381
8382 // Prepare request body based on provider
8383 if (strpos($selected_model, 'gemini-embedding') === 0) {
8384 // Gemini API format
8385 $request_body = array(
8386 'model' => 'models/' . $selected_model,
8387 'content' => array(
8388 'parts' => array(
8389 array('text' => $text)
8390 )
8391 )
8392 );
8393
8394 // Set output dimensionality to 1536 for consistency with other models
8395 $request_body['outputDimensionality'] = 1536;
8396 } else {
8397 // OpenAI/Voyage API format
8398 $request_body = array(
8399 'model' => $selected_model,
8400 'input' => $text
8401 );
8402
8403 // Add output_dimension for voyage-3-large model
8404 if ($selected_model === 'voyage-3-large') {
8405 $request_body['output_dimension'] = 2048;
8406 }
8407 }
8408
8409 //error_log('[MXCHAT-EMBED] Request prepared with model: ' . $selected_model);
8410
8411 // Prepare headers based on provider
8412 if (strpos($selected_model, 'gemini-embedding') === 0) {
8413 // Gemini uses API key as query parameter
8414 $endpoint .= '?key=' . $api_key;
8415 $headers = array(
8416 'Content-Type' => 'application/json'
8417 );
8418 } else {
8419 // OpenAI/Voyage use Bearer token
8420 $headers = array(
8421 'Authorization' => 'Bearer ' . $api_key,
8422 'Content-Type' => 'application/json'
8423 );
8424 }
8425
8426 // Make API request
8427 //error_log('[MXCHAT-EMBED] Sending API request to: ' . $endpoint);
8428 $response = wp_remote_post($endpoint, array(
8429 'body' => wp_json_encode($request_body),
8430 'headers' => $headers,
8431 'timeout' => 60 // Increased timeout for large inputs
8432 ));
8433
8434 // Handle wp_remote_post errors
8435 if (is_wp_error($response)) {
8436 $error_message = $response->get_error_message();
8437 //error_log('[MXCHAT-EMBED] WP Remote Post Error: ' . $error_message);
8438 return 'Connection error: ' . $error_message;
8439 }
8440
8441 // Get and check HTTP response code
8442 $http_code = wp_remote_retrieve_response_code($response);
8443 //error_log('[MXCHAT-EMBED] API Response Code: ' . $http_code);
8444
8445 if ($http_code !== 200) {
8446 $error_body = wp_remote_retrieve_body($response);
8447 //error_log('[MXCHAT-EMBED] API Error Response Body: ' . $error_body);
8448
8449 // Try to parse error for more details
8450 $error_json = json_decode($error_body, true);
8451 if (json_last_error() === JSON_ERROR_NONE && isset($error_json['error'])) {
8452 $error_type = $error_json['error']['type'] ?? 'unknown';
8453 $error_message = $error_json['error']['message'] ?? 'No message';
8454 //error_log('[MXCHAT-EMBED] API Error Type: ' . $error_type);
8455 //error_log('[MXCHAT-EMBED] API Error Message: ' . $error_message);
8456
8457 // Customize error message for common API errors
8458 if ($error_type === 'invalid_request_error' && strpos($error_message, 'API key') !== false) {
8459 $error_message = sprintf('Invalid %s API key. Please check your API key in the MxChat settings.', $provider_name);
8460 } elseif ($error_type === 'authentication_error') {
8461 $error_message = sprintf('%s authentication failed. Please verify your API key in the MxChat settings.', $provider_name);
8462 }
8463
8464 //error_log('[MXCHAT-EMBED] Returning error: ' . $error_message);
8465 return $error_message;
8466 }
8467
8468 $error_message = sprintf("API Error (HTTP %d): Unable to generate embedding", $http_code);
8469 //error_log('[MXCHAT-EMBED] Returning error: ' . $error_message);
8470 return $error_message;
8471 }
8472
8473 // Parse response body
8474 $response_body = wp_remote_retrieve_body($response);
8475 //error_log('[MXCHAT-EMBED] Received response length: ' . strlen($response_body) . ' bytes');
8476
8477 $response_data = json_decode($response_body, true);
8478
8479 if (json_last_error() !== JSON_ERROR_NONE) {
8480 $error = json_last_error_msg();
8481 //error_log('[MXCHAT-EMBED] JSON Parse Error: ' . $error);
8482 //error_log('[MXCHAT-EMBED] Response preview: ' . substr($response_body, 0, 200));
8483 return "Failed to parse API response: $error";
8484 }
8485
8486 // Handle different response formats based on provider
8487 if (strpos($selected_model, 'gemini-embedding') === 0) {
8488 // Gemini API response format
8489 if (isset($response_data['embedding']['values'])) {
8490 $embedding_dimensions = count($response_data['embedding']['values']);
8491 //error_log('[MXCHAT-EMBED] Successfully extracted Gemini embedding with ' . $embedding_dimensions . ' dimensions');
8492
8493 // Check if embedding dimensions are as expected (should be 1536)
8494 if ($embedding_dimensions !== 1536) {
8495 //error_log('[MXCHAT-EMBED] Warning: Unexpected Gemini embedding dimensions: ' . $embedding_dimensions);
8496 }
8497
8498 return $response_data['embedding']['values'];
8499 } else {
8500 //error_log('[MXCHAT-EMBED] Error: No embedding found in Gemini 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 = "Gemini 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 Gemini API response format: No embedding found";
8510 //error_log('[MXCHAT-EMBED] ' . $error_message);
8511 return $error_message;
8512 }
8513 } else {
8514 // OpenAI/Voyage API response format
8515 if (isset($response_data['data'][0]['embedding'])) {
8516 $embedding_dimensions = count($response_data['data'][0]['embedding']);
8517 //error_log('[MXCHAT-EMBED] Successfully extracted embedding with ' . $embedding_dimensions . ' dimensions');
8518
8519 // Check if embedding dimensions are as expected
8520 if (($selected_model === 'text-embedding-ada-002' && $embedding_dimensions !== 1536) ||
8521 ($selected_model === 'voyage-3-large' && $embedding_dimensions !== 2048)) {
8522 //error_log('[MXCHAT-EMBED] Warning: Unexpected embedding dimensions');
8523 }
8524
8525 return $response_data['data'][0]['embedding'];
8526 } else {
8527 //error_log('[MXCHAT-EMBED] Error: No embedding found in response');
8528 //error_log('[MXCHAT-EMBED] Response structure: ' . wp_json_encode(array_keys($response_data)));
8529
8530 if (isset($response_data['error'])) {
8531 $error_message = "API Error in response: " . wp_json_encode($response_data['error']);
8532 //error_log('[MXCHAT-EMBED] ' . $error_message);
8533 return $error_message;
8534 }
8535
8536 $error_message = "Invalid API response format: No embedding found";
8537 //error_log('[MXCHAT-EMBED] ' . $error_message);
8538 return $error_message;
8539 }
8540 }
8541 }
8542
8543
8544
8545 }
8546 ?>
8547