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

9,145 lines 455.7 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 'show_frontend_debugger' => 'on',
112 'close_button_color' => esc_html__('#fff', 'mxchat'),
113 'chatbot_bg_color' => esc_html__('#fff', 'mxchat'),
114 'user_message_bg_color' => esc_html__('#fff', 'mxchat'),
115 'user_message_font_color' => esc_html__('#212121', 'mxchat'),
116 'bot_message_bg_color' => esc_html__('#212121', 'mxchat'),
117 'bot_message_font_color' => esc_html__('#fff', 'mxchat'),
118 'top_bar_bg_color' => esc_html__('#212121', 'mxchat'),
119 'send_button_font_color' => esc_html__('#212121', 'mxchat'),
120 'chat_input_font_color' => esc_html__('#212121', 'mxchat'),
121 'chatbot_background_color' => esc_html__('#212121', 'mxchat'),
122 'icon_color' => esc_html__('#fff', 'mxchat'),
123 'enable_woocommerce_integration' => esc_html__('0', 'mxchat'),
124 'link_target_toggle' => esc_html__('off', 'mxchat'),
125 'pre_chat_message' => esc_html__('Hey there! Ask me anything!', 'mxchat'),
126
127 // New fields for Loops Integration
128 'loops_api_key' => '',
129 'loops_mailing_list' => '',
130 'triggered_phrase_response' => __('Would you like to join our mailing list? Please provide your email below.', 'mxchat'),
131 'email_capture_response' => __('Thank you for providing your email! You\'ve been added to our list.', 'mxchat'),
132 'popular_question_1' => '',
133 'popular_question_2' => '',
134 'popular_question_3' => '',
135 'pdf_intent_trigger_text' => __("Please provide the URL to the PDF you'd like to discuss.", 'mxchat'),
136 'pdf_intent_success_text' => __("I've processed the PDF. What questions do you have about it?", 'mxchat'),
137 'pdf_intent_error_text' => __("Sorry, I couldn't process the PDF. Please ensure it's a valid file.", 'mxchat'),
138 'pdf_max_pages' => 69,
139 'show_pdf_upload_button' => 'on',
140 'show_word_upload_button' => 'on',
141
142 // Live Agent Integration
143 'live_agent_webhook_url' => '',
144 'live_agent_secret_key' => '',
145 'live_agent_bot_token' => '',
146 'live_agent_message_bg_color' => esc_html__('#ffffff', 'mxchat'),
147 'live_agent_message_font_color' => esc_html__('#333333', 'mxchat'),
148 'chat_toolbar_toggle' => esc_html__('off', 'mxchat'),
149 'mode_indicator_bg_color' => esc_html__('#767676', 'mxchat'),
150 'mode_indicator_font_color' => esc_html__('#ffffff', 'mxchat'),
151 'toolbar_icon_color' => esc_html__('#212121', 'mxchat'),
152 );
153
154
155 // Merge existing options with defaults
156 $existing_options = get_option('mxchat_options', array());
157 $merged_options = wp_parse_args($existing_options, $default_options);
158
159 // Update the options if they have changed
160 if ($existing_options !== $merged_options) {
161 update_option('mxchat_options', $merged_options);
162 }
163
164 // Add default limits for each role
165 $roles = wp_roles()->get_names();
166 foreach ($roles as $role_id => $role_name) {
167 $default_options['role_rate_limits'][$role_id] = esc_html__('100', 'mxchat');
168 }
169
170 return $default_options;
171
172 // Update the $this->options property
173 $this->options = $merged_options;
174 }
175
176 public function mxchat_add_plugin_page() {
177 // Main menu page
178 add_menu_page(
179 esc_html__('MxChat Settings', 'mxchat'),
180 esc_html__('MxChat', 'mxchat'),
181 'manage_options',
182 'mxchat-max',
183 array($this, 'mxchat_create_admin_page'),
184 'dashicons-testimonial',
185 6
186 );
187
188 // Submenu page for Knowledge
189 add_submenu_page(
190 'mxchat-max',
191 esc_html__('Prompts', 'mxchat'),
192 esc_html__('Knowledge', 'mxchat'),
193 'manage_options',
194 'mxchat-prompts',
195 array($this, 'mxchat_create_prompts_page')
196 );
197
198 add_submenu_page(
199 'mxchat-max',
200 esc_html__('Chat Transcripts', 'mxchat'),
201 esc_html__('Transcripts', 'mxchat'),
202 'manage_options',
203 'mxchat-transcripts',
204 array($this, 'mxchat_create_transcripts_page')
205 );
206
207 add_submenu_page(
208 'mxchat-max',
209 esc_html__('MxChat Actions', 'mxchat'),
210 esc_html__('Actions', 'mxchat'),
211 'manage_options',
212 'mxchat-actions',
213 array($this, 'mxchat_actions_page_html')
214 );
215
216 add_submenu_page(
217 'mxchat-max',
218 esc_html__('Add Ons', 'mxchat'),
219 esc_html__('Add Ons', 'mxchat'),
220 'manage_options',
221 'mxchat-addons',
222 array($this, 'mxchat_create_addons_page')
223 );
224
225 // Submenu page for Activation Key
226 add_submenu_page(
227 'mxchat-max',
228 esc_html__('Pro Upgrade', 'mxchat'),
229 esc_html__('Pro Upgrade', 'mxchat'),
230 'manage_options',
231 'mxchat-activation',
232 array($this, 'mxchat_create_activation_page')
233 );
234 }
235
236 public function mxchat_create_addons_page() {
237 require_once plugin_dir_path(__FILE__) . 'class-mxchat-addons.php';
238 $addons_page = new MxChat_Addons();
239 $addons_page->render_page();
240 }
241
242 /**
243 * Test actual streaming functionality in the WordPress environment
244 */
245 public function mxchat_handle_test_streaming_actual() {
246 check_ajax_referer('mxchat_test_streaming_nonce', 'nonce');
247
248 // Check if headers have already been sent
249 if (headers_sent()) {
250 wp_send_json_error(['message' => 'Headers already sent - streaming not possible']);
251 return;
252 }
253
254 // Check for required functions
255 if (!function_exists('curl_init')) {
256 wp_send_json_error(['message' => 'cURL not available - streaming requires cURL']);
257 return;
258 }
259
260 // Get user's selected model and API key
261 $options = get_option('mxchat_options', []);
262 $selected_model = $options['model'] ?? 'gpt-4o';
263
264 // Get the provider from the model
265 $model_parts = explode('-', $selected_model);
266 $provider = strtolower($model_parts[0]);
267
268 // Get the appropriate API key
269 $api_key = '';
270 switch ($provider) {
271 case 'gpt':
272 case 'o1':
273 $api_key = $options['api_key'] ?? '';
274 break;
275 case 'claude':
276 $api_key = $options['claude_api_key'] ?? '';
277 break;
278 case 'grok':
279 $api_key = $options['xai_api_key'] ?? '';
280 break;
281 case 'deepseek':
282 $api_key = $options['deepseek_api_key'] ?? '';
283 break;
284 case 'gemini':
285 $api_key = $options['gemini_api_key'] ?? '';
286 break;
287 default:
288 // Default to OpenAI for unknown models
289 $api_key = $options['api_key'] ?? '';
290 $provider = 'gpt';
291 break;
292 }
293
294 if (empty($api_key)) {
295 wp_send_json_error(['message' => "API key not configured for {$provider} provider"]);
296 return;
297 }
298
299 // Test streaming with the selected model and provider
300 try {
301 $this->perform_streaming_test($provider, $selected_model, $api_key);
302 } catch (Exception $e) {
303 wp_send_json_error(['message' => 'Streaming test exception: ' . $e->getMessage()]);
304 }
305 }
306
307 /**
308 * Perform the actual streaming test
309 */
310 private function perform_streaming_test($provider, $model, $api_key) {
311 // Set streaming headers
312 header('Content-Type: text/event-stream');
313 header('Cache-Control: no-cache');
314 header('X-Accel-Buffering: no'); // Disable nginx buffering
315
316 // Prepare test message
317 $test_message = "Please respond with exactly: 'Streaming test successful!' - send this as a short response for testing.";
318
319 // Configure API request based on provider
320 $url = '';
321 $headers = [];
322 $body = [];
323
324 switch ($provider) {
325 case 'gpt':
326 case 'o1':
327 $url = 'https://api.openai.com/v1/chat/completions';
328 $headers = [
329 'Content-Type: application/json',
330 'Authorization: Bearer ' . $api_key
331 ];
332 $body = [
333 'model' => $model,
334 'messages' => [['role' => 'user', 'content' => $test_message]],
335 'max_tokens' => 50,
336 'temperature' => 0.3,
337 'stream' => true
338 ];
339 break;
340
341 case 'claude':
342 $url = 'https://api.anthropic.com/v1/messages';
343 $headers = [
344 'Content-Type: application/json',
345 'x-api-key: ' . $api_key,
346 'anthropic-version: 2023-06-01'
347 ];
348 $body = [
349 'model' => $model,
350 'messages' => [['role' => 'user', 'content' => $test_message]],
351 'max_tokens' => 50,
352 'temperature' => 0.3,
353 'stream' => true
354 ];
355 break;
356
357 case 'grok':
358 $url = 'https://api.x.ai/v1/chat/completions';
359 $headers = [
360 'Content-Type: application/json',
361 'Authorization: Bearer ' . $api_key
362 ];
363 $body = [
364 'model' => $model,
365 'messages' => [['role' => 'user', 'content' => $test_message]],
366 'max_tokens' => 50,
367 'temperature' => 0.3,
368 'stream' => true
369 ];
370 break;
371
372 case 'deepseek':
373 $url = 'https://api.deepseek.com/v1/chat/completions';
374 $headers = [
375 'Content-Type: application/json',
376 'Authorization: Bearer ' . $api_key
377 ];
378 $body = [
379 'model' => $model,
380 'messages' => [['role' => 'user', 'content' => $test_message]],
381 'max_tokens' => 50,
382 'temperature' => 0.3,
383 'stream' => true
384 ];
385 break;
386
387 default:
388 echo "data: " . json_encode(['error' => 'Unsupported provider for streaming test: ' . $provider]) . "\n\n";
389 flush();
390 return;
391 }
392
393 // Initialize cURL
394 $ch = curl_init();
395 curl_setopt($ch, CURLOPT_URL, $url);
396 curl_setopt($ch, CURLOPT_RETURNTRANSFER, false);
397 curl_setopt($ch, CURLOPT_POST, true);
398 curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
399 curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
400 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
401 curl_setopt($ch, CURLOPT_TIMEOUT, 30);
402 curl_setopt($ch, CURLOPT_WRITEFUNCTION, function($ch, $data) use ($provider) {
403 return $this->process_streaming_test_data($data, $provider);
404 });
405
406 // Send initial test message
407 echo "data: " . json_encode(['content' => '[Starting streaming test...]']) . "\n\n";
408 flush();
409
410 $result = curl_exec($ch);
411 $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
412 $curl_error = curl_error($ch);
413 curl_close($ch);
414
415 if ($curl_error) {
416 echo "data: " . json_encode(['error' => 'cURL Error: ' . $curl_error]) . "\n\n";
417 flush();
418 return;
419 }
420
421 if ($http_code !== 200) {
422 echo "data: " . json_encode(['error' => 'API returned HTTP ' . $http_code]) . "\n\n";
423 flush();
424 return;
425 }
426
427 // Send completion signal
428 echo "data: [DONE]\n\n";
429 flush();
430 }
431
432 /**
433 * Process streaming data for the test
434 */
435 private function process_streaming_test_data($data, $provider) {
436 static $chunk_count = 0;
437
438 $lines = explode("\n", $data);
439
440 foreach ($lines as $line) {
441 if (trim($line) === '') {
442 continue;
443 }
444
445 // Handle different provider formats
446 if ($provider === 'claude') {
447 // Claude uses event: and data: format
448 if (strpos($line, 'data: ') === 0) {
449 $json_str = substr($line, 6);
450 $json = json_decode($json_str, true);
451
452 if (isset($json['type']) && $json['type'] === 'content_block_delta') {
453 if (isset($json['delta']['text'])) {
454 $chunk_count++;
455 echo "data: " . json_encode([
456 'content' => $json['delta']['text'],
457 'test_chunk' => $chunk_count
458 ]) . "\n\n";
459 flush();
460 }
461 }
462 }
463 } else {
464 // OpenAI, X.AI, DeepSeek format
465 if (strpos($line, 'data: ') === 0) {
466 $json_str = substr($line, 6);
467
468 if ($json_str === '[DONE]') {
469 // Don't echo [DONE] here, let the main function handle it
470 continue;
471 }
472
473 $json = json_decode($json_str, true);
474 if (isset($json['choices'][0]['delta']['content'])) {
475 $chunk_count++;
476 echo "data: " . json_encode([
477 'content' => $json['choices'][0]['delta']['content'],
478 'test_chunk' => $chunk_count
479 ]) . "\n\n";
480 flush();
481 }
482 }
483 }
484 }
485
486 return strlen($data);
487 }
488
489 /**
490 * Updated version of your existing test method (keep this as a fallback)
491 */
492 public function mxchat_handle_test_streaming() {
493 check_ajax_referer('mxchat_test_streaming_nonce', 'nonce');
494
495 // Use the actual streaming test instead
496 $this->mxchat_handle_test_streaming_actual();
497 }
498
499
500 public function register_pinecone_settings() {
501 register_setting(
502 'mxchat_pinecone_addon_options',
503 'mxchat_pinecone_addon_options',
504 array(
505 'type' => 'array',
506 'sanitize_callback' => array($this, 'sanitize_pinecone_settings'),
507 'default' => array(
508 'mxchat_use_pinecone' => '0',
509 'mxchat_pinecone_api_key' => '',
510 'mxchat_pinecone_host' => '',
511 'mxchat_pinecone_index' => '',
512 'mxchat_pinecone_environment' => ''
513 )
514 )
515 );
516 }
517
518 public function sanitize_pinecone_settings($input) {
519 $sanitized = array();
520
521 $sanitized['mxchat_use_pinecone'] = isset($input['mxchat_use_pinecone']) ? '1' : '0';
522 $sanitized['mxchat_pinecone_api_key'] = sanitize_text_field($input['mxchat_pinecone_api_key'] ?? '');
523 $sanitized['mxchat_pinecone_host'] = sanitize_text_field($input['mxchat_pinecone_host'] ?? '');
524 $sanitized['mxchat_pinecone_index'] = sanitize_text_field($input['mxchat_pinecone_index'] ?? '');
525 $sanitized['mxchat_pinecone_environment'] = sanitize_text_field($input['mxchat_pinecone_environment'] ?? '');
526
527 // Remove https:// from host if present
528 $sanitized['mxchat_pinecone_host'] = str_replace(['https://', 'http://'], '', $sanitized['mxchat_pinecone_host']);
529
530 return $sanitized;
531 }
532
533 public function mxchat_display_admin_notice() {
534 // Success notice
535 if ($message = get_transient('mxchat_admin_notice_success')) {
536 ?>
537 <div class="notice notice-success is-dismissible">
538 <p><?php echo esc_html($message); ?></p>
539 <button type="button" class="notice-dismiss"><span class="screen-reader-text"><?php echo esc_html__('Dismiss this notice.', 'mxchat'); ?></span></button>
540 </div>
541 <?php
542 delete_transient('mxchat_admin_notice_success'); // Clear the transient after displaying
543 }
544
545 // Error notice
546 if ($message = get_transient('mxchat_admin_notice_error')) {
547 ?>
548 <div class="notice notice-error is-dismissible">
549 <p><?php echo esc_html($message); ?></p>
550 <button type="button" class="notice-dismiss"><span class="screen-reader-text"><?php echo esc_html__('Dismiss this notice.', 'mxchat'); ?></span></button>
551 </div>
552 <?php
553 delete_transient('mxchat_admin_notice_error'); // Clear the transient after displaying
554 }
555 }
556
557 public function show_live_agent_disabled_banner() {
558 $show_disabled_notice = get_option('mxchat_show_live_agent_disabled_notice', false);
559
560 if ($show_disabled_notice) {
561 ?>
562 <div class="mxchat-live-agent-disabled-notice" id="mxchat-disabled-notice">
563 <div class="mxchat-pro-notification">
564 <button type="button" class="mxchat-dismiss-btn" onclick="dismissLiveAgentNotice()" aria-label="Dismiss notification">
565 <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
566 <line x1="18" y1="6" x2="6" y2="18"></line>
567 <line x1="6" y1="6" x2="18" y2="18"></line>
568 </svg>
569 </button>
570 <div class="mxchat-live-agent-content">
571 <h3>🔧 Live Agent Integration Updated!</h3>
572 <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>
573 </div>
574 </div>
575 </div>
576 <?php
577 }
578 }
579 public function mxchat_create_admin_page() {
580 $this->add_live_agent_nonce();
581
582 ?>
583 <div class="wrap mxchat-wrapper">
584 <!-- Hero Section -->
585 <div class="mxchat-hero">
586 <h1 class="mxchat-main-title">
587 <span class="mxchat-gradient-text">MxChat</span> Settings
588 </h1>
589 <p class="mxchat-hero-subtitle">
590 <?php esc_html_e('Configure your AI chatbot, manage integrations and explore tutorials to get the most out of MxChat.', 'mxchat'); ?>
591 </p>
592 </div>
593
594 <div class="mxchat-content">
595
596 <?php if (!$this->is_activated): ?>
597 <div class="mxchat-pro-card">
598 <div class="mxchat-pro-notification">
599 <div class="mxchat-pro-content">
600 <h3>🚀 Limited Lifetime Offer: Save 15% on MxChat Pro, Agency, or Agency Plus!</h3>
601 <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>
602 <div class="mxchat-pro-cta">
603 <a href="https://mxchat.ai/" target="_blank" class="mxchat-button"><?php echo esc_html__('Upgrade Today', 'mxchat'); ?></a>
604 <a href="<?php echo admin_url('admin.php?page=mxchat-addons'); ?>" class="mxchat-link"><?php echo esc_html__('Preview Add-ons', 'mxchat'); ?></a>
605 </div>
606 </div>
607 </div>
608 <?php endif; ?>
609
610 <?php
611 $this->show_live_agent_disabled_banner();
612 ?>
613
614 <!-- Tabs Navigation -->
615 <div class="mxchat-tabs">
616 <button class="mxchat-tab-button active" data-tab="chatbot"><?php echo esc_html__('Chatbot', 'mxchat'); ?></button>
617 <button class="mxchat-tab-button" data-tab="api-keys"><?php echo esc_html__('API Keys', 'mxchat'); ?></button>
618 <button class="mxchat-tab-button" data-tab="embed"><?php echo esc_html__('Toolbar & Components', 'mxchat'); ?></button>
619 <button class="mxchat-tab-button" data-tab="general"><?php echo esc_html__('YouTube Tutorials', 'mxchat'); ?></button>
620 </div>
621
622 <!-- Tab Contents -->
623 <div id="chatbot" class="mxchat-tab-content active">
624 <div class="mxchat-card">
625 <div class="mxchat-autosave-section">
626 <?php do_settings_sections('mxchat-chatbot'); ?>
627 </div>
628 </div>
629 </div>
630
631 <div id="api-keys" class="mxchat-tab-content">
632 <div class="mxchat-card">
633 <div class="mxchat-autosave-section">
634 <table class="form-table">
635 <?php do_settings_fields('mxchat-api-keys', 'mxchat_api_keys_section'); ?>
636 </table>
637 </div>
638 </div>
639 </div>
640
641 <div id="embed" class="mxchat-tab-content">
642
643 <div class="mxchat-card">
644 <h2><?php esc_html_e('Toolbar Settings', 'mxchat'); ?></h2>
645 <div class="mxchat-autosave-section">
646 <table class="form-table">
647 <?php do_settings_fields('mxchat-embed', 'mxchat_pdf_intent_section'); ?>
648 </table>
649 </div>
650 </div>
651
652
653 <div class="mxchat-card">
654 <h2><?php esc_html_e('Loops Settings', 'mxchat'); ?></h2>
655 <div class="mxchat-autosave-section">
656 <table class="form-table">
657 <?php do_settings_fields('mxchat-embed', 'mxchat_loops_section'); ?>
658 </table>
659 </div>
660 </div>
661
662 <div class="mxchat-card">
663 <h2><?php esc_html_e('Brave Search Settings', 'mxchat'); ?></h2>
664 <div class="mxchat-autosave-section">
665 <table class="form-table">
666 <?php do_settings_fields('mxchat-embed', 'mxchat_brave_section'); ?>
667 </table>
668 </div>
669 </div>
670
671 <div class="mxchat-card">
672 <h2><?php esc_html_e('Live Agent Settings', 'mxchat'); ?></h2>
673 <div class="mxchat-autosave-section">
674 <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">
675 <?php do_settings_fields('mxchat-embed', 'mxchat_live_agent_section'); ?>
676 </table>
677 </div>
678 </div>
679 </div>
680
681 <div id="general" class="mxchat-tab-content">
682 <div class="mxchat-card">
683 <?php do_settings_sections('mxchat-general'); ?>
684 <div class="video-tutorials-section">
685
686
687 <div class="support-notification">
688 <div class="support-notification-icon">
689 <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">
690 <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"/>
691 <path d="M12 9v4"/>
692 <path d="M12 17h.01"/>
693 </svg>
694 </div>
695 <div class="support-notification-content">
696 <h3 class="support-notification-title"><?php echo esc_html__('Need Help? We\'re Here for You!', 'mxchat'); ?></h3>
697 <p class="support-notification-message">
698 <?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'); ?>
699 </p>
700 <a href="https://wordpress.org/support/plugin/mxchat-basic/" target="_blank" rel="noopener noreferrer" class="support-notification-button">
701 <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">
702 <path d="M14 9a2 2 0 0 1-2 2H6l-4 4V4c0-1.1.9-2 2-2h8a2 2 0 0 1 2 2v5Z"/>
703 <path d="M18 9h2a2 2 0 0 1 2 2v11l-4-4h-6a2 2 0 0 1-2-2v-1"/>
704 </svg>
705 <?php echo esc_html__('Submit Support Ticket', 'mxchat'); ?>
706 </a>
707 </div>
708 </div>
709
710 <div class="tutorial-grid">
711
712 <div class="tutorial-item">
713 <h3><?php echo esc_html__('AI Theme Generator Tutorial', 'mxchat'); ?></h3>
714 <div class="video-description">
715 <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>
716 <a href="https://www.youtube.com/watch?v=rSQDW2qbtRU&t" target="_blank" rel="noopener" class="video-link">
717 <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>
718 <?php echo esc_html__('Watch AI Theme Generator Tutorial', 'mxchat'); ?>
719 </a>
720 </div>
721 </div>
722
723
724 <div class="tutorial-item">
725 <h3><?php echo esc_html__('MxChat Forms Tutorial', 'mxchat'); ?></h3>
726 <div class="video-description">
727 <p><?php echo esc_html__('Learn how to create and manage smart forms that automatically trigger during chat conversations.', 'mxchat'); ?></p>
728 <a href="https://www.youtube.com/watch?v=3MrWy5dRalA" target="_blank" rel="noopener" class="video-link">
729 <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>
730 <?php echo esc_html__('Watch MxChat Forms Tutorial', 'mxchat'); ?>
731 </a>
732 </div>
733 </div>
734
735 <div class="tutorial-item">
736 <h3><?php echo esc_html__('Admin Assistant Add-on', 'mxchat'); ?></h3>
737 <div class="video-description">
738 <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>
739 <a href="https://youtu.be/AdEA1k-UCFM" target="_blank" rel="noopener" class="video-link">
740 <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>
741 <?php echo esc_html__('Watch Admin Assistant Tutorial', 'mxchat'); ?>
742 </a>
743 </div>
744 </div>
745
746 <div class="tutorial-item">
747 <h3><?php echo esc_html__('Intent Tester Guide', 'mxchat'); ?></h3>
748 <div class="video-description">
749 <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>
750 <a href="https://www.youtube.com/watch?v=uTr14tn59Hc" target="_blank" rel="noopener" class="video-link">
751 <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>
752 <?php echo esc_html__('Watch Intent Tester Tutorial', 'mxchat'); ?>
753 </a>
754 </div>
755 </div>
756
757 <div class="tutorial-item">
758 <h3><?php echo esc_html__('Theme Customizer Add-on', 'mxchat'); ?></h3>
759 <div class="video-description">
760 <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>
761 <a href="https://youtu.be/MfbB9mZi6ag" target="_blank" rel="noopener" class="video-link">
762 <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>
763 <?php echo esc_html__('Watch Theme Customizer Tutorial', 'mxchat'); ?>
764 </a>
765 </div>
766 </div>
767
768 <div class="tutorial-item">
769 <h3><?php echo esc_html__('WooCommerce Integration', 'mxchat'); ?></h3>
770 <div class="video-description">
771 <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>
772 <a href="https://www.youtube.com/watch?v=WsqAppHRGdA" target="_blank" rel="noopener" class="video-link">
773 <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>
774 <?php echo esc_html__('Watch WooCommerce Integration Tutorial', 'mxchat'); ?>
775 </a>
776 </div>
777 </div>
778
779 <div class="tutorial-item">
780 <h3><?php echo esc_html__('Knowledge Base Setup', 'mxchat'); ?></h3>
781 <div class="video-description">
782 <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>
783 <p><small><?php echo esc_html__('Note: This tutorial uses an older UI, but the process remains the same.', 'mxchat'); ?></small></p>
784 <a href="https://www.youtube.com/watch?v=8Ztjs66-VTo" target="_blank" rel="noopener" class="video-link">
785 <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>
786 <?php echo esc_html__('Watch Knowledge Base Setup Tutorial', 'mxchat'); ?>
787 </a>
788 </div>
789 </div>
790
791 <div class="tutorial-item">
792 <h3><?php echo esc_html__('Toolbar Chat with Documents', 'mxchat'); ?></h3>
793 <div class="video-description">
794 <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>
795 <a href="https://www.youtube.com/watch?v=j_c45WWCTG0" target="_blank" rel="noopener" class="video-link">
796 <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>
797 <?php echo esc_html__('Watch Document Chat Tutorial', 'mxchat'); ?>
798 </a>
799 </div>
800 </div>
801
802 <div class="tutorial-item">
803 <h3><?php echo esc_html__('Perplexity Integration', 'mxchat'); ?></h3>
804 <div class="video-description">
805 <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>
806 <a href="https://youtu.be/wpKkbt24-bo" target="_blank" rel="noopener" class="video-link">
807 <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>
808 <?php echo esc_html__('Watch Perplexity Integration Tutorial', 'mxchat'); ?>
809 </a>
810 </div>
811 </div>
812
813 <div class="tutorial-item">
814 <h3><?php echo esc_html__('Brave Search Intent', 'mxchat'); ?></h3>
815 <div class="video-description">
816 <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>
817 <a href="https://www.youtube.com/watch?v=7vDL5H7vToc" target="_blank" rel="noopener" class="video-link">
818 <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>
819 <?php echo esc_html__('Watch Brave Search Intent Tutorial', 'mxchat'); ?>
820 </a>
821 </div>
822 </div>
823
824 <div class="tutorial-item">
825 <h3><?php echo esc_html__('Loops Email Capture', 'mxchat'); ?></h3>
826 <div class="video-description">
827 <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>
828 <p><small><?php echo esc_html__('Note: This tutorial uses an older UI, but the process remains the same.', 'mxchat'); ?></small></p>
829 <a href="https://www.youtube.com/watch?v=CNgm5TYDyTc" target="_blank" rel="noopener" class="video-link">
830 <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>
831 <?php echo esc_html__('Watch Loops Email Capture Tutorial', 'mxchat'); ?>
832 </a>
833 </div>
834 </div>
835
836 <div class="tutorial-item">
837 <h3><?php echo esc_html__('MxChat AI Agent Testing Service', 'mxchat'); ?></h3>
838 <div class="video-description">
839 <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>
840 <p><small><?php echo esc_html__('Note: This tutorial uses an older UI, but the process remains the same.', 'mxchat'); ?></small></p>
841 <a href="https://www.youtube.com/watch?v=A0jowbpyX54" target="_blank" rel="noopener" class="video-link">
842 <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>
843 <?php echo esc_html__('Watch AI Agent Testing Tutorial', 'mxchat'); ?>
844 </a>
845 </div>
846 </div>
847 </div>
848
849 </div>
850 </div>
851 </div>
852 </div>
853 </div>
854 <?php
855 }
856
857 public function dismiss_live_agent_notice() {
858 // Add debugging
859 //error_log('dismiss_live_agent_notice called');
860 //error_log('POST data: ' . print_r($_POST, true));
861
862 // Verify nonce
863 if (!wp_verify_nonce($_POST['nonce'], 'dismiss_live_agent_notice')) {
864 //error_log('Nonce verification failed');
865 wp_die('Security check failed');
866 }
867
868 // Remove the notice flag
869 $deleted = delete_option('mxchat_show_live_agent_disabled_notice');
870 //error_log('Option deleted: ' . ($deleted ? 'yes' : 'no'));
871
872 wp_send_json_success();
873 }
874 public function add_live_agent_nonce() {
875 if (get_option('mxchat_show_live_agent_disabled_notice', false)) {
876 // Make sure your admin script is enqueued and localize the data
877 wp_localize_script('mxchat-admin-js', 'mxchatLiveAgent', array(
878 'nonce' => wp_create_nonce('dismiss_live_agent_notice'),
879 'ajaxurl' => admin_url('admin-ajax.php')
880 ));
881 }
882 }
883
884
885 public function mxchat_create_transcripts_page() {
886 global $wpdb;
887 $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
888
889 // Get basic stats
890 $total_chats = $wpdb->get_var("SELECT COUNT(DISTINCT session_id) FROM $table_name");
891 $total_messages = $wpdb->get_var("SELECT COUNT(*) FROM $table_name");
892
893 // Count unique users with detailed breakdown
894 $total_users = $wpdb->get_var("
895 SELECT COUNT(DISTINCT
896 CASE
897 WHEN user_email != '' AND user_email IS NOT NULL THEN user_email
898 WHEN user_id != 0 THEN CONCAT('user_', user_id)
899 WHEN user_identifier NOT LIKE 'Tech-Savvy User'
900 AND user_identifier NOT LIKE 'Detail-Oriented User'
901 AND user_identifier NOT LIKE 'Language Learner'
902 AND user_identifier NOT LIKE 'Casual Browser'
903 AND user_identifier NOT LIKE 'Policy Enforcer'
904 AND user_identifier NOT LIKE 'Researcher'
905 AND user_identifier NOT LIKE 'Loyalty Member'
906 AND user_identifier NOT LIKE 'Gift Buyer'
907 AND user_identifier NOT LIKE 'Parent or Caregiver'
908 THEN user_identifier
909 ELSE session_id
910 END
911 )
912 FROM $table_name
913 WHERE role != 'assistant'
914 ");
915
916 // Get user type breakdown
917 $registered_users = $wpdb->get_var("
918 SELECT COUNT(DISTINCT user_email)
919 FROM $table_name
920 WHERE user_email != '' AND user_email IS NOT NULL
921 ");
922
923 $guest_users = $wpdb->get_var("
924 SELECT COUNT(DISTINCT user_identifier)
925 FROM $table_name
926 WHERE (user_email = '' OR user_email IS NULL)
927 AND role != 'assistant'
928 AND user_identifier NOT LIKE 'Tech-Savvy User'
929 AND user_identifier NOT LIKE 'Detail-Oriented User'
930 AND user_identifier NOT LIKE 'Language Learner'
931 AND user_identifier NOT LIKE 'Casual Browser'
932 AND user_identifier NOT LIKE 'Policy Enforcer'
933 AND user_identifier NOT LIKE 'Researcher'
934 AND user_identifier NOT LIKE 'Loyalty Member'
935 AND user_identifier NOT LIKE 'Gift Buyer'
936 AND user_identifier NOT LIKE 'Parent or Caregiver'
937 ");
938
939 // Get agent test messages count
940 $agent_tests = $wpdb->get_var("
941 SELECT COUNT(DISTINCT session_id)
942 FROM $table_name
943 WHERE user_identifier IN (
944 'Tech-Savvy User',
945 'Detail-Oriented User',
946 'Language Learner',
947 'Casual Browser',
948 'Policy Enforcer',
949 'Researcher',
950 'Loyalty Member',
951 'Gift Buyer',
952 'Parent or Caregiver'
953 )
954 ");
955 // Get activity metrics
956 $today_chats = $wpdb->get_var("
957 SELECT COUNT(DISTINCT session_id)
958 FROM $table_name
959 WHERE DATE(timestamp) = CURDATE()
960 ");
961
962 $week_chats = $wpdb->get_var("
963 SELECT COUNT(DISTINCT session_id)
964 FROM $table_name
965 WHERE timestamp >= DATE_SUB(NOW(), INTERVAL 7 DAY)
966 ");
967
968 $month_chats = $wpdb->get_var("
969 SELECT COUNT(DISTINCT session_id)
970 FROM $table_name
971 WHERE timestamp >= DATE_SUB(NOW(), INTERVAL 30 DAY)
972 ");
973
974 // Get daily chat data for last 7 days
975 $daily_stats = $wpdb->get_results("
976 SELECT
977 DATE(timestamp) as date,
978 COUNT(DISTINCT session_id) as chats,
979 COUNT(*) as messages
980 FROM $table_name
981 WHERE timestamp >= DATE_SUB(NOW(), INTERVAL 7 DAY)
982 GROUP BY DATE(timestamp)
983 ORDER BY date ASC
984 ");
985
986 // Get average messages per chat
987 $avg_messages = $wpdb->get_var("
988 SELECT AVG(message_count)
989 FROM (
990 SELECT session_id, COUNT(*) as message_count
991 FROM $table_name
992 GROUP BY session_id
993 ) as chat_counts
994 ");
995 $avg_messages = $avg_messages ? round($avg_messages, 1) : 0;
996
997 // Get busiest hour
998 $busiest_hour = $wpdb->get_row("
999 SELECT HOUR(timestamp) as hour, COUNT(DISTINCT session_id) as chat_count
1000 FROM $table_name
1001 GROUP BY HOUR(timestamp)
1002 ORDER BY chat_count DESC
1003 LIMIT 1
1004 ");
1005
1006 // Prepare chart data
1007 $chart_labels = array();
1008 $chart_chats = array();
1009 $chart_messages = array();
1010
1011 // Fill last 7 days with data
1012 for ($i = 6; $i >= 0; $i--) {
1013 $date = date('Y-m-d', strtotime("-$i days"));
1014 $day_name = date('D', strtotime("-$i days"));
1015 $chart_labels[] = $day_name;
1016
1017 $found = false;
1018 foreach ($daily_stats as $stat) {
1019 if ($stat->date === $date) {
1020 $chart_chats[] = (int)$stat->chats;
1021 $chart_messages[] = (int)$stat->messages;
1022 $found = true;
1023 break;
1024 }
1025 }
1026 if (!$found) {
1027 $chart_chats[] = 0;
1028 $chart_messages[] = 0;
1029 }
1030 }
1031 ?>
1032 <div class="wrap mxchat-transcripts-wrapper">
1033 <!-- Hero Section -->
1034 <div class="mxchat-transcripts-hero">
1035 <h1 class="mxchat-main-title">
1036 Chat <span class="mxchat-gradient-text">Transcripts</span>
1037 </h1>
1038 <p class="mxchat-hero-subtitle">
1039 <?php esc_html_e('Review and manage your chatbot conversations with detailed message history.', 'mxchat'); ?>
1040 </p>
1041 </div>
1042 <div class="mxchat-content">
1043 <!-- Enhanced Metrics Dashboard -->
1044 <div class="mxchat-metrics-dashboard">
1045 <!-- Tab Navigation -->
1046 <div class="mxchat-metrics-tabs">
1047 <button class="mxchat-metrics-tab active" data-tab="overview">
1048 <span class="tab-icon">📊</span>
1049 <span class="tab-label">Overview</span>
1050 </button>
1051 <button class="mxchat-metrics-tab" data-tab="activity">
1052 <span class="tab-icon">📈</span>
1053 <span class="tab-label">Activity</span>
1054 </button>
1055 <button class="mxchat-metrics-tab" data-tab="insights">
1056 <span class="tab-icon">💡</span>
1057 <span class="tab-label">Insights</span>
1058 </button>
1059 </div>
1060
1061 <!-- Tab Content -->
1062 <div class="mxchat-metrics-content">
1063 <!-- Overview Tab -->
1064 <div class="mxchat-metrics-panel active" data-panel="overview">
1065 <div class="mxchat-stats-grid">
1066 <div class="mxchat-stat-card mxchat-stat-primary">
1067 <div class="stat-icon">💬</div>
1068 <div class="stat-content">
1069 <span class="stat-value"><?php echo esc_html($total_chats); ?></span>
1070 <span class="stat-label"><?php esc_html_e('Total Chats', 'mxchat'); ?></span>
1071 </div>
1072 </div>
1073 <div class="mxchat-stat-card mxchat-stat-primary">
1074 <div class="stat-icon">📝</div>
1075 <div class="stat-content">
1076 <span class="stat-value"><?php echo esc_html($total_messages); ?></span>
1077 <span class="stat-label"><?php esc_html_e('Total Messages', 'mxchat'); ?></span>
1078 </div>
1079 </div>
1080 <div class="mxchat-stat-card mxchat-stat-primary">
1081 <div class="stat-icon">👥</div>
1082 <div class="stat-content">
1083 <span class="stat-value"><?php echo esc_html($total_users); ?></span>
1084 <span class="stat-label"><?php esc_html_e('Unique Users', 'mxchat'); ?></span>
1085 <span class="stat-sublabel">
1086 <?php
1087 echo sprintf(
1088 esc_html__('%d registered • %d guests • %d tests', 'mxchat'),
1089 $registered_users,
1090 $guest_users,
1091 $agent_tests
1092 );
1093 ?>
1094 </span>
1095 </div>
1096 </div>
1097 <div class="mxchat-stat-card mxchat-stat-primary">
1098 <div class="stat-icon">💭</div>
1099 <div class="stat-content">
1100 <span class="stat-value"><?php echo esc_html($avg_messages); ?></span>
1101 <span class="stat-label"><?php esc_html_e('Avg Messages/Chat', 'mxchat'); ?></span>
1102 </div>
1103 </div>
1104 </div>
1105 </div>
1106
1107 <!-- Activity Tab -->
1108 <div class="mxchat-metrics-panel" data-panel="activity">
1109 <div class="mxchat-activity-layout">
1110 <div class="mxchat-activity-stats">
1111 <div class="mxchat-stat-card mxchat-stat-accent">
1112 <div class="stat-icon">📅</div>
1113 <div class="stat-content">
1114 <span class="stat-value"><?php echo esc_html($today_chats); ?></span>
1115 <span class="stat-label"><?php esc_html_e('Today', 'mxchat'); ?></span>
1116 </div>
1117 </div>
1118 <div class="mxchat-stat-card mxchat-stat-accent">
1119 <div class="stat-icon">📆</div>
1120 <div class="stat-content">
1121 <span class="stat-value"><?php echo esc_html($week_chats); ?></span>
1122 <span class="stat-label"><?php esc_html_e('Last 7 Days', 'mxchat'); ?></span>
1123 </div>
1124 </div>
1125 <div class="mxchat-stat-card mxchat-stat-accent">
1126 <div class="stat-icon">🗓️</div>
1127 <div class="stat-content">
1128 <span class="stat-value"><?php echo esc_html($month_chats); ?></span>
1129 <span class="stat-label"><?php esc_html_e('Last 30 Days', 'mxchat'); ?></span>
1130 </div>
1131 </div>
1132 </div>
1133
1134 <div class="mxchat-chart-container">
1135 <h3 class="mxchat-chart-title"><?php esc_html_e('7-Day Activity Trend', 'mxchat'); ?></h3>
1136 <canvas id="mxchat-activity-chart"></canvas>
1137 </div>
1138 </div>
1139 </div>
1140
1141 <!-- Insights Tab -->
1142 <div class="mxchat-metrics-panel" data-panel="insights">
1143 <div class="mxchat-insights-grid">
1144 <div class="mxchat-insight-card">
1145 <div class="insight-header">
1146 <span class="insight-icon">🕐</span>
1147 <h3><?php esc_html_e('Peak Activity', 'mxchat'); ?></h3>
1148 </div>
1149 <div class="insight-content">
1150 <div class="insight-value">
1151 <?php
1152 if ($busiest_hour) {
1153 $hour = $busiest_hour->hour;
1154 $formatted_hour = date('g A', strtotime("$hour:00"));
1155 echo esc_html($formatted_hour);
1156 } else {
1157 echo esc_html__('N/A', 'mxchat');
1158 }
1159 ?>
1160 </div>
1161 <div class="insight-description">
1162 <?php esc_html_e('Most active hour of the day', 'mxchat'); ?>
1163 </div>
1164 </div>
1165 </div>
1166
1167 <div class="mxchat-insight-card">
1168 <div class="insight-header">
1169 <span class="insight-icon">📊</span>
1170 <h3><?php esc_html_e('Engagement Rate', 'mxchat'); ?></h3>
1171 </div>
1172 <div class="insight-content">
1173 <div class="insight-value">
1174 <?php
1175 $engagement_rate = $total_chats > 0 ? round(($total_messages / $total_chats), 1) : 0;
1176 echo esc_html($engagement_rate);
1177 ?>
1178 </div>
1179 <div class="insight-description">
1180 <?php esc_html_e('Messages exchanged per chat', 'mxchat'); ?>
1181 </div>
1182 </div>
1183 </div>
1184
1185 <div class="mxchat-insight-card">
1186 <div class="insight-header">
1187 <span class="insight-icon">👤</span>
1188 <h3><?php esc_html_e('User Distribution', 'mxchat'); ?></h3>
1189 </div>
1190 <div class="insight-content">
1191 <div class="insight-breakdown">
1192 <div class="breakdown-item">
1193 <span class="breakdown-label"><?php esc_html_e('Registered', 'mxchat'); ?></span>
1194 <span class="breakdown-value">
1195 <?php
1196 $registered_pct = $total_users > 0 ? round(($registered_users / $total_users) * 100) : 0;
1197 echo esc_html($registered_pct . '%');
1198 ?>
1199 </span>
1200 </div>
1201 <div class="breakdown-item">
1202 <span class="breakdown-label"><?php esc_html_e('Guests', 'mxchat'); ?></span>
1203 <span class="breakdown-value">
1204 <?php
1205 $guest_pct = $total_users > 0 ? round(($guest_users / $total_users) * 100) : 0;
1206 echo esc_html($guest_pct . '%');
1207 ?>
1208 </span>
1209 </div>
1210 </div>
1211 </div>
1212 </div>
1213
1214 <div class="mxchat-insight-card">
1215 <div class="insight-header">
1216 <span class="insight-icon">⚡</span>
1217 <h3><?php esc_html_e('Activity Status', 'mxchat'); ?></h3>
1218 </div>
1219 <div class="insight-content">
1220 <div class="insight-value insight-status">
1221 <?php
1222 if ($today_chats > 0) {
1223 echo '<span class="status-badge status-active">' . esc_html__('Active', 'mxchat') . '</span>';
1224 } else if ($week_chats > 0) {
1225 echo '<span class="status-badge status-moderate">' . esc_html__('Moderate', 'mxchat') . '</span>';
1226 } else {
1227 echo '<span class="status-badge status-quiet">' . esc_html__('Quiet', 'mxchat') . '</span>';
1228 }
1229 ?>
1230 </div>
1231 <div class="insight-description">
1232 <?php
1233 if ($today_chats > 0) {
1234 echo esc_html(sprintf(__('%d chats today', 'mxchat'), $today_chats));
1235 } else if ($week_chats > 0) {
1236 echo esc_html(sprintf(__('%d chats this week', 'mxchat'), $week_chats));
1237 } else {
1238 echo esc_html__('No recent activity', 'mxchat');
1239 }
1240 ?>
1241 </div>
1242 </div>
1243 </div>
1244 </div>
1245 </div>
1246 </div>
1247 </div>
1248
1249 <script>
1250 var mxchatChartData = {
1251 labels: <?php echo json_encode($chart_labels); ?>,
1252 chats: <?php echo json_encode($chart_chats); ?>,
1253 messages: <?php echo json_encode($chart_messages); ?>
1254 };
1255 </script>
1256
1257 <!-- Search and Filter Controls with Notification Settings Button -->
1258 <div class="mxchat-controls-wrapper">
1259 <div class="mxchat-search-box">
1260 <input type="text" id="mxchat-search-transcripts"
1261 placeholder="<?php esc_attr_e('Search transcripts...', 'mxchat'); ?>"
1262 class="regular-text">
1263 </div>
1264 <form id="mxchat-delete-form" method="post">
1265 <?php wp_nonce_field('mxchat_delete_chat_history', 'mxchat_delete_chat_nonce'); ?>
1266 <div class="mxchat-controls">
1267 <button type="button" id="mxchat-chat-email-notification-btn" class="mxchat-action-button">
1268 <span class="dashicons dashicons-admin-generic"></span>
1269 <?php esc_html_e('Settings', 'mxchat'); ?>
1270 </button>
1271 <button type="button" id="mxchat-export-transcripts" class="mxchat-action-button">
1272 <span class="dashicons dashicons-download"></span>
1273 <?php esc_html_e('Export All Chats', 'mxchat'); ?>
1274 </button>
1275 <button type="button" id="mxchat-select-all-transcripts" class="mxchat-select-button">
1276 <span class="dashicons dashicons-yes-alt"></span>
1277 <span class="button-text"><?php esc_html_e('Select All', 'mxchat'); ?></span>
1278 </button>
1279 <button type="submit" class="button delete-chats-button">
1280 <span class="dashicons dashicons-trash"></span>
1281 <?php esc_html_e('Delete Selected', 'mxchat'); ?>
1282 </button>
1283 </div>
1284 </form>
1285 </div>
1286
1287 <!-- Transcripts Container -->
1288 <div id="mxchat-transcripts"></div>
1289 </div>
1290 </div>
1291
1292 <!-- Modal for Chat Email Notification Settings -->
1293 <div id="mxchat-chat-email-notification-modal" class="mxchat-chat-notification-modal-overlay" style="display: none;">
1294 <div class="mxchat-chat-notification-modal-content">
1295 <div class="mxchat-chat-notification-modal-header">
1296 <h2><?php esc_html_e('Transcript Settings', 'mxchat'); ?></h2>
1297 <button type="button" class="mxchat-chat-notification-modal-close">&times;</button>
1298 </div>
1299 <div class="mxchat-chat-notification-modal-body">
1300 <form method="post" action="options.php" id="mxchat-chat-email-notification-form">
1301 <?php
1302 settings_fields('mxchat_transcripts_options');
1303 do_settings_sections('mxchat-transcripts');
1304 ?>
1305 <div class="mxchat-chat-notification-modal-footer">
1306 <button type="submit" class="button button-primary">
1307 <?php esc_html_e('Save Settings', 'mxchat'); ?>
1308 </button>
1309 <button type="button" class="button mxchat-chat-notification-modal-cancel">
1310 <?php esc_html_e('Cancel', 'mxchat'); ?>
1311 </button>
1312 </div>
1313 </form>
1314 </div>
1315 </div>
1316 </div>
1317
1318 <!-- Modal for RAG Context Display -->
1319 <div id="mxchat-rag-context-modal" class="mxchat-rag-modal-overlay" style="display: none;">
1320 <div class="mxchat-rag-modal-content">
1321 <div class="mxchat-rag-modal-header">
1322 <h2><?php esc_html_e('Retrieved Documents', 'mxchat'); ?></h2>
1323 <button type="button" class="mxchat-rag-modal-close">&times;</button>
1324 </div>
1325 <div class="mxchat-rag-modal-body">
1326 <div class="mxchat-rag-loading" style="display: none;">
1327 <span class="spinner is-active"></span>
1328 <?php esc_html_e('Loading document context...', 'mxchat'); ?>
1329 </div>
1330 <div class="mxchat-rag-content">
1331 <!-- RAG context will be populated via JavaScript -->
1332 </div>
1333 </div>
1334 </div>
1335 </div>
1336 <?php
1337 }
1338
1339
1340 public function mxchat_transcripts_notification_section_callback() {
1341 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>';
1342 }
1343 public function mxchat_enable_notifications_callback() {
1344 $options = get_option('mxchat_transcripts_options', array());
1345 $enabled = isset($options['mxchat_enable_notifications']) ? $options['mxchat_enable_notifications'] : 0;
1346 ?>
1347 <label for="mxchat_enable_notifications">
1348 <input type="checkbox" id="mxchat_enable_notifications"
1349 name="mxchat_transcripts_options[mxchat_enable_notifications]"
1350 value="1" <?php checked(1, $enabled); ?>>
1351 <?php esc_html_e('Send email notification when a new chat session starts', 'mxchat'); ?>
1352 </label>
1353 <p class="description">
1354 <?php esc_html_e('Enable this option to receive email notifications for new chat sessions.', 'mxchat'); ?>
1355 </p>
1356 <?php
1357 }
1358 public function mxchat_notification_email_callback() {
1359 $options = get_option('mxchat_transcripts_options', array());
1360 $email = isset($options['mxchat_notification_email']) ? $options['mxchat_notification_email'] : get_option('admin_email');
1361 ?>
1362 <input type="email" id="mxchat_notification_email"
1363 name="mxchat_transcripts_options[mxchat_notification_email]"
1364 value="<?php echo esc_attr($email); ?>"
1365 class="regular-text">
1366 <p class="description">
1367 <?php esc_html_e('Enter the email address where notifications should be sent. Defaults to the admin email address.', 'mxchat'); ?>
1368 </p>
1369 <?php
1370 }
1371
1372 public function mxchat_auto_delete_transcripts_callback() {
1373 $options = get_option('mxchat_transcripts_options', array());
1374 $interval = isset($options['mxchat_auto_delete_transcripts']) ? $options['mxchat_auto_delete_transcripts'] : 'never';
1375 ?>
1376 <select id="mxchat_auto_delete_transcripts"
1377 name="mxchat_transcripts_options[mxchat_auto_delete_transcripts]">
1378 <option value="never" <?php selected($interval, 'never'); ?>>
1379 <?php esc_html_e('Never (Keep All Transcripts)', 'mxchat'); ?>
1380 </option>
1381 <option value="1week" <?php selected($interval, '1week'); ?>>
1382 <?php esc_html_e('After 1 Week', 'mxchat'); ?>
1383 </option>
1384 <option value="2weeks" <?php selected($interval, '2weeks'); ?>>
1385 <?php esc_html_e('After 2 Weeks', 'mxchat'); ?>
1386 </option>
1387 <option value="1month" <?php selected($interval, '1month'); ?>>
1388 <?php esc_html_e('After 1 Month', 'mxchat'); ?>
1389 </option>
1390 </select>
1391 <p class="description">
1392 <?php esc_html_e('Automatically delete old chat transcripts after the selected time period. This helps manage database size and privacy.', 'mxchat'); ?>
1393 </p>
1394 <?php
1395 }
1396
1397 public function mxchat_auto_email_transcript_callback() {
1398 $options = get_option('mxchat_transcripts_options', array());
1399 $enabled = isset($options['mxchat_auto_email_transcript_enabled']) ? $options['mxchat_auto_email_transcript_enabled'] : 0;
1400 $delay = isset($options['mxchat_auto_email_transcript_delay']) ? $options['mxchat_auto_email_transcript_delay'] : '30';
1401 $require_contact = isset($options['mxchat_auto_email_transcript_require_contact']) ? $options['mxchat_auto_email_transcript_require_contact'] : 0;
1402 ?>
1403 <label>
1404 <input type="checkbox"
1405 id="mxchat_auto_email_transcript_enabled"
1406 name="mxchat_transcripts_options[mxchat_auto_email_transcript_enabled]"
1407 value="1"
1408 <?php checked($enabled, 1); ?>>
1409 <?php esc_html_e('Enable Auto-Email of Full Transcript', 'mxchat'); ?>
1410 </label>
1411 <br><br>
1412 <label for="mxchat_auto_email_transcript_delay">
1413 <?php esc_html_e('Send transcript after:', 'mxchat'); ?>
1414 </label>
1415 <select id="mxchat_auto_email_transcript_delay"
1416 name="mxchat_transcripts_options[mxchat_auto_email_transcript_delay]">
1417 <option value="15" <?php selected($delay, '15'); ?>>
1418 <?php esc_html_e('15 minutes', 'mxchat'); ?>
1419 </option>
1420 <option value="30" <?php selected($delay, '30'); ?>>
1421 <?php esc_html_e('30 minutes', 'mxchat'); ?>
1422 </option>
1423 <option value="60" <?php selected($delay, '60'); ?>>
1424 <?php esc_html_e('1 hour', 'mxchat'); ?>
1425 </option>
1426 </select>
1427 <br><br>
1428 <label>
1429 <input type="checkbox"
1430 id="mxchat_auto_email_transcript_require_contact"
1431 name="mxchat_transcripts_options[mxchat_auto_email_transcript_require_contact]"
1432 value="1"
1433 <?php checked($require_contact, 1); ?>>
1434 <?php esc_html_e('Only send if visitor provided contact info', 'mxchat'); ?>
1435 </label>
1436 <p class="description">
1437 <?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'); ?>
1438 <br>
1439 <?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'); ?>
1440 </p>
1441 <?php
1442 }
1443
1444
1445
1446 public function sanitize_transcripts_options($input) {
1447 $sanitized = array();
1448
1449 $sanitized['mxchat_enable_notifications'] = isset($input['mxchat_enable_notifications']) ? 1 : 0;
1450
1451 if (isset($input['mxchat_notification_email'])) {
1452 $sanitized['mxchat_notification_email'] = sanitize_email($input['mxchat_notification_email']);
1453 if (!is_email($sanitized['mxchat_notification_email'])) {
1454 add_settings_error(
1455 'mxchat_transcripts_options',
1456 'invalid_email',
1457 __('Please enter a valid email address for notifications.', 'mxchat'),
1458 'error'
1459 );
1460 $sanitized['mxchat_notification_email'] = get_option('admin_email');
1461 }
1462 }
1463
1464 // Sanitize auto-delete setting
1465 $valid_intervals = array('never', '1week', '2weeks', '1month');
1466 if (isset($input['mxchat_auto_delete_transcripts'])) {
1467 $sanitized['mxchat_auto_delete_transcripts'] = in_array($input['mxchat_auto_delete_transcripts'], $valid_intervals)
1468 ? $input['mxchat_auto_delete_transcripts']
1469 : 'never';
1470 } else {
1471 $sanitized['mxchat_auto_delete_transcripts'] = 'never';
1472 }
1473
1474 // Get old value to check if auto-delete setting changed
1475 $old_options = get_option('mxchat_transcripts_options');
1476 $old_interval = isset($old_options['mxchat_auto_delete_transcripts']) ? $old_options['mxchat_auto_delete_transcripts'] : 'never';
1477
1478 // If the auto-delete setting changed, reschedule the cron job
1479 if ($old_interval !== $sanitized['mxchat_auto_delete_transcripts']) {
1480 $this->schedule_transcript_cleanup($sanitized['mxchat_auto_delete_transcripts']);
1481 }
1482
1483 // Sanitize auto-email transcript settings
1484 $sanitized['mxchat_auto_email_transcript_enabled'] = isset($input['mxchat_auto_email_transcript_enabled']) ? 1 : 0;
1485
1486 $valid_delays = array('15', '30', '60');
1487 if (isset($input['mxchat_auto_email_transcript_delay'])) {
1488 $sanitized['mxchat_auto_email_transcript_delay'] = in_array($input['mxchat_auto_email_transcript_delay'], $valid_delays)
1489 ? $input['mxchat_auto_email_transcript_delay']
1490 : '30';
1491 } else {
1492 $sanitized['mxchat_auto_email_transcript_delay'] = '30';
1493 }
1494
1495 // Sanitize require contact info setting
1496 $sanitized['mxchat_auto_email_transcript_require_contact'] = isset($input['mxchat_auto_email_transcript_require_contact']) ? 1 : 0;
1497
1498 return $sanitized;
1499 }
1500
1501 /**
1502 * Schedule or unschedule the transcript cleanup cron job
1503 */
1504 public function schedule_transcript_cleanup($interval) {
1505 // Clear any existing scheduled event
1506 $timestamp = wp_next_scheduled('mxchat_cleanup_old_transcripts');
1507 if ($timestamp) {
1508 wp_unschedule_event($timestamp, 'mxchat_cleanup_old_transcripts');
1509 }
1510
1511 // Schedule new event if not set to "never"
1512 if ($interval !== 'never') {
1513 // Schedule to run daily at 3 AM
1514 $next_run = strtotime('tomorrow 3:00 AM');
1515 wp_schedule_event($next_run, 'daily', 'mxchat_cleanup_old_transcripts');
1516 }
1517 }
1518
1519 /**
1520 * Delete old transcripts based on the configured interval
1521 */
1522 public function cleanup_old_transcripts() {
1523 $options = get_option('mxchat_transcripts_options', array());
1524 $interval = isset($options['mxchat_auto_delete_transcripts']) ? $options['mxchat_auto_delete_transcripts'] : 'never';
1525
1526 // If set to never, don't delete anything
1527 if ($interval === 'never') {
1528 return;
1529 }
1530
1531 // Calculate the cutoff date
1532 $days = 0;
1533 switch ($interval) {
1534 case '1week':
1535 $days = 7;
1536 break;
1537 case '2weeks':
1538 $days = 14;
1539 break;
1540 case '1month':
1541 $days = 30;
1542 break;
1543 default:
1544 return; // Invalid interval, don't delete anything
1545 }
1546
1547 global $wpdb;
1548 $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
1549
1550 // Calculate the cutoff timestamp
1551 $cutoff_date = date('Y-m-d H:i:s', strtotime("-{$days} days"));
1552
1553 // Delete transcripts older than the cutoff date
1554 $deleted = $wpdb->query(
1555 $wpdb->prepare(
1556 "DELETE FROM {$table_name} WHERE created_at < %s",
1557 $cutoff_date
1558 )
1559 );
1560
1561 // Log the cleanup action
1562 if ($deleted !== false && $deleted > 0) {
1563 error_log(sprintf('MXChat: Auto-deleted %d transcripts older than %d days', $deleted, $days));
1564 }
1565 }
1566
1567 public function export_chat_transcripts() {
1568 if (!current_user_can('manage_options')) {
1569 wp_die(esc_html__('You do not have sufficient permissions to access this page.', 'mxchat'));
1570 }
1571
1572 check_ajax_referer('mxchat_export_transcripts', 'security');
1573
1574 global $wpdb;
1575 $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
1576
1577 // Get all transcripts ordered by session and timestamp
1578 $results = $wpdb->get_results(
1579 "SELECT session_id, user_email, user_identifier, role, message, timestamp
1580 FROM {$table_name}
1581 ORDER BY session_id, timestamp ASC"
1582 );
1583
1584 if (empty($results)) {
1585 wp_send_json_error(array('message' => 'No transcripts found.'));
1586 wp_die();
1587 }
1588
1589 // Set headers for CSV download
1590 header('Content-Type: text/csv');
1591 header('Content-Disposition: attachment; filename="chat-transcripts-' . date('Y-m-d') . '.csv"');
1592 header('Pragma: no-cache');
1593 header('Expires: 0');
1594
1595 // Create output stream
1596 $output = fopen('php://output', 'w');
1597
1598 // Add UTF-8 BOM for proper Excel encoding
1599 fputs($output, "\xEF\xBB\xBF");
1600
1601 // Add CSV headers
1602 fputcsv($output, array(
1603 'Session ID',
1604 'Email',
1605 'User Identifier',
1606 'Role',
1607 'Message',
1608 'Timestamp'
1609 ));
1610
1611 // Add data rows
1612 foreach ($results as $row) {
1613 fputcsv($output, array(
1614 $row->session_id,
1615 $row->user_email,
1616 $row->user_identifier,
1617 $row->role,
1618 $row->message,
1619 $row->timestamp
1620 ));
1621 }
1622
1623 fclose($output);
1624 wp_die();
1625 }
1626
1627
1628 public function mxchat_fetch_chat_history() {
1629 global $wpdb;
1630 $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
1631 $url_clicks_table = $wpdb->prefix . 'mxchat_url_clicks';
1632
1633 if (!current_user_can('manage_options')) {
1634 wp_die(esc_html__('You do not have sufficient permissions to view this page.', 'mxchat'));
1635 }
1636
1637 $page = isset($_POST['page']) ? absint($_POST['page']) : 1;
1638 $per_page = isset($_POST['per_page']) ? absint($_POST['per_page']) : 50;
1639 $offset = ($page - 1) * $per_page;
1640 $search = isset($_POST['search']) ? sanitize_text_field($_POST['search']) : '';
1641
1642 $search_condition = '';
1643 $search_params = [];
1644
1645 if (!empty($search)) {
1646 $search_condition = "WHERE (
1647 session_id LIKE %s
1648 OR user_email LIKE %s
1649 OR user_name LIKE %s
1650 OR user_identifier LIKE %s
1651 OR message LIKE %s
1652 )";
1653 $search_params = array_fill(0, 5, '%' . $wpdb->esc_like($search) . '%');
1654 }
1655
1656 $count_query = !empty($search)
1657 ? $wpdb->prepare("SELECT COUNT(DISTINCT session_id) FROM {$table_name} {$search_condition}", $search_params)
1658 : "SELECT COUNT(DISTINCT session_id) FROM {$table_name}";
1659
1660 $total_sessions = $wpdb->get_var($count_query);
1661
1662 $session_query = !empty($search)
1663 ? $wpdb->prepare(
1664 "SELECT DISTINCT t.session_id FROM {$table_name} t {$search_condition}
1665 GROUP BY t.session_id ORDER BY MAX(t.timestamp) DESC LIMIT %d OFFSET %d",
1666 array_merge($search_params, [$per_page, $offset])
1667 )
1668 : $wpdb->prepare(
1669 "SELECT DISTINCT session_id FROM {$table_name}
1670 GROUP BY session_id ORDER BY MAX(timestamp) DESC LIMIT %d OFFSET %d",
1671 $per_page, $offset
1672 );
1673
1674 $session_ids = $wpdb->get_col($session_query);
1675
1676 if (empty($session_ids)) {
1677 ob_start();
1678 echo '<div class="mxchat-no-results">';
1679 echo esc_html__('No chat history found.', 'mxchat');
1680 if (!empty($search)) {
1681 echo ' ' . esc_html__('Try adjusting your search criteria.', 'mxchat');
1682 }
1683 echo '</div>';
1684 wp_send_json([
1685 'html' => ob_get_clean(),
1686 'page' => $page,
1687 'total_pages' => 0,
1688 'total_sessions' => 0
1689 ]);
1690 wp_die();
1691 }
1692
1693 $url_table_exists = $wpdb->get_var("SHOW TABLES LIKE '$url_clicks_table'") === $url_clicks_table;
1694 $originating_columns_exist = !empty($wpdb->get_results("SHOW COLUMNS FROM $table_name LIKE 'originating_page_url'"));
1695
1696 ob_start();
1697 echo '<div class="mxchat-transcript">';
1698
1699 foreach ($session_ids as $session_id) {
1700 $session_data = $originating_columns_exist
1701 ? $wpdb->get_row($wpdb->prepare(
1702 "SELECT user_email, user_name, user_identifier, originating_page_url, originating_page_title, timestamp
1703 FROM {$table_name} WHERE session_id = %s ORDER BY timestamp ASC LIMIT 1",
1704 $session_id
1705 ))
1706 : $wpdb->get_row($wpdb->prepare(
1707 "SELECT user_email, user_name, user_identifier, timestamp FROM {$table_name}
1708 WHERE session_id = %s LIMIT 1",
1709 $session_id
1710 ));
1711
1712 $clicked_urls = [];
1713 if ($url_table_exists) {
1714 $url_clicks = $wpdb->get_results(
1715 $wpdb->prepare(
1716 "SELECT DISTINCT clicked_url FROM {$url_clicks_table}
1717 WHERE session_id = %s ORDER BY click_timestamp ASC",
1718 $session_id
1719 )
1720 );
1721 foreach ($url_clicks as $click) {
1722 $clicked_urls[] = $click->clicked_url;
1723 }
1724 }
1725
1726 $messages = $wpdb->get_results(
1727 $wpdb->prepare(
1728 "SELECT * FROM {$table_name}
1729 WHERE session_id = %s ORDER BY timestamp ASC",
1730 $session_id
1731 )
1732 );
1733
1734 $latest_timestamp = !empty($messages) ? end($messages)->timestamp : $session_data->timestamp;
1735 $session_time = wp_date('M j, g:ia', strtotime($latest_timestamp . ' UTC'));
1736
1737 $user_email = !empty($session_data->user_email) ? $session_data->user_email : '';
1738 $user_name = !empty($session_data->user_name) ? $session_data->user_name : ''; // NEW
1739 $user_identifier = !empty($session_data->user_identifier) ? $session_data->user_identifier : 'Guest';
1740
1741 // Build user display with name if available
1742 if (!empty($user_email)) {
1743 $user_display = $user_email;
1744 if (!empty($user_name)) {
1745 $user_display .= ' (' . $user_name . ')';
1746 }
1747 } else {
1748 $user_display = $user_identifier;
1749 }
1750 echo '<div class="mxchat-session">';
1751 echo '<div class="mxchat-session-header" data-session-id="' . esc_attr($session_id) . '">';
1752 echo '<div class="mxchat-user-row">' . esc_html($user_display) . '</div>';
1753
1754 if ($originating_columns_exist && !empty($session_data->originating_page_url)) {
1755 $page_title = !empty($session_data->originating_page_title) ? $session_data->originating_page_title : $session_data->originating_page_url;
1756
1757 echo '<div class="mxchat-header-row flex-between">';
1758 echo '<div class="mxchat-main-info">';
1759 echo '<a href="' . esc_url($session_data->originating_page_url) . '" target="_blank" class="mxchat-page-link">';
1760 echo esc_html($page_title);
1761 echo '</a>';
1762 echo '<span class="mxchat-session-time">' . esc_html($session_time) . '</span>';
1763 echo '</div>';
1764 echo '</div>';
1765 }
1766
1767 if (!empty($clicked_urls)) {
1768 echo '<div class="mxchat-links-row">';
1769 echo '<span class="mxchat-label">Clicked:</span> ';
1770 $link_count = 0;
1771 foreach ($clicked_urls as $url) {
1772 if ($link_count > 0) echo ', ';
1773 echo '<a href="' . esc_url($url) . '" target="_blank" class="mxchat-link-item">' . esc_html(parse_url($url, PHP_URL_HOST)) . '</a>';
1774 $link_count++;
1775 if ($link_count >= 3) {
1776 if (count($clicked_urls) > 3) {
1777 echo ' +' . (count($clicked_urls) - 3) . ' more';
1778 }
1779 break;
1780 }
1781 }
1782 echo '</div>';
1783 }
1784
1785 echo '</div>'; // end session-header
1786 echo '<div class="mxchat-messages">';
1787
1788 foreach ($messages as $transcript) {
1789 $formatted_timestamp = wp_date('F j, Y g:i a', strtotime($transcript->timestamp . ' UTC'));
1790
1791 switch ($transcript->role) {
1792 case 'assistant':
1793 case 'bot':
1794 $message_class = 'bot-message';
1795 $display_role = esc_html__('Chatbot', 'mxchat');
1796 break;
1797 case 'user':
1798 $message_class = 'user-message';
1799 $display_role = !empty($transcript->user_identifier)
1800 ? sanitize_text_field($transcript->user_identifier)
1801 : esc_html__('User', 'mxchat');
1802 break;
1803 case 'agent':
1804 $message_class = 'agent-message';
1805 $display_role = esc_html__('Agent', 'mxchat');
1806 break;
1807 default:
1808 $message_class = 'unknown-message';
1809 $display_role = esc_html__('Unknown', 'mxchat');
1810 }
1811
1812 $message_content = wp_kses(
1813 stripslashes($transcript->message),
1814 [
1815 'b' => [], 'strong' => [], 'i' => [], 'em' => [], 'u' => [],
1816 'br' => [], 'p' => [], 'ul' => [], 'ol' => [], 'li' => [],
1817 'a' => ['href' => [], 'title' => [], 'target' => []]
1818 ]
1819 );
1820 // Note: Don't use nl2br() here - format_transcript_message() handles paragraph formatting
1821
1822 // Check if this bot message has RAG context data
1823 $has_rag_context = false;
1824 if (($transcript->role === 'bot' || $transcript->role === 'assistant') && !empty($transcript->rag_context)) {
1825 $has_rag_context = true;
1826 }
1827
1828 $message_id_attr = $has_rag_context ? ' data-message-id="' . esc_attr($transcript->id) . '"' : '';
1829
1830 // Process markdown in message content
1831 $formatted_content = $this->format_transcript_message($message_content);
1832
1833 echo '<div class="mxchat-message ' . esc_attr($message_class) . '"' . $message_id_attr . '>';
1834 echo '<div class="mxchat-message-header">';
1835 echo '<span class="mxchat-role-label">' . esc_html($display_role) . '</span>';
1836 if ($has_rag_context) {
1837 echo '<span class="mxchat-rag-link" title="' . esc_attr__('View retrieved documents', 'mxchat') . '">Sources &rarr;</span>';
1838 }
1839 echo '</div>';
1840 echo '<div class="mxchat-message-content">' . $formatted_content . '</div>';
1841 echo '<div class="mxchat-timestamp">' . esc_html($formatted_timestamp) . '</div>';
1842 echo '</div>';
1843 }
1844
1845 echo '</div></div>'; // end messages and session
1846 }
1847
1848 echo '</div>'; // end transcript
1849
1850 $total_pages = ceil($total_sessions / $per_page);
1851
1852 echo '<div class="mxchat-pagination">';
1853 echo '<div class="mxchat-pagination-info">';
1854 echo sprintf(
1855 esc_html__('Showing %1$d to %2$d of %3$d sessions', 'mxchat'),
1856 $offset + 1,
1857 min($offset + $per_page, $total_sessions),
1858 $total_sessions
1859 );
1860 echo '</div>';
1861
1862 if ($total_pages > 1) {
1863 echo '<div class="mxchat-pagination-controls">';
1864 if ($page > 1) {
1865 echo '<button class="mxchat-pagination-button" data-page="' . ($page - 1) . '">&laquo; ' . esc_html__('Previous', 'mxchat') . '</button>';
1866 }
1867
1868 $start_page = max(1, $page - 2);
1869 $end_page = min($total_pages, $page + 2);
1870
1871 if ($start_page > 1) {
1872 echo '<button class="mxchat-pagination-button" data-page="1">1</button>';
1873 if ($start_page > 2) {
1874 echo '<span class="mxchat-pagination-ellipsis">...</span>';
1875 }
1876 }
1877
1878 for ($i = $start_page; $i <= $end_page; $i++) {
1879 $class = $i === $page ? 'mxchat-pagination-button active' : 'mxchat-pagination-button';
1880 echo '<button class="' . $class . '" data-page="' . $i . '">' . $i . '</button>';
1881 }
1882
1883 if ($end_page < $total_pages) {
1884 if ($end_page < $total_pages - 1) {
1885 echo '<span class="mxchat-pagination-ellipsis">...</span>';
1886 }
1887 echo '<button class="mxchat-pagination-button" data-page="' . $total_pages . '">' . $total_pages . '</button>';
1888 }
1889
1890 if ($page < $total_pages) {
1891 echo '<button class="mxchat-pagination-button" data-page="' . ($page + 1) . '">' . esc_html__('Next', 'mxchat') . ' &raquo;</button>';
1892 }
1893
1894 echo '</div>';
1895 }
1896
1897 echo '</div>';
1898
1899 wp_send_json([
1900 'html' => ob_get_clean(),
1901 'page' => $page,
1902 'total_pages' => $total_pages,
1903 'total_sessions' => $total_sessions
1904 ]);
1905
1906 wp_die();
1907 }
1908
1909
1910 /**
1911 * ALTERNATIVE: Simpler helper method using string replacement
1912 */
1913 private function highlight_clicked_links($message_content, $clicked_urls) {
1914 if (empty($clicked_urls)) {
1915 return wp_kses(
1916 $message_content,
1917 [
1918 'b' => [], 'strong' => [], 'i' => [], 'em' => [], 'u' => [],
1919 'br' => [], 'p' => [], 'ul' => [], 'ol' => [], 'li' => [],
1920 'a' => ['href' => [], 'title' => [], 'target' => [], 'class' => []]
1921 ]
1922 );
1923 }
1924
1925 // First apply standard sanitization
1926 $message_content = wp_kses(
1927 $message_content,
1928 [
1929 'b' => [], 'strong' => [], 'i' => [], 'em' => [], 'u' => [],
1930 'br' => [], 'p' => [], 'ul' => [], 'ol' => [], 'li' => [],
1931 'a' => ['href' => [], 'title' => [], 'target' => [], 'class' => []]
1932 ]
1933 );
1934
1935 // Process each clicked URL
1936 foreach ($clicked_urls as $clicked_url) {
1937 // Try multiple patterns to catch different link formats
1938 $patterns = [
1939 // Standard link format
1940 '/<a([^>]*href=["\']' . preg_quote($clicked_url, '/') . '["\'][^>]*)>/i',
1941 // Link with trailing slash
1942 '/<a([^>]*href=["\']' . preg_quote(rtrim($clicked_url, '/'), '/') . '\/?["\'][^>]*)>/i',
1943 // Encoded entities version
1944 '/<a([^>]*href=["\']' . preg_quote(htmlentities($clicked_url), '/') . '["\'][^>]*)>/i',
1945 ];
1946
1947 foreach ($patterns as $pattern) {
1948 if (preg_match($pattern, $message_content)) {
1949 $message_content = preg_replace_callback(
1950 $pattern,
1951 function($matches) {
1952 $full_match = $matches[0];
1953 $attributes = $matches[1];
1954
1955 // Check if it already has the class
1956 if (strpos($full_match, 'mxchat-clicked-link') !== false) {
1957 return $full_match;
1958 }
1959
1960 // Check if class attribute exists
1961 if (preg_match('/class=["\']([^"\']*)["\']/', $attributes, $class_matches)) {
1962 // Add to existing class
1963 $new_attributes = preg_replace(
1964 '/class=["\']([^"\']*)["\']/',
1965 'class="$1 mxchat-clicked-link"',
1966 $attributes
1967 );
1968 } else {
1969 // Add new class attribute
1970 $new_attributes = $attributes . ' class="mxchat-clicked-link"';
1971 }
1972
1973 // Add title if not present
1974 if (strpos($new_attributes, 'title=') === false) {
1975 $new_attributes .= ' title="User clicked this link"';
1976 }
1977
1978 return '<a' . $new_attributes . '>';
1979 },
1980 $message_content
1981 );
1982
1983 // If we found and replaced, break out of the patterns loop
1984 break;
1985 }
1986 }
1987 }
1988
1989 return $message_content;
1990 }
1991
1992 public function mxchat_create_prompts_page() {
1993 //error_log('=== DEBUG: mxchat_create_prompts_page started ===');
1994
1995 global $wpdb;
1996 $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
1997
1998 $knowledge_manager = MxChat_Knowledge_Manager::get_instance();
1999 $pinecone_manager = MxChat_Pinecone_Manager::get_instance();
2000
2001 // Display success message if all prompts were deleted
2002 if (isset($_GET['all_deleted']) && $_GET['all_deleted'] === 'true') {
2003 echo '<div class="notice notice-success is-dismissible"><p>' . esc_html__('All knowledge has been deleted successfully.', 'mxchat') . '</p></div>';
2004 }
2005
2006 // Set up pagination, search query, and content type filter
2007 $nonce = isset($_GET['_wpnonce']) ? sanitize_text_field($_GET['_wpnonce']) : '';
2008 $search_query = (!empty($nonce) && wp_verify_nonce($nonce, 'mxchat_prompts_search_nonce') && isset($_GET['search'])) ? sanitize_text_field($_GET['search']) : '';
2009 $content_type_filter = isset($_GET['content_type']) ? sanitize_key($_GET['content_type']) : ''; // ADDED 2.5.6
2010 $current_page = isset($_GET['paged']) ? absint($_GET['paged']) : 1;
2011 $per_page = 10;
2012
2013 //error_log('DEBUG: Search query: ' . $search_query);
2014 //error_log('DEBUG: Content type filter: ' . $content_type_filter);
2015 //error_log('DEBUG: Current page: ' . $current_page);
2016 //error_log('DEBUG: Per page: ' . $per_page);
2017
2018 // ================================
2019 // MULTI-BOT CONFIGURATION
2020 // ================================
2021
2022 // Check for multi-bot and set up bot selection
2023 if (class_exists('MxChat_Multi_Bot_Manager')) {
2024 $multi_bot_manager = MxChat_Multi_Bot_Core_Manager::get_instance();
2025 $available_bots = $multi_bot_manager->get_available_bots();
2026
2027 // Get saved bot selection (user-specific first, then site-wide default)
2028 $user_id = get_current_user_id();
2029 $saved_bot_id = get_user_meta($user_id, 'mxchat_selected_knowledge_bot', true);
2030 if (empty($saved_bot_id)) {
2031 $saved_bot_id = get_option('mxchat_current_knowledge_bot', 'default');
2032 }
2033
2034 // Allow URL override but default to saved selection
2035 $current_bot_id = isset($_GET['bot_id']) ? sanitize_key($_GET['bot_id']) : $saved_bot_id;
2036 $multibot_active = true;
2037 } else {
2038 $current_bot_id = 'default';
2039 $multibot_active = false;
2040 }
2041
2042 // ================================
2043 // DATA SOURCE CONFIGURATION
2044 // ================================
2045
2046 // Get bot-specific Pinecone settings
2047 $pinecone_options = $pinecone_manager->mxchat_get_bot_pinecone_options($current_bot_id);
2048 $use_pinecone = ($pinecone_options['mxchat_use_pinecone'] ?? '0') === '1';
2049 $pinecone_api_key = $pinecone_options['mxchat_pinecone_api_key'] ?? '';
2050
2051 //error_log('DEBUG: Bot ' . $current_bot_id . ' - Use Pinecone: ' . ($use_pinecone ? 'YES' : 'NO'));
2052 //error_log('DEBUG: Bot ' . $current_bot_id . ' - Has API Key: ' . (!empty($pinecone_api_key) ? 'YES' : 'NO'));
2053 //error_log('DEBUG: Bot ' . $current_bot_id . ' - Host: ' . ($pinecone_options['mxchat_pinecone_host'] ?? 'NOT SET'));
2054 //error_log('DEBUG: Bot ' . $current_bot_id . ' - Namespace: ' . ($pinecone_options['mxchat_pinecone_namespace'] ?? 'NOT SET'));
2055
2056 if ($use_pinecone && !empty($pinecone_api_key)) {
2057 //error_log('DEBUG: Using PINECONE data source with bot-specific config');
2058 // PINECONE DATA SOURCE
2059 $data_source = 'pinecone';
2060
2061 // IMPORTANT: Pass the bot-specific $pinecone_options, not default options!
2062 // UPDATED 2.5.6: Added content_type_filter parameter
2063 $records = $pinecone_manager->mxchat_fetch_pinecone_records($pinecone_options, $search_query, $current_page, $per_page, $current_bot_id, $content_type_filter);
2064
2065 // TEMPORARY DEBUG - Add this right after the fetch call
2066 //error_log('=== DEBUG: Bot switching issue ===');
2067 //error_log('Current bot ID: ' . $current_bot_id);
2068 //error_log('Use Pinecone: ' . ($use_pinecone ? 'YES' : 'NO'));
2069 //error_log('Records returned: ' . count($records['data'] ?? []));
2070 //error_log('Total records: ' . ($records['total'] ?? 0));
2071
2072 // Check first few records to see their bot_id
2073 if (!empty($records['data'])) {
2074 foreach (array_slice($records['data'], 0, 3) as $i => $record) {
2075 $record_bot_id = $record->bot_id ?? 'NOT_SET';
2076 //error_log('Record ' . ($i+1) . ' bot_id: ' . $record_bot_id . ', content preview: ' . substr($record->article_content ?? '', 0, 30) . '...');
2077 }
2078 }
2079 //error_log('=== END DEBUG ===');
2080
2081 $total_records = $records['total'] ?? 0;
2082 $prompts = $records['data'] ?? array();
2083 $total_in_database = $records['total_in_database'] ?? 0;
2084 $showing_recent_only = $records['showing_recent_only'] ?? false;
2085
2086 $total_pages = ceil($total_records / $per_page);
2087
2088 } else {
2089 //error_log('DEBUG: Using WORDPRESS DB data source');
2090 // WORDPRESS DB DATA SOURCE (your existing logic)
2091 $data_source = 'wordpress';
2092
2093 // Initialize these variables for WordPress DB
2094 $total_in_database = 0;
2095 $showing_recent_only = false;
2096
2097 $offset = ($current_page - 1) * $per_page;
2098
2099 // UPDATED 2.5.6: Build WHERE clause for search and content type filtering
2100 $where_clauses = array();
2101 $where_values = array();
2102
2103 if ($search_query) {
2104 $where_clauses[] = "article_content LIKE %s";
2105 $where_values[] = '%' . $wpdb->esc_like($search_query) . '%';
2106 }
2107
2108 if ($content_type_filter) {
2109 $where_clauses[] = "content_type = %s";
2110 $where_values[] = $content_type_filter;
2111 }
2112
2113 $sql_where = "";
2114 if (!empty($where_clauses)) {
2115 $sql_where = "WHERE " . implode(" AND ", $where_clauses);
2116 }
2117
2118 // UPDATED 2.6.3: Count unique entries (by source_url) instead of individual rows
2119 // This ensures pagination shows X entries per page, not X chunks
2120 // Entries with empty source_url are counted individually
2121 if (!empty($where_values)) {
2122 // Count unique source_urls + count of rows with empty source_url
2123 $count_query = $wpdb->prepare(
2124 "SELECT (SELECT COUNT(DISTINCT source_url) FROM {$table_name} {$sql_where} AND source_url != '') +
2125 (SELECT COUNT(*) FROM {$table_name} {$sql_where} AND (source_url = '' OR source_url IS NULL))",
2126 array_merge($where_values, $where_values)
2127 );
2128 } else {
2129 $count_query = "SELECT (SELECT COUNT(DISTINCT source_url) FROM {$table_name} WHERE source_url != '') +
2130 (SELECT COUNT(*) FROM {$table_name} WHERE source_url = '' OR source_url IS NULL)";
2131 }
2132 $total_records = $wpdb->get_var($count_query);
2133 $total_pages = ceil($total_records / $per_page);
2134
2135 // UPDATED 2.6.3: Get unique source_urls for pagination, then fetch all their rows
2136 // Step 1: Get the source_urls for this page (distinct URLs ordered by latest timestamp)
2137 if (!empty($where_values)) {
2138 $urls_query = $wpdb->prepare(
2139 "SELECT source_url, MAX(timestamp) as latest_ts FROM {$table_name} {$sql_where}
2140 GROUP BY source_url ORDER BY latest_ts DESC LIMIT %d OFFSET %d",
2141 array_merge($where_values, array($per_page, $offset))
2142 );
2143 } else {
2144 $urls_query = $wpdb->prepare(
2145 "SELECT source_url, MAX(timestamp) as latest_ts FROM {$table_name}
2146 GROUP BY source_url ORDER BY latest_ts DESC LIMIT %d OFFSET %d",
2147 $per_page, $offset
2148 );
2149 }
2150 $page_urls = $wpdb->get_results($urls_query);
2151
2152 // Step 2: Fetch all rows for these source_urls
2153 $prompts = array();
2154 if (!empty($page_urls)) {
2155 $url_list = array();
2156 $has_empty_url = false;
2157 foreach ($page_urls as $url_row) {
2158 if (empty($url_row->source_url)) {
2159 $has_empty_url = true;
2160 } else {
2161 $url_list[] = $url_row->source_url;
2162 }
2163 }
2164
2165 // Build query to fetch all rows for these URLs
2166 $url_conditions = array();
2167 $url_values = array();
2168
2169 if (!empty($url_list)) {
2170 $placeholders = implode(',', array_fill(0, count($url_list), '%s'));
2171 $url_conditions[] = "source_url IN ($placeholders)";
2172 $url_values = array_merge($url_values, $url_list);
2173 }
2174
2175 if ($has_empty_url) {
2176 $url_conditions[] = "(source_url = '' OR source_url IS NULL)";
2177 }
2178
2179 if (!empty($url_conditions)) {
2180 $url_where = "WHERE (" . implode(" OR ", $url_conditions) . ")";
2181
2182 // Add original filters back
2183 if (!empty($where_clauses)) {
2184 $url_where .= " AND " . implode(" AND ", $where_clauses);
2185 $url_values = array_merge($url_values, $where_values);
2186 }
2187
2188 if (!empty($url_values)) {
2189 $prompts_query = $wpdb->prepare(
2190 "SELECT * FROM {$table_name} {$url_where} ORDER BY source_url, timestamp DESC",
2191 $url_values
2192 );
2193 } else {
2194 $prompts_query = "SELECT * FROM {$table_name} {$url_where} ORDER BY source_url, timestamp DESC";
2195 }
2196
2197 $prompts = $wpdb->get_results($prompts_query);
2198 }
2199 }
2200 }
2201
2202 // ================================
2203 // PAGINATION GENERATION
2204 // ================================
2205
2206 // Generate pagination links
2207 if ($total_pages > 1) {
2208 // Build base URL with necessary parameters
2209 $base_url = add_query_arg('paged', '%#%');
2210
2211 // Preserve bot_id parameter if multi-bot is active
2212 if ($multibot_active && !empty($current_bot_id) && $current_bot_id !== 'default') {
2213 $base_url = add_query_arg('bot_id', $current_bot_id, $base_url);
2214 }
2215
2216 // Preserve search query if present
2217 if ($search_query) {
2218 $base_url = add_query_arg('search', $search_query, $base_url);
2219 }
2220
2221 // UPDATED 2.5.6: Preserve content type filter if present
2222 if ($content_type_filter) {
2223 $base_url = add_query_arg('content_type', $content_type_filter, $base_url);
2224 }
2225
2226 $page_links = paginate_links(array(
2227 'base' => $base_url,
2228 'format' => '',
2229 'prev_text' => __('&laquo; Previous', 'mxchat'),
2230 'next_text' => __('Next &raquo;', 'mxchat'),
2231 'total' => $total_pages,
2232 'current' => $current_page,
2233 'type' => 'plain',
2234 ));
2235 } else {
2236 $page_links = '';
2237 }
2238
2239 // ================================
2240 // PROCESSING STATUS RETRIEVAL
2241 // ================================
2242
2243 // Retrieve processing statuses
2244 $pdf_url = get_transient('mxchat_last_pdf_url');
2245 $sitemap_url = get_transient('mxchat_last_sitemap_url');
2246 $pdf_status = $pdf_url ? $knowledge_manager->mxchat_get_pdf_processing_status($pdf_url) : false;
2247 $sitemap_status = $sitemap_url ? $knowledge_manager->mxchat_get_sitemap_processing_status($sitemap_url) : false;
2248
2249 $is_processing = ($pdf_status && ($pdf_status['status'] === 'processing' || $pdf_status['status'] === 'error'))
2250 || ($sitemap_status && ($sitemap_status['status'] === 'processing' || $sitemap_status['status'] === 'error'));
2251
2252 //error_log('=== DEBUG: mxchat_create_prompts_page data preparation completed ===');
2253
2254 ?>
2255
2256 <div class="wrap mxchat-wrapper">
2257 <!-- Hero Section -->
2258 <div class="mxchat-hero">
2259 <h1 class="mxchat-main-title">
2260 <span class="mxchat-gradient-text">Knowledge Base</span> Manager
2261 </h1>
2262 <p class="mxchat-hero-subtitle">
2263 <?php esc_html_e('Enhance your AI chatbot with custom knowledge. Import, manage, and organize your content to keep responses accurate and relevant.', 'mxchat'); ?>
2264 </p>
2265 </div>
2266
2267 <div class="mxchat-content">
2268
2269 <!-- Multi-Bot Selector (only shows if multi-bot addon is active) -->
2270 <?php if (class_exists('MxChat_Multi_Bot_Manager')) :
2271 $multi_bot_manager = MxChat_Multi_Bot_Core_Manager::get_instance();
2272 $available_bots = $multi_bot_manager->get_available_bots();
2273
2274 // Get saved bot selection (user-specific first, then site-wide default)
2275 $user_id = get_current_user_id();
2276 $saved_bot_id = get_user_meta($user_id, 'mxchat_selected_knowledge_bot', true);
2277 if (empty($saved_bot_id)) {
2278 $saved_bot_id = get_option('mxchat_current_knowledge_bot', 'default');
2279 }
2280
2281 // Allow URL override but default to saved selection
2282 $current_bot_id = isset($_GET['bot_id']) ? sanitize_key($_GET['bot_id']) : $saved_bot_id;
2283 ?>
2284 <div class="mxchat-card" style="margin-bottom: 20px;">
2285 <div class="mxchat-bot-selector-wrapper" style="display: flex; align-items: center; gap: 15px;">
2286 <label for="mxchat-bot-selector" style="font-weight: 600;">
2287 <?php esc_html_e('Select Bot Database:', 'mxchat'); ?>
2288 </label>
2289 <select id="mxchat-bot-selector" class="mxchat-bot-selector" style="min-width: 200px;">
2290 <?php foreach ($available_bots as $bot_id => $bot_name) : ?>
2291 <option value="<?php echo esc_attr($bot_id); ?>" <?php selected($current_bot_id, $bot_id); ?>>
2292 <?php echo esc_html($bot_name); ?>
2293 </option>
2294 <?php endforeach; ?>
2295 </select>
2296 <span class="mxchat-bot-info" style="color: #666; font-size: 13px;">
2297 <?php esc_html_e('Content will be added to the selected bot\'s knowledge base', 'mxchat'); ?>
2298 </span>
2299 <span id="mxchat-bot-save-status" style="display: none; color: #00a32a; font-size: 13px;">
2300 ✓ <?php esc_html_e('Saved', 'mxchat'); ?>
2301 </span>
2302 </div>
2303 </div>
2304
2305 <?php
2306 // Set a flag so we know multi-bot is active throughout the page
2307 $multibot_active = true;
2308 else:
2309 $current_bot_id = 'default';
2310 $multibot_active = false;
2311 endif;
2312 ?>
2313
2314 <!-- Tab Navigation -->
2315 <div class="mxchat-kb-tabs-nav">
2316 <button class="mxchat-kb-tab-button active" data-tab="import">
2317 <?php esc_html_e('Knowledge Import', 'mxchat'); ?>
2318 </button>
2319 <button class="mxchat-kb-tab-button" data-tab="sync">
2320 <?php esc_html_e('Settings', 'mxchat'); ?>
2321 </button>
2322 <button class="mxchat-kb-tab-button" data-tab="pinecone">
2323 <?php esc_html_e('Pinecone Settings', 'mxchat'); ?>
2324 </button>
2325 </div>
2326
2327 <div class="mxchat-kb-tabs-content">
2328 <div id="mxchat-kb-tab-import" class="mxchat-kb-tab-content active">
2329 <!-- Import Options Card -->
2330 <div class="mxchat-card">
2331
2332 <h2><?php esc_html_e('Knowledge Import Settings', 'mxchat'); ?></h2>
2333
2334 <?php
2335 // Check if the appropriate embedding API key exists
2336 $embedding_model = isset($this->options['embedding_model']) ? esc_attr($this->options['embedding_model']) : 'text-embedding-ada-002';
2337 $has_openai_key = !empty($this->options['api_key']);
2338 $has_voyage_key = !empty($this->options['voyage_api_key']);
2339 $has_gemini_key = !empty($this->options['gemini_api_key']);
2340
2341 // Determine if they have the needed API key for their selected embedding model
2342 $has_required_key = false;
2343 $required_key_type = '';
2344
2345 if (strpos($embedding_model, 'text-embedding-') !== false && $has_openai_key) {
2346 $has_required_key = true;
2347 $required_key_type = 'OpenAI';
2348 } elseif (strpos($embedding_model, 'voyage-') !== false && $has_voyage_key) {
2349 $has_required_key = true;
2350 $required_key_type = 'Voyage AI';
2351 } elseif (strpos($embedding_model, 'gemini-embedding-') !== false && $has_gemini_key) {
2352 $has_required_key = true;
2353 $required_key_type = 'Google Gemini';
2354 } elseif (strpos($embedding_model, 'text-embedding-') !== false) {
2355 $required_key_type = 'OpenAI';
2356 } elseif (strpos($embedding_model, 'voyage-') !== false) {
2357 $required_key_type = 'Voyage AI';
2358 } elseif (strpos($embedding_model, 'gemini-embedding-') !== false) {
2359 $required_key_type = 'Google Gemini';
2360 }
2361 ?>
2362
2363 <div class="mxchat-knowledge-warning <?php echo $has_required_key ? 'success' : 'warning'; ?>">
2364 <?php if ($has_required_key): ?>
2365 <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>
2366 <?php else: ?>
2367 <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>
2368 <?php endif; ?>
2369 </div>
2370
2371 <!-- Import Options Section -->
2372 <div class="mxchat-import-section">
2373 <h3><?php esc_html_e('Import Options', 'mxchat'); ?></h3>
2374
2375 <!-- Import Options Grid -->
2376 <div class="mxchat-import-options">
2377 <!-- WordPress Import Option -->
2378 <button type="button" id="mxchat-open-content-selector" class="mxchat-import-box mxchat-import-wordpress" data-option="wordpress">
2379 <div class="mxchat-import-icon">
2380 <span class="dashicons dashicons-wordpress"></span>
2381 </div>
2382 <div class="mxchat-import-content">
2383 <h4><?php esc_html_e('WordPress Content', 'mxchat'); ?></h4>
2384 <p><?php esc_html_e('Import specific posts and pages to your knowledge base.', 'mxchat'); ?></p>
2385 </div>
2386 <div class="mxchat-recommended-tag"><?php esc_html_e('Recommended', 'mxchat'); ?></div>
2387 </button>
2388
2389 <!-- Sitemap Import Option -->
2390 <button type="button" class="mxchat-import-box" data-option="sitemap" data-placeholder="<?php esc_attr_e('Enter sitemap URL here', 'mxchat'); ?>" data-type="sitemap">
2391 <div class="mxchat-import-icon">
2392 <span class="dashicons dashicons-admin-site-alt"></span>
2393 </div>
2394 <div class="mxchat-import-content">
2395 <h4><?php esc_html_e('Sitemap Import', 'mxchat'); ?></h4>
2396 <p><?php esc_html_e('Use a content-specific sub-sitemap, not the sitemap index.', 'mxchat'); ?></p>
2397 </div>
2398 </button>
2399
2400 <!-- Direct URL Import Option -->
2401 <button type="button" class="mxchat-import-box" data-option="url" data-placeholder="<?php esc_attr_e('Enter webpage URL here', 'mxchat'); ?>" data-type="url">
2402 <div class="mxchat-import-icon">
2403 <span class="dashicons dashicons-admin-links"></span>
2404 </div>
2405 <div class="mxchat-import-content">
2406 <h4><?php esc_html_e('Direct URL', 'mxchat'); ?></h4>
2407 <p><?php esc_html_e('Import content from any webpage.', 'mxchat'); ?></p>
2408 </div>
2409 </button>
2410
2411 <!-- Direct Content Import Option -->
2412 <button type="button" class="mxchat-import-box" data-option="content" data-type="content">
2413 <div class="mxchat-import-icon">
2414 <span class="dashicons dashicons-editor-paste-text"></span>
2415 </div>
2416 <div class="mxchat-import-content">
2417 <h4><?php esc_html_e('Direct Content', 'mxchat'); ?></h4>
2418 <p><?php esc_html_e('Submit content to be vectorized.', 'mxchat'); ?></p>
2419 </div>
2420 </button>
2421
2422 <!-- PDF Import Option -->
2423 <button type="button" class="mxchat-import-box" data-option="pdf" data-placeholder="<?php esc_attr_e('Enter PDF URL here', 'mxchat'); ?>" data-type="pdf">
2424 <div class="mxchat-import-icon">
2425 <span class="dashicons dashicons-media-document"></span>
2426 </div>
2427 <div class="mxchat-import-content">
2428 <h4><?php esc_html_e('PDF Import', 'mxchat'); ?></h4>
2429 <p><?php esc_html_e('Import knowledge from PDF documents.', 'mxchat'); ?></p>
2430 </div>
2431 </button>
2432 </div>
2433
2434 <!-- Input Areas for URL and Content -->
2435 <div class="mxchat-import-input-area" id="mxchat-url-input-area" style="display: none;">
2436 <?php if (!$is_processing) : ?>
2437 <form id="mxchat-url-form" method="post" action="<?php echo esc_url(admin_url('admin-post.php?action=mxchat_submit_sitemap')); ?>">
2438 <?php wp_nonce_field('mxchat_submit_sitemap_action', 'mxchat_submit_sitemap_nonce'); ?>
2439 <input type="hidden" name="import_type" id="import_type" value="url">
2440 <?php if ($multibot_active && $current_bot_id !== 'default') : ?>
2441 <input type="hidden" name="bot_id" value="<?php echo esc_attr($current_bot_id); ?>">
2442 <?php endif; ?>
2443 <div class="mxchat-url-input-group">
2444 <input type="url"
2445 name="sitemap_url"
2446 id="sitemap_url"
2447 placeholder="<?php esc_attr_e('Enter URL here', 'mxchat'); ?>"
2448 required />
2449 <button type="submit"
2450 name="submit_sitemap"
2451 class="mxchat-button-primary">
2452 <?php esc_html_e('Import', 'mxchat'); ?>
2453 </button>
2454 </div>
2455 <p class="mxchat-url-description" id="url-description-text"></p>
2456 </form>
2457 <?php endif; ?>
2458 </div>
2459
2460 <div class="mxchat-import-input-area" id="mxchat-content-input-area" style="display: none;">
2461 <?php if (!$is_processing) : ?>
2462 <form id="mxchat-content-form" method="post" action="<?php echo esc_url(admin_url('admin-post.php?action=mxchat_submit_content')); ?>">
2463 <?php wp_nonce_field('mxchat_submit_content_action', 'mxchat_submit_content_nonce'); ?>
2464 <?php if ($multibot_active && $current_bot_id !== 'default') : ?>
2465 <input type="hidden" name="bot_id" value="<?php echo esc_attr($current_bot_id); ?>">
2466 <?php endif; ?>
2467 <div class="mxchat-form-group">
2468 <textarea
2469 name="article_content"
2470 id="article_content"
2471 placeholder="<?php esc_attr_e('Enter your content here...', 'mxchat'); ?>"
2472 required
2473 rows="6"
2474 ></textarea>
2475 </div>
2476 <div class="mxchat-form-group">
2477 <input type="url"
2478 name="article_url"
2479 id="article_url"
2480 placeholder="<?php esc_attr_e('Enter source URL (Optional)', 'mxchat'); ?>">
2481 <div class="mxchat-url-helper" style="margin-top: 8px; display: flex; align-items: center; gap: 8px;">
2482 <label style="display: flex; align-items: center; gap: 6px; font-size: 13px; color: #666; cursor: pointer;">
2483 <input type="checkbox" id="mxchat-unique-url-toggle" style="margin: 0;">
2484 <?php esc_html_e('Generate unique URL for duplicate content', 'mxchat'); ?>
2485 </label>
2486 <button type="button"
2487 id="mxchat-generate-unique-url"
2488 class="mxchat-button-secondary"
2489 style="padding: 4px 12px; font-size: 12px; display: none;">
2490 <span class="dashicons dashicons-randomize" style="font-size: 14px; margin-top: 2px;"></span>
2491 <?php esc_html_e('Generate Unique', 'mxchat'); ?>
2492 </button>
2493 </div>
2494 <p class="description" style="margin-top: 8px; font-size: 12px; color: #666;">
2495 <?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'); ?>
2496 </p>
2497 </div>
2498 <button type="submit"
2499 name="submit_content"
2500 class="mxchat-button-primary">
2501 <?php esc_html_e('Import Content', 'mxchat'); ?>
2502 </button>
2503 </form>
2504 <?php endif; ?>
2505 </div>
2506 </div>
2507
2508 <!-- PDF Processing Status - ONLY PROCESSING -->
2509 <?php if ($pdf_status && $pdf_status['status'] === 'processing') : ?>
2510 <div class="mxchat-status-card" data-card-type="pdf">
2511 <div class="mxchat-status-header">
2512 <h4><?php esc_html_e('PDF Processing Status', 'mxchat'); ?></h4>
2513
2514 <!-- Processing controls -->
2515 <div class="mxchat-processing-controls">
2516 <form method="post" class="mxchat-stop-form"
2517 action="<?php echo esc_url(admin_url('admin-post.php?action=mxchat_stop_processing')); ?>">
2518 <?php wp_nonce_field('mxchat_stop_processing_action', 'mxchat_stop_processing_nonce'); ?>
2519 <button type="submit" name="stop_processing" class="mxchat-button-secondary">
2520 <?php esc_html_e('Stop Processing', 'mxchat'); ?>
2521 </button>
2522 </form>
2523
2524 <button type="button" class="mxchat-manual-batch-btn"
2525 data-process-type="pdf"
2526 data-url="<?php echo esc_attr(get_transient('mxchat_last_pdf_url')); ?>">
2527 <?php esc_html_e('Process Batch', 'mxchat'); ?>
2528 </button>
2529 </div>
2530 </div>
2531
2532 <div class="mxchat-progress-bar">
2533 <div class="mxchat-progress-fill" style="width: <?php echo esc_attr($pdf_status['percentage']); ?>%"></div>
2534 </div>
2535
2536 <div class="mxchat-status-details">
2537 <p><?php printf(
2538 esc_html__('Progress: %1$d of %2$d pages (%3$d%%)', 'mxchat'),
2539 absint($pdf_status['processed_pages']),
2540 absint($pdf_status['total_pages']),
2541 absint($pdf_status['percentage'])
2542 ); ?></p>
2543
2544 <?php if (!empty($pdf_status['failed_pages']) && $pdf_status['failed_pages'] > 0) : ?>
2545 <p><strong><?php esc_html_e('Failed pages:', 'mxchat'); ?></strong> <?php echo esc_html($pdf_status['failed_pages']); ?></p>
2546 <?php endif; ?>
2547
2548 <p><strong><?php esc_html_e('Status:', 'mxchat'); ?></strong> <?php echo esc_html(ucfirst($pdf_status['status'])); ?></p>
2549 <p><strong><?php esc_html_e('Last update:', 'mxchat'); ?></strong> <?php echo esc_html($pdf_status['last_update']); ?></p>
2550 </div>
2551 </div>
2552 <?php endif; ?>
2553
2554 <!-- Sitemap Processing Status - ONLY PROCESSING -->
2555 <?php if ($sitemap_status && $sitemap_status['status'] === 'processing') : ?>
2556 <div class="mxchat-status-card" data-card-type="sitemap">
2557 <div class="mxchat-status-header">
2558 <h4><?php esc_html_e('Sitemap Processing Status', 'mxchat'); ?></h4>
2559
2560 <!-- Processing controls -->
2561 <div class="mxchat-processing-controls">
2562 <form method="post" class="mxchat-stop-form"
2563 action="<?php echo esc_url(admin_url('admin-post.php?action=mxchat_stop_processing')); ?>">
2564 <?php wp_nonce_field('mxchat_stop_processing_action', 'mxchat_stop_processing_nonce'); ?>
2565 <button type="submit" name="stop_processing" class="mxchat-button-secondary">
2566 <?php esc_html_e('Stop Processing', 'mxchat'); ?>
2567 </button>
2568 </form>
2569
2570 <button type="button" class="mxchat-manual-batch-btn"
2571 data-process-type="sitemap"
2572 data-url="<?php echo esc_attr(get_transient('mxchat_last_sitemap_url')); ?>">
2573 <?php esc_html_e('Process Batch', 'mxchat'); ?>
2574 </button>
2575 </div>
2576 </div>
2577
2578 <div class="mxchat-progress-bar">
2579 <div class="mxchat-progress-fill" style="width: <?php echo esc_attr($sitemap_status['percentage']); ?>%"></div>
2580 </div>
2581
2582 <div class="mxchat-status-details">
2583 <p><?php printf(
2584 esc_html__('Progress: %1$d of %2$d URLs (%3$d%%)', 'mxchat'),
2585 absint($sitemap_status['processed_urls']),
2586 absint($sitemap_status['total_urls']),
2587 absint($sitemap_status['percentage'])
2588 ); ?></p>
2589
2590 <?php if (!empty($sitemap_status['failed_urls']) && $sitemap_status['failed_urls'] > 0) : ?>
2591 <p><strong><?php esc_html_e('Failed URLs:', 'mxchat'); ?></strong> <?php echo esc_html($sitemap_status['failed_urls']); ?></p>
2592 <?php endif; ?>
2593
2594 <p><strong><?php esc_html_e('Status:', 'mxchat'); ?></strong> <?php echo esc_html(ucfirst($sitemap_status['status'])); ?></p>
2595 <p><strong><?php esc_html_e('Last update:', 'mxchat'); ?></strong> <?php echo esc_html($sitemap_status['last_update']); ?></p>
2596
2597 <?php if (!empty($sitemap_status['error']) || !empty($sitemap_status['last_error'])) : ?>
2598 <div class="mxchat-error-notice">
2599 <?php if (!empty($sitemap_status['error'])) : ?>
2600 <p class="error"><?php echo esc_html($sitemap_status['error']); ?></p>
2601 <?php endif; ?>
2602 <?php if (!empty($sitemap_status['last_error'])) : ?>
2603 <p class="last-error"><?php echo esc_html__('Last error:', 'mxchat') . ' ' . esc_html($sitemap_status['last_error']); ?></p>
2604 <?php endif; ?>
2605 </div>
2606 <?php endif; ?>
2607 </div>
2608 </div>
2609 <?php endif; ?>
2610
2611 <!-- Single URL Submission Status -->
2612 <?php
2613 // Get single URL status
2614 $single_url_status = $this->knowledge_manager->mxchat_get_single_url_status();
2615 $is_active_processing =
2616 ($sitemap_status && $sitemap_status['status'] === 'processing') ||
2617 ($pdf_status && $pdf_status['status'] === 'processing');
2618 ?>
2619
2620 <div id="mxchat-single-url-status-container" <?php echo $is_active_processing ? 'style="display:none;"' : ''; ?>>
2621 <?php if ($single_url_status && !$is_active_processing) : ?>
2622 <div class="mxchat-status-card">
2623 <div class="mxchat-status-header">
2624 <h4><?php esc_html_e('Last URL Submission', 'mxchat'); ?></h4>
2625 <?php if ($single_url_status['status'] === 'failed') : ?>
2626 <span class="mxchat-status-badge mxchat-status-failed"><?php esc_html_e('Failed', 'mxchat'); ?></span>
2627 <?php else : ?>
2628 <span class="mxchat-status-badge mxchat-status-success"><?php esc_html_e('Success', 'mxchat'); ?></span>
2629 <?php endif; ?>
2630 </div>
2631 <div class="mxchat-status-details">
2632 <p><strong><?php esc_html_e('URL:', 'mxchat'); ?></strong>
2633 <a href="<?php echo esc_url($single_url_status['url']); ?>" target="_blank">
2634 <?php echo esc_html(strlen($single_url_status['url']) > 60 ? substr($single_url_status['url'], 0, 57) . '...' : $single_url_status['url']); ?>
2635 </a>
2636 </p>
2637 <p><strong><?php esc_html_e('Submitted:', 'mxchat'); ?></strong> <?php echo esc_html($single_url_status['human_time']); ?></p>
2638
2639 <?php if ($single_url_status['status'] === 'failed' && !empty($single_url_status['error'])) : ?>
2640 <div class="mxchat-error-notice">
2641 <p class="error"><?php echo esc_html($single_url_status['error']); ?></p>
2642 </div>
2643 <?php endif; ?>
2644
2645 <?php if ($single_url_status['status'] === 'complete') : ?>
2646 <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>
2647 <p><strong><?php esc_html_e('Embedding Dimensions:', 'mxchat'); ?></strong> <?php echo esc_html($single_url_status['embedding_dimensions']); ?></p>
2648 <?php endif; ?>
2649 </div>
2650 </div>
2651 <?php endif; ?>
2652 </div>
2653
2654 <!-- Completed Status Cards - Handles completed PDF/Sitemap processing -->
2655 <?php
2656 echo $knowledge_manager->mxchat_render_completed_status_cards();
2657 ?>
2658
2659 </div>
2660
2661 <!-- Knowledge Base Table Card -->
2662 <div class="mxchat-card">
2663 <div class="mxchat-card-header">
2664 <h2>
2665 <?php esc_html_e('Knowledge Base', 'mxchat'); ?>
2666 <span class="mxchat-record-count">
2667 <?php if ($showing_recent_only && $total_in_database > 1000): ?>
2668 (<?php echo esc_html($total_records); ?> of <?php echo esc_html(number_format($total_in_database)); ?> total - recent entries only)
2669 <?php else: ?>
2670 (<?php echo esc_html($total_records); ?>)
2671 <?php endif; ?>
2672 </span>
2673 <!-- Data source badge -->
2674 <?php if ($use_pinecone) : ?>
2675 <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;">
2676 <span class="dashicons dashicons-cloud"></span>
2677 <?php esc_html_e('Pinecone', 'mxchat'); ?>
2678 </span>
2679 <?php else : ?>
2680 <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;">
2681 <span class="dashicons dashicons-database-view"></span>
2682 <?php esc_html_e('WordPress DB', 'mxchat'); ?>
2683 </span>
2684 <?php endif; ?>
2685 </h2>
2686 <div class="mxchat-header-actions">
2687 <!-- Search -->
2688 <form method="get" id="knowledge-search" class="mxchat-search-form">
2689 <?php wp_nonce_field('mxchat_prompts_search_nonce'); ?>
2690 <input type="hidden" name="page" value="mxchat-prompts" />
2691 <?php if ($multibot_active && !empty($current_bot_id) && $current_bot_id !== 'default') : ?>
2692 <input type="hidden" name="bot_id" value="<?php echo esc_attr($current_bot_id); ?>" />
2693 <?php endif; ?>
2694 <div class="mxchat-search-group">
2695 <span class="dashicons dashicons-search"></span>
2696 <input type="text"
2697 name="search"
2698 placeholder="<?php esc_attr_e('Search Knowledge', 'mxchat'); ?>"
2699 value="<?php echo esc_attr($search_query); ?>" />
2700 </div>
2701
2702 <!-- ADDED 2.5.6: Content Type Filter -->
2703 <div class="mxchat-filter-group">
2704 <select name="content_type" id="mxchat-content-type-filter" onchange="this.form.submit()">
2705 <option value=""><?php esc_html_e('All Content Types', 'mxchat'); ?></option>
2706 <option value="post" <?php selected($content_type_filter, 'post'); ?>><?php esc_html_e('Posts', 'mxchat'); ?></option>
2707 <option value="page" <?php selected($content_type_filter, 'page'); ?>><?php esc_html_e('Pages', 'mxchat'); ?></option>
2708 <option value="product" <?php selected($content_type_filter, 'product'); ?>><?php esc_html_e('Products', 'mxchat'); ?></option>
2709 <option value="pdf" <?php selected($content_type_filter, 'pdf'); ?>><?php esc_html_e('PDFs', 'mxchat'); ?></option>
2710 <option value="url" <?php selected($content_type_filter, 'url'); ?>><?php esc_html_e('URLs', 'mxchat'); ?></option>
2711 <option value="content" <?php selected($content_type_filter, 'content'); ?>><?php esc_html_e('Manual Content', 'mxchat'); ?></option>
2712 </select>
2713 </div>
2714 </form>
2715
2716 <form method="post"
2717 action="<?php echo esc_url(admin_url('admin-post.php?action=mxchat_delete_all_prompts')); ?>"
2718 class="mxchat-delete-form"
2719 onsubmit="return confirm('<?php esc_attr_e('Are you sure you want to delete all knowledge? This action cannot be undone.', 'mxchat'); ?>');">
2720 <?php wp_nonce_field('mxchat_delete_all_prompts_action', 'mxchat_delete_all_prompts_nonce'); ?>
2721 <input type="hidden" name="data_source" value="<?php echo esc_attr($data_source); ?>" />
2722 <!-- Always include bot_id, even if it's 'default' -->
2723 <input type="hidden" name="bot_id" value="<?php echo esc_attr($current_bot_id); ?>" />
2724 <button type="submit" name="delete_all_prompts" class="mxchat-button-danger">
2725 <span class="dashicons dashicons-trash"></span>
2726 <?php esc_html_e('Delete All', 'mxchat'); ?>
2727 </button>
2728 </form>
2729 </div>
2730 </div>
2731
2732 <!-- Large Database Info Banner -->
2733 <?php if ($showing_recent_only && $total_in_database > 500): ?>
2734 <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;">
2735 <span class="dashicons dashicons-info"></span>
2736 <span>
2737 <?php printf(
2738 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'),
2739 number_format($total_records),
2740 number_format($total_in_database)
2741 ); ?>
2742 </span>
2743 </div>
2744 <?php endif; ?>
2745
2746 <!-- Data Source Info Banner -->
2747 <?php if ($use_pinecone) : ?>
2748 <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;">
2749 <span class="dashicons dashicons-cloud"></span>
2750 <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>
2751 </div>
2752 <?php else : ?>
2753 <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;">
2754 <span class="dashicons dashicons-database-view"></span>
2755 <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>
2756 </div>
2757 <?php endif; ?>
2758
2759 <!-- Table -->
2760 <?php
2761 // Group prompts by source_url for chunked content display
2762 $grouped_prompts = array();
2763 $ungrouped_prompts = array();
2764
2765 if ($prompts) {
2766 foreach ($prompts as $prompt) {
2767 $source_url = $prompt->source_url ?? '';
2768
2769 // Check if chunk metadata exists on the record itself (Pinecone)
2770 if (isset($prompt->chunk_index) && $prompt->chunk_index !== null) {
2771 // Pinecone: metadata is already on the record object
2772 $prompt->chunk_metadata = array(
2773 'chunk_index' => intval($prompt->chunk_index),
2774 'total_chunks' => isset($prompt->total_chunks) ? intval($prompt->total_chunks) : null,
2775 'is_chunked' => isset($prompt->is_chunked) ? (bool) $prompt->is_chunked : true
2776 );
2777 $prompt->display_content = $prompt->article_content; // Pinecone content is already clean
2778 } else {
2779 // WordPress: Parse chunk metadata from JSON prefix in article_content
2780 $chunk_meta = MxChat_Chunker::parse_stored_chunk($prompt->article_content);
2781 $prompt->chunk_metadata = $chunk_meta['metadata'];
2782 $prompt->display_content = $chunk_meta['text']; // Store clean content without JSON prefix
2783 }
2784
2785 if (!empty($source_url)) {
2786 if (!isset($grouped_prompts[$source_url])) {
2787 $grouped_prompts[$source_url] = array();
2788 }
2789 $grouped_prompts[$source_url][] = $prompt;
2790 } else {
2791 $ungrouped_prompts[] = $prompt;
2792 }
2793 }
2794
2795 // Sort each group by chunk_index from metadata
2796 foreach ($grouped_prompts as $source_url => &$group) {
2797 usort($group, function($a, $b) {
2798 $index_a = isset($a->chunk_metadata['chunk_index']) ? intval($a->chunk_metadata['chunk_index']) : 0;
2799 $index_b = isset($b->chunk_metadata['chunk_index']) ? intval($b->chunk_metadata['chunk_index']) : 0;
2800 return $index_a - $index_b;
2801 });
2802 }
2803 unset($group); // Break reference
2804 }
2805 $display_index = 0;
2806 ?>
2807 <div class="mxchat-table-wrapper">
2808 <table class="mxchat-records-table">
2809 <thead>
2810 <tr>
2811 <th><?php esc_html_e('ID', 'mxchat'); ?></th>
2812 <th><?php esc_html_e('Content', 'mxchat'); ?></th>
2813 <th><?php esc_html_e('Source', 'mxchat'); ?></th>
2814 <?php if ($data_source === 'pinecone') : ?>
2815 <th><?php esc_html_e('Vector ID', 'mxchat'); ?></th>
2816 <?php endif; ?>
2817 <th><?php esc_html_e('Actions', 'mxchat'); ?></th>
2818 </tr>
2819 </thead>
2820 <tbody>
2821 <?php if ($prompts) : ?>
2822 <?php
2823 // Display grouped prompts (content with same source_url)
2824 foreach ($grouped_prompts as $source_url => $group) :
2825 $chunk_count = count($group);
2826 $first_prompt = $group[0];
2827 $display_index++;
2828
2829 if ($chunk_count > 1) :
2830 // Multiple chunks - show grouped row with expand button
2831 $group_id = 'group-' . md5($source_url);
2832 ?>
2833 <tr id="prompt-<?php echo esc_attr($first_prompt->id); ?>"
2834 class="mxchat-chunk-group-header"
2835 data-source="<?php echo esc_attr($data_source); ?>"
2836 data-group-id="<?php echo esc_attr($group_id); ?>"
2837 <?php if ($data_source === 'pinecone') : ?>
2838 style="background: rgba(33, 150, 243, 0.02);"
2839 <?php endif; ?>>
2840 <td>
2841 <?php if ($data_source === 'pinecone') : ?>
2842 <?php echo esc_html($display_index + (($current_page - 1) * $per_page)); ?>
2843 <?php else : ?>
2844 <?php echo esc_html($first_prompt->id); ?>
2845 <?php endif; ?>
2846 </td>
2847 <td class="mxchat-content-cell">
2848 <div class="mxchat-chunk-group-info">
2849 <button type="button" class="mxchat-chunk-toggle" data-group-id="<?php echo esc_attr($group_id); ?>">
2850 <span class="dashicons dashicons-arrow-right-alt2"></span>
2851 </button>
2852 <span class="mxchat-chunk-badge"><?php echo esc_html($chunk_count); ?> <?php esc_html_e('chunks', 'mxchat'); ?></span>
2853 <span class="mxchat-chunk-preview">
2854 <?php
2855 // Use display_content (without JSON prefix) if available
2856 $parent_content = isset($first_prompt->display_content) ? $first_prompt->display_content : $first_prompt->article_content;
2857 $content_preview = mb_substr($parent_content, 0, 100);
2858 echo esc_html($content_preview . '...');
2859 ?>
2860 </span>
2861 </div>
2862 </td>
2863 <td class="mxchat-url-cell">
2864 <div class="url-view">
2865 <a href="<?php echo esc_url($source_url); ?>" target="_blank">
2866 <span class="dashicons dashicons-external"></span>
2867 <?php esc_html_e('View Source', 'mxchat'); ?>
2868 </a>
2869 </div>
2870 </td>
2871 <?php if ($data_source === 'pinecone') : ?>
2872 <td class="mxchat-vector-id-cell">
2873 <code style="background: #f5f5f5; padding: 2px 6px; border-radius: 3px; font-size: 11px;">
2874 <?php echo esc_html(substr($first_prompt->id, 0, 8) . '..._chunk_*'); ?>
2875 </code>
2876 </td>
2877 <?php endif; ?>
2878 <td class="mxchat-actions-cell">
2879 <div class="mxchat-role-restriction-container" style="margin-bottom: 8px;">
2880 <select class="mxchat-role-select mxchat-group-role-select"
2881 data-group-url="<?php echo esc_attr($source_url); ?>"
2882 data-data-source="<?php echo esc_attr($data_source); ?>"
2883 data-nonce="<?php echo wp_create_nonce('mxchat_update_role_nonce'); ?>">
2884 <?php
2885 $current_role = $first_prompt->role_restriction ?? 'public';
2886 $role_options = $knowledge_manager->mxchat_get_role_options();
2887 foreach ($role_options as $role_key => $role_label) : ?>
2888 <option value="<?php echo esc_attr($role_key); ?>"
2889 <?php selected($current_role, $role_key); ?>>
2890 <?php echo esc_html($role_label); ?>
2891 </option>
2892 <?php endforeach; ?>
2893 </select>
2894 </div>
2895 <div class="mxchat-delete-container">
2896 <button type="button"
2897 class="mxchat-button-icon delete-button-group"
2898 data-source-url="<?php echo esc_attr($source_url); ?>"
2899 data-chunk-count="<?php echo esc_attr($chunk_count); ?>"
2900 data-data-source="<?php echo esc_attr($data_source); ?>"
2901 data-bot-id="<?php echo esc_attr($current_bot_id); ?>"
2902 data-nonce="<?php echo wp_create_nonce('mxchat_delete_chunks_nonce'); ?>"
2903 title="<?php esc_attr_e('Delete all chunks', 'mxchat'); ?>">
2904 <span class="dashicons dashicons-trash"></span>
2905 </button>
2906 </div>
2907 </td>
2908 </tr>
2909 <?php
2910 // Render hidden chunk rows
2911 foreach ($group as $prompt) :
2912 // Get chunk index from metadata (0-based), default to array position
2913 $meta_chunk_index = isset($prompt->chunk_metadata['chunk_index']) ? intval($prompt->chunk_metadata['chunk_index']) : 0;
2914 $meta_total_chunks = isset($prompt->chunk_metadata['total_chunks']) ? intval($prompt->chunk_metadata['total_chunks']) : $chunk_count;
2915 ?>
2916 <tr id="prompt-<?php echo esc_attr($prompt->id); ?>"
2917 class="mxchat-chunk-row <?php echo esc_attr($group_id); ?>"
2918 data-source="<?php echo esc_attr($data_source); ?>"
2919 style="display: none; background: #f8f9fa;">
2920 <td style="padding-left: 30px;">
2921 <!-- Hidden ID column -->
2922 </td>
2923 <td class="mxchat-content-cell">
2924 <div class="mxchat-accordion-wrapper">
2925 <div class="mxchat-content-preview">
2926 <span class="mxchat-chunk-indicator" style="margin-right: 10px;">
2927 <?php printf(esc_html__('Chunk %d of %d', 'mxchat'), $meta_chunk_index + 1, $meta_total_chunks); ?>
2928 </span>
2929 <?php
2930 // Use display_content (without JSON prefix) if available
2931 $content = isset($prompt->display_content) ? $prompt->display_content : $prompt->article_content;
2932 $preview_length = 150;
2933 $content_preview = mb_strlen($content) > $preview_length
2934 ? mb_substr($content, 0, $preview_length) . '...'
2935 : $content;
2936 ?>
2937 <span class="preview-text"><?php echo esc_html($content_preview); ?></span>
2938 <?php if (mb_strlen($content) > $preview_length) : ?>
2939 <button class="mxchat-expand-toggle" type="button">
2940 <span class="dashicons dashicons-arrow-down-alt2"></span>
2941 </button>
2942 <?php endif; ?>
2943 </div>
2944 <div class="mxchat-content-full" style="display: none;">
2945 <div class="content-view">
2946 <?php
2947 if (preg_match('/[\x{0590}-\x{05FF}]/u', $content)) {
2948 echo '<div dir="rtl" lang="he" class="rtl-content">';
2949 echo wp_kses_post(wpautop($content));
2950 echo '</div>';
2951 } else {
2952 echo wp_kses_post(wpautop($content));
2953 }
2954 ?>
2955 </div>
2956 </div>
2957 </div>
2958 </td>
2959 <td class="mxchat-url-cell">
2960 <span class="mxchat-chunk-label"><?php esc_html_e('Same as parent', 'mxchat'); ?></span>
2961 </td>
2962 <?php if ($data_source === 'pinecone') : ?>
2963 <td class="mxchat-vector-id-cell">
2964 <code style="background: #f5f5f5; padding: 2px 6px; border-radius: 3px; font-size: 11px;">
2965 <?php echo esc_html(substr($prompt->id, 0, 12) . '...'); ?>
2966 </code>
2967 </td>
2968 <?php endif; ?>
2969 <td class="mxchat-actions-cell">
2970 <span class="mxchat-chunk-label"><?php esc_html_e('Managed by group', 'mxchat'); ?></span>
2971 </td>
2972 </tr>
2973 <?php endforeach; ?>
2974 <?php else :
2975 // Single entry - display normally
2976 $prompt = $first_prompt;
2977 $index = $display_index - 1;
2978 ?>
2979 <tr id="prompt-<?php echo esc_attr($prompt->id); ?>"
2980 data-source="<?php echo esc_attr($data_source); ?>"
2981 <?php if ($data_source === 'pinecone') : ?>
2982 style="background: rgba(33, 150, 243, 0.02);"
2983 <?php endif; ?>>
2984 <td>
2985 <?php if ($data_source === 'pinecone') : ?>
2986 <?php echo esc_html($index + 1 + (($current_page - 1) * $per_page)); ?>
2987 <?php else : ?>
2988 <?php echo esc_html($prompt->id); ?>
2989 <?php endif; ?>
2990 </td>
2991 <td class="mxchat-content-cell">
2992 <div class="mxchat-accordion-wrapper">
2993 <div class="mxchat-content-preview">
2994 <?php
2995 // Use display_content (without JSON prefix) if available
2996 $content = isset($prompt->display_content) ? $prompt->display_content : $prompt->article_content;
2997 $preview_length = 150;
2998 $content_preview = mb_strlen($content) > $preview_length
2999 ? mb_substr($content, 0, $preview_length) . '...'
3000 : $content;
3001 ?>
3002 <span class="preview-text"><?php echo esc_html($content_preview); ?></span>
3003 <?php if (mb_strlen($content) > $preview_length) : ?>
3004 <button class="mxchat-expand-toggle" type="button">
3005 <span class="dashicons dashicons-arrow-down-alt2"></span>
3006 </button>
3007 <?php endif; ?>
3008 </div>
3009 <div class="mxchat-content-full" style="display: none;">
3010 <div class="content-view">
3011 <?php
3012 // Check if content contains Hebrew characters
3013 if (preg_match('/[\x{0590}-\x{05FF}]/u', $content)) {
3014 // Apply RTL direction for Hebrew content
3015 echo '<div dir="rtl" lang="he" class="rtl-content">';
3016 echo wp_kses_post(wpautop($content));
3017 echo '</div>';
3018 } else {
3019 echo wp_kses_post(wpautop($content));
3020 }
3021 ?>
3022 </div>
3023 <?php if ($data_source === 'wordpress') : ?>
3024 <textarea class="content-edit" style="display:none;"
3025 <?php if (preg_match('/[\x{0590}-\x{05FF}]/u', $content)) echo 'dir="rtl" lang="he"'; ?>>
3026 <?php echo htmlspecialchars($content, ENT_QUOTES, 'UTF-8'); ?>
3027 </textarea>
3028 <?php endif; ?>
3029 </div>
3030 </div>
3031 </td>
3032 <td class="mxchat-url-cell">
3033 <div class="url-view">
3034 <?php if (!empty($prompt->source_url)) : ?>
3035 <a href="<?php echo esc_url($prompt->source_url); ?>" target="_blank">
3036 <span class="dashicons dashicons-external"></span>
3037 <?php esc_html_e('View Source', 'mxchat'); ?>
3038 </a>
3039 <?php else : ?>
3040 <span class="mxchat-na"><?php esc_html_e('N/A', 'mxchat'); ?></span>
3041 <?php endif; ?>
3042 </div>
3043 <?php if ($data_source === 'wordpress') : ?>
3044 <input type="text" class="url-edit" style="display:none;"
3045 value="<?php echo htmlspecialchars($prompt->source_url, ENT_QUOTES, 'UTF-8'); ?>" />
3046 <?php endif; ?>
3047 </td>
3048 <?php if ($data_source === 'pinecone') : ?>
3049 <td class="mxchat-vector-id-cell">
3050 <code style="background: #f5f5f5; padding: 2px 6px; border-radius: 3px; font-size: 11px;">
3051 <?php echo esc_html(substr($prompt->id, 0, 12) . '...'); ?>
3052 </code>
3053 </td>
3054 <?php endif; ?>
3055
3056 <td class="mxchat-actions-cell">
3057 <?php if ($data_source === 'wordpress') : ?>
3058 <!-- WordPress - Full editing capabilities -->
3059 <div class="mxchat-role-restriction-container" style="margin-bottom: 8px;">
3060 <select class="mxchat-role-select"
3061 data-entry-id="<?php echo esc_attr($prompt->id); ?>"
3062 data-data-source="wordpress"
3063 data-nonce="<?php echo wp_create_nonce('mxchat_update_role_nonce'); ?>">
3064 <?php
3065 $current_role = $prompt->role_restriction ?? 'public';
3066 $role_options = $knowledge_manager->mxchat_get_role_options();
3067 foreach ($role_options as $role_key => $role_label) : ?>
3068 <option value="<?php echo esc_attr($role_key); ?>"
3069 <?php selected($current_role, $role_key); ?>>
3070 <?php echo esc_html($role_label); ?>
3071 </option>
3072 <?php endforeach; ?>
3073 </select>
3074 </div>
3075
3076 <!-- Edit/Save Buttons -->
3077 <div class="mxchat-action-buttons">
3078 <button class="mxchat-button-icon edit-button"
3079 data-id="<?php echo esc_attr($prompt->id); ?>">
3080 <span class="dashicons dashicons-edit"></span>
3081 </button>
3082 <button class="mxchat-button-icon save-button"
3083 data-id="<?php echo esc_attr($prompt->id); ?>"
3084 style="display:none;"
3085 data-nonce="<?php echo wp_create_nonce('mxchat_save_inline_nonce'); ?>">
3086 <span class="dashicons dashicons-saved"></span>
3087 </button>
3088 </div>
3089 <?php else : ?>
3090 <!-- Pinecone - Role restriction editable, content read-only -->
3091 <div class="mxchat-role-restriction-container" style="margin-bottom: 8px;">
3092 <select class="mxchat-role-select"
3093 data-entry-id="<?php echo esc_attr($prompt->id); ?>"
3094 data-data-source="pinecone"
3095 data-nonce="<?php echo wp_create_nonce('mxchat_update_role_nonce'); ?>">
3096 <?php
3097 $current_role = $prompt->role_restriction ?? 'public';
3098 $role_options = $knowledge_manager->mxchat_get_role_options();
3099 foreach ($role_options as $role_key => $role_label) : ?>
3100 <option value="<?php echo esc_attr($role_key); ?>"
3101 <?php selected($current_role, $role_key); ?>>
3102 <?php echo esc_html($role_label); ?>
3103 </option>
3104 <?php endforeach; ?>
3105 </select>
3106 </div>
3107
3108 <?php endif; ?>
3109
3110 <!-- Delete button for both sources -->
3111 <div class="mxchat-delete-container">
3112 <?php if ($data_source === 'pinecone') : ?>
3113 <!-- AJAX Delete for Pinecone -->
3114 <button type="button"
3115 class="mxchat-button-icon delete-button-ajax"
3116 data-vector-id="<?php echo esc_attr($prompt->id); ?>"
3117 data-bot-id="<?php echo esc_attr($current_bot_id); ?>"
3118 data-nonce="<?php echo wp_create_nonce('mxchat_delete_pinecone_prompt_nonce'); ?>"
3119 title="<?php esc_attr_e('Delete entry', 'mxchat'); ?>">
3120 <span class="dashicons dashicons-trash"></span>
3121 </button>
3122 <?php else : ?>
3123 <!-- Regular link delete for WordPress DB -->
3124 <a href="<?php echo esc_url(admin_url(
3125 'admin-post.php?action=mxchat_delete_prompt&id=' . esc_attr($prompt->id)
3126 . '&_wpnonce=' . wp_create_nonce('mxchat_delete_prompt_nonce')
3127 )); ?>"
3128 class="mxchat-button-icon delete-button"
3129 onclick="return confirm('<?php esc_attr_e('Are you sure you want to delete this entry?', 'mxchat'); ?>');">
3130 <span class="dashicons dashicons-trash"></span>
3131 </a>
3132 <?php endif; ?>
3133 </div>
3134 </td>
3135
3136 </tr>
3137 <?php endif; ?>
3138 <?php endforeach; ?>
3139
3140 <?php
3141 // Display ungrouped prompts (content without source_url)
3142 foreach ($ungrouped_prompts as $prompt) :
3143 $display_index++;
3144 $index = $display_index - 1;
3145 ?>
3146 <tr id="prompt-<?php echo esc_attr($prompt->id); ?>"
3147 data-source="<?php echo esc_attr($data_source); ?>"
3148 <?php if ($data_source === 'pinecone') : ?>
3149 style="background: rgba(33, 150, 243, 0.02);"
3150 <?php endif; ?>>
3151 <td>
3152 <?php if ($data_source === 'pinecone') : ?>
3153 <?php echo esc_html($index + 1 + (($current_page - 1) * $per_page)); ?>
3154 <?php else : ?>
3155 <?php echo esc_html($prompt->id); ?>
3156 <?php endif; ?>
3157 </td>
3158 <td class="mxchat-content-cell">
3159 <div class="mxchat-accordion-wrapper">
3160 <div class="mxchat-content-preview">
3161 <?php
3162 // Use display_content (without JSON prefix) if available
3163 $content = isset($prompt->display_content) ? $prompt->display_content : $prompt->article_content;
3164 $preview_length = 150;
3165 $content_preview = mb_strlen($content) > $preview_length
3166 ? mb_substr($content, 0, $preview_length) . '...'
3167 : $content;
3168 ?>
3169 <span class="preview-text"><?php echo esc_html($content_preview); ?></span>
3170 <?php if (mb_strlen($content) > $preview_length) : ?>
3171 <button class="mxchat-expand-toggle" type="button">
3172 <span class="dashicons dashicons-arrow-down-alt2"></span>
3173 </button>
3174 <?php endif; ?>
3175 </div>
3176 <div class="mxchat-content-full" style="display: none;">
3177 <div class="content-view">
3178 <?php echo wp_kses_post(wpautop($content)); ?>
3179 </div>
3180 </div>
3181 </div>
3182 </td>
3183 <td class="mxchat-url-cell">
3184 <span class="mxchat-na"><?php esc_html_e('N/A', 'mxchat'); ?></span>
3185 </td>
3186 <?php if ($data_source === 'pinecone') : ?>
3187 <td class="mxchat-vector-id-cell">
3188 <code style="background: #f5f5f5; padding: 2px 6px; border-radius: 3px; font-size: 11px;">
3189 <?php echo esc_html(substr($prompt->id, 0, 12) . '...'); ?>
3190 </code>
3191 </td>
3192 <?php endif; ?>
3193 <td class="mxchat-actions-cell">
3194 <div class="mxchat-delete-container">
3195 <?php if ($data_source === 'pinecone') : ?>
3196 <button type="button"
3197 class="mxchat-button-icon delete-button-ajax"
3198 data-vector-id="<?php echo esc_attr($prompt->id); ?>"
3199 data-bot-id="<?php echo esc_attr($current_bot_id); ?>"
3200 data-nonce="<?php echo wp_create_nonce('mxchat_delete_pinecone_prompt_nonce'); ?>"
3201 title="<?php esc_attr_e('Delete entry', 'mxchat'); ?>">
3202 <span class="dashicons dashicons-trash"></span>
3203 </button>
3204 <?php else : ?>
3205 <a href="<?php echo esc_url(admin_url(
3206 'admin-post.php?action=mxchat_delete_prompt&id=' . esc_attr($prompt->id)
3207 . '&_wpnonce=' . wp_create_nonce('mxchat_delete_prompt_nonce')
3208 )); ?>"
3209 class="mxchat-button-icon delete-button"
3210 onclick="return confirm('<?php esc_attr_e('Are you sure you want to delete this entry?', 'mxchat'); ?>');">
3211 <span class="dashicons dashicons-trash"></span>
3212 </a>
3213 <?php endif; ?>
3214 </div>
3215 </td>
3216 </tr>
3217 <?php endforeach; ?>
3218 <?php else : ?>
3219 <tr>
3220 <td colspan="<?php echo $data_source === 'pinecone' ? '5' : '4'; ?>" class="mxchat-no-records">
3221 <?php if ($use_pinecone) : ?>
3222 <?php esc_html_e('No vectors found in Pinecone database.', 'mxchat'); ?>
3223 <?php else : ?>
3224 <?php esc_html_e('No knowledge base entries found.', 'mxchat'); ?>
3225 <?php endif; ?>
3226 </td>
3227 </tr>
3228 <?php endif; ?>
3229 </tbody>
3230 </table>
3231 </div>
3232
3233 <?php if ($page_links) : ?>
3234 <div class="mxchat-pagination">
3235 <?php echo wp_kses_post($page_links); ?>
3236 </div>
3237 <?php endif; ?>
3238 </div>
3239
3240 </div>
3241
3242 <!-- Settings Tab (Renamed from Auto-Sync Settings) -->
3243 <div id="mxchat-kb-tab-sync" class="mxchat-kb-tab-content">
3244
3245 <!-- Auto-Sync Settings Card -->
3246 <div class="mxchat-card">
3247 <div class="mxchat-settings-section">
3248 <h3><?php esc_html_e('Auto-Sync Settings', 'mxchat'); ?></h3>
3249 <p class="mxchat-description">
3250 <?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'); ?>
3251 </p>
3252 <div class="mxchat-autosave-section">
3253 <div class="mxchat-toggle-group">
3254 <div class="mxchat-toggle-container">
3255 <label class="mxchat-toggle-switch">
3256 <input type="checkbox"
3257 name="mxchat_auto_sync_posts"
3258 class="mxchat-autosave-field"
3259 value="1"
3260 data-nonce="<?php echo wp_create_nonce('mxchat_prompts_setting_nonce'); ?>"
3261 <?php checked(get_option('mxchat_auto_sync_posts', '0'), '1'); ?>>
3262 <span class="mxchat-toggle-slider"></span>
3263 </label>
3264 <span class="mxchat-toggle-label">
3265 <?php esc_html_e('Auto-sync Posts', 'mxchat'); ?>
3266 </span>
3267 </div>
3268
3269 <div class="mxchat-toggle-container">
3270 <label class="mxchat-toggle-switch">
3271 <input type="checkbox"
3272 name="mxchat_auto_sync_pages"
3273 class="mxchat-autosave-field"
3274 value="1"
3275 data-nonce="<?php echo wp_create_nonce('mxchat_prompts_setting_nonce'); ?>"
3276 <?php checked(get_option('mxchat_auto_sync_pages', '0'), '1'); ?>>
3277 <span class="mxchat-toggle-slider"></span>
3278 </label>
3279 <span class="mxchat-toggle-label">
3280 <?php esc_html_e('Auto-sync Pages', 'mxchat'); ?>
3281 </span>
3282 </div>
3283
3284 <!-- Custom Post Types Section -->
3285 <div class="mxchat-section-content">
3286 <div class="mxchat-custom-post-types-header">
3287 <button id="mxchat-custom-post-types-toggle" class="mxchat-button-secondary">
3288 <?php esc_html_e('Advanced Custom Post Sync Settings', 'mxchat'); ?>
3289 <span class="mxchat-toggle-icon">▼</span>
3290 </button>
3291 </div>
3292
3293 <div id="mxchat-custom-post-types-container" class="mxchat-custom-post-types-container" style="display: none;">
3294 <h3><?php esc_html_e('Sync Custom Post Types', 'mxchat'); ?></h3>
3295 <p><?php esc_html_e('Select additional custom post types to automatically sync with the chatbot.', 'mxchat'); ?></p>
3296
3297 <div class="mxchat-custom-post-types">
3298 <?php
3299 $post_types = $knowledge_manager->mxchat_get_public_post_types();
3300 // Skip post and page as they're handled separately
3301 unset($post_types['post']);
3302 unset($post_types['page']);
3303
3304 if (!empty($post_types)) {
3305 foreach ($post_types as $post_type => $label) {
3306 $option_name = 'mxchat_auto_sync_' . $post_type;
3307 $is_enabled = get_option($option_name, '0');
3308 ?>
3309 <div class="mxchat-toggle-container">
3310 <label class="mxchat-toggle-switch">
3311 <input type="checkbox"
3312 name="<?php echo esc_attr($option_name); ?>"
3313 class="mxchat-autosave-field"
3314 value="1"
3315 data-nonce="<?php echo wp_create_nonce('mxchat_prompts_setting_nonce'); ?>"
3316 <?php checked($is_enabled, '1'); ?>>
3317 <span class="mxchat-toggle-slider"></span>
3318 </label>
3319 <span class="mxchat-toggle-label">
3320 <?php echo esc_html($label); ?> (<?php echo esc_html($post_type); ?>)
3321 </span>
3322 </div>
3323 <?php
3324 }
3325 } else {
3326 echo '<p>' . esc_html__('No custom post types found.', 'mxchat') . '</p>';
3327 }
3328 ?>
3329 </div>
3330 </div>
3331 </div>
3332 </div>
3333 </div>
3334 </div>
3335 </div>
3336
3337 <!-- Content Chunking Settings Card -->
3338 <div class="mxchat-card" style="margin-top: 20px;">
3339 <div class="mxchat-settings-section">
3340 <h3><?php esc_html_e('Content Chunking Settings', 'mxchat'); ?></h3>
3341 <p class="mxchat-description">
3342 <?php esc_html_e('Chunking splits large content into smaller segments for more accurate semantic search. This improves retrieval quality for long documents. All chunks for a URL are reassembled before sending to the AI. Only applies to new submissions - entries added before v2.6.3 must be resubmitted to enable chunking.', 'mxchat'); ?>
3343 </p>
3344
3345 <?php
3346 // Get current chunking settings
3347 $chunking_settings = MxChat_Chunker::get_settings();
3348 $chunking_enabled = $chunking_settings['chunking_enabled'];
3349 $chunk_size = $chunking_settings['chunk_size'];
3350 ?>
3351
3352 <div class="mxchat-chunking-settings mxchat-autosave-section">
3353 <!-- Enable Chunking Toggle -->
3354 <div class="mxchat-toggle-container" style="margin-bottom: 20px;">
3355 <label class="mxchat-toggle-switch">
3356 <input type="checkbox"
3357 name="mxchat_chunking_enabled"
3358 id="mxchat_chunking_enabled"
3359 class="mxchat-autosave-field"
3360 value="1"
3361 data-option-name="mxchat_chunking_enabled"
3362 data-nonce="<?php echo wp_create_nonce('mxchat_prompts_setting_nonce'); ?>"
3363 <?php checked($chunking_enabled, true); ?>>
3364 <span class="mxchat-toggle-slider"></span>
3365 </label>
3366 <span class="mxchat-toggle-label">
3367 <?php esc_html_e('Enable Content Chunking', 'mxchat'); ?>
3368 </span>
3369 </div>
3370
3371 <!-- Chunk Size Setting -->
3372 <div class="mxchat-form-group" style="margin-bottom: 20px;">
3373 <label for="mxchat_chunk_size">
3374 <?php esc_html_e('Chunk Size (characters)', 'mxchat'); ?>
3375 </label>
3376 <input type="number"
3377 name="mxchat_chunk_size"
3378 id="mxchat_chunk_size"
3379 class="mxchat-autosave-field regular-text"
3380 value="<?php echo esc_attr($chunk_size); ?>"
3381 min="1000"
3382 max="10000"
3383 step="500"
3384 data-option-name="mxchat_chunk_size"
3385 data-nonce="<?php echo wp_create_nonce('mxchat_prompts_setting_nonce'); ?>"
3386 style="width: 150px;">
3387 <p class="description">
3388 <?php esc_html_e('Recommended: 4000 characters (~1000 tokens). Range: 1000-10000. Larger chunks preserve more context but are less precise for retrieval.', 'mxchat'); ?>
3389 </p>
3390 </div>
3391 </div>
3392 </div>
3393 </div>
3394
3395 <!-- Role-Based Content Restrictions Card -->
3396 <div class="mxchat-card" style="margin-top: 20px;">
3397 <h3><?php esc_html_e('Role-Based Content Restrictions', 'mxchat'); ?></h3>
3398 <p class="mxchat-description">
3399 <?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'); ?>
3400 </p>
3401
3402 <!-- Info Box -->
3403 <div class="mxchat-knowledge-warning info" style="background: #e3f2fd; border-left-color: #2196f3;">
3404 <p>
3405 <span class="dashicons dashicons-info"></span>
3406 <strong><?php esc_html_e('How it works:', 'mxchat'); ?></strong>
3407 <?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'); ?>
3408 </p>
3409 </div>
3410
3411 <!-- Add New Tag-Role Mapping -->
3412 <div class="mxchat-role-mapping-form">
3413 <h4><?php esc_html_e('Add Tag-Role Mapping', 'mxchat'); ?></h4>
3414
3415 <div class="mxchat-form-row" style="display: flex; gap: 15px; align-items: flex-end; margin-bottom: 20px;">
3416 <div class="mxchat-form-group" style="flex: 1;">
3417 <label for="mxchat-tag-input">
3418 <?php esc_html_e('Tag Name', 'mxchat'); ?>
3419 </label>
3420 <input type="text"
3421 id="mxchat-tag-input"
3422 class="regular-text"
3423 placeholder="<?php esc_attr_e('Enter tag name (e.g., premium, members-only)', 'mxchat'); ?>" />
3424 <p class="description">
3425 <?php esc_html_e('Enter the exact tag slug as it appears in WordPress', 'mxchat'); ?>
3426 </p>
3427 </div>
3428
3429 <div class="mxchat-form-group" style="flex: 1;">
3430 <label for="mxchat-role-select">
3431 <?php esc_html_e('Required Role', 'mxchat'); ?>
3432 </label>
3433 <select id="mxchat-role-select" class="regular-text">
3434 <?php
3435 // Get role options from knowledge manager
3436 $role_options = array(
3437 'public' => __('Public (Everyone)', 'mxchat'),
3438 'logged_in' => __('Logged In Users', 'mxchat'),
3439 'subscriber' => __('Subscribers & Above', 'mxchat'),
3440 'contributor' => __('Contributors & Above', 'mxchat'),
3441 'author' => __('Authors & Above', 'mxchat'),
3442 'editor' => __('Editors & Above', 'mxchat'),
3443 'administrator' => __('Administrators Only', 'mxchat')
3444 );
3445
3446 foreach ($role_options as $role_key => $role_label) : ?>
3447 <option value="<?php echo esc_attr($role_key); ?>">
3448 <?php echo esc_html($role_label); ?>
3449 </option>
3450 <?php endforeach; ?>
3451 </select>
3452 <p class="description">
3453 <?php esc_html_e('Select the minimum role required to access this content', 'mxchat'); ?>
3454 </p>
3455 </div>
3456
3457 <div style="padding-bottom: 25px;">
3458 <button type="button"
3459 id="mxchat-add-tag-role"
3460 class="mxchat-button-primary">
3461 <span class="dashicons dashicons-plus-alt"></span>
3462 <?php esc_html_e('Add Mapping', 'mxchat'); ?>
3463 </button>
3464 </div>
3465 </div>
3466 </div>
3467
3468 <!-- Existing Tag-Role Mappings -->
3469 <div class="mxchat-role-mappings-list">
3470 <h4><?php esc_html_e('Current Tag-Role Mappings', 'mxchat'); ?></h4>
3471
3472 <div id="mxchat-mappings-container">
3473 <!-- Mappings will be loaded here via AJAX -->
3474 <div class="mxchat-loading-mappings">
3475 <span class="mxchat-spinner is-active"></span>
3476 <?php esc_html_e('Loading mappings...', 'mxchat'); ?>
3477 </div>
3478 </div>
3479
3480 <!-- Empty State (initially hidden) -->
3481 <div id="mxchat-no-mappings" style="display: none; text-align: center; padding: 40px; color: #666;">
3482 <span class="dashicons dashicons-tag" style="font-size: 48px; opacity: 0.3;"></span>
3483 <p><?php esc_html_e('No tag-role mappings yet. Add your first mapping above to get started.', 'mxchat'); ?></p>
3484 </div>
3485 </div>
3486
3487 <!-- Bulk Update Section -->
3488 <div class="mxchat-bulk-update-section" style="margin-top: 30px; padding-top: 30px; border-top: 1px solid #ddd;">
3489 <h4><?php esc_html_e('Bulk Update Existing Content', 'mxchat'); ?></h4>
3490 <p class="mxchat-description">
3491 <?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'); ?>
3492 </p>
3493
3494 <div class="mxchat-bulk-update-controls">
3495 <button type="button"
3496 id="mxchat-bulk-update-roles"
3497 class="mxchat-button-secondary">
3498 <span class="dashicons dashicons-update"></span>
3499 <?php esc_html_e('Update All Existing Content', 'mxchat'); ?>
3500 </button>
3501
3502 <div id="mxchat-bulk-update-progress" style="display: none; margin-top: 15px;">
3503 <div class="mxchat-progress-bar">
3504 <div class="mxchat-progress-fill" style="width: 0%"></div>
3505 </div>
3506 <p class="mxchat-progress-text" style="margin-top: 10px; color: #666;">
3507 <?php esc_html_e('Processing...', 'mxchat'); ?>
3508 </p>
3509 </div>
3510
3511 <div id="mxchat-bulk-update-result" style="display: none; margin-top: 15px;"></div>
3512 </div>
3513 </div>
3514 </div>
3515
3516 </div>
3517
3518 <!-- Pinecone Settings Tab -->
3519 <div id="mxchat-kb-tab-pinecone" class="mxchat-kb-tab-content mxchat-autosave-section">
3520 <div class="mxchat-card">
3521 <h2><?php esc_html_e('Pinecone Vector Database Settings', 'mxchat'); ?></h2>
3522 <p class="mxchat-description">
3523 <?php echo wp_kses(
3524 __('<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'),
3525 array('strong' => array())
3526 ); ?>
3527 </p>
3528
3529 <div class="mxchat-pinecone-info-box">
3530 <div class="mxchat-info-icon">
3531 <span class="dashicons dashicons-info"></span>
3532 </div>
3533 <div class="mxchat-info-content">
3534 <h4><?php esc_html_e('What is Pinecone?', 'mxchat'); ?></h4>
3535 <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>
3536 <p><a href="https://www.pinecone.io/" target="_blank" rel="noopener noreferrer"><?php esc_html_e('Learn more about Pinecone →', 'mxchat'); ?></a></p>
3537 </div>
3538 </div>
3539
3540 <div class="mxchat-database-settings-form">
3541 <?php
3542 $pinecone_options = get_option('mxchat_pinecone_addon_options', array());
3543 $use_pinecone = $pinecone_options['mxchat_use_pinecone'] ?? '0';
3544 ?>
3545
3546 <div class="mxchat-toggle-container">
3547 <label class="mxchat-toggle-switch">
3548 <input type="checkbox"
3549 name="mxchat_pinecone_addon_options[mxchat_use_pinecone]"
3550 value="1"
3551 <?php checked($use_pinecone, '1'); ?>>
3552 <span class="mxchat-toggle-slider"></span>
3553 </label>
3554 <span class="mxchat-toggle-label">
3555 <?php esc_html_e('Enable Pinecone Database', 'mxchat'); ?>
3556 </span>
3557 </div>
3558
3559 <div class="mxchat-pinecone-settings" <?php echo $use_pinecone ? '' : 'style="display: none;"'; ?>>
3560
3561 <?php if ($use_pinecone) : ?>
3562 <div class="mxchat-knowledge-warning success">
3563 <p><span class="dashicons dashicons-yes-alt"></span>
3564 <?php esc_html_e('Pinecone is enabled. All new knowledge base content will be stored in Pinecone.', 'mxchat'); ?>
3565 </p>
3566 </div>
3567 <?php endif; ?>
3568
3569 <div class="mxchat-form-group">
3570 <label for="mxchat_pinecone_api_key">
3571 <?php esc_html_e('Pinecone API Key', 'mxchat'); ?> <span class="required">*</span>
3572 </label>
3573 <input type="password"
3574 id="mxchat_pinecone_api_key"
3575 name="mxchat_pinecone_addon_options[mxchat_pinecone_api_key]"
3576 value="<?php echo esc_attr($pinecone_options['mxchat_pinecone_api_key'] ?? ''); ?>"
3577 class="regular-text"
3578 placeholder="pcsk_..." />
3579 <p class="description">
3580 <?php esc_html_e('Found in your Pinecone dashboard under API Keys.', 'mxchat'); ?>
3581 <a href="https://app.pinecone.io/" target="_blank" rel="noopener noreferrer"><?php esc_html_e('Open Pinecone Dashboard', 'mxchat'); ?></a>
3582 </p>
3583 </div>
3584
3585 <div class="mxchat-form-group">
3586 <label for="mxchat_pinecone_environment">
3587 <?php esc_html_e('Region', 'mxchat'); ?>
3588 </label>
3589 <input type="text"
3590 id="mxchat_pinecone_environment"
3591 name="mxchat_pinecone_addon_options[mxchat_pinecone_environment]"
3592 value="<?php echo esc_attr($pinecone_options['mxchat_pinecone_environment'] ?? ''); ?>"
3593 placeholder="e.g., gcp-starter"
3594 class="regular-text" />
3595 <p class="description">
3596 <?php esc_html_e('Your Pinecone environment/region (e.g., gcp-starter, us-west1-gcp, us-east-1-aws)', 'mxchat'); ?>
3597 </p>
3598 </div>
3599
3600 <div class="mxchat-form-group">
3601 <label for="mxchat_pinecone_index">
3602 <?php esc_html_e('Index Name', 'mxchat'); ?> <span class="required">*</span>
3603 </label>
3604 <input type="text"
3605 id="mxchat_pinecone_index"
3606 name="mxchat_pinecone_addon_options[mxchat_pinecone_index]"
3607 value="<?php echo esc_attr($pinecone_options['mxchat_pinecone_index'] ?? ''); ?>"
3608 placeholder="e.g., my-wordpress-vectors"
3609 class="regular-text" />
3610 <p class="description">
3611 <?php esc_html_e('The name of your Pinecone index. Must be created in your Pinecone dashboard first.', 'mxchat'); ?>
3612 </p>
3613 </div>
3614
3615 <div class="mxchat-form-group">
3616 <label for="mxchat_pinecone_host">
3617 <?php esc_html_e('Pinecone Host', 'mxchat'); ?> <span class="required">*</span>
3618 </label>
3619 <input type="text"
3620 id="mxchat_pinecone_host"
3621 name="mxchat_pinecone_addon_options[mxchat_pinecone_host]"
3622 value="<?php echo esc_attr($pinecone_options['mxchat_pinecone_host'] ?? ''); ?>"
3623 placeholder="e.g., my-index-xyz123.svc.pinecone.io"
3624 class="regular-text" />
3625 <p class="description">
3626 <?php esc_html_e('The hostname from your Pinecone index URL (exclude https://). Found in your index details.', 'mxchat'); ?>
3627 </p>
3628 </div>
3629
3630 <div class="mxchat-setup-steps">
3631 <h3><?php esc_html_e('Setup Instructions', 'mxchat'); ?></h3>
3632 <div class="mxchat-setup-step">
3633 <span class="step-number">1</span>
3634 <div class="step-content">
3635 <h4><?php esc_html_e('Create Account', 'mxchat'); ?></h4>
3636 <p><?php esc_html_e('Create a free account at', 'mxchat'); ?> <a href="https://www.pinecone.io/" target="_blank">pinecone.io</a></p>
3637 </div>
3638 </div>
3639 <div class="mxchat-setup-step">
3640 <span class="step-number">2</span>
3641 <div class="step-content">
3642 <h4><?php esc_html_e('Create Index', 'mxchat'); ?></h4>
3643 <p><?php esc_html_e('Create a new index with these settings:', 'mxchat'); ?></p>
3644 <ul>
3645 <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>
3646 <li><?php esc_html_e('Metric: Cosine', 'mxchat'); ?></li>
3647 <li><?php esc_html_e('Cloud & Region: Your preferred region', 'mxchat'); ?></li>
3648 </ul>
3649 </div>
3650 </div>
3651 <div class="mxchat-setup-step">
3652 <span class="step-number">3</span>
3653 <div class="step-content">
3654 <h4><?php esc_html_e('Get API Key', 'mxchat'); ?></h4>
3655 <p><?php esc_html_e('Copy your API key from the API Keys section', 'mxchat'); ?></p>
3656 </div>
3657 </div>
3658 <div class="mxchat-setup-step">
3659 <span class="step-number">4</span>
3660 <div class="step-content">
3661 <h4><?php esc_html_e('Get Index Host', 'mxchat'); ?></h4>
3662 <p><?php esc_html_e('Copy your index host URL from the index details', 'mxchat'); ?></p>
3663 </div>
3664 </div>
3665 <div class="mxchat-setup-step">
3666 <span class="step-number">5</span>
3667 <div class="step-content">
3668 <h4><?php esc_html_e('Save Settings', 'mxchat'); ?></h4>
3669 <p><?php esc_html_e('Fill in the form above and save settings', 'mxchat'); ?></p>
3670 </div>
3671 </div>
3672 </div>
3673
3674 </div>
3675
3676 </div>
3677 </div>
3678 </div>
3679
3680 </div>
3681
3682 </div>
3683 </div>
3684
3685 <!-- Content Selector Modal -->
3686 <div id="mxchat-kb-content-selector-modal" class="mxchat-kb-modal">
3687 <div class="mxchat-kb-modal-content">
3688 <div class="mxchat-kb-modal-header">
3689 <h3>
3690 <?php esc_html_e('Select WordPress Content', 'mxchat'); ?><br>
3691 <span class="mxchat-kb-header-note"><?php esc_html_e('(Content imported here will be tagged "In Knowledge Base")', 'mxchat'); ?></span>
3692 </h3>
3693 <span class="mxchat-kb-modal-close">&times;</span>
3694 </div>
3695 <div class="mxchat-kb-modal-filters">
3696 <div class="mxchat-kb-search-group">
3697 <input type="text" id="mxchat-kb-content-search" placeholder="<?php esc_attr_e('Search...', 'mxchat'); ?>">
3698 </div>
3699
3700 <div class="mxchat-kb-filter-group">
3701 <select id="mxchat-kb-content-type-filter">
3702 <option value="all"><?php esc_html_e('All Content Types', 'mxchat'); ?></option>
3703 <option value="post"><?php esc_html_e('Posts', 'mxchat'); ?></option>
3704 <option value="page"><?php esc_html_e('Pages', 'mxchat'); ?></option>
3705 <?php
3706 // Add other post types dynamically
3707 $post_types = get_post_types(array('public' => true), 'objects');
3708 foreach ($post_types as $post_type) {
3709 // Skip post and page as they're already added
3710 if (!in_array($post_type->name, array('post', 'page'))) {
3711 echo '<option value="' . esc_attr($post_type->name) . '">' . esc_html($post_type->label) . '</option>';
3712 }
3713 }
3714 ?>
3715 </select>
3716
3717 <select id="mxchat-kb-content-status-filter">
3718 <option value="publish"><?php esc_html_e('Published', 'mxchat'); ?></option>
3719 <option value="draft"><?php esc_html_e('Drafts', 'mxchat'); ?></option>
3720 <option value="all"><?php esc_html_e('All Statuses', 'mxchat'); ?></option>
3721 </select>
3722
3723 <select id="mxchat-kb-processed-filter">
3724 <option value="all"><?php esc_html_e('All Content', 'mxchat'); ?></option>
3725 <option value="processed"><?php esc_html_e('In Knowledge Base', 'mxchat'); ?></option>
3726 <option value="unprocessed"><?php esc_html_e('Not In Knowledge Base', 'mxchat'); ?></option>
3727 </select>
3728 </div>
3729 </div>
3730
3731 <div class="mxchat-kb-content-selection">
3732 <div class="mxchat-kb-selection-header">
3733 <label>
3734 <input type="checkbox" id="mxchat-kb-select-all">
3735 <?php esc_html_e('Select All', 'mxchat'); ?>
3736 </label>
3737 <span class="mxchat-kb-selection-count">0 <?php esc_html_e('selected', 'mxchat'); ?></span>
3738 </div>
3739
3740 <div class="mxchat-kb-content-list">
3741 <!-- Content will be loaded here via AJAX -->
3742 <div class="mxchat-kb-loading">
3743 <span class="mxchat-kb-spinner is-active"></span>
3744 <?php esc_html_e('Loading content...', 'mxchat'); ?>
3745 </div>
3746 </div>
3747
3748 <div class="mxchat-kb-pagination">
3749 <!-- Pagination will be added here -->
3750 </div>
3751 </div>
3752
3753 <div class="mxchat-kb-modal-footer">
3754 <button type="button" id="mxchat-kb-process-selected" class="mxchat-kb-button-primary" disabled>
3755 <?php esc_html_e('Process Selected Content', 'mxchat'); ?>
3756 <span class="mxchat-kb-selected-count">(0)</span>
3757 </button>
3758 <button type="button" class="mxchat-kb-button-secondary mxchat-kb-modal-close">
3759 <?php esc_html_e('Cancel', 'mxchat'); ?>
3760 </button>
3761 </div>
3762 </div>
3763 </div>
3764
3765 <?php
3766 }
3767
3768
3769
3770
3771
3772
3773 /**
3774 * Get bot-specific Pinecone configuration
3775 * Used in the knowledge retrieval functions
3776 */
3777 private function get_bot_pinecone_config($bot_id = 'default') {
3778 //error_log("MXCHAT DEBUG: get_bot_pinecone_config called for bot: " . $bot_id);
3779
3780 // If default bot or multi-bot add-on not active, use default Pinecone config
3781 if ($bot_id === 'default' || !class_exists('MxChat_Multi_Bot_Manager')) {
3782 //error_log("MXCHAT DEBUG: Using default Pinecone config (no multi-bot or bot is 'default')");
3783 $addon_options = get_option('mxchat_pinecone_addon_options', array());
3784 $config = array(
3785 'use_pinecone' => (isset($addon_options['mxchat_use_pinecone']) && $addon_options['mxchat_use_pinecone'] === '1'),
3786 'api_key' => $addon_options['mxchat_pinecone_api_key'] ?? '',
3787 'host' => $addon_options['mxchat_pinecone_host'] ?? '',
3788 'namespace' => $addon_options['mxchat_pinecone_namespace'] ?? ''
3789 );
3790 //error_log("MXCHAT DEBUG: Default config - use_pinecone: " . ($config['use_pinecone'] ? 'true' : 'false'));
3791 return $config;
3792 }
3793
3794 //error_log("MXCHAT DEBUG: Calling filter 'mxchat_get_bot_pinecone_config' for bot: " . $bot_id);
3795
3796 // Hook for multi-bot add-on to provide bot-specific Pinecone config
3797 $bot_pinecone_config = apply_filters('mxchat_get_bot_pinecone_config', array(), $bot_id);
3798
3799 if (!empty($bot_pinecone_config)) {
3800 //error_log("MXCHAT DEBUG: Got bot-specific config from filter");
3801 //error_log(" - use_pinecone: " . (isset($bot_pinecone_config['use_pinecone']) ? ($bot_pinecone_config['use_pinecone'] ? 'true' : 'false') : 'not set'));
3802 //error_log(" - host: " . ($bot_pinecone_config['host'] ?? 'not set'));
3803 //error_log(" - namespace: " . ($bot_pinecone_config['namespace'] ?? 'not set'));
3804 } else {
3805 //error_log("MXCHAT DEBUG: Filter returned empty config!");
3806 }
3807
3808 return is_array($bot_pinecone_config) ? $bot_pinecone_config : array();
3809 }
3810
3811 public function mxchat_delete_chat_history() {
3812 if (!current_user_can('manage_options')) {
3813 echo wp_json_encode(['error' => esc_html__('You do not have sufficient permissions.', 'mxchat')]);
3814 wp_die();
3815 }
3816 check_ajax_referer('mxchat_delete_chat_history', 'security');
3817 global $wpdb;
3818 $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
3819
3820 if (isset($_POST['delete_session_ids']) && is_array($_POST['delete_session_ids'])) {
3821 $deleted_count = 0;
3822
3823 foreach ($_POST['delete_session_ids'] as $session_id) {
3824 $session_id_sanitized = sanitize_text_field($session_id);
3825
3826 // Clear relevant cache before deletion
3827 $cache_key = 'chat_session_' . $session_id_sanitized;
3828 wp_cache_delete($cache_key, 'mxchat_chat_sessions');
3829
3830 // Perform the deletion from the database table
3831 $wpdb->delete($table_name, ['session_id' => $session_id_sanitized]);
3832
3833 // Delete the corresponding option entry from wp_options table
3834 delete_option("mxchat_history_" . $session_id_sanitized);
3835
3836 // Delete any associated metadata options
3837 delete_option("mxchat_email_" . $session_id_sanitized);
3838 delete_option("mxchat_agent_name_" . $session_id_sanitized);
3839
3840 $deleted_count++;
3841 }
3842
3843 // Optionally, clear a general cache if you have one
3844 wp_cache_delete('all_chat_sessions', 'mxchat_chat_sessions');
3845
3846 echo wp_json_encode([
3847 'success' => sprintf(
3848 esc_html__('%d chat session(s) have been deleted from all storage locations.', 'mxchat'),
3849 $deleted_count
3850 )
3851 ]);
3852 } else {
3853 echo wp_json_encode(['error' => esc_html__('No chat sessions selected for deletion.', 'mxchat')]);
3854 }
3855
3856 wp_die();
3857 }
3858
3859 /**
3860 * Format transcript message content with markdown processing
3861 * Converts markdown links, bold, italic, code blocks, and plain URLs to HTML
3862 */
3863 private function format_transcript_message($text) {
3864 if (empty($text)) {
3865 return '';
3866 }
3867
3868 // Normalize line endings and clean up excessive whitespace
3869 $text = str_replace("\r\n", "\n", $text);
3870 $text = str_replace("\r", "\n", $text);
3871
3872 // Clean up existing <br> tags that may have been saved (legacy data)
3873 // Convert <br>, <br/>, <br /> back to newlines for consistent processing
3874 $text = preg_replace('/<br\s*\/?>\s*/i', "\n", $text);
3875
3876 // Collapse 3+ consecutive newlines to just 2 (paragraph break)
3877 $text = preg_replace('/\n{3,}/', "\n\n", $text);
3878
3879 // Process markdown headers (# Header)
3880 $text = preg_replace_callback('/^(#{1,6})\s+(.+)$/m', function($matches) {
3881 $level = strlen($matches[1]);
3882 $content = esc_html(trim($matches[2]));
3883 return "<h{$level}>{$content}</h{$level}>";
3884 }, $text);
3885
3886 // Process code blocks with triple backticks
3887 $text = preg_replace_callback('/```(\w+)?\n?([\s\S]*?)```/', function($matches) {
3888 $language = !empty($matches[1]) ? ' class="language-' . esc_attr($matches[1]) . '"' : '';
3889 $code = esc_html($matches[2]);
3890 return "<pre><code{$language}>{$code}</code></pre>";
3891 }, $text);
3892
3893 // Process inline code with single backticks
3894 $text = preg_replace('/`([^`]+)`/', '<code>$1</code>', $text);
3895
3896 // Process bold text **text** or __text__
3897 $text = preg_replace('/\*\*(.+?)\*\*/', '<strong>$1</strong>', $text);
3898 $text = preg_replace('/__(.+?)__/', '<strong>$1</strong>', $text);
3899
3900 // Process italic text *text* or _text_ (but not if part of URL)
3901 $text = preg_replace('/(?<![*_\w])\*([^*]+)\*(?![*\w])/', '<em>$1</em>', $text);
3902 $text = preg_replace('/(?<![*_\w])_([^_]+)_(?![*\w])/', '<em>$1</em>', $text);
3903
3904 // Process markdown links [text](url)
3905 $text = preg_replace_callback('/\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/', function($matches) {
3906 $link_text = esc_html($matches[1]);
3907 $url = esc_url($matches[2]);
3908 return "<a href=\"{$url}\" target=\"_blank\" rel=\"noopener\">{$link_text}</a>";
3909 }, $text);
3910
3911 // Process citation-style brackets [URL]
3912 $text = preg_replace_callback('/\[(https?:\/\/[^\]]+)\]/', function($matches) {
3913 $url = esc_url($matches[1]);
3914 return "<a href=\"{$url}\" target=\"_blank\" rel=\"noopener\">{$url}</a>";
3915 }, $text);
3916
3917 // Process standalone URLs (not already in links)
3918 $text = preg_replace_callback(
3919 '/(?<!href="|">)(https?:\/\/[^\s<>"]+)(?![^<]*<\/a>)/',
3920 function($matches) {
3921 $url = esc_url($matches[1]);
3922 // Truncate display URL if too long
3923 $display = strlen($matches[1]) > 50 ? substr($matches[1], 0, 47) . '...' : $matches[1];
3924 return "<a href=\"{$url}\" target=\"_blank\" rel=\"noopener\">{$display}</a>";
3925 },
3926 $text
3927 );
3928
3929 // Process mailto links
3930 $text = preg_replace_callback('/\[([^\]]+)\]\((mailto:[^)]+)\)/', function($matches) {
3931 $link_text = esc_html($matches[1]);
3932 $mailto = esc_url($matches[2]);
3933 return "<a href=\"{$mailto}\">{$link_text}</a>";
3934 }, $text);
3935
3936 // Convert paragraphs: split by double newlines, wrap in <p> tags
3937 // This creates proper paragraph structure instead of excessive <br> tags
3938 $paragraphs = preg_split('/\n\n+/', $text);
3939
3940 // Filter out empty paragraphs but preserve content like "0"
3941 $paragraphs = array_values(array_filter(array_map('trim', $paragraphs), function($p) {
3942 return $p !== '';
3943 }));
3944
3945 if (empty($paragraphs)) {
3946 // No content after filtering
3947 return '';
3948 } elseif (count($paragraphs) > 1) {
3949 // Multiple paragraphs - wrap each in <p> tags, convert single newlines to <br>
3950 $formatted_paragraphs = array_map(function($p) {
3951 return nl2br($p);
3952 }, $paragraphs);
3953 $text = '<p>' . implode('</p><p>', $formatted_paragraphs) . '</p>';
3954 } else {
3955 // Single paragraph - just convert newlines to <br>
3956 $text = nl2br($paragraphs[0]);
3957 }
3958
3959 return $text;
3960 }
3961
3962 /**
3963 * AJAX handler to fetch RAG context for a specific message
3964 * Used by the transcript viewer to show retrieved documents
3965 */
3966 public function mxchat_get_rag_context() {
3967 // Check permissions
3968 if (!current_user_can('manage_options')) {
3969 wp_send_json_error(['message' => esc_html__('You do not have sufficient permissions.', 'mxchat')]);
3970 wp_die();
3971 }
3972
3973 // Validate message ID
3974 if (!isset($_POST['message_id']) || empty($_POST['message_id'])) {
3975 wp_send_json_error(['message' => esc_html__('Message ID is required.', 'mxchat')]);
3976 wp_die();
3977 }
3978
3979 $message_id = absint($_POST['message_id']);
3980
3981 global $wpdb;
3982 $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
3983
3984 // Fetch the RAG context for this message
3985 $result = $wpdb->get_row($wpdb->prepare(
3986 "SELECT rag_context FROM {$table_name} WHERE id = %d",
3987 $message_id
3988 ));
3989
3990 if (!$result || empty($result->rag_context)) {
3991 wp_send_json_error(['message' => esc_html__('No RAG context found for this message.', 'mxchat')]);
3992 wp_die();
3993 }
3994
3995 // Decode the JSON data
3996 $rag_context = json_decode($result->rag_context, true);
3997
3998 if (json_last_error() !== JSON_ERROR_NONE) {
3999 wp_send_json_error(['message' => esc_html__('Invalid RAG context data.', 'mxchat')]);
4000 wp_die();
4001 }
4002
4003 wp_send_json_success($rag_context);
4004 wp_die();
4005 }
4006
4007 public function display_admin_notices() {
4008 // Check if we're on a MXChat admin page
4009 $screen = get_current_screen();
4010 if (!$screen || strpos($screen->base, 'mxchat') === false) {
4011 return;
4012 }
4013
4014 //error_log('MxChat admin_notices hook fired on screen: ' . $screen->base);
4015
4016 // Check for error notices
4017 $error_notice = get_transient('mxchat_admin_notice_error');
4018 if ($error_notice) {
4019 //error_log('Found error transient: ' . $error_notice);
4020 echo '<div class="notice notice-error is-dismissible"><p>' . wp_kses_post($error_notice) . '</p></div>';
4021 delete_transient('mxchat_admin_notice_error');
4022 //error_log('Displayed and deleted error transient');
4023 } else {
4024 //error_log('No error transient found');
4025 }
4026
4027 // Check for success notices
4028 $success_notice = get_transient('mxchat_admin_notice_success');
4029 if ($success_notice) {
4030 //error_log('Found success transient: ' . $success_notice);
4031 echo '<div class="notice notice-success is-dismissible"><p>' . wp_kses_post($success_notice) . '</p></div>';
4032 delete_transient('mxchat_admin_notice_success');
4033 //error_log('Displayed and deleted success transient');
4034 }
4035
4036 // Check for info notices
4037 $info_notice = get_transient('mxchat_admin_notice_info');
4038 if ($info_notice) {
4039 //error_log('Found info transient: ' . $info_notice);
4040 echo '<div class="notice notice-info is-dismissible"><p>' . wp_kses_post($info_notice) . '</p></div>';
4041 delete_transient('mxchat_admin_notice_info');
4042 //error_log('Displayed and deleted info transient');
4043 }
4044
4045 }
4046
4047
4048 public function mxchat_create_activation_page() {
4049 $license_status = get_option('mxchat_license_status', 'inactive');
4050 $license_error = get_option('mxchat_license_error', '');
4051 $current_domain = parse_url(home_url(), PHP_URL_HOST);
4052
4053 $license_management_url = 'https://mxchat.ai/my-account/orders/';
4054
4055 // Check if this activation has been linked to a domain
4056 $is_domain_linked = $this->is_current_activation_linked($current_domain);
4057 ?>
4058 <div class="wrap mxchat-admin-activation">
4059 <div class="mxchat-pro-hero">
4060 <h1 class="pro-title">
4061 <span class="pro-gradient-text">Manage</span> MxChat Pro
4062 </h1>
4063 <p class="pro-subtitle">
4064 <?php if ($license_status === 'active'): ?>
4065 <?php esc_html_e('Your pro license is active. Manage your activation below.', 'mxchat'); ?>
4066 <?php else: ?>
4067 <?php esc_html_e('Enter your license key to unlock premium features, advanced AI capabilities, and priority support.', 'mxchat'); ?>
4068 <?php endif; ?>
4069 </p>
4070 </div>
4071
4072 <?php if ($license_status === 'inactive' && !empty($license_error)): ?>
4073 <div class="error notice">
4074 <p><?php echo esc_html($license_error); ?></p>
4075 </div>
4076 <?php endif; ?>
4077
4078 <!-- Domain Linking Notice for Existing Users -->
4079 <?php if ($license_status === 'active' && !$is_domain_linked): ?>
4080 <div class="notice notice-info">
4081 <p>
4082 <strong><?php esc_html_e('New Feature: Domain Tracking', 'mxchat'); ?></strong><br>
4083 <?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'); ?>
4084 </p>
4085 <p>
4086 <button type="button" id="link-domain-button" class="button button-secondary">
4087 <?php esc_html_e('Link This Domain', 'mxchat'); ?>
4088 </button>
4089 <span id="domain-link-status" style="margin-left: 10px;"></span>
4090 </p>
4091 </div>
4092 <?php endif; ?>
4093
4094 <!-- Activation Form -->
4095 <form id="mxchat-activation-form" class="mxchat-pro-form" style="<?php echo $license_status === 'active' ? 'display: none;' : ''; ?>">
4096 <div class="mxchat-pro-form-container">
4097 <table class="form-table">
4098 <tr valign="top">
4099 <th scope="row"><?php esc_html_e('Email Address', 'mxchat'); ?></th>
4100 <td>
4101 <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 />
4102 </td>
4103 </tr>
4104 <tr valign="top">
4105 <th scope="row"><?php esc_html_e('Activation Key', 'mxchat'); ?></th>
4106 <td>
4107 <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 />
4108 </td>
4109 </tr>
4110 </table>
4111
4112 <!-- Hidden field for domain -->
4113 <input type="hidden" id="mxchat_domain" name="domain" value="<?php echo esc_attr($current_domain); ?>" />
4114
4115 <?php if ($license_status !== 'active'): ?>
4116 <div class="mxchat-pro-button-container">
4117 <button type="submit" id="activate_license_button" class="button button-primary mxchat-pro-button"><?php esc_html_e('Activate License', 'mxchat'); ?></button>
4118 <div id="mxchat-activation-spinner" class="mxchat-activation-spinner" style="display: none;"></div>
4119 </div>
4120 <?php endif; ?>
4121 </div>
4122 </form>
4123
4124 <!-- License Status Display -->
4125 <div class="mxchat-pro-status">
4126 <h3><?php esc_html_e('License Status:', 'mxchat'); ?>
4127 <span id="mxchat-license-status" class="mxchat-status-badge <?php echo $license_status; ?>">
4128 <?php echo $license_status === 'active' ? esc_html__('Active', 'mxchat') : esc_html__('Inactive', 'mxchat'); ?>
4129 </span>
4130 </h3>
4131
4132 <?php if ($license_status === 'active'): ?>
4133 <div class="mxchat-license-details">
4134 <p><strong><?php esc_html_e('Domain:', 'mxchat'); ?></strong> <?php echo esc_html($current_domain); ?>
4135 <?php if ($is_domain_linked): ?>
4136 <span style="color: green; margin-left: 10px;">✓ <?php esc_html_e('Linked', 'mxchat'); ?></span>
4137 <?php else: ?>
4138 <span style="color: orange; margin-left: 10px;">⚠ <?php esc_html_e('Not Linked', 'mxchat'); ?></span>
4139 <?php endif; ?>
4140 </p>
4141 <p><strong><?php esc_html_e('Email:', 'mxchat'); ?></strong> <?php echo esc_html(get_option('mxchat_pro_email')); ?></p>
4142 <p><strong><?php esc_html_e('License Key:', 'mxchat'); ?></strong>
4143 <code style="background: #f1f1f1; padding: 2px 6px; border-radius: 3px;">
4144 <?php echo esc_html(get_option('mxchat_activation_key')); ?>
4145 </code>
4146 </p>
4147 <p>
4148 <small>
4149 <?php esc_html_e('Manage all your licenses and domains on', 'mxchat'); ?>
4150 <a href="<?php echo esc_url($license_management_url); ?>" target="_blank">
4151 <?php esc_html_e('your account dashboard', 'mxchat'); ?>
4152 </a>
4153 </small>
4154 </p>
4155
4156 <!-- Deactivation Section -->
4157 <div class="mxchat-deactivation-section" style="margin-top: 20px; padding: 15px; background: #fff3cd; border-left: 4px solid #ffc107; border-radius: 4px;">
4158 <h4 style="margin-top: 0; color: #856404;">
4159 <?php esc_html_e('License Management', 'mxchat'); ?>
4160 </h4>
4161 <p style="margin-bottom: 15px; color: #856404;">
4162 <?php esc_html_e('Need to move this license to another site? Deactivate it here first, then activate it on your new site.', 'mxchat'); ?>
4163 </p>
4164 <button type="button" id="deactivate-license-button" class="button button-secondary" style="background: #dc3545; color: white; border-color: #dc3545;">
4165 🔓 <?php esc_html_e('Deactivate License', 'mxchat'); ?>
4166 </button>
4167 <span id="deactivate-status" style="margin-left: 10px;"></span>
4168 </div>
4169 </div>
4170 <?php endif; ?>
4171 </div>
4172
4173 <!-- Hidden fields for JavaScript -->
4174 <input type="hidden" id="mxchat-ajax-url" value="<?php echo admin_url('admin-ajax.php'); ?>" />
4175 <input type="hidden" id="mxchat-nonce" value="<?php echo wp_create_nonce('mxchat_activate_license_nonce'); ?>" />
4176 <input type="hidden" id="mxchat-api-url" value="https://mxchat.ai" />
4177 </div>
4178 <?php
4179 }
4180
4181 /**
4182 * Check if current activation is linked to a domain
4183 * This checks YOUR website's database, not the user's local database
4184 */
4185 private function is_current_activation_linked($domain) {
4186 $license_key = get_option('mxchat_activation_key');
4187 $email = get_option('mxchat_pro_email');
4188
4189 if (empty($license_key) || empty($email)) {
4190 return false;
4191 }
4192
4193 // Check with YOUR website's API
4194 $response = wp_remote_post('https://mxchat.ai/mxchat-api/check-domain', array(
4195 'body' => array(
4196 'license_key' => $license_key,
4197 'email' => $email,
4198 'domain' => $domain
4199 ),
4200 'timeout' => 10,
4201 'sslverify' => false
4202 ));
4203
4204 if (is_wp_error($response)) {
4205 return false;
4206 }
4207
4208 $body = json_decode(wp_remote_retrieve_body($response), true);
4209 return isset($body['success']) && $body['success'] && isset($body['data']['linked']) && $body['data']['linked'];
4210 }
4211
4212
4213 public function mxchat_actions_page_html() {
4214 if (!current_user_can('manage_options')) {
4215 return;
4216 }
4217
4218 // Keep existing data fetching logic
4219 global $wpdb;
4220 $table_name = $wpdb->prefix . 'mxchat_intents';
4221 $page = isset($_GET['paged']) ? max(1, intval($_GET['paged'])) : 1;
4222 $per_page = 20;
4223 $offset = ($page - 1) * $per_page;
4224
4225 // Success message
4226 if (isset($_GET['updated']) && $_GET['updated'] === 'true') {
4227 echo '<div class="notice notice-success is-dismissible"><p>' .
4228 esc_html__('Action updated successfully.', 'mxchat') .
4229 '</p></div>';
4230 }
4231
4232 // Filtering logic
4233 $where = '1=1';
4234 $search_term = isset($_GET['s']) ? trim($_GET['s']) : '';
4235 $callback_filter = isset($_GET['callback_filter']) ? sanitize_text_field($_GET['callback_filter']) : '';
4236
4237 if ($search_term) {
4238 $search_term_like = '%' . $wpdb->esc_like($search_term) . '%';
4239 $where .= $wpdb->prepare(' AND (intent_label LIKE %s OR phrases LIKE %s)',
4240 $search_term_like, $search_term_like);
4241 }
4242
4243 if ($callback_filter) {
4244 $where .= $wpdb->prepare(' AND callback_function = %s', $callback_filter);
4245 }
4246
4247 // Pagination
4248 $total_intents = $wpdb->get_var("SELECT COUNT(*) FROM $table_name WHERE $where");
4249 $total_pages = ceil($total_intents / $per_page);
4250
4251 // Get intents (now called actions)
4252 $actions = $wpdb->get_results($wpdb->prepare(
4253 "SELECT * FROM $table_name WHERE $where LIMIT %d OFFSET %d",
4254 $per_page, $offset
4255 ));
4256
4257 // Get callbacks
4258 $available_callbacks = $this->mxchat_get_available_callbacks();
4259
4260 ?>
4261 <div class="wrap mxchat-wrapper">
4262 <!-- Hero Section -->
4263 <div class="mxchat-hero">
4264 <h1 class="mxchat-main-title">
4265 <span class="mxchat-gradient-text">Actions</span> Manager
4266 </h1>
4267 <p class="mxchat-hero-subtitle">
4268 <?php esc_html_e('Create and manage custom actions to enhance your chatbot\'s capabilities.', 'mxchat'); ?>
4269 </p>
4270 </div>
4271
4272 <!-- Actions Header with Search and Filter -->
4273 <div class="mxchat-actions-header">
4274 <div class="mxchat-actions-filters">
4275 <form method="get" class="mxchat-search-form">
4276 <input type="hidden" name="page" value="mxchat-actions">
4277 <div class="mxchat-search-group">
4278 <span class="dashicons dashicons-search"></span>
4279 <input type="text" name="s" class="mxchat-search-input"
4280 placeholder="<?php esc_attr_e('Search Actions', 'mxchat'); ?>"
4281 value="<?php echo esc_attr($search_term); ?>">
4282 </div>
4283 <select name="callback_filter" class="mxchat-action-filter">
4284 <option value=""><?php esc_html_e('All Action Types', 'mxchat'); ?></option>
4285 <?php foreach ($available_callbacks as $function => $callback_data) :
4286 $label = $callback_data['label']; ?>
4287 <option value="<?php echo esc_attr($function); ?>"
4288 <?php selected($callback_filter, $function); ?>>
4289 <?php echo esc_html($label); ?>
4290 </option>
4291 <?php endforeach; ?>
4292 </select>
4293 <button type="submit" class="mxchat-button-secondary">
4294 <?php esc_html_e('Filter', 'mxchat'); ?>
4295 </button>
4296 </form>
4297 </div>
4298 <div class="mxchat-actions-controls">
4299 <button type="button" id="mxchat-add-action-btn" class="mxchat-button-primary">
4300 <span class="dashicons dashicons-plus-alt"></span>
4301 <?php esc_html_e('Add New Action', 'mxchat'); ?>
4302 </button>
4303 </div>
4304 </div>
4305
4306 <!-- Actions Grid Layout - All actions in a single grid -->
4307 <div class="mxchat-actions-grid">
4308 <div class="mxchat-cards-container">
4309 <?php if (!empty($actions)) : ?>
4310 <?php foreach ($actions as $action) :
4311 $callback_function = $action->callback_function;
4312 $callback_label = isset($available_callbacks[$callback_function]['label'])
4313 ? $available_callbacks[$callback_function]['label']
4314 : $callback_function;
4315 $threshold_value = isset($action->similarity_threshold)
4316 ? round($action->similarity_threshold * 100)
4317 : 85;
4318
4319 // Check if this is a form action
4320 $is_form_action = strpos($action->intent_label, 'Form ') === 0;
4321
4322 // Get action status (enabled/disabled) - default to true if column doesn't exist
4323 $is_enabled = isset($action->enabled) ? (bool)$action->enabled : true;
4324
4325 // Get enabled bots for display
4326 $enabled_bots = [];
4327 if (isset($action->enabled_bots) && !empty($action->enabled_bots)) {
4328 $enabled_bots = json_decode($action->enabled_bots, true);
4329 if (!is_array($enabled_bots)) {
4330 $enabled_bots = ['default'];
4331 }
4332 } else {
4333 $enabled_bots = ['default']; // Backward compatibility
4334 }
4335 ?>
4336 <div class="mxchat-action-card <?php echo $is_form_action ? 'mxchat-form-action' : ''; ?>">
4337 <div class="mxchat-card-header">
4338 <div class="mxchat-card-title"><?php echo esc_html($action->intent_label); ?></div>
4339 <div class="mxchat-card-toggle">
4340 <label class="mxchat-switch">
4341 <input type="checkbox" class="mxchat-action-toggle"
4342 data-action-id="<?php echo esc_attr($action->id); ?>"
4343 <?php checked($is_enabled); ?>>
4344 <span class="mxchat-slider round"></span>
4345 </label>
4346 </div>
4347 </div>
4348
4349 <div class="mxchat-card-body">
4350 <div class="mxchat-card-description">
4351 <strong><?php esc_html_e('Type:', 'mxchat'); ?></strong>
4352 <?php echo esc_html($callback_label); ?>
4353 </div>
4354
4355 <div class="mxchat-card-phrases">
4356 <strong><?php esc_html_e('Trigger phrases:', 'mxchat'); ?></strong>
4357 <div class="mxchat-phrases-preview">
4358 <?php
4359 // Check if the helper function exists, otherwise use a simple substring
4360 if (method_exists($this, 'get_trimmed_phrases')) {
4361 echo esc_html($this->get_trimmed_phrases($action->phrases));
4362 } else {
4363 echo esc_html(strlen($action->phrases) > 100 ?
4364 substr($action->phrases, 0, 97) . '...' :
4365 $action->phrases);
4366 }
4367 ?>
4368 </div>
4369 </div>
4370
4371 <div class="mxchat-threshold-control">
4372 <div class="mxchat-threshold-label">
4373 <?php esc_html_e('Similarity Threshold:', 'mxchat'); ?>
4374 <span class="mxchat-threshold-value"><?php echo esc_html($threshold_value); ?>%</span>
4375 </div>
4376 </div>
4377
4378 <!-- Bot Availability Display (Read-only) -->
4379 <div class="mxchat-card-bots">
4380 <strong><?php esc_html_e('Assigned bots:', 'mxchat'); ?></strong>
4381 <div class="mxchat-bot-badges">
4382 <?php
4383 foreach ($enabled_bots as $bot_id) {
4384 if ($bot_id === 'default') {
4385 echo '<span class="mxchat-bot-badge">' . esc_html__('Default Bot', 'mxchat') . '</span>';
4386 } else {
4387 // Try to get bot name from multi-bot manager
4388 if (class_exists('MxChat_Multi_Bot_Core_Manager')) {
4389 $multi_bot_manager = MxChat_Multi_Bot_Core_Manager::get_instance();
4390 $available_bots = $multi_bot_manager->get_available_bots();
4391 $bot_name = isset($available_bots[$bot_id]) ? $available_bots[$bot_id] : $bot_id;
4392 echo '<span class="mxchat-bot-badge">' . esc_html($bot_name) . '</span>';
4393 } else {
4394 echo '<span class="mxchat-bot-badge">' . esc_html($bot_id) . '</span>';
4395 }
4396 }
4397 }
4398 ?>
4399 </div>
4400 </div>
4401 </div>
4402
4403 <div class="mxchat-card-footer">
4404 <?php
4405 // Check if it's a form action
4406 $is_form_action = preg_match('/Form (\d+)/', $action->intent_label, $form_matches);
4407
4408 // Check if it's a recommendation flow action
4409 $is_flow_action = preg_match('/Recommendation Flow (\d+)/', $action->intent_label, $flow_matches);
4410
4411 if ($is_form_action) {
4412 $form_id = isset($form_matches[1]) ? $form_matches[1] : '';
4413 ?>
4414 <a href="<?php echo esc_url(admin_url('admin.php?page=mxchat-forms&action=edit&form_id=' . $form_id)); ?>"
4415 class="mxchat-button-primary">
4416 <span class="dashicons dashicons-feedback"></span>
4417 <?php esc_html_e('Edit Form', 'mxchat'); ?>
4418 </a>
4419 <?php } elseif ($is_flow_action) {
4420 ?>
4421 <a href="<?php echo esc_url(admin_url('admin.php?page=mxchat-smart-recommender')); ?>"
4422 class="mxchat-button-primary">
4423 <span class="dashicons dashicons-list-view"></span>
4424 <?php esc_html_e('Manage Flows', 'mxchat'); ?>
4425 </a>
4426 <?php } else { ?>
4427 <button type="button"
4428 class="mxchat-button-secondary mxchat-edit-button"
4429 data-action-id="<?php echo esc_attr($action->id); ?>"
4430 data-phrases="<?php echo esc_attr($action->phrases); ?>"
4431 data-label="<?php echo esc_attr($action->intent_label); ?>"
4432 data-threshold="<?php echo esc_attr(round($action->similarity_threshold * 100)); ?>"
4433 data-callback-function="<?php echo esc_attr($action->callback_function); ?>"
4434 data-enabled-bots="<?php echo esc_attr(json_encode($enabled_bots)); ?>">
4435 <span class="dashicons dashicons-edit"></span>
4436 <?php esc_html_e('Edit', 'mxchat'); ?>
4437 </button>
4438 <form method="post"
4439 action="<?php echo esc_url(admin_url('admin-post.php')); ?>"
4440 class="mxchat-delete-form"
4441 onsubmit="return confirm('<?php esc_attr_e('Are you sure you want to delete this action?', 'mxchat'); ?>');">
4442 <?php wp_nonce_field('mxchat_delete_intent_nonce'); ?>
4443 <input type="hidden" name="action" value="mxchat_delete_intent">
4444 <input type="hidden" name="intent_id" value="<?php echo esc_attr($action->id); ?>">
4445 <button type="submit" class="mxchat-button-text mxchat-delete-button">
4446 <span class="dashicons dashicons-trash"></span>
4447 <?php esc_html_e('Delete', 'mxchat'); ?>
4448 </button>
4449 </form>
4450 <?php } ?>
4451 </div>
4452 </div>
4453 <?php endforeach; ?>
4454 <?php else : ?>
4455 <!-- If no actions found -->
4456 <div class="mxchat-no-actions">
4457 <div class="mxchat-empty-state">
4458 <span class="dashicons dashicons-format-chat"></span>
4459 <h2><?php esc_html_e('No actions found', 'mxchat'); ?></h2>
4460 <p><?php esc_html_e('Get started by creating your first action to enhance your chatbot.', 'mxchat'); ?></p>
4461 <button type="button" id="mxchat-create-first-action" class="mxchat-button-primary">
4462 <?php esc_html_e('Create Your First Action', 'mxchat'); ?>
4463 </button>
4464 </div>
4465 </div>
4466 <?php endif; ?>
4467 </div>
4468 </div>
4469
4470 <?php if ($total_pages > 1) : ?>
4471 <div class="mxchat-pagination">
4472 <?php
4473 echo paginate_links(array(
4474 'base' => add_query_arg('paged', '%#%'),
4475 'format' => '',
4476 'prev_text' => __('&laquo; Previous', 'mxchat'),
4477 'next_text' => __('Next &raquo;', 'mxchat'),
4478 'total' => $total_pages,
4479 'current' => $page
4480 ));
4481 ?>
4482 </div>
4483 <?php endif; ?>
4484
4485 <!-- Add/Edit Action Modal with Step-Based Approach -->
4486 <!-- Complete Modal HTML with Defined Groups Variable -->
4487 <div id="mxchat-action-modal" class="mxchat-modal" style="display: none;">
4488 <div class="mxchat-modal-content">
4489 <span class="mxchat-modal-close">&times;</span>
4490
4491 <form id="mxchat-action-form" method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>">
4492 <!-- Dynamic nonce field -->
4493 <div id="action-nonce-container">
4494 <?php wp_nonce_field('mxchat_add_intent_nonce', 'add_intent_nonce'); ?>
4495 </div>
4496 <input type="hidden" name="action" id="form_action_type" value="mxchat_add_intent">
4497 <input type="hidden" name="intent_id" id="edit_action_id" value="">
4498 <input type="hidden" name="callback_function" id="callback_function" value="">
4499
4500 <!-- Step 1: Action Type Selection -->
4501 <div id="mxchat-action-step-1" class="mxchat-action-step active">
4502 <div class="mxchat-step-indicator">
4503 <div class="mxchat-step-number">1</div>
4504 <div class="mxchat-step-title"><?php esc_html_e('Select Action Type', 'mxchat'); ?></div>
4505 </div>
4506
4507 <div id="mxchat-action-type-selector" class="mxchat-action-type-selector">
4508 <div class="mxchat-action-type-search">
4509 <span class="dashicons dashicons-search"></span>
4510 <input type="text" id="action-type-search" placeholder="<?php esc_attr_e('Search action types...', 'mxchat'); ?>" class="mxchat-action-type-search-input">
4511 </div>
4512
4513 <?php
4514 // Get the callbacks - IMPORTANT: Define the $groups variable here
4515 $groups = $this->mxchat_get_available_callbacks(true, true);
4516 ?>
4517
4518 <div class="mxchat-action-type-categories">
4519 <button type="button" class="mxchat-category-button active" data-category="all"><?php esc_html_e('All', 'mxchat'); ?></button>
4520 <?php
4521 // Get unique categories from the defined groups
4522 foreach ($groups as $group_label => $group_callbacks) :
4523 $category_slug = sanitize_title($group_label);
4524 ?>
4525 <button type="button" class="mxchat-category-button" data-category="<?php echo esc_attr($category_slug); ?>"><?php echo esc_html($group_label); ?></button>
4526 <?php endforeach; ?>
4527 </div>
4528
4529 <div class="mxchat-action-types-grid">
4530 <?php
4531 // Generate action cards from available callbacks
4532 foreach ($groups as $group_label => $group_callbacks) :
4533 $category_slug = sanitize_title($group_label);
4534
4535 foreach ($group_callbacks as $function => $data) :
4536 $label = $data['label'];
4537 $pro_only = $data['pro_only'];
4538 $icon = isset($data['icon']) ? $data['icon'] : 'admin-generic';
4539 $description = isset($data['description']) ? $data['description'] : '';
4540 $is_addon = isset($data['addon']) && $data['addon'] !== false;
4541 $addon_name = isset($data['addon_name']) ? $data['addon_name'] : '';
4542 $is_installed = isset($data['installed']) ? $data['installed'] : true;
4543
4544 // Determine card status and styling
4545 $card_class = 'mxchat-action-type-card';
4546 $icon_class = 'mxchat-action-type-icon';
4547 $status_badge = '';
4548
4549 if ($pro_only && !$this->is_activated) {
4550 // Pro feature but no Pro license
4551 $icon_class .= ' pro-feature';
4552 $status_badge = '<span class="mxchat-pro-badge">' . esc_html__('Pro', 'mxchat') . '</span>';
4553 }
4554
4555 if ($is_addon && !$is_installed) {
4556 // Add-on not installed
4557 $card_class .= ' not-installed';
4558 $status_badge .= '<span class="mxchat-addon-badge">' . esc_html__('Add-on Required', 'mxchat') . '</span>';
4559 }
4560
4561 // Default description if none provided
4562 if (empty($description)) {
4563 $description = sprintf(
4564 esc_html__('Use the %s action in your chatbot', 'mxchat'),
4565 $label
4566 );
4567 }
4568 ?>
4569 <div class="<?php echo esc_attr($card_class); ?>"
4570 data-category="<?php echo esc_attr($category_slug); ?>"
4571 data-value="<?php echo esc_attr($function); ?>"
4572 data-label="<?php echo esc_attr($label); ?>"
4573 data-pro="<?php echo $pro_only ? 'true' : 'false'; ?>"
4574 data-addon="<?php echo esc_attr($is_addon ? $data['addon'] : ''); ?>"
4575 data-installed="<?php echo $is_installed ? 'true' : 'false'; ?>">
4576 <div class="<?php echo esc_attr($icon_class); ?>">
4577 <span class="dashicons dashicons-<?php echo esc_attr($icon); ?>"></span>
4578 </div>
4579 <div class="mxchat-action-type-info">
4580 <h4><?php echo esc_html($label); ?></h4>
4581 <p><?php echo esc_html($description); ?></p>
4582 <?php if (!empty($status_badge)) : ?>
4583 <?php echo $status_badge; ?>
4584 <?php endif; ?>
4585
4586 <?php if ($is_addon && !$is_installed) : ?>
4587 <div class="mxchat-addon-info">
4588 <?php echo esc_html(sprintf(
4589 __('Requires %s', 'mxchat'),
4590 $addon_name
4591 )); ?>
4592 </div>
4593 <?php endif; ?>
4594 </div>
4595 </div>
4596 <?php endforeach;
4597 endforeach; ?>
4598 </div>
4599 </div>
4600
4601 <div class="mxchat-modal-actions">
4602 <button type="button" class="mxchat-button-secondary mxchat-modal-cancel">
4603 <?php esc_html_e('Cancel', 'mxchat'); ?>
4604 </button>
4605 </div>
4606 </div>
4607
4608 <!-- Step 2: Action Configuration -->
4609 <div id="mxchat-action-step-2" class="mxchat-action-step">
4610 <div class="mxchat-step-indicator">
4611 <div class="mxchat-step-number">2</div>
4612 <div class="mxchat-step-title"><?php esc_html_e('Configure Action', 'mxchat'); ?></div>
4613 </div>
4614
4615 <div class="mxchat-selected-action">
4616 <button type="button" class="mxchat-back-button" id="mxchat-back-to-step-1">
4617 <span class="dashicons dashicons-arrow-left-alt"></span>
4618 <?php esc_html_e('Back to Action Types', 'mxchat'); ?>
4619 </button>
4620 <div class="mxchat-selected-action-info">
4621 <div id="selected-action-icon" class="mxchat-action-type-icon">
4622 <span class="dashicons dashicons-admin-generic"></span>
4623 </div>
4624 <div class="mxchat-selected-action-details">
4625 <h3 id="selected-action-title"><?php esc_html_e('Selected Action', 'mxchat'); ?></h3>
4626 <p id="selected-action-description"><?php esc_html_e('Configure this action for your chatbot', 'mxchat'); ?></p>
4627 </div>
4628 </div>
4629 </div>
4630
4631 <div class="mxchat-form-group">
4632 <label for="intent_label">
4633 <?php esc_html_e('Action Label (For your reference only)', 'mxchat'); ?>
4634 </label>
4635 <input name="intent_label" type="text" id="intent_label" required
4636 class="mxchat-intent-input"
4637 placeholder="<?php esc_attr_e('Example: Newsletter Signup', 'mxchat'); ?>">
4638 </div>
4639
4640 <div class="mxchat-form-group">
4641 <label for="phrases">
4642 <?php esc_html_e('Trigger Phrases (comma-separated)', 'mxchat'); ?>
4643 </label>
4644 <textarea name="phrases" id="action_phrases" rows="5" required
4645 class="mxchat-intent-textarea"
4646 placeholder="<?php esc_attr_e('Example: sign me up, subscribe me, I want to join, add me to the newsletter', 'mxchat'); ?>"></textarea>
4647 </div>
4648
4649 <div class="mxchat-form-group">
4650 <label for="similarity_threshold">
4651 <?php esc_html_e('Similarity Threshold', 'mxchat'); ?>
4652 <span class="mxchat-threshold-value-display">85%</span>
4653 </label>
4654 <div class="mxchat-slider-group modal-slider">
4655 <input type="range"
4656 name="similarity_threshold"
4657 id="similarity_threshold"
4658 min="10"
4659 max="95"
4660 value="85"
4661 class="mxchat-intent-slider"
4662 oninput="document.querySelector('.mxchat-threshold-value-display').textContent = this.value + '%'">
4663 </div>
4664 <div class="mxchat-threshold-hint">
4665 <?php esc_html_e('Lower values (10-30) make the action trigger more easily. Higher values (70-95) require more exact matches.', 'mxchat'); ?>
4666 </div>
4667 </div>
4668
4669 <!-- Bot Selection Section -->
4670 <div class="mxchat-form-group">
4671 <label for="enabled_bots">
4672 <?php esc_html_e('Which bots should we enable this action for?', 'mxchat'); ?>
4673 </label>
4674 <div class="mxchat-bot-selector">
4675 <div class="mxchat-bot-option">
4676 <label class="mxchat-checkbox-label">
4677 <input type="checkbox"
4678 name="enabled_bots[]"
4679 value="default"
4680 id="bot_default"
4681 checked="checked">
4682 <span class="mxchat-checkmark"></span>
4683 <?php esc_html_e('Default Bot', 'mxchat'); ?>
4684 </label>
4685 </div>
4686
4687 <?php if (class_exists('MxChat_Multi_Bot_Core_Manager')) :
4688 $multi_bot_manager = MxChat_Multi_Bot_Core_Manager::get_instance();
4689 $available_bots = $multi_bot_manager->get_available_bots();
4690
4691 foreach ($available_bots as $bot_id => $bot_name) :
4692 if ($bot_id === 'default') continue; // Skip default, already shown above
4693 ?>
4694 <div class="mxchat-bot-option">
4695 <label class="mxchat-checkbox-label">
4696 <input type="checkbox"
4697 name="enabled_bots[]"
4698 value="<?php echo esc_attr($bot_id); ?>"
4699 id="bot_<?php echo esc_attr($bot_id); ?>">
4700 <span class="mxchat-checkmark"></span>
4701 <?php echo esc_html($bot_name); ?>
4702 </label>
4703 </div>
4704 <?php
4705 endforeach;
4706 endif; ?>
4707 </div>
4708 </div>
4709
4710 <div class="mxchat-modal-actions">
4711 <button type="button" class="mxchat-button-secondary mxchat-modal-cancel">
4712 <?php esc_html_e('Cancel', 'mxchat'); ?>
4713 </button>
4714 <button type="submit" class="mxchat-button-primary" id="mxchat-save-action-btn">
4715 <?php esc_html_e('Save Action', 'mxchat'); ?>
4716 </button>
4717 </div>
4718 </div>
4719 </form>
4720 </div>
4721 </div>
4722
4723 <div id="mxchat-action-loading" class="mxchat-action-loading" style="display: none;">
4724 <div class="mxchat-action-loading-spinner"></div>
4725 <div class="mxchat-action-loading-text">
4726 <?php esc_html_e('Saving action, please wait...', 'mxchat'); ?>
4727 </div>
4728 </div>
4729 </div><!-- .mxchat-wrapper -->
4730 <?php
4731 }
4732 private function get_trimmed_phrases($phrases, $max_length = 100) {
4733 if (strlen($phrases) <= $max_length) {
4734 return $phrases;
4735 }
4736
4737 $trimmed = substr($phrases, 0, $max_length);
4738 $last_comma = strrpos($trimmed, ',');
4739
4740 if ($last_comma !== false) {
4741 $trimmed = substr($trimmed, 0, $last_comma);
4742 }
4743
4744 return $trimmed . '...';
4745 }
4746 public function mxchat_add_enabled_column_to_intents() {
4747 global $wpdb;
4748 $table_name = $wpdb->prefix . 'mxchat_intents';
4749
4750 // Check if the column already exists
4751 $columns = $wpdb->get_results("SHOW COLUMNS FROM $table_name LIKE 'enabled'");
4752
4753 if (empty($columns)) {
4754 // Add the column with default value of 1 (enabled)
4755 $wpdb->query("ALTER TABLE $table_name ADD COLUMN enabled TINYINT(1) NOT NULL DEFAULT 1");
4756 }
4757 }
4758
4759 public function mxchat_handle_delete_intent() {
4760 if ( ! current_user_can( 'manage_options' ) ) {
4761 wp_die( esc_html__('Unauthorized user', 'mxchat') );
4762 }
4763
4764 check_admin_referer('mxchat_delete_intent_nonce');
4765
4766 if (isset($_POST['intent_id'])) {
4767 global $wpdb;
4768 $table_name = $wpdb->prefix . 'mxchat_intents';
4769 $intent_id = intval($_POST['intent_id']);
4770
4771 $wpdb->delete($table_name, ['id' => $intent_id], ['%d']);
4772 }
4773
4774 wp_safe_redirect(admin_url('admin.php?page=mxchat-actions'));
4775 exit;
4776 }
4777 public function mxchat_handle_edit_intent() {
4778 // Security checks (nonce and permissions)
4779 if (!current_user_can('manage_options')) {
4780 wp_die(esc_html__('Unauthorized user', 'mxchat'));
4781 }
4782 check_admin_referer('mxchat_edit_intent');
4783
4784 // Get POST data
4785 $intent_id = isset($_POST['intent_id']) ? absint($_POST['intent_id']) : 0;
4786 $intent_label = isset($_POST['intent_label']) ? sanitize_text_field($_POST['intent_label']) : '';
4787 $phrases_input = isset($_POST['phrases']) ? sanitize_textarea_field($_POST['phrases']) : '';
4788 $threshold_percentage = isset($_POST['similarity_threshold']) ? intval($_POST['similarity_threshold']) : 85;
4789 $similarity_threshold = min(95, max(10, $threshold_percentage)) / 100; // Convert to 0.10–0.95
4790
4791 // Handle enabled_bots
4792 $enabled_bots = isset($_POST['enabled_bots']) ? $_POST['enabled_bots'] : array('default');
4793 $enabled_bots = array_map('sanitize_text_field', $enabled_bots);
4794
4795 // Ensure default is always included for backward compatibility
4796 if (!in_array('default', $enabled_bots)) {
4797 $enabled_bots[] = 'default';
4798 }
4799
4800 $enabled_bots_json = json_encode($enabled_bots);
4801
4802 // Validate inputs
4803 if (!$intent_id || empty($intent_label) || empty($phrases_input)) {
4804 $this->handle_embedding_error(__('Invalid input. Please ensure all fields are filled out.', 'mxchat'));
4805 return;
4806 }
4807
4808 $phrases_array = array_map('sanitize_text_field', array_filter(array_map('trim', explode(',', $phrases_input))));
4809 if (empty($phrases_array)) {
4810 $this->handle_embedding_error(__('Please enter at least one valid phrase.', 'mxchat'));
4811 return;
4812 }
4813
4814 // Generate embeddings with improved error handling
4815 $vectors = [];
4816 $failed_phrases = [];
4817
4818 foreach ($phrases_array as $phrase) {
4819 $embedding_vector = $this->mxchat_generate_embedding($phrase, $this->options['api_key']);
4820 if (is_array($embedding_vector)) {
4821 $vectors[] = $embedding_vector;
4822 } else {
4823 $failed_phrases[] = $phrase;
4824 }
4825 }
4826
4827 if (!empty($failed_phrases)) {
4828 $this->handle_embedding_error(
4829 sprintf(
4830 __('Error generating embeddings for phrases: %s. Check your embedding API.', 'mxchat'),
4831 implode(', ', $failed_phrases)
4832 )
4833 );
4834 return;
4835 }
4836
4837 if (empty($vectors)) {
4838 $this->handle_embedding_error(__('No valid embeddings generated. Please check your phrases.', 'mxchat'));
4839 return;
4840 }
4841
4842 $combined_vector = $this->mxchat_average_vectors($vectors);
4843 $serialized_vector = maybe_serialize($combined_vector);
4844
4845 // Update the database
4846 global $wpdb;
4847 $table_name = $wpdb->prefix . 'mxchat_intents';
4848
4849 $result = $wpdb->update(
4850 $table_name,
4851 array(
4852 'intent_label' => $intent_label,
4853 'phrases' => implode(', ', $phrases_array),
4854 'embedding_vector' => $serialized_vector,
4855 'similarity_threshold' => $similarity_threshold,
4856 'enabled_bots' => $enabled_bots_json // Include enabled_bots in update
4857 ),
4858 array('id' => $intent_id),
4859 array('%s', '%s', '%s', '%f', '%s'), // Format: string, string, string, float, string
4860 array('%d') // Where format: integer
4861 );
4862
4863 if (false === $result) {
4864 $this->handle_embedding_error(__('Failed to update action in database.', 'mxchat'));
4865 return;
4866 }
4867
4868 // Set success message and redirect
4869 set_transient('mxchat_admin_notice_success', __('Intent updated successfully!', 'mxchat'), 60);
4870
4871 $redirect_url = add_query_arg(
4872 array(
4873 'page' => 'mxchat-actions'
4874 ),
4875 admin_url('admin.php')
4876 );
4877 wp_safe_redirect($redirect_url);
4878 exit;
4879 }
4880 public function mxchat_handle_add_intent() {
4881 if (!current_user_can('manage_options')) {
4882 wp_die(esc_html__('Unauthorized user', 'mxchat'));
4883 }
4884 check_admin_referer('mxchat_add_intent_nonce');
4885 global $wpdb;
4886 $table_name = $wpdb->prefix . 'mxchat_intents';
4887
4888 // Sanitize and get form data
4889 $intent_label = isset($_POST['intent_label']) ? sanitize_text_field($_POST['intent_label']) : '';
4890 $phrases_input = isset($_POST['phrases']) ? sanitize_textarea_field($_POST['phrases']) : '';
4891 $callback_function = isset($_POST['callback_function']) ? sanitize_text_field($_POST['callback_function']) : '';
4892
4893 // Get similarity threshold from form (convert percentage to decimal)
4894 $similarity_threshold = isset($_POST['similarity_threshold']) ? floatval($_POST['similarity_threshold']) / 100 : 0.85;
4895
4896 // Handle enabled_bots
4897 $enabled_bots = isset($_POST['enabled_bots']) ? $_POST['enabled_bots'] : array('default');
4898 $enabled_bots = array_map('sanitize_text_field', $enabled_bots);
4899
4900 // Ensure default is always included for backward compatibility with existing actions
4901 if (!in_array('default', $enabled_bots)) {
4902 $enabled_bots[] = 'default';
4903 }
4904
4905 $enabled_bots_json = json_encode($enabled_bots);
4906
4907 // Validate required fields
4908 if (empty($intent_label) || empty($callback_function) || empty($phrases_input)) {
4909 $this->handle_embedding_error(__('Invalid input. Please ensure all fields are filled out.', 'mxchat'));
4910 return;
4911 }
4912
4913 // Validate callback function
4914 $available_callbacks = $this->mxchat_get_available_callbacks();
4915 if (!array_key_exists($callback_function, $available_callbacks)) {
4916 $this->handle_embedding_error(__('Invalid callback function selected.', 'mxchat'));
4917 return;
4918 }
4919
4920 // Check Pro requirements
4921 $is_pro_only = $available_callbacks[$callback_function]['pro_only'];
4922 if ($is_pro_only && !$this->is_activated) {
4923 $this->handle_embedding_error(__('This callback function is available in the Pro version only.', 'mxchat'));
4924 return;
4925 }
4926
4927 // Process phrases
4928 $phrases_array = array_map('sanitize_text_field', array_filter(array_map('trim', explode(',', $phrases_input))));
4929 if (empty($phrases_array)) {
4930 $this->handle_embedding_error(__('Please enter at least one valid phrase.', 'mxchat'));
4931 return;
4932 }
4933
4934 // Generate embeddings with improved error handling
4935 $vectors = [];
4936 $failed_phrases = [];
4937 foreach ($phrases_array as $phrase) {
4938 $embedding_vector = $this->mxchat_generate_embedding($phrase, $this->options['api_key']);
4939 if (is_array($embedding_vector)) {
4940 $vectors[] = $embedding_vector;
4941 } else {
4942 $failed_phrases[] = $phrase;
4943 }
4944 }
4945
4946 // Check for embedding failures
4947 if (!empty($failed_phrases)) {
4948 $this->handle_embedding_error(
4949 sprintf(
4950 __('Error generating embeddings for phrases: %s. Check your embedding API.', 'mxchat'),
4951 implode(', ', $failed_phrases)
4952 )
4953 );
4954 return;
4955 }
4956
4957 if (empty($vectors)) {
4958 $this->handle_embedding_error(__('No valid embeddings generated. Please check your phrases.', 'mxchat'));
4959 return;
4960 }
4961
4962 // Create combined vector and insert into database
4963 $combined_vector = $this->mxchat_average_vectors($vectors);
4964 $serialized_vector = maybe_serialize($combined_vector);
4965
4966 $result = $wpdb->insert($table_name, [
4967 'intent_label' => $intent_label,
4968 'phrases' => implode(', ', $phrases_array),
4969 'embedding_vector' => $serialized_vector,
4970 'callback_function' => $callback_function,
4971 'similarity_threshold' => $similarity_threshold,
4972 'enabled_bots' => $enabled_bots_json // NEW field
4973 ]);
4974
4975 if ($result === false) {
4976 $this->handle_embedding_error(__('Database error: ', 'mxchat') . $wpdb->last_error);
4977 return;
4978 }
4979
4980 // Set success message and redirect
4981 set_transient('mxchat_admin_notice_success', __('New intent added successfully!', 'mxchat'), 60);
4982 wp_safe_redirect(admin_url('admin.php?page=mxchat-actions'));
4983 exit;
4984 }
4985
4986
4987
4988
4989 private function handle_embedding_error($message, $redirect = true) {
4990 // Store the error message in the existing transient
4991 set_transient('mxchat_admin_notice_error', $message, 60);
4992
4993 if ($redirect) {
4994 // Redirect back to the actions page
4995 $redirect_url = add_query_arg(
4996 array(
4997 'page' => 'mxchat-actions'
4998 ),
4999 admin_url('admin.php')
5000 );
5001 wp_safe_redirect($redirect_url);
5002 exit;
5003 }
5004 }
5005
5006
5007 /**
5008 * Enhanced get_available_callbacks function with form action exclusion
5009 *
5010 * @param bool $grouped Whether to return callbacks grouped by category
5011 * @param bool $include_all Whether to include all potential actions (even if add-on not installed)
5012 * @return array Callbacks data with icons, descriptions and availability status
5013 */
5014 private function mxchat_get_available_callbacks($grouped = false, $include_all = true) {
5015 // Load WordPress plugin functions if needed
5016 if (!function_exists('get_plugins')) {
5017 require_once ABSPATH . 'wp-admin/includes/plugin.php';
5018 }
5019
5020 // Get active plugins
5021 $active_plugins = get_option('active_plugins', array());
5022
5023 // Functions to exclude from the action selector only if Pro is activated
5024 // If user doesn't have Pro, show these so they can see what they're missing
5025 $excluded_when_pro_active_functions = array(
5026 'mxchat_handle_form_collection' // Forms add-on action
5027 );
5028
5029 // Always excluded functions (regardless of Pro status)
5030 $always_excluded_functions = array();
5031
5032 // Combine exclusion lists based on Pro activation status
5033 $excluded_functions = $always_excluded_functions;
5034 if ($this->is_activated) {
5035 // Only exclude add-on managed functions if Pro is active
5036 $excluded_functions = array_merge($excluded_functions, $excluded_when_pro_active_functions);
5037 }
5038
5039 // Define add-on plugin files and their corresponding action functions
5040 $addon_plugins = array(
5041 'mxchat-woo/mxchat-woo.php' => array(
5042 'functions' => array(
5043 'mxchat_handle_product_recommendations',
5044 'mxchat_handle_order_history',
5045 'mxchat_show_product_card',
5046 'mxchat_add_to_cart',
5047 'mxchat_checkout_redirect'
5048 ),
5049 'name' => __('WooCommerce Add-on', 'mxchat'),
5050 'pro_required' => true
5051 ),
5052 'mxchat-perplexity/mxchat-perplexity.php' => array(
5053 'functions' => array('mxchat_perplexity_research'),
5054 'name' => __('Perplexity Add-on', 'mxchat'),
5055 'pro_required' => true
5056 ),
5057 'mxchat-forms/mxchat-forms.php' => array(
5058 'functions' => array('mxchat_handle_form_collection'),
5059 'name' => __('Forms Add-on', 'mxchat'),
5060 'pro_required' => true
5061 ),
5062 // Add other add-ons and their functions here
5063 );
5064
5065 // Get the functions that are provided by active add-ons
5066 $addon_provided_functions = array();
5067 $addon_function_mapping = array(); // Maps functions to their add-on info
5068
5069 // Check which add-ons are active
5070 foreach ($addon_plugins as $plugin_file => $addon_info) {
5071 $is_active = in_array($plugin_file, $active_plugins);
5072
5073 // For each function in this addon
5074 foreach ($addon_info['functions'] as $function) {
5075 // Consider a function installed only if:
5076 // 1. The add-on is active AND
5077 // 2. Either it doesn't require Pro OR Pro is activated
5078 $is_installed = $is_active && (!$addon_info['pro_required'] || $this->is_activated);
5079
5080 // If the add-on is installed, mark this function as provided by an add-on
5081 if ($is_installed) {
5082 $addon_provided_functions[] = $function;
5083 }
5084
5085 // Store addon info for this function regardless of installation status
5086 $addon_function_mapping[$function] = array(
5087 'addon' => basename(dirname($plugin_file)),
5088 'addon_name' => $addon_info['name'],
5089 'pro_required' => $addon_info['pro_required'],
5090 'is_active' => $is_active,
5091 'is_installed' => $is_installed
5092 );
5093 }
5094 }
5095
5096 // Core callbacks - always available in the base plugin
5097 $core_callbacks = array(
5098 'mxchat_handle_email_capture' => array(
5099 'label' => __('Loops Email Capture', 'mxchat'),
5100 'pro_only' => false,
5101 'group' => __('Customer Engagement', 'mxchat'),
5102 'icon' => 'email-alt',
5103 'description' => __('Collect visitor emails for your mailing list in Loops', 'mxchat'),
5104 'addon' => false, // Not from an add-on
5105 'installed' => true // Always installed with base plugin
5106 ),
5107 'mxchat_handle_search_request' => array(
5108 'label' => __('Brave Web Search', 'mxchat'),
5109 'pro_only' => false,
5110 'group' => __('Search Features', 'mxchat'),
5111 'icon' => 'search',
5112 'description' => __('Let users search the web directly from the chat', 'mxchat'),
5113 'addon' => false,
5114 'installed' => true
5115 ),
5116 'mxchat_handle_image_search_request' => array(
5117 'label' => __('Brave Image Search', 'mxchat'),
5118 'pro_only' => false,
5119 'group' => __('Search Features', 'mxchat'),
5120 'icon' => 'format-image',
5121 'description' => __('Search and display images in the chat conversation', 'mxchat'),
5122 'addon' => false,
5123 'installed' => true
5124 ),
5125 // Pro core features - check is_activated property
5126 'mxchat_generate_image' => array(
5127 'label' => __('Generate Image', 'mxchat'),
5128 'pro_only' => false,
5129 'group' => __('Other Features', 'mxchat'),
5130 'icon' => 'art',
5131 'description' => __('Create images with DALL-E 3 from OpenAI (requires OpenAI API key)', 'mxchat'),
5132 'addon' => false,
5133 'installed' => true
5134 ),
5135 'mxchat_handle_pdf_discussion' => array(
5136 'label' => __('Chat with PDF', 'mxchat'),
5137 'pro_only' => false,
5138 'group' => __('Other Features', 'mxchat'),
5139 'icon' => 'media-document',
5140 'description' => __('Answer questions about uploaded PDF documents', 'mxchat'),
5141 'addon' => false,
5142 'installed' => true
5143 ),
5144 'mxchat_live_agent_handover' => array(
5145 'label' => __('Slack Live Agent', 'mxchat'),
5146 'pro_only' => false,
5147 'group' => __('Customer Engagement', 'mxchat'),
5148 'icon' => 'admin-users',
5149 'description' => __('Transfer conversation to a human support agent on Slack', 'mxchat'),
5150 'addon' => false,
5151 'installed' => true
5152 ),
5153 'mxchat_handle_switch_to_chatbot_intent' => array(
5154 'label' => __('Back to Chatbot', 'mxchat'),
5155 'pro_only' => false,
5156 'group' => __('Customer Engagement', 'mxchat'),
5157 'icon' => 'backup',
5158 'description' => __('Return from live agent mode to AI chatbot', 'mxchat'),
5159 'addon' => false,
5160 'installed' => true
5161 ),
5162 );
5163
5164 // Add-on callbacks with placeholders - only include if the add-on is NOT active
5165 $addon_callbacks = array(
5166 // WooCommerce Add-on
5167 'mxchat_handle_product_recommendations' => array(
5168 'label' => __('Product Recommendations', 'mxchat'),
5169 'pro_only' => true,
5170 'group' => __('WooCommerce Features', 'mxchat'),
5171 'icon' => 'cart',
5172 'description' => __('Suggest products based on customer preferences', 'mxchat'),
5173 ),
5174 'mxchat_handle_order_history' => array(
5175 'label' => __('Order History', 'mxchat'),
5176 'pro_only' => true,
5177 'group' => __('WooCommerce Features', 'mxchat'),
5178 'icon' => 'clipboard',
5179 'description' => __('Allow customers to check their order status', 'mxchat'),
5180 ),
5181 'mxchat_show_product_card' => array(
5182 'label' => __('Show Product Card', 'mxchat'),
5183 'pro_only' => true,
5184 'group' => __('WooCommerce Features', 'mxchat'),
5185 'icon' => 'products',
5186 'description' => __('Display product information in the chat', 'mxchat'),
5187 ),
5188 'mxchat_add_to_cart' => array(
5189 'label' => __('Add to Cart', 'mxchat'),
5190 'pro_only' => true,
5191 'group' => __('WooCommerce Features', 'mxchat'),
5192 'icon' => 'plus-alt',
5193 'description' => __('Add products to cart directly from chat', 'mxchat'),
5194 ),
5195 'mxchat_checkout_redirect' => array(
5196 'label' => __('Proceed to Checkout', 'mxchat'),
5197 'pro_only' => true,
5198 'group' => __('WooCommerce Features', 'mxchat'),
5199 'icon' => 'arrow-right-alt',
5200 'description' => __('Redirect customer to checkout page', 'mxchat'),
5201 ),
5202
5203 // Perplexity Add-on
5204 'mxchat_perplexity_research' => array(
5205 'label' => __('Perplexity Research', 'mxchat'),
5206 'pro_only' => true,
5207 'group' => __('Search Features', 'mxchat'),
5208 'icon' => 'book-alt',
5209 'description' => __('Allows the chatbot to search the web for accurate, up-to-date answers', 'mxchat'),
5210 ),
5211
5212 // Forms Add-on (only shown when Pro is not activated)
5213 'mxchat_handle_form_collection' => array(
5214 'label' => __('Form Collection', 'mxchat'),
5215 'pro_only' => true,
5216 'group' => __('Form Features', 'mxchat'),
5217 'icon' => 'feedback',
5218 'description' => __('Collect user information through custom forms in chat', 'mxchat'),
5219 ),
5220 );
5221
5222 // Enhance add-on callbacks with installation status and addon info
5223 foreach ($addon_callbacks as $function => $data) {
5224 if (isset($addon_function_mapping[$function])) {
5225 $addon_info = $addon_function_mapping[$function];
5226
5227 $addon_callbacks[$function]['addon'] = $addon_info['addon'];
5228 $addon_callbacks[$function]['addon_name'] = $addon_info['addon_name'];
5229 $addon_callbacks[$function]['installed'] = $addon_info['is_installed'];
5230
5231 // Set pro_only based on add-on configuration
5232 $addon_callbacks[$function]['pro_only'] = $addon_info['pro_required'];
5233 } else {
5234 $addon_callbacks[$function]['addon'] = 'unknown';
5235 $addon_callbacks[$function]['addon_name'] = __('Unknown Add-on', 'mxchat');
5236 $addon_callbacks[$function]['installed'] = false;
5237 }
5238 }
5239
5240 // Initialize callbacks with core features
5241 $callbacks = $core_callbacks;
5242
5243 // Get callbacks from active add-ons
5244 $active_addon_callbacks = apply_filters('mxchat_available_callbacks', array());
5245
5246 // Add placeholder callbacks only for add-ons that aren't active
5247 if ($include_all) {
5248 foreach ($addon_callbacks as $function => $data) {
5249 // Skip placeholders for functions provided by active add-ons
5250 if (in_array($function, $addon_provided_functions)) {
5251 continue;
5252 }
5253
5254 // Skip excluded functions
5255 if (in_array($function, $excluded_functions)) {
5256 continue;
5257 }
5258
5259 // Add the placeholder
5260 $callbacks[$function] = $data;
5261 }
5262 }
5263
5264 // Add callbacks from active add-ons (will override placeholders)
5265 foreach ($active_addon_callbacks as $function => $data) {
5266 // Skip excluded functions
5267 if (in_array($function, $excluded_functions)) {
5268 continue;
5269 }
5270
5271 // Always include callbacks from add-ons
5272 $callbacks[$function] = $data;
5273
5274 // Ensure they have the proper add-on info
5275 if (isset($addon_function_mapping[$function])) {
5276 $addon_info = $addon_function_mapping[$function];
5277 $callbacks[$function]['addon'] = $addon_info['addon'];
5278 $callbacks[$function]['addon_name'] = $addon_info['addon_name'];
5279 $callbacks[$function]['installed'] = $addon_info['is_installed'];
5280 $callbacks[$function]['pro_only'] = $addon_info['pro_required'];
5281 }
5282 }
5283
5284 // Just before returning callbacks, sort them to prioritize free features
5285 if (!$grouped) {
5286 // Create temporary arrays for sorting
5287 $free_callbacks = array();
5288 $pro_callbacks = array();
5289
5290 // Split callbacks into free and pro
5291 foreach ($callbacks as $key => $data) {
5292 if (isset($data['pro_only']) && $data['pro_only']) {
5293 $pro_callbacks[$key] = $data;
5294 } else {
5295 $free_callbacks[$key] = $data;
5296 }
5297 }
5298
5299 // Merge with free callbacks first
5300 $callbacks = array_merge($free_callbacks, $pro_callbacks);
5301 }
5302
5303 // Return grouped structure if requested
5304 if ($grouped) {
5305 $grouped_callbacks = array();
5306 foreach ($callbacks as $key => $data) {
5307 $group_label = isset($data['group']) ? $data['group'] : __('Other Features', 'mxchat');
5308
5309 // Ensure we carry forward all the new fields in grouped mode
5310 $callback_data = array(
5311 'label' => $data['label'],
5312 'pro_only' => isset($data['pro_only']) ? $data['pro_only'] : false,
5313 'icon' => isset($data['icon']) ? $data['icon'] : 'admin-generic',
5314 'description' => isset($data['description']) ? $data['description'] : __('Custom action for your chatbot', 'mxchat'),
5315 'addon' => isset($data['addon']) ? $data['addon'] : false,
5316 'addon_name' => isset($data['addon_name']) ? $data['addon_name'] : '',
5317 'installed' => isset($data['installed']) ? $data['installed'] : true
5318 );
5319
5320 $grouped_callbacks[$group_label][$key] = $callback_data;
5321 }
5322
5323 // Sort within each group to prioritize free features
5324 foreach ($grouped_callbacks as $group => $items) {
5325 $free_items = array();
5326 $pro_items = array();
5327
5328 foreach ($items as $key => $data) {
5329 if (isset($data['pro_only']) && $data['pro_only']) {
5330 $pro_items[$key] = $data;
5331 } else {
5332 $free_items[$key] = $data;
5333 }
5334 }
5335
5336 $grouped_callbacks[$group] = array_merge($free_items, $pro_items);
5337 }
5338
5339 return $grouped_callbacks;
5340 }
5341
5342 return $callbacks;
5343 }
5344
5345 public function mxchat_page_init() {
5346 register_setting(
5347 'mxchat_option_group',
5348 'mxchat_options',
5349 array($this, 'mxchat_sanitize')
5350 );
5351
5352 register_setting(
5353 'mxchat_option_group',
5354 'mxchat_similarity_threshold',
5355 array(
5356 'type' => 'number',
5357 'sanitize_callback' => function($value) {
5358 $value = absint($value);
5359 return min(max($value, 20), 95);
5360 },
5361 'default' => 80,
5362 )
5363 );
5364
5365 // Chatbot Settings Section
5366 add_settings_section(
5367 'mxchat_chatbot_section',
5368 esc_html__('Chatbot Settings', 'mxchat'),
5369 null,
5370 'mxchat-chatbot'
5371 );
5372
5373 // API Keys Settings Section
5374 add_settings_section(
5375 'mxchat_api_keys_section',
5376 esc_html__('API Keys', 'mxchat'),
5377 array($this, 'mxchat_api_keys_section_callback'),
5378 'mxchat-api-keys'
5379 );
5380
5381 // OpenAI API Key
5382 add_settings_field(
5383 'api_key',
5384 esc_html__('OpenAI API Key', 'mxchat'),
5385 array($this, 'api_key_callback'),
5386 'mxchat-api-keys',
5387 'mxchat_api_keys_section'
5388 );
5389
5390 // X.AI API Key
5391 add_settings_field(
5392 'xai_api_key',
5393 esc_html__('X.AI API Key', 'mxchat'),
5394 array($this, 'xai_api_key_callback'),
5395 'mxchat-api-keys',
5396 'mxchat_api_keys_section'
5397 );
5398
5399 // Claude API Key
5400 add_settings_field(
5401 'claude_api_key',
5402 esc_html__('Claude API Key', 'mxchat'),
5403 array($this, 'claude_api_key_callback'),
5404 'mxchat-api-keys',
5405 'mxchat_api_keys_section'
5406 );
5407
5408 // DeepSeek API Key
5409 add_settings_field(
5410 'deepseek_api_key',
5411 esc_html__('DeepSeek API Key', 'mxchat'),
5412 array($this, 'deepseek_api_key_callback'),
5413 'mxchat-api-keys',
5414 'mxchat_api_keys_section'
5415 );
5416
5417 // Google Gemini API Key
5418 add_settings_field(
5419 'gemini_api_key',
5420 esc_html__('Google Gemini API Key', 'mxchat'),
5421 array($this, 'gemini_api_key_callback'),
5422 'mxchat-api-keys',
5423 'mxchat_api_keys_section'
5424 );
5425
5426 // Voyage AI API Key
5427 add_settings_field(
5428 'voyage_api_key',
5429 esc_html__('Voyage AI API Key', 'mxchat'),
5430 array($this, 'voyage_api_key_callback'),
5431 'mxchat-api-keys',
5432 'mxchat_api_keys_section'
5433 );
5434
5435 // OpenRouter API Key
5436 add_settings_field(
5437 'openrouter_api_key',
5438 esc_html__('OpenRouter API Key', 'mxchat'),
5439 array($this, 'openrouter_api_key_callback'),
5440 'mxchat-api-keys',
5441 'mxchat_api_keys_section'
5442 );
5443
5444 // Loops API Key
5445 add_settings_field(
5446 'loops_api_key',
5447 esc_html__('Loops API Key', 'mxchat'),
5448 array($this, 'mxchat_loops_api_key_callback'),
5449 'mxchat-api-keys',
5450 'mxchat_api_keys_section'
5451 );
5452
5453 // Brave Search API Key
5454 add_settings_field(
5455 'brave_api_key',
5456 __('Brave API Key', 'mxchat'),
5457 array($this, 'mxchat_brave_api_key_callback'),
5458 'mxchat-api-keys',
5459 'mxchat_api_keys_section'
5460 );
5461
5462 // Frontend Debugger Toggle
5463 add_settings_field(
5464 'show_frontend_debugger',
5465 esc_html__('Frontend Debugger', 'mxchat'),
5466 array($this, 'mxchat_frontend_debugger_callback'),
5467 'mxchat-chatbot',
5468 'mxchat_chatbot_section'
5469 );
5470
5471 // Similarity Threshold Slider
5472 add_settings_field(
5473 'similarity_threshold', // Field ID
5474 esc_html__('Similarity Threshold', 'mxchat'), // Field title
5475 array($this, 'mxchat_similarity_threshold_callback'), // Callback function
5476 'mxchat-chatbot', // Page
5477 'mxchat_chatbot_section' // Section
5478 );
5479
5480 add_settings_field(
5481 'append_to_body',
5482 esc_html__('Auto-Display Chatbot', 'mxchat'),
5483 array($this, 'mxchat_append_to_body_callback'),
5484 'mxchat-chatbot',
5485 'mxchat_chatbot_section'
5486 );
5487
5488 add_settings_field(
5489 'contextual_awareness_toggle',
5490 esc_html__('Contextual Awareness', 'mxchat'),
5491 array($this, 'mxchat_contextual_awareness_callback'),
5492 'mxchat-chatbot',
5493 'mxchat_chatbot_section'
5494 );
5495
5496 add_settings_field(
5497 'enable_streaming_toggle',
5498 esc_html__('Enable Streaming', 'mxchat'),
5499 array($this, 'enable_streaming_toggle_callback'),
5500 'mxchat-chatbot',
5501 'mxchat_chatbot_section', // Same section as your working toggle
5502 array(
5503 'class' => 'mxchat-setting-row streaming-setting',
5504 'style' => 'display: none;' // Hidden by default, shown when OpenAI/Claude selected
5505 )
5506 );
5507
5508
5509 add_settings_field(
5510 'model',
5511 esc_html__('Chat Model', 'mxchat'),
5512 array($this, 'mxchat_model_callback'),
5513 'mxchat-chatbot',
5514 'mxchat_chatbot_section'
5515 );
5516
5517 add_settings_field(
5518 'embedding_model',
5519 esc_html__('Embedding Model', 'mxchat'),
5520 array($this, 'embedding_model_callback'),
5521 'mxchat-chatbot',
5522 'mxchat_chatbot_section'
5523 );
5524
5525 add_settings_field(
5526 'system_prompt_instructions',
5527 esc_html__('AI Instructions (Behavior)', 'mxchat'),
5528 array($this, 'system_prompt_instructions_callback'),
5529 'mxchat-chatbot',
5530 'mxchat_chatbot_section'
5531 );
5532
5533
5534 add_settings_field(
5535 'top_bar_title',
5536 esc_html__('Top Bar Title', 'mxchat'),
5537 array($this, 'mxchat_top_bar_title_callback'),
5538 'mxchat-chatbot',
5539 'mxchat_chatbot_section'
5540 );
5541
5542 add_settings_field(
5543 'ai_agent_text',
5544 esc_html__('AI Agent Text', 'mxchat'),
5545 array($this, 'mxchat_ai_agent_text_callback'),
5546 'mxchat-chatbot',
5547 'mxchat_chatbot_section'
5548 );
5549
5550 add_settings_field(
5551 'enable_email_block',
5552 esc_html__('Require Email To Chat', 'mxchat'),
5553 array($this, 'enable_email_block_callback'),
5554 'mxchat-chatbot',
5555 'mxchat_chatbot_section'
5556 );
5557
5558 add_settings_field(
5559 'email_blocker_header_content',
5560 esc_html__('Require Email Chat Content', 'mxchat'),
5561 array($this, 'email_blocker_header_content_callback'),
5562 'mxchat-chatbot',
5563 'mxchat_chatbot_section'
5564 );
5565
5566 add_settings_field(
5567 'email_blocker_button_text',
5568 esc_html__('Require Email Chat Button Text', 'mxchat'),
5569 [$this, 'email_blocker_button_text_callback'],
5570 'mxchat-chatbot',
5571 'mxchat_chatbot_section'
5572 );
5573
5574 add_settings_field(
5575 'enable_name_field',
5576 esc_html__('Require Name Field', 'mxchat'),
5577 array($this, 'enable_name_field_callback'),
5578 'mxchat-chatbot',
5579 'mxchat_chatbot_section'
5580 );
5581
5582 add_settings_field(
5583 'name_field_placeholder',
5584 esc_html__('Name Field Placeholder', 'mxchat'),
5585 array($this, 'name_field_placeholder_callback'),
5586 'mxchat-chatbot',
5587 'mxchat_chatbot_section'
5588 );
5589
5590 add_settings_field(
5591 'intro_message',
5592 esc_html__('Introductory Message', 'mxchat'),
5593 array($this, 'mxchat_intro_message_callback'),
5594 'mxchat-chatbot',
5595 'mxchat_chatbot_section'
5596 );
5597
5598 add_settings_field(
5599 'input_copy',
5600 esc_html__('Input Copy', 'mxchat'),
5601 array($this, 'mxchat_input_copy_callback'),
5602 'mxchat-chatbot',
5603 'mxchat_chatbot_section'
5604 );
5605
5606 add_settings_field(
5607 'pre_chat_message',
5608 esc_html__('Chat Teaser Pop-up', 'mxchat'),
5609 array($this, 'mxchat_pre_chat_message_callback'),
5610 'mxchat-chatbot',
5611 'mxchat_chatbot_section'
5612 );
5613
5614 add_settings_field(
5615 'privacy_toggle',
5616 esc_html__('Toggle Privacy Notice', 'mxchat'),
5617 array($this, 'mxchat_privacy_toggle_callback'),
5618 'mxchat-chatbot',
5619 'mxchat_chatbot_section'
5620 );
5621
5622 add_settings_field(
5623 'complianz_toggle',
5624 esc_html__('Enable Complianz', 'mxchat'),
5625 array($this, 'mxchat_complianz_toggle_callback'),
5626 'mxchat-chatbot',
5627 'mxchat_chatbot_section'
5628 );
5629
5630 add_settings_field(
5631 'link_target_toggle',
5632 esc_html__('Open Links in a New Tab', 'mxchat'),
5633 array($this, 'mxchat_link_target_toggle_callback'),
5634 'mxchat-chatbot',
5635 'mxchat_chatbot_section'
5636 );
5637
5638 add_settings_field(
5639 'chat_persistence_toggle',
5640 esc_html__('Enable Chat Persistence', 'mxchat'),
5641 array($this, 'mxchat_chat_persistence_toggle_callback'),
5642 'mxchat-chatbot',
5643 'mxchat_chatbot_section'
5644 );
5645
5646 add_settings_field(
5647 'popular_question_1',
5648 esc_html__('Quick Question 1', 'mxchat'),
5649 array($this, 'mxchat_popular_question_1_callback'),
5650 'mxchat-chatbot',
5651 'mxchat_chatbot_section'
5652 );
5653
5654 add_settings_field(
5655 'popular_question_2',
5656 esc_html__('Quick Question 2', 'mxchat'),
5657 array($this, 'mxchat_popular_question_2_callback'),
5658 'mxchat-chatbot',
5659 'mxchat_chatbot_section'
5660 );
5661
5662 add_settings_field(
5663 'popular_question_3',
5664 esc_html__('Quick Question 3', 'mxchat'),
5665 array($this, 'mxchat_popular_question_3_callback'),
5666 'mxchat-chatbot',
5667 'mxchat_chatbot_section'
5668 );
5669
5670 add_settings_field(
5671 'additional_popular_questions',
5672 esc_html__('Additional Quick Questions', 'mxchat'),
5673 array($this, 'mxchat_additional_popular_questions_callback'),
5674 'mxchat-chatbot',
5675 'mxchat_chatbot_section'
5676 );
5677
5678
5679 add_settings_field(
5680 'rate_limits',
5681 __('Rate Limits Settings', 'mxchat'),
5682 array($this, 'mxchat_rate_limits_callback'),
5683 'mxchat-chatbot',
5684 'mxchat_chatbot_section'
5685 );
5686
5687 // Loops Settings Section
5688 add_settings_section(
5689 'mxchat_loops_section',
5690 esc_html__('Loops Settings', 'mxchat'),
5691 null,
5692 'mxchat-embed'
5693 );
5694
5695 // Loops Settings Fields (API Key moved to API Keys tab)
5696 add_settings_field(
5697 'loops_mailing_list',
5698 esc_html__('Loops Mailing List', 'mxchat'),
5699 array($this, 'mxchat_loops_mailing_list_callback'),
5700 'mxchat-embed',
5701 'mxchat_loops_section'
5702 );
5703
5704 add_settings_field(
5705 'triggered_phrase_response',
5706 esc_html__('Triggered Phrase Response', 'mxchat'),
5707 array($this, 'mxchat_triggered_phrase_response_callback'),
5708 'mxchat-embed',
5709 'mxchat_loops_section'
5710 );
5711
5712 add_settings_field(
5713 'email_capture_response',
5714 esc_html__('Email Capture Response', 'mxchat'),
5715 array($this, 'mxchat_email_capture_response_callback'),
5716 'mxchat-embed',
5717 'mxchat_loops_section'
5718 );
5719
5720 // Brave Search Settings Fields
5721 add_settings_section(
5722 'mxchat_brave_section',
5723 __('Brave Search Settings', 'mxchat'),
5724 array($this, 'mxchat_brave_section_callback'),
5725 'mxchat-embed'
5726 );
5727
5728 // Brave API Key moved to API Keys tab
5729 add_settings_field(
5730 'brave_image_count',
5731 __('Number of Images to Return', 'mxchat'),
5732 array($this, 'mxchat_brave_image_count_callback'),
5733 'mxchat-embed',
5734 'mxchat_brave_section'
5735 );
5736
5737 add_settings_field(
5738 'brave_safe_search',
5739 __('Safe Search', 'mxchat'),
5740 array($this, 'mxchat_brave_safe_search_callback'),
5741 'mxchat-embed',
5742 'mxchat_brave_section'
5743 );
5744
5745 add_settings_field(
5746 'brave_news_count',
5747 __('Number of News Articles', 'mxchat'),
5748 array($this, 'mxchat_brave_news_count_callback'),
5749 'mxchat-embed',
5750 'mxchat_brave_section'
5751 );
5752
5753 add_settings_field(
5754 'brave_country',
5755 __('Country', 'mxchat'),
5756 array($this, 'mxchat_brave_country_callback'),
5757 'mxchat-embed',
5758 'mxchat_brave_section'
5759 );
5760
5761 add_settings_field(
5762 'brave_language',
5763 __('Language', 'mxchat'),
5764 array($this, 'mxchat_brave_language_callback'),
5765 'mxchat-embed',
5766 'mxchat_brave_section'
5767 );
5768
5769 // Chat with PDF Intent Settings Fields
5770 add_settings_section(
5771 'mxchat_pdf_intent_section',
5772 __('Toolbar Settings & Intents', 'mxchat'),
5773 array($this, 'mxchat_pdf_intent_section_callback'),
5774 'mxchat-embed'
5775 );
5776
5777 add_settings_field(
5778 'chat_toolbar_toggle',
5779 __('Show Chat Toolbar', 'mxchat'),
5780 array($this, 'mxchat_chat_toolbar_toggle_callback'),
5781 'mxchat-embed',
5782 'mxchat_pdf_intent_section'
5783 );
5784
5785 // PDF Upload Button Toggle
5786 add_settings_field(
5787 'show_pdf_upload_button',
5788 __('Show PDF Upload Button', 'mxchat'),
5789 array($this, 'mxchat_show_pdf_upload_button_callback'),
5790 'mxchat-embed',
5791 'mxchat_pdf_intent_section'
5792 );
5793
5794 // Word Upload Button Toggle
5795 add_settings_field(
5796 'show_word_upload_button',
5797 __('Show Word Upload Button', 'mxchat'),
5798 array($this, 'mxchat_show_word_upload_button_callback'),
5799 'mxchat-embed',
5800 'mxchat_pdf_intent_section'
5801 );
5802
5803 add_settings_field(
5804 'pdf_intent_trigger_text',
5805 __('Intent Trigger Text', 'mxchat'),
5806 array($this, 'mxchat_pdf_intent_trigger_text_callback'),
5807 'mxchat-embed',
5808 'mxchat_pdf_intent_section'
5809 );
5810
5811 add_settings_field(
5812 'pdf_intent_success_text',
5813 __('Success Text', 'mxchat'),
5814 array($this, 'mxchat_pdf_intent_success_text_callback'),
5815 'mxchat-embed',
5816 'mxchat_pdf_intent_section'
5817 );
5818
5819 add_settings_field(
5820 'pdf_intent_error_text',
5821 __('Error Text', 'mxchat'),
5822 array($this, 'mxchat_pdf_intent_error_text_callback'),
5823 'mxchat-embed',
5824 'mxchat_pdf_intent_section'
5825 );
5826
5827 // Add PDF Maximum Pages Field
5828 add_settings_field(
5829 'pdf_max_pages',
5830 __('Maximum Document Pages', 'mxchat'),
5831 array($this, 'mxchat_pdf_max_pages_callback'),
5832 'mxchat-embed',
5833 'mxchat_pdf_intent_section'
5834 );
5835
5836 // Live Agent Settings Fields
5837 add_settings_section(
5838 'mxchat_live_agent_section',
5839 __('Live Agent Settings', 'mxchat'),
5840 array($this, 'mxchat_live_agent_section_callback'),
5841 'mxchat-embed'
5842 );
5843
5844 // Live Agent Status Fields (add at top of live agent settings)
5845 add_settings_field(
5846 'live_agent_status',
5847 __('Live Agent Status', 'mxchat'),
5848 array($this, 'mxchat_live_agent_status_callback'),
5849 'mxchat-embed',
5850 'mxchat_live_agent_section'
5851 );
5852
5853 add_settings_field(
5854 'live_agent_notification_message',
5855 __('Notification Message', 'mxchat'),
5856 array($this, 'mxchat_live_agent_notification_message_callback'),
5857 'mxchat-embed',
5858 'mxchat_live_agent_section'
5859 );
5860
5861 add_settings_field(
5862 'live_agent_away_message',
5863 __('Away Message', 'mxchat'),
5864 array($this, 'mxchat_live_agent_away_message_callback'),
5865 'mxchat-embed',
5866 'mxchat_live_agent_section'
5867 );
5868
5869 add_settings_field(
5870 'live_agent_user_ids',
5871 __('Slack Agent User IDs', 'mxchat'),
5872 array($this, 'mxchat_live_agent_user_ids_callback'),
5873 'mxchat-embed',
5874 'mxchat_live_agent_section'
5875 );
5876
5877 add_settings_field(
5878 'live_agent_webhook_url',
5879 __('Slack Webhook URL', 'mxchat'),
5880 array($this, 'mxchat_live_agent_webhook_url_callback'),
5881 'mxchat-embed',
5882 'mxchat_live_agent_section'
5883 );
5884
5885 add_settings_field(
5886 'live_agent_secret_key',
5887 __('Slack Secret Key', 'mxchat'),
5888 array($this, 'mxchat_live_agent_secret_key_callback'),
5889 'mxchat-embed',
5890 'mxchat_live_agent_section'
5891 );
5892
5893 // Live Agent Integration Fields
5894 add_settings_field(
5895 'live_agent_bot_token',
5896 __('Slack Bot OAuth Token', 'mxchat'),
5897 array($this, 'mxchat_live_agent_bot_token_callback'),
5898 'mxchat-embed',
5899 'mxchat_live_agent_section'
5900 );
5901
5902
5903
5904
5905 // General Settings Section
5906 add_settings_section(
5907 'mxchat_general_section',
5908 esc_html__('YouTube Tutorials', 'mxchat'),
5909 null,
5910 'mxchat-general'
5911 );
5912 }
5913
5914 public function mxchat_prompts_page_init() {
5915 register_setting(
5916 'mxchat_prompts_options',
5917 'mxchat_prompts_options',
5918 array(
5919 'type' => 'array',
5920 'description' => __('MXChat Knowledge Base Settings', 'mxchat'),
5921 'default' => array(
5922 'mxchat_auto_sync_posts' => 0,
5923 'mxchat_auto_sync_pages' => 0,
5924 'mxchat_use_pinecone' => 0,
5925 'mxchat_pinecone_api_key' => '',
5926 'mxchat_pinecone_environment' => '',
5927 'mxchat_pinecone_index' => '',
5928 'mxchat_pinecone_host' => '',
5929 ),
5930 'sanitize_callback' => array($this, 'sanitize_prompts_options'),
5931 )
5932 );
5933
5934 add_action('admin_notices', array($this, 'sync_settings_notice'));
5935 }
5936
5937 public function mxchat_transcripts_page_init() {
5938 register_setting(
5939 'mxchat_transcripts_options',
5940 'mxchat_transcripts_options',
5941 array(
5942 'type' => 'array',
5943 'description' => __('MXChat Transcripts Notification Settings', 'mxchat'),
5944 'default' => array(
5945 'mxchat_enable_notifications' => 0,
5946 'mxchat_notification_email' => get_option('admin_email'),
5947 'mxchat_auto_delete_transcripts' => 'never',
5948 ),
5949 'sanitize_callback' => array($this, 'sanitize_transcripts_options'),
5950 )
5951 );
5952
5953 add_settings_section(
5954 'mxchat_transcripts_notification_section',
5955 esc_html__('Chat Notification Settings', 'mxchat'),
5956 array($this, 'mxchat_transcripts_notification_section_callback'),
5957 'mxchat-transcripts'
5958 );
5959
5960 add_settings_field(
5961 'mxchat_enable_notifications',
5962 esc_html__('Enable Chat Notifications', 'mxchat'),
5963 array($this, 'mxchat_enable_notifications_callback'),
5964 'mxchat-transcripts',
5965 'mxchat_transcripts_notification_section'
5966 );
5967
5968 add_settings_field(
5969 'mxchat_notification_email',
5970 esc_html__('Notification Email Address', 'mxchat'),
5971 array($this, 'mxchat_notification_email_callback'),
5972 'mxchat-transcripts',
5973 'mxchat_transcripts_notification_section'
5974 );
5975
5976 add_settings_field(
5977 'mxchat_auto_delete_transcripts',
5978 esc_html__('Auto-Delete Old Transcripts', 'mxchat'),
5979 array($this, 'mxchat_auto_delete_transcripts_callback'),
5980 'mxchat-transcripts',
5981 'mxchat_transcripts_notification_section'
5982 );
5983
5984 add_settings_field(
5985 'mxchat_auto_email_transcript',
5986 esc_html__('Auto-Email Full Transcript', 'mxchat'),
5987 array($this, 'mxchat_auto_email_transcript_callback'),
5988 'mxchat-transcripts',
5989 'mxchat_transcripts_notification_section'
5990 );
5991 }
5992
5993
5994 /**
5995 * Sanitize all prompts options
5996 *
5997 * @param array $input The unsanitized options array
5998 * @return array The sanitized options array
5999 */
6000 public function sanitize_prompts_options($input) {
6001 // Log the incoming input.
6002 //error_log('Sanitizing inputs: ' . print_r($input, true));
6003
6004 $sanitized = array();
6005
6006 // Boolean options
6007 $sanitized['mxchat_auto_sync_posts'] = isset($input['mxchat_auto_sync_posts']) ? 1 : 0;
6008 $sanitized['mxchat_auto_sync_pages'] = isset($input['mxchat_auto_sync_pages']) ? 1 : 0;
6009 $sanitized['mxchat_use_pinecone'] = !empty($input['mxchat_use_pinecone']) ? 1 : 0;
6010
6011 // API Key: if less than 32 characters, flag as invalid.
6012 $api_key = sanitize_text_field($input['mxchat_pinecone_api_key'] ?? '');
6013 if (!empty($api_key) && strlen($api_key) < 32) {
6014 add_settings_error(
6015 'mxchat_prompts_options',
6016 'invalid_api_key',
6017 __('The Pinecone API key appears to be invalid. Please check your API key.', 'mxchat')
6018 );
6019 $existing_options = get_option('mxchat_prompts_options', array());
6020 $sanitized['mxchat_pinecone_api_key'] = $existing_options['mxchat_pinecone_api_key'] ?? '';
6021 } else {
6022 $sanitized['mxchat_pinecone_api_key'] = $api_key;
6023 }
6024
6025 // Environment and Index Name
6026 $sanitized['mxchat_pinecone_environment'] = sanitize_text_field($input['mxchat_pinecone_environment'] ?? '');
6027 $sanitized['mxchat_pinecone_index'] = sanitize_text_field($input['mxchat_pinecone_index'] ?? '');
6028
6029 // Host: Remove protocol and validate format.
6030 $host = sanitize_text_field($input['mxchat_pinecone_host'] ?? '');
6031 $host = preg_replace('#^https?://#', '', $host);
6032 //error_log('Host after removing protocol: ' . $host);
6033 if (!empty($host)) {
6034 if (!preg_match('/^[\w-]+\.svc\.[\w-]+\.pinecone\.io$/', $host)) {
6035 add_settings_error(
6036 'mxchat_prompts_options',
6037 'invalid_host',
6038 __('The Pinecone host appears to be invalid. It should look like "mxchat-vectors-zrmsquq.svc.aped-4627-b74a.pinecone.io"', 'mxchat')
6039 );
6040 $existing_options = get_option('mxchat_prompts_options', array());
6041 $sanitized['mxchat_pinecone_host'] = $existing_options['mxchat_pinecone_host'] ?? '';
6042 } else {
6043 $sanitized['mxchat_pinecone_host'] = $host;
6044 }
6045 } else {
6046 $sanitized['mxchat_pinecone_host'] = '';
6047 }
6048
6049 //error_log('Final sanitized array: ' . print_r($sanitized, true));
6050
6051 return $sanitized;
6052 }
6053
6054 public function sync_settings_notice() {
6055 // Only show notice on our plugin page
6056 if (!isset($_GET['page']) || $_GET['page'] !== 'mxchat-prompts') {
6057 return;
6058 }
6059
6060 // Check if settings were updated
6061 if (isset($_GET['settings-updated'])) {
6062
6063 ?>
6064 <div class="notice notice-success is-dismissible">
6065 <p><?php esc_html_e('Sync settings updated successfully.', 'mxchat'); ?></p>
6066 </div>
6067 <?php
6068
6069 }
6070 }
6071 // Add this sanitization function to your class
6072 public function sanitize_sync_setting($input) {
6073 return (bool)$input ? __('1', 'mxchat') : __('', 'mxchat');
6074 }
6075
6076 public function mxchat_rate_limits_callback() {
6077 $all_options = get_option('mxchat_options', []);
6078
6079 // Define available rate limits
6080 $rate_limits = array('1', '3', '5', '10', '15', '20', '50', '100', 'unlimited');
6081
6082 // Define available timeframes
6083 $timeframes = array(
6084 'hourly' => __('Per Hour', 'mxchat'),
6085 'daily' => __('Per Day', 'mxchat'),
6086 'weekly' => __('Per Week', 'mxchat'),
6087 'monthly' => __('Per Month', 'mxchat')
6088 );
6089
6090 // Get all roles plus a "logged_out" pseudo-role
6091 $roles = wp_roles()->get_names();
6092 $roles['logged_out'] = __('Logged Out Users', 'mxchat');
6093
6094 // Start the wrapper
6095 echo '<div class="pro-feature-wrapper active">';
6096 echo '<div class="mxchat-rate-limits-container">';
6097
6098 echo '<p class="description" style="margin-bottom: 20px;">' .
6099 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') .
6100 '</p>';
6101
6102 // Add markdown link documentation
6103 echo '<div class="notice notice-info inline" style="margin-bottom: 20px; padding: 10px;">';
6104 echo '<p><strong>' . esc_html__('Markdown Links Supported:', 'mxchat') . '</strong></p>';
6105 echo '<p>' . esc_html__('You can include clickable links in your custom messages using markdown syntax:', 'mxchat') . '</p>';
6106 echo '<ul style="margin-left: 20px;">';
6107 echo '<li><code>[Link text](https://example.com)</code> - Creates a clickable link</li>';
6108 echo '<li><code>[Visit our pricing](https://example.com/pricing)</code> - Link with custom text</li>';
6109 echo '<li><code>Plain URLs like https://example.com will also become clickable</code></li>';
6110 echo '</ul>';
6111 echo '</div>';
6112
6113 // Output the controls for each role
6114 foreach ($roles as $role_id => $role_name) {
6115 // Get saved options or defaults
6116 $default_limit = ($role_id === 'logged_out') ? '10' : '100';
6117 $default_timeframe = 'daily';
6118 $default_message = __('Rate limit exceeded. Please try again later.', 'mxchat');
6119
6120 $selected_limit = isset($all_options['rate_limits'][$role_id]['limit'])
6121 ? $all_options['rate_limits'][$role_id]['limit']
6122 : $default_limit;
6123
6124 $selected_timeframe = isset($all_options['rate_limits'][$role_id]['timeframe'])
6125 ? $all_options['rate_limits'][$role_id]['timeframe']
6126 : $default_timeframe;
6127
6128 $custom_message = isset($all_options['rate_limits'][$role_id]['message'])
6129 ? $all_options['rate_limits'][$role_id]['message']
6130 : $default_message;
6131
6132 // Output the row
6133 echo '<div class="mxchat-rate-limit-row mxchat-autosave-section">';
6134
6135 // Role label
6136 echo '<div class="mxchat-rate-limit-role">' . esc_html($role_name) . '</div>';
6137
6138 // Controls section
6139 echo '<div class="mxchat-rate-limit-controls-wrapper">';
6140
6141 // Rate limit and timeframe controls
6142 echo '<div class="mxchat-rate-limit-controls">';
6143
6144 // Limit dropdown
6145 echo '<div>';
6146 echo '<label for="rate_limits_' . esc_attr($role_id) . '_limit">' . esc_html__('Limit:', 'mxchat') . '</label>';
6147 echo '<select
6148 id="rate_limits_' . esc_attr($role_id) . '_limit"
6149 name="mxchat_options[rate_limits][' . esc_attr($role_id) . '][limit]"
6150 class="mxchat-autosave-field">';
6151 foreach ($rate_limits as $limit) {
6152 echo '<option value="' . esc_attr($limit) . '" ' . selected($selected_limit, $limit, false) . '>' . esc_html($limit) . '</option>';
6153 }
6154 echo '</select>';
6155 echo '</div>';
6156
6157 // Timeframe dropdown
6158 echo '<div>';
6159 echo '<label for="rate_limits_' . esc_attr($role_id) . '_timeframe">' . esc_html__('Timeframe:', 'mxchat') . '</label>';
6160 echo '<select
6161 id="rate_limits_' . esc_attr($role_id) . '_timeframe"
6162 name="mxchat_options[rate_limits][' . esc_attr($role_id) . '][timeframe]"
6163 class="mxchat-autosave-field">';
6164 foreach ($timeframes as $value => $label) {
6165 echo '<option value="' . esc_attr($value) . '" ' . selected($selected_timeframe, $value, false) . '>' . esc_html($label) . '</option>';
6166 }
6167 echo '</select>';
6168 echo '</div>';
6169
6170 echo '</div>'; // End controls
6171
6172 // Custom message textarea
6173 echo '<div class="mxchat-rate-limit-message">';
6174 echo '<label for="rate_limits_' . esc_attr($role_id) . '_message">' . esc_html__('Custom Message:', 'mxchat') . '</label>';
6175 echo '<textarea
6176 id="rate_limits_' . esc_attr($role_id) . '_message"
6177 name="mxchat_options[rate_limits][' . esc_attr($role_id) . '][message]"
6178 class="mxchat-autosave-field"
6179 placeholder="' . esc_attr__('Enter custom message when rate limit is exceeded', 'mxchat') . '">' .
6180 esc_textarea($custom_message) .
6181 '</textarea>';
6182 echo '<p class="description">' .
6183 esc_html__('Example: Rate limit reached! [Visit our pricing page](https://example.com/pricing) to upgrade.', 'mxchat') .
6184 '</p>';
6185 echo '</div>'; // End message
6186
6187 echo '</div>'; // End controls wrapper
6188
6189 echo '</div>'; // End row
6190 }
6191
6192 echo '</div>'; // End container
6193
6194 echo '</div>'; // End pro-feature-wrapper
6195 }
6196
6197 private function mxchat_add_option_field($id, $title, $callback = '') {
6198 add_settings_field(
6199 $id,
6200 __($title, 'mxchat'),
6201 $callback ? array($this, $callback) : array($this, $id . '_callback'),
6202 'mxchat-max',
6203 'mxchat_setting_section_id',
6204 $id === 'model' ? ['label_for' => 'model'] : []
6205 );
6206 }
6207
6208 // API Keys Section Callback
6209 public function mxchat_api_keys_section_callback() {
6210 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>';
6211 }
6212
6213 // OpenAI API Key
6214 public function api_key_callback() {
6215 $apiKey = isset($this->options['api_key']) ? esc_attr($this->options['api_key']) : '';
6216
6217 echo '<div class="api-key-wrapper">';
6218 echo '<input type="password" id="api_key" name="api_key" value="' . $apiKey . '" class="regular-text" autocomplete="off" />';
6219 echo '<button type="button" id="toggleApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
6220 echo '<p class="description">' . esc_html__('Required for OpenAI GPT models and OpenAI embeddings. Get your API key from OpenAI Platform.', 'mxchat') . '</p>';
6221 echo '</div>';
6222 }
6223
6224 // X.AI API Key
6225 public function xai_api_key_callback() {
6226 $xaiApiKey = isset($this->options['xai_api_key']) ? esc_attr($this->options['xai_api_key']) : '';
6227
6228 echo '<div class="api-key-wrapper">';
6229 echo '<input type="password" id="xai_api_key" name="xai_api_key" value="' . $xaiApiKey . '" class="regular-text" autocomplete="off" />';
6230 echo '<button type="button" id="toggleXaiApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
6231 echo '<p class="description">' . esc_html__('Required for X.AI Grok models. Get your API key from X.AI Console.', 'mxchat') . '</p>';
6232 echo '</div>';
6233 }
6234 // Claude API Key
6235 public function claude_api_key_callback() {
6236 $claudeApiKey = isset($this->options['claude_api_key']) ? esc_attr($this->options['claude_api_key']) : '';
6237
6238 echo '<div class="api-key-wrapper">';
6239 echo '<input type="password" id="claude_api_key" name="claude_api_key" value="' . $claudeApiKey . '" class="regular-text" autocomplete="off" />';
6240 echo '<button type="button" id="toggleClaudeApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
6241 echo '<p class="description">' . esc_html__('Required for Anthropic Claude models. Get your API key from Anthropic Console.', 'mxchat') . '</p>';
6242 echo '</div>';
6243 }
6244
6245 // DeepSeek API Key
6246 public function deepseek_api_key_callback() {
6247 $apiKey = isset($this->options['deepseek_api_key']) ? esc_attr($this->options['deepseek_api_key']) : '';
6248
6249 echo '<div class="api-key-wrapper">';
6250 echo '<input type="password" id="deepseek_api_key" name="deepseek_api_key" value="' . $apiKey . '" class="regular-text" autocomplete="off" />';
6251 echo '<button type="button" id="toggleDeepSeekApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
6252 echo '<p class="description">' . esc_html__('Required for DeepSeek models. Get your API key from DeepSeek Platform.', 'mxchat') . '</p>';
6253 echo '</div>';
6254 }
6255
6256 // Gemini API Key
6257 public function gemini_api_key_callback() {
6258 $geminiApiKey = isset($this->options['gemini_api_key']) ? esc_attr($this->options['gemini_api_key']) : '';
6259
6260 echo '<div class="api-key-wrapper">';
6261 echo '<input type="password" id="gemini_api_key" name="gemini_api_key" value="' . $geminiApiKey . '" class="regular-text" autocomplete="off" />';
6262 echo '<button type="button" id="toggleGeminiApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
6263 echo '<p class="description">' . esc_html__('Required for Google Gemini models and embeddings. Get your API key from Google AI Studio.', 'mxchat') . '</p>';
6264 echo '</div>';
6265 }
6266
6267
6268 // OpenRouter API Key
6269 public function openrouter_api_key_callback() {
6270 $openrouterApiKey = isset($this->options['openrouter_api_key']) ? esc_attr($this->options['openrouter_api_key']) : '';
6271 echo '<div class="api-key-wrapper">';
6272 echo '<input type="password" id="openrouter_api_key" name="openrouter_api_key" value="' . $openrouterApiKey . '" class="regular-text" autocomplete="off" />';
6273 echo '<button type="button" id="toggleOpenRouterApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
6274 echo '<p class="description">' . esc_html__('Required for OpenRouter models. Get your API key from OpenRouter.ai', 'mxchat') . '</p>';
6275 echo '</div>';
6276 }
6277
6278 // Voyage API Key
6279 public function voyage_api_key_callback() {
6280 $apiKey = isset($this->options['voyage_api_key']) ? esc_attr($this->options['voyage_api_key']) : '';
6281
6282 echo '<div class="api-key-wrapper">';
6283 echo '<input type="password" id="voyage_api_key" name="voyage_api_key" value="' . $apiKey . '" class="regular-text" autocomplete="off" />';
6284 echo '<button type="button" id="toggleVoyageAPIKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
6285 echo '<p class="description">' . esc_html__('Required for Voyage AI embedding models. Get your API key from Voyage AI.', 'mxchat') . '</p>';
6286 echo '</div>';
6287 }
6288
6289 public function mxchat_loops_api_key_callback() {
6290 $loops_api_key = isset($this->options['loops_api_key']) ? esc_attr($this->options['loops_api_key']) : '';
6291
6292 // Hidden fields to "trap" autofill
6293 echo '<input type="text" style="display:none" autocomplete="username" />';
6294 echo '<input type="password" style="display:none" autocomplete="current-password" />';
6295
6296 echo '<div class="api-key-wrapper">';
6297 echo sprintf(
6298 '<input type="password" id="loops_api_key" name="loops_api_key" value="%s" class="regular-text" autocomplete="new-password" />',
6299 $loops_api_key
6300 );
6301 echo '<button type="button" id="toggleLoopsApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
6302 echo '</div>';
6303 echo '<p class="description">' . esc_html__('Required for Loops email integration. Get your API key from Loops.so', 'mxchat') . '</p>';
6304 }
6305 public function mxchat_loops_mailing_list_callback() {
6306 // Add error handling and type checking
6307 $loops_api_key = '';
6308 $selected_list = '';
6309
6310 // Safely get the API key
6311 if (isset($this->options['loops_api_key']) && is_string($this->options['loops_api_key'])) {
6312 $loops_api_key = $this->options['loops_api_key'];
6313 }
6314
6315 // Safely get the selected list
6316 if (isset($this->options['loops_mailing_list']) && is_string($this->options['loops_mailing_list'])) {
6317 $selected_list = $this->options['loops_mailing_list'];
6318 }
6319
6320 if (!empty($loops_api_key)) {
6321 $lists = $this->mxchat_fetch_loops_mailing_lists($loops_api_key);
6322 if (is_array($lists) && !empty($lists)) {
6323 echo '<select id="loops_mailing_list" name="loops_mailing_list">';
6324
6325 // Add a default "Select a list" option
6326 echo '<option value="" ' . selected($selected_list, '', false) . '>' . esc_html__('Select a list', 'mxchat') . '</option>';
6327
6328 foreach ($lists as $list) {
6329 if (is_array($list) && isset($list['id']) && isset($list['name'])) {
6330 echo sprintf(
6331 '<option value="%s" %s>%s</option>',
6332 esc_attr($list['id']),
6333 selected($selected_list, $list['id'], false),
6334 esc_html($list['name'])
6335 );
6336 }
6337 }
6338 echo '</select>';
6339 echo '<p class="description">' . esc_html__('Please select a mailing list to use with Loops.', 'mxchat') . '</p>';
6340 } else {
6341 echo '<p class="description">' . esc_html__('No lists found. Please verify your API Key.', 'mxchat') . '</p>';
6342 }
6343 } else {
6344 echo '<p class="description">' . esc_html__('Enter a valid Loops API Key to load mailing lists.', 'mxchat') . '</p>';
6345 }
6346 }
6347 public function mxchat_triggered_phrase_response_callback() {
6348 $default_response = __('Would you like to join our mailing list? Please provide your email below.', 'mxchat');
6349 $triggered_response = isset($this->options['triggered_phrase_response'])
6350 ? $this->options['triggered_phrase_response']
6351 : $default_response;
6352 echo sprintf(
6353 '<textarea id="triggered_phrase_response" name="triggered_phrase_response" rows="3" cols="50">%s</textarea>',
6354 esc_textarea($triggered_response)
6355 );
6356 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>';
6357 }
6358
6359 public function mxchat_email_capture_response_callback() {
6360 $default_response = __('Thank you for providing your email! You\'ve been added to our list.', 'mxchat');
6361 $email_capture_response = isset($this->options['email_capture_response'])
6362 ? $this->options['email_capture_response']
6363 : $default_response;
6364 echo sprintf(
6365 '<textarea id="email_capture_response" name="email_capture_response" rows="3" cols="50">%s</textarea>',
6366 esc_textarea($email_capture_response)
6367 );
6368 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>';
6369 }
6370 public function mxchat_pre_chat_message_callback() {
6371 // Load the entire 'mxchat_options' array
6372 $all_options = get_option('mxchat_options', []);
6373
6374 // Retrieve the saved message or use the default value
6375 $default_message = __('Hey there! Ask me anything!', 'mxchat');
6376 $pre_chat_message = isset($all_options['pre_chat_message']) ? $all_options['pre_chat_message'] : $default_message;
6377
6378 // Output the textarea
6379 printf(
6380 '<textarea id="pre_chat_message" name="pre_chat_message" rows="5" cols="50">%s</textarea>',
6381 esc_textarea($pre_chat_message)
6382 );
6383 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>';
6384 }
6385
6386 // Callback for AI Instructions textarea
6387 public function system_prompt_instructions_callback() {
6388 // Retrieve the current value of the system prompt instructions
6389 $instructions = isset($this->options['system_prompt_instructions']) ? esc_textarea($this->options['system_prompt_instructions']) : '';
6390 // Render the textarea field
6391 printf(
6392 '<textarea id="system_prompt_instructions" name="system_prompt_instructions" rows="5" cols="50">%s</textarea>',
6393 $instructions
6394 );
6395 // Provide a helpful description with sample instructions button
6396 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>';
6397 echo '<div class="mxchat-instructions-container">';
6398 echo '<button type="button" class="mxchat-instructions-btn" id="mxchatViewSampleBtn">';
6399 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">';
6400 echo '<path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/>';
6401 echo '<path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/>';
6402 echo '</svg>';
6403 echo esc_html__('View Sample Instructions', 'mxchat');
6404 echo '</button>';
6405 echo '</div>';
6406
6407 // Add modal to WordPress admin footer instead of inline
6408 add_action('admin_footer', array($this, 'render_sample_instructions_modal'));
6409 }
6410
6411 // New method to render modal in admin footer
6412 public function render_sample_instructions_modal() {
6413 static $modal_rendered = false;
6414 if ($modal_rendered) return; // Prevent duplicate modals
6415 $modal_rendered = true;
6416
6417 echo '<div class="mxchat-instructions-modal-overlay" id="mxchatSampleModal">';
6418 echo '<div class="mxchat-instructions-modal-content">';
6419 echo '<div class="mxchat-instructions-modal-header">';
6420 echo '<h3 class="mxchat-instructions-modal-title">';
6421 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">';
6422 echo '<path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/>';
6423 echo '<path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/>';
6424 echo '</svg>';
6425 echo esc_html__('Sample AI Instructions', 'mxchat');
6426 echo '</h3>';
6427 echo '<button type="button" class="mxchat-instructions-modal-close" id="mxchatModalClose">';
6428 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">';
6429 echo '<line x1="18" y1="6" x2="6" y2="18"/>';
6430 echo '<line x1="6" y1="6" x2="18" y2="18"/>';
6431 echo '</svg>';
6432 echo '</button>';
6433 echo '</div>';
6434 echo '<div class="mxchat-instructions-modal-body">';
6435 echo '<div class="mxchat-instructions-content">';
6436 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:
6437
6438 # Response Style - CRITICALLY IMPORTANT
6439 - MAXIMUM LENGTH: 1-3 short sentences per response
6440 - Ultra-concise: Get straight to the answer with no filler
6441 - No introductions like "Sure!" or "I\'d be happy to help"
6442 - No phrases like "based on my knowledge" or "according to information"
6443 - No explanatory text before giving the answer
6444 - No summaries or repetition
6445 - Hyperlink all URLs
6446 - Respond in user\'s language
6447 - Minor chit chat or conversation is okay, but try to keep it focused on [insert topic]
6448
6449 # Knowledge Base Requirements - PREVENT HALLUCINATIONS
6450 - ONLY answer using information explicitly provided in OFFICIAL KNOWLEDGE DATABASE CONTENT sections marked with ===== delimiters
6451 - If required information is NOT in the knowledge database: "I don\'t have enough information in my knowledge base to answer that question accurately."
6452 - NEVER invent or hallucinate URLs, links, product specs, procedures, dates, statistics, names, contacts, or company information
6453 - When knowledge base information is unclear or contradictory, acknowledge the limitation rather than guessing
6454 - Better to admit insufficient information than provide inaccurate answers');
6455 echo '</div>';
6456 echo '<button type="button" class="mxchat-instructions-copy-btn" id="mxchatCopyBtn">';
6457 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">';
6458 echo '<rect x="9" y="9" width="13" height="13" rx="2" ry="2"/>';
6459 echo '<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/>';
6460 echo '</svg>';
6461 echo esc_html__('Copy Instructions', 'mxchat');
6462 echo '</button>';
6463 echo '</div>';
6464 echo '<div class="mxchat-instructions-modal-footer">';
6465 echo '<button type="button" class="mxchat-instructions-btn-secondary" id="mxchatCloseBtn">' . esc_html__('Close', 'mxchat') . '</button>';
6466 echo '</div>';
6467 echo '</div>';
6468 echo '</div>';
6469 }
6470
6471
6472 public function mxchat_model_callback() {
6473 // Define available models grouped by provider
6474 $models = array(
6475 esc_html__('OpenRouter (100+ Models)', 'mxchat') => array(
6476 'openrouter' => esc_html__('OpenRouter - Select after entering API key', 'mxchat'),
6477 ),
6478 esc_html__('Google Gemini Models', 'mxchat') => array(
6479 'gemini-2.0-flash' => esc_html__('Gemini 2.0 Flash (Next-Gen Features)', 'mxchat'),
6480 'gemini-2.0-flash-lite' => esc_html__('Gemini 2.0 Flash-Lite (Cost-Efficient)', 'mxchat'),
6481 'gemini-1.5-pro' => esc_html__('Gemini 1.5 Pro (Complex Reasoning)', 'mxchat'),
6482 'gemini-1.5-flash' => esc_html__('Gemini 1.5 Flash (Fast & Versatile)', 'mxchat'),
6483 ),
6484 esc_html__('Google Gemini Models', 'mxchat') => array(
6485 'gemini-2.0-flash' => esc_html__('Gemini 2.0 Flash (Next-Gen Features)', 'mxchat'),
6486 'gemini-2.0-flash-lite' => esc_html__('Gemini 2.0 Flash-Lite (Cost-Efficient)', 'mxchat'),
6487 'gemini-1.5-pro' => esc_html__('Gemini 1.5 Pro (Complex Reasoning)', 'mxchat'),
6488 'gemini-1.5-flash' => esc_html__('Gemini 1.5 Flash (Fast & Versatile)', 'mxchat'),
6489 ),
6490 esc_html__('X.AI Models', 'mxchat') => array(
6491 'grok-3-beta' => esc_html__('Grok-3 (Powerful)', 'mxchat'),
6492 'grok-3-fast-beta' => esc_html__('Grok-3 Fast (High Performance)', 'mxchat'),
6493 'grok-3-mini-beta' => esc_html__('Grok-3 Mini (Affordable)', 'mxchat'),
6494 'grok-3-mini-fast-beta' => esc_html__('Grok-3 Mini Fast (Quick Response)', 'mxchat'),
6495 'grok-2' => esc_html__('Grok 2', 'mxchat'),
6496 'grok-4-0709' => esc_html__('Grok 4 (Latest Flagship)', 'mxchat'),
6497 'grok-4-1-fast-reasoning' => esc_html__('Grok 4.1 Fast (Reasoning)', 'mxchat'),
6498 'grok-4-1-fast-non-reasoning' => esc_html__('Grok 4.1 Fast (Non-Reasoning)', 'mxchat'),
6499 ),
6500 esc_html__('DeepSeek Models', 'mxchat') => array(
6501 'deepseek-chat' => esc_html__('DeepSeek-V3', 'mxchat'),
6502 ),
6503 esc_html__('Claude Models', 'mxchat') => array(
6504 'claude-sonnet-4-5-20250929' => esc_html__('Claude Sonnet 4.5 (Best for Agents & Coding)', 'mxchat'),
6505 'claude-opus-4-1-20250805' => esc_html__('Claude Opus 4.1 (Exceptional for Complex Tasks)', 'mxchat'),
6506 'claude-haiku-4-5-20251001' => esc_html__('Claude Haiku 4.5 (Fastest & Most Intelligent)', 'mxchat'),
6507 'claude-opus-4-20250514' => esc_html__('Claude 4 Opus (Most Capable)', 'mxchat'),
6508 'claude-sonnet-4-20250514' => esc_html__('Claude 4 Sonnet (High Performance)', 'mxchat'),
6509 'claude-3-7-sonnet-20250219' => esc_html__('Claude 3.7 Sonnet (High Intelligence)', 'mxchat'),
6510 'claude-3-opus-20240229' => esc_html__('Claude 3 Opus (Complex Tasks)', 'mxchat'),
6511 'claude-3-sonnet-20240229' => esc_html__('Claude 3 Sonnet (Balanced)', 'mxchat'),
6512 'claude-3-haiku-20240307' => esc_html__('Claude 3 Haiku (Fastest)', 'mxchat'),
6513 ),
6514 esc_html__('OpenAI Models', 'mxchat') => array(
6515 // GPT-5 family
6516 'gpt-5.2' => esc_html__('GPT-5.2 (Best General-Purpose & Agentic Model)', 'mxchat'),
6517 'gpt-5.1-2025-11-13' => esc_html__('GPT-5.1 (Flagship for Coding & Agentic Tasks)', 'mxchat'),
6518 'gpt-5' => esc_html__('GPT-5 (Flagship for Coding, Reasoning & Agents)', 'mxchat'),
6519 'gpt-5-mini' => esc_html__('GPT-5 Mini (Faster, Cost-Efficient for Precise Prompts)', 'mxchat'),
6520 'gpt-5-nano' => esc_html__('GPT-5 Nano (Fastest & Cheapest for Summarization/Classification)', 'mxchat'),
6521
6522 // Existing OpenAI models
6523 'gpt-4.1-2025-04-14' => esc_html__('GPT-4.1 (Flagship for Complex Tasks)', 'mxchat'),
6524 'gpt-4o' => esc_html__('GPT-4o (Recommended)', 'mxchat'),
6525 'gpt-4o-mini' => esc_html__('GPT-4o Mini (Fast and Lightweight)', 'mxchat'),
6526 'gpt-4-turbo' => esc_html__('GPT-4 Turbo (High-Performance)', 'mxchat'),
6527 'gpt-4' => esc_html__('GPT-4 (High Intelligence)', 'mxchat'),
6528 'gpt-3.5-turbo' => esc_html__('GPT-3.5 Turbo (Affordable and Fast)', 'mxchat'),
6529 ),
6530 );
6531
6532 // Retrieve the currently selected model from saved options
6533 $selected_model = isset($this->options['model']) ? esc_attr($this->options['model']) : 'gpt-4o';
6534
6535 // Begin the select dropdown
6536 echo '<select id="model" name="model">';
6537
6538 // Iterate over groups of models
6539 foreach ($models as $group_label => $group_models) {
6540 echo '<optgroup label="' . esc_attr($group_label) . '">';
6541
6542 foreach ($group_models as $model_value => $model_label) {
6543 echo '<option value="' . esc_attr($model_value) . '" ' . selected($selected_model, $model_value, false) . '>' . esc_html($model_label) . '</option>';
6544 }
6545
6546 echo '</optgroup>';
6547 }
6548
6549 echo '</select>';
6550
6551 echo '<p class="description">' . esc_html__('Select the AI model your chatbot will use for chatting.', 'mxchat') . '</p>';
6552
6553 // Add a note for OpenRouter
6554 echo '<p class="description" id="openrouter-model-note" style="display:none; color: #d63638; font-weight: 500;">';
6555 echo '<span class="dashicons dashicons-info" style="font-size: 16px; vertical-align: middle;"></span> ';
6556 echo esc_html__('After entering your OpenRouter API key above, click the button below to load available models.', 'mxchat');
6557 echo '</p>';
6558
6559 // API Key Status Messages (hidden by default, shown by JS based on selected model)
6560 $has_openai_key = !empty($this->options['api_key']);
6561 $has_claude_key = !empty($this->options['claude_api_key']);
6562 $has_xai_key = !empty($this->options['xai_api_key']);
6563 $has_deepseek_key = !empty($this->options['deepseek_api_key']);
6564 $has_gemini_key = !empty($this->options['gemini_api_key']);
6565 $has_openrouter_key = !empty($this->options['openrouter_api_key']);
6566
6567 // OpenAI/GPT models
6568 echo '<p class="mxchat-api-status" data-provider="openai" style="display:none;">';
6569 if ($has_openai_key) {
6570 echo '<span style="color: #00a32a;">✓ ' . esc_html__('API key for OpenAI detected', 'mxchat') . '</span>';
6571 } else {
6572 echo '<span style="color: #d63638;">⚠ ' . esc_html__('No API key for OpenAI detected. Please enter API key in API Keys tab.', 'mxchat') . '</span>';
6573 }
6574 echo '</p>';
6575
6576 // Claude models
6577 echo '<p class="mxchat-api-status" data-provider="claude" style="display:none;">';
6578 if ($has_claude_key) {
6579 echo '<span style="color: #00a32a;">✓ ' . esc_html__('API key for Anthropic (Claude) detected', 'mxchat') . '</span>';
6580 } else {
6581 echo '<span style="color: #d63638;">⚠ ' . esc_html__('No API key for Anthropic (Claude) detected. Please enter API key in API Keys tab.', 'mxchat') . '</span>';
6582 }
6583 echo '</p>';
6584
6585 // X.AI models
6586 echo '<p class="mxchat-api-status" data-provider="xai" style="display:none;">';
6587 if ($has_xai_key) {
6588 echo '<span style="color: #00a32a;">✓ ' . esc_html__('API key for X.AI (Grok) detected', 'mxchat') . '</span>';
6589 } else {
6590 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>';
6591 }
6592 echo '</p>';
6593
6594 // DeepSeek models
6595 echo '<p class="mxchat-api-status" data-provider="deepseek" style="display:none;">';
6596 if ($has_deepseek_key) {
6597 echo '<span style="color: #00a32a;">✓ ' . esc_html__('API key for DeepSeek detected', 'mxchat') . '</span>';
6598 } else {
6599 echo '<span style="color: #d63638;">⚠ ' . esc_html__('No API key for DeepSeek detected. Please enter API key in API Keys tab.', 'mxchat') . '</span>';
6600 }
6601 echo '</p>';
6602
6603 // Gemini models
6604 echo '<p class="mxchat-api-status" data-provider="gemini" style="display:none;">';
6605 if ($has_gemini_key) {
6606 echo '<span style="color: #00a32a;">✓ ' . esc_html__('API key for Google Gemini detected', 'mxchat') . '</span>';
6607 } else {
6608 echo '<span style="color: #d63638;">⚠ ' . esc_html__('No API key for Google Gemini detected. Please enter API key in API Keys tab.', 'mxchat') . '</span>';
6609 }
6610 echo '</p>';
6611
6612 // OpenRouter models
6613 echo '<p class="mxchat-api-status" data-provider="openrouter" style="display:none;">';
6614 if ($has_openrouter_key) {
6615 echo '<span style="color: #00a32a;">✓ ' . esc_html__('API key for OpenRouter detected', 'mxchat') . '</span>';
6616 } else {
6617 echo '<span style="color: #d63638;">⚠ ' . esc_html__('No API key for OpenRouter detected. Please enter API key in API Keys tab.', 'mxchat') . '</span>';
6618 }
6619 echo '</p>';
6620
6621 // ADD THESE HIDDEN FIELDS RIGHT HERE:
6622 $openrouter_model = isset($this->options['openrouter_selected_model']) ? esc_attr($this->options['openrouter_selected_model']) : '';
6623 $openrouter_model_name = isset($this->options['openrouter_selected_model_name']) ? esc_attr($this->options['openrouter_selected_model_name']) : '';
6624
6625 echo '<input type="hidden" id="openrouter_selected_model" name="openrouter_selected_model" value="' . $openrouter_model . '" />';
6626 echo '<input type="hidden" id="openrouter_selected_model_name" name="openrouter_selected_model_name" value="' . $openrouter_model_name . '" />';
6627 }
6628
6629 // Update your existing callback method
6630 public function enable_streaming_toggle_callback() {
6631 // Get value from options array, default to 'on'
6632 $enabled = isset($this->options['enable_streaming_toggle']) ? $this->options['enable_streaming_toggle'] : 'on';
6633 $checked = ($enabled === 'on') ? 'checked' : '';
6634
6635 echo '<label class="toggle-switch">';
6636 echo sprintf(
6637 '<input type="checkbox" id="enable_streaming_toggle" name="enable_streaming_toggle" value="on" %s />',
6638 esc_attr($checked)
6639 );
6640 echo '<span class="slider"></span>';
6641 echo '</label>';
6642 echo '<p class="description">' .
6643 esc_html__('Enable real-time streaming responses for supported models (OpenAI, Claude, DeepSeek, and Grok). When disabled, responses will load all at once.', 'mxchat') .
6644 '</p>';
6645
6646 // Test button with better styling
6647 echo '<div style="margin-top: 15px;">';
6648 echo '<button type="button" id="mxchat-test-streaming-btn" class="button button-secondary">';
6649 echo '<span class="dashicons dashicons-admin-tools" style="vertical-align: middle; margin-right: 5px;"></span>';
6650 echo esc_html__('Test Streaming Compatibility', 'mxchat');
6651 echo '</button>';
6652 echo '<p id="mxchat-test-streaming-result" style="margin-top:10px; font-weight: bold;"></p>';
6653 echo '</div>';
6654 }
6655
6656 // AJAX handler to fetch OpenRouter models
6657 public function fetch_openrouter_models() {
6658 check_ajax_referer('mxchat_fetch_openrouter_models', 'nonce');
6659
6660 $api_key = isset($_POST['api_key']) ? sanitize_text_field($_POST['api_key']) : '';
6661
6662 if (empty($api_key)) {
6663 wp_send_json_error(array('message' => 'API key is required'));
6664 }
6665
6666 $response = wp_remote_get('https://openrouter.ai/api/v1/models', array(
6667 'headers' => array(
6668 'Authorization' => 'Bearer ' . $api_key,
6669 'Content-Type' => 'application/json',
6670 ),
6671 'timeout' => 15,
6672 ));
6673
6674 if (is_wp_error($response)) {
6675 wp_send_json_error(array('message' => $response->get_error_message()));
6676 }
6677
6678 $body = wp_remote_retrieve_body($response);
6679 $data = json_decode($body, true);
6680
6681 if (isset($data['data']) && is_array($data['data'])) {
6682 // Format the models for the frontend
6683 $models = array_map(function($model) {
6684 return array(
6685 'id' => $model['id'],
6686 'name' => $model['name'] ?? $model['id'],
6687 'description' => $model['description'] ?? '',
6688 'context_length' => $model['context_length'] ?? 0,
6689 'pricing' => array(
6690 'prompt' => $model['pricing']['prompt'] ?? 0,
6691 'completion' => $model['pricing']['completion'] ?? 0,
6692 ),
6693 );
6694 }, $data['data']);
6695
6696 wp_send_json_success(array('models' => $models));
6697 } else {
6698 wp_send_json_error(array('message' => 'Invalid response from OpenRouter'));
6699 }
6700 }
6701
6702 // Callback function for embedding model selection
6703 public function embedding_model_callback() {
6704 $models = array(
6705 esc_html__('OpenAI Embeddings', 'mxchat') => array(
6706 'text-embedding-3-small' => esc_html__('TE3 Small (1536, Efficient)', 'mxchat'),
6707 'text-embedding-ada-002' => esc_html__('Ada 2 (1536, Recommended)', 'mxchat'),
6708 'text-embedding-3-large' => esc_html__('TE3 Large (3072, Powerful)', 'mxchat'),
6709 ),
6710 esc_html__('Voyage AI Embeddings', 'mxchat') => array(
6711 'voyage-3-large' => esc_html__('Voyage-3 Large (2048, Most Capable)', 'mxchat'),
6712 ),
6713 esc_html__('Google Gemini Embeddings', 'mxchat') => array(
6714 'gemini-embedding-exp-03-07' => esc_html__('Gemini Embedding (1536, Experimental)', 'mxchat'),
6715 )
6716 );
6717 $selected_model = isset($this->options['embedding_model']) ? esc_attr($this->options['embedding_model']) : 'text-embedding-ada-002';
6718 echo '<select id="embedding_model" name="embedding_model">';
6719 foreach ($models as $group_label => $group_models) {
6720 echo '<optgroup label="' . esc_attr($group_label) . '">';
6721 foreach ($group_models as $model_value => $model_label) {
6722 echo '<option value="' . esc_attr($model_value) . '" ' . selected($selected_model, $model_value, false) . '>' . esc_html($model_label) . '</option>';
6723 }
6724 echo '</optgroup>';
6725 }
6726 echo '</select>';
6727 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>';
6728
6729 // API Key Status Messages for Embedding Models
6730 $has_openai_key = !empty($this->options['api_key']);
6731 $has_voyage_key = !empty($this->options['voyage_api_key']);
6732 $has_gemini_key = !empty($this->options['gemini_api_key']);
6733
6734 // OpenAI Embeddings
6735 echo '<p class="mxchat-embedding-api-status" data-provider="openai" style="display:none;">';
6736 if ($has_openai_key) {
6737 echo '<span style="color: #00a32a;">✓ ' . esc_html__('API key for OpenAI detected', 'mxchat') . '</span>';
6738 } else {
6739 echo '<span style="color: #d63638;">⚠ ' . esc_html__('No API key for OpenAI detected. Please enter API key in API Keys tab.', 'mxchat') . '</span>';
6740 }
6741 echo '</p>';
6742
6743 // Voyage AI Embeddings
6744 echo '<p class="mxchat-embedding-api-status" data-provider="voyage" style="display:none;">';
6745 if ($has_voyage_key) {
6746 echo '<span style="color: #00a32a;">✓ ' . esc_html__('API key for Voyage AI detected', 'mxchat') . '</span>';
6747 } else {
6748 echo '<span style="color: #d63638;">⚠ ' . esc_html__('No API key for Voyage AI detected. Please enter API key in API Keys tab.', 'mxchat') . '</span>';
6749 }
6750 echo '</p>';
6751
6752 // Gemini Embeddings
6753 echo '<p class="mxchat-embedding-api-status" data-provider="gemini" style="display:none;">';
6754 if ($has_gemini_key) {
6755 echo '<span style="color: #00a32a;">✓ ' . esc_html__('API key for Google Gemini detected', 'mxchat') . '</span>';
6756 } else {
6757 echo '<span style="color: #d63638;">⚠ ' . esc_html__('No API key for Google Gemini detected. Please enter API key in API Keys tab.', 'mxchat') . '</span>';
6758 }
6759 echo '</p>';
6760 }
6761
6762
6763 public function mxchat_top_bar_title_callback() {
6764 // Retrieve the current value of the top bar title from saved options
6765 $top_bar_title = isset($this->options['top_bar_title']) ? esc_attr($this->options['top_bar_title']) : '';
6766
6767 // Render the input field
6768 echo '<input type="text" id="top_bar_title" name="top_bar_title" value="' . $top_bar_title . '" />';
6769
6770 // Add a description
6771 echo '<p class="description">' . esc_html__('Enter the title text that will appear on the top bar of the chatbot.', 'mxchat') . '</p>';
6772 }
6773 public function mxchat_ai_agent_text_callback() {
6774 // Retrieve the current value of the AI agent text from saved options
6775 $ai_agent_text = isset($this->options['ai_agent_text']) ? esc_attr($this->options['ai_agent_text']) : '';
6776 // Render the input field
6777 echo '<input type="text" id="ai_agent_text" name="ai_agent_text" value="' . $ai_agent_text . '" />';
6778 // Add a description
6779 echo '<p class="description">' . esc_html__('Enter the text that will appear for AI agents in the status indicator. Default: "AI Agent"', 'mxchat') . '</p>';
6780 }
6781
6782
6783 public function enable_email_block_callback() {
6784 // Load full plugin options array
6785 $all_options = get_option('mxchat_options', []);
6786
6787 // Get the value, default to 'off'
6788 $enable_email_block = isset($all_options['enable_email_block']) ? $all_options['enable_email_block'] : 'off';
6789
6790 // Check if it's 'on'
6791 $checked = ($enable_email_block === 'on') ? 'checked' : '';
6792
6793 echo '<label class="toggle-switch">';
6794 echo sprintf(
6795 '<input type="checkbox" id="enable_email_block" name="enable_email_block" value="on" %s />',
6796 esc_attr($checked)
6797 );
6798 echo '<span class="slider"></span>';
6799 echo '</label>';
6800 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>';
6801 }
6802
6803 public function email_blocker_header_content_callback() {
6804 // Load the entire 'mxchat_options' array
6805 $all_options = get_option('mxchat_options', []);
6806
6807 // Retrieve the saved content or default to empty
6808 $content = isset($all_options['email_blocker_header_content'])
6809 ? $all_options['email_blocker_header_content']
6810 : '';
6811
6812 // Render the textarea - IMPORTANT: name should be just "email_blocker_header_content"
6813 echo '<textarea
6814 id="email_blocker_header_content"
6815 name="email_blocker_header_content"
6816 rows="5"
6817 cols="70"
6818 data-setting="email_blocker_header_content"
6819 >' . esc_textarea($content) . '</textarea>';
6820
6821 echo '<p class="description">';
6822 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');
6823 echo '</p>';
6824 }
6825
6826 public function email_blocker_button_text_callback() {
6827 // Load the entire 'mxchat_options' array
6828 $all_options = get_option('mxchat_options', []);
6829
6830 // Retrieve the saved button text or default to empty
6831 $button_text = isset($all_options['email_blocker_button_text'])
6832 ? $all_options['email_blocker_button_text']
6833 : '';
6834
6835 // Use esc_attr to safely render the existing text
6836 echo '<input type="text" id="email_blocker_button_text" name="email_blocker_button_text" value="' . esc_attr($button_text) . '" style="width: 300px;" />';
6837
6838 echo '<p class="description">';
6839 echo esc_html__('Enter the text you want on the submit button, e.g. "Start Chat".', 'mxchat');
6840 echo '</p>';
6841 }
6842
6843 //Enable name field callback
6844 public function enable_name_field_callback() {
6845 // Load full plugin options array
6846 $all_options = get_option('mxchat_options', []);
6847 // Get the value, default to 'off'
6848 $enable_name_field = isset($all_options['enable_name_field']) ? $all_options['enable_name_field'] : 'off';
6849 // Check if it's 'on'
6850 $checked = ($enable_name_field === 'on') ? 'checked' : '';
6851 echo '<label class="toggle-switch">';
6852 echo sprintf(
6853 '<input type="checkbox" id="enable_name_field" name="enable_name_field" value="on" %s />',
6854 esc_attr($checked)
6855 );
6856 echo '<span class="slider"></span>';
6857 echo '</label>';
6858 echo '<p class="description">' . esc_html__('Enable this to also require users to enter their name along with their email before chatting.', 'mxchat') . '</p>';
6859 }
6860 //Name field placeholder callback
6861 public function name_field_placeholder_callback() {
6862 $all_options = get_option('mxchat_options', []);
6863 $placeholder = isset($all_options['name_field_placeholder'])
6864 ? $all_options['name_field_placeholder']
6865 : esc_html__('Enter your name', 'mxchat');
6866
6867 echo '<input type="text" id="name_field_placeholder" name="name_field_placeholder" value="' . esc_attr($placeholder) . '" style="width: 300px;" />';
6868 echo '<p class="description">';
6869 echo esc_html__('Placeholder text for the name field.', 'mxchat');
6870 echo '</p>';
6871 }
6872
6873
6874
6875 public function mxchat_intro_message_callback() {
6876 // Load the entire 'mxchat_options' array
6877 $all_options = get_option('mxchat_options', []);
6878 // Retrieve the saved intro message or use the default
6879 $default_message = __('Hello! How can I assist you today?', 'mxchat');
6880 $saved_message = isset($all_options['intro_message']) ? $all_options['intro_message'] : $default_message;
6881 // Output the textarea with the saved value without escaping HTML
6882 ?>
6883 <textarea id="intro_message" name="intro_message" rows="5" cols="50"><?php echo $saved_message; ?></textarea>
6884 <p class="description">
6885 <?php esc_html_e('Enter your message. HTML tags and line breaks will be preserved.', 'mxchat'); ?>
6886 </p>
6887 <?php
6888 }
6889
6890 public function mxchat_input_copy_callback() {
6891 // Load the entire 'mxchat_options' array
6892 $all_options = get_option('mxchat_options', []);
6893
6894 // Retrieve the saved input copy or use the default value
6895 $default_copy = __('How can I assist?', 'mxchat');
6896 $input_copy = isset($all_options['input_copy']) ? $all_options['input_copy'] : $default_copy;
6897
6898 // Output the input field with the saved value
6899 printf(
6900 '<input type="text" id="input_copy" name="input_copy" value="%s" placeholder="%s" />',
6901 esc_attr($input_copy),
6902 esc_attr__('How can I assist?', 'mxchat')
6903 );
6904
6905 // Output the description
6906 echo '<p class="description">' . esc_html__('This is the placeholder text for the chat input field.', 'mxchat') . '</p>';
6907 }
6908
6909
6910
6911 public function mxchat_user_message_font_color_callback() {
6912 $disabled = $this->is_activated ? '' : 'disabled';
6913 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6914
6915 echo '<div class="' . esc_attr($class) . '">';
6916 echo sprintf(
6917 '<input type="text"
6918 id="user_message_font_color"
6919 name="user_message_font_color"
6920 value="%s"
6921 class="my-color-field"
6922 data-default-color="#ffffff"
6923 %s />',
6924 isset($this->options['user_message_font_color']) ? esc_attr($this->options['user_message_font_color']) : '#ffffff',
6925 esc_attr($disabled)
6926 );
6927
6928 if (!$this->is_activated) {
6929 echo '<div class="pro-feature-overlay">';
6930 echo '<a href="https://mxchat.ai/" target="_blank">';
6931 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6932 echo '</a>';
6933 echo '</div>';
6934 }
6935 echo '</div>';
6936 }
6937
6938 public function mxchat_bot_message_bg_color_callback() {
6939 $disabled = $this->is_activated ? '' : 'disabled';
6940 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6941
6942 echo '<div class="' . esc_attr($class) . '">';
6943 echo sprintf(
6944 '<input type="text"
6945 id="bot_message_bg_color"
6946 name="bot_message_bg_color"
6947 value="%s"
6948 class="my-color-field"
6949 data-default-color="#e1e1e1"
6950 %s />',
6951 isset($this->options['bot_message_bg_color']) ? esc_attr($this->options['bot_message_bg_color']) : '#e1e1e1',
6952 esc_attr($disabled)
6953 );
6954
6955 if (!$this->is_activated) {
6956 echo '<div class="pro-feature-overlay">';
6957 echo '<a href="https://mxchat.ai/" target="_blank">';
6958 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6959 echo '</a>';
6960 echo '</div>';
6961 }
6962 echo '</div>';
6963 }
6964
6965 public function mxchat_bot_message_font_color_callback() {
6966 $disabled = $this->is_activated ? '' : 'disabled';
6967 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6968
6969 echo '<div class="' . esc_attr($class) . '">';
6970 echo sprintf(
6971 '<input type="text"
6972 id="bot_message_font_color"
6973 name="bot_message_font_color"
6974 value="%s"
6975 class="my-color-field"
6976 data-default-color="#333333"
6977 %s />',
6978 isset($this->options['bot_message_font_color']) ? esc_attr($this->options['bot_message_font_color']) : '#333333',
6979 esc_attr($disabled)
6980 );
6981
6982 if (!$this->is_activated) {
6983 echo '<div class="pro-feature-overlay">';
6984 echo '<a href="https://mxchat.ai/" target="_blank">';
6985 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6986 echo '</a>';
6987 echo '</div>';
6988 }
6989 echo '</div>';
6990 }
6991
6992 public function mxchat_live_agent_message_bg_color_callback() {
6993 $disabled = $this->is_activated ? '' : 'disabled';
6994 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6995
6996 echo '<div class="' . esc_attr($class) . '">';
6997 echo sprintf(
6998 '<input type="text"
6999 id="live_agent_message_bg_color"
7000 name="live_agent_message_bg_color"
7001 value="%s"
7002 class="my-color-field"
7003 data-default-color="#ffffff"
7004 %s />',
7005 isset($this->options['live_agent_message_bg_color']) ? esc_attr($this->options['live_agent_message_bg_color']) : '#ffffff',
7006 esc_attr($disabled)
7007 );
7008
7009 if (!$this->is_activated) {
7010 echo '<div class="pro-feature-overlay">';
7011 echo '<a href="https://mxchat.ai/" target="_blank">';
7012 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
7013 echo '</a>';
7014 echo '</div>';
7015 }
7016 echo '</div>';
7017 }
7018
7019 public function mxchat_live_agent_message_font_color_callback() {
7020 $disabled = $this->is_activated ? '' : 'disabled';
7021 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
7022
7023 echo '<div class="' . esc_attr($class) . '">';
7024 echo sprintf(
7025 '<input type="text"
7026 id="live_agent_message_font_color"
7027 name="live_agent_message_font_color"
7028 value="%s"
7029 class="my-color-field"
7030 data-default-color="#333333"
7031 %s />',
7032 isset($this->options['live_agent_message_font_color']) ? esc_attr($this->options['live_agent_message_font_color']) : '#333333',
7033 esc_attr($disabled)
7034 );
7035
7036 if (!$this->is_activated) {
7037 echo '<div class="pro-feature-overlay">';
7038 echo '<a href="https://mxchat.ai/" target="_blank">';
7039 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
7040 echo '</a>';
7041 echo '</div>';
7042 }
7043 echo '</div>';
7044 }
7045
7046 public function mxchat_mode_indicator_bg_color_callback() {
7047 $disabled = $this->is_activated ? '' : 'disabled';
7048 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
7049
7050 echo '<div class="' . esc_attr($class) . '">';
7051 echo sprintf(
7052 '<input type="text"
7053 id="mode_indicator_bg_color"
7054 name="mode_indicator_bg_color"
7055 value="%s"
7056 class="my-color-field"
7057 data-default-color="#767676"
7058 %s />',
7059 isset($this->options['mode_indicator_bg_color']) ? esc_attr($this->options['mode_indicator_bg_color']) : '#767676',
7060 esc_attr($disabled)
7061 );
7062
7063 if (!$this->is_activated) {
7064 echo '<div class="pro-feature-overlay">';
7065 echo '<a href="https://mxchat.ai/" target="_blank">';
7066 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
7067 echo '</a>';
7068 echo '</div>';
7069 }
7070 echo '</div>';
7071 }
7072
7073 public function mxchat_mode_indicator_font_color_callback() {
7074 $disabled = $this->is_activated ? '' : 'disabled';
7075 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
7076
7077 echo '<div class="' . esc_attr($class) . '">';
7078 echo sprintf(
7079 '<input type="text"
7080 id="mode_indicator_font_color"
7081 name="mode_indicator_font_color"
7082 value="%s"
7083 class="my-color-field"
7084 data-default-color="#ffffff"
7085 %s />',
7086 isset($this->options['mode_indicator_font_color']) ? esc_attr($this->options['mode_indicator_font_color']) : '#ffffff',
7087 esc_attr($disabled)
7088 );
7089
7090 if (!$this->is_activated) {
7091 echo '<div class="pro-feature-overlay">';
7092 echo '<a href="https://mxchat.ai/" target="_blank">';
7093 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
7094 echo '</a>';
7095 echo '</div>';
7096 }
7097 echo '</div>';
7098 }
7099
7100 public function mxchat_toolbar_icon_color_callback() {
7101 $disabled = $this->is_activated ? '' : 'disabled';
7102 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
7103
7104 echo '<div class="' . esc_attr($class) . '">';
7105 echo sprintf(
7106 '<input type="text"
7107 id="toolbar_icon_color"
7108 name="toolbar_icon_color"
7109 value="%s"
7110 class="my-color-field"
7111 data-default-color="#212121"
7112 %s />',
7113 isset($this->options['toolbar_icon_color']) ? esc_attr($this->options['toolbar_icon_color']) : '#212121',
7114 esc_attr($disabled)
7115 );
7116
7117 if (!$this->is_activated) {
7118 echo '<div class="pro-feature-overlay">';
7119 echo '<a href="https://mxchat.ai/" target="_blank">';
7120 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
7121 echo '</a>';
7122 echo '</div>';
7123 }
7124 echo '</div>';
7125 }
7126
7127 public function mxchat_top_bar_bg_color_callback() {
7128 $disabled = $this->is_activated ? '' : 'disabled';
7129 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
7130
7131 echo '<div class="' . esc_attr($class) . '">';
7132 echo sprintf(
7133 '<input type="text"
7134 id="top_bar_bg_color"
7135 name="top_bar_bg_color"
7136 value="%s"
7137 class="my-color-field"
7138 data-default-color="#00b294"
7139 %s />',
7140 isset($this->options['top_bar_bg_color']) ? esc_attr($this->options['top_bar_bg_color']) : '#00b294',
7141 esc_attr($disabled)
7142 );
7143
7144 if (!$this->is_activated) {
7145 echo '<div class="pro-feature-overlay">';
7146 echo '<a href="https://mxchat.ai/" target="_blank">';
7147 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
7148 echo '</a>';
7149 echo '</div>';
7150 }
7151 echo '</div>';
7152 }
7153
7154 public function mxchat_send_button_font_color_callback() {
7155 $disabled = $this->is_activated ? '' : 'disabled';
7156 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
7157
7158 echo '<div class="' . esc_attr($class) . '">';
7159 echo sprintf(
7160 '<input type="text"
7161 id="send_button_font_color"
7162 name="send_button_font_color"
7163 value="%s"
7164 class="my-color-field"
7165 data-default-color="#ffffff"
7166 %s />',
7167 isset($this->options['send_button_font_color']) ? esc_attr($this->options['send_button_font_color']) : '#ffffff',
7168 esc_attr($disabled)
7169 );
7170
7171 if (!$this->is_activated) {
7172 echo '<div class="pro-feature-overlay">';
7173 echo '<a href="https://mxchat.ai/" target="_blank">';
7174 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
7175 echo '</a>';
7176 echo '</div>';
7177 }
7178 echo '</div>';
7179 }
7180
7181 public function mxchat_chatbot_background_color_callback() {
7182 $disabled = $this->is_activated ? '' : 'disabled';
7183 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
7184
7185 echo '<div class="' . esc_attr($class) . '">';
7186 echo sprintf(
7187 '<input type="text"
7188 id="chatbot_background_color"
7189 name="chatbot_background_color"
7190 value="%s"
7191 class="my-color-field"
7192 data-default-color="#000000"
7193 %s />',
7194 isset($this->options['chatbot_background_color']) ? esc_attr($this->options['chatbot_background_color']) : '#000000',
7195 esc_attr($disabled)
7196 );
7197
7198 if (!$this->is_activated) {
7199 echo '<div class="pro-feature-overlay">';
7200 echo '<a href="https://mxchat.ai/" target="_blank">';
7201 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
7202 echo '</a>';
7203 echo '</div>';
7204 }
7205 echo '</div>';
7206 }
7207
7208 public function mxchat_icon_color_callback() {
7209 $disabled = $this->is_activated ? '' : 'disabled';
7210 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
7211
7212 echo '<div class="' . esc_attr($class) . '">';
7213 echo sprintf(
7214 '<input type="text"
7215 id="icon_color"
7216 name="icon_color"
7217 value="%s"
7218 class="my-color-field"
7219 data-default-color="#ffffff"
7220 %s />',
7221 isset($this->options['icon_color']) ? esc_attr($this->options['icon_color']) : '#ffffff',
7222 esc_attr($disabled)
7223 );
7224
7225 if (!$this->is_activated) {
7226 echo '<div class="pro-feature-overlay">';
7227 echo '<a href="https://mxchat.ai/" target="_blank">';
7228 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
7229 echo '</a>';
7230 echo '</div>';
7231 }
7232 echo '</div>';
7233 }
7234
7235 public function mxchat_custom_icon_callback() {
7236 $disabled = $this->is_activated ? '' : 'disabled';
7237 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
7238 $custom_icon_url = isset($this->options['custom_icon']) ? esc_url($this->options['custom_icon']) : '';
7239
7240 echo '<div class="' . esc_attr($class) . '">';
7241 echo sprintf(
7242 '<input type="url"
7243 id="custom_icon"
7244 name="custom_icon"
7245 value="%s"
7246 placeholder="%s"
7247 class="regular-text"
7248 %s />',
7249 $custom_icon_url,
7250 esc_attr__('Enter PNG URL', 'mxchat'),
7251 esc_attr($disabled)
7252 );
7253
7254 // Preview container for the icon
7255 if (!empty($custom_icon_url)) {
7256 echo '<div class="icon-preview" style="margin-top: 10px;">';
7257 echo '<img src="' . esc_url($custom_icon_url) . '" alt="' . esc_attr__('Custom Icon Preview', 'mxchat') . '" style="max-width: 48px; height: auto;" />';
7258 echo '</div>';
7259 }
7260
7261 echo '<p class="description">' . esc_html__('Upload your PNG icon and paste the URL here. Recommended size: 48x48 pixels.', 'mxchat') . '</p>';
7262
7263 if (!$this->is_activated) {
7264 echo '<div class="pro-feature-overlay">';
7265 echo '<a href="https://mxchat.ai/" target="_blank">';
7266 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
7267 echo '</a>';
7268 echo '</div>';
7269 }
7270 echo '</div>';
7271 }
7272
7273 public function mxchat_title_icon_callback() {
7274 $disabled = $this->is_activated ? '' : 'disabled';
7275 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
7276 // Fixed the variable reference - it was using custom_icon instead of title_icon
7277 $title_icon_url = isset($this->options['title_icon']) ? esc_url($this->options['title_icon']) : '';
7278
7279 echo '<div class="' . esc_attr($class) . '">';
7280 echo sprintf(
7281 '<input type="url"
7282 id="title_icon"
7283 name="title_icon"
7284 value="%s"
7285 placeholder="%s"
7286 class="regular-text"
7287 %s />',
7288 $title_icon_url,
7289 esc_attr__('Enter PNG URL', 'mxchat'),
7290 esc_attr($disabled)
7291 );
7292
7293 // Preview container for the icon
7294 if (!empty($title_icon_url)) {
7295 echo '<div class="icon-preview" style="margin-top: 10px;">';
7296 echo '<img src="' . esc_url($title_icon_url) . '" alt="' . esc_attr__('Title Icon Preview', 'mxchat') . '" style="max-width: 48px; height: auto;" />';
7297 echo '</div>';
7298 }
7299
7300 echo '<p class="description">' . esc_html__('Upload your PNG icon and paste the URL here. Recommended size: 48x48 pixels.', 'mxchat') . '</p>';
7301
7302 if (!$this->is_activated) {
7303 echo '<div class="pro-feature-overlay">';
7304 echo '<a href="https://mxchat.ai/" target="_blank">';
7305 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
7306 echo '</a>';
7307 echo '</div>';
7308 }
7309 echo '</div>';
7310 }
7311
7312 public function mxchat_chat_input_font_color_callback() {
7313 $disabled = $this->is_activated ? '' : 'disabled';
7314 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
7315
7316 echo '<div class="' . esc_attr($class) . '">';
7317 echo sprintf(
7318 '<input type="text"
7319 id="chat_input_font_color"
7320 name="chat_input_font_color"
7321 value="%s"
7322 class="my-color-field"
7323 data-default-color="#555555"
7324 %s />',
7325 isset($this->options['chat_input_font_color']) ? esc_attr($this->options['chat_input_font_color']) : '#555555',
7326 esc_attr($disabled)
7327 );
7328
7329 if (!$this->is_activated) {
7330 echo '<div class="pro-feature-overlay">';
7331 echo '<a href="https://mxchat.ai/" target="_blank">';
7332 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
7333 echo '</a>';
7334 echo '</div>';
7335 }
7336 echo '</div>';
7337 }
7338
7339 public function mxchat_append_to_body_callback() {
7340 // Fetch fresh options to ensure we have the latest saved values
7341 $options = get_option('mxchat_options', array());
7342
7343 // Get value from options array, default to 'off'
7344 $append_to_body = isset($options['append_to_body']) ? $options['append_to_body'] : 'off';
7345 $checked = ($append_to_body === 'on') ? 'checked' : '';
7346
7347 // Get post type visibility settings
7348 $visibility_mode = isset($options['post_type_visibility_mode']) ? $options['post_type_visibility_mode'] : 'all';
7349 $visibility_list = isset($options['post_type_visibility_list']) ? $options['post_type_visibility_list'] : array();
7350 if (!is_array($visibility_list)) {
7351 $visibility_list = array();
7352 }
7353
7354 echo '<div class="mxchat-autosave-section">';
7355
7356 // Main toggle
7357 echo '<label class="toggle-switch">';
7358 echo sprintf(
7359 '<input type="checkbox" id="append_to_body" name="append_to_body" value="on" %s />',
7360 esc_attr($checked)
7361 );
7362 echo '<span class="slider"></span>';
7363 echo '</label>';
7364 echo '<p class="description">' .
7365 esc_html__('Show chatbot automatically on all pages. When disabled, you can place the chatbot manually using shortcode [mxchat_chatbot floating="yes"].', 'mxchat') .
7366 '</p>';
7367
7368 // Post Type Visibility Options (only visible when auto-display is ON)
7369 $display_style = ($append_to_body === 'on') ? '' : 'display: none;';
7370 echo '<div id="post-type-visibility-options" class="mxchat-sub-options" style="' . esc_attr($display_style) . '">';
7371
7372 echo '<div class="mxchat-post-type-visibility-header">';
7373 echo '<h4>' . esc_html__('Post Type Visibility', 'mxchat') . '</h4>';
7374 echo '<p class="description">' . esc_html__('Control which post types display the chatbot.', 'mxchat') . '</p>';
7375 echo '</div>';
7376
7377 // Mode selector (radio buttons)
7378 echo '<div class="mxchat-visibility-mode">';
7379
7380 echo '<label class="mxchat-radio-label">';
7381 echo '<input type="radio" name="post_type_visibility_mode" value="all" ' . checked($visibility_mode, 'all', false) . ' />';
7382 echo '<span>' . esc_html__('Show on all post types', 'mxchat') . '</span>';
7383 echo '</label>';
7384
7385 echo '<label class="mxchat-radio-label">';
7386 echo '<input type="radio" name="post_type_visibility_mode" value="include" ' . checked($visibility_mode, 'include', false) . ' />';
7387 echo '<span>' . esc_html__('Only show on selected post types', 'mxchat') . '</span>';
7388 echo '</label>';
7389
7390 echo '<label class="mxchat-radio-label">';
7391 echo '<input type="radio" name="post_type_visibility_mode" value="exclude" ' . checked($visibility_mode, 'exclude', false) . ' />';
7392 echo '<span>' . esc_html__('Hide on selected post types', 'mxchat') . '</span>';
7393 echo '</label>';
7394
7395 echo '</div>';
7396
7397 // Post type checkboxes (only visible when mode is include or exclude)
7398 $list_display = ($visibility_mode !== 'all') ? '' : 'display: none;';
7399 echo '<div id="post-type-list" class="mxchat-post-type-list" style="' . esc_attr($list_display) . '">';
7400
7401 // Get all public post types
7402 $post_types = get_post_types(array('public' => true), 'objects');
7403
7404 foreach ($post_types as $post_type) {
7405 // Skip attachments
7406 if ($post_type->name === 'attachment') {
7407 continue;
7408 }
7409
7410 $is_checked = in_array($post_type->name, $visibility_list) ? 'checked' : '';
7411
7412 echo '<label class="mxchat-checkbox-label">';
7413 echo '<input type="checkbox" name="post_type_visibility_list[]" value="' . esc_attr($post_type->name) . '" ' . $is_checked . ' />';
7414 echo '<span>' . esc_html($post_type->label) . '</span>';
7415 echo '</label>';
7416 }
7417
7418 echo '</div>'; // End post-type-list
7419 echo '</div>'; // End post-type-visibility-options
7420 echo '</div>'; // End mxchat-autosave-section
7421 }
7422
7423 public function mxchat_frontend_debugger_callback() {
7424 $show_debugger = isset($this->options['show_frontend_debugger']) ? $this->options['show_frontend_debugger'] : 'on';
7425 $checked = ($show_debugger === 'on') ? 'checked' : '';
7426 echo '<label class="toggle-switch">';
7427 echo sprintf(
7428 '<input type="checkbox" id="show_frontend_debugger" name="show_frontend_debugger" value="on" %s />',
7429 esc_attr($checked)
7430 );
7431 echo '<span class="slider"></span>';
7432 echo '</label>';
7433 echo '<p class="description">' .
7434 esc_html__('Show the debug panel on the frontend for administrators. Displays similarity scores, document matches, and query analysis in real-time.', 'mxchat') .
7435 '</p>';
7436 }
7437
7438 public function mxchat_contextual_awareness_callback() {
7439 // Get value from options array, default to 'off'
7440 $contextual_awareness = isset($this->options['contextual_awareness_toggle']) ? $this->options['contextual_awareness_toggle'] : 'off';
7441 $checked = ($contextual_awareness === 'on') ? 'checked' : '';
7442 echo '<label class="toggle-switch">';
7443 echo sprintf(
7444 '<input type="checkbox" id="contextual_awareness_toggle" name="contextual_awareness_toggle" value="on" %s />',
7445 esc_attr($checked)
7446 );
7447 echo '<span class="slider"></span>';
7448 echo '</label>';
7449 echo '<p class="description">' .
7450 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') .
7451 '</p>';
7452 }
7453
7454
7455 public function mxchat_privacy_toggle_callback() {
7456 // Load from mxchat_options array
7457 $options = get_option('mxchat_options', []);
7458
7459 // Get privacy toggle value with fallback
7460 $privacy_toggle = isset($options['privacy_toggle']) ? $options['privacy_toggle'] : 'off';
7461 $checked = ($privacy_toggle === 'on') ? 'checked' : '';
7462
7463 // Get privacy text with fallback
7464 $privacy_text = isset($options['privacy_text'])
7465 ? $options['privacy_text']
7466 : __('By chatting, you agree to our <a href="https://example.com/privacy-policy" target="_blank">privacy policy</a>.', 'mxchat');
7467
7468 // Output the toggle switch
7469 echo '<label class="toggle-switch">';
7470 echo sprintf(
7471 '<input type="checkbox" id="privacy_toggle" name="privacy_toggle" value="on" %s />',
7472 esc_attr($checked)
7473 );
7474 echo '<span class="slider"></span>';
7475 echo '</label>';
7476 echo '<p class="description">' . esc_html__('Enable this option to display a privacy notice below the chat widget.', 'mxchat') . '</p>';
7477
7478 // Output the custom text input field
7479 echo sprintf(
7480 '<textarea id="privacy_text" name="privacy_text" rows="5" cols="50" class="regular-text">%s</textarea>',
7481 esc_textarea($privacy_text)
7482 );
7483 echo '<p class="description">' . esc_html__('Enter the privacy policy text. You can include HTML links.', 'mxchat') . '</p>';
7484 }
7485
7486
7487 public function mxchat_complianz_toggle_callback() {
7488 // Load from mxchat_options array
7489 $options = get_option('mxchat_options', []);
7490
7491 // Get complianz toggle value with fallback
7492 $complianz_toggle = isset($options['complianz_toggle']) ? $options['complianz_toggle'] : 'off';
7493 $checked = ($complianz_toggle === 'on') ? 'checked' : '';
7494
7495 // Output the toggle switch
7496 echo '<label class="toggle-switch">';
7497 echo sprintf(
7498 '<input type="checkbox" id="complianz_toggle" name="complianz_toggle" value="on" %s />',
7499 esc_attr($checked)
7500 );
7501 echo '<span class="slider"></span>';
7502 echo '</label>';
7503
7504 echo '<p class="description">' . esc_html__('Enable this option to apply Complianz consent logic to the chatbot (must have Complianz Plugin).', 'mxchat') . '</p>';
7505 }
7506
7507 public function mxchat_link_target_toggle_callback() {
7508 // Load from mxchat_options array
7509 $options = get_option('mxchat_options', []);
7510
7511 // Get link target toggle value with fallback
7512 $link_target_toggle = isset($options['link_target_toggle']) ? $options['link_target_toggle'] : 'off';
7513 $checked = ($link_target_toggle === 'on') ? 'checked' : '';
7514
7515 // Output the toggle switch
7516 echo '<label class="toggle-switch">';
7517 echo sprintf(
7518 '<input type="checkbox" id="link_target_toggle" name="link_target_toggle" value="on" %s />',
7519 esc_attr($checked)
7520 );
7521 echo '<span class="slider"></span>';
7522 echo '</label>';
7523 echo '<p class="description">' . esc_html__('Enable to open links in a new tab (default is to open in the same tab).', 'mxchat') . '</p>';
7524 }
7525
7526 public function mxchat_chat_persistence_toggle_callback() {
7527 // Load from mxchat_options array
7528 $options = get_option('mxchat_options', []);
7529
7530 // Get chat persistence toggle value with fallback
7531 $chat_persistence_toggle = isset($options['chat_persistence_toggle']) ? $options['chat_persistence_toggle'] : 'off';
7532 $checked = ($chat_persistence_toggle === 'on') ? 'checked' : '';
7533
7534 // Output the toggle switch
7535 echo '<label class="toggle-switch">';
7536 echo sprintf(
7537 '<input type="checkbox" id="chat_persistence_toggle" name="chat_persistence_toggle" value="on" %s />',
7538 esc_attr($checked)
7539 );
7540 echo '<span class="slider"></span>';
7541 echo '</label>';
7542
7543 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>';
7544 }
7545
7546 public function mxchat_popular_question_1_callback() {
7547 // Load the full plugin options array
7548 $all_options = get_option('mxchat_options', []);
7549
7550 // Retrieve the specific option for popular_question_1
7551 $popular_question_1 = isset($all_options['popular_question_1']) ? $all_options['popular_question_1'] : '';
7552
7553 // Render the input field
7554 printf(
7555 '<input type="text" id="popular_question_1" name="popular_question_1" value="%s" placeholder="%s" class="regular-text" />',
7556 esc_attr($popular_question_1),
7557 esc_attr__('Enter Quick Question 1', 'mxchat')
7558 );
7559
7560 // Add a description for the field
7561 echo '<p class="description">' . esc_html__('This will be the first Quick Question in the chatbot, displayed above the input field.', 'mxchat') . '</p>';
7562 }
7563
7564
7565 public function mxchat_popular_question_2_callback() {
7566 // Load the full plugin options array
7567 $all_options = get_option('mxchat_options', []);
7568
7569 // Retrieve the specific option for popular_question_2
7570 $popular_question_2 = isset($all_options['popular_question_2']) ? $all_options['popular_question_2'] : '';
7571
7572 // Render the input field
7573 printf(
7574 '<input type="text" id="popular_question_2" name="popular_question_2" value="%s" placeholder="%s" class="regular-text" />',
7575 esc_attr($popular_question_2),
7576 esc_attr__('Enter Quick Question 2', 'mxchat')
7577 );
7578
7579 // Add a description for the field
7580 echo '<p class="description">' . esc_html__('This will be the second Quick Question in the chatbot.', 'mxchat') . '</p>';
7581 }
7582
7583
7584 public function mxchat_popular_question_3_callback() {
7585 // Load the full plugin options array
7586 $all_options = get_option('mxchat_options', []);
7587
7588 // Retrieve the specific option for popular_question_3
7589 $popular_question_3 = isset($all_options['popular_question_3']) ? $all_options['popular_question_3'] : '';
7590
7591 // Render the input field
7592 printf(
7593 '<input type="text" id="popular_question_3" name="popular_question_3" value="%s" placeholder="%s" class="regular-text" />',
7594 esc_attr($popular_question_3),
7595 esc_attr(__('Enter Quick Question 3', 'mxchat'))
7596 );
7597
7598 // Add a description for the field
7599 echo '<p class="description">' . esc_html__('This will be the third Quick Question in the chatbot.', 'mxchat') . '</p>';
7600 }
7601
7602 public function mxchat_additional_popular_questions_callback() {
7603 $options = get_option('mxchat_options', []);
7604 $additional_questions = isset($options['additional_popular_questions'])
7605 ? $options['additional_popular_questions']
7606 : get_option('additional_popular_questions', array());
7607
7608 echo '<div id="mxchat-additional-questions-container">';
7609 if (!empty($additional_questions)) {
7610 foreach ($additional_questions as $index => $question) {
7611 printf(
7612 '<div class="mxchat-question-row">
7613 <input type="text" name="additional_popular_questions[]"
7614 value="%s"
7615 placeholder="%s"
7616 class="regular-text mxchat-question-input"
7617 data-question-index="%d" />
7618 <button type="button" class="button mxchat-remove-question"
7619 aria-label="%s">%s</button>
7620 </div>',
7621 esc_attr($question),
7622 esc_attr(sprintf(__('Enter Additional Quick Question %d', 'mxchat'), $index + 4)),
7623 $index,
7624 esc_attr(__('Remove question', 'mxchat')),
7625 esc_html__('Remove', 'mxchat')
7626 );
7627 }
7628 } else {
7629 printf(
7630 '<div class="mxchat-question-row">
7631 <input type="text" name="additional_popular_questions[]"
7632 value=""
7633 placeholder="%s"
7634 class="regular-text mxchat-question-input"
7635 data-question-index="0" />
7636 <button type="button" class="button mxchat-remove-question"
7637 aria-label="%s">%s</button>
7638 </div>',
7639 esc_attr(__('Enter Additional Quick Question 4', 'mxchat')),
7640 esc_attr(__('Remove question', 'mxchat')),
7641 esc_html__('Remove', 'mxchat')
7642 );
7643 }
7644 echo '</div>';
7645 printf(
7646 '<button type="button" class="button mxchat-add-question" aria-label="%s">%s</button>',
7647 esc_attr(__('Add question', 'mxchat')),
7648 esc_html__('Add Question', 'mxchat')
7649 );
7650 echo '<p class="description">' . esc_html__('Add as many Quick Questions as you need.', 'mxchat') . '</p>';
7651 echo '</div>';
7652 }
7653
7654 public function mxchat_brave_api_key_callback() {
7655 $brave_api_key = isset($this->options['brave_api_key']) ? esc_attr($this->options['brave_api_key']) : '';
7656
7657 echo '<div class="api-key-wrapper">';
7658 echo sprintf(
7659 '<input type="password" id="brave_api_key" name="brave_api_key" value="%s" class="regular-text" />',
7660 $brave_api_key
7661 );
7662 echo '<button type="button" id="toggleBraveApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
7663 echo '</div>';
7664 echo '<p class="description">' . __('Required for Brave Search integration. Get your API key from Brave Search API.', 'mxchat') . '</p>';
7665 }
7666
7667 public function mxchat_brave_image_count_callback() {
7668 $brave_image_count = isset($this->options['brave_image_count'])
7669 ? intval($this->options['brave_image_count'])
7670 : 4;
7671
7672 echo sprintf(
7673 '<input type="number" id="brave_image_count" name="brave_image_count"
7674 value="%d" min="1" max="6" class="small-text" />',
7675 $brave_image_count
7676 );
7677 echo '<p class="description">' . __('Select the number of images to return (1-6).', 'mxchat') . '</p>';
7678 }
7679
7680 public function mxchat_brave_safe_search_callback() {
7681 $brave_safe_search = isset($this->options['brave_safe_search'])
7682 ? esc_attr($this->options['brave_safe_search'])
7683 : 'strict';
7684
7685 echo '<select id="brave_safe_search" name="brave_safe_search">';
7686 echo sprintf(
7687 '<option value="strict" %s>%s</option>',
7688 selected($brave_safe_search, 'strict', false),
7689 __('Strict', 'mxchat')
7690 );
7691 echo sprintf(
7692 '<option value="off" %s>%s</option>',
7693 selected($brave_safe_search, 'off', false),
7694 __('Off', 'mxchat')
7695 );
7696 echo '</select>';
7697 echo '<p class="description">' .
7698 esc_html__('Set the Safe Search level for image searches. Brave Search only supports "Strict" and "Off" options.', 'mxchat') .
7699 '</p>';
7700 }
7701
7702 public function mxchat_brave_news_count_callback() {
7703 $brave_news_count = isset($this->options['brave_news_count'])
7704 ? intval($this->options['brave_news_count'])
7705 : 3;
7706
7707 echo sprintf(
7708 '<input type="number" id="brave_news_count" name="brave_news_count"
7709 value="%d" min="1" max="10" class="small-text" />',
7710 $brave_news_count
7711 );
7712 echo '<p class="description">' . esc_html__('Select the number of news articles to retrieve (1-10).', 'mxchat') . '</p>';
7713 }
7714
7715 public function mxchat_brave_country_callback() {
7716 $brave_country = isset($this->options['brave_country'])
7717 ? esc_attr($this->options['brave_country'])
7718 : 'us';
7719
7720 echo sprintf(
7721 '<input type="text" id="brave_country" name="brave_country"
7722 value="%s" maxlength="2" class="small-text" />',
7723 $brave_country
7724 );
7725 echo '<p class="description">' . esc_html__('Enter the country code (e.g., "us" for United States).', 'mxchat') . '</p>';
7726 }
7727
7728 public function mxchat_brave_language_callback() {
7729 $brave_language = isset($this->options['brave_language'])
7730 ? esc_attr($this->options['brave_language'])
7731 : 'en';
7732
7733 echo sprintf(
7734 '<input type="text" id="brave_language" name="brave_language"
7735 value="%s" maxlength="2" class="small-text" />',
7736 $brave_language
7737 );
7738 echo '<p class="description">' . esc_html__('Enter the language code (e.g., "en" for English).', 'mxchat') . '</p>';
7739 }
7740
7741
7742
7743
7744
7745 // Section Callback
7746 public function mxchat_pdf_intent_section_callback() {
7747 echo '<p>' . esc_html__('Configure the intent settings for the Chat with PDF feature.', 'mxchat') . '</p>';
7748 }
7749
7750 public function mxchat_chat_toolbar_toggle_callback() {
7751 // Get chat toolbar toggle value with fallback
7752 $chat_toolbar_toggle = isset($this->options['chat_toolbar_toggle']) ? $this->options['chat_toolbar_toggle'] : 'off';
7753 $checked = ($chat_toolbar_toggle === 'on') ? 'checked' : '';
7754
7755 // Output the toggle switch
7756 echo '<label class="toggle-switch">';
7757 echo sprintf(
7758 '<input type="checkbox" id="chat_toolbar_toggle" name="chat_toolbar_toggle" value="on" %s />',
7759 esc_attr($checked)
7760 );
7761 echo '<span class="slider"></span>';
7762 echo '</label>';
7763
7764 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>';
7765 }
7766
7767 /**
7768 * Callback for PDF upload button toggle setting
7769 */
7770 public function mxchat_show_pdf_upload_button_callback() {
7771 // Get toggle value with fallback
7772 $show_pdf_button = isset($this->options['show_pdf_upload_button']) ? $this->options['show_pdf_upload_button'] : 'on';
7773 $checked = ($show_pdf_button === 'on') ? 'checked' : '';
7774
7775 // Output the toggle switch
7776 echo '<label class="toggle-switch">';
7777 echo sprintf(
7778 '<input type="checkbox" id="show_pdf_upload_button" name="show_pdf_upload_button" value="on" %s />',
7779 esc_attr($checked)
7780 );
7781 echo '<span class="slider"></span>';
7782 echo '</label>';
7783
7784 echo '<p class="description">' . esc_html__('Enable to show the PDF upload button in the chatbot toolbar. Disable to hide it.', 'mxchat') . '</p>';
7785 }
7786
7787 /**
7788 * Callback for Word upload button toggle setting
7789 */
7790 public function mxchat_show_word_upload_button_callback() {
7791 // Get toggle value with fallback
7792 $show_word_button = isset($this->options['show_word_upload_button']) ? $this->options['show_word_upload_button'] : 'on';
7793 $checked = ($show_word_button === 'on') ? 'checked' : '';
7794
7795 // Output the toggle switch
7796 echo '<label class="toggle-switch">';
7797 echo sprintf(
7798 '<input type="checkbox" id="show_word_upload_button" name="show_word_upload_button" value="on" %s />',
7799 esc_attr($checked)
7800 );
7801 echo '<span class="slider"></span>';
7802 echo '</label>';
7803
7804 echo '<p class="description">' . esc_html__('Enable to show the Word document upload button in the chatbot toolbar. Disable to hide it.', 'mxchat') . '</p>';
7805 }
7806
7807 public function mxchat_pdf_intent_trigger_text_callback() {
7808 $default_text = __("Please provide the URL to the PDF you'd like to discuss.", 'mxchat');
7809
7810 echo sprintf(
7811 '<textarea id="pdf_intent_trigger_text"
7812 name="pdf_intent_trigger_text"
7813 rows="3"
7814 cols="50"
7815 placeholder="%s">%s</textarea>',
7816 esc_attr__('Enter trigger text', 'mxchat'),
7817 isset($this->options['pdf_intent_trigger_text'])
7818 ? esc_textarea($this->options['pdf_intent_trigger_text'])
7819 : esc_textarea($default_text)
7820 );
7821 echo '<p class="description">' . esc_html__('Text displayed when the intent is triggered.', 'mxchat') . '</p>';
7822 }
7823
7824 public function mxchat_pdf_intent_success_text_callback() {
7825 $default_text = __("I've processed the PDF. What questions do you have about it?", 'mxchat');
7826
7827 echo sprintf(
7828 '<textarea id="pdf_intent_success_text"
7829 name="pdf_intent_success_text"
7830 rows="3"
7831 cols="50"
7832 placeholder="%s">%s</textarea>',
7833 esc_attr__('Enter success text', 'mxchat'),
7834 isset($this->options['pdf_intent_success_text'])
7835 ? esc_textarea($this->options['pdf_intent_success_text'])
7836 : esc_textarea($default_text)
7837 );
7838 echo '<p class="description">' . esc_html__('Text displayed when the intent is successful.', 'mxchat') . '</p>';
7839 }
7840
7841 public function mxchat_pdf_intent_error_text_callback() {
7842 $default_text = __("Sorry, I couldn't process the PDF. Please ensure it's a valid file.", 'mxchat');
7843
7844 echo sprintf(
7845 '<textarea id="pdf_intent_error_text"
7846 name="pdf_intent_error_text"
7847 rows="3"
7848 cols="50"
7849 placeholder="%s">%s</textarea>',
7850 esc_attr__('Enter error text', 'mxchat'),
7851 isset($this->options['pdf_intent_error_text'])
7852 ? esc_textarea($this->options['pdf_intent_error_text'])
7853 : esc_textarea($default_text)
7854 );
7855 echo '<p class="description">' . esc_html__('Text displayed when an error occurs during the intent.', 'mxchat') . '</p>';
7856 }
7857
7858 public function mxchat_pdf_max_pages_callback() {
7859 $max_pages = isset($this->options['pdf_max_pages']) ? intval($this->options['pdf_max_pages']) : 69;
7860
7861 echo sprintf(
7862 '<input type="range"
7863 id="pdf_max_pages"
7864 name="pdf_max_pages"
7865 min="1"
7866 max="69"
7867 value="%d"
7868 class="range-slider" />',
7869 esc_attr($max_pages)
7870 );
7871 echo '<span id="pdf_max_pages_output">' . esc_html($max_pages) . '</span>';
7872 echo '<p class="description">' . esc_html__('Set the maximum number of document pages users can upload for processing. (1-69 pages)', 'mxchat') . '</p>';
7873 }
7874
7875 public function mxchat_live_agent_status_callback() {
7876 // Always get fresh options instead of using cached $this->options
7877 $fresh_options = get_option('mxchat_options');
7878 $status = isset($fresh_options['live_agent_status']) ? $fresh_options['live_agent_status'] : 'off';
7879
7880 echo '<label class="toggle-switch">';
7881 echo sprintf(
7882 '<input type="checkbox" id="live_agent_status" name="live_agent_status" value="on" %s />',
7883 checked($status, 'on', false)
7884 );
7885 echo '<span class="slider"></span>';
7886 echo '</label>';
7887 echo '<label for="live_agent_status" class="mxchat-status-label">';
7888 echo '<span class="status-text">' . ($status === 'on' ? esc_html__('Online', 'mxchat') : esc_html__('Offline', 'mxchat')) . '</span>';
7889 echo '</label>';
7890 }
7891
7892 public function mxchat_live_agent_away_message_callback() {
7893 $message = isset($this->options['live_agent_away_message'])
7894 ? $this->options['live_agent_away_message']
7895 : __('Sorry, live agents are currently unavailable. I can continue helping you as an AI assistant.', 'mxchat');
7896
7897 printf(
7898 '<textarea id="live_agent_away_message" name="live_agent_away_message" rows="3" cols="50">%s</textarea>',
7899 esc_textarea($message)
7900 );
7901 echo '<p class="description">' . esc_html__('Message shown when live agents are offline.', 'mxchat') . '</p>';
7902 }
7903
7904 public function mxchat_live_agent_notification_message_callback() {
7905 $message = isset($this->options['live_agent_notification_message'])
7906 ? $this->options['live_agent_notification_message']
7907 : __('Live agent has been notified.', 'mxchat');
7908
7909 printf(
7910 '<textarea id="live_agent_notification_message" name="live_agent_notification_message" rows="3" cols="50">%s</textarea>',
7911 esc_textarea($message)
7912 );
7913 echo '<p class="description">' . esc_html__('Message shown when live transfer activated.', 'mxchat') . '</p>';
7914 }
7915
7916 public function mxchat_live_agent_webhook_url_callback() {
7917 $webhook_url = isset($this->options['live_agent_webhook_url'])
7918 ? esc_url($this->options['live_agent_webhook_url'])
7919 : esc_url(get_option('live_agent_webhook_url', ''));
7920
7921 printf(
7922 '<input type="password" id="live_agent_webhook_url" name="live_agent_webhook_url" value="%s" class="regular-text" />',
7923 $webhook_url
7924 );
7925 echo '<button type="button" id="toggleWebhookUrlVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
7926 echo '<p class="description">' . esc_html__('Enter your Slack webhook URL for live agent notifications.', 'mxchat') . '</p>';
7927 }
7928
7929 public function mxchat_live_agent_secret_key_callback() {
7930 printf(
7931 '<input type="password" id="live_agent_secret_key" name="live_agent_secret_key" value="%s" class="regular-text" />',
7932 isset($this->options['live_agent_secret_key']) ? esc_attr($this->options['live_agent_secret_key']) : ''
7933 );
7934 echo '<button type="button" id="toggleSecretKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
7935 echo '<p class="description">' . esc_html__('Secret key for validating Slack requests. Keep this secure.', 'mxchat') . '</p>';
7936 }
7937
7938 public function mxchat_live_agent_bot_token_callback() {
7939 printf(
7940 '<input type="password" id="live_agent_bot_token" name="live_agent_bot_token" value="%s" class="regular-text" />',
7941 isset($this->options['live_agent_bot_token']) ? esc_attr($this->options['live_agent_bot_token']) : ''
7942 );
7943 echo '<button type="button" id="toggleBotTokenVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
7944 echo '<p class="description">' . esc_html__('Your Slack Bot OAuth Token (starts with xoxb-). Keep this secure.', 'mxchat') . '</p>';
7945 }
7946
7947 public function mxchat_live_agent_user_ids_callback() {
7948 $user_ids = isset($this->options['live_agent_user_ids'])
7949 ? esc_textarea($this->options['live_agent_user_ids'])
7950 : '';
7951
7952 printf(
7953 '<textarea id="live_agent_user_ids" name="live_agent_user_ids" rows="4" class="large-text">%s</textarea>',
7954 $user_ids
7955 );
7956 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>';
7957 }
7958
7959 public function mxchat_similarity_threshold_callback() {
7960 // Load from mxchat_options array
7961 $options = get_option('mxchat_options', []);
7962
7963 // Get value from options array with default of 80
7964 $threshold = isset($options['similarity_threshold']) ? $options['similarity_threshold'] : 35;
7965
7966 echo '<div class="slider-container">';
7967 echo sprintf(
7968 '<input type="range"
7969 id="similarity_threshold"
7970 name="similarity_threshold"
7971 min="20"
7972 max="85"
7973 step="1"
7974 value="%s"
7975 class="range-slider" />',
7976 esc_attr($threshold)
7977 );
7978 echo sprintf(
7979 '<span id="threshold_value" class="range-value">%s</span>',
7980 esc_html($threshold)
7981 );
7982 echo '</div>';
7983 echo '<p class="description">';
7984 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');
7985 echo '</p>';
7986 }
7987
7988 public function mxchat_enqueue_admin_assets() {
7989 // Get plugin version
7990 $version = MXCHAT_VERSION;
7991
7992 // Use file modification time for development (remove in production)
7993 if (defined('WP_DEBUG') && WP_DEBUG) {
7994 $version = filemtime(plugin_dir_path(__FILE__) . '../mxchat-basic.php');
7995 }
7996
7997 $current_page = isset($_GET['page']) ? $_GET['page'] : '';
7998 $plugin_url = plugin_dir_url(__FILE__) . '../';
7999
8000 // Only load on MxChat pages
8001 if (strpos($current_page, 'mxchat') === false) {
8002 return;
8003 }
8004
8005 // Always load these on all MxChat pages
8006 $this->enqueue_core_admin_assets($plugin_url, $version);
8007 $this->enqueue_page_specific_assets($current_page, $plugin_url, $version);
8008 $this->localize_admin_scripts($current_page);
8009 }
8010 private function enqueue_core_admin_assets($plugin_url, $version) {
8011 // Core admin styles
8012 wp_enqueue_style('mxchat-admin-css', $plugin_url . 'css/admin-style.css', array(), $version);
8013 wp_enqueue_style('mxchat-knowledge-css', $plugin_url . 'css/knowledge-style.css', array(), $version);
8014
8015 // Core admin scripts
8016 wp_enqueue_script('mxchat-admin-js', $plugin_url . 'js/mxchat-admin.js', array('jquery'), $version, true);
8017 }
8018 private function enqueue_page_specific_assets($current_page, $plugin_url, $version) {
8019 switch ($current_page) {
8020 case 'mxchat-prompts':
8021 // Knowledge processing page assets
8022 wp_enqueue_style('mxchat-content-selector-css', $plugin_url . 'css/content-selector.css', array(), $version);
8023 wp_enqueue_script('mxchat-content-selector-js', $plugin_url . 'js/content-selector.js', array('jquery'), $version, true);
8024 // Add the knowledge processing script
8025 wp_enqueue_script('mxchat-knowledge-processing', $plugin_url . 'js/knowledge-processing.js', array('jquery'), $version, true);
8026 break;
8027
8028 case 'mxchat-transcripts':
8029 wp_enqueue_style('mxchat-chat-transcripts-css', $plugin_url . 'css/chat-transcripts.css', array(), $version);
8030 wp_enqueue_script('mxchat-transcripts-js', $plugin_url . 'js/mxchat_transcripts.js', array('jquery'), $version, true);
8031 break;
8032
8033 case 'mxchat-actions':
8034 wp_enqueue_style('mxchat-intent-css', $plugin_url . 'css/intent-style.css', array(), $version);
8035 break;
8036
8037 case 'mxchat-activation':
8038 wp_enqueue_script('mxchat-activation-js', $plugin_url . 'js/activation-script.js', array('jquery'), $version, true);
8039 break;
8040 default:
8041 wp_enqueue_script(
8042 'mxchat-test-streaming-js',
8043 $plugin_url . 'js/mxchat-test-streaming.js',
8044 ['jquery'],
8045 $version,
8046 true
8047 );
8048
8049 wp_localize_script('mxchat-test-streaming-js', 'mxchatTestStreamingAjax', [
8050 'ajax_url' => admin_url('admin-ajax.php'),
8051 'nonce' => wp_create_nonce('mxchat_test_streaming_nonce'),
8052 'settings_nonce' => wp_create_nonce('mxchat_save_setting_nonce')
8053 ]);
8054 break;
8055 }
8056 }
8057 private function localize_admin_scripts($current_page) {
8058 // Base localization data for main admin script
8059 $base_data = array(
8060 'ajax_url' => admin_url('admin-ajax.php'),
8061 'nonce' => wp_create_nonce('mxchat_status_nonce'),
8062 'admin_url' => admin_url(),
8063 'license_nonce' => wp_create_nonce('mxchat_activate_license_nonce'),
8064 'inline_edit_nonce' => wp_create_nonce('mxchat_save_inline_nonce'),
8065 'setting_nonce' => wp_create_nonce('mxchat_save_setting_nonce'),
8066 'export_nonce' => wp_create_nonce('mxchat_export_transcripts'),
8067 'actions_nonce' => wp_create_nonce('mxchat_actions_nonce'),
8068 'add_intent_nonce' => wp_create_nonce('mxchat_add_intent_nonce'),
8069 'edit_intent_nonce' => wp_create_nonce('mxchat_edit_intent'),
8070 'toggle_action_nonce' => wp_create_nonce('mxchat_actions_nonce'),
8071 'fetch_openrouter_models_nonce' => wp_create_nonce('mxchat_fetch_openrouter_models'), // ADD THIS LINE
8072 'is_activated' => $this->is_activated ? '1' : '0',
8073 'status_refresh_interval' => 5000,
8074 'prompts_setting_nonce' => wp_create_nonce('mxchat_prompts_setting_nonce'),
8075 'ajaxurl' => admin_url('admin-ajax.php')
8076 );
8077
8078 // Localize main admin script with base data
8079 wp_localize_script('mxchat-admin-js', 'mxchatAdmin', $base_data);
8080
8081 // Page-specific localizations
8082 $this->localize_page_specific_scripts($current_page);
8083 }
8084 private function localize_page_specific_scripts($current_page) {
8085 switch ($current_page) {
8086 case 'mxchat-prompts':
8087 // Status updater localization
8088 wp_localize_script('mxchat-status-updater', 'mxchat_status_data', array(
8089 'ajax_url' => admin_url('admin-ajax.php'),
8090 'nonce' => wp_create_nonce('mxchat_status_nonce')
8091 ));
8092
8093 // Content selector localization
8094 wp_localize_script('mxchat-content-selector-js', 'mxchatSelector', array(
8095 'ajaxurl' => admin_url('admin-ajax.php'),
8096 'nonce' => wp_create_nonce('mxchat_content_selector_nonce'),
8097 'i18n' => array(
8098 'searchPlaceholder' => __('Search posts and pages...', 'mxchat'),
8099 'selectAll' => __('Select All', 'mxchat'),
8100 'process' => __('Process Selected', 'mxchat'),
8101 'cancel' => __('Cancel', 'mxchat'),
8102 'noResults' => __('No content found.', 'mxchat')
8103 )
8104 ));
8105
8106 wp_localize_script('mxchat-knowledge-processing', 'mxchatAdmin', array(
8107 'ajax_url' => admin_url('admin-ajax.php'),
8108 'status_nonce' => wp_create_nonce('mxchat_status_nonce'),
8109 'queue_nonce' => wp_create_nonce('mxchat_queue_nonce'),
8110 'stop_nonce' => wp_create_nonce('mxchat_stop_processing_action'),
8111 'settings_nonce' => wp_create_nonce('mxchat_prompts_setting_nonce'),
8112 'setting_nonce' => wp_create_nonce('mxchat_save_setting_nonce'),
8113 'admin_url' => admin_url(),
8114 'ajaxurl' => admin_url('admin-ajax.php'),
8115 'status_refresh_interval' => 2000
8116 ));
8117 break;
8118
8119 case 'mxchat-activation':
8120 wp_localize_script('mxchat-activation-js', 'mxchatAdmin', array(
8121 'ajax_url' => admin_url('admin-ajax.php'),
8122 'license_nonce' => wp_create_nonce('mxchat_activate_license_nonce')
8123 ));
8124 break;
8125
8126 case 'mxchat-settings':
8127 default:
8128 // Color picker and settings localization
8129 wp_localize_script('mxchat-color-picker', 'mxchatStyleSettings', array(
8130 'ajax_url' => admin_url('admin-ajax.php'),
8131 'link_target_toggle' => $this->options['link_target_toggle'] ?? 'off',
8132 'user_message_bg_color' => $this->options['user_message_bg_color'] ?? '#fff',
8133 'user_message_font_color' => $this->options['user_message_font_color'] ?? '#212121',
8134 'bot_message_bg_color' => $this->options['bot_message_bg_color'] ?? '#212121',
8135 'bot_message_font_color' => $this->options['bot_message_font_color'] ?? '#fff',
8136 'top_bar_bg_color' => $this->options['top_bar_bg_color'] ?? '#212121',
8137 'send_button_font_color' => $this->options['send_button_font_color'] ?? '#212121',
8138 'close_button_color' => $this->options['close_button_color'] ?? '#fff',
8139 'chatbot_background_color' => $this->options['chatbot_background_color'] ?? '#212121',
8140 'icon_color' => $this->options['icon_color'] ?? '#fff',
8141 'chat_input_font_color' => $this->options['chat_input_font_color'] ?? '#212121',
8142 'pre_chat_message' => $this->options['pre_chat_message'] ?? esc_html__('Hey there! Ask me anything!', 'mxchat'),
8143 'rate_limit_message' => $this->options['rate_limit_message'] ?? esc_html__('Rate limit exceeded. Please try again later.', 'mxchat'),
8144 'loops_api_key' => $this->options['loops_api_key'] ?? '',
8145 'loops_mailing_list' => $this->options['loops_mailing_list'] ?? '',
8146 'triggered_phrase_response' => $this->options['triggered_phrase_response'] ?? esc_html__('Would you like to join our mailing list? Please provide your email below.', 'mxchat'),
8147 'email_capture_response' => $this->options['email_capture_response'] ?? esc_html__('Thank you for providing your email! You\'ve been added to our list.', 'mxchat'),
8148 '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'),
8149 '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'),
8150 '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'),
8151 'pdf_max_pages' => $this->options['pdf_max_pages'] ?? 69,
8152 'live_agent_webhook_url' => $this->options['live_agent_webhook_url'] ?? '',
8153 'live_agent_secret_key' => $this->options['live_agent_secret_key'] ?? '',
8154 'live_agent_bot_token' => $this->options['live_agent_bot_token'] ?? '',
8155 'live_agent_message_bg_color' => $this->options['live_agent_message_bg_color'] ?? '#ffffff',
8156 'live_agent_message_font_color' => $this->options['live_agent_message_font_color'] ?? '#333333',
8157 'chat_toolbar_toggle' => $this->options['chat_toolbar_toggle'] ?? 'off',
8158 'show_pdf_upload_button' => $this->options['show_pdf_upload_button'] ?? 'on',
8159 'show_word_upload_button' => $this->options['show_word_upload_button'] ?? 'on',
8160 'mode_indicator_bg_color' => $this->options['mode_indicator_bg_color'] ?? '#767676',
8161 'mode_indicator_font_color' => $this->options['mode_indicator_font_color'] ?? '#ffffff',
8162 'toolbar_icon_color' => $this->options['toolbar_icon_color'] ?? '#212121',
8163 ));
8164 break;
8165 }
8166
8167 // Additional localization that was in the original code
8168 wp_localize_script('mxchat-admin-js', 'mxchatPromptsAdmin', array(
8169 'ajax_url' => admin_url('admin-ajax.php'),
8170 'prompts_setting_nonce' => wp_create_nonce('mxchat_prompts_setting_nonce'),
8171 ));
8172 }
8173
8174 public function mxchat_sanitize($input) {
8175 $new_input = array();
8176
8177 if (isset($input['api_key'])) {
8178 $new_input['api_key'] = sanitize_text_field($input['api_key']);
8179 }
8180
8181 if (isset($input['similarity_threshold'])) {
8182 $new_input['similarity_threshold'] = absint($input['similarity_threshold']); // Ensure it's an integer
8183 $new_input['similarity_threshold'] = min(max($new_input['similarity_threshold'], 20), 85); // Enforce range
8184 }
8185
8186 if (isset($input['xai_api_key'])) {
8187 $new_input['xai_api_key'] = sanitize_text_field($input['xai_api_key']);
8188 }
8189
8190 if (isset($input['claude_api_key'])) {
8191 $new_input['claude_api_key'] = sanitize_text_field($input['claude_api_key']);
8192 }
8193
8194 if (isset($input['enable_streaming_toggle'])) {
8195 $new_input['enable_streaming_toggle'] = ($input['enable_streaming_toggle'] === 'on') ? 'on' : 'off';
8196 } else {
8197 // If checkbox not checked, it won't be in $input, so set to 'off'
8198 $new_input['enable_streaming_toggle'] = 'off';
8199 }
8200
8201
8202 if (isset($input['deepseek_api_key'])) {
8203 $new_input['deepseek_api_key'] = sanitize_text_field($input['deepseek_api_key']);
8204 }
8205
8206 if (isset($input['gemini_api_key'])) {
8207 $new_input['gemini_api_key'] = sanitize_text_field($input['gemini_api_key']);
8208 }
8209
8210 if (isset($input['enable_woocommerce_integration'])) {
8211 $new_input['enable_woocommerce_integration'] = $input['enable_woocommerce_integration'] === 'on' ? 'on' : 'off';
8212 }
8213
8214 if (isset($input['privacy_toggle'])) {
8215 $new_input['privacy_toggle'] = $input['privacy_toggle'];
8216 }
8217
8218 if (isset($input['complianz_toggle'])) {
8219 $new_input['complianz_toggle'] = $input['complianz_toggle'];
8220 }
8221
8222 // Handle custom privacy text input
8223 if (isset($input['privacy_text'])) {
8224 // Allow basic HTML for links
8225 $new_input['privacy_text'] = wp_kses_post($input['privacy_text']);
8226 }
8227
8228 if (isset($input['system_prompt_instructions'])) {
8229 $new_input['system_prompt_instructions'] = sanitize_textarea_field($input['system_prompt_instructions']);
8230 }
8231
8232 if (isset($input['mxchat_pro_email'])) {
8233 $new_input['mxchat_pro_email'] = sanitize_email($input['mxchat_pro_email']);
8234 }
8235
8236 if (isset($input['mxchat_activation_key'])) {
8237 $new_input['mxchat_activation_key'] = sanitize_text_field($input['mxchat_activation_key']);
8238 }
8239
8240 if (isset($input['append_to_body'])) {
8241 $new_input['append_to_body'] = $input['append_to_body'] === 'on' ? 'on' : 'off';
8242 }
8243
8244 // Post type visibility settings
8245 if (isset($input['post_type_visibility_mode'])) {
8246 $allowed_modes = array('all', 'include', 'exclude');
8247 $new_input['post_type_visibility_mode'] = in_array($input['post_type_visibility_mode'], $allowed_modes)
8248 ? $input['post_type_visibility_mode']
8249 : 'all';
8250 }
8251
8252 if (isset($input['post_type_visibility_list'])) {
8253 if (is_array($input['post_type_visibility_list'])) {
8254 $new_input['post_type_visibility_list'] = array_map('sanitize_key', $input['post_type_visibility_list']);
8255 } else {
8256 $new_input['post_type_visibility_list'] = array();
8257 }
8258 }
8259
8260 if (isset($input['contextual_awareness_toggle'])) {
8261 $new_input['contextual_awareness_toggle'] = $input['contextual_awareness_toggle'] === 'on' ? 'on' : 'off';
8262 }
8263
8264 if (isset($input['show_frontend_debugger'])) {
8265 $new_input['show_frontend_debugger'] = $input['show_frontend_debugger'] === 'on' ? 'on' : 'off';
8266 }
8267
8268 if (isset($input['top_bar_title'])) {
8269 $new_input['top_bar_title'] = sanitize_text_field($input['top_bar_title']);
8270 }
8271
8272 if (isset($input['ai_agent_text'])) {
8273 $new_input['ai_agent_text'] = sanitize_text_field($input['ai_agent_text']);
8274 }
8275
8276 if (isset($input['enable_email_block'])) {
8277 $new_input['enable_email_block'] = sanitize_text_field($input['enable_email_block']);
8278 }
8279
8280 if (isset($input['email_blocker_header_content'])) {
8281 // wp_kses_post() allows standard HTML tags permitted by WordPress
8282 $new_input['email_blocker_header_content'] = wp_kses_post($input['email_blocker_header_content']);
8283 }
8284 if (isset($input['email_blocker_button_text'])) {
8285 $new_input['email_blocker_button_text'] = sanitize_text_field($input['email_blocker_button_text']);
8286 }
8287 // Sanitize name field toggle
8288 if (isset($input['enable_name_field'])) {
8289 $new_input['enable_name_field'] = ($input['enable_name_field'] === 'on') ? 'on' : 'off';
8290 } else {
8291 $new_input['enable_name_field'] = 'off';
8292 }
8293 // Sanitize name field placeholder
8294 if (isset($input['name_field_placeholder'])) {
8295 $new_input['name_field_placeholder'] = sanitize_text_field($input['name_field_placeholder']);
8296 }
8297 if (isset($input['intro_message'])) {
8298 $new_input['intro_message'] = wp_kses_post($input['intro_message']); // Use wp_kses_post instead
8299 }
8300
8301 if (isset($input['input_copy'])) {
8302 $new_input['input_copy'] = sanitize_text_field($input['input_copy']);
8303 }
8304
8305 if (isset($input['rate_limit_message'])) {
8306 $new_input['rate_limit_message'] = sanitize_text_field($input['rate_limit_message']);
8307 }
8308
8309 // Handle the new rate limits format
8310 if (isset($input['rate_limits']) && is_array($input['rate_limits'])) {
8311 $new_input['rate_limits'] = array();
8312 $allowed_limits = array('1', '3', '5', '10', '15', '20', '50', '100', 'unlimited');
8313 $allowed_timeframes = array('hourly', 'daily', 'weekly', 'monthly');
8314
8315 foreach ($input['rate_limits'] as $role_id => $settings) {
8316 $new_input['rate_limits'][$role_id] = array();
8317
8318 // Sanitize limit
8319 if (isset($settings['limit'])) {
8320 $limit = sanitize_text_field($settings['limit']);
8321 if (in_array($limit, $allowed_limits, true)) {
8322 $new_input['rate_limits'][$role_id]['limit'] = $limit;
8323 } else {
8324 $new_input['rate_limits'][$role_id]['limit'] = ($role_id === 'logged_out') ? '10' : '100'; // Default
8325 }
8326 }
8327
8328 // Sanitize timeframe
8329 if (isset($settings['timeframe'])) {
8330 $timeframe = sanitize_text_field($settings['timeframe']);
8331 if (in_array($timeframe, $allowed_timeframes, true)) {
8332 $new_input['rate_limits'][$role_id]['timeframe'] = $timeframe;
8333 } else {
8334 $new_input['rate_limits'][$role_id]['timeframe'] = 'daily'; // Default
8335 }
8336 }
8337
8338 // Sanitize message
8339 if (isset($settings['message'])) {
8340 $new_input['rate_limits'][$role_id]['message'] = sanitize_textarea_field($settings['message']);
8341 }
8342 }
8343 }
8344
8345 if (isset($input['pre_chat_message'])) {
8346 $new_input['pre_chat_message'] = sanitize_textarea_field($input['pre_chat_message']);
8347 }
8348
8349 if (isset($input['voyage_api_key'])) {
8350 $new_input['voyage_api_key'] = sanitize_text_field($input['voyage_api_key']);
8351 }
8352
8353 // Add to your sanitize function
8354 if (isset($input['embedding_model'])) {
8355 $allowed_models = array(
8356 'text-embedding-ada-002',
8357 'text-embedding-3-small',
8358 'text-embedding-3-large',
8359 'voyage-3-large',
8360 'gemini-embedding-exp-03-07'
8361 );
8362 if (in_array($input['embedding_model'], $allowed_models)) {
8363 $new_input['embedding_model'] = sanitize_text_field($input['embedding_model']);
8364 }
8365 }
8366
8367 if (isset($input['model'])) {
8368 if ($input['model'] === 'openrouter') {
8369 $new_input['model'] = 'openrouter';
8370 } else {
8371 $allowed_models = array(
8372 'gemini-2.0-flash',
8373 'gemini-2.0-flash-lite',
8374 'gemini-1.5-pro',
8375 'gemini-1.5-flash',
8376 'grok-4-0709',
8377 'grok-4-1-fast-reasoning',
8378 'grok-4-1-fast-non-reasoning',
8379 'grok-3-beta',
8380 'grok-3-fast-beta',
8381 'grok-3-mini-beta',
8382 'grok-3-mini-fast-beta',
8383 'grok-2',
8384 'deepseek-chat',
8385 'claude-sonnet-4-5-20250929',
8386 'claude-opus-4-1-20250805',
8387 'claude-haiku-4-5-20251001',
8388 'claude-opus-4-20250514',
8389 'claude-sonnet-4-20250514',
8390 'claude-3-7-sonnet-20250219',
8391 // REMOVED: 'claude-3-5-sonnet-20241022',
8392 'claude-3-opus-20240229',
8393 'claude-3-sonnet-20240229',
8394 'claude-3-haiku-20240307',
8395 'gpt-5.2',
8396 'gpt-5.1-2025-11-13',
8397 'gpt-5',
8398 'gpt-5-mini',
8399 'gpt-5-nano',
8400 'gpt-4.1-2025-04-14',
8401 'gpt-4o',
8402 'gpt-4o-mini',
8403 'gpt-4-turbo',
8404 'gpt-4',
8405 'gpt-3.5-turbo',
8406 );
8407
8408 if (in_array($input['model'], $allowed_models)) {
8409 $new_input['model'] = sanitize_text_field($input['model']);
8410 } else {
8411 // Fallback for any deprecated model
8412 $new_input['model'] = 'claude-3-7-sonnet-20250219';
8413 }
8414 }
8415 }
8416
8417 if (isset($input['openrouter_selected_model'])) {
8418 // Just sanitize it, don't validate against a whitelist
8419 $new_input['openrouter_selected_model'] = sanitize_text_field($input['openrouter_selected_model']);
8420 }
8421
8422 // ADD THIS:
8423 if (isset($input['openrouter_selected_model_name'])) {
8424 $new_input['openrouter_selected_model_name'] = sanitize_text_field($input['openrouter_selected_model_name']);
8425 }
8426
8427 if (isset($input['openrouter_api_key'])) {
8428 $new_input['openrouter_api_key'] = sanitize_text_field($input['openrouter_api_key']);
8429 }
8430
8431
8432 if (isset($input['close_button_color'])) {
8433 $new_input['close_button_color'] = sanitize_hex_color($input['close_button_color']);
8434 }
8435
8436 if (isset($input['chatbot_bg_color'])) {
8437 $new_input['chatbot_bg_color'] = sanitize_hex_color($input['chatbot_bg_color']);
8438 }
8439
8440 if (isset($input['woocommerce_consumer_key'])) {
8441 $new_input['woocommerce_consumer_key'] = sanitize_text_field($input['woocommerce_consumer_key']);
8442 }
8443
8444 if (isset($input['woocommerce_consumer_secret'])) {
8445 $new_input['woocommerce_consumer_secret'] = sanitize_text_field($input['woocommerce_consumer_secret']);
8446 }
8447
8448 if (isset($input['user_message_bg_color'])) {
8449 $new_input['user_message_bg_color'] = sanitize_hex_color($input['user_message_bg_color']);
8450 }
8451
8452 if (isset($input['user_message_font_color'])) {
8453 $new_input['user_message_font_color'] = sanitize_hex_color($input['user_message_font_color']);
8454 }
8455
8456 if (isset($input['bot_message_bg_color'])) {
8457 $new_input['bot_message_bg_color'] = sanitize_hex_color($input['bot_message_bg_color']);
8458 }
8459
8460 if (isset($input['bot_message_font_color'])) {
8461 $new_input['bot_message_font_color'] = sanitize_hex_color($input['bot_message_font_color']);
8462 }
8463
8464 if (isset($input['live_agent_message_bg_color'])) {
8465 $new_input['live_agent_message_bg_color'] = sanitize_hex_color($input['live_agent_message_bg_color']);
8466 }
8467
8468 if (isset($input['live_agent_message_font_color'])) {
8469 $new_input['live_agent_message_font_color'] = sanitize_hex_color($input['live_agent_message_font_color']);
8470 }
8471
8472 if (isset($input['mode_indicator_bg_color'])) {
8473 $new_input['mode_indicator_bg_color'] = sanitize_hex_color($input['mode_indicator_bg_color']);
8474 }
8475
8476 if (isset($input['mode_indicator_font_color'])) {
8477 $new_input['mode_indicator_font_color'] = sanitize_hex_color($input['mode_indicator_font_color']);
8478 }
8479
8480 if (isset($input['toolbar_icon_color'])) {
8481 $new_input['toolbar_icon_color'] = sanitize_hex_color($input['toolbar_icon_color']);
8482 }
8483
8484 if (isset($input['top_bar_bg_color'])) {
8485 $new_input['top_bar_bg_color'] = sanitize_hex_color($input['top_bar_bg_color']);
8486 }
8487
8488 if (isset($input['quick_questions_toggle_color'])) {
8489 $new_input['quick_questions_toggle_color'] = sanitize_hex_color($input['quick_questions_toggle_color']);
8490 }
8491
8492 if (isset($input['send_button_font_color'])) {
8493 $new_input['send_button_font_color'] = sanitize_hex_color($input['send_button_font_color']);
8494 }
8495
8496 if (isset($input['chatbot_background_color'])) {
8497 $new_input['chatbot_background_color'] = sanitize_hex_color($input['chatbot_background_color']);
8498 }
8499
8500 if (isset($input['icon_color'])) {
8501 $new_input['icon_color'] = sanitize_hex_color($input['icon_color']);
8502 }
8503
8504 if (isset($input['custom_icon'])) {
8505 $new_input['custom_icon'] = esc_url_raw($input['custom_icon']);
8506 }
8507
8508 if (isset($input['title_icon'])) {
8509 $new_input['title_icon'] = esc_url_raw($input['title_icon']);
8510 }
8511
8512 if (isset($input['chat_input_font_color'])) {
8513 $new_input['chat_input_font_color'] = sanitize_hex_color($input['chat_input_font_color']);
8514 }
8515
8516 // Sanitize link_target_toggle
8517 if (isset($input['link_target_toggle'])) {
8518 $new_input['link_target_toggle'] = $input['link_target_toggle'] === 'on' ? 'on' : 'off';
8519 }
8520
8521 // Sanitize Loops API Key
8522 if (isset($input['loops_api_key'])) {
8523 $new_input['loops_api_key'] = sanitize_text_field($input['loops_api_key']);
8524 }
8525
8526 if (isset($input['chat_persistence_toggle'])) {
8527 $new_input['chat_persistence_toggle'] = $input['chat_persistence_toggle'] === 'on' ? 'on' : 'off';
8528 }
8529
8530 if (isset($input['popular_question_1'])) {
8531 $new_input['popular_question_1'] = sanitize_text_field($input['popular_question_1']);
8532 }
8533
8534 if (isset($input['popular_question_2'])) {
8535 $new_input['popular_question_2'] = sanitize_text_field($input['popular_question_2']);
8536 }
8537
8538 if (isset($input['popular_question_3'])) {
8539 $new_input['popular_question_3'] = sanitize_text_field($input['popular_question_3']);
8540 }
8541
8542 if (isset($input['additional_popular_questions']) && is_array($input['additional_popular_questions'])) {
8543 $new_input['additional_popular_questions'] = array_map('sanitize_text_field', $input['additional_popular_questions']);
8544 }
8545
8546 // Sanitize Loops Mailing List
8547 if (isset($input['loops_mailing_list'])) {
8548 $new_input['loops_mailing_list'] = sanitize_text_field($input['loops_mailing_list']);
8549 }
8550
8551 // Sanitize Triggered Phrase Response
8552 if (isset($input['triggered_phrase_response'])) {
8553 $new_input['triggered_phrase_response'] = wp_kses_post($input['triggered_phrase_response']);
8554 }
8555 if (isset($input['email_capture_response'])) {
8556 $new_input['email_capture_response'] = wp_kses_post($input['email_capture_response']);
8557 }
8558
8559 // Sanitize Brave Search Settings
8560 if (isset($input['brave_api_key'])) {
8561 $new_input['brave_api_key'] = sanitize_text_field($input['brave_api_key']);
8562 }
8563
8564 if (isset($input['brave_image_count'])) {
8565 $image_count = intval($input['brave_image_count']);
8566 $new_input['brave_image_count'] = ($image_count >=1 && $image_count <=6) ? $image_count : 4;
8567 }
8568
8569 if (isset($input['brave_safe_search'])) {
8570 $allowed = array('strict', 'off');
8571 $new_input['brave_safe_search'] = in_array($input['brave_safe_search'], $allowed, true) ? $input['brave_safe_search'] : 'strict';
8572 }
8573
8574 if (isset($input['brave_news_count'])) {
8575 $news_count = intval($input['brave_news_count']);
8576 $new_input['brave_news_count'] = ($news_count >=1 && $news_count <=10) ? $news_count : 3;
8577 }
8578
8579 if (isset($input['brave_country'])) {
8580 $new_input['brave_country'] = sanitize_text_field($input['brave_country']);
8581 }
8582
8583 if (isset($input['brave_language'])) {
8584 $new_input['brave_language'] = sanitize_text_field($input['brave_language']);
8585 }
8586
8587 if (isset($input['chat_toolbar_toggle'])) {
8588 $new_input['chat_toolbar_toggle'] = $input['chat_toolbar_toggle'] === 'on' ? 'on' : 'off';
8589 }
8590
8591 // Sanitize PDF upload button toggle
8592 if (isset($input['show_pdf_upload_button'])) {
8593 $new_input['show_pdf_upload_button'] = $input['show_pdf_upload_button'] === 'on' ? 'on' : 'off';
8594 } else {
8595 $new_input['show_pdf_upload_button'] = 'off'; // If checkbox is unchecked
8596 }
8597
8598 // Sanitize Word upload button toggle
8599 if (isset($input['show_word_upload_button'])) {
8600 $new_input['show_word_upload_button'] = $input['show_word_upload_button'] === 'on' ? 'on' : 'off';
8601 } else {
8602 $new_input['show_word_upload_button'] = 'off'; // If checkbox is unchecked
8603 }
8604
8605 if (isset($input['pdf_intent_trigger_text'])) {
8606 $new_input['pdf_intent_trigger_text'] = sanitize_text_field($input['pdf_intent_trigger_text']);
8607 }
8608
8609 if (isset($input['pdf_intent_success_text'])) {
8610 $new_input['pdf_intent_success_text'] = sanitize_text_field($input['pdf_intent_success_text']);
8611 }
8612
8613 if (isset($input['pdf_intent_error_text'])) {
8614 $new_input['pdf_intent_error_text'] = sanitize_text_field($input['pdf_intent_error_text']);
8615 }
8616
8617 if (isset($input['pdf_max_pages'])) {
8618 $new_input['pdf_max_pages'] = intval($input['pdf_max_pages']);
8619 if ($new_input['pdf_max_pages'] < 1 || $new_input['pdf_max_pages'] > 69) {
8620 $new_input['pdf_max_pages'] = 69; // Default to 69 if out of range
8621 }
8622 }
8623
8624 if (isset($input['live_agent_webhook_url'])) {
8625 $new_input['live_agent_webhook_url'] = esc_url_raw($input['live_agent_webhook_url']);
8626 }
8627 if (isset($input['live_agent_secret_key'])) {
8628 $new_input['live_agent_secret_key'] = sanitize_text_field($input['live_agent_secret_key']);
8629 }
8630
8631 // Live Agent Integration
8632 if (isset($input['live_agent_bot_token'])) {
8633 $new_input['live_agent_bot_token'] = sanitize_text_field($input['live_agent_bot_token']);
8634 }
8635
8636 if (isset($input['live_agent_user_ids'])) {
8637 $new_input['live_agent_user_ids'] = sanitize_textarea_field($input['live_agent_user_ids']);
8638 }
8639
8640 if (isset($input['live_agent_status'])) {
8641 $new_input['live_agent_status'] = ($input['live_agent_status'] === 'on') ? 'on' : 'off';
8642 }
8643 if (isset($input['live_agent_away_message'])) {
8644 $new_input['live_agent_away_message'] = sanitize_textarea_field($input['live_agent_away_message']);
8645 }
8646 if (isset($input['live_agent_notification_message'])) {
8647 $new_input['live_agent_notification_message'] = sanitize_textarea_field($input['live_agent_notification_message']);
8648 }
8649
8650 return $new_input;
8651 }
8652
8653
8654 // Method to append the chatbot to the body
8655 public function mxchat_append_chatbot_to_body() {
8656 $options = get_option('mxchat_options');
8657 if (isset($options['append_to_body']) && $options['append_to_body'] === 'on') {
8658 echo do_shortcode('[mxchat_chatbot floating="yes"]');
8659 }
8660 }
8661
8662
8663
8664
8665
8666 private function mxchat_fetch_loops_mailing_lists($api_key) {
8667 $url = 'https://app.loops.so/api/v1/lists';
8668 $response = wp_remote_get($url, array(
8669 'headers' => array(
8670 'Authorization' => 'Bearer ' . $api_key,
8671 'Content-Type' => 'application/json'
8672 )
8673 ));
8674
8675 if (is_wp_error($response)) {
8676 return array();
8677 }
8678
8679 $body = wp_remote_retrieve_body($response);
8680 $lists = json_decode($body, true);
8681
8682 return isset($lists) && is_array($lists) ? $lists : array();
8683 }
8684
8685 function mxchat_calculate_cosine_similarity($vec1, $vec2) {
8686 if (empty($vec1) || empty($vec2)) {
8687 return 0.0;
8688 }
8689
8690 $dot_product = 0.0;
8691 $norm_a = 0.0;
8692 $norm_b = 0.0;
8693
8694 for ($i = 0; $i < count($vec1); $i++) {
8695 $dot_product += $vec1[$i] * $vec2[$i];
8696 $norm_a += pow($vec1[$i], 2);
8697 $norm_b += pow($vec2[$i], 2);
8698 }
8699
8700 if ($norm_a == 0.0 || $norm_b == 0.0) {
8701 return 0.0;
8702 } else {
8703 return $dot_product / (sqrt($norm_a) * sqrt($norm_b));
8704 }
8705 }
8706
8707 /**
8708 * Validates nonce and deletes all chat prompts
8709 */
8710 public function mxchat_handle_delete_all_prompts() {
8711 //error_log('=== DELETE ALL DEBUG START ===');
8712
8713 // Verify nonce
8714 if (!isset($_POST['mxchat_delete_all_prompts_nonce']) || !wp_verify_nonce($_POST['mxchat_delete_all_prompts_nonce'], 'mxchat_delete_all_prompts_action')) {
8715 //error_log('DEBUG: Nonce verification failed');
8716 wp_die(__('Nonce verification failed.', 'mxchat'));
8717 }
8718
8719 // Check permissions
8720 if (!current_user_can('manage_options')) {
8721 //error_log('DEBUG: Permission check failed');
8722 wp_die(__('You do not have sufficient permissions to delete all prompts.', 'mxchat'));
8723 }
8724
8725 // Get bot_id from POST data
8726 $bot_id = isset($_POST['bot_id']) ? sanitize_text_field($_POST['bot_id']) : 'default';
8727 //error_log('DEBUG: Bot ID from POST: ' . $bot_id);
8728
8729 $success = true;
8730 $error_messages = array();
8731
8732 // Get bot-specific Pinecone configuration
8733 $pinecone_manager = MxChat_Pinecone_Manager::get_instance();
8734 $pinecone_options = $pinecone_manager->mxchat_get_bot_pinecone_options($bot_id);
8735
8736 //error_log('DEBUG: Pinecone options retrieved:');
8737 //error_log(' - Use Pinecone: ' . ($pinecone_options['mxchat_use_pinecone'] ?? 'NOT SET'));
8738 //error_log(' - API Key: ' . (empty($pinecone_options['mxchat_pinecone_api_key']) ? 'EMPTY' : 'SET'));
8739 //error_log(' - Host: ' . ($pinecone_options['mxchat_pinecone_host'] ?? 'NOT SET'));
8740
8741 $use_pinecone = ($pinecone_options['mxchat_use_pinecone'] ?? '0') === '1';
8742
8743 if ($use_pinecone && !empty($pinecone_options['mxchat_pinecone_api_key'])) {
8744 //error_log('[MXCHAT-DELETE] Pinecone is enabled for bot ' . $bot_id . ' - deleting all from Pinecone');
8745
8746 // Delete from bot-specific Pinecone index
8747 $result = $pinecone_manager->mxchat_delete_all_from_pinecone($pinecone_options);
8748
8749 //error_log('DEBUG: Delete all result: ' . ($result['success'] ? 'SUCCESS' : 'FAILED'));
8750 if (!$result['success']) {
8751 //error_log('DEBUG: Delete all error: ' . $result['message']);
8752 }
8753
8754 if (!$result['success']) {
8755 $success = false;
8756 $error_messages[] = $result['message'];
8757 }
8758
8759 // No cache clearing needed since we removed caching
8760
8761 } else {
8762 //error_log('[MXCHAT-DELETE] Pinecone not enabled for bot ' . $bot_id . ' - deleting from WordPress database');
8763
8764 // Delete from WordPress database
8765 global $wpdb;
8766 $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
8767
8768 // If multi-bot is active and we have a specific bot, delete only that bot's content
8769 if ($bot_id !== 'default' && class_exists('MxChat_Multi_Bot_Manager')) {
8770 $result = $wpdb->delete(
8771 $table_name,
8772 array('bot_id' => $bot_id),
8773 array('%s')
8774 );
8775 } else {
8776 // Delete all content for default bot
8777 $result = $wpdb->query("DELETE FROM {$table_name}");
8778 }
8779
8780 if ($result === false) {
8781 $success = false;
8782 $error_messages[] = 'Failed to delete from WordPress database';
8783 }
8784 }
8785
8786 // No cache clearing needed since we removed caching
8787
8788 //error_log('=== DELETE ALL DEBUG END ===');
8789
8790 // Redirect back with a success message and bot_id
8791 $redirect_url = add_query_arg(array(
8792 'page' => 'mxchat-prompts',
8793 'bot_id' => $bot_id,
8794 'all_deleted' => $success ? 'true' : 'false'
8795 ), admin_url('admin.php'));
8796
8797 wp_safe_redirect($redirect_url);
8798 exit;
8799 }
8800
8801
8802
8803 /**
8804 * Handles deletion prompt with nonce validation
8805 */
8806 public function mxchat_handle_delete_prompt() {
8807 // Sanitize and validate nonce
8808 $nonce = isset($_GET['_wpnonce']) ? sanitize_text_field(wp_unslash($_GET['_wpnonce'])) : '';
8809 if (empty($nonce) || !wp_verify_nonce($nonce, 'mxchat_delete_prompt_nonce')) {
8810 wp_die(esc_html__('Nonce verification failed.', 'mxchat'));
8811 }
8812
8813 // Check permissions
8814 if (!current_user_can('manage_options')) {
8815 wp_die(esc_html__('You do not have sufficient permissions to delete prompts.', 'mxchat'));
8816 }
8817
8818 // Get ID and source parameters
8819 $id = isset($_GET['id']) ? sanitize_text_field($_GET['id']) : '';
8820 $source = isset($_GET['source']) ? sanitize_text_field($_GET['source']) : '';
8821
8822 if (empty($id)) {
8823 wp_die(esc_html__('Invalid prompt ID.', 'mxchat'));
8824 }
8825
8826 $success = false;
8827 $error_message = '';
8828
8829 // Check if Pinecone is enabled and determine source automatically if not specified
8830 $pinecone_options = get_option('mxchat_pinecone_addon_options', array());
8831 $use_pinecone = ($pinecone_options['mxchat_use_pinecone'] ?? '0') === '1';
8832
8833 // If source is not specified, determine based on Pinecone configuration
8834 if (empty($source)) {
8835 $source = $use_pinecone ? 'pinecone' : 'wordpress';
8836 }
8837
8838 if ($source === 'pinecone' || $use_pinecone) {
8839 //error_log('[MXCHAT-DELETE] Deleting from Pinecone, ID: ' . $id);
8840
8841 // Handle Pinecone deletion
8842 if (empty($pinecone_options['mxchat_pinecone_host']) ||
8843 empty($pinecone_options['mxchat_pinecone_api_key'])) {
8844 wp_die(esc_html__('Pinecone configuration is missing.', 'mxchat'));
8845 }
8846
8847 // Delete from Pinecone using the vector ID directly
8848 $pinecone_manager = MxChat_Pinecone_Manager::get_instance();
8849 $result = $pinecone_manager->mxchat_delete_from_pinecone_by_vector_id(
8850 $id,
8851 $pinecone_options['mxchat_pinecone_api_key'],
8852 $pinecone_options['mxchat_pinecone_host']
8853 );
8854 if ($result['success']) {
8855 $success = true;
8856
8857 // Remove from vector cache
8858 $pinecone_manager->mxchat_remove_from_pinecone_vector_cache($id);
8859 $pinecone_manager->mxchat_remove_from_processed_content_caches($id);
8860
8861 set_transient('mxchat_admin_notice_success',
8862 esc_html__('Vector deleted successfully from Pinecone.', 'mxchat'), 30);
8863 } else {
8864 $error_message = $result['message'];
8865 set_transient('mxchat_admin_notice_error',
8866 esc_html__('Failed to delete from Pinecone: ', 'mxchat') . esc_html($error_message), 30);
8867 }
8868 } else {
8869 //error_log('[MXCHAT-DELETE] Deleting from WordPress database, ID: ' . $id);
8870
8871 // Handle WordPress database deletion
8872 global $wpdb;
8873 $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
8874
8875 // Clear cache and delete prompt
8876 wp_cache_delete('prompt_' . $id, 'mxchat_prompts');
8877
8878 $result = $wpdb->delete(
8879 $table_name,
8880 array('id' => intval($id)),
8881 array('%d')
8882 );
8883
8884 if ($result !== false) {
8885 $success = true;
8886 set_transient('mxchat_admin_notice_success',
8887 esc_html__('Entry deleted successfully.', 'mxchat'), 30);
8888 } else {
8889 set_transient('mxchat_admin_notice_error',
8890 esc_html__('Failed to delete entry from database.', 'mxchat'), 30);
8891 }
8892 }
8893
8894 // Redirect back to the prompts page
8895 wp_safe_redirect(add_query_arg(
8896 array(
8897 'page' => 'mxchat-prompts',
8898 'deleted' => $success ? 'true' : 'false'
8899 ),
8900 admin_url('admin.php')
8901 ));
8902 exit;
8903 }
8904
8905
8906
8907
8908 /**
8909 * Averages multiple vectors into a single vector
8910 */
8911 private function mxchat_average_vectors($vectors) {
8912 $vector_length = count($vectors[0]);
8913 $sum_vector = array_fill(0, $vector_length, 0);
8914
8915 foreach ($vectors as $vector) {
8916 for ($i = 0; $i < $vector_length; $i++) {
8917 $sum_vector[$i] += $vector[$i];
8918 }
8919 }
8920
8921 // Divide each component by the number of vectors to get the average
8922 $num_vectors = count($vectors);
8923 for ($i = 0; $i < $vector_length; $i++) {
8924 $sum_vector[$i] /= $num_vectors;
8925 }
8926
8927 return $sum_vector;
8928 }
8929
8930
8931
8932
8933 /**
8934 * Generates embeddings from input text for MXChat
8935 */
8936 public function mxchat_generate_embedding($text) {
8937 // Enable detailed logging for debugging
8938 //error_log('[MXCHAT-EMBED] Starting embedding generation. Text length: ' . strlen($text) . ' bytes');
8939 //error_log('[MXCHAT-EMBED] Text preview: ' . substr($text, 0, 100) . '...');
8940
8941 $options = get_option('mxchat_options');
8942 $selected_model = $options['embedding_model'] ?? 'text-embedding-ada-002';
8943 //error_log('[MXCHAT-EMBED] Selected embedding model: ' . $selected_model);
8944
8945 // Determine provider and endpoint
8946 if (strpos($selected_model, 'voyage') === 0) {
8947 $api_key = $options['voyage_api_key'] ?? '';
8948 $endpoint = 'https://api.voyageai.com/v1/embeddings';
8949 $provider_name = 'Voyage AI';
8950 //error_log('[MXCHAT-EMBED] Using Voyage AI API');
8951 } elseif (strpos($selected_model, 'gemini-embedding') === 0) {
8952 $api_key = $options['gemini_api_key'] ?? '';
8953 $endpoint = 'https://generativelanguage.googleapis.com/v1beta/models/' . $selected_model . ':embedContent';
8954 $provider_name = 'Google Gemini';
8955 //error_log('[MXCHAT-EMBED] Using Google Gemini API');
8956 } else {
8957 $api_key = $options['api_key'] ?? '';
8958 $endpoint = 'https://api.openai.com/v1/embeddings';
8959 $provider_name = 'OpenAI';
8960 //error_log('[MXCHAT-EMBED] Using OpenAI API');
8961 }
8962
8963 //error_log('[MXCHAT-EMBED] Using endpoint: ' . $endpoint);
8964
8965 if (empty($api_key)) {
8966 $error_message = sprintf('Missing %s API key. Please configure your API key in the MxChat settings.', $provider_name);
8967 //error_log('[MXCHAT-EMBED] Error: ' . $error_message);
8968 return $error_message;
8969 }
8970
8971 // Check if text is too long (for OpenAI, roughly estimate tokens as words/0.75)
8972 $estimated_tokens = ceil(str_word_count($text) / 0.75);
8973 //error_log('[MXCHAT-EMBED] Estimated token count: ~' . $estimated_tokens);
8974
8975 if ($estimated_tokens > 8000 && strpos($selected_model, 'voyage') === false && strpos($selected_model, 'gemini-embedding') === false) {
8976 //error_log('[MXCHAT-EMBED] Warning: Text may exceed OpenAI token limits (8K for most models)');
8977 // Consider truncating text here
8978 }
8979
8980 // Prepare request body based on provider
8981 if (strpos($selected_model, 'gemini-embedding') === 0) {
8982 // Gemini API format
8983 $request_body = array(
8984 'model' => 'models/' . $selected_model,
8985 'content' => array(
8986 'parts' => array(
8987 array('text' => $text)
8988 )
8989 )
8990 );
8991
8992 // Set output dimensionality to 1536 for consistency with other models
8993 $request_body['outputDimensionality'] = 1536;
8994 } else {
8995 // OpenAI/Voyage API format
8996 $request_body = array(
8997 'model' => $selected_model,
8998 'input' => $text
8999 );
9000
9001 // Add output_dimension for voyage-3-large model
9002 if ($selected_model === 'voyage-3-large') {
9003 $request_body['output_dimension'] = 2048;
9004 }
9005 }
9006
9007 //error_log('[MXCHAT-EMBED] Request prepared with model: ' . $selected_model);
9008
9009 // Prepare headers based on provider
9010 if (strpos($selected_model, 'gemini-embedding') === 0) {
9011 // Gemini uses API key as query parameter
9012 $endpoint .= '?key=' . $api_key;
9013 $headers = array(
9014 'Content-Type' => 'application/json'
9015 );
9016 } else {
9017 // OpenAI/Voyage use Bearer token
9018 $headers = array(
9019 'Authorization' => 'Bearer ' . $api_key,
9020 'Content-Type' => 'application/json'
9021 );
9022 }
9023
9024 // Make API request
9025 //error_log('[MXCHAT-EMBED] Sending API request to: ' . $endpoint);
9026 $response = wp_remote_post($endpoint, array(
9027 'body' => wp_json_encode($request_body),
9028 'headers' => $headers,
9029 'timeout' => 60 // Increased timeout for large inputs
9030 ));
9031
9032 // Handle wp_remote_post errors
9033 if (is_wp_error($response)) {
9034 $error_message = $response->get_error_message();
9035 //error_log('[MXCHAT-EMBED] WP Remote Post Error: ' . $error_message);
9036 return 'Connection error: ' . $error_message;
9037 }
9038
9039 // Get and check HTTP response code
9040 $http_code = wp_remote_retrieve_response_code($response);
9041 //error_log('[MXCHAT-EMBED] API Response Code: ' . $http_code);
9042
9043 if ($http_code !== 200) {
9044 $error_body = wp_remote_retrieve_body($response);
9045 //error_log('[MXCHAT-EMBED] API Error Response Body: ' . $error_body);
9046
9047 // Try to parse error for more details
9048 $error_json = json_decode($error_body, true);
9049 if (json_last_error() === JSON_ERROR_NONE && isset($error_json['error'])) {
9050 $error_type = $error_json['error']['type'] ?? 'unknown';
9051 $error_message = $error_json['error']['message'] ?? 'No message';
9052 //error_log('[MXCHAT-EMBED] API Error Type: ' . $error_type);
9053 //error_log('[MXCHAT-EMBED] API Error Message: ' . $error_message);
9054
9055 // Customize error message for common API errors
9056 if ($error_type === 'invalid_request_error' && strpos($error_message, 'API key') !== false) {
9057 $error_message = sprintf('Invalid %s API key. Please check your API key in the MxChat settings.', $provider_name);
9058 } elseif ($error_type === 'authentication_error') {
9059 $error_message = sprintf('%s authentication failed. Please verify your API key in the MxChat settings.', $provider_name);
9060 }
9061
9062 //error_log('[MXCHAT-EMBED] Returning error: ' . $error_message);
9063 return $error_message;
9064 }
9065
9066 $error_message = sprintf("API Error (HTTP %d): Unable to generate embedding", $http_code);
9067 //error_log('[MXCHAT-EMBED] Returning error: ' . $error_message);
9068 return $error_message;
9069 }
9070
9071 // Parse response body
9072 $response_body = wp_remote_retrieve_body($response);
9073 //error_log('[MXCHAT-EMBED] Received response length: ' . strlen($response_body) . ' bytes');
9074
9075 $response_data = json_decode($response_body, true);
9076
9077 if (json_last_error() !== JSON_ERROR_NONE) {
9078 $error = json_last_error_msg();
9079 //error_log('[MXCHAT-EMBED] JSON Parse Error: ' . $error);
9080 //error_log('[MXCHAT-EMBED] Response preview: ' . substr($response_body, 0, 200));
9081 return "Failed to parse API response: $error";
9082 }
9083
9084 // Handle different response formats based on provider
9085 if (strpos($selected_model, 'gemini-embedding') === 0) {
9086 // Gemini API response format
9087 if (isset($response_data['embedding']['values'])) {
9088 $embedding_dimensions = count($response_data['embedding']['values']);
9089 //error_log('[MXCHAT-EMBED] Successfully extracted Gemini embedding with ' . $embedding_dimensions . ' dimensions');
9090
9091 // Check if embedding dimensions are as expected (should be 1536)
9092 if ($embedding_dimensions !== 1536) {
9093 //error_log('[MXCHAT-EMBED] Warning: Unexpected Gemini embedding dimensions: ' . $embedding_dimensions);
9094 }
9095
9096 return $response_data['embedding']['values'];
9097 } else {
9098 //error_log('[MXCHAT-EMBED] Error: No embedding found in Gemini response');
9099 //error_log('[MXCHAT-EMBED] Response structure: ' . wp_json_encode(array_keys($response_data)));
9100
9101 if (isset($response_data['error'])) {
9102 $error_message = "Gemini API Error in response: " . wp_json_encode($response_data['error']);
9103 //error_log('[MXCHAT-EMBED] ' . $error_message);
9104 return $error_message;
9105 }
9106
9107 $error_message = "Invalid Gemini API response format: No embedding found";
9108 //error_log('[MXCHAT-EMBED] ' . $error_message);
9109 return $error_message;
9110 }
9111 } else {
9112 // OpenAI/Voyage API response format
9113 if (isset($response_data['data'][0]['embedding'])) {
9114 $embedding_dimensions = count($response_data['data'][0]['embedding']);
9115 //error_log('[MXCHAT-EMBED] Successfully extracted embedding with ' . $embedding_dimensions . ' dimensions');
9116
9117 // Check if embedding dimensions are as expected
9118 if (($selected_model === 'text-embedding-ada-002' && $embedding_dimensions !== 1536) ||
9119 ($selected_model === 'voyage-3-large' && $embedding_dimensions !== 2048)) {
9120 //error_log('[MXCHAT-EMBED] Warning: Unexpected embedding dimensions');
9121 }
9122
9123 return $response_data['data'][0]['embedding'];
9124 } else {
9125 //error_log('[MXCHAT-EMBED] Error: No embedding found in response');
9126 //error_log('[MXCHAT-EMBED] Response structure: ' . wp_json_encode(array_keys($response_data)));
9127
9128 if (isset($response_data['error'])) {
9129 $error_message = "API Error in response: " . wp_json_encode($response_data['error']);
9130 //error_log('[MXCHAT-EMBED] ' . $error_message);
9131 return $error_message;
9132 }
9133
9134 $error_message = "Invalid API response format: No embedding found";
9135 //error_log('[MXCHAT-EMBED] ' . $error_message);
9136 return $error_message;
9137 }
9138 }
9139 }
9140
9141
9142
9143 }
9144 ?>
9145