PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 2.4.9
MxChat – AI Chatbot & Content Generation for WordPress v2.4.9
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.4.9, at includes/class-mxchat-admin.php

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