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

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

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