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

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