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

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