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

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