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

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

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