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

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