PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 2.6.3
MxChat – AI Chatbot & Content Generation for WordPress v2.6.3
3.2.21 3.2.20 3.2.19 3.2.18 3.2.17 3.2.16 3.2.15 3.2.14 3.2.12 3.2.13 3.2.11 3.2.10 3.2.9 3.2.8 3.2.7 3.2.6 3.2.5 3.2.4 3.2.3 3.2.2 3.2.1 2.0.3 2.0.4 2.0.5 2.0.6 All 152 releases
← All changes | includes/class-mxchat-admin.php +6220 -3094 2.0.32.6.3 View file →
@@ -6,13 +6,15 @@
6 6 class MxChat_Admin {
7 7 private $options;
8 8 private $chat_count;
9 9 private $is_activated;
10 + private $knowledge_manager;
10 11
11 - public function __construct() {
12 + public function __construct($knowledge_manager = null) {
12 13 $this->options = get_option('mxchat_options');
13 14 $this->chat_count = get_option('mxchat_chat_count', 0);
14 15 $this->is_activated = $this->is_license_active();
16 + $this->knowledge_manager = $knowledge_manager;
15 17
16 18 // Initialize default options if they are not set
17 19 if (!$this->options) {
18 20 $this->initialize_default_options();
@@ -23,49 +25,44 @@
23 25 add_action('admin_init', array($this, 'mxchat_page_init'));
24 26 add_action('admin_init', array($this, 'mxchat_prompts_page_init'));
25 27 add_action('admin_enqueue_scripts', array($this, 'mxchat_enqueue_admin_assets'));
26 28 add_action('wp_ajax_mxchat_delete_chat_history', array($this, 'mxchat_delete_chat_history'));
27 - add_action('admin_post_mxchat_submit_content', array($this, 'mxchat_handle_content_submission'));
28 29 add_action('admin_post_mxchat_delete_prompt', array($this, 'mxchat_handle_delete_prompt'));
29 30 add_action('wp_ajax_mxchat_fetch_chat_history', array($this, 'mxchat_fetch_chat_history'));
30 31 add_action('wp_ajax_nopriv_mxchat_fetch_chat_history', array($this, 'mxchat_fetch_chat_history'));
31 - add_action('admin_post_mxchat_submit_sitemap', array($this, 'mxchat_handle_sitemap_submission'));
32 32 add_action('wp_footer', array($this, 'mxchat_append_chatbot_to_body'));
33 33 add_action('admin_head-mxchat-prompts', array($this, 'mxchat_enqueue_admin_assets'));
34 34 add_action('admin_head-toplevel_page_mxchat-max', array($this, 'mxchat_enqueue_admin_assets'));
35 - add_action('wp_ajax_mxchat_activate_license', array($this, 'mxchat_handle_activate_license'));
36 35 add_action('admin_notices', array($this, 'mxchat_display_admin_notice'));
37 - add_action('wp_ajax_mxchat_save_inline_prompt', array($this, 'mxchat_save_inline_prompt'));
38 36 add_action('admin_post_mxchat_delete_all_prompts', array($this, 'mxchat_handle_delete_all_prompts'));
39 37 add_action('admin_post_mxchat_add_intent', array($this, 'mxchat_handle_add_intent'));
40 38 add_action('admin_post_mxchat_delete_intent', array($this, 'mxchat_handle_delete_intent'));
41 - add_action('admin_post_mxchat_update_intent_threshold', array($this, 'mxchat_handle_update_intent_threshold'));
42 - add_action('admin_post_mxchat_stop_processing', array($this, 'mxchat_stop_processing'));
43 - add_action('admin_post_mxchat_edit_intent', array($this, 'handle_edit_intent'));
44 - add_action('save_post', array($this, 'handle_post_update'), 10, 3);
45 - add_action('post_updated', array($this, 'handle_post_update'), 10, 3);
46 - add_action('wp_ajax_mxchat_save_setting', array($this, 'mxchat_save_setting_callback'));
47 - add_action('wp_trash_post', array($this, 'mxchat_handle_post_delete'));
48 - add_action('before_delete_post', array($this, 'mxchat_handle_post_delete'));
49 - add_action('wp_ajax_mxchat_save_prompts_setting', array($this, 'mxchat_save_prompts_setting_callback'));
50 - if (isset($this->options['enable_woocommerce_integration']) &&
51 - ($this->options['enable_woocommerce_integration'] === '1' ||
52 - $this->options['enable_woocommerce_integration'] === 'on')) {
39 + add_action('admin_post_mxchat_edit_intent', array($this, 'mxchat_handle_edit_intent'));
40 + add_action('wp_ajax_mxchat_export_transcripts', array($this, 'export_chat_transcripts'));
41 + add_action('admin_init', array($this, 'mxchat_transcripts_page_init'));
42 + add_action('wp_ajax_dismiss_live_agent_notice', array($this, 'dismiss_live_agent_notice'));
43 + add_action('mxchat_cleanup_old_transcripts', array($this, 'cleanup_old_transcripts'));
53 44
54 - //error_log('MxChat Admin: WooCommerce integration is enabled, adding hooks');
55 - add_action('wp_insert_post', array($this, 'mxchat_handle_product_change'), 10, 3);
56 - add_action('wp_trash_post', array($this, 'mxchat_handle_product_delete'));
57 - add_action('before_delete_post', array($this, 'mxchat_handle_product_delete'));
58 - }
45 + add_action('admin_init', array($this, 'register_pinecone_settings'));
46 +
47 + add_action('admin_notices', array($this, 'display_admin_notices'));
48 +
49 + add_action('wp_ajax_mxchat_test_streaming_actual', [$this, 'mxchat_handle_test_streaming_actual']);
50 + add_action('wp_ajax_mxchat_test_streaming', [$this, 'mxchat_handle_test_streaming']); // Keep existing as fallback
51 +
52 + add_action('wp_ajax_mxchat_save_selected_bot', array($this, 'mxchat_save_selected_bot'));
53 + add_action('wp_ajax_mxchat_fetch_openrouter_models', array($this, 'fetch_openrouter_models'));
54 + add_action('wp_ajax_mxchat_get_rag_context', array($this, 'mxchat_get_rag_context'));
59 55 }
60 56
61 - // Method to check if the license is active
62 57 private function is_license_active() {
63 - $license_status = get_option('mxchat_license_status', esc_html__('inactive', 'mxchat'));
64 - return $license_status === esc_html__('active', 'mxchat');
65 - }
58 + // Get the raw value without translation
59 + $license_status = get_option('mxchat_license_status', 'inactive');
66 60
67 - // Initialize default options
61 + // Check against multiple possible values, bypassing translation issues
62 + return ($license_status === 'active' || $license_status === esc_html__('active', 'mxchat'));
63 +}
64 +
68 65 private function initialize_default_options() {
69 66 $default_options = array(
70 67 'api_key' => '',
71 68 'xai_api_key' => '',
@@ -70,25 +67,49 @@
70 67 'api_key' => '',
71 68 'xai_api_key' => '',
72 69 'claude_api_key' => '',
73 70 'deepseek_api_key' => '',
74 - 'system_prompt_instructions' => esc_html__('[EXAMPLE INSTRUCTIONS] You are an AI Chatbot assistant for this website. The primary subject you should focus on is [insert proper subject here]. Your main goal is to assist visitors with questions related to this specific topic. Here are some key things to keep in mind:
75 - - Your name is [Chatbot Name].
76 - - Stay focused on topics related to [insert proper subject here]. If a visitor asks about an unrelated topic, politely redirect the conversation to how you can assist them with this subject.
77 - - Do not make up links to pages. Only use specific links you see in your knowledgebase from [insert your domain]. Do not make assumptions or make up URLs.
78 - - Keep your responses short, concise, and to the point. Provide clear and direct answers suitable for a chatbot interaction.
79 - - Provide answers based on the knowledge available to you. If you do not have an answer to a specific question, let the visitor know that you don’t have the information.', 'mxchat'),
71 + 'voyage_api_key' => '',
72 + 'gemini_api_key' => '',
73 + 'enable_streaming_toggle' => 'on',
74 + 'embedding_model' => 'text-embedding-ada-002',
75 + 'system_prompt_instructions' => 'You are an AI Chatbot assistant for this website. Your main goal is to assist visitors with questions and provide helpful information. Here are your key guidelines:
76 +
77 + # Response Style - CRITICALLY IMPORTANT
78 + - MAXIMUM LENGTH: 1-3 short sentences per response
79 + - Ultra-concise: Get straight to the answer with no filler
80 + - No introductions like "Sure!" or "I\'d be happy to help"
81 + - No phrases like "based on my knowledge" or "according to information"
82 + - No explanatory text before giving the answer
83 + - No summaries or repetition
84 + - Hyperlink all URLs
85 + - Respond in user\'s language
86 + - Minor chit chat or conversation is okay, but try to keep it focused on [insert topic]
87 +
88 + # Knowledge Base Requirements - PREVENT HALLUCINATIONS
89 + - ONLY answer questions using information explicitly provided in OFFICIAL KNOWLEDGE DATABASE CONTENT sections marked with ===== delimiters
90 + - If required information is NOT in the knowledge database: "I don\'t have enough information in my knowledge base to answer that question accurately."
91 + - NEVER invent or hallucinate URLs, links, product specs, procedures, dates, statistics, names, contacts, or company information
92 + - When knowledge base information is unclear or contradictory, acknowledge the limitation rather than guessing
93 + - Better to admit insufficient information than provide inaccurate answers',
80 94 'model' => esc_html__('gpt-4o', 'mxchat'),
81 95 'rate_limit_logged_out' => esc_html__('100', 'mxchat'),
82 96 'role_rate_limits' => array(),
83 97 'rate_limit_message' => esc_html__('Rate limit exceeded. Please try again later.', 'mxchat'),
84 98 'enable_email_block' => '',
85 - 'email_blocker_header_content' => esc_html__("<h2>Welcome to Our Chat!</h2>\n<p>Let's get started. Enter your email to begin chatting with us.</p>", 'mxchat'),
99 + 'email_blocker_header_content' => __("<h2>Welcome to Our Chat!</h2>\n<p>Let's get started. Enter your email to begin chatting with us.</p>", 'mxchat'),
86 100 'email_blocker_button_text' => esc_html__('Start Chat', 'mxchat'),
101 + 'enable_name_field' => 'off', // NEW
102 + 'name_field_placeholder' => esc_html__('Enter your name', 'mxchat'), // NEW
87 103 'top_bar_title' => esc_html__('MxChat', 'mxchat'),
88 - 'intro_message' => esc_html__('Hello! How can I assist you today?', 'mxchat'),
104 + 'intro_message' => __('Hello! How can I assist you today?', 'mxchat'),
105 + 'ai_agent_text' => esc_html__('AI Agent', 'mxchat'),
89 106 'input_copy' => esc_html__('How can I assist?', 'mxchat'),
90 107 'append_to_body' => esc_html__('off', 'mxchat'),
108 + 'post_type_visibility_mode' => 'all', // 'all', 'include', 'exclude'
109 + 'post_type_visibility_list' => array(), // Array of post type slugs
110 + 'contextual_awareness_toggle' => 'off',
111 + 'show_frontend_debugger' => 'on',
91 112 'close_button_color' => esc_html__('#fff', 'mxchat'),
92 113 'chatbot_bg_color' => esc_html__('#fff', 'mxchat'),
93 114 'user_message_bg_color' => esc_html__('#fff', 'mxchat'),
94 115 'user_message_font_color' => esc_html__('#212121', 'mxchat'),
@@ -105,17 +126,19 @@
105 126
106 127 // New fields for Loops Integration
107 128 'loops_api_key' => '',
108 129 'loops_mailing_list' => '',
109 - 'triggered_phrase_response' => esc_html__('Would you like to join our mailing list? Please provide your email below.', 'mxchat'),
110 - 'email_capture_response' => esc_html__('Thank you for providing your email! You\'ve been added to our list.', 'mxchat'),
130 + 'triggered_phrase_response' => __('Would you like to join our mailing list? Please provide your email below.', 'mxchat'),
131 + 'email_capture_response' => __('Thank you for providing your email! You\'ve been added to our list.', 'mxchat'),
111 132 'popular_question_1' => '',
112 133 'popular_question_2' => '',
113 134 'popular_question_3' => '',
114 - 'pdf_intent_trigger_text' => esc_html__("Please provide the URL to the PDF you'd like to discuss.", 'mxchat'),
115 - 'pdf_intent_success_text' => esc_html__("I've processed the PDF. What questions do you have about it?", 'mxchat'),
116 - 'pdf_intent_error_text' => esc_html__("Sorry, I couldn't process the PDF. Please ensure it's a valid file.", 'mxchat'),
135 + 'pdf_intent_trigger_text' => __("Please provide the URL to the PDF you'd like to discuss.", 'mxchat'),
136 + 'pdf_intent_success_text' => __("I've processed the PDF. What questions do you have about it?", 'mxchat'),
137 + 'pdf_intent_error_text' => __("Sorry, I couldn't process the PDF. Please ensure it's a valid file.", 'mxchat'),
117 138 'pdf_max_pages' => 69,
139 + 'show_pdf_upload_button' => 'on',
140 + 'show_word_upload_button' => 'on',
118 141
119 142 // Live Agent Integration
120 143 'live_agent_webhook_url' => '',
121 144 'live_agent_secret_key' => '',
@@ -149,10 +172,8 @@
149 172 // Update the $this->options property
150 173 $this->options = $merged_options;
151 174 }
152 175
153 -
154 -
155 176 public function mxchat_add_plugin_page() {
156 177 // Main menu page
157 178 add_menu_page(
158 179 esc_html__('MxChat Settings', 'mxchat'),
@@ -173,26 +194,24 @@
173 194 'mxchat-prompts',
174 195 array($this, 'mxchat_create_prompts_page')
175 196 );
176 197
177 - // Submenu page for Chat Transcripts
178 198 add_submenu_page(
179 - 'mxchat-max', // Corrected parent slug to match the main menu
199 + 'mxchat-max',
180 200 esc_html__('Chat Transcripts', 'mxchat'),
181 201 esc_html__('Transcripts', 'mxchat'),
182 202 'manage_options',
183 203 'mxchat-transcripts',
184 - array($this, 'mxchat_create_transcripts_page') // Prefixed function name with mxchat_
204 + array($this, 'mxchat_create_transcripts_page')
185 205 );
186 206
187 - // Submenu page for Intents
188 207 add_submenu_page(
189 - 'mxchat-max', // Parent slug
190 - esc_html__('MxChat Intents', 'mxchat'), // Page title
191 - esc_html__('Intents', 'mxchat'), // Menu title
192 - 'manage_options', // Capability
193 - 'mxchat-intents', // Menu slug
194 - array($this, 'mxchat_intents_page_html') // Callback function
208 + 'mxchat-max',
209 + esc_html__('MxChat Actions', 'mxchat'),
210 + esc_html__('Actions', 'mxchat'),
211 + 'manage_options',
212 + 'mxchat-actions',
213 + array($this, 'mxchat_actions_page_html')
195 214 );
196 215
197 216 add_submenu_page(
198 217 'mxchat-max',
@@ -219,156 +238,299 @@
219 238 $addons_page = new MxChat_Addons();
220 239 $addons_page->render_page();
221 240 }
222 241
223 -public function mxchat_save_setting_callback() {
224 - check_ajax_referer('mxchat_save_setting_nonce');
225 - if (!current_user_can('manage_options')) {
226 - wp_send_json_error(['message' => esc_html__('Unauthorized', 'mxchat')]);
242 +/**
243 + * Test actual streaming functionality in the WordPress environment
244 + */
245 +public function mxchat_handle_test_streaming_actual() {
246 + check_ajax_referer('mxchat_test_streaming_nonce', 'nonce');
247 +
248 + // Check if headers have already been sent
249 + if (headers_sent()) {
250 + wp_send_json_error(['message' => 'Headers already sent - streaming not possible']);
251 + return;
227 252 }
228 - $name = isset($_POST['name']) ? $_POST['name'] : '';
229 - // Strip slashes from the value before saving
230 - $value = isset($_POST['value']) ? stripslashes($_POST['value']) : '';
231 - if (empty($name)) {
232 - wp_send_json_error(['message' => esc_html__('Invalid field name', 'mxchat')]);
253 +
254 + // Check for required functions
255 + if (!function_exists('curl_init')) {
256 + wp_send_json_error(['message' => 'cURL not available - streaming requires cURL']);
257 + return;
233 258 }
234 - // Load the full options array
259 +
260 + // Get user's selected model and API key
235 261 $options = get_option('mxchat_options', []);
236 - // Handle special cases
237 - switch ($name) {
238 - case 'additional_popular_questions':
239 - $questions = json_decode($value, true); // No need for stripslashes here
240 - if (is_array($questions)) {
241 - $options[$name] = $questions;
242 - // Also update old option for backwards compatibility
243 - update_option('additional_popular_questions', $questions);
244 - }
262 + $selected_model = $options['model'] ?? 'gpt-4o';
263 +
264 + // Get the provider from the model
265 + $model_parts = explode('-', $selected_model);
266 + $provider = strtolower($model_parts[0]);
267 +
268 + // Get the appropriate API key
269 + $api_key = '';
270 + switch ($provider) {
271 + case 'gpt':
272 + case 'o1':
273 + $api_key = $options['api_key'] ?? '';
245 274 break;
246 - case 'user_message_bg_color':
247 - case 'user_message_font_color':
248 - case 'bot_message_bg_color':
249 - case 'bot_message_font_color':
250 - case 'top_bar_bg_color':
251 - case 'send_button_font_color':
252 - case 'chatbot_background_color':
253 - case 'icon_color':
254 - case 'chat_input_font_color':
255 - case 'live_agent_message_bg_color':
256 - case 'live_agent_message_font_color':
257 - case 'mode_indicator_bg_color':
258 - case 'mode_indicator_font_color':
259 - case 'toolbar_icon_color':
260 - // Store color values directly
261 - $options[$name] = $value;
275 + case 'claude':
276 + $api_key = $options['claude_api_key'] ?? '';
262 277 break;
263 - case 'live_agent_status':
264 - // Set the new value
265 - $options[$name] = ($value === 'on') ? 'on' : 'off';
278 + case 'grok':
279 + $api_key = $options['xai_api_key'] ?? '';
266 280 break;
267 - case 'enable_woocommerce_integration':
268 - // Handle values that used to be 1/0
269 - $options[$name] = ($value === 'on' || $value === '1') ? 'on' : 'off';
281 + case 'deepseek':
282 + $api_key = $options['deepseek_api_key'] ?? '';
270 283 break;
284 + case 'gemini':
285 + $api_key = $options['gemini_api_key'] ?? '';
286 + break;
271 287 default:
272 - // Handle role rate limits
273 - if (strpos($name, 'mxchat_options[role_rate_limits]') !== false) {
274 - // Extract role ID from the name
275 - preg_match('/\[role_rate_limits\]\[(.*?)\]/', $name, $matches);
276 - if (isset($matches[1])) {
277 - $role_id = $matches[1];
278 - // Initialize role_rate_limits if it doesn't exist
279 - if (!isset($options['role_rate_limits'])) {
280 - $options['role_rate_limits'] = [];
281 - }
282 - // Update the specific role's rate limit
283 - $options['role_rate_limits'][$role_id] = sanitize_text_field($value);
284 - break;
285 - }
286 - }
287 - // Handle toggles
288 - if (strpos($name, 'toggle') !== false || in_array($name, [
289 - 'chat_persistence_toggle',
290 - 'privacy_toggle',
291 - 'complianz_toggle',
292 - 'chat_toolbar_toggle' // Removed live_agent_status from here
293 - ])) {
294 - $options[$name] = ($value === 'on') ? 'on' : 'off';
295 - } else {
296 - // Store all other values directly
297 - $options[$name] = $value;
298 - }
288 + // Default to OpenAI for unknown models
289 + $api_key = $options['api_key'] ?? '';
290 + $provider = 'gpt';
299 291 break;
300 292 }
301 - // Save all updates
302 - $updated = update_option('mxchat_options', $options);
303 - // Handle backwards compatibility for certain fields
304 - $legacy_fields = ['brave_api_key', 'brave_image_count', 'brave_safe_search', 'brave_news_count',
305 - 'brave_country', 'brave_language', 'similarity_threshold'];
306 - if (in_array($name, $legacy_fields)) {
307 - // Also update the individual option for backwards compatibility
308 - update_option($name, $value);
293 +
294 + if (empty($api_key)) {
295 + wp_send_json_error(['message' => "API key not configured for {$provider} provider"]);
296 + return;
309 297 }
310 - if ($updated) {
311 - wp_send_json_success(['message' => esc_html__('Setting saved', 'mxchat')]);
312 - } else {
313 - wp_send_json_error(['message' => esc_html__('Update failed or no changes', 'mxchat')]);
298 +
299 + // Test streaming with the selected model and provider
300 + try {
301 + $this->perform_streaming_test($provider, $selected_model, $api_key);
302 + } catch (Exception $e) {
303 + wp_send_json_error(['message' => 'Streaming test exception: ' . $e->getMessage()]);
314 304 }
315 305 }
316 306
317 307 /**
318 - * Handles AJAX auto-save for prompts and auto-sync settings.
308 + * Perform the actual streaming test
319 309 */
320 -public function mxchat_save_prompts_setting_callback() {
321 - // Check the correct nonce for this action.
322 - check_ajax_referer( 'mxchat_prompts_setting_nonce', '_ajax_nonce' );
310 +private function perform_streaming_test($provider, $model, $api_key) {
311 + // Set streaming headers
312 + header('Content-Type: text/event-stream');
313 + header('Cache-Control: no-cache');
314 + header('X-Accel-Buffering: no'); // Disable nginx buffering
323 315
324 - if ( ! current_user_can( 'manage_options' ) ) {
325 - wp_send_json_error( [ 'message' => esc_html__( 'Unauthorized', 'mxchat' ) ] );
316 + // Prepare test message
317 + $test_message = "Please respond with exactly: 'Streaming test successful!' - send this as a short response for testing.";
318 +
319 + // Configure API request based on provider
320 + $url = '';
321 + $headers = [];
322 + $body = [];
323 +
324 + switch ($provider) {
325 + case 'gpt':
326 + case 'o1':
327 + $url = 'https://api.openai.com/v1/chat/completions';
328 + $headers = [
329 + 'Content-Type: application/json',
330 + 'Authorization: Bearer ' . $api_key
331 + ];
332 + $body = [
333 + 'model' => $model,
334 + 'messages' => [['role' => 'user', 'content' => $test_message]],
335 + 'max_tokens' => 50,
336 + 'temperature' => 0.3,
337 + 'stream' => true
338 + ];
339 + break;
340 +
341 + case 'claude':
342 + $url = 'https://api.anthropic.com/v1/messages';
343 + $headers = [
344 + 'Content-Type: application/json',
345 + 'x-api-key: ' . $api_key,
346 + 'anthropic-version: 2023-06-01'
347 + ];
348 + $body = [
349 + 'model' => $model,
350 + 'messages' => [['role' => 'user', 'content' => $test_message]],
351 + 'max_tokens' => 50,
352 + 'temperature' => 0.3,
353 + 'stream' => true
354 + ];
355 + break;
356 +
357 + case 'grok':
358 + $url = 'https://api.x.ai/v1/chat/completions';
359 + $headers = [
360 + 'Content-Type: application/json',
361 + 'Authorization: Bearer ' . $api_key
362 + ];
363 + $body = [
364 + 'model' => $model,
365 + 'messages' => [['role' => 'user', 'content' => $test_message]],
366 + 'max_tokens' => 50,
367 + 'temperature' => 0.3,
368 + 'stream' => true
369 + ];
370 + break;
371 +
372 + case 'deepseek':
373 + $url = 'https://api.deepseek.com/v1/chat/completions';
374 + $headers = [
375 + 'Content-Type: application/json',
376 + 'Authorization: Bearer ' . $api_key
377 + ];
378 + $body = [
379 + 'model' => $model,
380 + 'messages' => [['role' => 'user', 'content' => $test_message]],
381 + 'max_tokens' => 50,
382 + 'temperature' => 0.3,
383 + 'stream' => true
384 + ];
385 + break;
386 +
387 + default:
388 + echo "data: " . json_encode(['error' => 'Unsupported provider for streaming test: ' . $provider]) . "\n\n";
389 + flush();
390 + return;
326 391 }
327 392
328 - $name = isset( $_POST['name'] ) ? sanitize_text_field( wp_unslash( $_POST['name'] ) ) : '';
329 - $value = isset( $_POST['value'] ) ? sanitize_text_field( wp_unslash( $_POST['value'] ) ) : '';
393 + // Initialize cURL
394 + $ch = curl_init();
395 + curl_setopt($ch, CURLOPT_URL, $url);
396 + curl_setopt($ch, CURLOPT_RETURNTRANSFER, false);
397 + curl_setopt($ch, CURLOPT_POST, true);
398 + curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
399 + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
400 + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
401 + curl_setopt($ch, CURLOPT_TIMEOUT, 30);
402 + curl_setopt($ch, CURLOPT_WRITEFUNCTION, function($ch, $data) use ($provider) {
403 + return $this->process_streaming_test_data($data, $provider);
404 + });
330 405
331 - if ( empty( $name ) ) {
332 - wp_send_json_error( [ 'message' => esc_html__( 'Invalid field name', 'mxchat' ) ] );
406 + // Send initial test message
407 + echo "data: " . json_encode(['content' => '[Starting streaming test...]']) . "\n\n";
408 + flush();
409 +
410 + $result = curl_exec($ch);
411 + $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
412 + $curl_error = curl_error($ch);
413 + curl_close($ch);
414 +
415 + if ($curl_error) {
416 + echo "data: " . json_encode(['error' => 'cURL Error: ' . $curl_error]) . "\n\n";
417 + flush();
418 + return;
333 419 }
334 420
335 - // Handle standalone auto-sync settings.
336 - if ( in_array( $name, [ 'mxchat_auto_sync_posts', 'mxchat_auto_sync_pages' ], true ) ) {
337 - // Convert checkbox value to "1" if checked, "0" if not.
338 - $value = ( $value === 'on' ) ? '1' : '0';
339 - update_option( $name, $value );
340 - wp_send_json_success( [ 'message' => esc_html__( 'Setting saved', 'mxchat' ) ] );
421 + if ($http_code !== 200) {
422 + echo "data: " . json_encode(['error' => 'API returned HTTP ' . $http_code]) . "\n\n";
423 + flush();
424 + return;
341 425 }
342 426
343 - // Handle fields stored in the 'mxchat_prompts_options' array.
344 -// Handle fields stored in the 'mxchat_prompts_options' array.
345 -if ( false !== strpos( $name, 'mxchat_prompts_options[' ) ) {
346 - // Extract the key name using regex.
347 - if ( preg_match( '/mxchat_prompts_options\[(.*?)\]/', $name, $matches ) && ! empty( $matches[1] ) ) {
348 - $key = sanitize_text_field( $matches[1] );
349 - $options = get_option( 'mxchat_prompts_options', [] );
427 + // Send completion signal
428 + echo "data: [DONE]\n\n";
429 + flush();
430 +}
350 431
351 - // Remove Pinecone-specific settings from the main plugin handling.
352 - if ( 'mxchat_use_pinecone' === $key ) {
353 - // Optionally, you could simply ignore this field or set a default value.
354 - // For example, ensure it's always 0 or remove it entirely:
355 - // $options[ $key ] = '0';
356 - // Or, if you prefer, just return an error so that the main plugin doesn't handle it:
357 - wp_send_json_error( [ 'message' => esc_html__( 'Pinecone settings are now handled in the add-on.', 'mxchat' ) ] );
432 +/**
433 + * Process streaming data for the test
434 + */
435 +private function process_streaming_test_data($data, $provider) {
436 + static $chunk_count = 0;
437 +
438 + $lines = explode("\n", $data);
439 +
440 + foreach ($lines as $line) {
441 + if (trim($line) === '') {
442 + continue;
443 + }
444 +
445 + // Handle different provider formats
446 + if ($provider === 'claude') {
447 + // Claude uses event: and data: format
448 + if (strpos($line, 'data: ') === 0) {
449 + $json_str = substr($line, 6);
450 + $json = json_decode($json_str, true);
451 +
452 + if (isset($json['type']) && $json['type'] === 'content_block_delta') {
453 + if (isset($json['delta']['text'])) {
454 + $chunk_count++;
455 + echo "data: " . json_encode([
456 + 'content' => $json['delta']['text'],
457 + 'test_chunk' => $chunk_count
458 + ]) . "\n\n";
459 + flush();
460 + }
461 + }
462 + }
358 463 } else {
359 - // For all other fields, use the existing logic.
360 - $options[ $key ] = ( $value === 'on' || $value === '1' ) ? '1' : $value;
464 + // OpenAI, X.AI, DeepSeek format
465 + if (strpos($line, 'data: ') === 0) {
466 + $json_str = substr($line, 6);
467 +
468 + if ($json_str === '[DONE]') {
469 + // Don't echo [DONE] here, let the main function handle it
470 + continue;
471 + }
472 +
473 + $json = json_decode($json_str, true);
474 + if (isset($json['choices'][0]['delta']['content'])) {
475 + $chunk_count++;
476 + echo "data: " . json_encode([
477 + 'content' => $json['choices'][0]['delta']['content'],
478 + 'test_chunk' => $chunk_count
479 + ]) . "\n\n";
480 + flush();
481 + }
482 + }
361 483 }
362 - update_option( 'mxchat_prompts_options', $options );
363 - wp_send_json_success( [ 'message' => esc_html__( 'Setting saved', 'mxchat' ) ] );
364 484 }
485 +
486 + return strlen($data);
365 487 }
366 488
489 +/**
490 + * Updated version of your existing test method (keep this as a fallback)
491 + */
492 +public function mxchat_handle_test_streaming() {
493 + check_ajax_referer('mxchat_test_streaming_nonce', 'nonce');
367 494
368 - wp_send_json_error( [ 'message' => esc_html__( 'Field not recognized', 'mxchat' ) ] );
495 + // Use the actual streaming test instead
496 + $this->mxchat_handle_test_streaming_actual();
369 497 }
370 498
499 +
500 +public function register_pinecone_settings() {
501 + register_setting(
502 + 'mxchat_pinecone_addon_options',
503 + 'mxchat_pinecone_addon_options',
504 + array(
505 + 'type' => 'array',
506 + 'sanitize_callback' => array($this, 'sanitize_pinecone_settings'),
507 + 'default' => array(
508 + 'mxchat_use_pinecone' => '0',
509 + 'mxchat_pinecone_api_key' => '',
510 + 'mxchat_pinecone_host' => '',
511 + 'mxchat_pinecone_index' => '',
512 + 'mxchat_pinecone_environment' => ''
513 + )
514 + )
515 + );
516 +}
517 +
518 +public function sanitize_pinecone_settings($input) {
519 + $sanitized = array();
520 +
521 + $sanitized['mxchat_use_pinecone'] = isset($input['mxchat_use_pinecone']) ? '1' : '0';
522 + $sanitized['mxchat_pinecone_api_key'] = sanitize_text_field($input['mxchat_pinecone_api_key'] ?? '');
523 + $sanitized['mxchat_pinecone_host'] = sanitize_text_field($input['mxchat_pinecone_host'] ?? '');
524 + $sanitized['mxchat_pinecone_index'] = sanitize_text_field($input['mxchat_pinecone_index'] ?? '');
525 + $sanitized['mxchat_pinecone_environment'] = sanitize_text_field($input['mxchat_pinecone_environment'] ?? '');
526 +
527 + // Remove https:// from host if present
528 + $sanitized['mxchat_pinecone_host'] = str_replace(['https://', 'http://'], '', $sanitized['mxchat_pinecone_host']);
529 +
530 + return $sanitized;
531 +}
532 +
371 533 public function mxchat_display_admin_notice() {
372 534 // Success notice
373 535 if ($message = get_transient('mxchat_admin_notice_success')) {
374 536 ?>
@@ -391,2041 +553,3496 @@
391 553 delete_transient('mxchat_admin_notice_error'); // Clear the transient after displaying
392 554 }
393 555 }
394 556
557 +public function show_live_agent_disabled_banner() {
558 + $show_disabled_notice = get_option('mxchat_show_live_agent_disabled_notice', false);
395 559
396 -
397 -
398 -
560 + if ($show_disabled_notice) {
561 + ?>
562 + <div class="mxchat-live-agent-disabled-notice" id="mxchat-disabled-notice">
563 + <div class="mxchat-pro-notification">
564 + <button type="button" class="mxchat-dismiss-btn" onclick="dismissLiveAgentNotice()" aria-label="Dismiss notification">
565 + <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
566 + <line x1="18" y1="6" x2="6" y2="18"></line>
567 + <line x1="6" y1="6" x2="18" y2="18"></line>
568 + </svg>
569 + </button>
570 + <div class="mxchat-live-agent-content">
571 + <h3>🔧 Live Agent Integration Updated!</h3>
572 + <p>We've temporarily disabled your Live Agent integration due to recent enhancements that have made it much better! You can easily turn it back on by going to <strong>Toolbar & Components → Live Agent Settings</strong> and reviewing the new configuration options.</p>
573 + </div>
574 + </div>
575 + </div>
576 + <?php
577 + }
578 +}
399 579 public function mxchat_create_admin_page() {
580 + $this->add_live_agent_nonce();
400 581
401 582 ?>
402 - <div class="wrap mxchat-admin">
403 - <?php if (!$this->is_activated): ?>
404 - <div class="mxchat-pro-banner">
405 - <p>
406 - <?php echo esc_html__('For a limited time, get lifetime access and save $20 on MxChat Pro!', 'mxchat'); ?>
407 - <a href="https://mxchat.ai/" target="_blank"><?php echo esc_html__('Upgrade to Pro today', 'mxchat'); ?></a>
408 - </p>
583 + <div class="wrap mxchat-wrapper">
584 + <!-- Hero Section -->
585 + <div class="mxchat-hero">
586 + <h1 class="mxchat-main-title">
587 + <span class="mxchat-gradient-text">MxChat</span> Settings
588 + </h1>
589 + <p class="mxchat-hero-subtitle">
590 + <?php esc_html_e('Configure your AI chatbot, manage integrations and explore tutorials to get the most out of MxChat.', 'mxchat'); ?>
591 + </p>
409 592 </div>
410 - <?php endif; ?>
411 593
412 - <div class="mxchat-agents-banner">
413 - <p>
414 - <strong><?php echo esc_html__('The MxChat Add-Ons Platform', 'mxchat'); ?></strong><?php echo esc_html__(' is here! Pro users get unlimited access to our growing collection of powerful add-ons. ', 'mxchat'); ?><a href="<?php echo admin_url('admin.php?page=mxchat-addons'); ?>"><?php echo esc_html__('Explore all add-ons', 'mxchat'); ?></a><?php echo esc_html__(' including Forms Builder, Theme Customizer, Content Moderation, and more – all included with your lifetime Pro license!', 'mxchat'); ?>
415 - </p>
416 - </div>
594 + <div class="mxchat-content">
595 +
596 + <?php if (!$this->is_activated): ?>
597 + <div class="mxchat-pro-card">
598 + <div class="mxchat-pro-notification">
599 + <div class="mxchat-pro-content">
600 + <h3>🚀 Limited Lifetime Offer: Save 15% on MxChat Pro, Agency, or Agency Plus!</h3>
601 + <p>Unlock <strong>unlimited access</strong> to our growing collection of powerful add-ons including Admin AI Assistant (ChatGPT-like experience), Forms Builder, AI Theme Generator, WooCommerce, multi-bot, and more – all included with your <strong>lifetime license!</strong></p> </div>
602 + <div class="mxchat-pro-cta">
603 + <a href="https://mxchat.ai/" target="_blank" class="mxchat-button"><?php echo esc_html__('Upgrade Today', 'mxchat'); ?></a>
604 + <a href="<?php echo admin_url('admin.php?page=mxchat-addons'); ?>" class="mxchat-link"><?php echo esc_html__('Preview Add-ons', 'mxchat'); ?></a>
605 + </div>
606 + </div>
607 + </div>
608 + <?php endif; ?>
417 609
418 - <h2 class="mxchat-nav-tab-wrapper">
419 - <a href="#chatbot" class="mxchat-nav-tab mxchat-nav-tab-active" data-tab="chatbot"><?php echo esc_html__('Chatbot', 'mxchat'); ?></a>
420 - <a href="#embed" class="mxchat-nav-tab" data-tab="embed"><?php echo esc_html__('Integrations', 'mxchat'); ?></a>
421 - <a href="#general" class="mxchat-nav-tab" data-tab="general"><?php echo esc_html__('FAQ', 'mxchat'); ?></a>
422 - </h2>
610 + <?php
611 + $this->show_live_agent_disabled_banner();
612 + ?>
423 613
424 - <div id="chatbot" class="mxchat-tab-content active">
425 - <div class="mxchat-autosave-section">
426 - <?php do_settings_sections('mxchat-chatbot'); ?>
614 + <!-- Tabs Navigation -->
615 + <div class="mxchat-tabs">
616 + <button class="mxchat-tab-button active" data-tab="chatbot"><?php echo esc_html__('Chatbot', 'mxchat'); ?></button>
617 + <button class="mxchat-tab-button" data-tab="api-keys"><?php echo esc_html__('API Keys', 'mxchat'); ?></button>
618 + <button class="mxchat-tab-button" data-tab="embed"><?php echo esc_html__('Toolbar & Components', 'mxchat'); ?></button>
619 + <button class="mxchat-tab-button" data-tab="general"><?php echo esc_html__('YouTube Tutorials', 'mxchat'); ?></button>
427 620 </div>
428 - </div>
429 621
430 - <div id="embed" class="mxchat-tab-content">
431 - <div class="mxchat-autosave-section">
432 - <div class="mxchat-settings-section">
433 - <h2><?php echo esc_html__('WooCommerce Settings', 'mxchat'); ?></h2>
434 - <table class="form-table">
435 - <?php do_settings_fields('mxchat-embed', 'mxchat_woocommerce_section'); ?>
436 - </table>
622 + <!-- Tab Contents -->
623 + <div id="chatbot" class="mxchat-tab-content active">
624 + <div class="mxchat-card">
625 + <div class="mxchat-autosave-section">
626 + <?php do_settings_sections('mxchat-chatbot'); ?>
627 + </div>
437 628 </div>
438 - <div class="section-divider"></div>
629 + </div>
439 630
440 - <div class="mxchat-settings-section">
441 - <h2><?php echo esc_html__('Loops Settings', 'mxchat'); ?></h2>
442 - <table class="form-table">
443 - <?php do_settings_fields('mxchat-embed', 'mxchat_loops_section'); ?>
444 - </table>
631 + <div id="api-keys" class="mxchat-tab-content">
632 + <div class="mxchat-card">
633 + <div class="mxchat-autosave-section">
634 + <table class="form-table">
635 + <?php do_settings_fields('mxchat-api-keys', 'mxchat_api_keys_section'); ?>
636 + </table>
637 + </div>
445 638 </div>
639 + </div>
446 640
447 - <div class="section-divider"></div>
641 + <div id="embed" class="mxchat-tab-content">
448 642
449 - <div class="mxchat-settings-section">
450 - <h2><?php echo esc_html__('Brave Search Settings', 'mxchat'); ?></h2>
451 - <table class="form-table">
452 - <?php do_settings_fields('mxchat-embed', 'mxchat_brave_section'); ?>
453 - </table>
643 + <div class="mxchat-card">
644 + <h2><?php esc_html_e('Toolbar Settings', 'mxchat'); ?></h2>
645 + <div class="mxchat-autosave-section">
646 + <table class="form-table">
647 + <?php do_settings_fields('mxchat-embed', 'mxchat_pdf_intent_section'); ?>
648 + </table>
649 + </div>
454 650 </div>
455 651
456 - <div class="section-divider"></div>
457 652
458 - <div class="mxchat-settings-section">
459 - <h2><?php echo esc_html__('Toolbar Settings & Intents', 'mxchat'); ?></h2>
460 - <table class="form-table">
461 - <?php do_settings_fields('mxchat-embed', 'mxchat_pdf_intent_section'); ?>
462 - </table>
653 + <div class="mxchat-card">
654 + <h2><?php esc_html_e('Loops Settings', 'mxchat'); ?></h2>
655 + <div class="mxchat-autosave-section">
656 + <table class="form-table">
657 + <?php do_settings_fields('mxchat-embed', 'mxchat_loops_section'); ?>
658 + </table>
659 + </div>
463 660 </div>
464 661
465 - <div class="section-divider"></div>
662 + <div class="mxchat-card">
663 + <h2><?php esc_html_e('Brave Search Settings', 'mxchat'); ?></h2>
664 + <div class="mxchat-autosave-section">
665 + <table class="form-table">
666 + <?php do_settings_fields('mxchat-embed', 'mxchat_brave_section'); ?>
667 + </table>
668 + </div>
669 + </div>
466 670
467 - <!-- Live Agent Settings Section -->
468 - <div class="mxchat-settings-section">
469 - <h2><?php echo esc_html__('Live Agent Settings', 'mxchat'); ?></h2>
470 - <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__('to set up live agent transfer via Slack.', 'mxchat'); ?></p>
471 - <table class="form-table">
472 - <?php do_settings_fields('mxchat-embed', 'mxchat_live_agent_section'); ?>
473 - </table>
671 + <div class="mxchat-card">
672 + <h2><?php esc_html_e('Live Agent Settings', 'mxchat'); ?></h2>
673 + <div class="mxchat-autosave-section">
674 +<p><?php echo esc_html__('Visit our', 'mxchat'); ?> <a href="https://mxchat.ai/documentation/#slack_integration" target="_blank"><?php echo esc_html__('documentation page', 'mxchat'); ?></a> <?php echo esc_html__('for setup instructions and to see what\'s changed in the latest update.', 'mxchat'); ?></p> <table class="form-table">
675 + <?php do_settings_fields('mxchat-embed', 'mxchat_live_agent_section'); ?>
676 + </table>
677 + </div>
474 678 </div>
475 679 </div>
476 - </div>
477 680
681 + <div id="general" class="mxchat-tab-content">
682 + <div class="mxchat-card">
683 + <?php do_settings_sections('mxchat-general'); ?>
684 + <div class="video-tutorials-section">
478 685
479 - <div id="general" class="mxchat-tab-content">
480 - <?php do_settings_sections('mxchat-general'); ?>
481 - <p>
482 - <?php echo esc_html__('If you’re having trouble with setup or getting the responses you need, we encourage you to review our', 'mxchat'); ?>
483 - <a href="https://mxchat.ai/documentation/" target="_blank" rel="noopener noreferrer"><?php echo esc_html__('documentation', 'mxchat'); ?></a> <?php echo esc_html__('or', 'mxchat'); ?>
484 - <a href="https://wordpress.org/support/plugin/mxchat-basic/" target="_blank" rel="noopener noreferrer"><?php echo esc_html__('create a support ticket', 'mxchat'); ?></a>.
485 - </p>
486 - <p>
487 - <?php echo esc_html__('If you like our plugin, please consider', 'mxchat'); ?> <a href="https://wordpress.org/plugins/mxchat-basic/#reviews" target="_blank" rel="noopener noreferrer"><?php echo esc_html__('leaving us a review', 'mxchat'); ?></a>.
488 - </p>
489 686
490 - <div class="faq-item">
491 - <h3><?php echo esc_html__('How does the Claude API integration work?', 'mxchat'); ?></h3>
492 - <p>
493 - <?php echo esc_html__('The Claude API, provided by Anthropic, allows for intelligent, context-aware chatbot responses in MxChat. To use it, you will need both an OpenAI API key and a Claude API key. This is necessary because the system utilizes OpenAI for vector embedding in the Retrieval-Augmented Generation (RAG) process, as Claude does not currently offer an embedding API.', 'mxchat'); ?>
494 - </p>
495 - <p>
496 - <?php echo esc_html__('When using the Claude API, your custom content is sent to Claude for generating responses, while the embeddings are processed through OpenAI. This ensures your chatbot provides accurate and engaging responses while maintaining knowledge relevant to your website.', 'mxchat'); ?>
497 - </p>
498 - <p>
499 - <?php echo esc_html__('You can obtain your Claude API key by signing up on the', 'mxchat'); ?> <a href="https://www.anthropic.com/api" target="_blank" rel="noopener"><?php echo esc_html__('Anthropic API page', 'mxchat'); ?></a>.
500 - </p>
501 - </div>
687 + <div class="support-notification">
688 + <div class="support-notification-icon">
689 + <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
690 + <path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"/>
691 + <path d="M12 9v4"/>
692 + <path d="M12 17h.01"/>
693 + </svg>
694 + </div>
695 + <div class="support-notification-content">
696 + <h3 class="support-notification-title"><?php echo esc_html__('Need Help? We\'re Here for You!', 'mxchat'); ?></h3>
697 + <p class="support-notification-message">
698 + <?php echo esc_html__('Our goal is to provide the best experience and AI chatbot plugin available. If you\'re having trouble or believe something is not working as expected, please don\'t hesitate to submit a support ticket.', 'mxchat'); ?>
699 + </p>
700 + <a href="https://wordpress.org/support/plugin/mxchat-basic/" target="_blank" rel="noopener noreferrer" class="support-notification-button">
701 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
702 + <path d="M14 9a2 2 0 0 1-2 2H6l-4 4V4c0-1.1.9-2 2-2h8a2 2 0 0 1 2 2v5Z"/>
703 + <path d="M18 9h2a2 2 0 0 1 2 2v11l-4-4h-6a2 2 0 0 1-2-2v-1"/>
704 + </svg>
705 + <?php echo esc_html__('Submit Support Ticket', 'mxchat'); ?>
706 + </a>
707 + </div>
708 +</div>
502 709
503 - <div class="faq-item">
504 - <h3><?php echo esc_html__('How does the X.AI API integration work?', 'mxchat'); ?></h3>
505 - <p>
506 - <?php echo esc_html__('The X.AI API, released on 10.21.24, is currently in beta. To use it, you will need both an OpenAI API key and an X.AI API key. This is because OpenAI handles vector embeddings in the Retrieval-Augmented Generation (RAG) process, as X.AI does not yet provide an embedding API.', 'mxchat'); ?>
507 - </p>
508 - <p>
509 - <?php echo esc_html__('Custom content is sent to the X.AI API for generating responses, while OpenAI processes the embeddings. This allows the chatbot to provide advanced responses while maintaining context from your website. You can get your X.AI API key from the', 'mxchat'); ?> <a href="https://docs.x.ai/docs" target="_blank" rel="noopener"><?php echo esc_html__('X.AI API documentation', 'mxchat'); ?></a>.
510 - </p>
511 - </div>
710 + <div class="tutorial-grid">
512 711
513 - <div class="faq-item">
514 - <h3><?php echo esc_html__('Do I need an OpenAI API key to use the chatbot?', 'mxchat'); ?></h3>
515 - <p>
516 - <?php echo esc_html__('Yes, you will need an OpenAI API key to power the chatbot. You can obtain an API key by signing up on the', 'mxchat'); ?> <a href="https://platform.openai.com/signup" target="_blank" rel="noopener"><?php echo esc_html__('OpenAI platform', 'mxchat'); ?></a>. <?php echo esc_html__('After signing up, you must add credits to your account—typically, $5 in credits is sufficient to get started.', 'mxchat'); ?>
517 - </p>
518 - <p>
519 - <?php echo esc_html__('Once you have your API key, simply enter it in the chatbot\'s settings to enable functionality. The chatbot relies on OpenAI’s models for generating responses, so having sufficient credits in your OpenAI account is essential for smooth operation.', 'mxchat'); ?>
520 - </p>
712 + <div class="tutorial-item">
713 + <h3><?php echo esc_html__('AI Theme Generator Tutorial', 'mxchat'); ?></h3>
714 + <div class="video-description">
715 + <p><?php echo esc_html__('Learn how to instantly restyle your chatbot using plain English prompts. The AI Theme Generator lets you generate and apply beautiful designs with real-time previews—no CSS skills needed.', 'mxchat'); ?></p>
716 + <a href="https://www.youtube.com/watch?v=rSQDW2qbtRU&t" target="_blank" rel="noopener" class="video-link">
717 + <span class="video-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 19c-2.3 0-6.4-.2-8.1-.6-.7-.2-1.2-.7-1.4-1.4-.3-1.1-.5-3.4-.5-5s.2-3.9.5-5c.2-.7.7-1.2 1.4-1.4C5.6 5.2 9.7 5 12 5s6.4.2 8.1.6c.7.2 1.2.7 1.4 1.4.3 1.1.5 3.4.5 5s-.2 3.9-.5 5c-.2.7-.7 1.2-1.4 1.4-1.7.4-5.8.6-8.1.6z"></path><polygon points="10 15 15 12 10 9 10 15"></polygon></svg></span>
718 + <?php echo esc_html__('Watch AI Theme Generator Tutorial', 'mxchat'); ?>
719 + </a>
720 + </div>
721 +</div>
722 +
723 +
724 + <div class="tutorial-item">
725 + <h3><?php echo esc_html__('MxChat Forms Tutorial', 'mxchat'); ?></h3>
726 + <div class="video-description">
727 + <p><?php echo esc_html__('Learn how to create and manage smart forms that automatically trigger during chat conversations.', 'mxchat'); ?></p>
728 + <a href="https://www.youtube.com/watch?v=3MrWy5dRalA" target="_blank" rel="noopener" class="video-link">
729 + <span class="video-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 19c-2.3 0-6.4-.2-8.1-.6-.7-.2-1.2-.7-1.4-1.4-.3-1.1-.5-3.4-.5-5s.2-3.9.5-5c.2-.7.7-1.2 1.4-1.4C5.6 5.2 9.7 5 12 5s6.4.2 8.1.6c.7.2 1.2.7 1.4 1.4.3 1.1.5 3.4.5 5s-.2 3.9-.5 5c-.2.7-.7 1.2-1.4 1.4-1.7.4-5.8.6-8.1.6z"></path><polygon points="10 15 15 12 10 9 10 15"></polygon></svg></span>
730 + <?php echo esc_html__('Watch MxChat Forms Tutorial', 'mxchat'); ?>
731 + </a>
521 732 </div>
733 + </div>
522 734
523 - <div class="faq-item">
524 - <h3><?php echo esc_html__('How do I add the chatbot to my site?', 'mxchat'); ?></h3>
525 - <p>
526 - <?php echo esc_html__('You can add the chatbot using the shortcode', 'mxchat'); ?> <code>[mxchat_chatbot floating="yes"]</code> <?php echo esc_html__('or', 'mxchat'); ?> <code>[mxchat_chatbot floating="no"]</code>. <?php echo esc_html__('For initial testing and styling, it\'s best to use the shortcode on a draft or non-public page. Once you’re ready to go live, enable the “Append Chat Widget to Body” option in the settings for site-wide integration (recommended) or add shortcode to the footer.', 'mxchat'); ?>
527 - </p>
528 - </div>
735 +<div class="tutorial-item">
736 + <h3><?php echo esc_html__('Admin Assistant Add-on', 'mxchat'); ?></h3>
737 + <div class="video-description">
738 + <p><?php echo esc_html__('Discover how to use the MxChat Admin Assistant to bring a ChatGPT-like experience directly inside your WordPress dashboard. Learn to access multiple AI models, save conversations, generate images, and use web search - all without leaving your admin panel.', 'mxchat'); ?></p>
739 + <a href="https://youtu.be/AdEA1k-UCFM" target="_blank" rel="noopener" class="video-link">
740 + <span class="video-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 19c-2.3 0-6.4-.2-8.1-.6-.7-.2-1.2-.7-1.4-1.4-.3-1.1-.5-3.4-.5-5s.2-3.9.5-5c.2-.7.7-1.2 1.4-1.4C5.6 5.2 9.7 5 12 5s6.4.2 8.1.6c.7.2 1.2.7 1.4 1.4.3 1.1.5 3.4.5 5s-.2-3.9-.5 5c-.2.7-.7 1.2-1.4 1.4-1.7.4-5.8.6-8.1.6z"></path><polygon points="10 15 15 12 10 9 10 15"></polygon></svg></span>
741 + <?php echo esc_html__('Watch Admin Assistant Tutorial', 'mxchat'); ?>
742 + </a>
743 + </div>
744 +</div>
529 745
530 - <div class="faq-item">
531 - <h3><?php echo esc_html__('How does the chatbot use my content to generate responses?', 'mxchat'); ?></h3>
532 - <p>
533 - <?php echo esc_html__('The chatbot uses AI and vector embeddings to connect users with relevant information. When you submit content, it converts it into a mathematical format. When a user asks a question, the bot matches it to your stored content and generates a response based on relevance. For example, submitting "Our phone number is 910-123-4567" allows the bot to retrieve this information when asked about contact details. To ensure related information is provided together, submit it in one entry.', 'mxchat'); ?>
534 - </p>
746 + <div class="tutorial-item">
747 + <h3><?php echo esc_html__('Intent Tester Guide', 'mxchat'); ?></h3>
748 + <div class="video-description">
749 + <p><?php echo esc_html__('Discover how to use the Intent Tester to fine-tune your chatbot\'s responses and ensure it accurately understands user queries.', 'mxchat'); ?></p>
750 + <a href="https://www.youtube.com/watch?v=uTr14tn59Hc" target="_blank" rel="noopener" class="video-link">
751 + <span class="video-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 19c-2.3 0-6.4-.2-8.1-.6-.7-.2-1.2-.7-1.4-1.4-.3-1.1-.5-3.4-.5-5s.2-3.9.5-5c.2-.7.7-1.2 1.4-1.4C5.6 5.2 9.7 5 12 5s6.4.2 8.1.6c.7.2 1.2.7 1.4 1.4.3 1.1.5 3.4.5 5s-.2 3.9-.5 5c-.2.7-.7 1.2-1.4 1.4-1.7.4-5.8.6-8.1.6z"></path><polygon points="10 15 15 12 10 9 10 15"></polygon></svg></span>
752 + <?php echo esc_html__('Watch Intent Tester Tutorial', 'mxchat'); ?>
753 + </a>
535 754 </div>
755 + </div>
536 756
537 - <div class="faq-item">
538 - <h3><?php echo esc_html__('Why does the chatbot sometimes make up links or information?', 'mxchat'); ?></h3>
539 - <p>
540 - <?php echo esc_html__('Occasionally, the chatbot may generate inaccurate links or information, known as "hallucinations." To minimize this, you can add system instructions to guide its behavior. For example, include an instruction like: "Only provide links you directly have access to or retrieve from the knowledge base. Do not make up links or information that you do not have direct access to." This helps the bot stay aligned with your content.', 'mxchat'); ?>
541 - </p>
757 + <div class="tutorial-item">
758 + <h3><?php echo esc_html__('Theme Customizer Add-on', 'mxchat'); ?></h3>
759 + <div class="video-description">
760 + <p><?php echo esc_html__('Learn how to customize your chatbot appearance with the Theme Customizer add-on. Easily modify colors, fonts, and styles with real-time previews to match your brand perfectly.', 'mxchat'); ?></p>
761 + <a href="https://youtu.be/MfbB9mZi6ag" target="_blank" rel="noopener" class="video-link">
762 + <span class="video-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 19c-2.3 0-6.4-.2-8.1-.6-.7-.2-1.2-.7-1.4-1.4-.3-1.1-.5-3.4-.5-5s.2-3.9.5-5c.2-.7.7-1.2 1.4-1.4C5.6 5.2 9.7 5 12 5s6.4.2 8.1.6c.7.2 1.2.7 1.4 1.4.3 1.1.5 3.4.5 5s-.2 3.9-.5 5c-.2.7-.7 1.2-1.4 1.4-1.7.4-5.8.6-8.1.6z"></path><polygon points="10 15 15 12 10 9 10 15"></polygon></svg></span>
763 + <?php echo esc_html__('Watch Theme Customizer Tutorial', 'mxchat'); ?>
764 + </a>
542 765 </div>
766 + </div>
543 767
544 - <div class="faq-item">
545 - <h3><?php echo esc_html__('How do intents work in MxChat?', 'mxchat'); ?></h3>
546 - <p>
547 - <?php echo esc_html__('Intents allow MxChat to recognize user requests and trigger specific actions, such as capturing emails or displaying product information. This helps provide a more interactive and responsive experience. For a detailed explanation of each intent, please refer to our', 'mxchat'); ?> <a href="https://mxchat.ai/documentation/#intents" target="_blank" rel="noopener noreferrer"><?php echo esc_html__('Intents Documentation', 'mxchat'); ?></a>.
548 - </p>
768 + <div class="tutorial-item">
769 + <h3><?php echo esc_html__('WooCommerce Integration', 'mxchat'); ?></h3>
770 + <div class="video-description">
771 + <p><?php echo esc_html__('See how to integrate MxChat with your WooCommerce store to provide product recommendations and shopping assistance to your customers.', 'mxchat'); ?></p>
772 + <a href="https://www.youtube.com/watch?v=WsqAppHRGdA" target="_blank" rel="noopener" class="video-link">
773 + <span class="video-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 19c-2.3 0-6.4-.2-8.1-.6-.7-.2-1.2-.7-1.4-1.4-.3-1.1-.5-3.4-.5-5s.2-3.9.5-5c.2-.7.7-1.2 1.4-1.4C5.6 5.2 9.7 5 12 5s6.4.2 8.1.6c.7.2 1.2.7 1.4 1.4.3 1.1.5 3.4.5 5s-.2 3.9-.5 5c-.2.7-.7 1.2-1.4 1.4-1.7.4-5.8.6-8.1.6z"></path><polygon points="10 15 15 12 10 9 10 15"></polygon></svg></span>
774 + <?php echo esc_html__('Watch WooCommerce Integration Tutorial', 'mxchat'); ?>
775 + </a>
549 776 </div>
777 + </div>
550 778
551 - <div class="faq-item">
552 - <h3><?php echo esc_html__('How does the Complianz integration work?', 'mxchat'); ?></h3>
553 - <p>
554 - <?php echo esc_html__('The Pro version offers direct integration with the Complianz GDPR plugin, making it easy to stay compliant with GDPR. If Complianz is enabled on your website, users must accept consent before the chatbot widget appears. The chatbot will only display once the user has accepted, ensuring compliance with data privacy regulations.', 'mxchat'); ?>
555 - </p>
779 + <div class="tutorial-item">
780 + <h3><?php echo esc_html__('Knowledge Base Setup', 'mxchat'); ?></h3>
781 + <div class="video-description">
782 + <p><?php echo esc_html__('Learn how to set up your knowledge base using PDFs, sitemaps, and manual entries to enhance your chatbot\'s responses with site-specific information.', 'mxchat'); ?></p>
783 + <p><small><?php echo esc_html__('Note: This tutorial uses an older UI, but the process remains the same.', 'mxchat'); ?></small></p>
784 + <a href="https://www.youtube.com/watch?v=8Ztjs66-VTo" target="_blank" rel="noopener" class="video-link">
785 + <span class="video-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 19c-2.3 0-6.4-.2-8.1-.6-.7-.2-1.2-.7-1.4-1.4-.3-1.1-.5-3.4-.5-5s.2-3.9.5-5c.2-.7.7-1.2 1.4-1.4C5.6 5.2 9.7 5 12 5s6.4.2 8.1.6c.7.2 1.2.7 1.4 1.4.3 1.1.5 3.4.5 5s-.2 3.9-.5 5c-.2.7-.7 1.2-1.4 1.4-1.7.4-5.8.6-8.1.6z"></path><polygon points="10 15 15 12 10 9 10 15"></polygon></svg></span>
786 + <?php echo esc_html__('Watch Knowledge Base Setup Tutorial', 'mxchat'); ?>
787 + </a>
556 788 </div>
789 + </div>
557 790
558 - <div class="faq-item">
559 - <h3><?php echo esc_html__('How does the WooCommerce integration work?', 'mxchat'); ?></h3>
560 - <p>
561 - <?php echo esc_html__('With WooCommerce integration, the chatbot automatically embeds new products and updates existing ones. For already-published products, you can click update or submit the product sitemap. The “Order History Access” setting allows the bot to access users\' order history (requires login) and assist with order inquiries. To enable the “Add to Cart” feature, add this system instruction: “After discussing a product, ask if the user wants to add it to their cart. The user must say \'Add to cart\' exactly.” Currently, this feature supports English only, with more languages coming soon.', 'mxchat'); ?>
562 - </p>
791 + <div class="tutorial-item">
792 + <h3><?php echo esc_html__('Toolbar Chat with Documents', 'mxchat'); ?></h3>
793 + <div class="video-description">
794 + <p><?php echo esc_html__('See how to use the MxChat toolbar to chat with PDF and Word documents for enhanced document analysis and information retrieval.', 'mxchat'); ?></p>
795 + <a href="https://www.youtube.com/watch?v=j_c45WWCTG0" target="_blank" rel="noopener" class="video-link">
796 + <span class="video-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 19c-2.3 0-6.4-.2-8.1-.6-.7-.2-1.2-.7-1.4-1.4-.3-1.1-.5-3.4-.5-5s.2-3.9.5-5c.2-.7.7-1.2 1.4-1.4C5.6 5.2 9.7 5 12 5s6.4.2 8.1.6c.7.2 1.2.7 1.4 1.4.3 1.1.5 3.4.5 5s-.2 3.9-.5 5c-.2.7-.7 1.2-1.4 1.4-1.7.4-5.8.6-8.1.6z"></path><polygon points="10 15 15 12 10 9 10 15"></polygon></svg></span>
797 + <?php echo esc_html__('Watch Document Chat Tutorial', 'mxchat'); ?>
798 + </a>
563 799 </div>
800 + </div>
564 801
565 - <div class="faq-item">
566 - <h3><?php echo esc_html__('Why isn\'t my chatbot responding as expected?', 'mxchat'); ?></h3>
567 - <p>
568 - <?php echo esc_html__('AI chatbots can sometimes behave in unexpected ways, especially if you\'re new to configuring AI for specific tasks. We\'re committed to helping you get the most out of your chatbot experience. While we’re working on comprehensive guides and video tutorials, our team is here to assist you directly. If your chatbot isn\'t delivering the responses you need, please don’t hesitate to', 'mxchat'); ?> <a href="https://mxchat.ai/contact/" target="_blank" rel="noopener noreferrer"><?php echo esc_html__('contact us', 'mxchat'); ?></a> <?php echo esc_html__('for personalized support.', 'mxchat'); ?>
569 - </p>
570 - <p>
571 - <?php echo esc_html__('We’re dedicated to your success and ready to guide you in aligning the AI\'s behavior to meet your goals.', 'mxchat'); ?>
572 - </p>
802 + <div class="tutorial-item">
803 + <h3><?php echo esc_html__('Perplexity Integration', 'mxchat'); ?></h3>
804 + <div class="video-description">
805 + <p><?php echo esc_html__('Learn how to integrate Perplexity with your chatbot for real-time web search capabilities. This tutorial covers intent recognition, the toolbar toggle button, and how to enable your chatbot to search the web and provide up-to-date information to your visitors.', 'mxchat'); ?></p>
806 + <a href="https://youtu.be/wpKkbt24-bo" target="_blank" rel="noopener" class="video-link">
807 + <span class="video-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 19c-2.3 0-6.4-.2-8.1-.6-.7-.2-1.2-.7-1.4-1.4-.3-1.1-.5-3.4-.5-5s.2-3.9.5-5c.2-.7.7-1.2 1.4-1.4C5.6 5.2 9.7 5 12 5s6.4.2 8.1.6c.7.2 1.2.7 1.4 1.4.3 1.1.5 3.4.5 5s-.2 3.9-.5 5c-.2.7-.7 1.2-1.4 1.4-1.7.4-5.8.6-8.1.6z"></path><polygon points="10 15 15 12 10 9 10 15"></polygon></svg></span>
808 + <?php echo esc_html__('Watch Perplexity Integration Tutorial', 'mxchat'); ?>
809 + </a>
573 810 </div>
811 + </div>
574 812
575 - <div class="faq-item">
576 - <h3><?php echo esc_html__('What is Loops, and how do I get an API key?', 'mxchat'); ?></h3>
577 - <p>
578 - <?php echo esc_html__('Loops is a powerful SaaS email service that helps you automate and enhance your email marketing campaigns, making it easy to reach and engage with your audience. To integrate Loops with MxChat, you’ll need an API key from Loops. You can obtain this key by logging into your Loops account and navigating to the API settings. Visit the', 'mxchat'); ?> <a href="https://loops.so" target="_blank" rel="noopener noreferrer"><?php echo esc_html__('Loops website', 'mxchat'); ?></a> <?php echo esc_html__('to get started or to sign up for an account.', 'mxchat'); ?>
579 - </p>
813 + <div class="tutorial-item">
814 + <h3><?php echo esc_html__('Brave Search Intent', 'mxchat'); ?></h3>
815 + <div class="video-description">
816 + <p><?php echo esc_html__('Learn how to leverage Brave Search intent capabilities to improve your chatbot\'s understanding of user queries and provide more accurate responses.', 'mxchat'); ?></p>
817 + <a href="https://www.youtube.com/watch?v=7vDL5H7vToc" target="_blank" rel="noopener" class="video-link">
818 + <span class="video-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 19c-2.3 0-6.4-.2-8.1-.6-.7-.2-1.2-.7-1.4-1.4-.3-1.1-.5-3.4-.5-5s.2-3.9.5-5c.2-.7.7-1.2 1.4-1.4C5.6 5.2 9.7 5 12 5s6.4.2 8.1.6c.7.2 1.2.7 1.4 1.4.3 1.1.5 3.4.5 5s-.2 3.9-.5 5c-.2.7-.7 1.2-1.4 1.4-1.7.4-5.8.6-8.1.6z"></path><polygon points="10 15 15 12 10 9 10 15"></polygon></svg></span>
819 + <?php echo esc_html__('Watch Brave Search Intent Tutorial', 'mxchat'); ?>
820 + </a>
580 821 </div>
581 -
582 822 </div>
583 - </div>
584 - <?php
585 -}
586 823
587 -
588 -
589 -public function mxchat_create_transcripts_page() {
590 - global $wpdb;
591 - $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
592 -
593 - // Get basic stats
594 - $total_chats = $wpdb->get_var("SELECT COUNT(DISTINCT session_id) FROM $table_name");
595 - $total_messages = $wpdb->get_var("SELECT COUNT(*) FROM $table_name");
596 - $total_users = $wpdb->get_var("SELECT COUNT(DISTINCT user_email) FROM $table_name WHERE user_email != ''");
597 - ?>
598 - <div class="wrap mxchat-admin">
599 - <!-- Stats Cards -->
600 - <div class="mxchat-stats-grid">
601 - <div class="mxchat-stat-card">
602 - <div class="stat-icon">💬</div>
603 - <div class="stat-content">
604 - <span class="stat-value"><?php echo esc_html($total_chats); ?></span>
605 - <span class="stat-label"><?php esc_html_e('Total Chats', 'mxchat'); ?></span>
606 - </div>
824 + <div class="tutorial-item">
825 + <h3><?php echo esc_html__('Loops Email Capture', 'mxchat'); ?></h3>
826 + <div class="video-description">
827 + <p><?php echo esc_html__('Discover how to set up email capture with MxChat using Loops to grow your mailing list while providing value through your chatbot.', 'mxchat'); ?></p>
828 + <p><small><?php echo esc_html__('Note: This tutorial uses an older UI, but the process remains the same.', 'mxchat'); ?></small></p>
829 + <a href="https://www.youtube.com/watch?v=CNgm5TYDyTc" target="_blank" rel="noopener" class="video-link">
830 + <span class="video-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 19c-2.3 0-6.4-.2-8.1-.6-.7-.2-1.2-.7-1.4-1.4-.3-1.1-.5-3.4-.5-5s.2-3.9.5-5c.2-.7.7-1.2 1.4-1.4C5.6 5.2 9.7 5 12 5s6.4.2 8.1.6c.7.2 1.2.7 1.4 1.4.3 1.1.5 3.4.5 5s-.2 3.9-.5 5c-.2.7-.7 1.2-1.4 1.4-1.7.4-5.8.6-8.1.6z"></path><polygon points="10 15 15 12 10 9 10 15"></polygon></svg></span>
831 + <?php echo esc_html__('Watch Loops Email Capture Tutorial', 'mxchat'); ?>
832 + </a>
607 833 </div>
608 - <div class="mxchat-stat-card">
609 - <div class="stat-icon">📝</div>
610 - <div class="stat-content">
611 - <span class="stat-value"><?php echo esc_html($total_messages); ?></span>
612 - <span class="stat-label"><?php esc_html_e('Total Messages', 'mxchat'); ?></span>
613 - </div>
614 - </div>
615 - <div class="mxchat-stat-card">
616 - <div class="stat-icon">👥</div>
617 - <div class="stat-content">
618 - <span class="stat-value"><?php echo esc_html($total_users); ?></span>
619 - <span class="stat-label"><?php esc_html_e('Unique Users', 'mxchat'); ?></span>
620 - </div>
621 - </div>
622 834 </div>
623 835
624 - <!-- Search and Filter Controls -->
625 - <div class="mxchat-controls-wrapper">
626 - <div class="mxchat-search-box">
627 - <input type="text" id="mxchat-search-transcripts"
628 - placeholder="<?php esc_attr_e('Search transcripts...', 'mxchat'); ?>"
629 - class="regular-text">
836 + <div class="tutorial-item">
837 + <h3><?php echo esc_html__('MxChat AI Agent Testing Service', 'mxchat'); ?></h3>
838 + <div class="video-description">
839 + <p><?php echo esc_html__('Learn how to use the MxChat AI Agent Testing Service to evaluate and improve your chatbot\'s performance and accuracy.', 'mxchat'); ?></p>
840 + <p><small><?php echo esc_html__('Note: This tutorial uses an older UI, but the process remains the same.', 'mxchat'); ?></small></p>
841 + <a href="https://www.youtube.com/watch?v=A0jowbpyX54" target="_blank" rel="noopener" class="video-link">
842 + <span class="video-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 19c-2.3 0-6.4-.2-8.1-.6-.7-.2-1.2-.7-1.4-1.4-.3-1.1-.5-3.4-.5-5s.2-3.9.5-5c.2-.7.7-1.2 1.4-1.4C5.6 5.2 9.7 5 12 5s6.4.2 8.1.6c.7.2 1.2.7 1.4 1.4.3 1.1.5 3.4.5 5s-.2 3.9-.5 5c-.2.7-.7 1.2-1.4 1.4-1.7.4-5.8.6-8.1.6z"></path><polygon points="10 15 15 12 10 9 10 15"></polygon></svg></span>
843 + <?php echo esc_html__('Watch AI Agent Testing Tutorial', 'mxchat'); ?>
844 + </a>
630 845 </div>
846 + </div>
847 + </div>
631 848
632 - <form id="mxchat-delete-form" method="post">
633 - <?php wp_nonce_field('mxchat_delete_chat_history', 'mxchat_delete_chat_nonce'); ?>
634 - <div class="mxchat-controls">
635 - <button type="button" id="mxchat-select-all-transcripts" class="mxchat-select-button">
636 - <span class="dashicons dashicons-yes-alt"></span>
637 - <span class="button-text"><?php esc_html_e('Select All', 'mxchat'); ?></span>
638 - </button>
639 - <button type="submit" class="button delete-chats-button">
640 - <span class="dashicons dashicons-trash"></span>
641 - <?php esc_html_e('Delete Selected', 'mxchat'); ?>
642 - </button>
849 + </div>
643 850 </div>
644 - </form>
851 + </div>
645 852 </div>
646 -
647 - <!-- Transcripts Container -->
648 - <div id="mxchat-transcripts"></div>
649 853 </div>
650 854 <?php
651 855 }
652 856
653 -public function mxchat_create_prompts_page() {
654 - global $wpdb;
655 - $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
857 +public function dismiss_live_agent_notice() {
858 + // Add debugging
859 + //error_log('dismiss_live_agent_notice called');
860 + //error_log('POST data: ' . print_r($_POST, true));
656 861
657 - // Display success message if all prompts were deleted
658 - if (isset($_GET['all_deleted']) && $_GET['all_deleted'] === 'true') {
659 - echo '<div class="notice notice-success is-dismissible"><p>' . esc_html__('All knowledge has been deleted successfully.', 'mxchat') . '</p></div>';
862 + // Verify nonce
863 + if (!wp_verify_nonce($_POST['nonce'], 'dismiss_live_agent_notice')) {
864 + //error_log('Nonce verification failed');
865 + wp_die('Security check failed');
660 866 }
661 867
662 - // Set up pagination and search query
663 - $nonce = isset($_GET['_wpnonce']) ? sanitize_text_field($_GET['_wpnonce']) : '';
664 - $search_query = (!empty($nonce) && wp_verify_nonce($nonce, 'mxchat_prompts_search_nonce') && isset($_GET['search'])) ? sanitize_text_field($_GET['search']) : '';
665 - $current_page = isset($_GET['paged']) ? absint($_GET['paged']) : 1;
666 - $per_page = 10;
667 - $offset = ($current_page - 1) * $per_page;
868 + // Remove the notice flag
869 + $deleted = delete_option('mxchat_show_live_agent_disabled_notice');
870 + //error_log('Option deleted: ' . ($deleted ? 'yes' : 'no'));
668 871
669 - // Modify query to handle search input
670 - $sql_search = "";
671 - if ($search_query) {
672 - $sql_search = $wpdb->prepare("WHERE article_content LIKE %s", '%' . $wpdb->esc_like($search_query) . '%');
872 + wp_send_json_success();
873 +}
874 +public function add_live_agent_nonce() {
875 + if (get_option('mxchat_show_live_agent_disabled_notice', false)) {
876 + // Make sure your admin script is enqueued and localize the data
877 + wp_localize_script('mxchat-admin-js', 'mxchatLiveAgent', array(
878 + 'nonce' => wp_create_nonce('dismiss_live_agent_notice'),
879 + 'ajaxurl' => admin_url('admin-ajax.php')
880 + ));
673 881 }
882 +}
674 883
675 - // Retrieve total number of prompts, considering search filter
676 - $total_prompts = $wpdb->get_var("SELECT COUNT(*) FROM {$table_name} {$sql_search}");
677 - $total_pages = ceil($total_prompts / $per_page);
678 884
679 - // Retrieve prompts from the database
680 - $prompts = $wpdb->get_results(
681 - $wpdb->prepare(
682 - "SELECT * FROM {$table_name} {$sql_search} ORDER BY timestamp DESC LIMIT %d OFFSET %d",
683 - $per_page,
684 - $offset
885 +public function mxchat_create_transcripts_page() {
886 + global $wpdb;
887 + $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
888 +
889 + // Get basic stats
890 + $total_chats = $wpdb->get_var("SELECT COUNT(DISTINCT session_id) FROM $table_name");
891 + $total_messages = $wpdb->get_var("SELECT COUNT(*) FROM $table_name");
892 +
893 + // Count unique users with detailed breakdown
894 + $total_users = $wpdb->get_var("
895 + SELECT COUNT(DISTINCT
896 + CASE
897 + WHEN user_email != '' AND user_email IS NOT NULL THEN user_email
898 + WHEN user_id != 0 THEN CONCAT('user_', user_id)
899 + WHEN user_identifier NOT LIKE 'Tech-Savvy User'
900 + AND user_identifier NOT LIKE 'Detail-Oriented User'
901 + AND user_identifier NOT LIKE 'Language Learner'
902 + AND user_identifier NOT LIKE 'Casual Browser'
903 + AND user_identifier NOT LIKE 'Policy Enforcer'
904 + AND user_identifier NOT LIKE 'Researcher'
905 + AND user_identifier NOT LIKE 'Loyalty Member'
906 + AND user_identifier NOT LIKE 'Gift Buyer'
907 + AND user_identifier NOT LIKE 'Parent or Caregiver'
908 + THEN user_identifier
909 + ELSE session_id
910 + END
685 911 )
686 - );
912 + FROM $table_name
913 + WHERE role != 'assistant'
914 + ");
687 915
688 - // Add the pagination links generation here
689 - $page_links = paginate_links(array(
690 - 'base' => add_query_arg(array(
691 - 'paged' => '%#%',
692 - 'search' => urlencode($search_query),
693 - '_wpnonce' => wp_create_nonce('mxchat_prompts_search_nonce')
694 - ), admin_url('admin.php?page=mxchat-prompts')),
695 - 'format' => '',
696 - 'prev_text' => __('&laquo; Previous', 'mxchat'),
697 - 'next_text' => __('Next &raquo;', 'mxchat'),
698 - 'total' => $total_pages,
699 - 'current' => $current_page,
700 - ));
916 + // Get user type breakdown
917 + $registered_users = $wpdb->get_var("
918 + SELECT COUNT(DISTINCT user_email)
919 + FROM $table_name
920 + WHERE user_email != '' AND user_email IS NOT NULL
921 + ");
701 922
702 - // Retrieve processing statuses
703 - $pdf_url = get_transient('mxchat_last_pdf_url');
704 - $sitemap_url = get_transient('mxchat_last_sitemap_url');
705 - $pdf_status = $pdf_url ? $this->get_pdf_processing_status($pdf_url) : false;
706 - $sitemap_status = $sitemap_url ? $this->get_sitemap_processing_status($sitemap_url) : false;
923 + $guest_users = $wpdb->get_var("
924 + SELECT COUNT(DISTINCT user_identifier)
925 + FROM $table_name
926 + WHERE (user_email = '' OR user_email IS NULL)
927 + AND role != 'assistant'
928 + AND user_identifier NOT LIKE 'Tech-Savvy User'
929 + AND user_identifier NOT LIKE 'Detail-Oriented User'
930 + AND user_identifier NOT LIKE 'Language Learner'
931 + AND user_identifier NOT LIKE 'Casual Browser'
932 + AND user_identifier NOT LIKE 'Policy Enforcer'
933 + AND user_identifier NOT LIKE 'Researcher'
934 + AND user_identifier NOT LIKE 'Loyalty Member'
935 + AND user_identifier NOT LIKE 'Gift Buyer'
936 + AND user_identifier NOT LIKE 'Parent or Caregiver'
937 + ");
707 938
708 - if ($pdf_status && $pdf_status['status'] === 'complete') {
709 - delete_transient('mxchat_last_pdf_url');
710 - $pdf_status = false;
939 + // Get agent test messages count
940 + $agent_tests = $wpdb->get_var("
941 + SELECT COUNT(DISTINCT session_id)
942 + FROM $table_name
943 + WHERE user_identifier IN (
944 + 'Tech-Savvy User',
945 + 'Detail-Oriented User',
946 + 'Language Learner',
947 + 'Casual Browser',
948 + 'Policy Enforcer',
949 + 'Researcher',
950 + 'Loyalty Member',
951 + 'Gift Buyer',
952 + 'Parent or Caregiver'
953 + )
954 + ");
955 + // Get activity metrics
956 + $today_chats = $wpdb->get_var("
957 + SELECT COUNT(DISTINCT session_id)
958 + FROM $table_name
959 + WHERE DATE(timestamp) = CURDATE()
960 + ");
961 +
962 + $week_chats = $wpdb->get_var("
963 + SELECT COUNT(DISTINCT session_id)
964 + FROM $table_name
965 + WHERE timestamp >= DATE_SUB(NOW(), INTERVAL 7 DAY)
966 + ");
967 +
968 + $month_chats = $wpdb->get_var("
969 + SELECT COUNT(DISTINCT session_id)
970 + FROM $table_name
971 + WHERE timestamp >= DATE_SUB(NOW(), INTERVAL 30 DAY)
972 + ");
973 +
974 + // Get daily chat data for last 7 days
975 + $daily_stats = $wpdb->get_results("
976 + SELECT
977 + DATE(timestamp) as date,
978 + COUNT(DISTINCT session_id) as chats,
979 + COUNT(*) as messages
980 + FROM $table_name
981 + WHERE timestamp >= DATE_SUB(NOW(), INTERVAL 7 DAY)
982 + GROUP BY DATE(timestamp)
983 + ORDER BY date ASC
984 + ");
985 +
986 + // Get average messages per chat
987 + $avg_messages = $wpdb->get_var("
988 + SELECT AVG(message_count)
989 + FROM (
990 + SELECT session_id, COUNT(*) as message_count
991 + FROM $table_name
992 + GROUP BY session_id
993 + ) as chat_counts
994 + ");
995 + $avg_messages = $avg_messages ? round($avg_messages, 1) : 0;
996 +
997 + // Get busiest hour
998 + $busiest_hour = $wpdb->get_row("
999 + SELECT HOUR(timestamp) as hour, COUNT(DISTINCT session_id) as chat_count
1000 + FROM $table_name
1001 + GROUP BY HOUR(timestamp)
1002 + ORDER BY chat_count DESC
1003 + LIMIT 1
1004 + ");
1005 +
1006 + // Prepare chart data
1007 + $chart_labels = array();
1008 + $chart_chats = array();
1009 + $chart_messages = array();
1010 +
1011 + // Fill last 7 days with data
1012 + for ($i = 6; $i >= 0; $i--) {
1013 + $date = date('Y-m-d', strtotime("-$i days"));
1014 + $day_name = date('D', strtotime("-$i days"));
1015 + $chart_labels[] = $day_name;
1016 +
1017 + $found = false;
1018 + foreach ($daily_stats as $stat) {
1019 + if ($stat->date === $date) {
1020 + $chart_chats[] = (int)$stat->chats;
1021 + $chart_messages[] = (int)$stat->messages;
1022 + $found = true;
1023 + break;
1024 + }
1025 + }
1026 + if (!$found) {
1027 + $chart_chats[] = 0;
1028 + $chart_messages[] = 0;
1029 + }
711 1030 }
712 - if ($sitemap_status && $sitemap_status['status'] === 'complete') {
713 - delete_transient('mxchat_last_sitemap_url');
714 - $sitemap_status = false;
715 - }
716 -
717 - $is_processing = ($pdf_status && ($pdf_status['status'] === 'processing' || $pdf_status['status'] === 'error'))
718 - || ($sitemap_status && ($sitemap_status['status'] === 'processing' || $sitemap_status['status'] === 'error'));
719 1031 ?>
720 - <div class="wrap mxchat-wrapper">
1032 + <div class="wrap mxchat-transcripts-wrapper">
721 1033 <!-- Hero Section -->
722 - <div class="mxchat-hero">
1034 + <div class="mxchat-transcripts-hero">
723 1035 <h1 class="mxchat-main-title">
724 - <span class="mxchat-gradient-text">Knowledge Base</span> Manager
1036 + Chat <span class="mxchat-gradient-text">Transcripts</span>
725 1037 </h1>
726 1038 <p class="mxchat-hero-subtitle">
727 - <?php esc_html_e('Enhance your AI chatbot with custom knowledge. Import, manage, and organize your content to keep responses accurate and relevant.', 'mxchat'); ?>
1039 + <?php esc_html_e('Review and manage your chatbot conversations with detailed message history.', 'mxchat'); ?>
728 1040 </p>
729 1041 </div>
730 -
731 1042 <div class="mxchat-content">
732 - <!-- Import Settings Card -->
733 - <div class="mxchat-card">
734 - <h2><?php esc_html_e('Knowledge Import Settings', 'mxchat'); ?></h2>
735 - <p><?php esc_html_e('Using the free Pinecone DB Manager add-on is recommended for high performance and a knowledge database of more than 500.', 'mxchat'); ?></p>
736 -
737 - <!-- Tab Contents -->
738 - <div class="mxchat-tab-contents">
739 - <!-- Default Database Tab -->
740 - <div id="default-db" class="mxchat-tab-content active">
741 - <!-- Auto-Sync Settings -->
742 - <div class="mxchat-settings-section">
743 - <h3><?php esc_html_e('Auto-Sync Settings', 'mxchat'); ?></h3>
744 - <p class="mxchat-description">
745 - <?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'); ?>
746 - </p>
747 - <div class="mxchat-autosave-section"> <!-- Add this wrapper -->
748 - <div class="mxchat-toggle-group">
749 - <div class="mxchat-toggle-container">
750 - <label class="mxchat-toggle-switch">
751 - <input type="checkbox"
752 - name="mxchat_auto_sync_posts"
753 - class="mxchat-autosave-field"
754 - value="1"
755 - data-nonce="<?php echo wp_create_nonce('mxchat_prompts_setting_nonce'); ?>"
756 - <?php checked(get_option('mxchat_auto_sync_posts', '0'), '1'); ?>>
757 - <span class="mxchat-toggle-slider"></span>
758 - </label>
759 - <span class="mxchat-toggle-label">
760 - <?php esc_html_e('Auto-sync Posts', 'mxchat'); ?>
1043 + <!-- Enhanced Metrics Dashboard -->
1044 + <div class="mxchat-metrics-dashboard">
1045 + <!-- Tab Navigation -->
1046 + <div class="mxchat-metrics-tabs">
1047 + <button class="mxchat-metrics-tab active" data-tab="overview">
1048 + <span class="tab-icon">📊</span>
1049 + <span class="tab-label">Overview</span>
1050 + </button>
1051 + <button class="mxchat-metrics-tab" data-tab="activity">
1052 + <span class="tab-icon">📈</span>
1053 + <span class="tab-label">Activity</span>
1054 + </button>
1055 + <button class="mxchat-metrics-tab" data-tab="insights">
1056 + <span class="tab-icon">💡</span>
1057 + <span class="tab-label">Insights</span>
1058 + </button>
1059 + </div>
1060 +
1061 + <!-- Tab Content -->
1062 + <div class="mxchat-metrics-content">
1063 + <!-- Overview Tab -->
1064 + <div class="mxchat-metrics-panel active" data-panel="overview">
1065 + <div class="mxchat-stats-grid">
1066 + <div class="mxchat-stat-card mxchat-stat-primary">
1067 + <div class="stat-icon">💬</div>
1068 + <div class="stat-content">
1069 + <span class="stat-value"><?php echo esc_html($total_chats); ?></span>
1070 + <span class="stat-label"><?php esc_html_e('Total Chats', 'mxchat'); ?></span>
1071 + </div>
1072 + </div>
1073 + <div class="mxchat-stat-card mxchat-stat-primary">
1074 + <div class="stat-icon">📝</div>
1075 + <div class="stat-content">
1076 + <span class="stat-value"><?php echo esc_html($total_messages); ?></span>
1077 + <span class="stat-label"><?php esc_html_e('Total Messages', 'mxchat'); ?></span>
1078 + </div>
1079 + </div>
1080 + <div class="mxchat-stat-card mxchat-stat-primary">
1081 + <div class="stat-icon">👥</div>
1082 + <div class="stat-content">
1083 + <span class="stat-value"><?php echo esc_html($total_users); ?></span>
1084 + <span class="stat-label"><?php esc_html_e('Unique Users', 'mxchat'); ?></span>
1085 + <span class="stat-sublabel">
1086 + <?php
1087 + echo sprintf(
1088 + esc_html__('%d registered • %d guests • %d tests', 'mxchat'),
1089 + $registered_users,
1090 + $guest_users,
1091 + $agent_tests
1092 + );
1093 + ?>
761 1094 </span>
762 1095 </div>
763 - <div class="mxchat-toggle-container">
764 - <label class="mxchat-toggle-switch">
765 - <input type="checkbox"
766 - name="mxchat_auto_sync_pages"
767 - class="mxchat-autosave-field"
768 - value="1"
769 - data-nonce="<?php echo wp_create_nonce('mxchat_prompts_setting_nonce'); ?>"
770 - <?php checked(get_option('mxchat_auto_sync_pages', '0'), '1'); ?>>
771 - <span class="mxchat-toggle-slider"></span>
772 - </label>
773 - <span class="mxchat-toggle-label">
774 - <?php esc_html_e('Auto-sync Pages', 'mxchat'); ?>
775 - </span>
1096 + </div>
1097 + <div class="mxchat-stat-card mxchat-stat-primary">
1098 + <div class="stat-icon">💭</div>
1099 + <div class="stat-content">
1100 + <span class="stat-value"><?php echo esc_html($avg_messages); ?></span>
1101 + <span class="stat-label"><?php esc_html_e('Avg Messages/Chat', 'mxchat'); ?></span>
776 1102 </div>
777 1103 </div>
778 1104 </div>
779 1105 </div>
780 -
781 - <!-- Import Methods -->
782 - <div class="mxchat-import-methods">
783 - <div class="mxchat-method-card">
784 - <h4><?php esc_html_e('Sitemap Import', 'mxchat'); ?></h4>
785 - <p><?php esc_html_e('Import all content from your sitemap automatically.', 'mxchat'); ?></p>
1106 +
1107 + <!-- Activity Tab -->
1108 + <div class="mxchat-metrics-panel" data-panel="activity">
1109 + <div class="mxchat-activity-layout">
1110 + <div class="mxchat-activity-stats">
1111 + <div class="mxchat-stat-card mxchat-stat-accent">
1112 + <div class="stat-icon">📅</div>
1113 + <div class="stat-content">
1114 + <span class="stat-value"><?php echo esc_html($today_chats); ?></span>
1115 + <span class="stat-label"><?php esc_html_e('Today', 'mxchat'); ?></span>
1116 + </div>
1117 + </div>
1118 + <div class="mxchat-stat-card mxchat-stat-accent">
1119 + <div class="stat-icon">📆</div>
1120 + <div class="stat-content">
1121 + <span class="stat-value"><?php echo esc_html($week_chats); ?></span>
1122 + <span class="stat-label"><?php esc_html_e('Last 7 Days', 'mxchat'); ?></span>
1123 + </div>
1124 + </div>
1125 + <div class="mxchat-stat-card mxchat-stat-accent">
1126 + <div class="stat-icon">🗓️</div>
1127 + <div class="stat-content">
1128 + <span class="stat-value"><?php echo esc_html($month_chats); ?></span>
1129 + <span class="stat-label"><?php esc_html_e('Last 30 Days', 'mxchat'); ?></span>
1130 + </div>
1131 + </div>
786 1132 </div>
787 - <div class="mxchat-method-card">
788 - <h4><?php esc_html_e('PDF Import', 'mxchat'); ?></h4>
789 - <p><?php esc_html_e('Import knowledge directly from PDF documents.', 'mxchat'); ?></p>
1133 +
1134 + <div class="mxchat-chart-container">
1135 + <h3 class="mxchat-chart-title"><?php esc_html_e('7-Day Activity Trend', 'mxchat'); ?></h3>
1136 + <canvas id="mxchat-activity-chart"></canvas>
790 1137 </div>
791 - <div class="mxchat-method-card">
792 - <h4><?php esc_html_e('Direct URL', 'mxchat'); ?></h4>
793 - <p><?php esc_html_e('Import content from any specific webpage.', 'mxchat'); ?></p>
794 - </div>
795 - <div class="mxchat-method-card">
796 - <h4><?php esc_html_e('Direct Content', 'mxchat'); ?></h4>
797 - <p><?php esc_html_e('Directly submit content to be vectorized.', 'mxchat'); ?></p>
798 - </div>
799 1138 </div>
800 -
801 - <!-- Import Form -->
802 - <?php if (!$is_processing) : ?>
803 - <div class="mxchat-import-form">
804 - <form id="mxchat-sitemap-form" method="post"
805 - action="<?php echo esc_url(admin_url('admin-post.php?action=mxchat_submit_sitemap')); ?>">
806 - <?php wp_nonce_field('mxchat_submit_sitemap_action', 'mxchat_submit_sitemap_nonce'); ?>
807 - <div class="mxchat-url-input-group">
808 - <input type="url"
809 - name="sitemap_url"
810 - id="sitemap_url"
811 - placeholder="<?php esc_attr_e('Enter Sitemap, PDF, or Webpage URL', 'mxchat'); ?>"
812 - required />
813 - <button type="submit"
814 - name="submit_sitemap"
815 - class="mxchat-button-primary">
816 - <?php esc_html_e('Import', 'mxchat'); ?>
817 - </button>
1139 + </div>
1140 +
1141 + <!-- Insights Tab -->
1142 + <div class="mxchat-metrics-panel" data-panel="insights">
1143 + <div class="mxchat-insights-grid">
1144 + <div class="mxchat-insight-card">
1145 + <div class="insight-header">
1146 + <span class="insight-icon">🕐</span>
1147 + <h3><?php esc_html_e('Peak Activity', 'mxchat'); ?></h3>
1148 + </div>
1149 + <div class="insight-content">
1150 + <div class="insight-value">
1151 + <?php
1152 + if ($busiest_hour) {
1153 + $hour = $busiest_hour->hour;
1154 + $formatted_hour = date('g A', strtotime("$hour:00"));
1155 + echo esc_html($formatted_hour);
1156 + } else {
1157 + echo esc_html__('N/A', 'mxchat');
1158 + }
1159 + ?>
818 1160 </div>
819 - </form>
1161 + <div class="insight-description">
1162 + <?php esc_html_e('Most active hour of the day', 'mxchat'); ?>
1163 + </div>
1164 + </div>
820 1165 </div>
821 - <?php endif; ?>
822 -
823 - <!-- Processing Status -->
824 - <?php if ($pdf_status && $pdf_status['status'] !== 'complete') : ?>
825 - <div class="mxchat-status-card">
826 - <div class="mxchat-status-header">
827 - <h4><?php esc_html_e('PDF Processing Status', 'mxchat'); ?></h4>
828 - <?php if ($is_processing) : ?>
829 - <form method="post" class="mxchat-stop-form"
830 - action="<?php echo esc_url(admin_url('admin-post.php?action=mxchat_stop_processing')); ?>">
831 - <?php wp_nonce_field('mxchat_stop_processing_action', 'mxchat_stop_processing_nonce'); ?>
832 - <button type="submit" name="stop_processing" class="mxchat-button-secondary">
833 - <?php esc_html_e('Stop Processing', 'mxchat'); ?>
834 - </button>
835 - </form>
836 - <?php endif; ?>
1166 +
1167 + <div class="mxchat-insight-card">
1168 + <div class="insight-header">
1169 + <span class="insight-icon">📊</span>
1170 + <h3><?php esc_html_e('Engagement Rate', 'mxchat'); ?></h3>
837 1171 </div>
838 - <div class="mxchat-progress-bar">
839 - <div class="mxchat-progress-fill" style="width: <?php echo esc_attr($pdf_status['percentage']); ?>%"></div>
1172 + <div class="insight-content">
1173 + <div class="insight-value">
1174 + <?php
1175 + $engagement_rate = $total_chats > 0 ? round(($total_messages / $total_chats), 1) : 0;
1176 + echo esc_html($engagement_rate);
1177 + ?>
1178 + </div>
1179 + <div class="insight-description">
1180 + <?php esc_html_e('Messages exchanged per chat', 'mxchat'); ?>
1181 + </div>
840 1182 </div>
841 - <div class="mxchat-status-details">
842 - <p><?php printf(
843 - esc_html__('Progress: %1$d of %2$d pages (%3$d%%)', 'mxchat'),
844 - absint($pdf_status['processed_pages']),
845 - absint($pdf_status['total_pages']),
846 - absint($pdf_status['percentage'])
847 - ); ?></p>
848 - <p><?php printf(
849 - esc_html__('Status: %s', 'mxchat'),
850 - esc_html(ucfirst($pdf_status['status']))
851 - ); ?></p>
852 - <p><?php printf(
853 - esc_html__('Last update: %s', 'mxchat'),
854 - esc_html($pdf_status['last_update'])
855 - ); ?></p>
1183 + </div>
1184 +
1185 + <div class="mxchat-insight-card">
1186 + <div class="insight-header">
1187 + <span class="insight-icon">👤</span>
1188 + <h3><?php esc_html_e('User Distribution', 'mxchat'); ?></h3>
856 1189 </div>
1190 + <div class="insight-content">
1191 + <div class="insight-breakdown">
1192 + <div class="breakdown-item">
1193 + <span class="breakdown-label"><?php esc_html_e('Registered', 'mxchat'); ?></span>
1194 + <span class="breakdown-value">
1195 + <?php
1196 + $registered_pct = $total_users > 0 ? round(($registered_users / $total_users) * 100) : 0;
1197 + echo esc_html($registered_pct . '%');
1198 + ?>
1199 + </span>
1200 + </div>
1201 + <div class="breakdown-item">
1202 + <span class="breakdown-label"><?php esc_html_e('Guests', 'mxchat'); ?></span>
1203 + <span class="breakdown-value">
1204 + <?php
1205 + $guest_pct = $total_users > 0 ? round(($guest_users / $total_users) * 100) : 0;
1206 + echo esc_html($guest_pct . '%');
1207 + ?>
1208 + </span>
1209 + </div>
1210 + </div>
1211 + </div>
857 1212 </div>
858 - <?php endif; ?>
859 -
860 - <?php if ($sitemap_status && $sitemap_status['status'] !== 'complete') : ?>
861 - <div class="mxchat-status-card">
862 - <div class="mxchat-status-header">
863 - <h4><?php esc_html_e('Sitemap Processing Status (Refresh for update)', 'mxchat'); ?></h4>
864 - <?php if ($is_processing) : ?>
865 - <form method="post" class="mxchat-stop-form"
866 - action="<?php echo esc_url(admin_url('admin-post.php?action=mxchat_stop_processing')); ?>">
867 - <?php wp_nonce_field('mxchat_stop_processing_action', 'mxchat_stop_processing_nonce'); ?>
868 - <button type="submit" name="stop_processing" class="mxchat-button-secondary">
869 - <?php esc_html_e('Stop Processing', 'mxchat'); ?>
870 - </button>
871 - </form>
872 - <?php endif; ?>
1213 +
1214 + <div class="mxchat-insight-card">
1215 + <div class="insight-header">
1216 + <span class="insight-icon">⚡</span>
1217 + <h3><?php esc_html_e('Activity Status', 'mxchat'); ?></h3>
873 1218 </div>
874 - <div class="mxchat-progress-bar">
875 - <div class="mxchat-progress-fill" style="width: <?php echo esc_attr($sitemap_status['percentage']); ?>%"></div>
1219 + <div class="insight-content">
1220 + <div class="insight-value insight-status">
1221 + <?php
1222 + if ($today_chats > 0) {
1223 + echo '<span class="status-badge status-active">' . esc_html__('Active', 'mxchat') . '</span>';
1224 + } else if ($week_chats > 0) {
1225 + echo '<span class="status-badge status-moderate">' . esc_html__('Moderate', 'mxchat') . '</span>';
1226 + } else {
1227 + echo '<span class="status-badge status-quiet">' . esc_html__('Quiet', 'mxchat') . '</span>';
1228 + }
1229 + ?>
1230 + </div>
1231 + <div class="insight-description">
1232 + <?php
1233 + if ($today_chats > 0) {
1234 + echo esc_html(sprintf(__('%d chats today', 'mxchat'), $today_chats));
1235 + } else if ($week_chats > 0) {
1236 + echo esc_html(sprintf(__('%d chats this week', 'mxchat'), $week_chats));
1237 + } else {
1238 + echo esc_html__('No recent activity', 'mxchat');
1239 + }
1240 + ?>
1241 + </div>
876 1242 </div>
877 - <div class="mxchat-status-details">
878 - <p><?php printf(
879 - esc_html__('Progress: %1$d of %2$d URLs (%3$d%%)', 'mxchat'),
880 - absint($sitemap_status['processed_urls']),
881 - absint($sitemap_status['total_urls']),
882 - absint($sitemap_status['percentage'])
883 - ); ?></p>
884 - <?php if (!empty($sitemap_status['error']) || !empty($sitemap_status['last_error'])) : ?>
885 - <div class="mxchat-error-notice">
886 - <?php if (!empty($sitemap_status['error'])) : ?>
887 - <p class="error"><?php echo esc_html($sitemap_status['error']); ?></p>
888 - <?php endif; ?>
889 - <?php if (!empty($sitemap_status['last_error'])) : ?>
890 - <p class="last-error"><?php echo esc_html__('Last error:', 'mxchat') . ' ' . esc_html($sitemap_status['last_error']); ?></p>
891 - <?php endif; ?>
892 - </div>
893 - <?php endif; ?>
894 - </div>
895 1243 </div>
896 - <?php endif; ?>
1244 + </div>
897 1245 </div>
1246 + </div>
1247 + </div>
1248 +
1249 + <script>
1250 + var mxchatChartData = {
1251 + labels: <?php echo json_encode($chart_labels); ?>,
1252 + chats: <?php echo json_encode($chart_chats); ?>,
1253 + messages: <?php echo json_encode($chart_messages); ?>
1254 + };
1255 + </script>
898 1256
899 -
1257 + <!-- Search and Filter Controls with Notification Settings Button -->
1258 + <div class="mxchat-controls-wrapper">
1259 + <div class="mxchat-search-box">
1260 + <input type="text" id="mxchat-search-transcripts"
1261 + placeholder="<?php esc_attr_e('Search transcripts...', 'mxchat'); ?>"
1262 + class="regular-text">
900 1263 </div>
1264 + <form id="mxchat-delete-form" method="post">
1265 + <?php wp_nonce_field('mxchat_delete_chat_history', 'mxchat_delete_chat_nonce'); ?>
1266 + <div class="mxchat-controls">
1267 + <button type="button" id="mxchat-chat-email-notification-btn" class="mxchat-action-button">
1268 + <span class="dashicons dashicons-admin-generic"></span>
1269 + <?php esc_html_e('Settings', 'mxchat'); ?>
1270 + </button>
1271 + <button type="button" id="mxchat-export-transcripts" class="mxchat-action-button">
1272 + <span class="dashicons dashicons-download"></span>
1273 + <?php esc_html_e('Export All Chats', 'mxchat'); ?>
1274 + </button>
1275 + <button type="button" id="mxchat-select-all-transcripts" class="mxchat-select-button">
1276 + <span class="dashicons dashicons-yes-alt"></span>
1277 + <span class="button-text"><?php esc_html_e('Select All', 'mxchat'); ?></span>
1278 + </button>
1279 + <button type="submit" class="button delete-chats-button">
1280 + <span class="dashicons dashicons-trash"></span>
1281 + <?php esc_html_e('Delete Selected', 'mxchat'); ?>
1282 + </button>
1283 + </div>
1284 + </form>
901 1285 </div>
902 1286
903 - <!-- Direct Content Submission Card -->
904 -<div class="mxchat-card">
905 - <div class="mxchat-card-header">
906 - <h2><?php esc_html_e('Submit Direct Content', 'mxchat'); ?></h2>
1287 + <!-- Transcripts Container -->
1288 + <div id="mxchat-transcripts"></div>
1289 + </div>
907 1290 </div>
908 - <form id="mxchat-content-form" method="post"
909 - action="<?php echo esc_url(admin_url('admin-post.php?action=mxchat_submit_content')); ?>">
910 - <?php wp_nonce_field('mxchat_submit_content_action', 'mxchat_submit_content_nonce'); ?>
911 - <div class="mxchat-form-group">
912 - <label for="article_content"><?php esc_html_e('Content:', 'mxchat'); ?></label>
913 - <textarea name="article_content" id="article_content" required
914 - placeholder="<?php esc_attr_e('Enter your content here...', 'mxchat'); ?>"></textarea>
1291 +
1292 + <!-- Modal for Chat Email Notification Settings -->
1293 + <div id="mxchat-chat-email-notification-modal" class="mxchat-chat-notification-modal-overlay" style="display: none;">
1294 + <div class="mxchat-chat-notification-modal-content">
1295 + <div class="mxchat-chat-notification-modal-header">
1296 + <h2><?php esc_html_e('Transcript Settings', 'mxchat'); ?></h2>
1297 + <button type="button" class="mxchat-chat-notification-modal-close">&times;</button>
1298 + </div>
1299 + <div class="mxchat-chat-notification-modal-body">
1300 + <form method="post" action="options.php" id="mxchat-chat-email-notification-form">
1301 + <?php
1302 + settings_fields('mxchat_transcripts_options');
1303 + do_settings_sections('mxchat-transcripts');
1304 + ?>
1305 + <div class="mxchat-chat-notification-modal-footer">
1306 + <button type="submit" class="button button-primary">
1307 + <?php esc_html_e('Save Settings', 'mxchat'); ?>
1308 + </button>
1309 + <button type="button" class="button mxchat-chat-notification-modal-cancel">
1310 + <?php esc_html_e('Cancel', 'mxchat'); ?>
1311 + </button>
1312 + </div>
1313 + </form>
1314 + </div>
915 1315 </div>
916 - <div class="mxchat-form-group">
917 - <label for="article_url"><?php esc_html_e('Source URL (Optional):', 'mxchat'); ?></label>
918 - <input type="url" name="article_url" id="article_url"
919 - placeholder="<?php esc_attr_e('Enter source URL', 'mxchat'); ?>">
920 - </div>
921 - <button type="submit" name="submit_content" class="mxchat-button-primary">
922 - <?php esc_html_e('Submit Content', 'mxchat'); ?>
923 - </button>
924 - </form>
925 -</div>
1316 + </div>
926 1317
927 -<!-- Knowledge Base Table Card -->
928 -<div class="mxchat-card">
929 - <div class="mxchat-card-header">
930 - <h2><?php esc_html_e('Knowledge Base', 'mxchat'); ?></h2>
931 - <div class="mxchat-header-actions">
932 - <!-- Search -->
933 - <form method="get" id="knowledge-search" class="mxchat-search-form">
934 - <?php wp_nonce_field('mxchat_prompts_search_nonce'); ?>
935 - <input type="hidden" name="page" value="mxchat-prompts" />
936 - <div class="mxchat-search-group">
937 - <span class="dashicons dashicons-search"></span>
938 - <input type="text"
939 - name="search"
940 - placeholder="<?php esc_attr_e('Search Knowledge', 'mxchat'); ?>"
941 - value="<?php echo esc_attr($search_query); ?>" />
1318 + <!-- Modal for RAG Context Display -->
1319 + <div id="mxchat-rag-context-modal" class="mxchat-rag-modal-overlay" style="display: none;">
1320 + <div class="mxchat-rag-modal-content">
1321 + <div class="mxchat-rag-modal-header">
1322 + <h2><?php esc_html_e('Retrieved Documents', 'mxchat'); ?></h2>
1323 + <button type="button" class="mxchat-rag-modal-close">&times;</button>
1324 + </div>
1325 + <div class="mxchat-rag-modal-body">
1326 + <div class="mxchat-rag-loading" style="display: none;">
1327 + <span class="spinner is-active"></span>
1328 + <?php esc_html_e('Loading document context...', 'mxchat'); ?>
942 1329 </div>
943 - </form>
944 -
945 - <!-- Delete All -->
946 - <form method="post"
947 - action="<?php echo esc_url(admin_url('admin-post.php?action=mxchat_delete_all_prompts')); ?>"
948 - class="mxchat-delete-form"
949 - onsubmit="return confirm('<?php esc_attr_e('Are you sure you want to delete all knowledge? This action cannot be undone.', 'mxchat'); ?>');">
950 - <?php wp_nonce_field('mxchat_delete_all_prompts_action', 'mxchat_delete_all_prompts_nonce'); ?>
951 - <button type="submit" name="delete_all_prompts" class="mxchat-button-danger">
952 - <span class="dashicons dashicons-trash"></span>
953 - <?php esc_html_e('Delete All', 'mxchat'); ?>
954 - </button>
955 - </form>
1330 + <div class="mxchat-rag-content">
1331 + <!-- RAG context will be populated via JavaScript -->
1332 + </div>
1333 + </div>
956 1334 </div>
957 1335 </div>
1336 + <?php
1337 +}
958 1338
959 - <!-- Table -->
960 - <div class="mxchat-table-wrapper">
961 - <table class="mxchat-records-table">
962 - <thead>
963 - <tr>
964 - <th><?php esc_html_e('ID', 'mxchat'); ?></th>
965 - <th><?php esc_html_e('Content', 'mxchat'); ?></th>
966 - <th><?php esc_html_e('Source', 'mxchat'); ?></th>
967 - <th><?php esc_html_e('Actions', 'mxchat'); ?></th>
968 - </tr>
969 - </thead>
970 - <tbody>
971 - <?php if ($prompts) : ?>
972 - <?php foreach ($prompts as $prompt) : ?>
973 - <tr id="prompt-<?php echo esc_attr($prompt->id); ?>">
974 - <td><?php echo esc_html($prompt->id); ?></td>
975 - <td class="mxchat-content-cell">
976 - <div class="content-view">
977 - <?php echo wp_kses_post(wpautop(esc_textarea($prompt->article_content))); ?>
978 - </div>
979 - <textarea class="content-edit" style="display:none;">
980 - <?php echo esc_textarea($prompt->article_content); ?>
981 - </textarea>
982 - </td>
983 - <td class="mxchat-url-cell">
984 - <?php if (!empty($prompt->source_url)) : ?>
985 - <a href="<?php echo esc_url($prompt->source_url); ?>" target="_blank">
986 - <span class="dashicons dashicons-external"></span>
987 - <?php esc_html_e('View Source', 'mxchat'); ?>
988 - </a>
989 - <?php else : ?>
990 - <span class="mxchat-na"><?php esc_html_e('N/A', 'mxchat'); ?></span>
991 - <?php endif; ?>
992 - </td>
993 - <td class="mxchat-actions-cell">
994 - <button class="mxchat-button-icon edit-button"
995 - data-id="<?php echo esc_attr($prompt->id); ?>">
996 - <span class="dashicons dashicons-edit"></span>
997 - </button>
998 - <button class="mxchat-button-icon save-button"
999 - data-id="<?php echo esc_attr($prompt->id); ?>"
1000 - style="display:none;"
1001 - data-nonce="<?php echo wp_create_nonce('mxchat_save_inline_nonce'); ?>">
1002 - <span class="dashicons dashicons-saved"></span>
1003 - </button>
1004 - <a href="<?php echo esc_url(admin_url(
1005 - 'admin-post.php?action=mxchat_delete_prompt&id=' . esc_attr($prompt->id)
1006 - . '&_wpnonce=' . wp_create_nonce('mxchat_delete_prompt_nonce')
1007 - )); ?>"
1008 - class="mxchat-button-icon delete-button"
1009 - onclick="return confirm('<?php esc_attr_e('Are you sure you want to delete this entry?', 'mxchat'); ?>');">
1010 - <span class="dashicons dashicons-trash"></span>
1011 - </a>
1012 - </td>
1013 - </tr>
1014 - <?php endforeach; ?>
1015 - <?php else : ?>
1016 - <tr>
1017 - <td colspan="4" class="mxchat-no-records">
1018 - <?php esc_html_e('No knowledge base entries found.', 'mxchat'); ?>
1019 - </td>
1020 - </tr>
1021 - <?php endif; ?>
1022 - </tbody>
1023 - </table>
1024 - </div>
1025 1339
1026 - <?php if ($page_links) : ?>
1027 - <div class="mxchat-pagination">
1028 - <?php echo wp_kses_post($page_links); ?>
1029 - </div>
1030 - <?php endif; ?>
1031 -</div>
1032 -</div>
1033 -</div>
1034 -<?php
1340 +public function mxchat_transcripts_notification_section_callback() {
1341 + echo '<p>' . esc_html__('Configure email notifications for new chat transcripts. You will receive an email notification when a new chat session begins.', 'mxchat') . '</p>';
1035 1342 }
1343 +public function mxchat_enable_notifications_callback() {
1344 + $options = get_option('mxchat_transcripts_options', array());
1345 + $enabled = isset($options['mxchat_enable_notifications']) ? $options['mxchat_enable_notifications'] : 0;
1346 + ?>
1347 + <label for="mxchat_enable_notifications">
1348 + <input type="checkbox" id="mxchat_enable_notifications"
1349 + name="mxchat_transcripts_options[mxchat_enable_notifications]"
1350 + value="1" <?php checked(1, $enabled); ?>>
1351 + <?php esc_html_e('Send email notification when a new chat session starts', 'mxchat'); ?>
1352 + </label>
1353 + <p class="description">
1354 + <?php esc_html_e('Enable this option to receive email notifications for new chat sessions.', 'mxchat'); ?>
1355 + </p>
1356 + <?php
1357 +}
1358 +public function mxchat_notification_email_callback() {
1359 + $options = get_option('mxchat_transcripts_options', array());
1360 + $email = isset($options['mxchat_notification_email']) ? $options['mxchat_notification_email'] : get_option('admin_email');
1361 + ?>
1362 + <input type="email" id="mxchat_notification_email"
1363 + name="mxchat_transcripts_options[mxchat_notification_email]"
1364 + value="<?php echo esc_attr($email); ?>"
1365 + class="regular-text">
1366 + <p class="description">
1367 + <?php esc_html_e('Enter the email address where notifications should be sent. Defaults to the admin email address.', 'mxchat'); ?>
1368 + </p>
1369 + <?php
1370 +}
1036 1371
1372 +public function mxchat_auto_delete_transcripts_callback() {
1373 + $options = get_option('mxchat_transcripts_options', array());
1374 + $interval = isset($options['mxchat_auto_delete_transcripts']) ? $options['mxchat_auto_delete_transcripts'] : 'never';
1375 + ?>
1376 + <select id="mxchat_auto_delete_transcripts"
1377 + name="mxchat_transcripts_options[mxchat_auto_delete_transcripts]">
1378 + <option value="never" <?php selected($interval, 'never'); ?>>
1379 + <?php esc_html_e('Never (Keep All Transcripts)', 'mxchat'); ?>
1380 + </option>
1381 + <option value="1week" <?php selected($interval, '1week'); ?>>
1382 + <?php esc_html_e('After 1 Week', 'mxchat'); ?>
1383 + </option>
1384 + <option value="2weeks" <?php selected($interval, '2weeks'); ?>>
1385 + <?php esc_html_e('After 2 Weeks', 'mxchat'); ?>
1386 + </option>
1387 + <option value="1month" <?php selected($interval, '1month'); ?>>
1388 + <?php esc_html_e('After 1 Month', 'mxchat'); ?>
1389 + </option>
1390 + </select>
1391 + <p class="description">
1392 + <?php esc_html_e('Automatically delete old chat transcripts after the selected time period. This helps manage database size and privacy.', 'mxchat'); ?>
1393 + </p>
1394 + <?php
1395 +}
1037 1396
1038 -public function mxchat_handle_delete_all_prompts() {
1039 - // Verify nonce
1040 - if (!isset($_POST['mxchat_delete_all_prompts_nonce']) || !wp_verify_nonce($_POST['mxchat_delete_all_prompts_nonce'], 'mxchat_delete_all_prompts_action')) {
1041 - wp_die(__('Nonce verification failed.', 'mxchat'));
1397 +public function mxchat_auto_email_transcript_callback() {
1398 + $options = get_option('mxchat_transcripts_options', array());
1399 + $enabled = isset($options['mxchat_auto_email_transcript_enabled']) ? $options['mxchat_auto_email_transcript_enabled'] : 0;
1400 + $delay = isset($options['mxchat_auto_email_transcript_delay']) ? $options['mxchat_auto_email_transcript_delay'] : '30';
1401 + $require_contact = isset($options['mxchat_auto_email_transcript_require_contact']) ? $options['mxchat_auto_email_transcript_require_contact'] : 0;
1402 + ?>
1403 + <label>
1404 + <input type="checkbox"
1405 + id="mxchat_auto_email_transcript_enabled"
1406 + name="mxchat_transcripts_options[mxchat_auto_email_transcript_enabled]"
1407 + value="1"
1408 + <?php checked($enabled, 1); ?>>
1409 + <?php esc_html_e('Enable Auto-Email of Full Transcript', 'mxchat'); ?>
1410 + </label>
1411 + <br><br>
1412 + <label for="mxchat_auto_email_transcript_delay">
1413 + <?php esc_html_e('Send transcript after:', 'mxchat'); ?>
1414 + </label>
1415 + <select id="mxchat_auto_email_transcript_delay"
1416 + name="mxchat_transcripts_options[mxchat_auto_email_transcript_delay]">
1417 + <option value="15" <?php selected($delay, '15'); ?>>
1418 + <?php esc_html_e('15 minutes', 'mxchat'); ?>
1419 + </option>
1420 + <option value="30" <?php selected($delay, '30'); ?>>
1421 + <?php esc_html_e('30 minutes', 'mxchat'); ?>
1422 + </option>
1423 + <option value="60" <?php selected($delay, '60'); ?>>
1424 + <?php esc_html_e('1 hour', 'mxchat'); ?>
1425 + </option>
1426 + </select>
1427 + <br><br>
1428 + <label>
1429 + <input type="checkbox"
1430 + id="mxchat_auto_email_transcript_require_contact"
1431 + name="mxchat_transcripts_options[mxchat_auto_email_transcript_require_contact]"
1432 + value="1"
1433 + <?php checked($require_contact, 1); ?>>
1434 + <?php esc_html_e('Only send if visitor provided contact info', 'mxchat'); ?>
1435 + </label>
1436 + <p class="description">
1437 + <?php esc_html_e('Automatically email the full transcript as a .txt file after the specified time has passed since the last user message. The scheduled email will be cancelled if a new message is received.', 'mxchat'); ?>
1438 + <br>
1439 + <?php esc_html_e('When "Only send if visitor provided contact info" is enabled, transcripts will only be emailed if the visitor shared an email address or phone number (including WhatsApp) in the chat.', 'mxchat'); ?>
1440 + </p>
1441 + <?php
1442 +}
1443 +
1444 +
1445 +
1446 +public function sanitize_transcripts_options($input) {
1447 + $sanitized = array();
1448 +
1449 + $sanitized['mxchat_enable_notifications'] = isset($input['mxchat_enable_notifications']) ? 1 : 0;
1450 +
1451 + if (isset($input['mxchat_notification_email'])) {
1452 + $sanitized['mxchat_notification_email'] = sanitize_email($input['mxchat_notification_email']);
1453 + if (!is_email($sanitized['mxchat_notification_email'])) {
1454 + add_settings_error(
1455 + 'mxchat_transcripts_options',
1456 + 'invalid_email',
1457 + __('Please enter a valid email address for notifications.', 'mxchat'),
1458 + 'error'
1459 + );
1460 + $sanitized['mxchat_notification_email'] = get_option('admin_email');
1461 + }
1042 1462 }
1043 1463
1044 - // Check permissions
1045 - if (!current_user_can('manage_options')) {
1046 - wp_die(__('You do not have sufficient permissions to delete all prompts.', 'mxchat'));
1464 + // Sanitize auto-delete setting
1465 + $valid_intervals = array('never', '1week', '2weeks', '1month');
1466 + if (isset($input['mxchat_auto_delete_transcripts'])) {
1467 + $sanitized['mxchat_auto_delete_transcripts'] = in_array($input['mxchat_auto_delete_transcripts'], $valid_intervals)
1468 + ? $input['mxchat_auto_delete_transcripts']
1469 + : 'never';
1470 + } else {
1471 + $sanitized['mxchat_auto_delete_transcripts'] = 'never';
1047 1472 }
1048 1473
1049 - global $wpdb;
1050 - $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
1474 + // Get old value to check if auto-delete setting changed
1475 + $old_options = get_option('mxchat_transcripts_options');
1476 + $old_interval = isset($old_options['mxchat_auto_delete_transcripts']) ? $old_options['mxchat_auto_delete_transcripts'] : 'never';
1477 +
1478 + // If the auto-delete setting changed, reschedule the cron job
1479 + if ($old_interval !== $sanitized['mxchat_auto_delete_transcripts']) {
1480 + $this->schedule_transcript_cleanup($sanitized['mxchat_auto_delete_transcripts']);
1481 + }
1051 1482
1052 - // Delete all prompts from the table
1053 - $wpdb->query("DELETE FROM {$table_name}");
1483 + // Sanitize auto-email transcript settings
1484 + $sanitized['mxchat_auto_email_transcript_enabled'] = isset($input['mxchat_auto_email_transcript_enabled']) ? 1 : 0;
1054 1485
1055 - // Clear relevant cache
1056 - wp_cache_delete('all_prompts', 'mxchat_prompts');
1486 + $valid_delays = array('15', '30', '60');
1487 + if (isset($input['mxchat_auto_email_transcript_delay'])) {
1488 + $sanitized['mxchat_auto_email_transcript_delay'] = in_array($input['mxchat_auto_email_transcript_delay'], $valid_delays)
1489 + ? $input['mxchat_auto_email_transcript_delay']
1490 + : '30';
1491 + } else {
1492 + $sanitized['mxchat_auto_email_transcript_delay'] = '30';
1493 + }
1057 1494
1058 - // Redirect back with a success message
1059 - $redirect_url = add_query_arg(array(
1060 - 'page' => 'mxchat-prompts',
1061 - 'all_deleted' => 'true'
1062 - ), admin_url('admin.php'));
1495 + // Sanitize require contact info setting
1496 + $sanitized['mxchat_auto_email_transcript_require_contact'] = isset($input['mxchat_auto_email_transcript_require_contact']) ? 1 : 0;
1063 1497
1064 - wp_safe_redirect($redirect_url);
1065 - exit;
1498 + return $sanitized;
1066 1499 }
1067 -public function mxchat_handle_delete_prompt() {
1068 - // Sanitize and validate nonce
1069 - $nonce = isset($_GET['_wpnonce']) ? sanitize_text_field(wp_unslash($_GET['_wpnonce'])) : '';
1070 - if (empty($nonce) || !wp_verify_nonce($nonce, 'mxchat_delete_prompt_nonce')) {
1071 - wp_die(esc_html__('Nonce verification failed.', 'mxchat'));
1500 +
1501 +/**
1502 + * Schedule or unschedule the transcript cleanup cron job
1503 + */
1504 +public function schedule_transcript_cleanup($interval) {
1505 + // Clear any existing scheduled event
1506 + $timestamp = wp_next_scheduled('mxchat_cleanup_old_transcripts');
1507 + if ($timestamp) {
1508 + wp_unschedule_event($timestamp, 'mxchat_cleanup_old_transcripts');
1072 1509 }
1510 +
1511 + // Schedule new event if not set to "never"
1512 + if ($interval !== 'never') {
1513 + // Schedule to run daily at 3 AM
1514 + $next_run = strtotime('tomorrow 3:00 AM');
1515 + wp_schedule_event($next_run, 'daily', 'mxchat_cleanup_old_transcripts');
1516 + }
1517 +}
1073 1518
1074 - // Check permissions
1519 +/**
1520 + * Delete old transcripts based on the configured interval
1521 + */
1522 +public function cleanup_old_transcripts() {
1523 + $options = get_option('mxchat_transcripts_options', array());
1524 + $interval = isset($options['mxchat_auto_delete_transcripts']) ? $options['mxchat_auto_delete_transcripts'] : 'never';
1525 +
1526 + // If set to never, don't delete anything
1527 + if ($interval === 'never') {
1528 + return;
1529 + }
1530 +
1531 + // Calculate the cutoff date
1532 + $days = 0;
1533 + switch ($interval) {
1534 + case '1week':
1535 + $days = 7;
1536 + break;
1537 + case '2weeks':
1538 + $days = 14;
1539 + break;
1540 + case '1month':
1541 + $days = 30;
1542 + break;
1543 + default:
1544 + return; // Invalid interval, don't delete anything
1545 + }
1546 +
1547 + global $wpdb;
1548 + $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
1549 +
1550 + // Calculate the cutoff timestamp
1551 + $cutoff_date = date('Y-m-d H:i:s', strtotime("-{$days} days"));
1552 +
1553 + // Delete transcripts older than the cutoff date
1554 + $deleted = $wpdb->query(
1555 + $wpdb->prepare(
1556 + "DELETE FROM {$table_name} WHERE created_at < %s",
1557 + $cutoff_date
1558 + )
1559 + );
1560 +
1561 + // Log the cleanup action
1562 + if ($deleted !== false && $deleted > 0) {
1563 + error_log(sprintf('MXChat: Auto-deleted %d transcripts older than %d days', $deleted, $days));
1564 + }
1565 +}
1566 +
1567 +public function export_chat_transcripts() {
1075 1568 if (!current_user_can('manage_options')) {
1076 - wp_die(esc_html__('You do not have sufficient permissions to delete prompts.', 'mxchat'));
1569 + wp_die(esc_html__('You do not have sufficient permissions to access this page.', 'mxchat'));
1077 1570 }
1078 1571
1079 - // Validate and sanitize ID parameter
1080 - $id = isset($_GET['id']) ? intval($_GET['id']) : 0;
1081 - if ($id <= 0) {
1082 - wp_die(esc_html__('Invalid prompt ID.', 'mxchat'));
1572 + check_ajax_referer('mxchat_export_transcripts', 'security');
1573 +
1574 + global $wpdb;
1575 + $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
1576 +
1577 + // Get all transcripts ordered by session and timestamp
1578 + $results = $wpdb->get_results(
1579 + "SELECT session_id, user_email, user_identifier, role, message, timestamp
1580 + FROM {$table_name}
1581 + ORDER BY session_id, timestamp ASC"
1582 + );
1583 +
1584 + if (empty($results)) {
1585 + wp_send_json_error(array('message' => 'No transcripts found.'));
1586 + wp_die();
1083 1587 }
1084 1588
1085 - global $wpdb;
1086 - $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
1589 + // Set headers for CSV download
1590 + header('Content-Type: text/csv');
1591 + header('Content-Disposition: attachment; filename="chat-transcripts-' . date('Y-m-d') . '.csv"');
1592 + header('Pragma: no-cache');
1593 + header('Expires: 0');
1087 1594
1088 - // Clear cache and delete prompt
1089 - wp_cache_delete('prompt_' . $id, 'mxchat_prompts');
1090 - $wpdb->delete($table_name, array('id' => $id), array('%d'));
1595 + // Create output stream
1596 + $output = fopen('php://output', 'w');
1091 1597
1092 - wp_safe_redirect(add_query_arg(array('page' => 'mxchat-prompts', 'deleted' => 'true'), admin_url('admin.php')));
1093 - exit;
1598 + // Add UTF-8 BOM for proper Excel encoding
1599 + fputs($output, "\xEF\xBB\xBF");
1600 +
1601 + // Add CSV headers
1602 + fputcsv($output, array(
1603 + 'Session ID',
1604 + 'Email',
1605 + 'User Identifier',
1606 + 'Role',
1607 + 'Message',
1608 + 'Timestamp'
1609 + ));
1610 +
1611 + // Add data rows
1612 + foreach ($results as $row) {
1613 + fputcsv($output, array(
1614 + $row->session_id,
1615 + $row->user_email,
1616 + $row->user_identifier,
1617 + $row->role,
1618 + $row->message,
1619 + $row->timestamp
1620 + ));
1621 + }
1622 +
1623 + fclose($output);
1624 + wp_die();
1094 1625 }
1095 -public function mxchat_generate_embedding($text) {
1096 - $options = get_option('mxchat_options');
1097 - $api_key = $options['api_key'] ?? esc_html__('default_api_key', 'mxchat');
1098 1626
1099 - $response = wp_remote_post('https://api.openai.com/v1/embeddings', array(
1100 - 'body' => wp_json_encode(array(
1101 - 'model' => esc_html__('text-embedding-ada-002', 'mxchat'),
1102 - 'input' => $text
1103 - )),
1104 - 'headers' => array(
1105 - 'Authorization' => 'Bearer ' . $api_key,
1106 - 'Content-Type' => esc_html__('application/json', 'mxchat')
1107 - ),
1108 - ));
1109 1627
1110 - if (is_wp_error($response)) {
1111 - return null;
1112 - }
1628 +public function mxchat_fetch_chat_history() {
1629 + global $wpdb;
1630 + $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
1631 + $url_clicks_table = $wpdb->prefix . 'mxchat_url_clicks';
1113 1632
1114 - $response_data = json_decode(wp_remote_retrieve_body($response), true);
1115 - return $response_data['data'][0]['embedding'] ?? null;
1633 + if (!current_user_can('manage_options')) {
1634 + wp_die(esc_html__('You do not have sufficient permissions to view this page.', 'mxchat'));
1116 1635 }
1117 -public function mxchat_delete_chat_history() {
1118 - if (!current_user_can('manage_options')) {
1119 - echo wp_json_encode(['error' => esc_html__('You do not have sufficient permissions.', 'mxchat')]);
1120 - wp_die();
1636 +
1637 + $page = isset($_POST['page']) ? absint($_POST['page']) : 1;
1638 + $per_page = isset($_POST['per_page']) ? absint($_POST['per_page']) : 50;
1639 + $offset = ($page - 1) * $per_page;
1640 + $search = isset($_POST['search']) ? sanitize_text_field($_POST['search']) : '';
1641 +
1642 + $search_condition = '';
1643 + $search_params = [];
1644 +
1645 + if (!empty($search)) {
1646 + $search_condition = "WHERE (
1647 + session_id LIKE %s
1648 + OR user_email LIKE %s
1649 + OR user_name LIKE %s
1650 + OR user_identifier LIKE %s
1651 + OR message LIKE %s
1652 + )";
1653 + $search_params = array_fill(0, 5, '%' . $wpdb->esc_like($search) . '%');
1654 + }
1655 +
1656 + $count_query = !empty($search)
1657 + ? $wpdb->prepare("SELECT COUNT(DISTINCT session_id) FROM {$table_name} {$search_condition}", $search_params)
1658 + : "SELECT COUNT(DISTINCT session_id) FROM {$table_name}";
1659 +
1660 + $total_sessions = $wpdb->get_var($count_query);
1661 +
1662 + $session_query = !empty($search)
1663 + ? $wpdb->prepare(
1664 + "SELECT DISTINCT t.session_id FROM {$table_name} t {$search_condition}
1665 + GROUP BY t.session_id ORDER BY MAX(t.timestamp) DESC LIMIT %d OFFSET %d",
1666 + array_merge($search_params, [$per_page, $offset])
1667 + )
1668 + : $wpdb->prepare(
1669 + "SELECT DISTINCT session_id FROM {$table_name}
1670 + GROUP BY session_id ORDER BY MAX(timestamp) DESC LIMIT %d OFFSET %d",
1671 + $per_page, $offset
1672 + );
1673 +
1674 + $session_ids = $wpdb->get_col($session_query);
1675 +
1676 + if (empty($session_ids)) {
1677 + ob_start();
1678 + echo '<div class="mxchat-no-results">';
1679 + echo esc_html__('No chat history found.', 'mxchat');
1680 + if (!empty($search)) {
1681 + echo ' ' . esc_html__('Try adjusting your search criteria.', 'mxchat');
1121 1682 }
1683 + echo '</div>';
1684 + wp_send_json([
1685 + 'html' => ob_get_clean(),
1686 + 'page' => $page,
1687 + 'total_pages' => 0,
1688 + 'total_sessions' => 0
1689 + ]);
1690 + wp_die();
1691 + }
1122 1692
1123 - check_ajax_referer('mxchat_delete_chat_history', 'security');
1693 + $url_table_exists = $wpdb->get_var("SHOW TABLES LIKE '$url_clicks_table'") === $url_clicks_table;
1694 + $originating_columns_exist = !empty($wpdb->get_results("SHOW COLUMNS FROM $table_name LIKE 'originating_page_url'"));
1124 1695
1125 - global $wpdb;
1126 - $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
1696 + ob_start();
1697 + echo '<div class="mxchat-transcript">';
1127 1698
1128 - if (isset($_POST['delete_session_ids']) && is_array($_POST['delete_session_ids'])) {
1129 - foreach ($_POST['delete_session_ids'] as $session_id) {
1130 - $session_id_sanitized = sanitize_text_field($session_id);
1699 + foreach ($session_ids as $session_id) {
1700 + $session_data = $originating_columns_exist
1701 + ? $wpdb->get_row($wpdb->prepare(
1702 + "SELECT user_email, user_name, user_identifier, originating_page_url, originating_page_title, timestamp
1703 + FROM {$table_name} WHERE session_id = %s ORDER BY timestamp ASC LIMIT 1",
1704 + $session_id
1705 + ))
1706 + : $wpdb->get_row($wpdb->prepare(
1707 + "SELECT user_email, user_name, user_identifier, timestamp FROM {$table_name}
1708 + WHERE session_id = %s LIMIT 1",
1709 + $session_id
1710 + ));
1131 1711
1132 - // Clear relevant cache before deletion
1133 - $cache_key = 'chat_session_' . $session_id_sanitized;
1134 - wp_cache_delete($cache_key, 'mxchat_chat_sessions');
1712 + $clicked_urls = [];
1713 + if ($url_table_exists) {
1714 + $url_clicks = $wpdb->get_results(
1715 + $wpdb->prepare(
1716 + "SELECT DISTINCT clicked_url FROM {$url_clicks_table}
1717 + WHERE session_id = %s ORDER BY click_timestamp ASC",
1718 + $session_id
1719 + )
1720 + );
1721 + foreach ($url_clicks as $click) {
1722 + $clicked_urls[] = $click->clicked_url;
1723 + }
1724 + }
1135 1725
1136 - // Perform the deletion
1137 - $wpdb->delete($table_name, ['session_id' => $session_id_sanitized]);
1138 - }
1726 + $messages = $wpdb->get_results(
1727 + $wpdb->prepare(
1728 + "SELECT * FROM {$table_name}
1729 + WHERE session_id = %s ORDER BY timestamp ASC",
1730 + $session_id
1731 + )
1732 + );
1139 1733
1140 - // Optionally, clear a general cache if you have one
1141 - wp_cache_delete('all_chat_sessions', 'mxchat_chat_sessions');
1734 + $latest_timestamp = !empty($messages) ? end($messages)->timestamp : $session_data->timestamp;
1735 + $session_time = wp_date('M j, g:ia', strtotime($latest_timestamp . ' UTC'));
1142 1736
1143 - echo wp_json_encode(['success' => esc_html__('Selected chat sessions have been deleted.', 'mxchat')]);
1737 + $user_email = !empty($session_data->user_email) ? $session_data->user_email : '';
1738 + $user_name = !empty($session_data->user_name) ? $session_data->user_name : ''; // NEW
1739 + $user_identifier = !empty($session_data->user_identifier) ? $session_data->user_identifier : 'Guest';
1740 +
1741 + // Build user display with name if available
1742 + if (!empty($user_email)) {
1743 + $user_display = $user_email;
1744 + if (!empty($user_name)) {
1745 + $user_display .= ' (' . $user_name . ')';
1746 + }
1144 1747 } else {
1145 - echo wp_json_encode(['error' => esc_html__('No chat sessions selected for deletion.', 'mxchat')]);
1748 + $user_display = $user_identifier;
1146 1749 }
1750 + echo '<div class="mxchat-session">';
1751 + echo '<div class="mxchat-session-header" data-session-id="' . esc_attr($session_id) . '">';
1752 + echo '<div class="mxchat-user-row">' . esc_html($user_display) . '</div>';
1147 1753
1148 - wp_die();
1149 - }
1754 + if ($originating_columns_exist && !empty($session_data->originating_page_url)) {
1755 + $page_title = !empty($session_data->originating_page_title) ? $session_data->originating_page_title : $session_data->originating_page_url;
1150 1756
1757 + echo '<div class="mxchat-header-row flex-between">';
1758 + echo '<div class="mxchat-main-info">';
1759 + echo '<a href="' . esc_url($session_data->originating_page_url) . '" target="_blank" class="mxchat-page-link">';
1760 + echo esc_html($page_title);
1761 + echo '</a>';
1762 + echo '<span class="mxchat-session-time">' . esc_html($session_time) . '</span>';
1763 + echo '</div>';
1764 + echo '</div>';
1765 + }
1151 1766
1152 -public function mxchat_save_inline_prompt() {
1153 - // Check for nonce security
1154 - check_ajax_referer('mxchat_save_inline_nonce');
1767 + if (!empty($clicked_urls)) {
1768 + echo '<div class="mxchat-links-row">';
1769 + echo '<span class="mxchat-label">Clicked:</span> ';
1770 + $link_count = 0;
1771 + foreach ($clicked_urls as $url) {
1772 + if ($link_count > 0) echo ', ';
1773 + echo '<a href="' . esc_url($url) . '" target="_blank" class="mxchat-link-item">' . esc_html(parse_url($url, PHP_URL_HOST)) . '</a>';
1774 + $link_count++;
1775 + if ($link_count >= 3) {
1776 + if (count($clicked_urls) > 3) {
1777 + echo ' +' . (count($clicked_urls) - 3) . ' more';
1778 + }
1779 + break;
1780 + }
1781 + }
1782 + echo '</div>';
1783 + }
1155 1784
1156 - // Verify permissions
1157 - if (!current_user_can('manage_options')) {
1158 - wp_send_json_error(esc_html__('Permission denied.', 'mxchat'));
1159 - return;
1160 - }
1785 + echo '</div>'; // end session-header
1786 + echo '<div class="mxchat-messages">';
1161 1787
1162 - global $wpdb;
1163 - $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
1788 + foreach ($messages as $transcript) {
1789 + $formatted_timestamp = wp_date('F j, Y g:i a', strtotime($transcript->timestamp . ' UTC'));
1164 1790
1165 - // Validate and sanitize input data
1166 - $prompt_id = isset($_POST['id']) ? absint($_POST['id']) : 0;
1167 - $article_content = isset($_POST['article_content']) ? sanitize_textarea_field($_POST['article_content']) : '';
1168 - $article_url = isset($_POST['article_url']) ? esc_url_raw($_POST['article_url']) : '';
1791 + switch ($transcript->role) {
1792 + case 'assistant':
1793 + case 'bot':
1794 + $message_class = 'bot-message';
1795 + $display_role = esc_html__('Chatbot', 'mxchat');
1796 + break;
1797 + case 'user':
1798 + $message_class = 'user-message';
1799 + $display_role = !empty($transcript->user_identifier)
1800 + ? sanitize_text_field($transcript->user_identifier)
1801 + : esc_html__('User', 'mxchat');
1802 + break;
1803 + case 'agent':
1804 + $message_class = 'agent-message';
1805 + $display_role = esc_html__('Agent', 'mxchat');
1806 + break;
1807 + default:
1808 + $message_class = 'unknown-message';
1809 + $display_role = esc_html__('Unknown', 'mxchat');
1810 + }
1169 1811
1170 - if ($prompt_id > 0 && !empty($article_content)) {
1171 - // Re-generate the embedding vector for the updated content
1172 - $embedding_vector = $this->mxchat_generate_embedding($article_content);
1812 + $message_content = wp_kses(
1813 + stripslashes($transcript->message),
1814 + [
1815 + 'b' => [], 'strong' => [], 'i' => [], 'em' => [], 'u' => [],
1816 + 'br' => [], 'p' => [], 'ul' => [], 'ol' => [], 'li' => [],
1817 + 'a' => ['href' => [], 'title' => [], 'target' => []]
1818 + ]
1819 + );
1820 + // Note: Don't use nl2br() here - format_transcript_message() handles paragraph formatting
1173 1821
1174 - if (is_array($embedding_vector)) {
1175 - // Serialize the embedding vector before storing it
1176 - $embedding_vector_serialized = serialize($embedding_vector);
1822 + // Check if this bot message has RAG context data
1823 + $has_rag_context = false;
1824 + if (($transcript->role === 'bot' || $transcript->role === 'assistant') && !empty($transcript->rag_context)) {
1825 + $has_rag_context = true;
1826 + }
1177 1827
1178 - // Update the prompt in the database
1179 - $updated = $wpdb->update(
1180 - $table_name,
1181 - array(
1182 - 'article_content' => $article_content,
1183 - 'embedding_vector' => $embedding_vector_serialized,
1184 - 'source_url' => $article_url,
1185 - ),
1186 - array('id' => $prompt_id),
1187 - array('%s', '%s', '%s'),
1188 - array('%d')
1189 - );
1828 + $message_id_attr = $has_rag_context ? ' data-message-id="' . esc_attr($transcript->id) . '"' : '';
1190 1829
1191 - if ($updated !== false) {
1192 - wp_send_json_success();
1193 - } else {
1194 - wp_send_json_error(esc_html__('Database update failed.', 'mxchat'));
1830 + // Process markdown in message content
1831 + $formatted_content = $this->format_transcript_message($message_content);
1832 +
1833 + echo '<div class="mxchat-message ' . esc_attr($message_class) . '"' . $message_id_attr . '>';
1834 + echo '<div class="mxchat-message-header">';
1835 + echo '<span class="mxchat-role-label">' . esc_html($display_role) . '</span>';
1836 + if ($has_rag_context) {
1837 + echo '<span class="mxchat-rag-link" title="' . esc_attr__('View retrieved documents', 'mxchat') . '">Sources &rarr;</span>';
1195 1838 }
1196 - } else {
1197 - wp_send_json_error(esc_html__('Embedding generation failed.', 'mxchat'));
1839 + echo '</div>';
1840 + echo '<div class="mxchat-message-content">' . $formatted_content . '</div>';
1841 + echo '<div class="mxchat-timestamp">' . esc_html($formatted_timestamp) . '</div>';
1842 + echo '</div>';
1198 1843 }
1199 - } else {
1200 - wp_send_json_error(esc_html__('Invalid data.', 'mxchat'));
1844 +
1845 + echo '</div></div>'; // end messages and session
1201 1846 }
1202 -}
1203 1847
1848 + echo '</div>'; // end transcript
1204 1849
1850 + $total_pages = ceil($total_sessions / $per_page);
1205 1851
1206 -// Add this method to handle post updates
1207 -public function handle_post_update($post_id, $post, $update) {
1208 - // Basic validation checks
1209 - if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE || wp_is_post_revision($post_id)) {
1210 - return;
1211 - }
1852 + echo '<div class="mxchat-pagination">';
1853 + echo '<div class="mxchat-pagination-info">';
1854 + echo sprintf(
1855 + esc_html__('Showing %1$d to %2$d of %3$d sessions', 'mxchat'),
1856 + $offset + 1,
1857 + min($offset + $per_page, $total_sessions),
1858 + $total_sessions
1859 + );
1860 + echo '</div>';
1212 1861
1213 - // Only process published content
1214 - if (!in_array($post->post_status, array('publish'))) {
1215 - return;
1216 - }
1862 + if ($total_pages > 1) {
1863 + echo '<div class="mxchat-pagination-controls">';
1864 + if ($page > 1) {
1865 + echo '<button class="mxchat-pagination-button" data-page="' . ($page - 1) . '">&laquo; ' . esc_html__('Previous', 'mxchat') . '</button>';
1866 + }
1217 1867
1218 - // Check if sync is enabled for this post type
1219 - $post_type = $post->post_type;
1220 - if (!in_array($post_type, array('post', 'page'))) {
1221 - return;
1222 - }
1868 + $start_page = max(1, $page - 2);
1869 + $end_page = min($total_pages, $page + 2);
1223 1870
1224 - $sync_option = ($post_type === 'post') ? 'mxchat_auto_sync_posts' : 'mxchat_auto_sync_pages';
1225 - if (get_option($sync_option) != '1') {
1226 - return;
1227 - }
1871 + if ($start_page > 1) {
1872 + echo '<button class="mxchat-pagination-button" data-page="1">1</button>';
1873 + if ($start_page > 2) {
1874 + echo '<span class="mxchat-pagination-ellipsis">...</span>';
1875 + }
1876 + }
1228 1877
1229 - // Prepare content and URL
1230 - $content = wp_strip_all_tags($post->post_content);
1231 - $url = get_permalink($post_id);
1878 + for ($i = $start_page; $i <= $end_page; $i++) {
1879 + $class = $i === $page ? 'mxchat-pagination-button active' : 'mxchat-pagination-button';
1880 + echo '<button class="' . $class . '" data-page="' . $i . '">' . $i . '</button>';
1881 + }
1232 1882
1233 - // Generate embedding vector
1234 - $embedding_vector = $this->mxchat_generate_embedding($content);
1235 - if (!$embedding_vector) {
1236 - return;
1237 - }
1883 + if ($end_page < $total_pages) {
1884 + if ($end_page < $total_pages - 1) {
1885 + echo '<span class="mxchat-pagination-ellipsis">...</span>';
1886 + }
1887 + echo '<button class="mxchat-pagination-button" data-page="' . $total_pages . '">' . $total_pages . '</button>';
1888 + }
1238 1889
1239 - // Check for Pinecone addon and its settings
1240 - $pinecone_settings = get_option('mxchat_pinecone_addon_options');
1241 - $use_pinecone = false;
1890 + if ($page < $total_pages) {
1891 + echo '<button class="mxchat-pagination-button" data-page="' . ($page + 1) . '">' . esc_html__('Next', 'mxchat') . ' &raquo;</button>';
1892 + }
1242 1893
1243 - if ($pinecone_settings && is_array($pinecone_settings)) {
1244 - // Check if Pinecone is enabled and all required settings are present
1245 - $use_pinecone = (
1246 - isset($pinecone_settings['mxchat_use_pinecone']) &&
1247 - $pinecone_settings['mxchat_use_pinecone'] === '1' &&
1248 - !empty($pinecone_settings['mxchat_pinecone_api_key']) &&
1249 - !empty($pinecone_settings['mxchat_pinecone_host']) &&
1250 - !empty($pinecone_settings['mxchat_pinecone_index']) &&
1251 - !empty($pinecone_settings['mxchat_pinecone_environment'])
1252 - );
1894 + echo '</div>';
1253 1895 }
1254 1896
1255 - if ($use_pinecone) {
1256 - // Use Pinecone
1257 - $pinecone_result = $this->store_in_pinecone_main(
1258 - $embedding_vector,
1259 - $content,
1260 - $url,
1261 - $pinecone_settings['mxchat_pinecone_api_key'],
1262 - $pinecone_settings['mxchat_pinecone_environment'],
1263 - $pinecone_settings['mxchat_pinecone_index']
1264 - );
1897 + echo '</div>';
1265 1898
1266 - if (!$pinecone_result['success']) {
1267 - //error_log('MXChat: Pinecone sync failed - falling back to WordPress DB');
1268 - $this->store_in_wordpress_db($content, $url, $embedding_vector);
1269 - }
1270 - } else {
1271 - // Fallback to WordPress DB storage
1272 - $this->store_in_wordpress_db($content, $url, $embedding_vector);
1273 - }
1899 + wp_send_json([
1900 + 'html' => ob_get_clean(),
1901 + 'page' => $page,
1902 + 'total_pages' => $total_pages,
1903 + 'total_sessions' => $total_sessions
1904 + ]);
1905 +
1906 + wp_die();
1274 1907 }
1275 -// Modified storage function to add type field
1276 -private function store_in_pinecone_main($embedding_vector, $content, $url, $api_key, $environment, $index_name, $vector_id = null) {
1277 - $vector_id = $vector_id ?: md5($url);
1278 - $options = get_option('mxchat_pinecone_addon_options');
1279 - $host = $options['mxchat_pinecone_host'] ?? '';
1280 1908
1281 - if (empty($host)) {
1282 - return array(
1283 - 'success' => false,
1284 - 'message' => 'Pinecone host is not configured. Please set the host in your settings.'
1909 +
1910 +/**
1911 + * ALTERNATIVE: Simpler helper method using string replacement
1912 + */
1913 +private function highlight_clicked_links($message_content, $clicked_urls) {
1914 + if (empty($clicked_urls)) {
1915 + return wp_kses(
1916 + $message_content,
1917 + [
1918 + 'b' => [], 'strong' => [], 'i' => [], 'em' => [], 'u' => [],
1919 + 'br' => [], 'p' => [], 'ul' => [], 'ol' => [], 'li' => [],
1920 + 'a' => ['href' => [], 'title' => [], 'target' => [], 'class' => []]
1921 + ]
1285 1922 );
1286 1923 }
1287 1924
1288 - // Determine if this is a product URL
1289 - $is_product = (strpos($url, '/product/') !== false || strpos($url, '/shop/') !== false);
1925 + // First apply standard sanitization
1926 + $message_content = wp_kses(
1927 + $message_content,
1928 + [
1929 + 'b' => [], 'strong' => [], 'i' => [], 'em' => [], 'u' => [],
1930 + 'br' => [], 'p' => [], 'ul' => [], 'ol' => [], 'li' => [],
1931 + 'a' => ['href' => [], 'title' => [], 'target' => [], 'class' => []]
1932 + ]
1933 + );
1290 1934
1291 - $api_endpoint = "https://{$host}/vectors/upsert";
1292 - $request_body = array(
1293 - 'vectors' => array(
1294 - array(
1295 - 'id' => $vector_id,
1296 - 'values' => $embedding_vector,
1297 - 'metadata' => array(
1298 - 'text' => $content,
1299 - 'source_url' => $url,
1300 - 'type' => $is_product ? 'product' : 'content',
1301 - 'last_updated' => time()
1302 - )
1303 - )
1304 - )
1305 - );
1935 + // Process each clicked URL
1936 + foreach ($clicked_urls as $clicked_url) {
1937 + // Try multiple patterns to catch different link formats
1938 + $patterns = [
1939 + // Standard link format
1940 + '/<a([^>]*href=["\']' . preg_quote($clicked_url, '/') . '["\'][^>]*)>/i',
1941 + // Link with trailing slash
1942 + '/<a([^>]*href=["\']' . preg_quote(rtrim($clicked_url, '/'), '/') . '\/?["\'][^>]*)>/i',
1943 + // Encoded entities version
1944 + '/<a([^>]*href=["\']' . preg_quote(htmlentities($clicked_url), '/') . '["\'][^>]*)>/i',
1945 + ];
1306 1946
1307 - $response = wp_remote_post($api_endpoint, array(
1308 - 'headers' => array(
1309 - 'Api-Key' => $api_key,
1310 - 'accept' => 'application/json',
1311 - 'content-type' => 'application/json'
1312 - ),
1313 - 'body' => wp_json_encode($request_body),
1314 - 'timeout' => 30,
1315 - 'data_format' => 'body'
1316 - ));
1947 + foreach ($patterns as $pattern) {
1948 + if (preg_match($pattern, $message_content)) {
1949 + $message_content = preg_replace_callback(
1950 + $pattern,
1951 + function($matches) {
1952 + $full_match = $matches[0];
1953 + $attributes = $matches[1];
1317 1954
1318 - if (is_wp_error($response)) {
1319 - return array(
1320 - 'success' => false,
1321 - 'message' => $response->get_error_message()
1322 - );
1323 - }
1955 + // Check if it already has the class
1956 + if (strpos($full_match, 'mxchat-clicked-link') !== false) {
1957 + return $full_match;
1958 + }
1324 1959
1325 - $response_code = wp_remote_retrieve_response_code($response);
1326 - if ($response_code !== 200) {
1327 - $body = wp_remote_retrieve_body($response);
1328 - return array(
1329 - 'success' => false,
1330 - 'message' => sprintf(
1331 - 'Pinecone API error (HTTP %d): %s',
1332 - $response_code,
1333 - $body
1334 - )
1335 - );
1960 + // Check if class attribute exists
1961 + if (preg_match('/class=["\']([^"\']*)["\']/', $attributes, $class_matches)) {
1962 + // Add to existing class
1963 + $new_attributes = preg_replace(
1964 + '/class=["\']([^"\']*)["\']/',
1965 + 'class="$1 mxchat-clicked-link"',
1966 + $attributes
1967 + );
1968 + } else {
1969 + // Add new class attribute
1970 + $new_attributes = $attributes . ' class="mxchat-clicked-link"';
1971 + }
1972 +
1973 + // Add title if not present
1974 + if (strpos($new_attributes, 'title=') === false) {
1975 + $new_attributes .= ' title="User clicked this link"';
1976 + }
1977 +
1978 + return '<a' . $new_attributes . '>';
1979 + },
1980 + $message_content
1981 + );
1982 +
1983 + // If we found and replaced, break out of the patterns loop
1984 + break;
1985 + }
1986 + }
1336 1987 }
1337 1988
1338 - return array(
1339 - 'success' => true,
1340 - 'message' => 'Successfully stored in Pinecone'
1341 - );
1989 + return $message_content;
1342 1990 }
1343 -// Helper method for WordPress DB storage
1344 -private function store_in_wordpress_db($content, $url, $embedding_vector) {
1991 +
1992 +public function mxchat_create_prompts_page() {
1993 + //error_log('=== DEBUG: mxchat_create_prompts_page started ===');
1994 +
1345 1995 global $wpdb;
1346 1996 $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
1347 1997
1348 - $existing_entry = $wpdb->get_row(
1349 - $wpdb->prepare("SELECT id FROM $table_name WHERE source_url = %s", $url)
1350 - );
1998 + $knowledge_manager = MxChat_Knowledge_Manager::get_instance();
1999 + $pinecone_manager = MxChat_Pinecone_Manager::get_instance();
1351 2000
1352 - if ($existing_entry) {
1353 - $wpdb->update(
1354 - $table_name,
1355 - array(
1356 - 'article_content' => $content,
1357 - 'embedding_vector' => serialize($embedding_vector),
1358 - 'timestamp' => current_time('mysql')
1359 - ),
1360 - array('id' => $existing_entry->id),
1361 - array('%s', '%s', '%s')
1362 - );
1363 - } else {
1364 - $wpdb->insert(
1365 - $table_name,
1366 - array(
1367 - 'article_content' => $content,
1368 - 'source_url' => $url,
1369 - 'embedding_vector' => serialize($embedding_vector),
1370 - 'timestamp' => current_time('mysql')
1371 - ),
1372 - array('%s', '%s', '%s', '%s')
1373 - );
2001 + // Display success message if all prompts were deleted
2002 + if (isset($_GET['all_deleted']) && $_GET['all_deleted'] === 'true') {
2003 + echo '<div class="notice notice-success is-dismissible"><p>' . esc_html__('All knowledge has been deleted successfully.', 'mxchat') . '</p></div>';
1374 2004 }
1375 -}
1376 -/**
1377 - * Handle deletion of posts and pages from both Pinecone and WordPress DB
1378 - *
1379 - * @param int $post_id The ID of the post being deleted
1380 - * @return void
1381 - */
1382 -public function mxchat_handle_post_delete($post_id) {
1383 - // Get post data before it's deleted
1384 - $post = get_post($post_id);
1385 2005
1386 - // Basic validation
1387 - if (!$post || wp_is_post_revision($post_id)) {
1388 - return;
1389 - }
2006 + // Set up pagination, search query, and content type filter
2007 + $nonce = isset($_GET['_wpnonce']) ? sanitize_text_field($_GET['_wpnonce']) : '';
2008 + $search_query = (!empty($nonce) && wp_verify_nonce($nonce, 'mxchat_prompts_search_nonce') && isset($_GET['search'])) ? sanitize_text_field($_GET['search']) : '';
2009 + $content_type_filter = isset($_GET['content_type']) ? sanitize_key($_GET['content_type']) : ''; // ADDED 2.5.6
2010 + $current_page = isset($_GET['paged']) ? absint($_GET['paged']) : 1;
2011 + $per_page = 10;
1390 2012
1391 - // Check post type
1392 - $post_type = $post->post_type;
1393 - if (!in_array($post_type, array('post', 'page'))) {
1394 - return;
2013 + //error_log('DEBUG: Search query: ' . $search_query);
2014 + //error_log('DEBUG: Content type filter: ' . $content_type_filter);
2015 + //error_log('DEBUG: Current page: ' . $current_page);
2016 + //error_log('DEBUG: Per page: ' . $per_page);
2017 +
2018 + // ================================
2019 + // MULTI-BOT CONFIGURATION
2020 + // ================================
2021 +
2022 + // Check for multi-bot and set up bot selection
2023 + if (class_exists('MxChat_Multi_Bot_Manager')) {
2024 + $multi_bot_manager = MxChat_Multi_Bot_Core_Manager::get_instance();
2025 + $available_bots = $multi_bot_manager->get_available_bots();
2026 +
2027 + // Get saved bot selection (user-specific first, then site-wide default)
2028 + $user_id = get_current_user_id();
2029 + $saved_bot_id = get_user_meta($user_id, 'mxchat_selected_knowledge_bot', true);
2030 + if (empty($saved_bot_id)) {
2031 + $saved_bot_id = get_option('mxchat_current_knowledge_bot', 'default');
2032 + }
2033 +
2034 + // Allow URL override but default to saved selection
2035 + $current_bot_id = isset($_GET['bot_id']) ? sanitize_key($_GET['bot_id']) : $saved_bot_id;
2036 + $multibot_active = true;
2037 + } else {
2038 + $current_bot_id = 'default';
2039 + $multibot_active = false;
1395 2040 }
1396 2041
1397 - // Check if sync is enabled for this post type
1398 - $sync_option = ($post_type === 'post') ? 'mxchat_auto_sync_posts' : 'mxchat_auto_sync_pages';
1399 - if (get_option($sync_option) != '1') {
1400 - return;
1401 - }
2042 + // ================================
2043 + // DATA SOURCE CONFIGURATION
2044 + // ================================
1402 2045
1403 - // Get the URL before post is deleted
1404 - $source_url = get_permalink($post_id);
1405 - if (!$source_url) {
1406 - //error_log('MXChat: Failed to get permalink for post ' . $post_id);
1407 - return;
1408 - }
2046 + // Get bot-specific Pinecone settings
2047 + $pinecone_options = $pinecone_manager->mxchat_get_bot_pinecone_options($current_bot_id);
2048 + $use_pinecone = ($pinecone_options['mxchat_use_pinecone'] ?? '0') === '1';
2049 + $pinecone_api_key = $pinecone_options['mxchat_pinecone_api_key'] ?? '';
1409 2050
1410 - // Check for Pinecone addon and its settings
1411 - $pinecone_settings = get_option('mxchat_pinecone_addon_options');
1412 - $use_pinecone = false;
1413 - if ($pinecone_settings && is_array($pinecone_settings)) {
1414 - $use_pinecone = (
1415 - isset($pinecone_settings['mxchat_use_pinecone']) &&
1416 - $pinecone_settings['mxchat_use_pinecone'] === '1' &&
1417 - !empty($pinecone_settings['mxchat_pinecone_api_key']) &&
1418 - !empty($pinecone_settings['mxchat_pinecone_host']) &&
1419 - !empty($pinecone_settings['mxchat_pinecone_index']) &&
1420 - !empty($pinecone_settings['mxchat_pinecone_environment'])
1421 - );
1422 - }
2051 + //error_log('DEBUG: Bot ' . $current_bot_id . ' - Use Pinecone: ' . ($use_pinecone ? 'YES' : 'NO'));
2052 + //error_log('DEBUG: Bot ' . $current_bot_id . ' - Has API Key: ' . (!empty($pinecone_api_key) ? 'YES' : 'NO'));
2053 + //error_log('DEBUG: Bot ' . $current_bot_id . ' - Host: ' . ($pinecone_options['mxchat_pinecone_host'] ?? 'NOT SET'));
2054 + //error_log('DEBUG: Bot ' . $current_bot_id . ' - Namespace: ' . ($pinecone_options['mxchat_pinecone_namespace'] ?? 'NOT SET'));
1423 2055
1424 - $deletion_successful = false;
2056 + if ($use_pinecone && !empty($pinecone_api_key)) {
2057 + //error_log('DEBUG: Using PINECONE data source with bot-specific config');
2058 + // PINECONE DATA SOURCE
2059 + $data_source = 'pinecone';
2060 +
2061 + // IMPORTANT: Pass the bot-specific $pinecone_options, not default options!
2062 + // UPDATED 2.5.6: Added content_type_filter parameter
2063 + $records = $pinecone_manager->mxchat_fetch_pinecone_records($pinecone_options, $search_query, $current_page, $per_page, $current_bot_id, $content_type_filter);
1425 2064
1426 - if ($use_pinecone) {
1427 - try {
1428 - $pinecone_result = $this->delete_from_pinecone(
1429 - array($source_url),
1430 - $pinecone_settings['mxchat_pinecone_api_key'],
1431 - $pinecone_settings['mxchat_pinecone_environment'],
1432 - $pinecone_settings['mxchat_pinecone_index']
1433 - );
2065 + // TEMPORARY DEBUG - Add this right after the fetch call
2066 + //error_log('=== DEBUG: Bot switching issue ===');
2067 + //error_log('Current bot ID: ' . $current_bot_id);
2068 + //error_log('Use Pinecone: ' . ($use_pinecone ? 'YES' : 'NO'));
2069 + //error_log('Records returned: ' . count($records['data'] ?? []));
2070 + //error_log('Total records: ' . ($records['total'] ?? 0));
1434 2071
1435 - if (!$pinecone_result['success']) {
1436 - //error_log('MXChat: Pinecone deletion failed for URL: ' . $source_url . ' - ' . $pinecone_result['message']);
1437 - } else {
1438 - $deletion_successful = true;
2072 + // Check first few records to see their bot_id
2073 + if (!empty($records['data'])) {
2074 + foreach (array_slice($records['data'], 0, 3) as $i => $record) {
2075 + $record_bot_id = $record->bot_id ?? 'NOT_SET';
2076 + //error_log('Record ' . ($i+1) . ' bot_id: ' . $record_bot_id . ', content preview: ' . substr($record->article_content ?? '', 0, 30) . '...');
1439 2077 }
1440 - } catch (Exception $e) {
1441 - //error_log('MXChat: Exception during Pinecone deletion - ' . $e->getMessage());
1442 2078 }
1443 - }
2079 + //error_log('=== END DEBUG ===');
1444 2080
1445 - // Always attempt WordPress DB deletion, regardless of Pinecone status
1446 - try {
1447 - global $wpdb;
1448 - $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
2081 + $total_records = $records['total'] ?? 0;
2082 + $prompts = $records['data'] ?? array();
2083 + $total_in_database = $records['total_in_database'] ?? 0;
2084 + $showing_recent_only = $records['showing_recent_only'] ?? false;
1449 2085
1450 - $result = $wpdb->delete(
1451 - $table_name,
1452 - array('source_url' => $source_url),
1453 - array('%s')
1454 - );
2086 + $total_pages = ceil($total_records / $per_page);
1455 2087
1456 - if ($result === false) {
1457 - //error_log('MXChat: WordPress DB deletion failed for URL: ' . $source_url);
2088 + } else {
2089 + //error_log('DEBUG: Using WORDPRESS DB data source');
2090 + // WORDPRESS DB DATA SOURCE (your existing logic)
2091 + $data_source = 'wordpress';
2092 +
2093 + // Initialize these variables for WordPress DB
2094 + $total_in_database = 0;
2095 + $showing_recent_only = false;
2096 +
2097 + $offset = ($current_page - 1) * $per_page;
2098 +
2099 + // UPDATED 2.5.6: Build WHERE clause for search and content type filtering
2100 + $where_clauses = array();
2101 + $where_values = array();
2102 +
2103 + if ($search_query) {
2104 + $where_clauses[] = "article_content LIKE %s";
2105 + $where_values[] = '%' . $wpdb->esc_like($search_query) . '%';
2106 + }
2107 +
2108 + if ($content_type_filter) {
2109 + $where_clauses[] = "content_type = %s";
2110 + $where_values[] = $content_type_filter;
2111 + }
2112 +
2113 + $sql_where = "";
2114 + if (!empty($where_clauses)) {
2115 + $sql_where = "WHERE " . implode(" AND ", $where_clauses);
2116 + }
2117 +
2118 + // UPDATED 2.6.3: Count unique entries (by source_url) instead of individual rows
2119 + // This ensures pagination shows X entries per page, not X chunks
2120 + // Entries with empty source_url are counted individually
2121 + if (!empty($where_values)) {
2122 + // Count unique source_urls + count of rows with empty source_url
2123 + $count_query = $wpdb->prepare(
2124 + "SELECT (SELECT COUNT(DISTINCT source_url) FROM {$table_name} {$sql_where} AND source_url != '') +
2125 + (SELECT COUNT(*) FROM {$table_name} {$sql_where} AND (source_url = '' OR source_url IS NULL))",
2126 + array_merge($where_values, $where_values)
2127 + );
1458 2128 } else {
1459 - $deletion_successful = true;
2129 + $count_query = "SELECT (SELECT COUNT(DISTINCT source_url) FROM {$table_name} WHERE source_url != '') +
2130 + (SELECT COUNT(*) FROM {$table_name} WHERE source_url = '' OR source_url IS NULL)";
1460 2131 }
1461 - } catch (Exception $e) {
1462 - //error_log('MXChat: Exception during WordPress DB deletion - ' . $e->getMessage());
1463 - }
2132 + $total_records = $wpdb->get_var($count_query);
2133 + $total_pages = ceil($total_records / $per_page);
1464 2134
1465 - if (!$deletion_successful) {
1466 - //error_log('MXChat: Complete deletion failure for post ID: ' . $post_id . ' URL: ' . $source_url);
1467 - }
1468 -}
2135 + // UPDATED 2.6.3: Get unique source_urls for pagination, then fetch all their rows
2136 + // Step 1: Get the source_urls for this page (distinct URLs ordered by latest timestamp)
2137 + if (!empty($where_values)) {
2138 + $urls_query = $wpdb->prepare(
2139 + "SELECT source_url, MAX(timestamp) as latest_ts FROM {$table_name} {$sql_where}
2140 + GROUP BY source_url ORDER BY latest_ts DESC LIMIT %d OFFSET %d",
2141 + array_merge($where_values, array($per_page, $offset))
2142 + );
2143 + } else {
2144 + $urls_query = $wpdb->prepare(
2145 + "SELECT source_url, MAX(timestamp) as latest_ts FROM {$table_name}
2146 + GROUP BY source_url ORDER BY latest_ts DESC LIMIT %d OFFSET %d",
2147 + $per_page, $offset
2148 + );
2149 + }
2150 + $page_urls = $wpdb->get_results($urls_query);
1469 2151
2152 + // Step 2: Fetch all rows for these source_urls
2153 + $prompts = array();
2154 + if (!empty($page_urls)) {
2155 + $url_list = array();
2156 + $has_empty_url = false;
2157 + foreach ($page_urls as $url_row) {
2158 + if (empty($url_row->source_url)) {
2159 + $has_empty_url = true;
2160 + } else {
2161 + $url_list[] = $url_row->source_url;
2162 + }
2163 + }
1470 2164
1471 -public function mxchat_handle_content_submission() {
1472 - // Check if the form was submitted and the user has permission.
1473 - if (!isset($_POST['submit_content']) || !current_user_can('manage_options')) {
1474 - return;
1475 - }
2165 + // Build query to fetch all rows for these URLs
2166 + $url_conditions = array();
2167 + $url_values = array();
1476 2168
1477 - // Verify the nonce.
1478 - $nonce = isset($_POST['mxchat_submit_content_nonce']) ? sanitize_text_field(wp_unslash($_POST['mxchat_submit_content_nonce'])) : '';
1479 - if (!wp_verify_nonce($nonce, 'mxchat_submit_content_action')) {
1480 - wp_die(esc_html__('Nonce verification failed.', 'mxchat'));
1481 - }
2169 + if (!empty($url_list)) {
2170 + $placeholders = implode(',', array_fill(0, count($url_list), '%s'));
2171 + $url_conditions[] = "source_url IN ($placeholders)";
2172 + $url_values = array_merge($url_values, $url_list);
2173 + }
1482 2174
1483 - // Sanitize the inputs.
1484 - $article_content = sanitize_textarea_field($_POST['article_content']);
1485 - $article_url = isset($_POST['article_url']) ? esc_url_raw($_POST['article_url']) : '';
2175 + if ($has_empty_url) {
2176 + $url_conditions[] = "(source_url = '' OR source_url IS NULL)";
2177 + }
1486 2178
1487 - // Generate the embedding vector.
1488 - $embedding_vector = $this->mxchat_generate_embedding($article_content);
1489 - if (!is_array($embedding_vector)) {
1490 - set_transient('mxchat_admin_notice_error',
1491 - esc_html__('Embedding generation failed. Please ensure your API key is correct and try again.', 'mxchat'),
1492 - 30
1493 - );
1494 - wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
1495 - exit;
1496 - }
2179 + if (!empty($url_conditions)) {
2180 + $url_where = "WHERE (" . implode(" OR ", $url_conditions) . ")";
1497 2181
1498 - // Store in the WordPress database.
1499 - global $wpdb;
1500 - $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
2182 + // Add original filters back
2183 + if (!empty($where_clauses)) {
2184 + $url_where .= " AND " . implode(" AND ", $where_clauses);
2185 + $url_values = array_merge($url_values, $where_values);
2186 + }
1501 2187
1502 - // Ensure the source_url column exists.
1503 - if ($wpdb->get_var($wpdb->prepare("SHOW COLUMNS FROM {$table_name} LIKE %s", 'source_url')) != 'source_url') {
1504 - $wpdb->query($wpdb->prepare("ALTER TABLE {$table_name} ADD source_url VARCHAR(255) DEFAULT ''"));
2188 + if (!empty($url_values)) {
2189 + $prompts_query = $wpdb->prepare(
2190 + "SELECT * FROM {$table_name} {$url_where} ORDER BY source_url, timestamp DESC",
2191 + $url_values
2192 + );
2193 + } else {
2194 + $prompts_query = "SELECT * FROM {$table_name} {$url_where} ORDER BY source_url, timestamp DESC";
2195 + }
2196 +
2197 + $prompts = $wpdb->get_results($prompts_query);
2198 + }
2199 + }
1505 2200 }
1506 2201
1507 - // Serialize and store the embedding vector.
1508 - $embedding_vector_serialized = serialize($embedding_vector);
1509 - $inserted = $wpdb->insert(
1510 - $table_name,
1511 - array(
1512 - 'article_content' => $article_content,
1513 - 'embedding_vector' => $embedding_vector_serialized,
1514 - 'source_url' => $article_url,
1515 - ),
1516 - array('%s', '%s', '%s')
1517 - );
2202 + // ================================
2203 + // PAGINATION GENERATION
2204 + // ================================
2205 +
2206 + // Generate pagination links
2207 + if ($total_pages > 1) {
2208 + // Build base URL with necessary parameters
2209 + $base_url = add_query_arg('paged', '%#%');
2210 +
2211 + // Preserve bot_id parameter if multi-bot is active
2212 + if ($multibot_active && !empty($current_bot_id) && $current_bot_id !== 'default') {
2213 + $base_url = add_query_arg('bot_id', $current_bot_id, $base_url);
2214 + }
1518 2215
1519 - if ($inserted === false) {
1520 - set_transient('mxchat_admin_notice_error',
1521 - esc_html__('Error inserting content into the database. Please try again.', 'mxchat'),
1522 - 30
1523 - );
2216 + // Preserve search query if present
2217 + if ($search_query) {
2218 + $base_url = add_query_arg('search', $search_query, $base_url);
2219 + }
2220 +
2221 + // UPDATED 2.5.6: Preserve content type filter if present
2222 + if ($content_type_filter) {
2223 + $base_url = add_query_arg('content_type', $content_type_filter, $base_url);
2224 + }
2225 +
2226 + $page_links = paginate_links(array(
2227 + 'base' => $base_url,
2228 + 'format' => '',
2229 + 'prev_text' => __('&laquo; Previous', 'mxchat'),
2230 + 'next_text' => __('Next &raquo;', 'mxchat'),
2231 + 'total' => $total_pages,
2232 + 'current' => $current_page,
2233 + 'type' => 'plain',
2234 + ));
1524 2235 } else {
1525 - set_transient('mxchat_admin_notice_success',
1526 - esc_html__('Content successfully submitted!', 'mxchat'),
1527 - 30
1528 - );
2236 + $page_links = '';
1529 2237 }
1530 2238
1531 - wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
1532 - exit;
1533 -}
2239 + // ================================
2240 + // PROCESSING STATUS RETRIEVAL
2241 + // ================================
2242 +
2243 + // Retrieve processing statuses
2244 + $pdf_url = get_transient('mxchat_last_pdf_url');
2245 + $sitemap_url = get_transient('mxchat_last_sitemap_url');
2246 + $pdf_status = $pdf_url ? $knowledge_manager->mxchat_get_pdf_processing_status($pdf_url) : false;
2247 + $sitemap_status = $sitemap_url ? $knowledge_manager->mxchat_get_sitemap_processing_status($sitemap_url) : false;
1534 2248
1535 -private function is_pdf_url($url, $response) {
1536 - $content_type = wp_remote_retrieve_header($response, 'content-type');
1537 - $file_extension = strtolower(pathinfo($url, PATHINFO_EXTENSION));
2249 + $is_processing = ($pdf_status && ($pdf_status['status'] === 'processing' || $pdf_status['status'] === 'error'))
2250 + || ($sitemap_status && ($sitemap_status['status'] === 'processing' || $sitemap_status['status'] === 'error'));
1538 2251
1539 - return strpos($content_type, 'pdf') !== false || $file_extension === 'pdf';
1540 -}
1541 -private function handle_pdf_for_knowledge_base($pdf_url, $response) {
1542 - if (!current_user_can('manage_options')) {
1543 - //error_log(esc_html__('Unauthorized PDF processing attempt', 'mxchat'));
1544 - return false;
1545 - }
2252 + //error_log('=== DEBUG: mxchat_create_prompts_page data preparation completed ===');
1546 2253
1547 - $pdf_url = esc_url_raw($pdf_url);
1548 - $upload_dir = wp_upload_dir();
2254 + ?>
1549 2255
1550 - if (isset($upload_dir['error']) && $upload_dir['error'] !== false) {
1551 - //error_log(sprintf(esc_html__('Upload directory error: %s', 'mxchat'), esc_html($upload_dir['error'])));
1552 - return false;
1553 - }
2256 + <div class="wrap mxchat-wrapper">
2257 + <!-- Hero Section -->
2258 + <div class="mxchat-hero">
2259 + <h1 class="mxchat-main-title">
2260 + <span class="mxchat-gradient-text">Knowledge Base</span> Manager
2261 + </h1>
2262 + <p class="mxchat-hero-subtitle">
2263 + <?php esc_html_e('Enhance your AI chatbot with custom knowledge. Import, manage, and organize your content to keep responses accurate and relevant.', 'mxchat'); ?>
2264 + </p>
2265 + </div>
1554 2266
1555 - $pdf_filename = sanitize_file_name('mxchat_kb_' . time() . '.pdf');
1556 - $pdf_path = trailingslashit($upload_dir['path']) . $pdf_filename;
2267 + <div class="mxchat-content">
2268 +
2269 + <!-- Multi-Bot Selector (only shows if multi-bot addon is active) -->
2270 + <?php if (class_exists('MxChat_Multi_Bot_Manager')) :
2271 + $multi_bot_manager = MxChat_Multi_Bot_Core_Manager::get_instance();
2272 + $available_bots = $multi_bot_manager->get_available_bots();
2273 +
2274 + // Get saved bot selection (user-specific first, then site-wide default)
2275 + $user_id = get_current_user_id();
2276 + $saved_bot_id = get_user_meta($user_id, 'mxchat_selected_knowledge_bot', true);
2277 + if (empty($saved_bot_id)) {
2278 + $saved_bot_id = get_option('mxchat_current_knowledge_bot', 'default');
2279 + }
2280 +
2281 + // Allow URL override but default to saved selection
2282 + $current_bot_id = isset($_GET['bot_id']) ? sanitize_key($_GET['bot_id']) : $saved_bot_id;
2283 + ?>
2284 + <div class="mxchat-card" style="margin-bottom: 20px;">
2285 + <div class="mxchat-bot-selector-wrapper" style="display: flex; align-items: center; gap: 15px;">
2286 + <label for="mxchat-bot-selector" style="font-weight: 600;">
2287 + <?php esc_html_e('Select Bot Database:', 'mxchat'); ?>
2288 + </label>
2289 + <select id="mxchat-bot-selector" class="mxchat-bot-selector" style="min-width: 200px;">
2290 + <?php foreach ($available_bots as $bot_id => $bot_name) : ?>
2291 + <option value="<?php echo esc_attr($bot_id); ?>" <?php selected($current_bot_id, $bot_id); ?>>
2292 + <?php echo esc_html($bot_name); ?>
2293 + </option>
2294 + <?php endforeach; ?>
2295 + </select>
2296 + <span class="mxchat-bot-info" style="color: #666; font-size: 13px;">
2297 + <?php esc_html_e('Content will be added to the selected bot\'s knowledge base', 'mxchat'); ?>
2298 + </span>
2299 + <span id="mxchat-bot-save-status" style="display: none; color: #00a32a; font-size: 13px;">
2300 + ✓ <?php esc_html_e('Saved', 'mxchat'); ?>
2301 + </span>
2302 + </div>
2303 + </div>
1557 2304
1558 - $response_body = wp_remote_retrieve_body($response);
1559 - if (empty($response_body)) {
1560 - //error_log(esc_html__('Empty PDF response body', 'mxchat'));
1561 - return false;
1562 - }
2305 + <?php
2306 + // Set a flag so we know multi-bot is active throughout the page
2307 + $multibot_active = true;
2308 + else:
2309 + $current_bot_id = 'default';
2310 + $multibot_active = false;
2311 + endif;
2312 + ?>
1563 2313
1564 - if (!wp_mkdir_p(dirname($pdf_path))) {
1565 - //error_log(sprintf(esc_html__('Failed to create directory for PDF: %s', 'mxchat'), esc_html($pdf_path)));
1566 - return false;
1567 - }
2314 + <!-- Tab Navigation -->
2315 + <div class="mxchat-kb-tabs-nav">
2316 + <button class="mxchat-kb-tab-button active" data-tab="import">
2317 + <?php esc_html_e('Knowledge Import', 'mxchat'); ?>
2318 + </button>
2319 + <button class="mxchat-kb-tab-button" data-tab="sync">
2320 + <?php esc_html_e('Settings', 'mxchat'); ?>
2321 + </button>
2322 + <button class="mxchat-kb-tab-button" data-tab="pinecone">
2323 + <?php esc_html_e('Pinecone Settings', 'mxchat'); ?>
2324 + </button>
2325 + </div>
1568 2326
1569 - try {
1570 - file_put_contents($pdf_path, $response_body);
2327 + <div class="mxchat-kb-tabs-content">
2328 + <div id="mxchat-kb-tab-import" class="mxchat-kb-tab-content active">
2329 + <!-- Import Options Card -->
2330 + <div class="mxchat-card">
1571 2331
1572 - if (!file_exists($pdf_path)) {
1573 - throw new Exception(__('Failed to save PDF file', 'mxchat'));
1574 - }
2332 + <h2><?php esc_html_e('Knowledge Import Settings', 'mxchat'); ?></h2>
1575 2333
1576 - $parser = new \Smalot\PdfParser\Parser();
1577 - $pdf = $parser->parseFile($pdf_path);
1578 - $total_pages = absint(count($pdf->getPages()));
2334 + <?php
2335 + // Check if the appropriate embedding API key exists
2336 + $embedding_model = isset($this->options['embedding_model']) ? esc_attr($this->options['embedding_model']) : 'text-embedding-ada-002';
2337 + $has_openai_key = !empty($this->options['api_key']);
2338 + $has_voyage_key = !empty($this->options['voyage_api_key']);
2339 + $has_gemini_key = !empty($this->options['gemini_api_key']);
1579 2340
1580 - if ($total_pages < 1) {
1581 - throw new Exception(__('Invalid PDF: no pages found', 'mxchat'));
1582 - }
2341 + // Determine if they have the needed API key for their selected embedding model
2342 + $has_required_key = false;
2343 + $required_key_type = '';
1583 2344
1584 - wp_schedule_single_event(time(), 'mxchat_process_pdf_pages', array(
1585 - 'pdf_path' => $pdf_path,
1586 - 'pdf_url' => $pdf_url,
1587 - 'total_pages' => $total_pages,
1588 - 'batch_size' => absint(15),
1589 - 'batch_pause' => absint(10)
1590 - ));
2345 + if (strpos($embedding_model, 'text-embedding-') !== false && $has_openai_key) {
2346 + $has_required_key = true;
2347 + $required_key_type = 'OpenAI';
2348 + } elseif (strpos($embedding_model, 'voyage-') !== false && $has_voyage_key) {
2349 + $has_required_key = true;
2350 + $required_key_type = 'Voyage AI';
2351 + } elseif (strpos($embedding_model, 'gemini-embedding-') !== false && $has_gemini_key) {
2352 + $has_required_key = true;
2353 + $required_key_type = 'Google Gemini';
2354 + } elseif (strpos($embedding_model, 'text-embedding-') !== false) {
2355 + $required_key_type = 'OpenAI';
2356 + } elseif (strpos($embedding_model, 'voyage-') !== false) {
2357 + $required_key_type = 'Voyage AI';
2358 + } elseif (strpos($embedding_model, 'gemini-embedding-') !== false) {
2359 + $required_key_type = 'Google Gemini';
2360 + }
2361 + ?>
1591 2362
1592 - $status_data = array(
1593 - 'total_pages' => $total_pages,
1594 - 'processed_pages' => 0,
1595 - 'status' => 'processing',
1596 - 'last_update' => time()
1597 - );
2363 + <div class="mxchat-knowledge-warning <?php echo $has_required_key ? 'success' : 'warning'; ?>">
2364 + <?php if ($has_required_key): ?>
2365 + <p><span class="dashicons dashicons-yes-alt"></span> <?php echo wp_kses_post(sprintf(__('We detected your %s API key. <strong>Remember to add credits to your %s account</strong> before using the knowledgebase.', 'mxchat'), $required_key_type, $required_key_type)); ?></p>
2366 + <?php else: ?>
2367 + <p><span class="dashicons dashicons-warning"></span> <strong><?php esc_html_e('Important:', 'mxchat'); ?></strong> <?php echo sprintf(esc_html__('Before importing knowledge, you must add a %s API key with sufficient credits in the Chatbot settings.', 'mxchat'), $required_key_type); ?> <a href="<?php echo admin_url('admin.php?page=mxchat-max'); ?>"><?php esc_html_e('Go to API Key Settings', 'mxchat'); ?></a></p>
2368 + <?php endif; ?>
2369 + </div>
1598 2370
1599 - set_transient(
1600 - sanitize_key('mxchat_pdf_status_' . md5($pdf_url)),
1601 - array_map('sanitize_text_field', $status_data),
1602 - DAY_IN_SECONDS
1603 - );
2371 + <!-- Import Options Section -->
2372 + <div class="mxchat-import-section">
2373 + <h3><?php esc_html_e('Import Options', 'mxchat'); ?></h3>
1604 2374
1605 - return __('scheduled', 'mxchat');
2375 + <!-- Import Options Grid -->
2376 + <div class="mxchat-import-options">
2377 + <!-- WordPress Import Option -->
2378 + <button type="button" id="mxchat-open-content-selector" class="mxchat-import-box mxchat-import-wordpress" data-option="wordpress">
2379 + <div class="mxchat-import-icon">
2380 + <span class="dashicons dashicons-wordpress"></span>
2381 + </div>
2382 + <div class="mxchat-import-content">
2383 + <h4><?php esc_html_e('WordPress Content', 'mxchat'); ?></h4>
2384 + <p><?php esc_html_e('Import specific posts and pages to your knowledge base.', 'mxchat'); ?></p>
2385 + </div>
2386 + <div class="mxchat-recommended-tag"><?php esc_html_e('Recommended', 'mxchat'); ?></div>
2387 + </button>
1606 2388
1607 - } catch (Exception $e) {
1608 - //error_log(sprintf(esc_html__('Error preparing PDF for processing: %s', 'mxchat'), esc_html($e->getMessage())));
1609 - if (file_exists($pdf_path)) {
1610 - wp_delete_file($pdf_path);
1611 - }
1612 - return false;
1613 - }
1614 -}
1615 -public static function process_pdf_pages_cron($pdf_path, $pdf_url, $total_pages, $batch_size, $batch_pause) {
1616 - // Validate inputs
1617 - $pdf_path = sanitize_text_field($pdf_path);
1618 - $pdf_url = esc_url_raw($pdf_url);
1619 - $total_pages = absint($total_pages);
1620 - $batch_size = absint($batch_size);
1621 - $batch_pause = absint($batch_pause);
2389 + <!-- Sitemap Import Option -->
2390 + <button type="button" class="mxchat-import-box" data-option="sitemap" data-placeholder="<?php esc_attr_e('Enter sitemap URL here', 'mxchat'); ?>" data-type="sitemap">
2391 + <div class="mxchat-import-icon">
2392 + <span class="dashicons dashicons-admin-site-alt"></span>
2393 + </div>
2394 + <div class="mxchat-import-content">
2395 + <h4><?php esc_html_e('Sitemap Import', 'mxchat'); ?></h4>
2396 + <p><?php esc_html_e('Use a content-specific sub-sitemap, not the sitemap index.', 'mxchat'); ?></p>
2397 + </div>
2398 + </button>
1622 2399
1623 - try {
1624 - if (!file_exists($pdf_path)) {
1625 - throw new Exception(sprintf('PDF file not found at path: %s', esc_html($pdf_path)));
1626 - }
2400 + <!-- Direct URL Import Option -->
2401 + <button type="button" class="mxchat-import-box" data-option="url" data-placeholder="<?php esc_attr_e('Enter webpage URL here', 'mxchat'); ?>" data-type="url">
2402 + <div class="mxchat-import-icon">
2403 + <span class="dashicons dashicons-admin-links"></span>
2404 + </div>
2405 + <div class="mxchat-import-content">
2406 + <h4><?php esc_html_e('Direct URL', 'mxchat'); ?></h4>
2407 + <p><?php esc_html_e('Import content from any webpage.', 'mxchat'); ?></p>
2408 + </div>
2409 + </button>
1627 2410
1628 - $parser = new \Smalot\PdfParser\Parser();
1629 - $pdf = $parser->parseFile($pdf_path);
1630 - $pages = $pdf->getPages();
2411 + <!-- Direct Content Import Option -->
2412 + <button type="button" class="mxchat-import-box" data-option="content" data-type="content">
2413 + <div class="mxchat-import-icon">
2414 + <span class="dashicons dashicons-editor-paste-text"></span>
2415 + </div>
2416 + <div class="mxchat-import-content">
2417 + <h4><?php esc_html_e('Direct Content', 'mxchat'); ?></h4>
2418 + <p><?php esc_html_e('Submit content to be vectorized.', 'mxchat'); ?></p>
2419 + </div>
2420 + </button>
1631 2421
1632 - // Get current progress
1633 - $status_key = sanitize_key('mxchat_pdf_status_' . md5($pdf_url));
1634 - $status = get_transient($status_key);
2422 + <!-- PDF Import Option -->
2423 + <button type="button" class="mxchat-import-box" data-option="pdf" data-placeholder="<?php esc_attr_e('Enter PDF URL here', 'mxchat'); ?>" data-type="pdf">
2424 + <div class="mxchat-import-icon">
2425 + <span class="dashicons dashicons-media-document"></span>
2426 + </div>
2427 + <div class="mxchat-import-content">
2428 + <h4><?php esc_html_e('PDF Import', 'mxchat'); ?></h4>
2429 + <p><?php esc_html_e('Import knowledge from PDF documents.', 'mxchat'); ?></p>
2430 + </div>
2431 + </button>
2432 + </div>
1635 2433
1636 - if (!$status || !is_array($status)) {
1637 - throw new Exception('Invalid status data retrieved from transient');
1638 - }
2434 + <!-- Input Areas for URL and Content -->
2435 + <div class="mxchat-import-input-area" id="mxchat-url-input-area" style="display: none;">
2436 + <?php if (!$is_processing) : ?>
2437 + <form id="mxchat-url-form" method="post" action="<?php echo esc_url(admin_url('admin-post.php?action=mxchat_submit_sitemap')); ?>">
2438 + <?php wp_nonce_field('mxchat_submit_sitemap_action', 'mxchat_submit_sitemap_nonce'); ?>
2439 + <input type="hidden" name="import_type" id="import_type" value="url">
2440 + <?php if ($multibot_active && $current_bot_id !== 'default') : ?>
2441 + <input type="hidden" name="bot_id" value="<?php echo esc_attr($current_bot_id); ?>">
2442 + <?php endif; ?>
2443 + <div class="mxchat-url-input-group">
2444 + <input type="url"
2445 + name="sitemap_url"
2446 + id="sitemap_url"
2447 + placeholder="<?php esc_attr_e('Enter URL here', 'mxchat'); ?>"
2448 + required />
2449 + <button type="submit"
2450 + name="submit_sitemap"
2451 + class="mxchat-button-primary">
2452 + <?php esc_html_e('Import', 'mxchat'); ?>
2453 + </button>
2454 + </div>
2455 + <p class="mxchat-url-description" id="url-description-text"></p>
2456 + </form>
2457 + <?php endif; ?>
2458 + </div>
1639 2459
1640 - $start_page = absint($status['processed_pages']);
1641 - $end_page = min($start_page + $batch_size, $total_pages);
2460 + <div class="mxchat-import-input-area" id="mxchat-content-input-area" style="display: none;">
2461 + <?php if (!$is_processing) : ?>
2462 + <form id="mxchat-content-form" method="post" action="<?php echo esc_url(admin_url('admin-post.php?action=mxchat_submit_content')); ?>">
2463 + <?php wp_nonce_field('mxchat_submit_content_action', 'mxchat_submit_content_nonce'); ?>
2464 + <?php if ($multibot_active && $current_bot_id !== 'default') : ?>
2465 + <input type="hidden" name="bot_id" value="<?php echo esc_attr($current_bot_id); ?>">
2466 + <?php endif; ?>
2467 + <div class="mxchat-form-group">
2468 + <textarea
2469 + name="article_content"
2470 + id="article_content"
2471 + placeholder="<?php esc_attr_e('Enter your content here...', 'mxchat'); ?>"
2472 + required
2473 + rows="6"
2474 + ></textarea>
2475 + </div>
2476 + <div class="mxchat-form-group">
2477 + <input type="url"
2478 + name="article_url"
2479 + id="article_url"
2480 + placeholder="<?php esc_attr_e('Enter source URL (Optional)', 'mxchat'); ?>">
2481 + <div class="mxchat-url-helper" style="margin-top: 8px; display: flex; align-items: center; gap: 8px;">
2482 + <label style="display: flex; align-items: center; gap: 6px; font-size: 13px; color: #666; cursor: pointer;">
2483 + <input type="checkbox" id="mxchat-unique-url-toggle" style="margin: 0;">
2484 + <?php esc_html_e('Generate unique URL for duplicate content', 'mxchat'); ?>
2485 + </label>
2486 + <button type="button"
2487 + id="mxchat-generate-unique-url"
2488 + class="mxchat-button-secondary"
2489 + style="padding: 4px 12px; font-size: 12px; display: none;">
2490 + <span class="dashicons dashicons-randomize" style="font-size: 14px; margin-top: 2px;"></span>
2491 + <?php esc_html_e('Generate Unique', 'mxchat'); ?>
2492 + </button>
2493 + </div>
2494 + <p class="description" style="margin-top: 8px; font-size: 12px; color: #666;">
2495 + <?php esc_html_e('Enable this option to submit multiple entries with the same base URL. A unique reference will be appended (e.g., ?ref=1731234567890).', 'mxchat'); ?>
2496 + </p>
2497 + </div>
2498 + <button type="submit"
2499 + name="submit_content"
2500 + class="mxchat-button-primary">
2501 + <?php esc_html_e('Import Content', 'mxchat'); ?>
2502 + </button>
2503 + </form>
2504 + <?php endif; ?>
2505 + </div>
2506 + </div>
1642 2507
1643 - $instance = new self(); // Create an instance of the class
2508 + <!-- PDF Processing Status - ONLY PROCESSING -->
2509 + <?php if ($pdf_status && $pdf_status['status'] === 'processing') : ?>
2510 + <div class="mxchat-status-card" data-card-type="pdf">
2511 + <div class="mxchat-status-header">
2512 + <h4><?php esc_html_e('PDF Processing Status', 'mxchat'); ?></h4>
1644 2513
1645 - for ($i = $start_page; $i < $end_page; $i++) {
1646 - $text = $pages[$i]->getText();
1647 - $sanitized_content = $instance->mxchat_sanitize_content_for_api($text); // Call via instance
2514 + <!-- Processing controls -->
2515 + <div class="mxchat-processing-controls">
2516 + <form method="post" class="mxchat-stop-form"
2517 + action="<?php echo esc_url(admin_url('admin-post.php?action=mxchat_stop_processing')); ?>">
2518 + <?php wp_nonce_field('mxchat_stop_processing_action', 'mxchat_stop_processing_nonce'); ?>
2519 + <button type="submit" name="stop_processing" class="mxchat-button-secondary">
2520 + <?php esc_html_e('Stop Processing', 'mxchat'); ?>
2521 + </button>
2522 + </form>
1648 2523
1649 - if (!empty($sanitized_content)) {
1650 - $embedding_vector = $instance->mxchat_generate_embedding($sanitized_content); // Call via instance
1651 - if (is_array($embedding_vector)) {
1652 - $metadata = array(
1653 - 'document_type' => 'pdf',
1654 - 'total_pages' => $total_pages,
1655 - 'current_page' => $i + 1,
1656 - 'prev_page' => $i > 0 ? $i : null,
1657 - 'next_page' => $i < ($total_pages - 1) ? $i + 2 : null,
1658 - 'source_url' => $pdf_url
1659 - );
2524 + <button type="button" class="mxchat-manual-batch-btn"
2525 + data-process-type="pdf"
2526 + data-url="<?php echo esc_attr(get_transient('mxchat_last_pdf_url')); ?>">
2527 + <?php esc_html_e('Process Batch', 'mxchat'); ?>
2528 + </button>
2529 + </div>
2530 + </div>
1660 2531
1661 - $content_with_metadata = wp_json_encode($metadata) . "\n---\n" . $sanitized_content;
1662 - $page_url = esc_url($pdf_url . "#page=" . ($i + 1));
2532 + <div class="mxchat-progress-bar">
2533 + <div class="mxchat-progress-fill" style="width: <?php echo esc_attr($pdf_status['percentage']); ?>%"></div>
2534 + </div>
1663 2535
1664 - $options = get_option('mxchat_options');
1665 - MxChat_Utils::submit_content_to_db($content_with_metadata, $page_url, $options['api_key']);
1666 - }
1667 - }
2536 + <div class="mxchat-status-details">
2537 + <p><?php printf(
2538 + esc_html__('Progress: %1$d of %2$d pages (%3$d%%)', 'mxchat'),
2539 + absint($pdf_status['processed_pages']),
2540 + absint($pdf_status['total_pages']),
2541 + absint($pdf_status['percentage'])
2542 + ); ?></p>
1668 2543
1669 - // Update progress with sanitized data
1670 - $status['processed_pages'] = absint($i + 1);
1671 - $status['last_update'] = time();
1672 - set_transient($status_key, array_map('sanitize_text_field', $status), DAY_IN_SECONDS);
1673 - }
2544 + <?php if (!empty($pdf_status['failed_pages']) && $pdf_status['failed_pages'] > 0) : ?>
2545 + <p><strong><?php esc_html_e('Failed pages:', 'mxchat'); ?></strong> <?php echo esc_html($pdf_status['failed_pages']); ?></p>
2546 + <?php endif; ?>
1674 2547
1675 - // Schedule next batch if needed
1676 - if ($end_page < $total_pages) {
1677 - wp_schedule_single_event(time() + $batch_pause, 'mxchat_process_pdf_pages', array(
1678 - 'pdf_path' => $pdf_path,
1679 - 'pdf_url' => $pdf_url,
1680 - 'total_pages' => $total_pages,
1681 - 'batch_size' => $batch_size,
1682 - 'batch_pause' => $batch_pause
1683 - ));
1684 - } else {
1685 - // Processing complete
1686 - $status['status'] = 'complete';
1687 - set_transient($status_key, array_map('sanitize_text_field', $status), DAY_IN_SECONDS);
1688 - if (file_exists($pdf_path)) {
1689 - wp_delete_file($pdf_path);
1690 - }
1691 - }
2548 + <p><strong><?php esc_html_e('Status:', 'mxchat'); ?></strong> <?php echo esc_html(ucfirst($pdf_status['status'])); ?></p>
2549 + <p><strong><?php esc_html_e('Last update:', 'mxchat'); ?></strong> <?php echo esc_html($pdf_status['last_update']); ?></p>
2550 + </div>
2551 + </div>
2552 + <?php endif; ?>
1692 2553
1693 - } catch (\Exception $e) {
1694 - $status['status'] = 'error';
1695 - $status['error'] = sanitize_text_field($e->getMessage());
1696 - set_transient($status_key, array_map('sanitize_text_field', $status), DAY_IN_SECONDS);
1697 - if (file_exists($pdf_path)) {
1698 - wp_delete_file($pdf_path);
1699 - }
1700 - }
1701 -}
1702 -public function get_pdf_processing_status($pdf_url) {
1703 - $pdf_url = esc_url_raw($pdf_url);
1704 - $status = get_transient(sanitize_key('mxchat_pdf_status_' . md5($pdf_url)));
2554 + <!-- Sitemap Processing Status - ONLY PROCESSING -->
2555 + <?php if ($sitemap_status && $sitemap_status['status'] === 'processing') : ?>
2556 + <div class="mxchat-status-card" data-card-type="sitemap">
2557 + <div class="mxchat-status-header">
2558 + <h4><?php esc_html_e('Sitemap Processing Status', 'mxchat'); ?></h4>
1705 2559
1706 - if (!$status || !is_array($status)) {
1707 - return false;
1708 - }
2560 + <!-- Processing controls -->
2561 + <div class="mxchat-processing-controls">
2562 + <form method="post" class="mxchat-stop-form"
2563 + action="<?php echo esc_url(admin_url('admin-post.php?action=mxchat_stop_processing')); ?>">
2564 + <?php wp_nonce_field('mxchat_stop_processing_action', 'mxchat_stop_processing_nonce'); ?>
2565 + <button type="submit" name="stop_processing" class="mxchat-button-secondary">
2566 + <?php esc_html_e('Stop Processing', 'mxchat'); ?>
2567 + </button>
2568 + </form>
1709 2569
1710 - return array(
1711 - 'total_pages' => absint($status['total_pages']),
1712 - 'processed_pages' => absint($status['processed_pages']),
1713 - 'percentage' => ($status['total_pages'] > 0)
1714 - ? round((absint($status['processed_pages']) / absint($status['total_pages'])) * 100)
1715 - : 0,
1716 - 'status' => sanitize_text_field($status['status']),
1717 - 'last_update' => human_time_diff(absint($status['last_update']), time()) . ' ' . esc_html__('ago', 'mxchat')
1718 - );
1719 -}
1720 -public function mxchat_handle_sitemap_submission() {
1721 - // Check if the form was submitted and verify permissions
1722 - if (!isset($_POST['submit_sitemap']) || !current_user_can('manage_options')) {
1723 - wp_die(esc_html__('Unauthorized access', 'mxchat'));
1724 - }
2570 + <button type="button" class="mxchat-manual-batch-btn"
2571 + data-process-type="sitemap"
2572 + data-url="<?php echo esc_attr(get_transient('mxchat_last_sitemap_url')); ?>">
2573 + <?php esc_html_e('Process Batch', 'mxchat'); ?>
2574 + </button>
2575 + </div>
2576 + </div>
1725 2577
1726 - // Verify nonce
1727 - check_admin_referer('mxchat_submit_sitemap_action', 'mxchat_submit_sitemap_nonce');
2578 + <div class="mxchat-progress-bar">
2579 + <div class="mxchat-progress-fill" style="width: <?php echo esc_attr($sitemap_status['percentage']); ?>%"></div>
2580 + </div>
1728 2581
1729 - // Validate URL
1730 - if (!isset($_POST['sitemap_url']) || empty($_POST['sitemap_url'])) {
1731 - set_transient('mxchat_admin_notice_error',
1732 - esc_html__('Please provide a valid URL.', 'mxchat'),
1733 - 30
1734 - );
1735 - wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
1736 - exit;
1737 - }
2582 + <div class="mxchat-status-details">
2583 + <p><?php printf(
2584 + esc_html__('Progress: %1$d of %2$d URLs (%3$d%%)', 'mxchat'),
2585 + absint($sitemap_status['processed_urls']),
2586 + absint($sitemap_status['total_urls']),
2587 + absint($sitemap_status['percentage'])
2588 + ); ?></p>
1738 2589
1739 - $submitted_url = esc_url_raw($_POST['sitemap_url']);
1740 - $response = wp_remote_get($submitted_url, array('timeout' => 30));
2590 + <?php if (!empty($sitemap_status['failed_urls']) && $sitemap_status['failed_urls'] > 0) : ?>
2591 + <p><strong><?php esc_html_e('Failed URLs:', 'mxchat'); ?></strong> <?php echo esc_html($sitemap_status['failed_urls']); ?></p>
2592 + <?php endif; ?>
1741 2593
1742 - if (is_wp_error($response) || wp_remote_retrieve_response_code($response) !== 200) {
1743 - $error_message = is_wp_error($response) ? $response->get_error_message() : __('Failed to fetch URL', 'mxchat');
1744 - set_transient('mxchat_admin_notice_error',
1745 - sprintf(
1746 - esc_html__('Failed to fetch the URL: %s', 'mxchat'),
1747 - esc_html($error_message)
1748 - ),
1749 - 30
1750 - );
1751 - wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
1752 - exit;
1753 - }
2594 + <p><strong><?php esc_html_e('Status:', 'mxchat'); ?></strong> <?php echo esc_html(ucfirst($sitemap_status['status'])); ?></p>
2595 + <p><strong><?php esc_html_e('Last update:', 'mxchat'); ?></strong> <?php echo esc_html($sitemap_status['last_update']); ?></p>
1754 2596
1755 - $content_type = wp_remote_retrieve_header($response, 'content-type');
1756 - $body_content = wp_remote_retrieve_body($response);
2597 + <?php if (!empty($sitemap_status['error']) || !empty($sitemap_status['last_error'])) : ?>
2598 + <div class="mxchat-error-notice">
2599 + <?php if (!empty($sitemap_status['error'])) : ?>
2600 + <p class="error"><?php echo esc_html($sitemap_status['error']); ?></p>
2601 + <?php endif; ?>
2602 + <?php if (!empty($sitemap_status['last_error'])) : ?>
2603 + <p class="last-error"><?php echo esc_html__('Last error:', 'mxchat') . ' ' . esc_html($sitemap_status['last_error']); ?></p>
2604 + <?php endif; ?>
2605 + </div>
2606 + <?php endif; ?>
2607 + </div>
2608 + </div>
2609 + <?php endif; ?>
1757 2610
1758 - if (empty($body_content)) {
1759 - set_transient('mxchat_admin_notice_error',
1760 - esc_html__('Empty response received from URL.', 'mxchat'),
1761 - 30
1762 - );
1763 - wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
1764 - exit;
1765 - }
2611 + <!-- Single URL Submission Status -->
2612 + <?php
2613 + // Get single URL status
2614 + $single_url_status = $this->knowledge_manager->mxchat_get_single_url_status();
2615 + $is_active_processing =
2616 + ($sitemap_status && $sitemap_status['status'] === 'processing') ||
2617 + ($pdf_status && $pdf_status['status'] === 'processing');
2618 + ?>
1766 2619
1767 - // Handle PDF URL
1768 - if ($this->is_pdf_url($submitted_url, $response)) {
1769 - $result = $this->handle_pdf_for_knowledge_base($submitted_url, $response);
2620 + <div id="mxchat-single-url-status-container" <?php echo $is_active_processing ? 'style="display:none;"' : ''; ?>>
2621 + <?php if ($single_url_status && !$is_active_processing) : ?>
2622 + <div class="mxchat-status-card">
2623 + <div class="mxchat-status-header">
2624 + <h4><?php esc_html_e('Last URL Submission', 'mxchat'); ?></h4>
2625 + <?php if ($single_url_status['status'] === 'failed') : ?>
2626 + <span class="mxchat-status-badge mxchat-status-failed"><?php esc_html_e('Failed', 'mxchat'); ?></span>
2627 + <?php else : ?>
2628 + <span class="mxchat-status-badge mxchat-status-success"><?php esc_html_e('Success', 'mxchat'); ?></span>
2629 + <?php endif; ?>
2630 + </div>
2631 + <div class="mxchat-status-details">
2632 + <p><strong><?php esc_html_e('URL:', 'mxchat'); ?></strong>
2633 + <a href="<?php echo esc_url($single_url_status['url']); ?>" target="_blank">
2634 + <?php echo esc_html(strlen($single_url_status['url']) > 60 ? substr($single_url_status['url'], 0, 57) . '...' : $single_url_status['url']); ?>
2635 + </a>
2636 + </p>
2637 + <p><strong><?php esc_html_e('Submitted:', 'mxchat'); ?></strong> <?php echo esc_html($single_url_status['human_time']); ?></p>
1770 2638
1771 - if ($result === 'scheduled') {
1772 - set_transient(
1773 - 'mxchat_last_pdf_url',
1774 - sanitize_text_field($submitted_url),
1775 - DAY_IN_SECONDS
1776 - );
1777 - set_transient('mxchat_admin_notice_info',
1778 - esc_html__('PDF processing has started in the background. You can check the progress in the Knowledge Base section.', 'mxchat'),
1779 - 30
1780 - );
1781 - } else {
1782 - set_transient('mxchat_admin_notice_error',
1783 - esc_html__('Failed to start PDF processing. Please try again.', 'mxchat'),
1784 - 30
1785 - );
1786 - }
2639 + <?php if ($single_url_status['status'] === 'failed' && !empty($single_url_status['error'])) : ?>
2640 + <div class="mxchat-error-notice">
2641 + <p class="error"><?php echo esc_html($single_url_status['error']); ?></p>
2642 + </div>
2643 + <?php endif; ?>
1787 2644
1788 - wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
1789 - exit;
1790 - }
2645 + <?php if ($single_url_status['status'] === 'complete') : ?>
2646 + <p><strong><?php esc_html_e('Content Length:', 'mxchat'); ?></strong> <?php echo esc_html($single_url_status['content_length']); ?> <?php esc_html_e('characters', 'mxchat'); ?></p>
2647 + <p><strong><?php esc_html_e('Embedding Dimensions:', 'mxchat'); ?></strong> <?php echo esc_html($single_url_status['embedding_dimensions']); ?></p>
2648 + <?php endif; ?>
2649 + </div>
2650 + </div>
2651 + <?php endif; ?>
2652 + </div>
1791 2653
1792 - // Handle Sitemap XML
1793 - if (strpos($content_type, 'xml') !== false || strpos($body_content, '<urlset') !== false) {
1794 - libxml_use_internal_errors(true);
1795 - $xml = simplexml_load_string($body_content);
1796 - $xml_errors = libxml_get_errors();
1797 - libxml_clear_errors();
2654 + <!-- Completed Status Cards - Handles completed PDF/Sitemap processing -->
2655 + <?php
2656 + echo $knowledge_manager->mxchat_render_completed_status_cards();
2657 + ?>
1798 2658
1799 - if ($xml === false || !empty($xml_errors)) {
1800 - set_transient('mxchat_admin_notice_error',
1801 - esc_html__('Invalid sitemap XML. Please provide a valid sitemap.', 'mxchat'),
1802 - 30
1803 - );
1804 - wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
1805 - exit;
1806 - }
2659 + </div>
1807 2660
1808 - $result = $this->handle_sitemap_for_knowledge_base($xml, $submitted_url);
2661 + <!-- Knowledge Base Table Card -->
2662 + <div class="mxchat-card">
2663 + <div class="mxchat-card-header">
2664 + <h2>
2665 + <?php esc_html_e('Knowledge Base', 'mxchat'); ?>
2666 + <span class="mxchat-record-count">
2667 + <?php if ($showing_recent_only && $total_in_database > 1000): ?>
2668 + (<?php echo esc_html($total_records); ?> of <?php echo esc_html(number_format($total_in_database)); ?> total - recent entries only)
2669 + <?php else: ?>
2670 + (<?php echo esc_html($total_records); ?>)
2671 + <?php endif; ?>
2672 + </span>
2673 + <!-- Data source badge -->
2674 + <?php if ($use_pinecone) : ?>
2675 + <span class="mxchat-data-source-badge pinecone" style="display: inline-flex; align-items: center; gap: 4px; padding: 4px 8px; border-radius: 12px; font-size: 12px; font-weight: 500; margin-left: 8px; background: #e3f2fd; color: #1976d2;">
2676 + <span class="dashicons dashicons-cloud"></span>
2677 + <?php esc_html_e('Pinecone', 'mxchat'); ?>
2678 + </span>
2679 + <?php else : ?>
2680 + <span class="mxchat-data-source-badge wordpress" style="display: inline-flex; align-items: center; gap: 4px; padding: 4px 8px; border-radius: 12px; font-size: 12px; font-weight: 500; margin-left: 8px; background: #f3e5f5; color: #7b1fa2;">
2681 + <span class="dashicons dashicons-database-view"></span>
2682 + <?php esc_html_e('WordPress DB', 'mxchat'); ?>
2683 + </span>
2684 + <?php endif; ?>
2685 + </h2>
2686 + <div class="mxchat-header-actions">
2687 + <!-- Search -->
2688 + <form method="get" id="knowledge-search" class="mxchat-search-form">
2689 + <?php wp_nonce_field('mxchat_prompts_search_nonce'); ?>
2690 + <input type="hidden" name="page" value="mxchat-prompts" />
2691 + <?php if ($multibot_active && !empty($current_bot_id) && $current_bot_id !== 'default') : ?>
2692 + <input type="hidden" name="bot_id" value="<?php echo esc_attr($current_bot_id); ?>" />
2693 + <?php endif; ?>
2694 + <div class="mxchat-search-group">
2695 + <span class="dashicons dashicons-search"></span>
2696 + <input type="text"
2697 + name="search"
2698 + placeholder="<?php esc_attr_e('Search Knowledge', 'mxchat'); ?>"
2699 + value="<?php echo esc_attr($search_query); ?>" />
2700 + </div>
1809 2701
1810 - if ($result === 'scheduled') {
1811 - set_transient(
1812 - 'mxchat_last_sitemap_url',
1813 - sanitize_text_field($submitted_url),
1814 - DAY_IN_SECONDS
1815 - );
1816 - set_transient('mxchat_admin_notice_info',
1817 - esc_html__('Sitemap processing has started in the background. You can check the progress in the Knowledge Base section.', 'mxchat'),
1818 - 30
1819 - );
1820 - } else {
1821 - set_transient('mxchat_admin_notice_error',
1822 - esc_html__('Failed to start sitemap processing. Please try again.', 'mxchat'),
1823 - 30
1824 - );
1825 - }
2702 + <!-- ADDED 2.5.6: Content Type Filter -->
2703 + <div class="mxchat-filter-group">
2704 + <select name="content_type" id="mxchat-content-type-filter" onchange="this.form.submit()">
2705 + <option value=""><?php esc_html_e('All Content Types', 'mxchat'); ?></option>
2706 + <option value="post" <?php selected($content_type_filter, 'post'); ?>><?php esc_html_e('Posts', 'mxchat'); ?></option>
2707 + <option value="page" <?php selected($content_type_filter, 'page'); ?>><?php esc_html_e('Pages', 'mxchat'); ?></option>
2708 + <option value="product" <?php selected($content_type_filter, 'product'); ?>><?php esc_html_e('Products', 'mxchat'); ?></option>
2709 + <option value="pdf" <?php selected($content_type_filter, 'pdf'); ?>><?php esc_html_e('PDFs', 'mxchat'); ?></option>
2710 + <option value="url" <?php selected($content_type_filter, 'url'); ?>><?php esc_html_e('URLs', 'mxchat'); ?></option>
2711 + <option value="content" <?php selected($content_type_filter, 'content'); ?>><?php esc_html_e('Manual Content', 'mxchat'); ?></option>
2712 + </select>
2713 + </div>
2714 + </form>
1826 2715
1827 - wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
1828 - exit;
1829 - }
2716 + <form method="post"
2717 + action="<?php echo esc_url(admin_url('admin-post.php?action=mxchat_delete_all_prompts')); ?>"
2718 + class="mxchat-delete-form"
2719 + onsubmit="return confirm('<?php esc_attr_e('Are you sure you want to delete all knowledge? This action cannot be undone.', 'mxchat'); ?>');">
2720 + <?php wp_nonce_field('mxchat_delete_all_prompts_action', 'mxchat_delete_all_prompts_nonce'); ?>
2721 + <input type="hidden" name="data_source" value="<?php echo esc_attr($data_source); ?>" />
2722 + <!-- Always include bot_id, even if it's 'default' -->
2723 + <input type="hidden" name="bot_id" value="<?php echo esc_attr($current_bot_id); ?>" />
2724 + <button type="submit" name="delete_all_prompts" class="mxchat-button-danger">
2725 + <span class="dashicons dashicons-trash"></span>
2726 + <?php esc_html_e('Delete All', 'mxchat'); ?>
2727 + </button>
2728 + </form>
2729 + </div>
2730 + </div>
1830 2731
1831 - // Handle Regular URL
1832 - $page_content = $this->mxchat_extract_main_content($body_content);
1833 - $sanitized_content = $this->mxchat_sanitize_content_for_api($page_content);
2732 + <!-- Large Database Info Banner -->
2733 + <?php if ($showing_recent_only && $total_in_database > 500): ?>
2734 + <div class="mxchat-info-banner recent-only" style="display: flex; align-items: center; gap: 8px; padding: 12px 16px; margin-bottom: 20px; border-radius: 6px; font-size: 14px; background: #e3f2fd; border-left: 4px solid #2196f3; color: #1565c0;">
2735 + <span class="dashicons dashicons-info"></span>
2736 + <span>
2737 + <?php printf(
2738 + esc_html__('Browsing shows %s of %s total entries. Use the search box to find any entry in your complete database - search works across all entries!', 'mxchat'),
2739 + number_format($total_records),
2740 + number_format($total_in_database)
2741 + ); ?>
2742 + </span>
2743 + </div>
2744 + <?php endif; ?>
1834 2745
1835 - if (empty($sanitized_content)) {
1836 - set_transient('mxchat_admin_notice_error',
1837 - esc_html__('No valid content found on the provided URL.', 'mxchat'),
1838 - 30
1839 - );
1840 - wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
1841 - exit;
1842 - }
2746 + <!-- Data Source Info Banner -->
2747 + <?php if ($use_pinecone) : ?>
2748 + <div class="mxchat-info-banner pinecone" style="display: flex; align-items: center; gap: 8px; padding: 12px 16px; margin-bottom: 20px; border-radius: 6px; font-size: 14px; background: #e8f5e8; border-left: 4px solid #4caf50; color: #2e7d2e;">
2749 + <span class="dashicons dashicons-cloud"></span>
2750 + <span><?php esc_html_e('Refresh page to see new content. New entires or deletions can take up to 60 seconds to reflect! Refreshing too early means content won\'t show and you\'ll need to refresh again.', 'mxchat'); ?></span>
2751 + </div>
2752 + <?php else : ?>
2753 + <div class="mxchat-info-banner wordpress" style="display: flex; align-items: center; gap: 8px; padding: 12px 16px; margin-bottom: 20px; border-radius: 6px; font-size: 14px; background: #fff3e0; border-left: 4px solid #ff9800; color: #e65100;">
2754 + <span class="dashicons dashicons-database-view"></span>
2755 + <span><?php esc_html_e('Refresh page to see new content, but wait 5-10 seconds first! Refreshing too early means content won\'t show and you\'ll need to refresh again.', 'mxchat'); ?></span>
2756 + </div>
2757 + <?php endif; ?>
1843 2758
1844 - $embedding_vector = $this->mxchat_generate_embedding($sanitized_content);
1845 - if (is_array($embedding_vector)) {
1846 - MxChat_Utils::submit_content_to_db(
1847 - $sanitized_content,
1848 - $submitted_url,
1849 - $this->options['api_key']
1850 - );
1851 - set_transient('mxchat_admin_notice_success',
1852 - esc_html__('URL content successfully submitted!', 'mxchat'),
1853 - 30
1854 - );
1855 - } else {
1856 - set_transient('mxchat_admin_notice_error',
1857 - esc_html__('Failed to generate embedding for the URL content. Please check your API key and try again.', 'mxchat'),
1858 - 30
1859 - );
1860 - }
2759 + <!-- Table -->
2760 + <?php
2761 + // Group prompts by source_url for chunked content display
2762 + $grouped_prompts = array();
2763 + $ungrouped_prompts = array();
1861 2764
1862 - wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
1863 - exit;
1864 -}
1865 -private function handle_sitemap_for_knowledge_base($xml, $sitemap_url) {
1866 - if (!current_user_can('manage_options')) {
1867 - //error_log(esc_html__('Unauthorized sitemap processing attempt', 'mxchat'));
1868 - return false;
1869 - }
2765 + if ($prompts) {
2766 + foreach ($prompts as $prompt) {
2767 + $source_url = $prompt->source_url ?? '';
1870 2768
1871 - try {
1872 - $sitemap_url = esc_url_raw($sitemap_url);
2769 + // Check if chunk metadata exists on the record itself (Pinecone)
2770 + if (isset($prompt->chunk_index) && $prompt->chunk_index !== null) {
2771 + // Pinecone: metadata is already on the record object
2772 + $prompt->chunk_metadata = array(
2773 + 'chunk_index' => intval($prompt->chunk_index),
2774 + 'total_chunks' => isset($prompt->total_chunks) ? intval($prompt->total_chunks) : null,
2775 + 'is_chunked' => isset($prompt->is_chunked) ? (bool) $prompt->is_chunked : true
2776 + );
2777 + $prompt->display_content = $prompt->article_content; // Pinecone content is already clean
2778 + } else {
2779 + // WordPress: Parse chunk metadata from JSON prefix in article_content
2780 + $chunk_meta = MxChat_Chunker::parse_stored_chunk($prompt->article_content);
2781 + $prompt->chunk_metadata = $chunk_meta['metadata'];
2782 + $prompt->display_content = $chunk_meta['text']; // Store clean content without JSON prefix
2783 + }
1873 2784
1874 - if (!$xml || !is_object($xml)) {
1875 - throw new Exception(__('Invalid XML object provided', 'mxchat'));
1876 - }
2785 + if (!empty($source_url)) {
2786 + if (!isset($grouped_prompts[$source_url])) {
2787 + $grouped_prompts[$source_url] = array();
2788 + }
2789 + $grouped_prompts[$source_url][] = $prompt;
2790 + } else {
2791 + $ungrouped_prompts[] = $prompt;
2792 + }
2793 + }
1877 2794
1878 - $urls = [];
1879 - foreach ($xml->url as $url_element) {
1880 - $url = esc_url_raw((string)$url_element->loc);
1881 - if ($url) {
1882 - $urls[] = $url;
1883 - }
1884 - }
2795 + // Sort each group by chunk_index from metadata
2796 + foreach ($grouped_prompts as $source_url => &$group) {
2797 + usort($group, function($a, $b) {
2798 + $index_a = isset($a->chunk_metadata['chunk_index']) ? intval($a->chunk_metadata['chunk_index']) : 0;
2799 + $index_b = isset($b->chunk_metadata['chunk_index']) ? intval($b->chunk_metadata['chunk_index']) : 0;
2800 + return $index_a - $index_b;
2801 + });
2802 + }
2803 + unset($group); // Break reference
2804 + }
2805 + $display_index = 0;
2806 + ?>
2807 + <div class="mxchat-table-wrapper">
2808 + <table class="mxchat-records-table">
2809 + <thead>
2810 + <tr>
2811 + <th><?php esc_html_e('ID', 'mxchat'); ?></th>
2812 + <th><?php esc_html_e('Content', 'mxchat'); ?></th>
2813 + <th><?php esc_html_e('Source', 'mxchat'); ?></th>
2814 + <?php if ($data_source === 'pinecone') : ?>
2815 + <th><?php esc_html_e('Vector ID', 'mxchat'); ?></th>
2816 + <?php endif; ?>
2817 + <th><?php esc_html_e('Actions', 'mxchat'); ?></th>
2818 + </tr>
2819 + </thead>
2820 + <tbody>
2821 + <?php if ($prompts) : ?>
2822 + <?php
2823 + // Display grouped prompts (content with same source_url)
2824 + foreach ($grouped_prompts as $source_url => $group) :
2825 + $chunk_count = count($group);
2826 + $first_prompt = $group[0];
2827 + $display_index++;
1885 2828
1886 - $total_urls = absint(count($urls));
2829 + if ($chunk_count > 1) :
2830 + // Multiple chunks - show grouped row with expand button
2831 + $group_id = 'group-' . md5($source_url);
2832 + ?>
2833 + <tr id="prompt-<?php echo esc_attr($first_prompt->id); ?>"
2834 + class="mxchat-chunk-group-header"
2835 + data-source="<?php echo esc_attr($data_source); ?>"
2836 + data-group-id="<?php echo esc_attr($group_id); ?>"
2837 + <?php if ($data_source === 'pinecone') : ?>
2838 + style="background: rgba(33, 150, 243, 0.02);"
2839 + <?php endif; ?>>
2840 + <td>
2841 + <?php if ($data_source === 'pinecone') : ?>
2842 + <?php echo esc_html($display_index + (($current_page - 1) * $per_page)); ?>
2843 + <?php else : ?>
2844 + <?php echo esc_html($first_prompt->id); ?>
2845 + <?php endif; ?>
2846 + </td>
2847 + <td class="mxchat-content-cell">
2848 + <div class="mxchat-chunk-group-info">
2849 + <button type="button" class="mxchat-chunk-toggle" data-group-id="<?php echo esc_attr($group_id); ?>">
2850 + <span class="dashicons dashicons-arrow-right-alt2"></span>
2851 + </button>
2852 + <span class="mxchat-chunk-badge"><?php echo esc_html($chunk_count); ?> <?php esc_html_e('chunks', 'mxchat'); ?></span>
2853 + <span class="mxchat-chunk-preview">
2854 + <?php
2855 + // Use display_content (without JSON prefix) if available
2856 + $parent_content = isset($first_prompt->display_content) ? $first_prompt->display_content : $first_prompt->article_content;
2857 + $content_preview = mb_substr($parent_content, 0, 100);
2858 + echo esc_html($content_preview . '...');
2859 + ?>
2860 + </span>
2861 + </div>
2862 + </td>
2863 + <td class="mxchat-url-cell">
2864 + <div class="url-view">
2865 + <a href="<?php echo esc_url($source_url); ?>" target="_blank">
2866 + <span class="dashicons dashicons-external"></span>
2867 + <?php esc_html_e('View Source', 'mxchat'); ?>
2868 + </a>
2869 + </div>
2870 + </td>
2871 + <?php if ($data_source === 'pinecone') : ?>
2872 + <td class="mxchat-vector-id-cell">
2873 + <code style="background: #f5f5f5; padding: 2px 6px; border-radius: 3px; font-size: 11px;">
2874 + <?php echo esc_html(substr($first_prompt->id, 0, 8) . '..._chunk_*'); ?>
2875 + </code>
2876 + </td>
2877 + <?php endif; ?>
2878 + <td class="mxchat-actions-cell">
2879 + <div class="mxchat-role-restriction-container" style="margin-bottom: 8px;">
2880 + <select class="mxchat-role-select mxchat-group-role-select"
2881 + data-group-url="<?php echo esc_attr($source_url); ?>"
2882 + data-data-source="<?php echo esc_attr($data_source); ?>"
2883 + data-nonce="<?php echo wp_create_nonce('mxchat_update_role_nonce'); ?>">
2884 + <?php
2885 + $current_role = $first_prompt->role_restriction ?? 'public';
2886 + $role_options = $knowledge_manager->mxchat_get_role_options();
2887 + foreach ($role_options as $role_key => $role_label) : ?>
2888 + <option value="<?php echo esc_attr($role_key); ?>"
2889 + <?php selected($current_role, $role_key); ?>>
2890 + <?php echo esc_html($role_label); ?>
2891 + </option>
2892 + <?php endforeach; ?>
2893 + </select>
2894 + </div>
2895 + <div class="mxchat-delete-container">
2896 + <button type="button"
2897 + class="mxchat-button-icon delete-button-group"
2898 + data-source-url="<?php echo esc_attr($source_url); ?>"
2899 + data-chunk-count="<?php echo esc_attr($chunk_count); ?>"
2900 + data-data-source="<?php echo esc_attr($data_source); ?>"
2901 + data-bot-id="<?php echo esc_attr($current_bot_id); ?>"
2902 + data-nonce="<?php echo wp_create_nonce('mxchat_delete_chunks_nonce'); ?>"
2903 + title="<?php esc_attr_e('Delete all chunks', 'mxchat'); ?>">
2904 + <span class="dashicons dashicons-trash"></span>
2905 + </button>
2906 + </div>
2907 + </td>
2908 + </tr>
2909 + <?php
2910 + // Render hidden chunk rows
2911 + foreach ($group as $prompt) :
2912 + // Get chunk index from metadata (0-based), default to array position
2913 + $meta_chunk_index = isset($prompt->chunk_metadata['chunk_index']) ? intval($prompt->chunk_metadata['chunk_index']) : 0;
2914 + $meta_total_chunks = isset($prompt->chunk_metadata['total_chunks']) ? intval($prompt->chunk_metadata['total_chunks']) : $chunk_count;
2915 + ?>
2916 + <tr id="prompt-<?php echo esc_attr($prompt->id); ?>"
2917 + class="mxchat-chunk-row <?php echo esc_attr($group_id); ?>"
2918 + data-source="<?php echo esc_attr($data_source); ?>"
2919 + style="display: none; background: #f8f9fa;">
2920 + <td style="padding-left: 30px;">
2921 + <!-- Hidden ID column -->
2922 + </td>
2923 + <td class="mxchat-content-cell">
2924 + <div class="mxchat-accordion-wrapper">
2925 + <div class="mxchat-content-preview">
2926 + <span class="mxchat-chunk-indicator" style="margin-right: 10px;">
2927 + <?php printf(esc_html__('Chunk %d of %d', 'mxchat'), $meta_chunk_index + 1, $meta_total_chunks); ?>
2928 + </span>
2929 + <?php
2930 + // Use display_content (without JSON prefix) if available
2931 + $content = isset($prompt->display_content) ? $prompt->display_content : $prompt->article_content;
2932 + $preview_length = 150;
2933 + $content_preview = mb_strlen($content) > $preview_length
2934 + ? mb_substr($content, 0, $preview_length) . '...'
2935 + : $content;
2936 + ?>
2937 + <span class="preview-text"><?php echo esc_html($content_preview); ?></span>
2938 + <?php if (mb_strlen($content) > $preview_length) : ?>
2939 + <button class="mxchat-expand-toggle" type="button">
2940 + <span class="dashicons dashicons-arrow-down-alt2"></span>
2941 + </button>
2942 + <?php endif; ?>
2943 + </div>
2944 + <div class="mxchat-content-full" style="display: none;">
2945 + <div class="content-view">
2946 + <?php
2947 + if (preg_match('/[\x{0590}-\x{05FF}]/u', $content)) {
2948 + echo '<div dir="rtl" lang="he" class="rtl-content">';
2949 + echo wp_kses_post(wpautop($content));
2950 + echo '</div>';
2951 + } else {
2952 + echo wp_kses_post(wpautop($content));
2953 + }
2954 + ?>
2955 + </div>
2956 + </div>
2957 + </div>
2958 + </td>
2959 + <td class="mxchat-url-cell">
2960 + <span class="mxchat-chunk-label"><?php esc_html_e('Same as parent', 'mxchat'); ?></span>
2961 + </td>
2962 + <?php if ($data_source === 'pinecone') : ?>
2963 + <td class="mxchat-vector-id-cell">
2964 + <code style="background: #f5f5f5; padding: 2px 6px; border-radius: 3px; font-size: 11px;">
2965 + <?php echo esc_html(substr($prompt->id, 0, 12) . '...'); ?>
2966 + </code>
2967 + </td>
2968 + <?php endif; ?>
2969 + <td class="mxchat-actions-cell">
2970 + <span class="mxchat-chunk-label"><?php esc_html_e('Managed by group', 'mxchat'); ?></span>
2971 + </td>
2972 + </tr>
2973 + <?php endforeach; ?>
2974 + <?php else :
2975 + // Single entry - display normally
2976 + $prompt = $first_prompt;
2977 + $index = $display_index - 1;
2978 + ?>
2979 + <tr id="prompt-<?php echo esc_attr($prompt->id); ?>"
2980 + data-source="<?php echo esc_attr($data_source); ?>"
2981 + <?php if ($data_source === 'pinecone') : ?>
2982 + style="background: rgba(33, 150, 243, 0.02);"
2983 + <?php endif; ?>>
2984 + <td>
2985 + <?php if ($data_source === 'pinecone') : ?>
2986 + <?php echo esc_html($index + 1 + (($current_page - 1) * $per_page)); ?>
2987 + <?php else : ?>
2988 + <?php echo esc_html($prompt->id); ?>
2989 + <?php endif; ?>
2990 + </td>
2991 + <td class="mxchat-content-cell">
2992 + <div class="mxchat-accordion-wrapper">
2993 + <div class="mxchat-content-preview">
2994 + <?php
2995 + // Use display_content (without JSON prefix) if available
2996 + $content = isset($prompt->display_content) ? $prompt->display_content : $prompt->article_content;
2997 + $preview_length = 150;
2998 + $content_preview = mb_strlen($content) > $preview_length
2999 + ? mb_substr($content, 0, $preview_length) . '...'
3000 + : $content;
3001 + ?>
3002 + <span class="preview-text"><?php echo esc_html($content_preview); ?></span>
3003 + <?php if (mb_strlen($content) > $preview_length) : ?>
3004 + <button class="mxchat-expand-toggle" type="button">
3005 + <span class="dashicons dashicons-arrow-down-alt2"></span>
3006 + </button>
3007 + <?php endif; ?>
3008 + </div>
3009 + <div class="mxchat-content-full" style="display: none;">
3010 + <div class="content-view">
3011 + <?php
3012 + // Check if content contains Hebrew characters
3013 + if (preg_match('/[\x{0590}-\x{05FF}]/u', $content)) {
3014 + // Apply RTL direction for Hebrew content
3015 + echo '<div dir="rtl" lang="he" class="rtl-content">';
3016 + echo wp_kses_post(wpautop($content));
3017 + echo '</div>';
3018 + } else {
3019 + echo wp_kses_post(wpautop($content));
3020 + }
3021 + ?>
3022 + </div>
3023 + <?php if ($data_source === 'wordpress') : ?>
3024 + <textarea class="content-edit" style="display:none;"
3025 + <?php if (preg_match('/[\x{0590}-\x{05FF}]/u', $content)) echo 'dir="rtl" lang="he"'; ?>>
3026 + <?php echo htmlspecialchars($content, ENT_QUOTES, 'UTF-8'); ?>
3027 + </textarea>
3028 + <?php endif; ?>
3029 + </div>
3030 + </div>
3031 + </td>
3032 + <td class="mxchat-url-cell">
3033 + <div class="url-view">
3034 + <?php if (!empty($prompt->source_url)) : ?>
3035 + <a href="<?php echo esc_url($prompt->source_url); ?>" target="_blank">
3036 + <span class="dashicons dashicons-external"></span>
3037 + <?php esc_html_e('View Source', 'mxchat'); ?>
3038 + </a>
3039 + <?php else : ?>
3040 + <span class="mxchat-na"><?php esc_html_e('N/A', 'mxchat'); ?></span>
3041 + <?php endif; ?>
3042 + </div>
3043 + <?php if ($data_source === 'wordpress') : ?>
3044 + <input type="text" class="url-edit" style="display:none;"
3045 + value="<?php echo htmlspecialchars($prompt->source_url, ENT_QUOTES, 'UTF-8'); ?>" />
3046 + <?php endif; ?>
3047 + </td>
3048 + <?php if ($data_source === 'pinecone') : ?>
3049 + <td class="mxchat-vector-id-cell">
3050 + <code style="background: #f5f5f5; padding: 2px 6px; border-radius: 3px; font-size: 11px;">
3051 + <?php echo esc_html(substr($prompt->id, 0, 12) . '...'); ?>
3052 + </code>
3053 + </td>
3054 + <?php endif; ?>
3055 +
3056 + <td class="mxchat-actions-cell">
3057 + <?php if ($data_source === 'wordpress') : ?>
3058 + <!-- WordPress - Full editing capabilities -->
3059 + <div class="mxchat-role-restriction-container" style="margin-bottom: 8px;">
3060 + <select class="mxchat-role-select"
3061 + data-entry-id="<?php echo esc_attr($prompt->id); ?>"
3062 + data-data-source="wordpress"
3063 + data-nonce="<?php echo wp_create_nonce('mxchat_update_role_nonce'); ?>">
3064 + <?php
3065 + $current_role = $prompt->role_restriction ?? 'public';
3066 + $role_options = $knowledge_manager->mxchat_get_role_options();
3067 + foreach ($role_options as $role_key => $role_label) : ?>
3068 + <option value="<?php echo esc_attr($role_key); ?>"
3069 + <?php selected($current_role, $role_key); ?>>
3070 + <?php echo esc_html($role_label); ?>
3071 + </option>
3072 + <?php endforeach; ?>
3073 + </select>
3074 + </div>
3075 +
3076 + <!-- Edit/Save Buttons -->
3077 + <div class="mxchat-action-buttons">
3078 + <button class="mxchat-button-icon edit-button"
3079 + data-id="<?php echo esc_attr($prompt->id); ?>">
3080 + <span class="dashicons dashicons-edit"></span>
3081 + </button>
3082 + <button class="mxchat-button-icon save-button"
3083 + data-id="<?php echo esc_attr($prompt->id); ?>"
3084 + style="display:none;"
3085 + data-nonce="<?php echo wp_create_nonce('mxchat_save_inline_nonce'); ?>">
3086 + <span class="dashicons dashicons-saved"></span>
3087 + </button>
3088 + </div>
3089 + <?php else : ?>
3090 + <!-- Pinecone - Role restriction editable, content read-only -->
3091 + <div class="mxchat-role-restriction-container" style="margin-bottom: 8px;">
3092 + <select class="mxchat-role-select"
3093 + data-entry-id="<?php echo esc_attr($prompt->id); ?>"
3094 + data-data-source="pinecone"
3095 + data-nonce="<?php echo wp_create_nonce('mxchat_update_role_nonce'); ?>">
3096 + <?php
3097 + $current_role = $prompt->role_restriction ?? 'public';
3098 + $role_options = $knowledge_manager->mxchat_get_role_options();
3099 + foreach ($role_options as $role_key => $role_label) : ?>
3100 + <option value="<?php echo esc_attr($role_key); ?>"
3101 + <?php selected($current_role, $role_key); ?>>
3102 + <?php echo esc_html($role_label); ?>
3103 + </option>
3104 + <?php endforeach; ?>
3105 + </select>
3106 + </div>
3107 +
3108 + <?php endif; ?>
1887 3109
1888 - if ($total_urls < 1) {
1889 - throw new Exception(__('No valid URLs found in sitemap', 'mxchat'));
1890 - }
3110 + <!-- Delete button for both sources -->
3111 + <div class="mxchat-delete-container">
3112 + <?php if ($data_source === 'pinecone') : ?>
3113 + <!-- AJAX Delete for Pinecone -->
3114 + <button type="button"
3115 + class="mxchat-button-icon delete-button-ajax"
3116 + data-vector-id="<?php echo esc_attr($prompt->id); ?>"
3117 + data-bot-id="<?php echo esc_attr($current_bot_id); ?>"
3118 + data-nonce="<?php echo wp_create_nonce('mxchat_delete_pinecone_prompt_nonce'); ?>"
3119 + title="<?php esc_attr_e('Delete entry', 'mxchat'); ?>">
3120 + <span class="dashicons dashicons-trash"></span>
3121 + </button>
3122 + <?php else : ?>
3123 + <!-- Regular link delete for WordPress DB -->
3124 + <a href="<?php echo esc_url(admin_url(
3125 + 'admin-post.php?action=mxchat_delete_prompt&id=' . esc_attr($prompt->id)
3126 + . '&_wpnonce=' . wp_create_nonce('mxchat_delete_prompt_nonce')
3127 + )); ?>"
3128 + class="mxchat-button-icon delete-button"
3129 + onclick="return confirm('<?php esc_attr_e('Are you sure you want to delete this entry?', 'mxchat'); ?>');">
3130 + <span class="dashicons dashicons-trash"></span>
3131 + </a>
3132 + <?php endif; ?>
3133 + </div>
3134 + </td>
1891 3135
1892 - wp_schedule_single_event(time(), 'mxchat_process_sitemap_urls', array(
1893 - 'urls' => $urls,
1894 - 'sitemap_url' => $sitemap_url,
1895 - 'total_urls' => $total_urls,
1896 - 'batch_size' => absint(10),
1897 - 'batch_pause' => absint(5)
1898 - ));
3136 + </tr>
3137 + <?php endif; ?>
3138 + <?php endforeach; ?>
1899 3139
1900 - $status_data = array(
1901 - 'total_urls' => $total_urls,
1902 - 'processed_urls' => 0,
1903 - 'status' => 'processing',
1904 - 'last_update' => time()
1905 - );
3140 + <?php
3141 + // Display ungrouped prompts (content without source_url)
3142 + foreach ($ungrouped_prompts as $prompt) :
3143 + $display_index++;
3144 + $index = $display_index - 1;
3145 + ?>
3146 + <tr id="prompt-<?php echo esc_attr($prompt->id); ?>"
3147 + data-source="<?php echo esc_attr($data_source); ?>"
3148 + <?php if ($data_source === 'pinecone') : ?>
3149 + style="background: rgba(33, 150, 243, 0.02);"
3150 + <?php endif; ?>>
3151 + <td>
3152 + <?php if ($data_source === 'pinecone') : ?>
3153 + <?php echo esc_html($index + 1 + (($current_page - 1) * $per_page)); ?>
3154 + <?php else : ?>
3155 + <?php echo esc_html($prompt->id); ?>
3156 + <?php endif; ?>
3157 + </td>
3158 + <td class="mxchat-content-cell">
3159 + <div class="mxchat-accordion-wrapper">
3160 + <div class="mxchat-content-preview">
3161 + <?php
3162 + // Use display_content (without JSON prefix) if available
3163 + $content = isset($prompt->display_content) ? $prompt->display_content : $prompt->article_content;
3164 + $preview_length = 150;
3165 + $content_preview = mb_strlen($content) > $preview_length
3166 + ? mb_substr($content, 0, $preview_length) . '...'
3167 + : $content;
3168 + ?>
3169 + <span class="preview-text"><?php echo esc_html($content_preview); ?></span>
3170 + <?php if (mb_strlen($content) > $preview_length) : ?>
3171 + <button class="mxchat-expand-toggle" type="button">
3172 + <span class="dashicons dashicons-arrow-down-alt2"></span>
3173 + </button>
3174 + <?php endif; ?>
3175 + </div>
3176 + <div class="mxchat-content-full" style="display: none;">
3177 + <div class="content-view">
3178 + <?php echo wp_kses_post(wpautop($content)); ?>
3179 + </div>
3180 + </div>
3181 + </div>
3182 + </td>
3183 + <td class="mxchat-url-cell">
3184 + <span class="mxchat-na"><?php esc_html_e('N/A', 'mxchat'); ?></span>
3185 + </td>
3186 + <?php if ($data_source === 'pinecone') : ?>
3187 + <td class="mxchat-vector-id-cell">
3188 + <code style="background: #f5f5f5; padding: 2px 6px; border-radius: 3px; font-size: 11px;">
3189 + <?php echo esc_html(substr($prompt->id, 0, 12) . '...'); ?>
3190 + </code>
3191 + </td>
3192 + <?php endif; ?>
3193 + <td class="mxchat-actions-cell">
3194 + <div class="mxchat-delete-container">
3195 + <?php if ($data_source === 'pinecone') : ?>
3196 + <button type="button"
3197 + class="mxchat-button-icon delete-button-ajax"
3198 + data-vector-id="<?php echo esc_attr($prompt->id); ?>"
3199 + data-bot-id="<?php echo esc_attr($current_bot_id); ?>"
3200 + data-nonce="<?php echo wp_create_nonce('mxchat_delete_pinecone_prompt_nonce'); ?>"
3201 + title="<?php esc_attr_e('Delete entry', 'mxchat'); ?>">
3202 + <span class="dashicons dashicons-trash"></span>
3203 + </button>
3204 + <?php else : ?>
3205 + <a href="<?php echo esc_url(admin_url(
3206 + 'admin-post.php?action=mxchat_delete_prompt&id=' . esc_attr($prompt->id)
3207 + . '&_wpnonce=' . wp_create_nonce('mxchat_delete_prompt_nonce')
3208 + )); ?>"
3209 + class="mxchat-button-icon delete-button"
3210 + onclick="return confirm('<?php esc_attr_e('Are you sure you want to delete this entry?', 'mxchat'); ?>');">
3211 + <span class="dashicons dashicons-trash"></span>
3212 + </a>
3213 + <?php endif; ?>
3214 + </div>
3215 + </td>
3216 + </tr>
3217 + <?php endforeach; ?>
3218 + <?php else : ?>
3219 + <tr>
3220 + <td colspan="<?php echo $data_source === 'pinecone' ? '5' : '4'; ?>" class="mxchat-no-records">
3221 + <?php if ($use_pinecone) : ?>
3222 + <?php esc_html_e('No vectors found in Pinecone database.', 'mxchat'); ?>
3223 + <?php else : ?>
3224 + <?php esc_html_e('No knowledge base entries found.', 'mxchat'); ?>
3225 + <?php endif; ?>
3226 + </td>
3227 + </tr>
3228 + <?php endif; ?>
3229 + </tbody>
3230 + </table>
3231 + </div>
1906 3232
1907 - set_transient(
1908 - sanitize_key('mxchat_sitemap_status_' . md5($sitemap_url)),
1909 - array_map('sanitize_text_field', $status_data),
1910 - DAY_IN_SECONDS
1911 - );
3233 + <?php if ($page_links) : ?>
3234 + <div class="mxchat-pagination">
3235 + <?php echo wp_kses_post($page_links); ?>
3236 + </div>
3237 + <?php endif; ?>
3238 + </div>
1912 3239
1913 - return __('scheduled', 'mxchat');
3240 + </div>
1914 3241
1915 - } catch (\Exception $e) {
1916 - //error_log(sprintf(esc_html__('Error preparing sitemap for processing: %s', 'mxchat'), esc_html($e->getMessage())));
1917 - return false;
1918 - }
1919 -}
1920 -public static function process_sitemap_urls_cron($urls, $sitemap_url, $total_urls, $batch_size, $batch_pause) {
1921 - // Validate inputs
1922 - $sitemap_url = esc_url_raw($sitemap_url);
1923 - $total_urls = absint($total_urls);
1924 - $batch_size = absint($batch_size);
1925 - $batch_pause = absint($batch_pause);
3242 + <!-- Settings Tab (Renamed from Auto-Sync Settings) -->
3243 + <div id="mxchat-kb-tab-sync" class="mxchat-kb-tab-content">
3244 +
3245 + <!-- Auto-Sync Settings Card -->
3246 + <div class="mxchat-card">
3247 + <div class="mxchat-settings-section">
3248 + <h3><?php esc_html_e('Auto-Sync Settings', 'mxchat'); ?></h3>
3249 + <p class="mxchat-description">
3250 + <?php esc_html_e('Note: Auto-sync works only for newly published content. Existing posts and pages must be imported manually below. Works with Pinecone if enabled', 'mxchat'); ?>
3251 + </p>
3252 + <div class="mxchat-autosave-section">
3253 + <div class="mxchat-toggle-group">
3254 + <div class="mxchat-toggle-container">
3255 + <label class="mxchat-toggle-switch">
3256 + <input type="checkbox"
3257 + name="mxchat_auto_sync_posts"
3258 + class="mxchat-autosave-field"
3259 + value="1"
3260 + data-nonce="<?php echo wp_create_nonce('mxchat_prompts_setting_nonce'); ?>"
3261 + <?php checked(get_option('mxchat_auto_sync_posts', '0'), '1'); ?>>
3262 + <span class="mxchat-toggle-slider"></span>
3263 + </label>
3264 + <span class="mxchat-toggle-label">
3265 + <?php esc_html_e('Auto-sync Posts', 'mxchat'); ?>
3266 + </span>
3267 + </div>
3268 +
3269 + <div class="mxchat-toggle-container">
3270 + <label class="mxchat-toggle-switch">
3271 + <input type="checkbox"
3272 + name="mxchat_auto_sync_pages"
3273 + class="mxchat-autosave-field"
3274 + value="1"
3275 + data-nonce="<?php echo wp_create_nonce('mxchat_prompts_setting_nonce'); ?>"
3276 + <?php checked(get_option('mxchat_auto_sync_pages', '0'), '1'); ?>>
3277 + <span class="mxchat-toggle-slider"></span>
3278 + </label>
3279 + <span class="mxchat-toggle-label">
3280 + <?php esc_html_e('Auto-sync Pages', 'mxchat'); ?>
3281 + </span>
3282 + </div>
3283 +
3284 + <!-- Custom Post Types Section -->
3285 + <div class="mxchat-section-content">
3286 + <div class="mxchat-custom-post-types-header">
3287 + <button id="mxchat-custom-post-types-toggle" class="mxchat-button-secondary">
3288 + <?php esc_html_e('Advanced Custom Post Sync Settings', 'mxchat'); ?>
3289 + <span class="mxchat-toggle-icon">▼</span>
3290 + </button>
3291 + </div>
3292 +
3293 + <div id="mxchat-custom-post-types-container" class="mxchat-custom-post-types-container" style="display: none;">
3294 + <h3><?php esc_html_e('Sync Custom Post Types', 'mxchat'); ?></h3>
3295 + <p><?php esc_html_e('Select additional custom post types to automatically sync with the chatbot.', 'mxchat'); ?></p>
3296 +
3297 + <div class="mxchat-custom-post-types">
3298 + <?php
3299 + $post_types = $knowledge_manager->mxchat_get_public_post_types();
3300 + // Skip post and page as they're handled separately
3301 + unset($post_types['post']);
3302 + unset($post_types['page']);
3303 +
3304 + if (!empty($post_types)) {
3305 + foreach ($post_types as $post_type => $label) {
3306 + $option_name = 'mxchat_auto_sync_' . $post_type;
3307 + $is_enabled = get_option($option_name, '0');
3308 + ?>
3309 + <div class="mxchat-toggle-container">
3310 + <label class="mxchat-toggle-switch">
3311 + <input type="checkbox"
3312 + name="<?php echo esc_attr($option_name); ?>"
3313 + class="mxchat-autosave-field"
3314 + value="1"
3315 + data-nonce="<?php echo wp_create_nonce('mxchat_prompts_setting_nonce'); ?>"
3316 + <?php checked($is_enabled, '1'); ?>>
3317 + <span class="mxchat-toggle-slider"></span>
3318 + </label>
3319 + <span class="mxchat-toggle-label">
3320 + <?php echo esc_html($label); ?> (<?php echo esc_html($post_type); ?>)
3321 + </span>
3322 + </div>
3323 + <?php
3324 + }
3325 + } else {
3326 + echo '<p>' . esc_html__('No custom post types found.', 'mxchat') . '</p>';
3327 + }
3328 + ?>
3329 + </div>
3330 + </div>
3331 + </div>
3332 + </div>
3333 + </div>
3334 + </div>
3335 + </div>
1926 3336
1927 - if (!is_array($urls) || empty($urls)) {
1928 - return;
1929 - }
3337 + <!-- Content Chunking Settings Card -->
3338 + <div class="mxchat-card" style="margin-top: 20px;">
3339 + <div class="mxchat-settings-section">
3340 + <h3><?php esc_html_e('Content Chunking Settings', 'mxchat'); ?></h3>
3341 + <p class="mxchat-description">
3342 + <?php esc_html_e('Chunking splits large content into smaller segments for more accurate semantic search. This improves retrieval quality for long documents. All chunks for a URL are reassembled before sending to the AI. Only applies to new submissions - entries added before v2.6.3 must be resubmitted to enable chunking.', 'mxchat'); ?>
3343 + </p>
1930 3344
1931 - try {
1932 - $status_key = sanitize_key('mxchat_sitemap_status_' . md5($sitemap_url));
1933 - $status = get_transient($status_key);
3345 + <?php
3346 + // Get current chunking settings
3347 + $chunking_settings = MxChat_Chunker::get_settings();
3348 + $chunking_enabled = $chunking_settings['chunking_enabled'];
3349 + $chunk_size = $chunking_settings['chunk_size'];
3350 + ?>
1934 3351
1935 - if (!$status || !is_array($status)) {
1936 - throw new Exception('Invalid status data retrieved from transient');
1937 - }
3352 + <div class="mxchat-chunking-settings mxchat-autosave-section">
3353 + <!-- Enable Chunking Toggle -->
3354 + <div class="mxchat-toggle-container" style="margin-bottom: 20px;">
3355 + <label class="mxchat-toggle-switch">
3356 + <input type="checkbox"
3357 + name="mxchat_chunking_enabled"
3358 + id="mxchat_chunking_enabled"
3359 + class="mxchat-autosave-field"
3360 + value="1"
3361 + data-option-name="mxchat_chunking_enabled"
3362 + data-nonce="<?php echo wp_create_nonce('mxchat_prompts_setting_nonce'); ?>"
3363 + <?php checked($chunking_enabled, true); ?>>
3364 + <span class="mxchat-toggle-slider"></span>
3365 + </label>
3366 + <span class="mxchat-toggle-label">
3367 + <?php esc_html_e('Enable Content Chunking', 'mxchat'); ?>
3368 + </span>
3369 + </div>
1938 3370
1939 - $start_url = absint($status['processed_urls']);
1940 - $end_url = min($start_url + $batch_size, $total_urls);
1941 - $instance = new self();
3371 + <!-- Chunk Size Setting -->
3372 + <div class="mxchat-form-group" style="margin-bottom: 20px;">
3373 + <label for="mxchat_chunk_size">
3374 + <?php esc_html_e('Chunk Size (characters)', 'mxchat'); ?>
3375 + </label>
3376 + <input type="number"
3377 + name="mxchat_chunk_size"
3378 + id="mxchat_chunk_size"
3379 + class="mxchat-autosave-field regular-text"
3380 + value="<?php echo esc_attr($chunk_size); ?>"
3381 + min="1000"
3382 + max="10000"
3383 + step="500"
3384 + data-option-name="mxchat_chunk_size"
3385 + data-nonce="<?php echo wp_create_nonce('mxchat_prompts_setting_nonce'); ?>"
3386 + style="width: 150px;">
3387 + <p class="description">
3388 + <?php esc_html_e('Recommended: 4000 characters (~1000 tokens). Range: 1000-10000. Larger chunks preserve more context but are less precise for retrieval.', 'mxchat'); ?>
3389 + </p>
3390 + </div>
3391 + </div>
3392 + </div>
3393 + </div>
1942 3394
1943 - // Track failures in batch
1944 - $batch_stats = [
1945 - 'processed' => 0,
1946 - 'failed' => 0,
1947 - 'last_error' => ''
1948 - ];
3395 + <!-- Role-Based Content Restrictions Card -->
3396 + <div class="mxchat-card" style="margin-top: 20px;">
3397 + <h3><?php esc_html_e('Role-Based Content Restrictions', 'mxchat'); ?></h3>
3398 + <p class="mxchat-description">
3399 + <?php esc_html_e('Automatically restrict content access based on WordPress tags. When you assign a tag to a role, any post with that tag will only be accessible to users with that role or higher in the chatbot knowledge base.', 'mxchat'); ?>
3400 + </p>
3401 +
3402 + <!-- Info Box -->
3403 + <div class="mxchat-knowledge-warning info" style="background: #e3f2fd; border-left-color: #2196f3;">
3404 + <p>
3405 + <span class="dashicons dashicons-info"></span>
3406 + <strong><?php esc_html_e('How it works:', 'mxchat'); ?></strong>
3407 + <?php esc_html_e('Add a tag below and select which role should have access. Any content tagged with that tag will automatically be restricted to that role level. Works with both auto-sync and manual imports.', 'mxchat'); ?>
3408 + </p>
3409 + </div>
3410 +
3411 + <!-- Add New Tag-Role Mapping -->
3412 + <div class="mxchat-role-mapping-form">
3413 + <h4><?php esc_html_e('Add Tag-Role Mapping', 'mxchat'); ?></h4>
3414 +
3415 + <div class="mxchat-form-row" style="display: flex; gap: 15px; align-items: flex-end; margin-bottom: 20px;">
3416 + <div class="mxchat-form-group" style="flex: 1;">
3417 + <label for="mxchat-tag-input">
3418 + <?php esc_html_e('Tag Name', 'mxchat'); ?>
3419 + </label>
3420 + <input type="text"
3421 + id="mxchat-tag-input"
3422 + class="regular-text"
3423 + placeholder="<?php esc_attr_e('Enter tag name (e.g., premium, members-only)', 'mxchat'); ?>" />
3424 + <p class="description">
3425 + <?php esc_html_e('Enter the exact tag slug as it appears in WordPress', 'mxchat'); ?>
3426 + </p>
3427 + </div>
3428 +
3429 + <div class="mxchat-form-group" style="flex: 1;">
3430 + <label for="mxchat-role-select">
3431 + <?php esc_html_e('Required Role', 'mxchat'); ?>
3432 + </label>
3433 + <select id="mxchat-role-select" class="regular-text">
3434 + <?php
3435 + // Get role options from knowledge manager
3436 + $role_options = array(
3437 + 'public' => __('Public (Everyone)', 'mxchat'),
3438 + 'logged_in' => __('Logged In Users', 'mxchat'),
3439 + 'subscriber' => __('Subscribers & Above', 'mxchat'),
3440 + 'contributor' => __('Contributors & Above', 'mxchat'),
3441 + 'author' => __('Authors & Above', 'mxchat'),
3442 + 'editor' => __('Editors & Above', 'mxchat'),
3443 + 'administrator' => __('Administrators Only', 'mxchat')
3444 + );
3445 +
3446 + foreach ($role_options as $role_key => $role_label) : ?>
3447 + <option value="<?php echo esc_attr($role_key); ?>">
3448 + <?php echo esc_html($role_label); ?>
3449 + </option>
3450 + <?php endforeach; ?>
3451 + </select>
3452 + <p class="description">
3453 + <?php esc_html_e('Select the minimum role required to access this content', 'mxchat'); ?>
3454 + </p>
3455 + </div>
3456 +
3457 + <div style="padding-bottom: 25px;">
3458 + <button type="button"
3459 + id="mxchat-add-tag-role"
3460 + class="mxchat-button-primary">
3461 + <span class="dashicons dashicons-plus-alt"></span>
3462 + <?php esc_html_e('Add Mapping', 'mxchat'); ?>
3463 + </button>
3464 + </div>
3465 + </div>
3466 + </div>
3467 +
3468 + <!-- Existing Tag-Role Mappings -->
3469 + <div class="mxchat-role-mappings-list">
3470 + <h4><?php esc_html_e('Current Tag-Role Mappings', 'mxchat'); ?></h4>
3471 +
3472 + <div id="mxchat-mappings-container">
3473 + <!-- Mappings will be loaded here via AJAX -->
3474 + <div class="mxchat-loading-mappings">
3475 + <span class="mxchat-spinner is-active"></span>
3476 + <?php esc_html_e('Loading mappings...', 'mxchat'); ?>
3477 + </div>
3478 + </div>
3479 +
3480 + <!-- Empty State (initially hidden) -->
3481 + <div id="mxchat-no-mappings" style="display: none; text-align: center; padding: 40px; color: #666;">
3482 + <span class="dashicons dashicons-tag" style="font-size: 48px; opacity: 0.3;"></span>
3483 + <p><?php esc_html_e('No tag-role mappings yet. Add your first mapping above to get started.', 'mxchat'); ?></p>
3484 + </div>
3485 + </div>
3486 +
3487 + <!-- Bulk Update Section -->
3488 + <div class="mxchat-bulk-update-section" style="margin-top: 30px; padding-top: 30px; border-top: 1px solid #ddd;">
3489 + <h4><?php esc_html_e('Bulk Update Existing Content', 'mxchat'); ?></h4>
3490 + <p class="mxchat-description">
3491 + <?php esc_html_e('Apply role restrictions to all existing content that has the mapped tags. This is useful when setting up tag-role mappings for the first time or after changing mappings.', 'mxchat'); ?>
3492 + </p>
3493 +
3494 + <div class="mxchat-bulk-update-controls">
3495 + <button type="button"
3496 + id="mxchat-bulk-update-roles"
3497 + class="mxchat-button-secondary">
3498 + <span class="dashicons dashicons-update"></span>
3499 + <?php esc_html_e('Update All Existing Content', 'mxchat'); ?>
3500 + </button>
3501 +
3502 + <div id="mxchat-bulk-update-progress" style="display: none; margin-top: 15px;">
3503 + <div class="mxchat-progress-bar">
3504 + <div class="mxchat-progress-fill" style="width: 0%"></div>
3505 + </div>
3506 + <p class="mxchat-progress-text" style="margin-top: 10px; color: #666;">
3507 + <?php esc_html_e('Processing...', 'mxchat'); ?>
3508 + </p>
3509 + </div>
3510 +
3511 + <div id="mxchat-bulk-update-result" style="display: none; margin-top: 15px;"></div>
3512 + </div>
3513 + </div>
3514 + </div>
3515 +
3516 + </div>
1949 3517
1950 - for ($i = $start_url; $i < $end_url; $i++) {
1951 - $page_url = esc_url_raw($urls[$i]);
1952 - $page_response = wp_remote_get($page_url);
3518 + <!-- Pinecone Settings Tab -->
3519 + <div id="mxchat-kb-tab-pinecone" class="mxchat-kb-tab-content mxchat-autosave-section">
3520 + <div class="mxchat-card">
3521 + <h2><?php esc_html_e('Pinecone Vector Database Settings', 'mxchat'); ?></h2>
3522 + <p class="mxchat-description">
3523 + <?php echo wp_kses(
3524 + __('<strong>Pinecone is optional</strong> and not required for MxChat to function. It provides enhanced search performance for larger knowledge bases. When enabled, content will be stored in Pinecone instead of the WordPress database, but you can use MxChat without it.', 'mxchat'),
3525 + array('strong' => array())
3526 + ); ?>
3527 + </p>
1953 3528
1954 - if (is_wp_error($page_response) || wp_remote_retrieve_response_code($page_response) !== 200) {
1955 - $batch_stats['failed']++;
1956 - continue;
1957 - }
3529 + <div class="mxchat-pinecone-info-box">
3530 + <div class="mxchat-info-icon">
3531 + <span class="dashicons dashicons-info"></span>
3532 + </div>
3533 + <div class="mxchat-info-content">
3534 + <h4><?php esc_html_e('What is Pinecone?', 'mxchat'); ?></h4>
3535 + <p><?php esc_html_e('Pinecone is a specialized vector database designed for AI applications. It provides faster similarity searches and better scalability compared to traditional databases for AI-powered features.', 'mxchat'); ?></p>
3536 + <p><a href="https://www.pinecone.io/" target="_blank" rel="noopener noreferrer"><?php esc_html_e('Learn more about Pinecone →', 'mxchat'); ?></a></p>
3537 + </div>
3538 + </div>
1958 3539
1959 - $page_html = wp_remote_retrieve_body($page_response);
1960 - $page_content = $instance->mxchat_extract_main_content($page_html);
1961 - $sanitized_content = $instance->mxchat_sanitize_content_for_api($page_content);
3540 + <div class="mxchat-database-settings-form">
3541 + <?php
3542 + $pinecone_options = get_option('mxchat_pinecone_addon_options', array());
3543 + $use_pinecone = $pinecone_options['mxchat_use_pinecone'] ?? '0';
3544 + ?>
1962 3545
1963 - if (!empty($sanitized_content)) {
1964 - $embedding_vector = $instance->mxchat_generate_embedding($sanitized_content);
3546 + <div class="mxchat-toggle-container">
3547 + <label class="mxchat-toggle-switch">
3548 + <input type="checkbox"
3549 + name="mxchat_pinecone_addon_options[mxchat_use_pinecone]"
3550 + value="1"
3551 + <?php checked($use_pinecone, '1'); ?>>
3552 + <span class="mxchat-toggle-slider"></span>
3553 + </label>
3554 + <span class="mxchat-toggle-label">
3555 + <?php esc_html_e('Enable Pinecone Database', 'mxchat'); ?>
3556 + </span>
3557 + </div>
1965 3558
1966 - if (is_array($embedding_vector)) {
1967 - $options = get_option('mxchat_options');
1968 - $submission_result = MxChat_Utils::submit_content_to_db($sanitized_content, $page_url, $options['api_key']);
3559 + <div class="mxchat-pinecone-settings" <?php echo $use_pinecone ? '' : 'style="display: none;"'; ?>>
1969 3560
1970 - if (is_wp_error($submission_result)) {
1971 - $batch_stats['failed']++;
1972 - $batch_stats['last_error'] = $submission_result->get_error_message();
1973 - continue;
1974 - }
3561 + <?php if ($use_pinecone) : ?>
3562 + <div class="mxchat-knowledge-warning success">
3563 + <p><span class="dashicons dashicons-yes-alt"></span>
3564 + <?php esc_html_e('Pinecone is enabled. All new knowledge base content will be stored in Pinecone.', 'mxchat'); ?>
3565 + </p>
3566 + </div>
3567 + <?php endif; ?>
1975 3568
1976 - $batch_stats['processed']++;
1977 - } else {
1978 - $batch_stats['failed']++;
1979 - $batch_stats['last_error'] = 'Failed to generate embedding. Please check OpenAI API key.';
1980 - }
1981 - }
3569 + <div class="mxchat-form-group">
3570 + <label for="mxchat_pinecone_api_key">
3571 + <?php esc_html_e('Pinecone API Key', 'mxchat'); ?> <span class="required">*</span>
3572 + </label>
3573 + <input type="password"
3574 + id="mxchat_pinecone_api_key"
3575 + name="mxchat_pinecone_addon_options[mxchat_pinecone_api_key]"
3576 + value="<?php echo esc_attr($pinecone_options['mxchat_pinecone_api_key'] ?? ''); ?>"
3577 + class="regular-text"
3578 + placeholder="pcsk_..." />
3579 + <p class="description">
3580 + <?php esc_html_e('Found in your Pinecone dashboard under API Keys.', 'mxchat'); ?>
3581 + <a href="https://app.pinecone.io/" target="_blank" rel="noopener noreferrer"><?php esc_html_e('Open Pinecone Dashboard', 'mxchat'); ?></a>
3582 + </p>
3583 + </div>
1982 3584
1983 - $status['processed_urls'] = absint($i + 1);
1984 - $status['last_update'] = time();
1985 - $status['failed_urls'] = absint($status['failed_urls'] ?? 0) + $batch_stats['failed'];
1986 - $status['last_error'] = $batch_stats['last_error'];
3585 + <div class="mxchat-form-group">
3586 + <label for="mxchat_pinecone_environment">
3587 + <?php esc_html_e('Region', 'mxchat'); ?>
3588 + </label>
3589 + <input type="text"
3590 + id="mxchat_pinecone_environment"
3591 + name="mxchat_pinecone_addon_options[mxchat_pinecone_environment]"
3592 + value="<?php echo esc_attr($pinecone_options['mxchat_pinecone_environment'] ?? ''); ?>"
3593 + placeholder="e.g., gcp-starter"
3594 + class="regular-text" />
3595 + <p class="description">
3596 + <?php esc_html_e('Your Pinecone environment/region (e.g., gcp-starter, us-west1-gcp, us-east-1-aws)', 'mxchat'); ?>
3597 + </p>
3598 + </div>
1987 3599
1988 - set_transient($status_key, array_map('sanitize_text_field', $status), DAY_IN_SECONDS);
1989 - }
3600 + <div class="mxchat-form-group">
3601 + <label for="mxchat_pinecone_index">
3602 + <?php esc_html_e('Index Name', 'mxchat'); ?> <span class="required">*</span>
3603 + </label>
3604 + <input type="text"
3605 + id="mxchat_pinecone_index"
3606 + name="mxchat_pinecone_addon_options[mxchat_pinecone_index]"
3607 + value="<?php echo esc_attr($pinecone_options['mxchat_pinecone_index'] ?? ''); ?>"
3608 + placeholder="e.g., my-wordpress-vectors"
3609 + class="regular-text" />
3610 + <p class="description">
3611 + <?php esc_html_e('The name of your Pinecone index. Must be created in your Pinecone dashboard first.', 'mxchat'); ?>
3612 + </p>
3613 + </div>
1990 3614
1991 - // If all URLs in this batch failed, stop processing
1992 - if ($batch_stats['processed'] === 0 && $batch_stats['failed'] > 0) {
1993 - $status['status'] = 'error';
1994 - $status['error'] = sprintf(
1995 - 'Processing stopped: %d consecutive failures. Last error: %s',
1996 - $batch_stats['failed'],
1997 - $batch_stats['last_error']
1998 - );
1999 - set_transient($status_key, array_map('sanitize_text_field', $status), DAY_IN_SECONDS);
2000 - return;
2001 - }
3615 + <div class="mxchat-form-group">
3616 + <label for="mxchat_pinecone_host">
3617 + <?php esc_html_e('Pinecone Host', 'mxchat'); ?> <span class="required">*</span>
3618 + </label>
3619 + <input type="text"
3620 + id="mxchat_pinecone_host"
3621 + name="mxchat_pinecone_addon_options[mxchat_pinecone_host]"
3622 + value="<?php echo esc_attr($pinecone_options['mxchat_pinecone_host'] ?? ''); ?>"
3623 + placeholder="e.g., my-index-xyz123.svc.pinecone.io"
3624 + class="regular-text" />
3625 + <p class="description">
3626 + <?php esc_html_e('The hostname from your Pinecone index URL (exclude https://). Found in your index details.', 'mxchat'); ?>
3627 + </p>
3628 + </div>
2002 3629
2003 - if ($end_url < $total_urls) {
2004 - wp_schedule_single_event(time() + $batch_pause, 'mxchat_process_sitemap_urls', array(
2005 - 'urls' => array_map('esc_url_raw', $urls),
2006 - 'sitemap_url' => $sitemap_url,
2007 - 'total_urls' => $total_urls,
2008 - 'batch_size' => $batch_size,
2009 - 'batch_pause' => $batch_pause,
2010 - ));
2011 - } else {
2012 - $status['status'] = 'complete';
2013 - set_transient($status_key, array_map('sanitize_text_field', $status), DAY_IN_SECONDS);
2014 - }
2015 - } catch (\Exception $e) {
2016 - $status['status'] = 'error';
2017 - $status['error'] = sanitize_text_field($e->getMessage());
2018 - set_transient($status_key, array_map('sanitize_text_field', $status), DAY_IN_SECONDS);
2019 - }
2020 -}
3630 + <div class="mxchat-setup-steps">
3631 + <h3><?php esc_html_e('Setup Instructions', 'mxchat'); ?></h3>
3632 + <div class="mxchat-setup-step">
3633 + <span class="step-number">1</span>
3634 + <div class="step-content">
3635 + <h4><?php esc_html_e('Create Account', 'mxchat'); ?></h4>
3636 + <p><?php esc_html_e('Create a free account at', 'mxchat'); ?> <a href="https://www.pinecone.io/" target="_blank">pinecone.io</a></p>
3637 + </div>
3638 + </div>
3639 + <div class="mxchat-setup-step">
3640 + <span class="step-number">2</span>
3641 + <div class="step-content">
3642 + <h4><?php esc_html_e('Create Index', 'mxchat'); ?></h4>
3643 + <p><?php esc_html_e('Create a new index with these settings:', 'mxchat'); ?></p>
3644 + <ul>
3645 + <li><?php esc_html_e('Dimensions: Choose based on your embedding model - 1536 (OpenAI Ada 2, TE3 Small), 2048 (Voyage-3 Large), 3072 (TE3 Large, Gemini Embedding), 1536 (Gemini Embedding), or 768 (Gemini Embedding)', 'mxchat'); ?></li>
3646 + <li><?php esc_html_e('Metric: Cosine', 'mxchat'); ?></li>
3647 + <li><?php esc_html_e('Cloud & Region: Your preferred region', 'mxchat'); ?></li>
3648 + </ul>
3649 + </div>
3650 + </div>
3651 + <div class="mxchat-setup-step">
3652 + <span class="step-number">3</span>
3653 + <div class="step-content">
3654 + <h4><?php esc_html_e('Get API Key', 'mxchat'); ?></h4>
3655 + <p><?php esc_html_e('Copy your API key from the API Keys section', 'mxchat'); ?></p>
3656 + </div>
3657 + </div>
3658 + <div class="mxchat-setup-step">
3659 + <span class="step-number">4</span>
3660 + <div class="step-content">
3661 + <h4><?php esc_html_e('Get Index Host', 'mxchat'); ?></h4>
3662 + <p><?php esc_html_e('Copy your index host URL from the index details', 'mxchat'); ?></p>
3663 + </div>
3664 + </div>
3665 + <div class="mxchat-setup-step">
3666 + <span class="step-number">5</span>
3667 + <div class="step-content">
3668 + <h4><?php esc_html_e('Save Settings', 'mxchat'); ?></h4>
3669 + <p><?php esc_html_e('Fill in the form above and save settings', 'mxchat'); ?></p>
3670 + </div>
3671 + </div>
3672 + </div>
2021 3673
2022 -public function get_sitemap_processing_status($sitemap_url) {
2023 - $sitemap_url = esc_url_raw($sitemap_url);
2024 - $status = get_transient(sanitize_key('mxchat_sitemap_status_' . md5($sitemap_url)));
3674 + </div>
2025 3675
2026 - if (!$status || !is_array($status)) {
2027 - return false;
2028 - }
3676 + </div>
3677 + </div>
3678 + </div>
2029 3679
2030 - return array(
2031 - 'total_urls' => absint($status['total_urls']),
2032 - 'processed_urls' => absint($status['processed_urls']),
2033 - 'failed_urls' => absint($status['failed_urls'] ?? 0),
2034 - 'percentage' => ($status['total_urls'] > 0)
2035 - ? round((absint($status['processed_urls']) / absint($status['total_urls'])) * 100)
2036 - : 0,
2037 - 'status' => sanitize_text_field($status['status']),
2038 - 'last_update' => human_time_diff(absint($status['last_update']), time()) . ' ' . esc_html__('ago', 'mxchat'),
2039 - 'error' => isset($status['error']) ? sanitize_text_field($status['error']) : '',
2040 - 'last_error' => isset($status['last_error']) ? sanitize_text_field($status['last_error']) : ''
2041 - );
2042 -}
3680 + </div>
2043 3681
2044 -public function mxchat_stop_processing() {
2045 - // Verify permissions
2046 - if (!current_user_can('manage_options')) {
2047 - wp_die(esc_html__('Unauthorized access', 'mxchat'));
2048 - }
3682 + </div>
3683 + </div>
2049 3684
2050 - // Verify nonce
2051 - check_admin_referer('mxchat_stop_processing_action', 'mxchat_stop_processing_nonce');
3685 + <!-- Content Selector Modal -->
3686 + <div id="mxchat-kb-content-selector-modal" class="mxchat-kb-modal">
3687 + <div class="mxchat-kb-modal-content">
3688 + <div class="mxchat-kb-modal-header">
3689 + <h3>
3690 + <?php esc_html_e('Select WordPress Content', 'mxchat'); ?><br>
3691 + <span class="mxchat-kb-header-note"><?php esc_html_e('(Content imported here will be tagged "In Knowledge Base")', 'mxchat'); ?></span>
3692 + </h3>
3693 + <span class="mxchat-kb-modal-close">&times;</span>
3694 + </div>
3695 + <div class="mxchat-kb-modal-filters">
3696 + <div class="mxchat-kb-search-group">
3697 + <input type="text" id="mxchat-kb-content-search" placeholder="<?php esc_attr_e('Search...', 'mxchat'); ?>">
3698 + </div>
2052 3699
2053 - // Get the last sitemap URL and clear its transient
2054 - $sitemap_url = get_transient('mxchat_last_sitemap_url');
2055 - if ($sitemap_url) {
2056 - delete_transient('mxchat_sitemap_status_' . md5($sitemap_url));
2057 - delete_transient('mxchat_last_sitemap_url');
2058 - }
3700 + <div class="mxchat-kb-filter-group">
3701 + <select id="mxchat-kb-content-type-filter">
3702 + <option value="all"><?php esc_html_e('All Content Types', 'mxchat'); ?></option>
3703 + <option value="post"><?php esc_html_e('Posts', 'mxchat'); ?></option>
3704 + <option value="page"><?php esc_html_e('Pages', 'mxchat'); ?></option>
3705 + <?php
3706 + // Add other post types dynamically
3707 + $post_types = get_post_types(array('public' => true), 'objects');
3708 + foreach ($post_types as $post_type) {
3709 + // Skip post and page as they're already added
3710 + if (!in_array($post_type->name, array('post', 'page'))) {
3711 + echo '<option value="' . esc_attr($post_type->name) . '">' . esc_html($post_type->label) . '</option>';
3712 + }
3713 + }
3714 + ?>
3715 + </select>
2059 3716
2060 - // Get the last PDF URL and clear its transient
2061 - $pdf_url = get_transient('mxchat_last_pdf_url');
2062 - if ($pdf_url) {
2063 - delete_transient('mxchat_pdf_status_' . md5($pdf_url));
2064 - delete_transient('mxchat_last_pdf_url');
2065 - }
3717 + <select id="mxchat-kb-content-status-filter">
3718 + <option value="publish"><?php esc_html_e('Published', 'mxchat'); ?></option>
3719 + <option value="draft"><?php esc_html_e('Drafts', 'mxchat'); ?></option>
3720 + <option value="all"><?php esc_html_e('All Statuses', 'mxchat'); ?></option>
3721 + </select>
2066 3722
2067 - // Unschedule any pending sitemap events
2068 - $timestamp = wp_next_scheduled('mxchat_process_sitemap_urls');
2069 - if ($timestamp) {
2070 - wp_unschedule_event($timestamp, 'mxchat_process_sitemap_urls');
2071 - }
3723 + <select id="mxchat-kb-processed-filter">
3724 + <option value="all"><?php esc_html_e('All Content', 'mxchat'); ?></option>
3725 + <option value="processed"><?php esc_html_e('In Knowledge Base', 'mxchat'); ?></option>
3726 + <option value="unprocessed"><?php esc_html_e('Not In Knowledge Base', 'mxchat'); ?></option>
3727 + </select>
3728 + </div>
3729 + </div>
2072 3730
2073 - // Redirect back with a success message
2074 - set_transient('mxchat_admin_notice_success',
2075 - esc_html__('Processing has been stopped successfully.', 'mxchat'),
2076 - 30
2077 - );
2078 - wp_safe_redirect(admin_url('admin.php?page=mxchat-prompts'));
2079 - exit;
2080 -}
2081 -private function mxchat_sanitize_content_for_api($content) {
2082 - // Remove script, style tags, and HTML comments
2083 - $content = preg_replace('/<script\b[^>]*>(.*?)<\/script>/is', '', $content);
2084 - $content = preg_replace('/<style\b[^>]*>(.*?)<\/style>/is', '', $content);
2085 - $content = preg_replace('/<!--(.|\s)*?-->/', '', $content);
3731 + <div class="mxchat-kb-content-selection">
3732 + <div class="mxchat-kb-selection-header">
3733 + <label>
3734 + <input type="checkbox" id="mxchat-kb-select-all">
3735 + <?php esc_html_e('Select All', 'mxchat'); ?>
3736 + </label>
3737 + <span class="mxchat-kb-selection-count">0 <?php esc_html_e('selected', 'mxchat'); ?></span>
3738 + </div>
2086 3739
2087 - // Remove all HTML tags and decode HTML entities
2088 - $content = wp_strip_all_tags($content);
2089 - $content = html_entity_decode($content, ENT_QUOTES | ENT_HTML5);
3740 + <div class="mxchat-kb-content-list">
3741 + <!-- Content will be loaded here via AJAX -->
3742 + <div class="mxchat-kb-loading">
3743 + <span class="mxchat-kb-spinner is-active"></span>
3744 + <?php esc_html_e('Loading content...', 'mxchat'); ?>
3745 + </div>
3746 + </div>
2090 3747
2091 - // Trim and normalize whitespace
2092 - $content = trim(preg_replace('/\s+/', ' ', $content));
3748 + <div class="mxchat-kb-pagination">
3749 + <!-- Pagination will be added here -->
3750 + </div>
3751 + </div>
2093 3752
2094 - return $content;
3753 + <div class="mxchat-kb-modal-footer">
3754 + <button type="button" id="mxchat-kb-process-selected" class="mxchat-kb-button-primary" disabled>
3755 + <?php esc_html_e('Process Selected Content', 'mxchat'); ?>
3756 + <span class="mxchat-kb-selected-count">(0)</span>
3757 + </button>
3758 + <button type="button" class="mxchat-kb-button-secondary mxchat-kb-modal-close">
3759 + <?php esc_html_e('Cancel', 'mxchat'); ?>
3760 + </button>
3761 + </div>
3762 + </div>
3763 + </div>
3764 +
3765 +<?php
2095 3766 }
2096 3767
2097 3768
2098 3769
2099 3770
2100 -public function mxchat_handle_product_change($post_id, $post, $update) {
2101 - if ($post->post_type !== 'product') {
2102 - return;
3771 +
3772 +
3773 +/**
3774 + * Get bot-specific Pinecone configuration
3775 + * Used in the knowledge retrieval functions
3776 + */
3777 +private function get_bot_pinecone_config($bot_id = 'default') {
3778 + //error_log("MXCHAT DEBUG: get_bot_pinecone_config called for bot: " . $bot_id);
3779 +
3780 + // If default bot or multi-bot add-on not active, use default Pinecone config
3781 + if ($bot_id === 'default' || !class_exists('MxChat_Multi_Bot_Manager')) {
3782 + //error_log("MXCHAT DEBUG: Using default Pinecone config (no multi-bot or bot is 'default')");
3783 + $addon_options = get_option('mxchat_pinecone_addon_options', array());
3784 + $config = array(
3785 + 'use_pinecone' => (isset($addon_options['mxchat_use_pinecone']) && $addon_options['mxchat_use_pinecone'] === '1'),
3786 + 'api_key' => $addon_options['mxchat_pinecone_api_key'] ?? '',
3787 + 'host' => $addon_options['mxchat_pinecone_host'] ?? '',
3788 + 'namespace' => $addon_options['mxchat_pinecone_namespace'] ?? ''
3789 + );
3790 + //error_log("MXCHAT DEBUG: Default config - use_pinecone: " . ($config['use_pinecone'] ? 'true' : 'false'));
3791 + return $config;
2103 3792 }
2104 -
2105 - if ($post->post_status === 'publish') {
2106 - add_action('shutdown', function() use ($post_id) {
2107 - $product = wc_get_product($post_id);
2108 - if ($product) {
2109 - $this->mxchat_store_product_embedding($product);
2110 - }
2111 - });
3793 +
3794 + //error_log("MXCHAT DEBUG: Calling filter 'mxchat_get_bot_pinecone_config' for bot: " . $bot_id);
3795 +
3796 + // Hook for multi-bot add-on to provide bot-specific Pinecone config
3797 + $bot_pinecone_config = apply_filters('mxchat_get_bot_pinecone_config', array(), $bot_id);
3798 +
3799 + if (!empty($bot_pinecone_config)) {
3800 + //error_log("MXCHAT DEBUG: Got bot-specific config from filter");
3801 + //error_log(" - use_pinecone: " . (isset($bot_pinecone_config['use_pinecone']) ? ($bot_pinecone_config['use_pinecone'] ? 'true' : 'false') : 'not set'));
3802 + //error_log(" - host: " . ($bot_pinecone_config['host'] ?? 'not set'));
3803 + //error_log(" - namespace: " . ($bot_pinecone_config['namespace'] ?? 'not set'));
3804 + } else {
3805 + //error_log("MXCHAT DEBUG: Filter returned empty config!");
2112 3806 }
3807 +
3808 + return is_array($bot_pinecone_config) ? $bot_pinecone_config : array();
2113 3809 }
2114 3810
2115 -private function mxchat_store_product_embedding($product) {
2116 - if (!isset($this->options['enable_woocommerce_integration']) ||
2117 - !in_array($this->options['enable_woocommerce_integration'], ['1', 'on'])) {
2118 - return;
3811 +public function mxchat_delete_chat_history() {
3812 + if (!current_user_can('manage_options')) {
3813 + echo wp_json_encode(['error' => esc_html__('You do not have sufficient permissions.', 'mxchat')]);
3814 + wp_die();
2119 3815 }
3816 + check_ajax_referer('mxchat_delete_chat_history', 'security');
3817 + global $wpdb;
3818 + $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
2120 3819
2121 - $source_url = get_permalink($product->get_id());
3820 + if (isset($_POST['delete_session_ids']) && is_array($_POST['delete_session_ids'])) {
3821 + $deleted_count = 0;
2122 3822
2123 - // Build description
2124 - $regular_price = $product->get_regular_price();
2125 - $sale_price = $product->get_sale_price();
2126 - $description = $product->get_description() . "\n\n" .
2127 - esc_html__('Short Description:', 'mxchat') . " " . $product->get_short_description() . "\n" .
2128 - esc_html__('Price:', 'mxchat') . " " . $regular_price . "\n" .
2129 - esc_html__('Sale Price:', 'mxchat') . " " . ($sale_price ?: esc_html__('N/A', 'mxchat')) . "\n" .
2130 - esc_html__('SKU:', 'mxchat') . " " . $product->get_sku();
3823 + foreach ($_POST['delete_session_ids'] as $session_id) {
3824 + $session_id_sanitized = sanitize_text_field($session_id);
2131 3825
2132 - // Generate embedding vector
2133 - $embedding_vector = $this->mxchat_generate_embedding($description);
2134 - if (!is_array($embedding_vector)) {
2135 - return;
2136 - }
3826 + // Clear relevant cache before deletion
3827 + $cache_key = 'chat_session_' . $session_id_sanitized;
3828 + wp_cache_delete($cache_key, 'mxchat_chat_sessions');
2137 3829
2138 - // Check for Pinecone addon and its settings
2139 - $pinecone_settings = get_option('mxchat_pinecone_addon_options');
2140 - $use_pinecone = false;
3830 + // Perform the deletion from the database table
3831 + $wpdb->delete($table_name, ['session_id' => $session_id_sanitized]);
2141 3832
2142 - if ($pinecone_settings && is_array($pinecone_settings)) {
2143 - $use_pinecone = (
2144 - isset($pinecone_settings['mxchat_use_pinecone']) &&
2145 - $pinecone_settings['mxchat_use_pinecone'] === '1' &&
2146 - !empty($pinecone_settings['mxchat_pinecone_api_key']) &&
2147 - !empty($pinecone_settings['mxchat_pinecone_host']) &&
2148 - !empty($pinecone_settings['mxchat_pinecone_index']) &&
2149 - !empty($pinecone_settings['mxchat_pinecone_environment'])
2150 - );
2151 - }
3833 + // Delete the corresponding option entry from wp_options table
3834 + delete_option("mxchat_history_" . $session_id_sanitized);
2152 3835
2153 - if ($use_pinecone) {
2154 - $result = $this->store_in_pinecone_main(
2155 - $embedding_vector,
2156 - $description,
2157 - $source_url,
2158 - $pinecone_settings['mxchat_pinecone_api_key'],
2159 - $pinecone_settings['mxchat_pinecone_environment'],
2160 - $pinecone_settings['mxchat_pinecone_index']
2161 - );
3836 + // Delete any associated metadata options
3837 + delete_option("mxchat_email_" . $session_id_sanitized);
3838 + delete_option("mxchat_agent_name_" . $session_id_sanitized);
2162 3839
2163 - if (!$result['success']) {
2164 - // Fallback to WordPress DB if Pinecone storage fails
2165 - $this->store_in_wordpress_db($description, $source_url, $embedding_vector);
3840 + $deleted_count++;
2166 3841 }
3842 +
3843 + // Optionally, clear a general cache if you have one
3844 + wp_cache_delete('all_chat_sessions', 'mxchat_chat_sessions');
3845 +
3846 + echo wp_json_encode([
3847 + 'success' => sprintf(
3848 + esc_html__('%d chat session(s) have been deleted from all storage locations.', 'mxchat'),
3849 + $deleted_count
3850 + )
3851 + ]);
2167 3852 } else {
2168 - // Use WordPress DB storage
2169 - $this->store_in_wordpress_db($description, $source_url, $embedding_vector);
3853 + echo wp_json_encode(['error' => esc_html__('No chat sessions selected for deletion.', 'mxchat')]);
2170 3854 }
3855 +
3856 + wp_die();
2171 3857 }
2172 3858
2173 -public function mxchat_handle_product_delete($post_id) {
2174 - if (get_post_type($post_id) !== 'product') {
2175 - return;
3859 +/**
3860 + * Format transcript message content with markdown processing
3861 + * Converts markdown links, bold, italic, code blocks, and plain URLs to HTML
3862 + */
3863 +private function format_transcript_message($text) {
3864 + if (empty($text)) {
3865 + return '';
2176 3866 }
2177 3867
2178 - $source_url = get_permalink($post_id);
3868 + // Normalize line endings and clean up excessive whitespace
3869 + $text = str_replace("\r\n", "\n", $text);
3870 + $text = str_replace("\r", "\n", $text);
2179 3871
2180 - // Check for Pinecone addon and its settings
2181 - $pinecone_settings = get_option('mxchat_pinecone_addon_options');
2182 - $use_pinecone = false;
3872 + // Clean up existing <br> tags that may have been saved (legacy data)
3873 + // Convert <br>, <br/>, <br /> back to newlines for consistent processing
3874 + $text = preg_replace('/<br\s*\/?>\s*/i', "\n", $text);
2183 3875
2184 - if ($pinecone_settings && is_array($pinecone_settings)) {
2185 - $use_pinecone = (
2186 - isset($pinecone_settings['mxchat_use_pinecone']) &&
2187 - $pinecone_settings['mxchat_use_pinecone'] === '1' &&
2188 - !empty($pinecone_settings['mxchat_pinecone_api_key']) &&
2189 - !empty($pinecone_settings['mxchat_pinecone_host']) &&
2190 - !empty($pinecone_settings['mxchat_pinecone_index']) &&
2191 - !empty($pinecone_settings['mxchat_pinecone_environment'])
2192 - );
2193 - }
3876 + // Collapse 3+ consecutive newlines to just 2 (paragraph break)
3877 + $text = preg_replace('/\n{3,}/', "\n\n", $text);
2194 3878
2195 - if ($use_pinecone) {
2196 - $this->delete_from_pinecone(
2197 - array($source_url),
2198 - $pinecone_settings['mxchat_pinecone_api_key'],
2199 - $pinecone_settings['mxchat_pinecone_environment'],
2200 - $pinecone_settings['mxchat_pinecone_index']
2201 - );
2202 - } else {
2203 - // Fallback to WordPress DB deletion
2204 - global $wpdb;
2205 - $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
2206 - $wpdb->delete($table_name, array('source_url' => $source_url), array('%s'));
2207 - }
2208 -}
3879 + // Process markdown headers (# Header)
3880 + $text = preg_replace_callback('/^(#{1,6})\s+(.+)$/m', function($matches) {
3881 + $level = strlen($matches[1]);
3882 + $content = esc_html(trim($matches[2]));
3883 + return "<h{$level}>{$content}</h{$level}>";
3884 + }, $text);
2209 3885
2210 -/**
2211 - * Delete vectors from Pinecone by source URL
2212 - *
2213 - * @param array $urls Array of source URLs to delete
2214 - * @param string $api_key Pinecone API key
2215 - * @param string $environment Pinecone environment
2216 - * @param string $index_name Pinecone index name
2217 - * @return array Associative array with 'success' boolean and 'message' string
2218 - */
2219 -private function delete_from_pinecone($urls, $api_key, $environment, $index_name) {
2220 - // Get the Pinecone host from options (matching your store_in_pinecone_main pattern)
2221 - $options = get_option('mxchat_pinecone_addon_options');
2222 - $host = $options['mxchat_pinecone_host'] ?? '';
3886 + // Process code blocks with triple backticks
3887 + $text = preg_replace_callback('/```(\w+)?\n?([\s\S]*?)```/', function($matches) {
3888 + $language = !empty($matches[1]) ? ' class="language-' . esc_attr($matches[1]) . '"' : '';
3889 + $code = esc_html($matches[2]);
3890 + return "<pre><code{$language}>{$code}</code></pre>";
3891 + }, $text);
2223 3892
2224 - if (empty($host)) {
2225 - return array(
2226 - 'success' => false,
2227 - 'message' => 'Pinecone host is not configured. Please set the host in your settings.'
2228 - );
2229 - }
3893 + // Process inline code with single backticks
3894 + $text = preg_replace('/`([^`]+)`/', '<code>$1</code>', $text);
2230 3895
2231 - // Build API endpoint using the configured host
2232 - $api_endpoint = "https://{$host}/vectors/delete";
3896 + // Process bold text **text** or __text__
3897 + $text = preg_replace('/\*\*(.+?)\*\*/', '<strong>$1</strong>', $text);
3898 + $text = preg_replace('/__(.+?)__/', '<strong>$1</strong>', $text);
2233 3899
2234 - // Create vector IDs from URLs (matching your store method's ID generation)
2235 - $vector_ids = array_map('md5', $urls);
3900 + // Process italic text *text* or _text_ (but not if part of URL)
3901 + $text = preg_replace('/(?<![*_\w])\*([^*]+)\*(?![*\w])/', '<em>$1</em>', $text);
3902 + $text = preg_replace('/(?<![*_\w])_([^_]+)_(?![*\w])/', '<em>$1</em>', $text);
2236 3903
2237 - // Prepare the delete request body
2238 - $request_body = array(
2239 - 'ids' => $vector_ids,
2240 - 'filter' => array(
2241 - 'source_url' => array(
2242 - '$in' => $urls
2243 - )
2244 - )
3904 + // Process markdown links [text](url)
3905 + $text = preg_replace_callback('/\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/', function($matches) {
3906 + $link_text = esc_html($matches[1]);
3907 + $url = esc_url($matches[2]);
3908 + return "<a href=\"{$url}\" target=\"_blank\" rel=\"noopener\">{$link_text}</a>";
3909 + }, $text);
3910 +
3911 + // Process citation-style brackets [URL]
3912 + $text = preg_replace_callback('/\[(https?:\/\/[^\]]+)\]/', function($matches) {
3913 + $url = esc_url($matches[1]);
3914 + return "<a href=\"{$url}\" target=\"_blank\" rel=\"noopener\">{$url}</a>";
3915 + }, $text);
3916 +
3917 + // Process standalone URLs (not already in links)
3918 + $text = preg_replace_callback(
3919 + '/(?<!href="|">)(https?:\/\/[^\s<>"]+)(?![^<]*<\/a>)/',
3920 + function($matches) {
3921 + $url = esc_url($matches[1]);
3922 + // Truncate display URL if too long
3923 + $display = strlen($matches[1]) > 50 ? substr($matches[1], 0, 47) . '...' : $matches[1];
3924 + return "<a href=\"{$url}\" target=\"_blank\" rel=\"noopener\">{$display}</a>";
3925 + },
3926 + $text
2245 3927 );
2246 3928
2247 - // Make the deletion request
2248 - $response = wp_remote_post($api_endpoint, array(
2249 - 'headers' => array(
2250 - 'Api-Key' => $api_key,
2251 - 'accept' => 'application/json',
2252 - 'content-type' => 'application/json'
2253 - ),
2254 - 'body' => wp_json_encode($request_body),
2255 - 'timeout' => 30,
2256 - 'data_format' => 'body'
2257 - ));
3929 + // Process mailto links
3930 + $text = preg_replace_callback('/\[([^\]]+)\]\((mailto:[^)]+)\)/', function($matches) {
3931 + $link_text = esc_html($matches[1]);
3932 + $mailto = esc_url($matches[2]);
3933 + return "<a href=\"{$mailto}\">{$link_text}</a>";
3934 + }, $text);
2258 3935
2259 - // Handle WordPress HTTP API errors
2260 - if (is_wp_error($response)) {
2261 - return array(
2262 - 'success' => false,
2263 - 'message' => $response->get_error_message()
2264 - );
2265 - }
3936 + // Convert paragraphs: split by double newlines, wrap in <p> tags
3937 + // This creates proper paragraph structure instead of excessive <br> tags
3938 + $paragraphs = preg_split('/\n\n+/', $text);
2266 3939
2267 - // Check response status
2268 - $response_code = wp_remote_retrieve_response_code($response);
2269 - if ($response_code !== 200) {
2270 - $body = wp_remote_retrieve_body($response);
2271 - return array(
2272 - 'success' => false,
2273 - 'message' => sprintf(
2274 - 'Pinecone API error (HTTP %d): %s',
2275 - $response_code,
2276 - $body
2277 - )
2278 - );
2279 - }
3940 + // Filter out empty paragraphs but preserve content like "0"
3941 + $paragraphs = array_values(array_filter(array_map('trim', $paragraphs), function($p) {
3942 + return $p !== '';
3943 + }));
2280 3944
2281 - // Parse response body
2282 - $body = wp_remote_retrieve_body($response);
2283 - $response_data = json_decode($body, true);
2284 -
2285 - // Final validation of the response
2286 - if (json_last_error() !== JSON_ERROR_NONE) {
2287 - return array(
2288 - 'success' => false,
2289 - 'message' => 'Failed to parse Pinecone response: ' . json_last_error_msg()
2290 - );
3945 + if (empty($paragraphs)) {
3946 + // No content after filtering
3947 + return '';
3948 + } elseif (count($paragraphs) > 1) {
3949 + // Multiple paragraphs - wrap each in <p> tags, convert single newlines to <br>
3950 + $formatted_paragraphs = array_map(function($p) {
3951 + return nl2br($p);
3952 + }, $paragraphs);
3953 + $text = '<p>' . implode('</p><p>', $formatted_paragraphs) . '</p>';
3954 + } else {
3955 + // Single paragraph - just convert newlines to <br>
3956 + $text = nl2br($paragraphs[0]);
2291 3957 }
2292 3958
2293 - return array(
2294 - 'success' => true,
2295 - 'message' => sprintf('Successfully deleted %d vectors from Pinecone', count($vector_ids))
2296 - );
3959 + return $text;
2297 3960 }
2298 3961
2299 -
2300 -public function mxchat_fetch_chat_history() {
2301 - global $wpdb;
2302 - $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
2303 -
3962 +/**
3963 + * AJAX handler to fetch RAG context for a specific message
3964 + * Used by the transcript viewer to show retrieved documents
3965 + */
3966 +public function mxchat_get_rag_context() {
3967 + // Check permissions
2304 3968 if (!current_user_can('manage_options')) {
2305 - wp_die(esc_html__('You do not have sufficient permissions to view this page.', 'mxchat'));
3969 + wp_send_json_error(['message' => esc_html__('You do not have sufficient permissions.', 'mxchat')]);
3970 + wp_die();
2306 3971 }
2307 3972
2308 - // First get unique session IDs ordered by most recent message in each session
2309 - $session_ids = $wpdb->get_col(
2310 - $wpdb->prepare(
2311 - "SELECT DISTINCT session_id
2312 - FROM {$table_name}
2313 - GROUP BY session_id
2314 - ORDER BY MAX(timestamp) DESC"
2315 - )
2316 - );
2317 -
2318 - if (empty($session_ids)) {
2319 - wp_die(esc_html__('No chat history available.', 'mxchat'));
3973 + // Validate message ID
3974 + if (!isset($_POST['message_id']) || empty($_POST['message_id'])) {
3975 + wp_send_json_error(['message' => esc_html__('Message ID is required.', 'mxchat')]);
3976 + wp_die();
2320 3977 }
2321 3978
2322 - ob_start();
2323 - echo '<div class="mxchat-transcript">';
3979 + $message_id = absint($_POST['message_id']);
2324 3980
2325 - // Iterate through sessions from newest to oldest
2326 - foreach ($session_ids as $session_id) {
2327 - // Get the email associated with this session (if available)
2328 - $email = $wpdb->get_var(
2329 - $wpdb->prepare(
2330 - "SELECT user_email
2331 - FROM {$table_name}
2332 - WHERE session_id = %s AND user_email != ''
2333 - ORDER BY timestamp ASC
2334 - LIMIT 1",
2335 - $session_id
2336 - )
2337 - );
3981 + global $wpdb;
3982 + $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
2338 3983
2339 - // Debug: Check email retrieval
2340 - if (empty($email)) {
2341 - //error_log("No email found for session {$session_id}");
2342 - } else {
2343 - //error_log("Email for session {$session_id}: " . $email);
2344 - }
3984 + // Fetch the RAG context for this message
3985 + $result = $wpdb->get_row($wpdb->prepare(
3986 + "SELECT rag_context FROM {$table_name} WHERE id = %d",
3987 + $message_id
3988 + ));
2345 3989
2346 - // Get messages for this session ordered by timestamp
2347 - $messages = $wpdb->get_results(
2348 - $wpdb->prepare(
2349 - "SELECT * FROM {$table_name}
2350 - WHERE session_id = %s
2351 - ORDER BY timestamp ASC",
2352 - $session_id
2353 - )
2354 - );
3990 + if (!$result || empty($result->rag_context)) {
3991 + wp_send_json_error(['message' => esc_html__('No RAG context found for this message.', 'mxchat')]);
3992 + wp_die();
3993 + }
2355 3994
2356 - // Start session block
2357 - echo '<div class="mxchat-session">';
2358 - echo '<div class="mxchat-session-header">';
2359 - // Wrap checkbox and session ID in one block
2360 - echo '<div class="mxchat-session-id">';
2361 - echo '<input type="checkbox" name="delete_session_ids[]" value="' . esc_attr($session_id) . '"> ';
2362 - echo '<strong>' . esc_html__('Session ID:', 'mxchat') . '</strong> ' . esc_html($session_id);
2363 - echo '</div>';
3995 + // Decode the JSON data
3996 + $rag_context = json_decode($result->rag_context, true);
2364 3997
2365 - // Place email directly below the session ID block
2366 - if (!empty($email)) {
2367 - echo '<div class="mxchat-session-email">';
2368 - echo '<strong>' . esc_html__('Email:', 'mxchat') . '</strong> ' . esc_html($email);
2369 - echo '</div>';
2370 - }
2371 - echo '</div>';
3998 + if (json_last_error() !== JSON_ERROR_NONE) {
3999 + wp_send_json_error(['message' => esc_html__('Invalid RAG context data.', 'mxchat')]);
4000 + wp_die();
4001 + }
2372 4002
2373 - echo '<div class="mxchat-messages">';
4003 + wp_send_json_success($rag_context);
4004 + wp_die();
4005 +}
2374 4006
2375 - // Display messages for this session
2376 - foreach ($messages as $transcript) {
2377 - $formatted_timestamp = date_i18n('F j, Y g:i a', strtotime($transcript->timestamp));
4007 +public function display_admin_notices() {
4008 + // Check if we're on a MXChat admin page
4009 + $screen = get_current_screen();
4010 + if (!$screen || strpos($screen->base, 'mxchat') === false) {
4011 + return;
4012 + }
2378 4013
2379 - // Determine message styling
2380 - switch ($transcript->role) {
2381 - case 'assistant':
2382 - case 'bot':
2383 - $message_class = 'bot-message';
2384 - $display_role = esc_html__('Chatbot', 'mxchat');
2385 - break;
2386 - case 'user':
2387 - $message_class = 'user-message';
2388 - $display_role = !empty($transcript->user_identifier)
2389 - ? sanitize_text_field($transcript->user_identifier)
2390 - : esc_html__('User', 'mxchat');
2391 - break;
2392 - case 'agent':
2393 - $message_class = 'agent-message';
2394 - $display_role = esc_html__('Agent', 'mxchat');
2395 - break;
2396 - default:
2397 - $message_class = 'unknown-message';
2398 - $display_role = esc_html__('Unknown', 'mxchat');
2399 - }
4014 + //error_log('MxChat admin_notices hook fired on screen: ' . $screen->base);
2400 4015
2401 - // Process message content
2402 - $message_content = wp_kses(
2403 - stripslashes($transcript->message),
2404 - [
2405 - 'b' => [], 'strong' => [], 'i' => [], 'em' => [], 'u' => [],
2406 - 'br' => [], 'p' => [], 'ul' => [], 'ol' => [], 'li' => [],
2407 - 'a' => ['href' => [], 'title' => []]
2408 - ]
2409 - );
2410 - $message_content = nl2br($message_content);
4016 + // Check for error notices
4017 + $error_notice = get_transient('mxchat_admin_notice_error');
4018 + if ($error_notice) {
4019 + //error_log('Found error transient: ' . $error_notice);
4020 + echo '<div class="notice notice-error is-dismissible"><p>' . wp_kses_post($error_notice) . '</p></div>';
4021 + delete_transient('mxchat_admin_notice_error');
4022 + //error_log('Displayed and deleted error transient');
4023 + } else {
4024 + //error_log('No error transient found');
4025 + }
2411 4026
2412 - // Render message
2413 - echo '<div class="mxchat-message ' . esc_attr($message_class) . '">';
2414 - echo '<div class="mxchat-message-header">' . esc_html($display_role) . '</div>';
2415 - echo '<div class="mxchat-message-content">' . $message_content . '</div>';
2416 - echo '<div class="mxchat-timestamp">' . esc_html($formatted_timestamp) . '</div>';
2417 - echo '</div>';
2418 - }
4027 + // Check for success notices
4028 + $success_notice = get_transient('mxchat_admin_notice_success');
4029 + if ($success_notice) {
4030 + //error_log('Found success transient: ' . $success_notice);
4031 + echo '<div class="notice notice-success is-dismissible"><p>' . wp_kses_post($success_notice) . '</p></div>';
4032 + delete_transient('mxchat_admin_notice_success');
4033 + //error_log('Displayed and deleted success transient');
4034 + }
2419 4035
2420 - // Close session block
2421 - echo '</div></div>';
4036 + // Check for info notices
4037 + $info_notice = get_transient('mxchat_admin_notice_info');
4038 + if ($info_notice) {
4039 + //error_log('Found info transient: ' . $info_notice);
4040 + echo '<div class="notice notice-info is-dismissible"><p>' . wp_kses_post($info_notice) . '</p></div>';
4041 + delete_transient('mxchat_admin_notice_info');
4042 + //error_log('Displayed and deleted info transient');
2422 4043 }
2423 4044
2424 - echo '</div>';
2425 - $output = ob_get_clean();
2426 - echo $output;
2427 - wp_die();
2428 4045 }
2429 4046
2430 4047
2431 4048 public function mxchat_create_activation_page() {
@@ -2430,16 +4047,26 @@
2430 4047
2431 4048 public function mxchat_create_activation_page() {
2432 4049 $license_status = get_option('mxchat_license_status', 'inactive');
2433 4050 $license_error = get_option('mxchat_license_error', '');
4051 + $current_domain = parse_url(home_url(), PHP_URL_HOST);
4052 +
4053 + $license_management_url = 'https://mxchat.ai/my-account/orders/';
4054 +
4055 + // Check if this activation has been linked to a domain
4056 + $is_domain_linked = $this->is_current_activation_linked($current_domain);
2434 4057 ?>
2435 4058 <div class="wrap mxchat-admin-activation">
2436 4059 <div class="mxchat-pro-hero">
2437 4060 <h1 class="pro-title">
2438 - <span class="pro-gradient-text">Activate</span> MxChat Pro
4061 + <span class="pro-gradient-text">Manage</span> MxChat Pro
2439 4062 </h1>
2440 4063 <p class="pro-subtitle">
2441 - <?php esc_html_e('Enter your license key to unlock premium features, advanced AI capabilities, and priority support.', 'mxchat'); ?>
4064 + <?php if ($license_status === 'active'): ?>
4065 + <?php esc_html_e('Your pro license is active. Manage your activation below.', 'mxchat'); ?>
4066 + <?php else: ?>
4067 + <?php esc_html_e('Enter your license key to unlock premium features, advanced AI capabilities, and priority support.', 'mxchat'); ?>
4068 + <?php endif; ?>
2442 4069 </p>
2443 4070 </div>
2444 4071
2445 4072 <?php if ($license_status === 'inactive' && !empty($license_error)): ?>
@@ -2447,8 +4074,25 @@
2447 4074 <p><?php echo esc_html($license_error); ?></p>
2448 4075 </div>
2449 4076 <?php endif; ?>
2450 4077
4078 + <!-- Domain Linking Notice for Existing Users -->
4079 + <?php if ($license_status === 'active' && !$is_domain_linked): ?>
4080 + <div class="notice notice-info">
4081 + <p>
4082 + <strong><?php esc_html_e('New Feature: Domain Tracking', 'mxchat'); ?></strong><br>
4083 + <?php esc_html_e('We\'ve enhanced our license system to track your activated domains. Link this activation to your domain for better license management.', 'mxchat'); ?>
4084 + </p>
4085 + <p>
4086 + <button type="button" id="link-domain-button" class="button button-secondary">
4087 + <?php esc_html_e('Link This Domain', 'mxchat'); ?>
4088 + </button>
4089 + <span id="domain-link-status" style="margin-left: 10px;"></span>
4090 + </p>
4091 + </div>
4092 + <?php endif; ?>
4093 +
4094 + <!-- Activation Form -->
2451 4095 <form id="mxchat-activation-form" class="mxchat-pro-form" style="<?php echo $license_status === 'active' ? 'display: none;' : ''; ?>">
2452 4096 <div class="mxchat-pro-form-container">
2453 4097 <table class="form-table">
2454 4098 <tr valign="top">
@@ -2463,8 +4107,12 @@
2463 4107 <input type="text" id="mxchat_activation_key" name="mxchat_activation_key" value="<?php echo esc_attr(get_option('mxchat_activation_key')); ?>" class="regular-text mxchat-pro-input" required />
2464 4108 </td>
2465 4109 </tr>
2466 4110 </table>
4111 +
4112 + <!-- Hidden field for domain -->
4113 + <input type="hidden" id="mxchat_domain" name="domain" value="<?php echo esc_attr($current_domain); ?>" />
4114 +
2467 4115 <?php if ($license_status !== 'active'): ?>
2468 4116 <div class="mxchat-pro-button-container">
2469 4117 <button type="submit" id="activate_license_button" class="button button-primary mxchat-pro-button"><?php esc_html_e('Activate License', 'mxchat'); ?></button>
2470 4118 <div id="mxchat-activation-spinner" class="mxchat-activation-spinner" style="display: none;"></div>
@@ -2471,8 +4119,9 @@
2471 4119 </div>
2472 4120 <?php endif; ?>
2473 4121 </div>
2474 4122 </form>
4123 +
2475 4124 <!-- License Status Display -->
2476 4125 <div class="mxchat-pro-status">
2477 4126 <h3><?php esc_html_e('License Status:', 'mxchat'); ?>
2478 4127 <span id="mxchat-license-status" class="mxchat-status-badge <?php echo $license_status; ?>">
@@ -2478,15 +4127,91 @@
2478 4127 <span id="mxchat-license-status" class="mxchat-status-badge <?php echo $license_status; ?>">
2479 4128 <?php echo $license_status === 'active' ? esc_html__('Active', 'mxchat') : esc_html__('Inactive', 'mxchat'); ?>
2480 4129 </span>
2481 4130 </h3>
4131 +
4132 + <?php if ($license_status === 'active'): ?>
4133 + <div class="mxchat-license-details">
4134 + <p><strong><?php esc_html_e('Domain:', 'mxchat'); ?></strong> <?php echo esc_html($current_domain); ?>
4135 + <?php if ($is_domain_linked): ?>
4136 + <span style="color: green; margin-left: 10px;">✓ <?php esc_html_e('Linked', 'mxchat'); ?></span>
4137 + <?php else: ?>
4138 + <span style="color: orange; margin-left: 10px;">⚠ <?php esc_html_e('Not Linked', 'mxchat'); ?></span>
4139 + <?php endif; ?>
4140 + </p>
4141 + <p><strong><?php esc_html_e('Email:', 'mxchat'); ?></strong> <?php echo esc_html(get_option('mxchat_pro_email')); ?></p>
4142 + <p><strong><?php esc_html_e('License Key:', 'mxchat'); ?></strong>
4143 + <code style="background: #f1f1f1; padding: 2px 6px; border-radius: 3px;">
4144 + <?php echo esc_html(get_option('mxchat_activation_key')); ?>
4145 + </code>
4146 + </p>
4147 + <p>
4148 + <small>
4149 + <?php esc_html_e('Manage all your licenses and domains on', 'mxchat'); ?>
4150 + <a href="<?php echo esc_url($license_management_url); ?>" target="_blank">
4151 + <?php esc_html_e('your account dashboard', 'mxchat'); ?>
4152 + </a>
4153 + </small>
4154 + </p>
4155 +
4156 + <!-- Deactivation Section -->
4157 + <div class="mxchat-deactivation-section" style="margin-top: 20px; padding: 15px; background: #fff3cd; border-left: 4px solid #ffc107; border-radius: 4px;">
4158 + <h4 style="margin-top: 0; color: #856404;">
4159 + <?php esc_html_e('License Management', 'mxchat'); ?>
4160 + </h4>
4161 + <p style="margin-bottom: 15px; color: #856404;">
4162 + <?php esc_html_e('Need to move this license to another site? Deactivate it here first, then activate it on your new site.', 'mxchat'); ?>
4163 + </p>
4164 + <button type="button" id="deactivate-license-button" class="button button-secondary" style="background: #dc3545; color: white; border-color: #dc3545;">
4165 + 🔓 <?php esc_html_e('Deactivate License', 'mxchat'); ?>
4166 + </button>
4167 + <span id="deactivate-status" style="margin-left: 10px;"></span>
4168 + </div>
4169 + </div>
4170 + <?php endif; ?>
2482 4171 </div>
2483 4172
4173 + <!-- Hidden fields for JavaScript -->
4174 + <input type="hidden" id="mxchat-ajax-url" value="<?php echo admin_url('admin-ajax.php'); ?>" />
4175 + <input type="hidden" id="mxchat-nonce" value="<?php echo wp_create_nonce('mxchat_activate_license_nonce'); ?>" />
4176 + <input type="hidden" id="mxchat-api-url" value="https://mxchat.ai" />
2484 4177 </div>
2485 4178 <?php
2486 4179 }
2487 4180
2488 -public function mxchat_intents_page_html() {
4181 +/**
4182 + * Check if current activation is linked to a domain
4183 + * This checks YOUR website's database, not the user's local database
4184 + */
4185 +private function is_current_activation_linked($domain) {
4186 + $license_key = get_option('mxchat_activation_key');
4187 + $email = get_option('mxchat_pro_email');
4188 +
4189 + if (empty($license_key) || empty($email)) {
4190 + return false;
4191 + }
4192 +
4193 + // Check with YOUR website's API
4194 + $response = wp_remote_post('https://mxchat.ai/mxchat-api/check-domain', array(
4195 + 'body' => array(
4196 + 'license_key' => $license_key,
4197 + 'email' => $email,
4198 + 'domain' => $domain
4199 + ),
4200 + 'timeout' => 10,
4201 + 'sslverify' => false
4202 + ));
4203 +
4204 + if (is_wp_error($response)) {
4205 + return false;
4206 + }
4207 +
4208 + $body = json_decode(wp_remote_retrieve_body($response), true);
4209 + return isset($body['success']) && $body['success'] && isset($body['data']['linked']) && $body['data']['linked'];
4210 +}
4211 +
4212 +
4213 +public function mxchat_actions_page_html() {
2489 4214 if (!current_user_can('manage_options')) {
2490 4215 return;
2491 4216 }
2492 4217
@@ -2499,9 +4224,9 @@
2499 4224
2500 4225 // Success message
2501 4226 if (isset($_GET['updated']) && $_GET['updated'] === 'true') {
2502 4227 echo '<div class="notice notice-success is-dismissible"><p>' .
2503 - esc_html__('Intent updated successfully.', 'mxchat') .
4228 + esc_html__('Action updated successfully.', 'mxchat') .
2504 4229 '</p></div>';
2505 4230 }
2506 4231
2507 4232 // Filtering logic
@@ -2522,10 +4247,10 @@
2522 4247 // Pagination
2523 4248 $total_intents = $wpdb->get_var("SELECT COUNT(*) FROM $table_name WHERE $where");
2524 4249 $total_pages = ceil($total_intents / $per_page);
2525 4250
2526 - // Get intents
2527 - $intents = $wpdb->get_results($wpdb->prepare(
4251 + // Get intents (now called actions)
4252 + $actions = $wpdb->get_results($wpdb->prepare(
2528 4253 "SELECT * FROM $table_name WHERE $where LIMIT %d OFFSET %d",
2529 4254 $per_page, $offset
2530 4255 ));
2531 4256
@@ -2536,629 +4261,1089 @@
2536 4261 <div class="wrap mxchat-wrapper">
2537 4262 <!-- Hero Section -->
2538 4263 <div class="mxchat-hero">
2539 4264 <h1 class="mxchat-main-title">
2540 - <span class="mxchat-gradient-text">Intent</span> Manager
4265 + <span class="mxchat-gradient-text">Actions</span> Manager
2541 4266 </h1>
2542 4267 <p class="mxchat-hero-subtitle">
2543 - <?php esc_html_e('Create and manage custom intents to enhance your chatbots understanding and response capabilities.', 'mxchat'); ?>
4268 + <?php esc_html_e('Create and manage custom actions to enhance your chatbot\'s capabilities.', 'mxchat'); ?>
2544 4269 </p>
2545 4270 </div>
2546 4271
2547 - <div class="mxchat-content">
2548 - <!-- Add Intent Card -->
2549 - <div class="mxchat-card">
2550 - <div class="mxchat-card-header">
2551 - <h2><?php esc_html_e('Add New Intent', 'mxchat'); ?></h2>
2552 - </div>
2553 -
2554 - <div class="mxchat-intent-documentation">
2555 - <p>
2556 - <?php esc_html_e('We highly encourage users to quickly read our ', 'mxchat'); ?>
2557 - <a href="https://mxchat.ai/documentation/#intents" target="_blank" rel="noopener noreferrer">
2558 - <?php esc_html_e('documentation', 'mxchat'); ?>
2559 - </a>
2560 - <?php esc_html_e(' to better understand intents. Some intents require setup in the Integration tab. If your intent is not triggering lower similarity threshold test.', 'mxchat'); ?>
2561 - </p>
2562 - </div>
2563 -
2564 - <form id="mxchat-add-intent-form" method="post"
2565 - action="<?php echo esc_url(admin_url('admin-post.php')); ?>">
2566 - <input type="hidden" name="action" value="mxchat_add_intent">
2567 - <?php wp_nonce_field('mxchat_add_intent_nonce'); ?>
2568 -
2569 - <div class="mxchat-form-group">
2570 - <label for="intent_label">
2571 - <?php esc_html_e('Intent Label (For your reference only)', 'mxchat'); ?>
2572 - </label>
2573 - <input name="intent_label" type="text" id="intent_label" required
2574 - class="mxchat-intent-input"
2575 - placeholder="<?php esc_attr_e('Example Email Capture: Newsletter Signup', 'mxchat'); ?>">
4272 + <!-- Actions Header with Search and Filter -->
4273 + <div class="mxchat-actions-header">
4274 + <div class="mxchat-actions-filters">
4275 + <form method="get" class="mxchat-search-form">
4276 + <input type="hidden" name="page" value="mxchat-actions">
4277 + <div class="mxchat-search-group">
4278 + <span class="dashicons dashicons-search"></span>
4279 + <input type="text" name="s" class="mxchat-search-input"
4280 + placeholder="<?php esc_attr_e('Search Actions', 'mxchat'); ?>"
4281 + value="<?php echo esc_attr($search_term); ?>">
2576 4282 </div>
2577 -
2578 - <div class="mxchat-form-group">
2579 - <label for="phrases">
2580 - <?php esc_html_e('Phrases (comma-separated)', 'mxchat'); ?>
2581 - </label>
2582 - <textarea name="phrases" id="phrases" rows="5" required
2583 - class="mxchat-intent-textarea"
2584 - placeholder="<?php esc_attr_e('Example Email Capture: sign me up, subscribe me, I want to join, add me to the newsletter, send me updates, keep me informed', 'mxchat'); ?>"></textarea>
2585 - </div>
2586 -
2587 - <div class="mxchat-form-group">
2588 - <label for="callback_function">
2589 - <?php esc_html_e('Callback Function', 'mxchat'); ?>
2590 - </label>
2591 - <select name="callback_function" id="callback_function"
2592 - class="mxchat-intent-select" required>
2593 - <option value="">
2594 - <?php esc_html_e('Select a Callback', 'mxchat'); ?>
4283 + <select name="callback_filter" class="mxchat-action-filter">
4284 + <option value=""><?php esc_html_e('All Action Types', 'mxchat'); ?></option>
4285 + <?php foreach ($available_callbacks as $function => $callback_data) :
4286 + $label = $callback_data['label']; ?>
4287 + <option value="<?php echo esc_attr($function); ?>"
4288 + <?php selected($callback_filter, $function); ?>>
4289 + <?php echo esc_html($label); ?>
2595 4290 </option>
2596 - <?php
2597 - $groups = $this->mxchat_get_available_callbacks(true);
2598 - foreach ($groups as $group_label => $group_callbacks) :
2599 - echo '<optgroup label="' . esc_attr($group_label) . '">';
2600 - foreach ($group_callbacks as $function => $data) :
2601 - $label = $data['label'];
2602 - $pro_only = $data['pro_only'];
2603 - $disabled = (!$this->is_activated && $pro_only) ? 'disabled' : '';
2604 - $label_suffix = (!$this->is_activated && $pro_only) ? ' (Pro Only)' : '';
2605 - ?>
2606 - <option value="<?php echo esc_attr($function); ?>"
2607 - <?php echo $disabled; ?>>
2608 - <?php echo esc_html($label . $label_suffix); ?>
2609 - </option>
2610 - <?php
2611 - endforeach;
2612 - echo '</optgroup>';
2613 - endforeach;
2614 - ?>
2615 - </select>
2616 - </div>
2617 -
2618 - <button type="submit" class="mxchat-button-primary">
2619 - <?php esc_html_e('Add Intent', 'mxchat'); ?>
4291 + <?php endforeach; ?>
4292 + </select>
4293 + <button type="submit" class="mxchat-button-secondary">
4294 + <?php esc_html_e('Filter', 'mxchat'); ?>
2620 4295 </button>
2621 4296 </form>
2622 4297 </div>
4298 + <div class="mxchat-actions-controls">
4299 + <button type="button" id="mxchat-add-action-btn" class="mxchat-button-primary">
4300 + <span class="dashicons dashicons-plus-alt"></span>
4301 + <?php esc_html_e('Add New Action', 'mxchat'); ?>
4302 + </button>
4303 + </div>
4304 + </div>
2623 4305
2624 - <!-- Manage Intents Card -->
2625 - <div class="mxchat-card">
2626 - <div class="mxchat-card-header">
2627 - <h2><?php esc_html_e('Manage Intents', 'mxchat'); ?></h2>
2628 - <div class="mxchat-header-actions">
2629 - <form method="get" class="mxchat-search-form">
2630 - <input type="hidden" name="page" value="mxchat-intents">
2631 - <div class="mxchat-search-group">
2632 - <span class="dashicons dashicons-search"></span>
2633 - <input type="text" name="s"
2634 - placeholder="<?php esc_attr_e('Search Intents', 'mxchat'); ?>"
2635 - value="<?php echo esc_attr($search_term); ?>">
2636 - <select name="callback_filter" class="mxchat-intent-filter">
2637 - <option value="">
2638 - <?php esc_html_e('All Callbacks', 'mxchat'); ?>
2639 - </option>
2640 - <?php foreach ($available_callbacks as $function => $callback_data) :
2641 - $label = $callback_data['label']; ?>
2642 - <option value="<?php echo esc_attr($function); ?>"
2643 - <?php selected($callback_filter, $function); ?>>
2644 - <?php echo esc_html($label); ?>
2645 - </option>
2646 - <?php endforeach; ?>
2647 - </select>
2648 - <button type="submit" class="mxchat-button-secondary">
2649 - <?php esc_html_e('Filter', 'mxchat'); ?>
2650 - </button>
4306 + <!-- Actions Grid Layout - All actions in a single grid -->
4307 + <div class="mxchat-actions-grid">
4308 + <div class="mxchat-cards-container">
4309 + <?php if (!empty($actions)) : ?>
4310 + <?php foreach ($actions as $action) :
4311 + $callback_function = $action->callback_function;
4312 + $callback_label = isset($available_callbacks[$callback_function]['label'])
4313 + ? $available_callbacks[$callback_function]['label']
4314 + : $callback_function;
4315 + $threshold_value = isset($action->similarity_threshold)
4316 + ? round($action->similarity_threshold * 100)
4317 + : 85;
4318 +
4319 + // Check if this is a form action
4320 + $is_form_action = strpos($action->intent_label, 'Form ') === 0;
4321 +
4322 + // Get action status (enabled/disabled) - default to true if column doesn't exist
4323 + $is_enabled = isset($action->enabled) ? (bool)$action->enabled : true;
4324 +
4325 + // Get enabled bots for display
4326 + $enabled_bots = [];
4327 + if (isset($action->enabled_bots) && !empty($action->enabled_bots)) {
4328 + $enabled_bots = json_decode($action->enabled_bots, true);
4329 + if (!is_array($enabled_bots)) {
4330 + $enabled_bots = ['default'];
4331 + }
4332 + } else {
4333 + $enabled_bots = ['default']; // Backward compatibility
4334 + }
4335 + ?>
4336 + <div class="mxchat-action-card <?php echo $is_form_action ? 'mxchat-form-action' : ''; ?>">
4337 + <div class="mxchat-card-header">
4338 + <div class="mxchat-card-title"><?php echo esc_html($action->intent_label); ?></div>
4339 + <div class="mxchat-card-toggle">
4340 + <label class="mxchat-switch">
4341 + <input type="checkbox" class="mxchat-action-toggle"
4342 + data-action-id="<?php echo esc_attr($action->id); ?>"
4343 + <?php checked($is_enabled); ?>>
4344 + <span class="mxchat-slider round"></span>
4345 + </label>
4346 + </div>
2651 4347 </div>
2652 - </form>
2653 - </div>
2654 - </div>
2655 4348
2656 - <div class="mxchat-table-wrapper">
2657 - <table class="mxchat-records-table">
2658 - <thead>
2659 - <tr>
2660 - <th><?php esc_html_e('Intent Label', 'mxchat'); ?></th>
2661 - <th><?php esc_html_e('Phrases', 'mxchat'); ?></th>
2662 - <th><?php esc_html_e('Callback Function', 'mxchat'); ?></th>
2663 - <th><?php esc_html_e('Similarity Threshold', 'mxchat'); ?></th>
2664 - <th><?php esc_html_e('Actions', 'mxchat'); ?></th>
2665 - </tr>
2666 - </thead>
2667 - <tbody>
2668 - <?php if ($intents) :
2669 - foreach ($intents as $intent) :
2670 - $callback_function = $intent->callback_function;
2671 - $callback_label = isset($available_callbacks[$callback_function]['label'])
2672 - ? $available_callbacks[$callback_function]['label']
2673 - : $callback_function;
2674 - $threshold_value = isset($intent->similarity_threshold)
2675 - ? round($intent->similarity_threshold * 100)
2676 - : 85;
2677 -
2678 - // Check if this is a form intent (its intent_label starts with "Form ")
2679 - $is_form_intent = strpos($intent->intent_label, 'Form ') === 0;
4349 + <div class="mxchat-card-body">
4350 + <div class="mxchat-card-description">
4351 + <strong><?php esc_html_e('Type:', 'mxchat'); ?></strong>
4352 + <?php echo esc_html($callback_label); ?>
4353 + </div>
4354 +
4355 + <div class="mxchat-card-phrases">
4356 + <strong><?php esc_html_e('Trigger phrases:', 'mxchat'); ?></strong>
4357 + <div class="mxchat-phrases-preview">
4358 + <?php
4359 + // Check if the helper function exists, otherwise use a simple substring
4360 + if (method_exists($this, 'get_trimmed_phrases')) {
4361 + echo esc_html($this->get_trimmed_phrases($action->phrases));
4362 + } else {
4363 + echo esc_html(strlen($action->phrases) > 100 ?
4364 + substr($action->phrases, 0, 97) . '...' :
4365 + $action->phrases);
4366 + }
2680 4367 ?>
2681 - <tr<?php echo $is_form_intent ? ' class="mxchat-form-intent"' : ''; ?>>
2682 - <td>
2683 - <?php
2684 - if ($is_form_intent) {
2685 - // Attempt to extract the form ID from the intent_label.
2686 - preg_match('/Form (\d+)/', $intent->intent_label, $matches);
2687 - $form_id = isset($matches[1]) ? intval($matches[1]) : 0;
2688 - if ($form_id) {
2689 - global $wpdb;
2690 - $forms_table = $wpdb->prefix . 'mxchat_forms';
2691 - $form = $wpdb->get_row($wpdb->prepare("SELECT title FROM $forms_table WHERE id = %d", $form_id));
2692 - if ($form && !empty($form->title)) {
2693 - echo esc_html($form->title);
2694 - } else {
2695 - echo esc_html($intent->intent_label);
2696 - }
2697 - } else {
2698 - echo esc_html($intent->intent_label);
2699 - }
2700 - echo ' <span class="mxchat-badge">Form Intent</span>';
4368 + </div>
4369 + </div>
4370 +
4371 + <div class="mxchat-threshold-control">
4372 + <div class="mxchat-threshold-label">
4373 + <?php esc_html_e('Similarity Threshold:', 'mxchat'); ?>
4374 + <span class="mxchat-threshold-value"><?php echo esc_html($threshold_value); ?>%</span>
4375 + </div>
4376 + </div>
4377 +
4378 + <!-- Bot Availability Display (Read-only) -->
4379 + <div class="mxchat-card-bots">
4380 + <strong><?php esc_html_e('Assigned bots:', 'mxchat'); ?></strong>
4381 + <div class="mxchat-bot-badges">
4382 + <?php
4383 + foreach ($enabled_bots as $bot_id) {
4384 + if ($bot_id === 'default') {
4385 + echo '<span class="mxchat-bot-badge">' . esc_html__('Default Bot', 'mxchat') . '</span>';
4386 + } else {
4387 + // Try to get bot name from multi-bot manager
4388 + if (class_exists('MxChat_Multi_Bot_Core_Manager')) {
4389 + $multi_bot_manager = MxChat_Multi_Bot_Core_Manager::get_instance();
4390 + $available_bots = $multi_bot_manager->get_available_bots();
4391 + $bot_name = isset($available_bots[$bot_id]) ? $available_bots[$bot_id] : $bot_id;
4392 + echo '<span class="mxchat-bot-badge">' . esc_html($bot_name) . '</span>';
2701 4393 } else {
2702 - echo esc_html($intent->intent_label);
4394 + echo '<span class="mxchat-bot-badge">' . esc_html($bot_id) . '</span>';
2703 4395 }
2704 - ?>
2705 - </td>
2706 - <td class="mxchat-content-cell">
2707 - <?php echo esc_html($intent->phrases); ?>
2708 - </td>
2709 - <td><?php echo esc_html($callback_label); ?></td>
2710 - <td>
2711 - <!-- Similarity threshold adjustment (shown for all intents) -->
2712 - <form method="post"
2713 - action="<?php echo esc_url(admin_url('admin-post.php')); ?>"
2714 - class="mxchat-threshold-form">
2715 - <?php wp_nonce_field('mxchat_update_intent_threshold_nonce'); ?>
2716 - <input type="hidden" name="action"
2717 - value="mxchat_update_intent_threshold">
2718 - <input type="hidden" name="intent_id"
2719 - value="<?php echo esc_attr($intent->id); ?>">
2720 - <div class="mxchat-slider-group">
2721 - <input type="range"
2722 - name="intent_threshold"
2723 - id="intent_threshold_<?php echo esc_attr($intent->id); ?>"
2724 - min="70"
2725 - max="95"
2726 - value="<?php echo esc_attr($threshold_value); ?>"
2727 - class="mxchat-intent-slider"
2728 - oninput="document.getElementById('threshold_output_<?php echo esc_attr($intent->id); ?>').value = this.value + '%'">
2729 - <output id="threshold_output_<?php echo esc_attr($intent->id); ?>"
2730 - class="mxchat-intent-output">
2731 - <?php echo esc_html($threshold_value); ?>%
2732 - </output>
2733 - </div>
2734 - <button type="submit" class="mxchat-button-icon">
2735 - <span class="dashicons dashicons-saved"></span>
2736 - </button>
2737 - </form>
2738 - </td>
2739 - <td class="mxchat-actions-cell">
2740 - <?php if (!$is_form_intent) : ?>
2741 - <!-- Standard intents: allow edit and delete -->
2742 - <button type="button"
2743 - class="mxchat-button-icon mxchat-edit-button"
2744 - data-intent-id="<?php echo esc_attr($intent->id); ?>"
2745 - data-phrases="<?php echo esc_attr($intent->phrases); ?>">
2746 - <span class="dashicons dashicons-edit"></span>
2747 - </button>
2748 - <form method="post"
2749 - action="<?php echo esc_url(admin_url('admin-post.php')); ?>"
2750 - class="mxchat-delete-form"
2751 - onsubmit="return confirm('<?php esc_attr_e('Are you sure you want to delete this intent?', 'mxchat'); ?>');">
2752 - <?php wp_nonce_field('mxchat_delete_intent_nonce'); ?>
2753 - <input type="hidden" name="action" value="mxchat_delete_intent">
2754 - <input type="hidden" name="intent_id"
2755 - value="<?php echo esc_attr($intent->id); ?>">
2756 - <button type="submit" class="mxchat-button-icon">
2757 - <span class="dashicons dashicons-trash"></span>
2758 - </button>
2759 - </form>
2760 - <?php else : ?>
2761 - <!-- Form intents: show manage in forms button -->
2762 - <?php
2763 - preg_match('/Form (\d+)/', $intent->intent_label, $matches);
2764 - $form_id = isset($matches[1]) ? $matches[1] : '';
2765 - ?>
2766 - <a href="<?php echo esc_url(admin_url('admin.php?page=mxchat-forms&action=edit&form_id=' . $form_id)); ?>"
2767 - class="mxchat-button-secondary">
2768 - <?php esc_html_e('Manage in Forms', 'mxchat'); ?>
2769 - </a>
2770 - <?php endif; ?>
2771 - </td>
2772 - </tr>
2773 - <?php endforeach;
2774 - else : ?>
2775 - <tr>
2776 - <td colspan="5" class="mxchat-no-records">
2777 - <?php esc_html_e('No intents found.', 'mxchat'); ?>
2778 - </td>
2779 - </tr>
2780 - <?php endif; ?>
2781 - </tbody>
2782 - </table>
2783 - </div>
4396 + }
4397 + }
4398 + ?>
4399 + </div>
4400 + </div>
4401 + </div>
2784 4402
2785 - <?php if ($total_pages > 1) : ?>
2786 - <div class="mxchat-pagination">
2787 - <?php
2788 - echo paginate_links(array(
2789 - 'base' => add_query_arg('paged', '%#%'),
2790 - 'format' => '',
2791 - 'prev_text' => __('&laquo; Previous', 'mxchat'),
2792 - 'next_text' => __('Next &raquo;', 'mxchat'),
2793 - 'total' => $total_pages,
2794 - 'current' => $page
2795 - ));
2796 - ?>
4403 + <div class="mxchat-card-footer">
4404 + <?php
4405 + // Check if it's a form action
4406 + $is_form_action = preg_match('/Form (\d+)/', $action->intent_label, $form_matches);
4407 +
4408 + // Check if it's a recommendation flow action
4409 + $is_flow_action = preg_match('/Recommendation Flow (\d+)/', $action->intent_label, $flow_matches);
4410 +
4411 + if ($is_form_action) {
4412 + $form_id = isset($form_matches[1]) ? $form_matches[1] : '';
4413 + ?>
4414 + <a href="<?php echo esc_url(admin_url('admin.php?page=mxchat-forms&action=edit&form_id=' . $form_id)); ?>"
4415 + class="mxchat-button-primary">
4416 + <span class="dashicons dashicons-feedback"></span>
4417 + <?php esc_html_e('Edit Form', 'mxchat'); ?>
4418 + </a>
4419 + <?php } elseif ($is_flow_action) {
4420 + ?>
4421 + <a href="<?php echo esc_url(admin_url('admin.php?page=mxchat-smart-recommender')); ?>"
4422 + class="mxchat-button-primary">
4423 + <span class="dashicons dashicons-list-view"></span>
4424 + <?php esc_html_e('Manage Flows', 'mxchat'); ?>
4425 + </a>
4426 + <?php } else { ?>
4427 + <button type="button"
4428 + class="mxchat-button-secondary mxchat-edit-button"
4429 + data-action-id="<?php echo esc_attr($action->id); ?>"
4430 + data-phrases="<?php echo esc_attr($action->phrases); ?>"
4431 + data-label="<?php echo esc_attr($action->intent_label); ?>"
4432 + data-threshold="<?php echo esc_attr(round($action->similarity_threshold * 100)); ?>"
4433 + data-callback-function="<?php echo esc_attr($action->callback_function); ?>"
4434 + data-enabled-bots="<?php echo esc_attr(json_encode($enabled_bots)); ?>">
4435 + <span class="dashicons dashicons-edit"></span>
4436 + <?php esc_html_e('Edit', 'mxchat'); ?>
4437 + </button>
4438 + <form method="post"
4439 + action="<?php echo esc_url(admin_url('admin-post.php')); ?>"
4440 + class="mxchat-delete-form"
4441 + onsubmit="return confirm('<?php esc_attr_e('Are you sure you want to delete this action?', 'mxchat'); ?>');">
4442 + <?php wp_nonce_field('mxchat_delete_intent_nonce'); ?>
4443 + <input type="hidden" name="action" value="mxchat_delete_intent">
4444 + <input type="hidden" name="intent_id" value="<?php echo esc_attr($action->id); ?>">
4445 + <button type="submit" class="mxchat-button-text mxchat-delete-button">
4446 + <span class="dashicons dashicons-trash"></span>
4447 + <?php esc_html_e('Delete', 'mxchat'); ?>
4448 + </button>
4449 + </form>
4450 + <?php } ?>
4451 + </div>
4452 + </div>
4453 + <?php endforeach; ?>
4454 + <?php else : ?>
4455 + <!-- If no actions found -->
4456 + <div class="mxchat-no-actions">
4457 + <div class="mxchat-empty-state">
4458 + <span class="dashicons dashicons-format-chat"></span>
4459 + <h2><?php esc_html_e('No actions found', 'mxchat'); ?></h2>
4460 + <p><?php esc_html_e('Get started by creating your first action to enhance your chatbot.', 'mxchat'); ?></p>
4461 + <button type="button" id="mxchat-create-first-action" class="mxchat-button-primary">
4462 + <?php esc_html_e('Create Your First Action', 'mxchat'); ?>
4463 + </button>
4464 + </div>
2797 4465 </div>
2798 4466 <?php endif; ?>
2799 4467 </div>
2800 4468 </div>
2801 4469
2802 - <!-- Edit Modal -->
2803 -<div id="mxchat-edit-modal" class="mxchat-modal" style="display: none;">
4470 + <?php if ($total_pages > 1) : ?>
4471 + <div class="mxchat-pagination">
4472 + <?php
4473 + echo paginate_links(array(
4474 + 'base' => add_query_arg('paged', '%#%'),
4475 + 'format' => '',
4476 + 'prev_text' => __('&laquo; Previous', 'mxchat'),
4477 + 'next_text' => __('Next &raquo;', 'mxchat'),
4478 + 'total' => $total_pages,
4479 + 'current' => $page
4480 + ));
4481 + ?>
4482 + </div>
4483 + <?php endif; ?>
4484 +
4485 +<!-- Add/Edit Action Modal with Step-Based Approach -->
4486 +<!-- Complete Modal HTML with Defined Groups Variable -->
4487 +<div id="mxchat-action-modal" class="mxchat-modal" style="display: none;">
2804 4488 <div class="mxchat-modal-content">
2805 4489 <span class="mxchat-modal-close">&times;</span>
2806 - <div class="mxchat-card-header">
2807 - <h2><?php esc_html_e('Edit Intent Phrases', 'mxchat'); ?></h2>
2808 - </div>
2809 4490
2810 - <form id="mxchat-edit-intent-form" method="post"
2811 - action="<?php echo esc_url(admin_url('admin-post.php')); ?>">
2812 - <?php wp_nonce_field('mxchat_edit_intent_nonce'); ?>
2813 - <input type="hidden" name="action" value="mxchat_edit_intent">
2814 - <input type="hidden" name="intent_id" id="edit_intent_id">
4491 + <form id="mxchat-action-form" method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>">
4492 + <!-- Dynamic nonce field -->
4493 + <div id="action-nonce-container">
4494 + <?php wp_nonce_field('mxchat_add_intent_nonce', 'add_intent_nonce'); ?>
4495 + </div>
4496 + <input type="hidden" name="action" id="form_action_type" value="mxchat_add_intent">
4497 + <input type="hidden" name="intent_id" id="edit_action_id" value="">
4498 + <input type="hidden" name="callback_function" id="callback_function" value="">
2815 4499
2816 - <div class="mxchat-form-group">
2817 - <label for="edit_phrases">
2818 - <?php esc_html_e('Phrases (comma-separated)', 'mxchat'); ?>
2819 - </label>
2820 - <textarea name="phrases"
2821 - id="edit_phrases"
2822 - rows="5"
2823 - class="mxchat-intent-textarea"
2824 - required></textarea>
4500 + <!-- Step 1: Action Type Selection -->
4501 + <div id="mxchat-action-step-1" class="mxchat-action-step active">
4502 + <div class="mxchat-step-indicator">
4503 + <div class="mxchat-step-number">1</div>
4504 + <div class="mxchat-step-title"><?php esc_html_e('Select Action Type', 'mxchat'); ?></div>
4505 + </div>
4506 +
4507 + <div id="mxchat-action-type-selector" class="mxchat-action-type-selector">
4508 + <div class="mxchat-action-type-search">
4509 + <span class="dashicons dashicons-search"></span>
4510 + <input type="text" id="action-type-search" placeholder="<?php esc_attr_e('Search action types...', 'mxchat'); ?>" class="mxchat-action-type-search-input">
4511 + </div>
4512 +
4513 + <?php
4514 + // Get the callbacks - IMPORTANT: Define the $groups variable here
4515 + $groups = $this->mxchat_get_available_callbacks(true, true);
4516 + ?>
4517 +
4518 + <div class="mxchat-action-type-categories">
4519 + <button type="button" class="mxchat-category-button active" data-category="all"><?php esc_html_e('All', 'mxchat'); ?></button>
4520 + <?php
4521 + // Get unique categories from the defined groups
4522 + foreach ($groups as $group_label => $group_callbacks) :
4523 + $category_slug = sanitize_title($group_label);
4524 + ?>
4525 + <button type="button" class="mxchat-category-button" data-category="<?php echo esc_attr($category_slug); ?>"><?php echo esc_html($group_label); ?></button>
4526 + <?php endforeach; ?>
4527 + </div>
4528 +
4529 + <div class="mxchat-action-types-grid">
4530 + <?php
4531 + // Generate action cards from available callbacks
4532 + foreach ($groups as $group_label => $group_callbacks) :
4533 + $category_slug = sanitize_title($group_label);
4534 +
4535 + foreach ($group_callbacks as $function => $data) :
4536 + $label = $data['label'];
4537 + $pro_only = $data['pro_only'];
4538 + $icon = isset($data['icon']) ? $data['icon'] : 'admin-generic';
4539 + $description = isset($data['description']) ? $data['description'] : '';
4540 + $is_addon = isset($data['addon']) && $data['addon'] !== false;
4541 + $addon_name = isset($data['addon_name']) ? $data['addon_name'] : '';
4542 + $is_installed = isset($data['installed']) ? $data['installed'] : true;
4543 +
4544 + // Determine card status and styling
4545 + $card_class = 'mxchat-action-type-card';
4546 + $icon_class = 'mxchat-action-type-icon';
4547 + $status_badge = '';
4548 +
4549 + if ($pro_only && !$this->is_activated) {
4550 + // Pro feature but no Pro license
4551 + $icon_class .= ' pro-feature';
4552 + $status_badge = '<span class="mxchat-pro-badge">' . esc_html__('Pro', 'mxchat') . '</span>';
4553 + }
4554 +
4555 + if ($is_addon && !$is_installed) {
4556 + // Add-on not installed
4557 + $card_class .= ' not-installed';
4558 + $status_badge .= '<span class="mxchat-addon-badge">' . esc_html__('Add-on Required', 'mxchat') . '</span>';
4559 + }
4560 +
4561 + // Default description if none provided
4562 + if (empty($description)) {
4563 + $description = sprintf(
4564 + esc_html__('Use the %s action in your chatbot', 'mxchat'),
4565 + $label
4566 + );
4567 + }
4568 + ?>
4569 + <div class="<?php echo esc_attr($card_class); ?>"
4570 + data-category="<?php echo esc_attr($category_slug); ?>"
4571 + data-value="<?php echo esc_attr($function); ?>"
4572 + data-label="<?php echo esc_attr($label); ?>"
4573 + data-pro="<?php echo $pro_only ? 'true' : 'false'; ?>"
4574 + data-addon="<?php echo esc_attr($is_addon ? $data['addon'] : ''); ?>"
4575 + data-installed="<?php echo $is_installed ? 'true' : 'false'; ?>">
4576 + <div class="<?php echo esc_attr($icon_class); ?>">
4577 + <span class="dashicons dashicons-<?php echo esc_attr($icon); ?>"></span>
4578 + </div>
4579 + <div class="mxchat-action-type-info">
4580 + <h4><?php echo esc_html($label); ?></h4>
4581 + <p><?php echo esc_html($description); ?></p>
4582 + <?php if (!empty($status_badge)) : ?>
4583 + <?php echo $status_badge; ?>
4584 + <?php endif; ?>
4585 +
4586 + <?php if ($is_addon && !$is_installed) : ?>
4587 + <div class="mxchat-addon-info">
4588 + <?php echo esc_html(sprintf(
4589 + __('Requires %s', 'mxchat'),
4590 + $addon_name
4591 + )); ?>
4592 + </div>
4593 + <?php endif; ?>
4594 + </div>
4595 + </div>
4596 + <?php endforeach;
4597 + endforeach; ?>
4598 + </div>
4599 + </div>
4600 +
4601 + <div class="mxchat-modal-actions">
4602 + <button type="button" class="mxchat-button-secondary mxchat-modal-cancel">
4603 + <?php esc_html_e('Cancel', 'mxchat'); ?>
4604 + </button>
4605 + </div>
2825 4606 </div>
2826 4607
2827 - <div class="mxchat-modal-actions">
2828 - <button type="submit" class="mxchat-button-primary">
2829 - <?php esc_html_e('Update Phrases', 'mxchat'); ?>
2830 - </button>
2831 - <button type="button" class="mxchat-button-secondary mxchat-modal-cancel">
2832 - <?php esc_html_e('Cancel', 'mxchat'); ?>
2833 - </button>
4608 + <!-- Step 2: Action Configuration -->
4609 + <div id="mxchat-action-step-2" class="mxchat-action-step">
4610 + <div class="mxchat-step-indicator">
4611 + <div class="mxchat-step-number">2</div>
4612 + <div class="mxchat-step-title"><?php esc_html_e('Configure Action', 'mxchat'); ?></div>
4613 + </div>
4614 +
4615 + <div class="mxchat-selected-action">
4616 + <button type="button" class="mxchat-back-button" id="mxchat-back-to-step-1">
4617 + <span class="dashicons dashicons-arrow-left-alt"></span>
4618 + <?php esc_html_e('Back to Action Types', 'mxchat'); ?>
4619 + </button>
4620 + <div class="mxchat-selected-action-info">
4621 + <div id="selected-action-icon" class="mxchat-action-type-icon">
4622 + <span class="dashicons dashicons-admin-generic"></span>
4623 + </div>
4624 + <div class="mxchat-selected-action-details">
4625 + <h3 id="selected-action-title"><?php esc_html_e('Selected Action', 'mxchat'); ?></h3>
4626 + <p id="selected-action-description"><?php esc_html_e('Configure this action for your chatbot', 'mxchat'); ?></p>
4627 + </div>
4628 + </div>
4629 + </div>
4630 +
4631 + <div class="mxchat-form-group">
4632 + <label for="intent_label">
4633 + <?php esc_html_e('Action Label (For your reference only)', 'mxchat'); ?>
4634 + </label>
4635 + <input name="intent_label" type="text" id="intent_label" required
4636 + class="mxchat-intent-input"
4637 + placeholder="<?php esc_attr_e('Example: Newsletter Signup', 'mxchat'); ?>">
4638 + </div>
4639 +
4640 + <div class="mxchat-form-group">
4641 + <label for="phrases">
4642 + <?php esc_html_e('Trigger Phrases (comma-separated)', 'mxchat'); ?>
4643 + </label>
4644 + <textarea name="phrases" id="action_phrases" rows="5" required
4645 + class="mxchat-intent-textarea"
4646 + placeholder="<?php esc_attr_e('Example: sign me up, subscribe me, I want to join, add me to the newsletter', 'mxchat'); ?>"></textarea>
4647 + </div>
4648 +
4649 + <div class="mxchat-form-group">
4650 + <label for="similarity_threshold">
4651 + <?php esc_html_e('Similarity Threshold', 'mxchat'); ?>
4652 + <span class="mxchat-threshold-value-display">85%</span>
4653 + </label>
4654 + <div class="mxchat-slider-group modal-slider">
4655 + <input type="range"
4656 + name="similarity_threshold"
4657 + id="similarity_threshold"
4658 + min="10"
4659 + max="95"
4660 + value="85"
4661 + class="mxchat-intent-slider"
4662 + oninput="document.querySelector('.mxchat-threshold-value-display').textContent = this.value + '%'">
4663 + </div>
4664 + <div class="mxchat-threshold-hint">
4665 + <?php esc_html_e('Lower values (10-30) make the action trigger more easily. Higher values (70-95) require more exact matches.', 'mxchat'); ?>
4666 + </div>
4667 + </div>
4668 +
4669 + <!-- Bot Selection Section -->
4670 + <div class="mxchat-form-group">
4671 + <label for="enabled_bots">
4672 + <?php esc_html_e('Which bots should we enable this action for?', 'mxchat'); ?>
4673 + </label>
4674 + <div class="mxchat-bot-selector">
4675 + <div class="mxchat-bot-option">
4676 + <label class="mxchat-checkbox-label">
4677 + <input type="checkbox"
4678 + name="enabled_bots[]"
4679 + value="default"
4680 + id="bot_default"
4681 + checked="checked">
4682 + <span class="mxchat-checkmark"></span>
4683 + <?php esc_html_e('Default Bot', 'mxchat'); ?>
4684 + </label>
4685 + </div>
4686 +
4687 + <?php if (class_exists('MxChat_Multi_Bot_Core_Manager')) :
4688 + $multi_bot_manager = MxChat_Multi_Bot_Core_Manager::get_instance();
4689 + $available_bots = $multi_bot_manager->get_available_bots();
4690 +
4691 + foreach ($available_bots as $bot_id => $bot_name) :
4692 + if ($bot_id === 'default') continue; // Skip default, already shown above
4693 + ?>
4694 + <div class="mxchat-bot-option">
4695 + <label class="mxchat-checkbox-label">
4696 + <input type="checkbox"
4697 + name="enabled_bots[]"
4698 + value="<?php echo esc_attr($bot_id); ?>"
4699 + id="bot_<?php echo esc_attr($bot_id); ?>">
4700 + <span class="mxchat-checkmark"></span>
4701 + <?php echo esc_html($bot_name); ?>
4702 + </label>
4703 + </div>
4704 + <?php
4705 + endforeach;
4706 + endif; ?>
4707 + </div>
4708 + </div>
4709 +
4710 + <div class="mxchat-modal-actions">
4711 + <button type="button" class="mxchat-button-secondary mxchat-modal-cancel">
4712 + <?php esc_html_e('Cancel', 'mxchat'); ?>
4713 + </button>
4714 + <button type="submit" class="mxchat-button-primary" id="mxchat-save-action-btn">
4715 + <?php esc_html_e('Save Action', 'mxchat'); ?>
4716 + </button>
4717 + </div>
2834 4718 </div>
2835 4719 </form>
2836 4720 </div>
2837 4721 </div>
2838 4722
2839 - </div><!-- .mxchat-content -->
4723 +<div id="mxchat-action-loading" class="mxchat-action-loading" style="display: none;">
4724 + <div class="mxchat-action-loading-spinner"></div>
4725 + <div class="mxchat-action-loading-text">
4726 + <?php esc_html_e('Saving action, please wait...', 'mxchat'); ?>
4727 + </div>
4728 +</div>
2840 4729 </div><!-- .mxchat-wrapper -->
4730 + <?php
4731 +}
4732 +private function get_trimmed_phrases($phrases, $max_length = 100) {
4733 + if (strlen($phrases) <= $max_length) {
4734 + return $phrases;
4735 + }
2841 4736
2842 - <div id="mxchat-intent-loading" class="mxchat-intent-loading" style="display: none;">
2843 - <div class="mxchat-intent-loading-spinner"></div>
2844 - <div class="mxchat-intent-loading-text">
2845 - <?php esc_html_e('Saving intent, please wait...', 'mxchat'); ?>
2846 - </div>
2847 - </div>
2848 -<?php
4737 + $trimmed = substr($phrases, 0, $max_length);
4738 + $last_comma = strrpos($trimmed, ',');
4739 +
4740 + if ($last_comma !== false) {
4741 + $trimmed = substr($trimmed, 0, $last_comma);
4742 + }
4743 +
4744 + return $trimmed . '...';
2849 4745 }
4746 +public function mxchat_add_enabled_column_to_intents() {
4747 + global $wpdb;
4748 + $table_name = $wpdb->prefix . 'mxchat_intents';
2850 4749
4750 + // Check if the column already exists
4751 + $columns = $wpdb->get_results("SHOW COLUMNS FROM $table_name LIKE 'enabled'");
2851 4752
4753 + if (empty($columns)) {
4754 + // Add the column with default value of 1 (enabled)
4755 + $wpdb->query("ALTER TABLE $table_name ADD COLUMN enabled TINYINT(1) NOT NULL DEFAULT 1");
4756 + }
4757 +}
2852 4758
2853 -/**
2854 - * Handle editing of intent phrases
2855 - *
2856 - * @since 1.0.0
2857 - * @return void
2858 - */
2859 -public function handle_edit_intent() {
2860 - // Verify nonce and user capabilities
2861 - if (!isset($_POST['_wpnonce']) || !wp_verify_nonce($_POST['_wpnonce'], 'mxchat_edit_intent_nonce')) {
2862 - wp_die(esc_html__('Security check failed.', 'mxchat'));
4759 +public function mxchat_handle_delete_intent() {
4760 + if ( ! current_user_can( 'manage_options' ) ) {
4761 + wp_die( esc_html__('Unauthorized user', 'mxchat') );
2863 4762 }
2864 4763
4764 + check_admin_referer('mxchat_delete_intent_nonce');
4765 +
4766 + if (isset($_POST['intent_id'])) {
4767 + global $wpdb;
4768 + $table_name = $wpdb->prefix . 'mxchat_intents';
4769 + $intent_id = intval($_POST['intent_id']);
4770 +
4771 + $wpdb->delete($table_name, ['id' => $intent_id], ['%d']);
4772 + }
4773 +
4774 + wp_safe_redirect(admin_url('admin.php?page=mxchat-actions'));
4775 + exit;
4776 +}
4777 +public function mxchat_handle_edit_intent() {
4778 + // Security checks (nonce and permissions)
2865 4779 if (!current_user_can('manage_options')) {
2866 - wp_die(esc_html__('You do not have permission to perform this action.', 'mxchat'));
4780 + wp_die(esc_html__('Unauthorized user', 'mxchat'));
2867 4781 }
4782 + check_admin_referer('mxchat_edit_intent');
2868 4783
2869 - // Validate and sanitize input
4784 + // Get POST data
2870 4785 $intent_id = isset($_POST['intent_id']) ? absint($_POST['intent_id']) : 0;
2871 - if (!$intent_id) {
2872 - wp_die(esc_html__('Invalid intent ID.', 'mxchat'));
4786 + $intent_label = isset($_POST['intent_label']) ? sanitize_text_field($_POST['intent_label']) : '';
4787 + $phrases_input = isset($_POST['phrases']) ? sanitize_textarea_field($_POST['phrases']) : '';
4788 + $threshold_percentage = isset($_POST['similarity_threshold']) ? intval($_POST['similarity_threshold']) : 85;
4789 + $similarity_threshold = min(95, max(10, $threshold_percentage)) / 100; // Convert to 0.10–0.95
4790 +
4791 + // Handle enabled_bots
4792 + $enabled_bots = isset($_POST['enabled_bots']) ? $_POST['enabled_bots'] : array('default');
4793 + $enabled_bots = array_map('sanitize_text_field', $enabled_bots);
4794 +
4795 + // Ensure default is always included for backward compatibility
4796 + if (!in_array('default', $enabled_bots)) {
4797 + $enabled_bots[] = 'default';
2873 4798 }
4799 +
4800 + $enabled_bots_json = json_encode($enabled_bots);
2874 4801
2875 - $phrases_input = isset($_POST['phrases']) ? sanitize_textarea_field($_POST['phrases']) : '';
2876 - if (empty($phrases_input)) {
2877 - wp_die(esc_html__('Phrases cannot be empty.', 'mxchat'));
4802 + // Validate inputs
4803 + if (!$intent_id || empty($intent_label) || empty($phrases_input)) {
4804 + $this->handle_embedding_error(__('Invalid input. Please ensure all fields are filled out.', 'mxchat'));
4805 + return;
2878 4806 }
2879 4807
2880 - // Process phrases the same way as in intent creation
2881 4808 $phrases_array = array_map('sanitize_text_field', array_filter(array_map('trim', explode(',', $phrases_input))));
2882 -
2883 4809 if (empty($phrases_array)) {
2884 - wp_die(esc_html__('Please enter at least one valid phrase.', 'mxchat'));
4810 + $this->handle_embedding_error(__('Please enter at least one valid phrase.', 'mxchat'));
4811 + return;
2885 4812 }
2886 4813
2887 - // Generate embeddings and combine them
4814 + // Generate embeddings with improved error handling
2888 4815 $vectors = [];
4816 + $failed_phrases = [];
4817 +
2889 4818 foreach ($phrases_array as $phrase) {
2890 4819 $embedding_vector = $this->mxchat_generate_embedding($phrase, $this->options['api_key']);
2891 4820 if (is_array($embedding_vector)) {
2892 4821 $vectors[] = $embedding_vector;
2893 4822 } else {
2894 - wp_die(esc_html__('Error generating embedding for phrase: ', 'mxchat') . esc_html($phrase));
4823 + $failed_phrases[] = $phrase;
2895 4824 }
2896 4825 }
2897 4826
4827 + if (!empty($failed_phrases)) {
4828 + $this->handle_embedding_error(
4829 + sprintf(
4830 + __('Error generating embeddings for phrases: %s. Check your embedding API.', 'mxchat'),
4831 + implode(', ', $failed_phrases)
4832 + )
4833 + );
4834 + return;
4835 + }
4836 +
2898 4837 if (empty($vectors)) {
2899 - wp_die(esc_html__('No valid embeddings generated. Please check your phrases.', 'mxchat'));
4838 + $this->handle_embedding_error(__('No valid embeddings generated. Please check your phrases.', 'mxchat'));
4839 + return;
2900 4840 }
2901 4841
2902 - // Create combined vector just like in intent creation
2903 4842 $combined_vector = $this->mxchat_average_vectors($vectors);
2904 4843 $serialized_vector = maybe_serialize($combined_vector);
2905 4844
4845 + // Update the database
2906 4846 global $wpdb;
2907 4847 $table_name = $wpdb->prefix . 'mxchat_intents';
2908 4848
2909 - // Update the intent with both new phrases and the combined vector
2910 4849 $result = $wpdb->update(
2911 4850 $table_name,
2912 4851 array(
4852 + 'intent_label' => $intent_label,
2913 4853 'phrases' => implode(', ', $phrases_array),
2914 - 'embedding_vector' => $serialized_vector
4854 + 'embedding_vector' => $serialized_vector,
4855 + 'similarity_threshold' => $similarity_threshold,
4856 + 'enabled_bots' => $enabled_bots_json // Include enabled_bots in update
2915 4857 ),
2916 4858 array('id' => $intent_id),
2917 - array('%s', '%s'),
2918 - array('%d')
4859 + array('%s', '%s', '%s', '%f', '%s'), // Format: string, string, string, float, string
4860 + array('%d') // Where format: integer
2919 4861 );
2920 4862
2921 4863 if (false === $result) {
2922 - wp_die(esc_html__('Failed to update intent.', 'mxchat'));
4864 + $this->handle_embedding_error(__('Failed to update action in database.', 'mxchat'));
4865 + return;
2923 4866 }
2924 4867
2925 - // Redirect back to the intents page with a success message
4868 + // Set success message and redirect
4869 + set_transient('mxchat_admin_notice_success', __('Intent updated successfully!', 'mxchat'), 60);
4870 +
2926 4871 $redirect_url = add_query_arg(
2927 4872 array(
2928 - 'page' => 'mxchat-intents',
2929 - 'updated' => 'true'
4873 + 'page' => 'mxchat-actions'
2930 4874 ),
2931 4875 admin_url('admin.php')
2932 4876 );
2933 -
2934 4877 wp_safe_redirect($redirect_url);
2935 4878 exit;
2936 4879 }
2937 -public function mxchat_handle_update_intent_threshold() {
2938 - if ( ! current_user_can( 'manage_options' ) ) {
2939 - wp_die( esc_html__('Unauthorized user', 'mxchat') );
2940 - }
2941 -
2942 - check_admin_referer('mxchat_update_intent_threshold_nonce');
2943 -
2944 - if (isset($_POST['intent_id'], $_POST['intent_threshold'])) {
2945 - global $wpdb;
2946 - $table_name = $wpdb->prefix . 'mxchat_intents';
2947 - $intent_id = intval($_POST['intent_id']);
2948 - $threshold_percentage = max(70, min(95, intval($_POST['intent_threshold'])));
2949 - $similarity_threshold = $threshold_percentage / 100;
2950 -
2951 - $wpdb->update(
2952 - $table_name,
2953 - ['similarity_threshold' => $similarity_threshold],
2954 - ['id' => $intent_id],
2955 - ['%f'],
2956 - ['%d']
2957 - );
2958 - }
2959 -
2960 - wp_safe_redirect(admin_url('admin.php?page=mxchat-intents'));
2961 - exit;
2962 -}
2963 -
2964 4880 public function mxchat_handle_add_intent() {
2965 - if ( ! current_user_can( 'manage_options' ) ) {
2966 - wp_die( esc_html__('Unauthorized user', 'mxchat') );
4881 + if (!current_user_can('manage_options')) {
4882 + wp_die(esc_html__('Unauthorized user', 'mxchat'));
2967 4883 }
2968 -
2969 4884 check_admin_referer('mxchat_add_intent_nonce');
2970 -
2971 4885 global $wpdb;
2972 4886 $table_name = $wpdb->prefix . 'mxchat_intents';
2973 -
4887 +
4888 + // Sanitize and get form data
2974 4889 $intent_label = isset($_POST['intent_label']) ? sanitize_text_field($_POST['intent_label']) : '';
2975 4890 $phrases_input = isset($_POST['phrases']) ? sanitize_textarea_field($_POST['phrases']) : '';
2976 4891 $callback_function = isset($_POST['callback_function']) ? sanitize_text_field($_POST['callback_function']) : '';
2977 - $default_threshold = 0.85;
2978 -
4892 +
4893 + // Get similarity threshold from form (convert percentage to decimal)
4894 + $similarity_threshold = isset($_POST['similarity_threshold']) ? floatval($_POST['similarity_threshold']) / 100 : 0.85;
4895 +
4896 + // Handle enabled_bots
4897 + $enabled_bots = isset($_POST['enabled_bots']) ? $_POST['enabled_bots'] : array('default');
4898 + $enabled_bots = array_map('sanitize_text_field', $enabled_bots);
4899 +
4900 + // Ensure default is always included for backward compatibility with existing actions
4901 + if (!in_array('default', $enabled_bots)) {
4902 + $enabled_bots[] = 'default';
4903 + }
4904 +
4905 + $enabled_bots_json = json_encode($enabled_bots);
4906 +
4907 + // Validate required fields
2979 4908 if (empty($intent_label) || empty($callback_function) || empty($phrases_input)) {
2980 - wp_die( esc_html__('Invalid input. Please ensure all fields are filled out.', 'mxchat') );
4909 + $this->handle_embedding_error(__('Invalid input. Please ensure all fields are filled out.', 'mxchat'));
4910 + return;
2981 4911 }
2982 -
4912 +
4913 + // Validate callback function
2983 4914 $available_callbacks = $this->mxchat_get_available_callbacks();
2984 -
2985 4915 if (!array_key_exists($callback_function, $available_callbacks)) {
2986 - wp_die( esc_html__('Invalid callback function selected.', 'mxchat') );
4916 + $this->handle_embedding_error(__('Invalid callback function selected.', 'mxchat'));
4917 + return;
2987 4918 }
2988 -
4919 +
4920 + // Check Pro requirements
2989 4921 $is_pro_only = $available_callbacks[$callback_function]['pro_only'];
2990 4922 if ($is_pro_only && !$this->is_activated) {
2991 - wp_die( esc_html__('This callback function is available in the Pro version only.', 'mxchat') );
4923 + $this->handle_embedding_error(__('This callback function is available in the Pro version only.', 'mxchat'));
4924 + return;
2992 4925 }
2993 -
4926 +
4927 + // Process phrases
2994 4928 $phrases_array = array_map('sanitize_text_field', array_filter(array_map('trim', explode(',', $phrases_input))));
2995 -
2996 4929 if (empty($phrases_array)) {
2997 - wp_die( esc_html__('Please enter at least one valid phrase.', 'mxchat') );
4930 + $this->handle_embedding_error(__('Please enter at least one valid phrase.', 'mxchat'));
4931 + return;
2998 4932 }
2999 -
4933 +
4934 + // Generate embeddings with improved error handling
3000 4935 $vectors = [];
4936 + $failed_phrases = [];
3001 4937 foreach ($phrases_array as $phrase) {
3002 4938 $embedding_vector = $this->mxchat_generate_embedding($phrase, $this->options['api_key']);
3003 4939 if (is_array($embedding_vector)) {
3004 4940 $vectors[] = $embedding_vector;
3005 4941 } else {
3006 - wp_die( esc_html__('Error generating embedding for phrase: ', 'mxchat') . esc_html($phrase) );
4942 + $failed_phrases[] = $phrase;
3007 4943 }
3008 4944 }
4945 +
4946 + // Check for embedding failures
4947 + if (!empty($failed_phrases)) {
4948 + $this->handle_embedding_error(
4949 + sprintf(
4950 + __('Error generating embeddings for phrases: %s. Check your embedding API.', 'mxchat'),
4951 + implode(', ', $failed_phrases)
4952 + )
4953 + );
4954 + return;
4955 + }
4956 +
4957 + if (empty($vectors)) {
4958 + $this->handle_embedding_error(__('No valid embeddings generated. Please check your phrases.', 'mxchat'));
4959 + return;
4960 + }
4961 +
4962 + // Create combined vector and insert into database
4963 + $combined_vector = $this->mxchat_average_vectors($vectors);
4964 + $serialized_vector = maybe_serialize($combined_vector);
4965 +
4966 + $result = $wpdb->insert($table_name, [
4967 + 'intent_label' => $intent_label,
4968 + 'phrases' => implode(', ', $phrases_array),
4969 + 'embedding_vector' => $serialized_vector,
4970 + 'callback_function' => $callback_function,
4971 + 'similarity_threshold' => $similarity_threshold,
4972 + 'enabled_bots' => $enabled_bots_json // NEW field
4973 + ]);
4974 +
4975 + if ($result === false) {
4976 + $this->handle_embedding_error(__('Database error: ', 'mxchat') . $wpdb->last_error);
4977 + return;
4978 + }
4979 +
4980 + // Set success message and redirect
4981 + set_transient('mxchat_admin_notice_success', __('New intent added successfully!', 'mxchat'), 60);
4982 + wp_safe_redirect(admin_url('admin.php?page=mxchat-actions'));
4983 + exit;
4984 +}
3009 4985
3010 - if (!empty($vectors)) {
3011 - $combined_vector = $this->mxchat_average_vectors($vectors);
3012 - $serialized_vector = maybe_serialize($combined_vector);
3013 4986
3014 - $result = $wpdb->insert($table_name, [
3015 - 'intent_label' => $intent_label,
3016 - 'phrases' => implode(', ', $phrases_array),
3017 - 'embedding_vector' => $serialized_vector,
3018 - 'callback_function' => $callback_function,
3019 - 'similarity_threshold' => $default_threshold,
3020 - ]);
3021 4987
3022 - if ($result === false) {
3023 - wp_die( esc_html__('Database error: ', 'mxchat') . esc_html($wpdb->last_error) );
4988 +
4989 +private function handle_embedding_error($message, $redirect = true) {
4990 + // Store the error message in the existing transient
4991 + set_transient('mxchat_admin_notice_error', $message, 60);
4992 +
4993 + if ($redirect) {
4994 + // Redirect back to the actions page
4995 + $redirect_url = add_query_arg(
4996 + array(
4997 + 'page' => 'mxchat-actions'
4998 + ),
4999 + admin_url('admin.php')
5000 + );
5001 + wp_safe_redirect($redirect_url);
5002 + exit;
5003 + }
5004 +}
5005 +
5006 +
5007 +/**
5008 + * Enhanced get_available_callbacks function with form action exclusion
5009 + *
5010 + * @param bool $grouped Whether to return callbacks grouped by category
5011 + * @param bool $include_all Whether to include all potential actions (even if add-on not installed)
5012 + * @return array Callbacks data with icons, descriptions and availability status
5013 + */
5014 +private function mxchat_get_available_callbacks($grouped = false, $include_all = true) {
5015 + // Load WordPress plugin functions if needed
5016 + if (!function_exists('get_plugins')) {
5017 + require_once ABSPATH . 'wp-admin/includes/plugin.php';
5018 + }
5019 +
5020 + // Get active plugins
5021 + $active_plugins = get_option('active_plugins', array());
5022 +
5023 + // Functions to exclude from the action selector only if Pro is activated
5024 + // If user doesn't have Pro, show these so they can see what they're missing
5025 + $excluded_when_pro_active_functions = array(
5026 + 'mxchat_handle_form_collection' // Forms add-on action
5027 + );
5028 +
5029 + // Always excluded functions (regardless of Pro status)
5030 + $always_excluded_functions = array();
5031 +
5032 + // Combine exclusion lists based on Pro activation status
5033 + $excluded_functions = $always_excluded_functions;
5034 + if ($this->is_activated) {
5035 + // Only exclude add-on managed functions if Pro is active
5036 + $excluded_functions = array_merge($excluded_functions, $excluded_when_pro_active_functions);
5037 + }
5038 +
5039 + // Define add-on plugin files and their corresponding action functions
5040 + $addon_plugins = array(
5041 + 'mxchat-woo/mxchat-woo.php' => array(
5042 + 'functions' => array(
5043 + 'mxchat_handle_product_recommendations',
5044 + 'mxchat_handle_order_history',
5045 + 'mxchat_show_product_card',
5046 + 'mxchat_add_to_cart',
5047 + 'mxchat_checkout_redirect'
5048 + ),
5049 + 'name' => __('WooCommerce Add-on', 'mxchat'),
5050 + 'pro_required' => true
5051 + ),
5052 + 'mxchat-perplexity/mxchat-perplexity.php' => array(
5053 + 'functions' => array('mxchat_perplexity_research'),
5054 + 'name' => __('Perplexity Add-on', 'mxchat'),
5055 + 'pro_required' => true
5056 + ),
5057 + 'mxchat-forms/mxchat-forms.php' => array(
5058 + 'functions' => array('mxchat_handle_form_collection'),
5059 + 'name' => __('Forms Add-on', 'mxchat'),
5060 + 'pro_required' => true
5061 + ),
5062 + // Add other add-ons and their functions here
5063 + );
5064 +
5065 + // Get the functions that are provided by active add-ons
5066 + $addon_provided_functions = array();
5067 + $addon_function_mapping = array(); // Maps functions to their add-on info
5068 +
5069 + // Check which add-ons are active
5070 + foreach ($addon_plugins as $plugin_file => $addon_info) {
5071 + $is_active = in_array($plugin_file, $active_plugins);
5072 +
5073 + // For each function in this addon
5074 + foreach ($addon_info['functions'] as $function) {
5075 + // Consider a function installed only if:
5076 + // 1. The add-on is active AND
5077 + // 2. Either it doesn't require Pro OR Pro is activated
5078 + $is_installed = $is_active && (!$addon_info['pro_required'] || $this->is_activated);
5079 +
5080 + // If the add-on is installed, mark this function as provided by an add-on
5081 + if ($is_installed) {
5082 + $addon_provided_functions[] = $function;
5083 + }
5084 +
5085 + // Store addon info for this function regardless of installation status
5086 + $addon_function_mapping[$function] = array(
5087 + 'addon' => basename(dirname($plugin_file)),
5088 + 'addon_name' => $addon_info['name'],
5089 + 'pro_required' => $addon_info['pro_required'],
5090 + 'is_active' => $is_active,
5091 + 'is_installed' => $is_installed
5092 + );
3024 5093 }
3025 - } else {
3026 - wp_die( esc_html__('No valid embeddings generated. Please check your phrases.', 'mxchat') );
3027 5094 }
3028 5095
3029 - wp_safe_redirect(admin_url('admin.php?page=mxchat-intents'));
3030 - exit;
3031 -}
5096 + // Core callbacks - always available in the base plugin
5097 + $core_callbacks = array(
5098 + 'mxchat_handle_email_capture' => array(
5099 + 'label' => __('Loops Email Capture', 'mxchat'),
5100 + 'pro_only' => false,
5101 + 'group' => __('Customer Engagement', 'mxchat'),
5102 + 'icon' => 'email-alt',
5103 + 'description' => __('Collect visitor emails for your mailing list in Loops', 'mxchat'),
5104 + 'addon' => false, // Not from an add-on
5105 + 'installed' => true // Always installed with base plugin
5106 + ),
5107 + 'mxchat_handle_search_request' => array(
5108 + 'label' => __('Brave Web Search', 'mxchat'),
5109 + 'pro_only' => false,
5110 + 'group' => __('Search Features', 'mxchat'),
5111 + 'icon' => 'search',
5112 + 'description' => __('Let users search the web directly from the chat', 'mxchat'),
5113 + 'addon' => false,
5114 + 'installed' => true
5115 + ),
5116 + 'mxchat_handle_image_search_request' => array(
5117 + 'label' => __('Brave Image Search', 'mxchat'),
5118 + 'pro_only' => false,
5119 + 'group' => __('Search Features', 'mxchat'),
5120 + 'icon' => 'format-image',
5121 + 'description' => __('Search and display images in the chat conversation', 'mxchat'),
5122 + 'addon' => false,
5123 + 'installed' => true
5124 + ),
5125 + // Pro core features - check is_activated property
5126 + 'mxchat_generate_image' => array(
5127 + 'label' => __('Generate Image', 'mxchat'),
5128 + 'pro_only' => false,
5129 + 'group' => __('Other Features', 'mxchat'),
5130 + 'icon' => 'art',
5131 + 'description' => __('Create images with DALL-E 3 from OpenAI (requires OpenAI API key)', 'mxchat'),
5132 + 'addon' => false,
5133 + 'installed' => true
5134 + ),
5135 + 'mxchat_handle_pdf_discussion' => array(
5136 + 'label' => __('Chat with PDF', 'mxchat'),
5137 + 'pro_only' => false,
5138 + 'group' => __('Other Features', 'mxchat'),
5139 + 'icon' => 'media-document',
5140 + 'description' => __('Answer questions about uploaded PDF documents', 'mxchat'),
5141 + 'addon' => false,
5142 + 'installed' => true
5143 + ),
5144 + 'mxchat_live_agent_handover' => array(
5145 + 'label' => __('Slack Live Agent', 'mxchat'),
5146 + 'pro_only' => false,
5147 + 'group' => __('Customer Engagement', 'mxchat'),
5148 + 'icon' => 'admin-users',
5149 + 'description' => __('Transfer conversation to a human support agent on Slack', 'mxchat'),
5150 + 'addon' => false,
5151 + 'installed' => true
5152 + ),
5153 + 'mxchat_handle_switch_to_chatbot_intent' => array(
5154 + 'label' => __('Back to Chatbot', 'mxchat'),
5155 + 'pro_only' => false,
5156 + 'group' => __('Customer Engagement', 'mxchat'),
5157 + 'icon' => 'backup',
5158 + 'description' => __('Return from live agent mode to AI chatbot', 'mxchat'),
5159 + 'addon' => false,
5160 + 'installed' => true
5161 + ),
5162 + );
3032 5163
5164 + // Add-on callbacks with placeholders - only include if the add-on is NOT active
5165 + $addon_callbacks = array(
5166 + // WooCommerce Add-on
5167 + 'mxchat_handle_product_recommendations' => array(
5168 + 'label' => __('Product Recommendations', 'mxchat'),
5169 + 'pro_only' => true,
5170 + 'group' => __('WooCommerce Features', 'mxchat'),
5171 + 'icon' => 'cart',
5172 + 'description' => __('Suggest products based on customer preferences', 'mxchat'),
5173 + ),
5174 + 'mxchat_handle_order_history' => array(
5175 + 'label' => __('Order History', 'mxchat'),
5176 + 'pro_only' => true,
5177 + 'group' => __('WooCommerce Features', 'mxchat'),
5178 + 'icon' => 'clipboard',
5179 + 'description' => __('Allow customers to check their order status', 'mxchat'),
5180 + ),
5181 + 'mxchat_show_product_card' => array(
5182 + 'label' => __('Show Product Card', 'mxchat'),
5183 + 'pro_only' => true,
5184 + 'group' => __('WooCommerce Features', 'mxchat'),
5185 + 'icon' => 'products',
5186 + 'description' => __('Display product information in the chat', 'mxchat'),
5187 + ),
5188 + 'mxchat_add_to_cart' => array(
5189 + 'label' => __('Add to Cart', 'mxchat'),
5190 + 'pro_only' => true,
5191 + 'group' => __('WooCommerce Features', 'mxchat'),
5192 + 'icon' => 'plus-alt',
5193 + 'description' => __('Add products to cart directly from chat', 'mxchat'),
5194 + ),
5195 + 'mxchat_checkout_redirect' => array(
5196 + 'label' => __('Proceed to Checkout', 'mxchat'),
5197 + 'pro_only' => true,
5198 + 'group' => __('WooCommerce Features', 'mxchat'),
5199 + 'icon' => 'arrow-right-alt',
5200 + 'description' => __('Redirect customer to checkout page', 'mxchat'),
5201 + ),
3033 5202
5203 + // Perplexity Add-on
5204 + 'mxchat_perplexity_research' => array(
5205 + 'label' => __('Perplexity Research', 'mxchat'),
5206 + 'pro_only' => true,
5207 + 'group' => __('Search Features', 'mxchat'),
5208 + 'icon' => 'book-alt',
5209 + 'description' => __('Allows the chatbot to search the web for accurate, up-to-date answers', 'mxchat'),
5210 + ),
3034 5211
5212 + // Forms Add-on (only shown when Pro is not activated)
5213 + 'mxchat_handle_form_collection' => array(
5214 + 'label' => __('Form Collection', 'mxchat'),
5215 + 'pro_only' => true,
5216 + 'group' => __('Form Features', 'mxchat'),
5217 + 'icon' => 'feedback',
5218 + 'description' => __('Collect user information through custom forms in chat', 'mxchat'),
5219 + ),
5220 + );
3035 5221
5222 + // Enhance add-on callbacks with installation status and addon info
5223 + foreach ($addon_callbacks as $function => $data) {
5224 + if (isset($addon_function_mapping[$function])) {
5225 + $addon_info = $addon_function_mapping[$function];
3036 5226
5227 + $addon_callbacks[$function]['addon'] = $addon_info['addon'];
5228 + $addon_callbacks[$function]['addon_name'] = $addon_info['addon_name'];
5229 + $addon_callbacks[$function]['installed'] = $addon_info['is_installed'];
3037 5230
3038 -private function mxchat_get_available_callbacks($grouped = false) {
3039 - $callbacks = [
3040 - 'mxchat_handle_email_capture' => [
3041 - 'label' => __('Email Capture', 'mxchat'),
3042 - 'pro_only' => false,
3043 - 'group' => __('Customer Engagement', 'mxchat'),
3044 - ],
3045 - 'mxchat_handle_product_inquiry' => [
3046 - 'label' => __('Show Product Card', 'mxchat'),
3047 - 'pro_only' => true,
3048 - 'group' => __('WooCommerce Features', 'mxchat'),
3049 - ],
3050 - 'mxchat_handle_order_history' => [
3051 - 'label' => __('Order History', 'mxchat'),
3052 - 'pro_only' => true,
3053 - 'group' => __('WooCommerce Features', 'mxchat'),
3054 - ],
3055 - 'mxchat_generate_image' => [
3056 - 'label' => __('Generate Image', 'mxchat'),
3057 - 'pro_only' => true,
3058 - 'group' => __('Other Features', 'mxchat'),
3059 - ],
3060 - 'mxchat_handle_search_request' => [
3061 - 'label' => __('Brave Web Search', 'mxchat'),
3062 - 'pro_only' => false,
3063 - 'group' => __('Search Features', 'mxchat'),
3064 - ],
3065 - 'mxchat_handle_image_search_request' => [
3066 - 'label' => __('Brave Image Search', 'mxchat'),
3067 - 'pro_only' => false,
3068 - 'group' => __('Search Features', 'mxchat'),
3069 - ],
3070 - 'mxchat_handle_add_to_cart_intent' => [
3071 - 'label' => __('Add to Cart', 'mxchat'),
3072 - 'pro_only' => true,
3073 - 'group' => __('WooCommerce Features', 'mxchat'),
3074 - ],
3075 - 'mxchat_handle_checkout_intent' => [
3076 - 'label' => __('Proceed to Checkout', 'mxchat'),
3077 - 'pro_only' => true,
3078 - 'group' => __('WooCommerce Features', 'mxchat'),
3079 - ],
3080 - 'mxchat_handle_pdf_discussion' => [
3081 - 'label' => __('Chat with PDF', 'mxchat'),
3082 - 'pro_only' => true,
3083 - 'group' => __('Other Features', 'mxchat'),
3084 - ],
3085 - 'mxchat_handle_product_recommendations' => [
3086 - 'label' => __('Product Recommendations', 'mxchat'),
3087 - 'pro_only' => true,
3088 - 'group' => __('WooCommerce Features', 'mxchat'),
3089 - ],
3090 - 'mxchat_live_agent_handover' => [
3091 - 'label' => __('Live Agent', 'mxchat'),
3092 - 'pro_only' => true,
3093 - 'group' => __('Customer Engagement', 'mxchat'),
3094 - ],
3095 - 'mxchat_handle_switch_to_chatbot_intent' => [
3096 - 'label' => __('Back to Chatbot', 'mxchat'),
3097 - 'pro_only' => true,
3098 - 'group' => __('Customer Engagement', 'mxchat'),
3099 - ],
3100 - ];
3101 -
3102 - // Return grouped structure if requested
3103 - if ($grouped) {
3104 - $grouped_callbacks = [];
3105 - foreach ($callbacks as $key => $data) {
3106 - $group_label = $data['group'] ?? __('Other Features', 'mxchat');
3107 - $grouped_callbacks[$group_label][$key] = [
3108 - 'label' => $data['label'],
3109 - 'pro_only' => $data['pro_only'],
3110 - ];
5231 + // Set pro_only based on add-on configuration
5232 + $addon_callbacks[$function]['pro_only'] = $addon_info['pro_required'];
5233 + } else {
5234 + $addon_callbacks[$function]['addon'] = 'unknown';
5235 + $addon_callbacks[$function]['addon_name'] = __('Unknown Add-on', 'mxchat');
5236 + $addon_callbacks[$function]['installed'] = false;
3111 5237 }
3112 - return $grouped_callbacks;
3113 5238 }
3114 5239
3115 - return $callbacks;
3116 -}
5240 + // Initialize callbacks with core features
5241 + $callbacks = $core_callbacks;
3117 5242
5243 + // Get callbacks from active add-ons
5244 + $active_addon_callbacks = apply_filters('mxchat_available_callbacks', array());
3118 5245
5246 + // Add placeholder callbacks only for add-ons that aren't active
5247 + if ($include_all) {
5248 + foreach ($addon_callbacks as $function => $data) {
5249 + // Skip placeholders for functions provided by active add-ons
5250 + if (in_array($function, $addon_provided_functions)) {
5251 + continue;
5252 + }
3119 5253
3120 -private function mxchat_average_vectors($vectors) {
3121 - $vector_length = count($vectors[0]);
3122 - $sum_vector = array_fill(0, $vector_length, 0);
5254 + // Skip excluded functions
5255 + if (in_array($function, $excluded_functions)) {
5256 + continue;
5257 + }
3123 5258
3124 - foreach ($vectors as $vector) {
3125 - for ($i = 0; $i < $vector_length; $i++) {
3126 - $sum_vector[$i] += $vector[$i];
5259 + // Add the placeholder
5260 + $callbacks[$function] = $data;
3127 5261 }
3128 5262 }
3129 5263
3130 - // Divide each component by the number of vectors to get the average
3131 - $num_vectors = count($vectors);
3132 - for ($i = 0; $i < $vector_length; $i++) {
3133 - $sum_vector[$i] /= $num_vectors;
5264 + // Add callbacks from active add-ons (will override placeholders)
5265 + foreach ($active_addon_callbacks as $function => $data) {
5266 + // Skip excluded functions
5267 + if (in_array($function, $excluded_functions)) {
5268 + continue;
5269 + }
5270 +
5271 + // Always include callbacks from add-ons
5272 + $callbacks[$function] = $data;
5273 +
5274 + // Ensure they have the proper add-on info
5275 + if (isset($addon_function_mapping[$function])) {
5276 + $addon_info = $addon_function_mapping[$function];
5277 + $callbacks[$function]['addon'] = $addon_info['addon'];
5278 + $callbacks[$function]['addon_name'] = $addon_info['addon_name'];
5279 + $callbacks[$function]['installed'] = $addon_info['is_installed'];
5280 + $callbacks[$function]['pro_only'] = $addon_info['pro_required'];
5281 + }
3134 5282 }
3135 5283
3136 - return $sum_vector;
3137 -}
5284 + // Just before returning callbacks, sort them to prioritize free features
5285 + if (!$grouped) {
5286 + // Create temporary arrays for sorting
5287 + $free_callbacks = array();
5288 + $pro_callbacks = array();
3138 5289
3139 -public function mxchat_handle_delete_intent() {
3140 - if ( ! current_user_can( 'manage_options' ) ) {
3141 - wp_die( esc_html__('Unauthorized user', 'mxchat') );
5290 + // Split callbacks into free and pro
5291 + foreach ($callbacks as $key => $data) {
5292 + if (isset($data['pro_only']) && $data['pro_only']) {
5293 + $pro_callbacks[$key] = $data;
5294 + } else {
5295 + $free_callbacks[$key] = $data;
5296 + }
5297 + }
5298 +
5299 + // Merge with free callbacks first
5300 + $callbacks = array_merge($free_callbacks, $pro_callbacks);
3142 5301 }
3143 5302
3144 - check_admin_referer('mxchat_delete_intent_nonce');
5303 + // Return grouped structure if requested
5304 + if ($grouped) {
5305 + $grouped_callbacks = array();
5306 + foreach ($callbacks as $key => $data) {
5307 + $group_label = isset($data['group']) ? $data['group'] : __('Other Features', 'mxchat');
3145 5308
3146 - if (isset($_POST['intent_id'])) {
3147 - global $wpdb;
3148 - $table_name = $wpdb->prefix . 'mxchat_intents';
3149 - $intent_id = intval($_POST['intent_id']);
5309 + // Ensure we carry forward all the new fields in grouped mode
5310 + $callback_data = array(
5311 + 'label' => $data['label'],
5312 + 'pro_only' => isset($data['pro_only']) ? $data['pro_only'] : false,
5313 + 'icon' => isset($data['icon']) ? $data['icon'] : 'admin-generic',
5314 + 'description' => isset($data['description']) ? $data['description'] : __('Custom action for your chatbot', 'mxchat'),
5315 + 'addon' => isset($data['addon']) ? $data['addon'] : false,
5316 + 'addon_name' => isset($data['addon_name']) ? $data['addon_name'] : '',
5317 + 'installed' => isset($data['installed']) ? $data['installed'] : true
5318 + );
3150 5319
3151 - $wpdb->delete($table_name, ['id' => $intent_id], ['%d']);
5320 + $grouped_callbacks[$group_label][$key] = $callback_data;
5321 + }
5322 +
5323 + // Sort within each group to prioritize free features
5324 + foreach ($grouped_callbacks as $group => $items) {
5325 + $free_items = array();
5326 + $pro_items = array();
5327 +
5328 + foreach ($items as $key => $data) {
5329 + if (isset($data['pro_only']) && $data['pro_only']) {
5330 + $pro_items[$key] = $data;
5331 + } else {
5332 + $free_items[$key] = $data;
5333 + }
5334 + }
5335 +
5336 + $grouped_callbacks[$group] = array_merge($free_items, $pro_items);
5337 + }
5338 +
5339 + return $grouped_callbacks;
3152 5340 }
3153 5341
3154 - wp_safe_redirect(admin_url('admin.php?page=mxchat-intents'));
3155 - exit;
5342 + return $callbacks;
3156 5343 }
3157 5344
3158 -
3159 5345 public function mxchat_page_init() {
3160 - // Register settings
3161 5346 register_setting(
3162 5347 'mxchat_option_group',
3163 5348 'mxchat_options',
3164 5349 array($this, 'mxchat_sanitize')
@@ -3169,10 +5354,10 @@
3169 5354 'mxchat_similarity_threshold',
3170 5355 array(
3171 5356 'type' => 'number',
3172 5357 'sanitize_callback' => function($value) {
3173 - $value = absint($value); // Ensure it's an integer
3174 - return min(max($value, 70), 85); // Enforce range
5358 + $value = absint($value);
5359 + return min(max($value, 20), 95);
3175 5360 },
3176 5361 'default' => 80,
3177 5362 )
3178 5363 );
@@ -3184,8 +5369,106 @@
3184 5369 null,
3185 5370 'mxchat-chatbot'
3186 5371 );
3187 5372
5373 + // API Keys Settings Section
5374 + add_settings_section(
5375 + 'mxchat_api_keys_section',
5376 + esc_html__('API Keys', 'mxchat'),
5377 + array($this, 'mxchat_api_keys_section_callback'),
5378 + 'mxchat-api-keys'
5379 + );
5380 +
5381 + // OpenAI API Key
5382 + add_settings_field(
5383 + 'api_key',
5384 + esc_html__('OpenAI API Key', 'mxchat'),
5385 + array($this, 'api_key_callback'),
5386 + 'mxchat-api-keys',
5387 + 'mxchat_api_keys_section'
5388 + );
5389 +
5390 + // X.AI API Key
5391 + add_settings_field(
5392 + 'xai_api_key',
5393 + esc_html__('X.AI API Key', 'mxchat'),
5394 + array($this, 'xai_api_key_callback'),
5395 + 'mxchat-api-keys',
5396 + 'mxchat_api_keys_section'
5397 + );
5398 +
5399 + // Claude API Key
5400 + add_settings_field(
5401 + 'claude_api_key',
5402 + esc_html__('Claude API Key', 'mxchat'),
5403 + array($this, 'claude_api_key_callback'),
5404 + 'mxchat-api-keys',
5405 + 'mxchat_api_keys_section'
5406 + );
5407 +
5408 + // DeepSeek API Key
5409 + add_settings_field(
5410 + 'deepseek_api_key',
5411 + esc_html__('DeepSeek API Key', 'mxchat'),
5412 + array($this, 'deepseek_api_key_callback'),
5413 + 'mxchat-api-keys',
5414 + 'mxchat_api_keys_section'
5415 + );
5416 +
5417 + // Google Gemini API Key
5418 + add_settings_field(
5419 + 'gemini_api_key',
5420 + esc_html__('Google Gemini API Key', 'mxchat'),
5421 + array($this, 'gemini_api_key_callback'),
5422 + 'mxchat-api-keys',
5423 + 'mxchat_api_keys_section'
5424 + );
5425 +
5426 + // Voyage AI API Key
5427 + add_settings_field(
5428 + 'voyage_api_key',
5429 + esc_html__('Voyage AI API Key', 'mxchat'),
5430 + array($this, 'voyage_api_key_callback'),
5431 + 'mxchat-api-keys',
5432 + 'mxchat_api_keys_section'
5433 + );
5434 +
5435 + // OpenRouter API Key
5436 + add_settings_field(
5437 + 'openrouter_api_key',
5438 + esc_html__('OpenRouter API Key', 'mxchat'),
5439 + array($this, 'openrouter_api_key_callback'),
5440 + 'mxchat-api-keys',
5441 + 'mxchat_api_keys_section'
5442 + );
5443 +
5444 + // Loops API Key
5445 + add_settings_field(
5446 + 'loops_api_key',
5447 + esc_html__('Loops API Key', 'mxchat'),
5448 + array($this, 'mxchat_loops_api_key_callback'),
5449 + 'mxchat-api-keys',
5450 + 'mxchat_api_keys_section'
5451 + );
5452 +
5453 + // Brave Search API Key
5454 + add_settings_field(
5455 + 'brave_api_key',
5456 + __('Brave API Key', 'mxchat'),
5457 + array($this, 'mxchat_brave_api_key_callback'),
5458 + 'mxchat-api-keys',
5459 + 'mxchat_api_keys_section'
5460 + );
5461 +
5462 + // Frontend Debugger Toggle
5463 + add_settings_field(
5464 + 'show_frontend_debugger',
5465 + esc_html__('Frontend Debugger', 'mxchat'),
5466 + array($this, 'mxchat_frontend_debugger_callback'),
5467 + 'mxchat-chatbot',
5468 + 'mxchat_chatbot_section'
5469 + );
5470 +
3188 5471 // Similarity Threshold Slider
3189 5472 add_settings_field(
3190 5473 'similarity_threshold', // Field ID
3191 5474 esc_html__('Similarity Threshold', 'mxchat'), // Field title
@@ -3201,38 +5484,41 @@
3201 5484 'mxchat-chatbot',
3202 5485 'mxchat_chatbot_section'
3203 5486 );
3204 5487
3205 -
3206 - // Existing fields...
3207 5488 add_settings_field(
3208 - 'api_key',
3209 - esc_html__('OpenAI API Key', 'mxchat'),
3210 - array($this, 'api_key_callback'),
5489 + 'contextual_awareness_toggle',
5490 + esc_html__('Contextual Awareness', 'mxchat'),
5491 + array($this, 'mxchat_contextual_awareness_callback'),
3211 5492 'mxchat-chatbot',
3212 5493 'mxchat_chatbot_section'
3213 5494 );
3214 5495
3215 5496 add_settings_field(
3216 - 'xai_api_key',
3217 - esc_html__('X.AI API Key', 'mxchat'),
3218 - array($this, 'xai_api_key_callback'),
5497 + 'enable_streaming_toggle',
5498 + esc_html__('Enable Streaming', 'mxchat'),
5499 + array($this, 'enable_streaming_toggle_callback'),
3219 5500 'mxchat-chatbot',
3220 - 'mxchat_chatbot_section'
5501 + 'mxchat_chatbot_section', // Same section as your working toggle
5502 + array(
5503 + 'class' => 'mxchat-setting-row streaming-setting',
5504 + 'style' => 'display: none;' // Hidden by default, shown when OpenAI/Claude selected
5505 + )
3221 5506 );
3222 5507
5508 +
3223 5509 add_settings_field(
3224 - 'claude_api_key',
3225 - esc_html__('Claude API Key', 'mxchat'),
3226 - array($this, 'claude_api_key_callback'),
5510 + 'model',
5511 + esc_html__('Chat Model', 'mxchat'),
5512 + array($this, 'mxchat_model_callback'),
3227 5513 'mxchat-chatbot',
3228 5514 'mxchat_chatbot_section'
3229 5515 );
3230 5516
3231 5517 add_settings_field(
3232 - 'deepseek_api_key',
3233 - esc_html__('DeepSeek API Key', 'mxchat'),
3234 - array($this, 'deepseek_api_key_callback'),
5518 + 'embedding_model',
5519 + esc_html__('Embedding Model', 'mxchat'),
5520 + array($this, 'embedding_model_callback'),
3235 5521 'mxchat-chatbot',
3236 5522 'mxchat_chatbot_section'
3237 5523 );
3238 5524
@@ -3243,20 +5529,21 @@
3243 5529 'mxchat-chatbot',
3244 5530 'mxchat_chatbot_section'
3245 5531 );
3246 5532
5533 +
3247 5534 add_settings_field(
3248 - 'model',
3249 - esc_html__('Model', 'mxchat'),
3250 - array($this, 'mxchat_model_callback'),
5535 + 'top_bar_title',
5536 + esc_html__('Top Bar Title', 'mxchat'),
5537 + array($this, 'mxchat_top_bar_title_callback'),
3251 5538 'mxchat-chatbot',
3252 5539 'mxchat_chatbot_section'
3253 5540 );
3254 5541
3255 5542 add_settings_field(
3256 - 'top_bar_title',
3257 - esc_html__('Top Bar Title', 'mxchat'),
3258 - array($this, 'mxchat_top_bar_title_callback'),
5543 + 'ai_agent_text',
5544 + esc_html__('AI Agent Text', 'mxchat'),
5545 + array($this, 'mxchat_ai_agent_text_callback'),
3259 5546 'mxchat-chatbot',
3260 5547 'mxchat_chatbot_section'
3261 5548 );
3262 5549
@@ -3284,48 +5571,40 @@
3284 5571 'mxchat_chatbot_section'
3285 5572 );
3286 5573
3287 5574 add_settings_field(
3288 - 'intro_message',
3289 - esc_html__('Introductory Message', 'mxchat'),
3290 - array($this, 'mxchat_intro_message_callback'),
5575 + 'enable_name_field',
5576 + esc_html__('Require Name Field', 'mxchat'),
5577 + array($this, 'enable_name_field_callback'),
3291 5578 'mxchat-chatbot',
3292 5579 'mxchat_chatbot_section'
3293 5580 );
3294 5581
3295 5582 add_settings_field(
3296 - 'input_copy',
3297 - esc_html__('Input Copy', 'mxchat'),
3298 - array($this, 'mxchat_input_copy_callback'),
5583 + 'name_field_placeholder',
5584 + esc_html__('Name Field Placeholder', 'mxchat'),
5585 + array($this, 'name_field_placeholder_callback'),
3299 5586 'mxchat-chatbot',
3300 5587 'mxchat_chatbot_section'
3301 5588 );
3302 5589
3303 5590 add_settings_field(
3304 - 'rate_limit_logged_out',
3305 - __('Rate Limit for Logged-out Users', 'mxchat'),
3306 - array($this, 'mxchat_rate_limit_logged_out_callback'),
5591 + 'intro_message',
5592 + esc_html__('Introductory Message', 'mxchat'),
5593 + array($this, 'mxchat_intro_message_callback'),
3307 5594 'mxchat-chatbot',
3308 5595 'mxchat_chatbot_section'
3309 5596 );
3310 5597
3311 5598 add_settings_field(
3312 - 'rate_limit_roles',
3313 - __('Rate Limits by User Role', 'mxchat'),
3314 - array($this, 'mxchat_rate_limit_roles_callback'),
5599 + 'input_copy',
5600 + esc_html__('Input Copy', 'mxchat'),
5601 + array($this, 'mxchat_input_copy_callback'),
3315 5602 'mxchat-chatbot',
3316 5603 'mxchat_chatbot_section'
3317 5604 );
3318 5605
3319 5606 add_settings_field(
3320 - 'rate_limit_message',
3321 - esc_html__('Rate Limit Message', 'mxchat'),
3322 - array($this, 'mxchat_rate_limit_message_callback'),
3323 - 'mxchat-chatbot',
3324 - 'mxchat_chatbot_section'
3325 - );
3326 -
3327 - add_settings_field(
3328 5607 'pre_chat_message',
3329 5608 esc_html__('Chat Teaser Pop-up', 'mxchat'),
3330 5609 array($this, 'mxchat_pre_chat_message_callback'),
3331 5610 'mxchat-chatbot',
@@ -3365,9 +5644,9 @@
3365 5644 );
3366 5645
3367 5646 add_settings_field(
3368 5647 'popular_question_1',
3369 - esc_html__('Popular Question 1', 'mxchat'),
5648 + esc_html__('Quick Question 1', 'mxchat'),
3370 5649 array($this, 'mxchat_popular_question_1_callback'),
3371 5650 'mxchat-chatbot',
3372 5651 'mxchat_chatbot_section'
3373 5652 );
@@ -3373,9 +5652,9 @@
3373 5652 );
3374 5653
3375 5654 add_settings_field(
3376 5655 'popular_question_2',
3377 - esc_html__('Popular Question 2', 'mxchat'),
5656 + esc_html__('Quick Question 2', 'mxchat'),
3378 5657 array($this, 'mxchat_popular_question_2_callback'),
3379 5658 'mxchat-chatbot',
3380 5659 'mxchat_chatbot_section'
3381 5660 );
@@ -3381,9 +5660,9 @@
3381 5660 );
3382 5661
3383 5662 add_settings_field(
3384 5663 'popular_question_3',
3385 - esc_html__('Popular Question 3', 'mxchat'),
5664 + esc_html__('Quick Question 3', 'mxchat'),
3386 5665 array($this, 'mxchat_popular_question_3_callback'),
3387 5666 'mxchat-chatbot',
3388 5667 'mxchat_chatbot_section'
3389 5668 );
@@ -3389,20 +5668,21 @@
3389 5668 );
3390 5669
3391 5670 add_settings_field(
3392 5671 'additional_popular_questions',
3393 - esc_html__('Additional Popular Questions', 'mxchat'),
5672 + esc_html__('Additional Quick Questions', 'mxchat'),
3394 5673 array($this, 'mxchat_additional_popular_questions_callback'),
3395 5674 'mxchat-chatbot',
3396 5675 'mxchat_chatbot_section'
3397 5676 );
3398 5677
3399 - // WooCommerce Settings Section
3400 - add_settings_section(
3401 - 'mxchat_woocommerce_section',
3402 - esc_html__('WooCommerce Settings', 'mxchat'),
3403 - null,
3404 - 'mxchat-embed'
5678 +
5679 + add_settings_field(
5680 + 'rate_limits',
5681 + __('Rate Limits Settings', 'mxchat'),
5682 + array($this, 'mxchat_rate_limits_callback'),
5683 + 'mxchat-chatbot',
5684 + 'mxchat_chatbot_section'
3405 5685 );
3406 5686
3407 5687 // Loops Settings Section
3408 5688 add_settings_section(
@@ -3411,43 +5691,10 @@
3411 5691 null,
3412 5692 'mxchat-embed'
3413 5693 );
3414 5694
3415 - // WooCommerce Settings Fields
5695 + // Loops Settings Fields (API Key moved to API Keys tab)
3416 5696 add_settings_field(
3417 - 'enable_woocommerce_integration',
3418 - esc_html__('Automatically Embed Products', 'mxchat'),
3419 - array($this, 'mxchat_enable_woocommerce_integration_callback'),
3420 - 'mxchat-embed',
3421 - 'mxchat_woocommerce_section'
3422 - );
3423 -
3424 - add_settings_field(
3425 - 'woocommerce_consumer_key',
3426 - esc_html__('WooCommerce Consumer Key', 'mxchat'),
3427 - array($this, 'mxchat_woocommerce_consumer_key_callback'),
3428 - 'mxchat-embed',
3429 - 'mxchat_woocommerce_section'
3430 - );
3431 -
3432 - add_settings_field(
3433 - 'woocommerce_consumer_secret',
3434 - esc_html__('WooCommerce Consumer Secret', 'mxchat'),
3435 - array($this, 'mxchat_woocommerce_consumer_secret_callback'),
3436 - 'mxchat-embed',
3437 - 'mxchat_woocommerce_section'
3438 - );
3439 -
3440 - // Loops Settings Fields
3441 - add_settings_field(
3442 - 'loops_api_key',
3443 - esc_html__('Loops API Key', 'mxchat'),
3444 - array($this, 'mxchat_loops_api_key_callback'),
3445 - 'mxchat-embed',
3446 - 'mxchat_loops_section'
3447 - );
3448 -
3449 - add_settings_field(
3450 5697 'loops_mailing_list',
3451 5698 esc_html__('Loops Mailing List', 'mxchat'),
3452 5699 array($this, 'mxchat_loops_mailing_list_callback'),
3453 5700 'mxchat-embed',
@@ -3477,17 +5724,10 @@
3477 5724 array($this, 'mxchat_brave_section_callback'),
3478 5725 'mxchat-embed'
3479 5726 );
3480 5727
5728 + // Brave API Key moved to API Keys tab
3481 5729 add_settings_field(
3482 - 'brave_api_key',
3483 - __('Brave API Key', 'mxchat'),
3484 - array($this, 'mxchat_brave_api_key_callback'),
3485 - 'mxchat-embed',
3486 - 'mxchat_brave_section'
3487 - );
3488 -
3489 - add_settings_field(
3490 5730 'brave_image_count',
3491 5731 __('Number of Images to Return', 'mxchat'),
3492 5732 array($this, 'mxchat_brave_image_count_callback'),
3493 5733 'mxchat-embed',
@@ -3541,9 +5781,27 @@
3541 5781 'mxchat-embed',
3542 5782 'mxchat_pdf_intent_section'
3543 5783 );
3544 5784
5785 + // PDF Upload Button Toggle
3545 5786 add_settings_field(
5787 + 'show_pdf_upload_button',
5788 + __('Show PDF Upload Button', 'mxchat'),
5789 + array($this, 'mxchat_show_pdf_upload_button_callback'),
5790 + 'mxchat-embed',
5791 + 'mxchat_pdf_intent_section'
5792 + );
5793 +
5794 + // Word Upload Button Toggle
5795 + add_settings_field(
5796 + 'show_word_upload_button',
5797 + __('Show Word Upload Button', 'mxchat'),
5798 + array($this, 'mxchat_show_word_upload_button_callback'),
5799 + 'mxchat-embed',
5800 + 'mxchat_pdf_intent_section'
5801 + );
5802 +
5803 + add_settings_field(
3546 5804 'pdf_intent_trigger_text',
3547 5805 __('Intent Trigger Text', 'mxchat'),
3548 5806 array($this, 'mxchat_pdf_intent_trigger_text_callback'),
3549 5807 'mxchat-embed',
@@ -3608,8 +5866,16 @@
3608 5866 'mxchat_live_agent_section'
3609 5867 );
3610 5868
3611 5869 add_settings_field(
5870 + 'live_agent_user_ids',
5871 + __('Slack Agent User IDs', 'mxchat'),
5872 + array($this, 'mxchat_live_agent_user_ids_callback'),
5873 + 'mxchat-embed',
5874 + 'mxchat_live_agent_section'
5875 + );
5876 +
5877 + add_settings_field(
3612 5878 'live_agent_webhook_url',
3613 5879 __('Slack Webhook URL', 'mxchat'),
3614 5880 array($this, 'mxchat_live_agent_webhook_url_callback'),
3615 5881 'mxchat-embed',
@@ -3638,9 +5904,9 @@
3638 5904
3639 5905 // General Settings Section
3640 5906 add_settings_section(
3641 5907 'mxchat_general_section',
3642 - esc_html__('Frequently Asked Questions (FAQ)', 'mxchat'),
5908 + esc_html__('YouTube Tutorials', 'mxchat'),
3643 5909 null,
3644 5910 'mxchat-general'
3645 5911 );
3646 5912 }
@@ -3645,9 +5911,8 @@
3645 5911 );
3646 5912 }
3647 5913
3648 5914 public function mxchat_prompts_page_init() {
3649 - // Register all settings as a single array
3650 5915 register_setting(
3651 5916 'mxchat_prompts_options',
3652 5917 'mxchat_prompts_options',
3653 5918 array(
@@ -3659,18 +5924,74 @@
3659 5924 'mxchat_use_pinecone' => 0,
3660 5925 'mxchat_pinecone_api_key' => '',
3661 5926 'mxchat_pinecone_environment' => '',
3662 5927 'mxchat_pinecone_index' => '',
3663 - 'mxchat_pinecone_host' => '', // Add this line
5928 + 'mxchat_pinecone_host' => '',
3664 5929 ),
3665 5930 'sanitize_callback' => array($this, 'sanitize_prompts_options'),
3666 5931 )
3667 5932 );
3668 5933
3669 - // Add settings saved message
3670 5934 add_action('admin_notices', array($this, 'sync_settings_notice'));
3671 5935 }
3672 5936
5937 +public function mxchat_transcripts_page_init() {
5938 + register_setting(
5939 + 'mxchat_transcripts_options',
5940 + 'mxchat_transcripts_options',
5941 + array(
5942 + 'type' => 'array',
5943 + 'description' => __('MXChat Transcripts Notification Settings', 'mxchat'),
5944 + 'default' => array(
5945 + 'mxchat_enable_notifications' => 0,
5946 + 'mxchat_notification_email' => get_option('admin_email'),
5947 + 'mxchat_auto_delete_transcripts' => 'never',
5948 + ),
5949 + 'sanitize_callback' => array($this, 'sanitize_transcripts_options'),
5950 + )
5951 + );
5952 +
5953 + add_settings_section(
5954 + 'mxchat_transcripts_notification_section',
5955 + esc_html__('Chat Notification Settings', 'mxchat'),
5956 + array($this, 'mxchat_transcripts_notification_section_callback'),
5957 + 'mxchat-transcripts'
5958 + );
5959 +
5960 + add_settings_field(
5961 + 'mxchat_enable_notifications',
5962 + esc_html__('Enable Chat Notifications', 'mxchat'),
5963 + array($this, 'mxchat_enable_notifications_callback'),
5964 + 'mxchat-transcripts',
5965 + 'mxchat_transcripts_notification_section'
5966 + );
5967 +
5968 + add_settings_field(
5969 + 'mxchat_notification_email',
5970 + esc_html__('Notification Email Address', 'mxchat'),
5971 + array($this, 'mxchat_notification_email_callback'),
5972 + 'mxchat-transcripts',
5973 + 'mxchat_transcripts_notification_section'
5974 + );
5975 +
5976 + add_settings_field(
5977 + 'mxchat_auto_delete_transcripts',
5978 + esc_html__('Auto-Delete Old Transcripts', 'mxchat'),
5979 + array($this, 'mxchat_auto_delete_transcripts_callback'),
5980 + 'mxchat-transcripts',
5981 + 'mxchat_transcripts_notification_section'
5982 + );
5983 +
5984 + add_settings_field(
5985 + 'mxchat_auto_email_transcript',
5986 + esc_html__('Auto-Email Full Transcript', 'mxchat'),
5987 + array($this, 'mxchat_auto_email_transcript_callback'),
5988 + 'mxchat-transcripts',
5989 + 'mxchat_transcripts_notification_section'
5990 + );
5991 +}
5992 +
5993 +
3673 5994 /**
3674 5995 * Sanitize all prompts options
3675 5996 *
3676 5997 * @param array $input The unsanitized options array
@@ -3729,9 +6050,8 @@
3729 6050
3730 6051 return $sanitized;
3731 6052 }
3732 6053
3733 -
3734 6054 public function sync_settings_notice() {
3735 6055 // Only show notice on our plugin page
3736 6056 if (!isset($_GET['page']) || $_GET['page'] !== 'mxchat-prompts') {
3737 6057 return;
@@ -3752,183 +6072,129 @@
3752 6072 public function sanitize_sync_setting($input) {
3753 6073 return (bool)$input ? __('1', 'mxchat') : __('', 'mxchat');
3754 6074 }
3755 6075
6076 +public function mxchat_rate_limits_callback() {
6077 + $all_options = get_option('mxchat_options', []);
3756 6078
6079 + // Define available rate limits
6080 + $rate_limits = array('1', '3', '5', '10', '15', '20', '50', '100', 'unlimited');
3757 6081
3758 -public function mxchat_handle_activate_license() {
3759 - // Check nonce
3760 - if (!check_ajax_referer('mxchat_activate_license_nonce', 'security', false)) {
3761 - wp_send_json_error(esc_html__('Invalid security token', 'mxchat'));
3762 - return;
3763 - }
6082 + // Define available timeframes
6083 + $timeframes = array(
6084 + 'hourly' => __('Per Hour', 'mxchat'),
6085 + 'daily' => __('Per Day', 'mxchat'),
6086 + 'weekly' => __('Per Week', 'mxchat'),
6087 + 'monthly' => __('Per Month', 'mxchat')
6088 + );
3764 6089
3765 - // Verify user capabilities
3766 - if (!current_user_can('manage_options')) {
3767 - wp_send_json_error(esc_html__('Unauthorized access', 'mxchat'));
3768 - return;
3769 - }
6090 + // Get all roles plus a "logged_out" pseudo-role
6091 + $roles = wp_roles()->get_names();
6092 + $roles['logged_out'] = __('Logged Out Users', 'mxchat');
3770 6093
3771 - $license_key = isset($_POST['mxchat_activation_key']) ? sanitize_text_field($_POST['mxchat_activation_key']) : '';
3772 - $customer_email = isset($_POST['mxchat_pro_email']) ? sanitize_email($_POST['mxchat_pro_email']) : '';
6094 + // Start the wrapper
6095 + echo '<div class="pro-feature-wrapper active">';
6096 + echo '<div class="mxchat-rate-limits-container">';
3773 6097
3774 - if (empty($license_key) || empty($customer_email)) {
3775 - wp_send_json_error(esc_html__('Email or License Key is missing', 'mxchat'));
3776 - return;
3777 - }
6098 + echo '<p class="description" style="margin-bottom: 20px;">' .
6099 + esc_html__('Set message limits for each user role and customize the experience when users reach those limits. You can use {limit}, {timeframe}, {count}, and {remaining} as placeholders.', 'mxchat') .
6100 + '</p>';
3778 6101
3779 - $product_id = 'MxChatPRO';
3780 - $response = wp_remote_get(
3781 - add_query_arg(
3782 - array(
3783 - 'wc-api' => 'software-api',
3784 - 'request' => 'activation',
3785 - 'email' => $customer_email,
3786 - 'license_key' => $license_key,
3787 - 'product_id' => $product_id
3788 - ),
3789 - 'http://mxchat.ai/'
3790 - )
3791 - );
6102 + // Add markdown link documentation
6103 + echo '<div class="notice notice-info inline" style="margin-bottom: 20px; padding: 10px;">';
6104 + echo '<p><strong>' . esc_html__('Markdown Links Supported:', 'mxchat') . '</strong></p>';
6105 + echo '<p>' . esc_html__('You can include clickable links in your custom messages using markdown syntax:', 'mxchat') . '</p>';
6106 + echo '<ul style="margin-left: 20px;">';
6107 + echo '<li><code>[Link text](https://example.com)</code> - Creates a clickable link</li>';
6108 + echo '<li><code>[Visit our pricing](https://example.com/pricing)</code> - Link with custom text</li>';
6109 + echo '<li><code>Plain URLs like https://example.com will also become clickable</code></li>';
6110 + echo '</ul>';
6111 + echo '</div>';
3792 6112
3793 - if (is_wp_error($response)) {
3794 - wp_send_json_error(esc_html__('Activation failed due to a server error: ', 'mxchat') . $response->get_error_message());
3795 - return;
3796 - }
6113 + // Output the controls for each role
6114 + foreach ($roles as $role_id => $role_name) {
6115 + // Get saved options or defaults
6116 + $default_limit = ($role_id === 'logged_out') ? '10' : '100';
6117 + $default_timeframe = 'daily';
6118 + $default_message = __('Rate limit exceeded. Please try again later.', 'mxchat');
3797 6119
3798 - $body = wp_remote_retrieve_body($response);
3799 - $data = json_decode($body);
6120 + $selected_limit = isset($all_options['rate_limits'][$role_id]['limit'])
6121 + ? $all_options['rate_limits'][$role_id]['limit']
6122 + : $default_limit;
3800 6123
3801 - if ($data && isset($data->activated) && $data->activated) {
3802 - update_option('mxchat_license_status', 'active');
3803 - update_option('mxchat_pro_email', $customer_email);
3804 - update_option('mxchat_activation_key', $license_key);
3805 - wp_send_json_success(array('message' => esc_html__('License activated successfully', 'mxchat')));
3806 - } else {
3807 - $error_message = isset($data->error) ? $data->error : esc_html__('Activation failed', 'mxchat');
3808 - update_option('mxchat_license_status', 'inactive');
3809 - update_option('mxchat_license_error', $error_message);
3810 - wp_send_json_error($error_message);
3811 - }
3812 -}
6124 + $selected_timeframe = isset($all_options['rate_limits'][$role_id]['timeframe'])
6125 + ? $all_options['rate_limits'][$role_id]['timeframe']
6126 + : $default_timeframe;
3813 6127
6128 + $custom_message = isset($all_options['rate_limits'][$role_id]['message'])
6129 + ? $all_options['rate_limits'][$role_id]['message']
6130 + : $default_message;
3814 6131
3815 -public function mxchat_rate_limit_logged_out_callback() {
3816 - // Load the entire 'mxchat_options' array
3817 - $all_options = get_option('mxchat_options', []);
6132 + // Output the row
6133 + echo '<div class="mxchat-rate-limit-row mxchat-autosave-section">';
3818 6134
3819 - // Retrieve the saved rate limit or use the default value
3820 - $default_rate_limit = '10';
3821 - $selected_rate_limit = isset($all_options['rate_limit_logged_out']) ? $all_options['rate_limit_logged_out'] : $default_rate_limit;
6135 + // Role label
6136 + echo '<div class="mxchat-rate-limit-role">' . esc_html($role_name) . '</div>';
3822 6137
3823 - // Define available rate limits
3824 - $rate_limits = array('1', '3', '5', '10', '15', '20', '100', 'unlimited');
6138 + // Controls section
6139 + echo '<div class="mxchat-rate-limit-controls-wrapper">';
3825 6140
3826 - // Output the dropdown
3827 - echo '<div class="pro-feature-wrapper active">';
3828 - echo '<select id="rate_limit_logged_out" name="rate_limit_logged_out">';
3829 - foreach ($rate_limits as $limit) {
3830 - echo '<option value="' . esc_attr($limit) . '" ' . selected($selected_rate_limit, $limit, false) . '>' . esc_html($limit) . '</option>';
3831 - }
3832 - echo '</select>';
3833 - echo '<p class="description">' . esc_html__('Set the maximum number of messages a logged-out user can send within a 24-hour period.', 'mxchat') . '</p>';
3834 - echo '</div>';
3835 -}
6141 + // Rate limit and timeframe controls
6142 + echo '<div class="mxchat-rate-limit-controls">';
3836 6143
3837 -// Add this new callback function
3838 -public function mxchat_rate_limit_roles_callback() {
3839 - $all_options = get_option('mxchat_options', []);
3840 - $roles = wp_roles()->get_names();
3841 - $rate_limits = array('1', '3', '5', '10', '15', '20', '100', 'unlimited');
3842 -
3843 - echo '<div class="pro-feature-wrapper active mxchat-autosave-section">';
3844 -
3845 - foreach ($roles as $role_id => $role_name) {
3846 - $default_rate_limit = '100';
3847 - $selected_rate_limit = isset($all_options['role_rate_limits'][$role_id])
3848 - ? $all_options['role_rate_limits'][$role_id]
3849 - : $default_rate_limit;
3850 -
3851 - echo '<div style="margin-bottom: 10px;">';
3852 - echo '<label style="display: inline-block; width: 150px;">' . esc_html($role_name) . ':</label>';
6144 + // Limit dropdown
6145 + echo '<div>';
6146 + echo '<label for="rate_limits_' . esc_attr($role_id) . '_limit">' . esc_html__('Limit:', 'mxchat') . '</label>';
3853 6147 echo '<select
3854 - id="role_rate_limits_' . esc_attr($role_id) . '"
3855 - name="mxchat_options[role_rate_limits][' . esc_attr($role_id) . ']"
6148 + id="rate_limits_' . esc_attr($role_id) . '_limit"
6149 + name="mxchat_options[rate_limits][' . esc_attr($role_id) . '][limit]"
3856 6150 class="mxchat-autosave-field">';
3857 6151 foreach ($rate_limits as $limit) {
3858 - echo '<option value="' . esc_attr($limit) . '" ' . selected($selected_rate_limit, $limit, false) . '>' . esc_html($limit) . '</option>';
6152 + echo '<option value="' . esc_attr($limit) . '" ' . selected($selected_limit, $limit, false) . '>' . esc_html($limit) . '</option>';
3859 6153 }
3860 6154 echo '</select>';
3861 6155 echo '</div>';
3862 - }
3863 6156
3864 - echo '<p class="description">' . esc_html__('Set the maximum number of messages users in each role can send within a 24-hour period.', 'mxchat') . '</p>';
3865 - echo '</div>';
3866 -}
6157 + // Timeframe dropdown
6158 + echo '<div>';
6159 + echo '<label for="rate_limits_' . esc_attr($role_id) . '_timeframe">' . esc_html__('Timeframe:', 'mxchat') . '</label>';
6160 + echo '<select
6161 + id="rate_limits_' . esc_attr($role_id) . '_timeframe"
6162 + name="mxchat_options[rate_limits][' . esc_attr($role_id) . '][timeframe]"
6163 + class="mxchat-autosave-field">';
6164 + foreach ($timeframes as $value => $label) {
6165 + echo '<option value="' . esc_attr($value) . '" ' . selected($selected_timeframe, $value, false) . '>' . esc_html($label) . '</option>';
6166 + }
6167 + echo '</select>';
6168 + echo '</div>';
3867 6169
3868 -public function mxchat_rate_limit_message_callback() {
3869 - // Load the entire 'mxchat_options' array
3870 - $all_options = get_option('mxchat_options', []);
6170 + echo '</div>'; // End controls
3871 6171
3872 - // Retrieve the saved message or use the default value
3873 - $default_message = esc_html__('Rate limit exceeded. Please try again later.', 'mxchat');
3874 - $rate_limit_message = isset($all_options['rate_limit_message']) ? $all_options['rate_limit_message'] : $default_message;
6172 + // Custom message textarea
6173 + echo '<div class="mxchat-rate-limit-message">';
6174 + echo '<label for="rate_limits_' . esc_attr($role_id) . '_message">' . esc_html__('Custom Message:', 'mxchat') . '</label>';
6175 + echo '<textarea
6176 + id="rate_limits_' . esc_attr($role_id) . '_message"
6177 + name="mxchat_options[rate_limits][' . esc_attr($role_id) . '][message]"
6178 + class="mxchat-autosave-field"
6179 + placeholder="' . esc_attr__('Enter custom message when rate limit is exceeded', 'mxchat') . '">' .
6180 + esc_textarea($custom_message) .
6181 + '</textarea>';
6182 + echo '<p class="description">' .
6183 + esc_html__('Example: Rate limit reached! [Visit our pricing page](https://example.com/pricing) to upgrade.', 'mxchat') .
6184 + '</p>';
6185 + echo '</div>'; // End message
3875 6186
3876 - // Output the textarea
3877 - echo '<div class="pro-feature-wrapper active">';
3878 - printf(
3879 - '<textarea id="rate_limit_message" name="rate_limit_message" rows="3" cols="50">%s</textarea>',
3880 - esc_textarea($rate_limit_message)
3881 - );
3882 - echo '<p class="description">' . esc_html__('This message will be displayed when a user exceeds the rate limit.', 'mxchat') . '</p>';
3883 - echo '</div>';
3884 -}
6187 + echo '</div>'; // End controls wrapper
3885 6188
6189 + echo '</div>'; // End row
6190 + }
3886 6191
3887 -public function mxchat_enable_woocommerce_integration_callback() {
3888 - // Load from mxchat_options array
3889 - $options = get_option('mxchat_options', []);
6192 + echo '</div>'; // End container
3890 6193
3891 - // Get WooCommerce integration status with backwards compatibility
3892 - $woo_integration = isset($options['enable_woocommerce_integration'])
3893 - ? $options['enable_woocommerce_integration']
3894 - : '';
3895 -
3896 - // Support both old ('1') and new ('on') values
3897 - $checked = ($woo_integration === 'on' || $woo_integration === '1') ? 'checked' : '';
3898 -
3899 - // Check if the feature is activated (paid feature)
3900 - $disabled = $this->is_activated ? '' : 'disabled';
3901 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
3902 -
3903 - echo '<div class="' . esc_attr($class) . '">';
3904 -
3905 - echo '<label class="toggle-switch">';
3906 - echo sprintf(
3907 - '<input type="checkbox" id="enable_woocommerce_integration" name="enable_woocommerce_integration" value="on" %s %s />',
3908 - esc_attr($checked),
3909 - esc_attr($disabled)
3910 - );
3911 - echo '<span class="slider"></span>';
3912 - echo '</label>';
3913 -
3914 - echo '<p class="description">' . esc_html__('Automatically syncs new product additions, updates, and removals to the knowledge base. For existing products, submit your product sitemap in the Knowledge Base section to add them initially.', 'mxchat') . '</p>';
3915 -
3916 - // Pro feature overlay for non-activated users
3917 - if (!$this->is_activated) {
3918 - echo '<div class="pro-feature-overlay">';
3919 - echo '<a href="https://mxchat.ai/" target="_blank">';
3920 - echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
3921 - echo '</a>';
3922 - echo '</div>';
3923 - }
3924 -
3925 - echo '</div>';
6194 + echo '</div>'; // End pro-feature-wrapper
3926 6195 }
3927 6196
3928 -
3929 -
3930 -
3931 6197 private function mxchat_add_option_field($id, $title, $callback = '') {
3932 6198 add_settings_field(
3933 6199 $id,
3934 6200 __($title, 'mxchat'),
@@ -3938,162 +6204,105 @@
3938 6204 $id === 'model' ? ['label_for' => 'model'] : []
3939 6205 );
3940 6206 }
3941 6207
6208 +// API Keys Section Callback
6209 +public function mxchat_api_keys_section_callback() {
6210 + echo '<p>' . esc_html__('Manage all your API keys in one place. Add the API keys for the services you want to use with your chatbot.', 'mxchat') . '</p>';
6211 +}
6212 +
6213 +// OpenAI API Key
3942 6214 public function api_key_callback() {
3943 - // Retrieve from your stored 'api_key' in the mxchat_options array
3944 6215 $apiKey = isset($this->options['api_key']) ? esc_attr($this->options['api_key']) : '';
3945 6216
3946 - // Notice we changed the name to "api_key" (no array notation).
3947 - echo '<input type="password" id="api_key" name="api_key" value="' . $apiKey . '" class="regular-text" />';
6217 + echo '<div class="api-key-wrapper">';
6218 + echo '<input type="password" id="api_key" name="api_key" value="' . $apiKey . '" class="regular-text" autocomplete="off" />';
3948 6219 echo '<button type="button" id="toggleApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
3949 - echo '<p class="description">' . esc_html__('The OpenAI API key is required even when using other models as it is used for vector embedding functionality. You must add credits to your OpenAI billing account before use.', 'mxchat') . '</p>';
6220 + echo '<p class="description">' . esc_html__('Required for OpenAI GPT models and OpenAI embeddings. Get your API key from OpenAI Platform.', 'mxchat') . '</p>';
6221 + echo '</div>';
3950 6222 }
6223 +
6224 +// X.AI API Key
3951 6225 public function xai_api_key_callback() {
3952 - // Check if the feature is activated (paid feature)
3953 - $disabled = $this->is_activated ? '' : 'disabled'; // Disable input if not activated
3954 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive'; // CSS class to style the wrapper based on activation status
3955 -
3956 - // Retrieve the X.AI API key value
3957 6226 $xaiApiKey = isset($this->options['xai_api_key']) ? esc_attr($this->options['xai_api_key']) : '';
3958 6227
3959 - // Render the input field for the X.AI API key
3960 - echo '<div class="' . esc_attr($class) . '">';
3961 - printf(
3962 - '<input type="password" id="xai_api_key" name="xai_api_key" value="%s" class="regular-text" %s />',
3963 - $xaiApiKey,
3964 - $disabled
3965 - );
6228 + echo '<div class="api-key-wrapper">';
6229 + echo '<input type="password" id="xai_api_key" name="xai_api_key" value="' . $xaiApiKey . '" class="regular-text" autocomplete="off" />';
3966 6230 echo '<button type="button" id="toggleXaiApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
3967 -
3968 - // If the feature is not activated, show the overlay with a "Pro Only" message
3969 - if (!$this->is_activated) {
3970 - echo '<div class="pro-feature-overlay">';
3971 - echo '<a href="https://mxchat.ai/" target="_blank"><img src="' . plugin_dir_url(__FILE__) . '../images/pro-only-dark.png" alt="' . esc_attr__('Pro Only', 'mxchat') . '" /></a>';
3972 - echo '</div>';
3973 - }
3974 -
6231 + echo '<p class="description">' . esc_html__('Required for X.AI Grok models. Get your API key from X.AI Console.', 'mxchat') . '</p>';
3975 6232 echo '</div>';
3976 6233 }
6234 +// Claude API Key
3977 6235 public function claude_api_key_callback() {
3978 - // Check if the feature is activated (paid feature)
3979 - $disabled = $this->is_activated ? '' : 'disabled';
3980 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
3981 -
3982 - // Retrieve the Claude API key value directly like the others
3983 6236 $claudeApiKey = isset($this->options['claude_api_key']) ? esc_attr($this->options['claude_api_key']) : '';
3984 -
3985 - // Use direct name field like the other working API keys
3986 - echo '<div class="' . esc_attr($class) . '">';
3987 - printf(
3988 - '<input type="password" id="claude_api_key" name="claude_api_key" value="%s" class="regular-text" %s />',
3989 - $claudeApiKey,
3990 - $disabled
3991 - );
6237 +
6238 + echo '<div class="api-key-wrapper">';
6239 + echo '<input type="password" id="claude_api_key" name="claude_api_key" value="' . $claudeApiKey . '" class="regular-text" autocomplete="off" />';
3992 6240 echo '<button type="button" id="toggleClaudeApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
3993 -
3994 - if (!$this->is_activated) {
3995 - echo '<div class="pro-feature-overlay">';
3996 - echo '<a href="https://mxchat.ai/" target="_blank"><img src="' . plugin_dir_url(__FILE__) . '../images/pro-only-dark.png" alt="' . esc_attr__('Pro Only', 'mxchat') . '" /></a>';
3997 - echo '</div>';
3998 - }
6241 + echo '<p class="description">' . esc_html__('Required for Anthropic Claude models. Get your API key from Anthropic Console.', 'mxchat') . '</p>';
3999 6242 echo '</div>';
4000 6243 }
6244 +
6245 +// DeepSeek API Key
4001 6246 public function deepseek_api_key_callback() {
4002 - // Retrieve from your stored 'deepseek_api_key' in the mxchat_options array
4003 6247 $apiKey = isset($this->options['deepseek_api_key']) ? esc_attr($this->options['deepseek_api_key']) : '';
4004 6248
4005 - // Notice we changed the name to "deepseek_api_key" (no array notation).
4006 - echo '<input type="password" id="deepseek_api_key" name="deepseek_api_key" value="' . $apiKey . '" class="regular-text" />';
6249 + echo '<div class="api-key-wrapper">';
6250 + echo '<input type="password" id="deepseek_api_key" name="deepseek_api_key" value="' . $apiKey . '" class="regular-text" autocomplete="off" />';
4007 6251 echo '<button type="button" id="toggleDeepSeekApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
6252 + echo '<p class="description">' . esc_html__('Required for DeepSeek models. Get your API key from DeepSeek Platform.', 'mxchat') . '</p>';
6253 + echo '</div>';
4008 6254 }
4009 6255
6256 +// Gemini API Key
6257 +public function gemini_api_key_callback() {
6258 + $geminiApiKey = isset($this->options['gemini_api_key']) ? esc_attr($this->options['gemini_api_key']) : '';
4010 6259
4011 -public function mxchat_woocommerce_consumer_key_callback() {
4012 - // Load the entire 'mxchat_options' array
4013 - $all_options = get_option('mxchat_options', []);
6260 + echo '<div class="api-key-wrapper">';
6261 + echo '<input type="password" id="gemini_api_key" name="gemini_api_key" value="' . $geminiApiKey . '" class="regular-text" autocomplete="off" />';
6262 + echo '<button type="button" id="toggleGeminiApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
6263 + echo '<p class="description">' . esc_html__('Required for Google Gemini models and embeddings. Get your API key from Google AI Studio.', 'mxchat') . '</p>';
6264 + echo '</div>';
6265 +}
4014 6266
4015 - // Retrieve the WooCommerce consumer key or set a default
4016 - $consumer_key = isset($all_options['woocommerce_consumer_key']) ? $all_options['woocommerce_consumer_key'] : '';
4017 6267
4018 - // Check if the feature is activated (paid feature)
4019 - $disabled = $this->is_activated ? '' : 'disabled';
4020 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
4021 -
4022 - echo '<div class="' . esc_attr($class) . '">';
4023 -
4024 - // Render the input field
4025 - printf(
4026 - '<input type="text" id="woocommerce_consumer_key" name="woocommerce_consumer_key" value="%s" class="regular-text" %s />',
4027 - esc_attr($consumer_key),
4028 - esc_attr($disabled)
4029 - );
4030 -
4031 - // Description for the input field
4032 - echo '<p class="description">' . esc_html__('Enter your WooCommerce consumer key for integration.', 'mxchat') . '</p>';
4033 -
4034 - // Pro feature overlay for non-activated users
4035 - if (!$this->is_activated) {
4036 - echo '<div class="pro-feature-overlay">';
4037 - echo '<a href="https://mxchat.ai/" target="_blank"><img src="' . plugin_dir_url(__FILE__) . '../images/pro-only-dark.png" alt="' . esc_attr__('Pro Only', 'mxchat') . '" /></a>';
4038 - echo '</div>';
4039 - }
4040 -
6268 +// OpenRouter API Key
6269 +public function openrouter_api_key_callback() {
6270 + $openrouterApiKey = isset($this->options['openrouter_api_key']) ? esc_attr($this->options['openrouter_api_key']) : '';
6271 + echo '<div class="api-key-wrapper">';
6272 + echo '<input type="password" id="openrouter_api_key" name="openrouter_api_key" value="' . $openrouterApiKey . '" class="regular-text" autocomplete="off" />';
6273 + echo '<button type="button" id="toggleOpenRouterApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
6274 + echo '<p class="description">' . esc_html__('Required for OpenRouter models. Get your API key from OpenRouter.ai', 'mxchat') . '</p>';
4041 6275 echo '</div>';
4042 6276 }
4043 6277
4044 -public function mxchat_woocommerce_consumer_secret_callback() {
4045 - // Get value with fallback
4046 - $consumer_secret = isset($this->options['woocommerce_consumer_secret'])
4047 - ? esc_attr($this->options['woocommerce_consumer_secret'])
4048 - : '';
6278 +// Voyage API Key
6279 +public function voyage_api_key_callback() {
6280 + $apiKey = isset($this->options['voyage_api_key']) ? esc_attr($this->options['voyage_api_key']) : '';
4049 6281
4050 - // Check if the feature is activated (paid feature)
4051 - $disabled = $this->is_activated ? '' : 'disabled';
4052 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
4053 -
4054 - echo '<div class="' . esc_attr($class) . '">';
4055 -
4056 - // Output the password input
4057 - echo sprintf(
4058 - '<input type="password"
4059 - id="woocommerce_consumer_secret"
4060 - name="woocommerce_consumer_secret"
4061 - value="%s"
4062 - class="regular-text"
4063 - %s />',
4064 - $consumer_secret,
4065 - esc_attr($disabled)
4066 - );
4067 -
4068 - // Add show/hide button
4069 - echo '<button type="button" id="toggleWooCommerceSecretVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
4070 -
4071 - // Pro feature overlay
4072 - if (!$this->is_activated) {
4073 - echo '<div class="pro-feature-overlay">';
4074 - echo '<a href="https://mxchat.ai/" target="_blank">';
4075 - echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
4076 - echo '</a>';
4077 - echo '</div>';
4078 - }
4079 -
6282 + echo '<div class="api-key-wrapper">';
6283 + echo '<input type="password" id="voyage_api_key" name="voyage_api_key" value="' . $apiKey . '" class="regular-text" autocomplete="off" />';
6284 + echo '<button type="button" id="toggleVoyageAPIKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
6285 + echo '<p class="description">' . esc_html__('Required for Voyage AI embedding models. Get your API key from Voyage AI.', 'mxchat') . '</p>';
4080 6286 echo '</div>';
4081 6287 }
6288 +
4082 6289 public function mxchat_loops_api_key_callback() {
4083 - // Support both old and new format
4084 6290 $loops_api_key = isset($this->options['loops_api_key']) ? esc_attr($this->options['loops_api_key']) : '';
4085 6291
6292 + // Hidden fields to "trap" autofill
6293 + echo '<input type="text" style="display:none" autocomplete="username" />';
6294 + echo '<input type="password" style="display:none" autocomplete="current-password" />';
6295 +
4086 6296 echo '<div class="api-key-wrapper">';
4087 6297 echo sprintf(
4088 - '<input type="password" id="loops_api_key" name="loops_api_key" value="%s" class="regular-text" />',
6298 + '<input type="password" id="loops_api_key" name="loops_api_key" value="%s" class="regular-text" autocomplete="new-password" />',
4089 6299 $loops_api_key
4090 6300 );
4091 6301 echo '<button type="button" id="toggleLoopsApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
4092 6302 echo '</div>';
4093 - echo '<p class="description">' . esc_html__('Enter your Loops API Key here. (See FAQ for details)', 'mxchat') . '</p>';
6303 + echo '<p class="description">' . esc_html__('Required for Loops email integration. Get your API key from Loops.so', 'mxchat') . '</p>';
4094 6304 }
4095 -
4096 6305 public function mxchat_loops_mailing_list_callback() {
4097 6306 // Add error handling and type checking
4098 6307 $loops_api_key = '';
4099 6308 $selected_list = '';
@@ -4111,8 +6320,12 @@
4111 6320 if (!empty($loops_api_key)) {
4112 6321 $lists = $this->mxchat_fetch_loops_mailing_lists($loops_api_key);
4113 6322 if (is_array($lists) && !empty($lists)) {
4114 6323 echo '<select id="loops_mailing_list" name="loops_mailing_list">';
6324 +
6325 + // Add a default "Select a list" option
6326 + echo '<option value="" ' . selected($selected_list, '', false) . '>' . esc_html__('Select a list', 'mxchat') . '</option>';
6327 +
4115 6328 foreach ($lists as $list) {
4116 6329 if (is_array($list) && isset($list['id']) && isset($list['name'])) {
4117 6330 echo sprintf(
4118 6331 '<option value="%s" %s>%s</option>',
@@ -4122,8 +6335,9 @@
4122 6335 );
4123 6336 }
4124 6337 }
4125 6338 echo '</select>';
6339 + echo '<p class="description">' . esc_html__('Please select a mailing list to use with Loops.', 'mxchat') . '</p>';
4126 6340 } else {
4127 6341 echo '<p class="description">' . esc_html__('No lists found. Please verify your API Key.', 'mxchat') . '</p>';
4128 6342 }
4129 6343 } else {
@@ -4134,14 +6348,13 @@
4134 6348 $default_response = __('Would you like to join our mailing list? Please provide your email below.', 'mxchat');
4135 6349 $triggered_response = isset($this->options['triggered_phrase_response'])
4136 6350 ? $this->options['triggered_phrase_response']
4137 6351 : $default_response;
4138 -
4139 6352 echo sprintf(
4140 6353 '<textarea id="triggered_phrase_response" name="triggered_phrase_response" rows="3" cols="50">%s</textarea>',
4141 6354 esc_textarea($triggered_response)
4142 6355 );
4143 - echo '<p class="description">' . esc_html__('Enter the chatbot response when a trigger keyword is detected, prompting the user to share their email.', 'mxchat') . '</p>';
6356 + echo '<p class="description">' . esc_html__('Enter the instruction for the AI when a trigger keyword is detected. The AI will use this as guidance to naturally ask for the user\'s email in a conversational way.', 'mxchat') . '</p>';
4144 6357 }
4145 6358
4146 6359 public function mxchat_email_capture_response_callback() {
4147 6360 $default_response = __('Thank you for providing your email! You\'ve been added to our list.', 'mxchat');
@@ -4147,16 +6360,14 @@
4147 6360 $default_response = __('Thank you for providing your email! You\'ve been added to our list.', 'mxchat');
4148 6361 $email_capture_response = isset($this->options['email_capture_response'])
4149 6362 ? $this->options['email_capture_response']
4150 6363 : $default_response;
4151 -
4152 6364 echo sprintf(
4153 6365 '<textarea id="email_capture_response" name="email_capture_response" rows="3" cols="50">%s</textarea>',
4154 6366 esc_textarea($email_capture_response)
4155 6367 );
4156 - echo '<p class="description">' . esc_html__('Enter the message to send when a user provides their email.', 'mxchat') . '</p>';
6368 + echo '<p class="description">' . esc_html__('Enter the instruction for the AI when a user provides their email. The AI will use this as guidance to naturally confirm the email capture in a conversational way.', 'mxchat') . '</p>';
4157 6369 }
4158 -
4159 6370 public function mxchat_pre_chat_message_callback() {
4160 6371 // Load the entire 'mxchat_options' array
4161 6372 $all_options = get_option('mxchat_options', []);
4162 6373
@@ -4171,49 +6382,152 @@
4171 6382 );
4172 6383 echo '<p class="description">' . esc_html__('Set the message displayed to users before they start a chat. Use this to provide a friendly greeting or instructions.', 'mxchat') . '</p>';
4173 6384 }
4174 6385
4175 -
4176 -
4177 6386 // Callback for AI Instructions textarea
4178 6387 public function system_prompt_instructions_callback() {
4179 6388 // Retrieve the current value of the system prompt instructions
4180 6389 $instructions = isset($this->options['system_prompt_instructions']) ? esc_textarea($this->options['system_prompt_instructions']) : '';
4181 -
4182 6390 // Render the textarea field
4183 6391 printf(
4184 6392 '<textarea id="system_prompt_instructions" name="system_prompt_instructions" rows="5" cols="50">%s</textarea>',
4185 6393 $instructions
4186 6394 );
6395 + // Provide a helpful description with sample instructions button
6396 + echo '<p class="description">' . esc_html__('Provide system-level instructions for the AI to guide its behavior. Be clear and concise for better results.', 'mxchat') . '</p>';
6397 + echo '<div class="mxchat-instructions-container">';
6398 + echo '<button type="button" class="mxchat-instructions-btn" id="mxchatViewSampleBtn">';
6399 + echo '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">';
6400 + echo '<path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/>';
6401 + echo '<path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/>';
6402 + echo '</svg>';
6403 + echo esc_html__('View Sample Instructions', 'mxchat');
6404 + echo '</button>';
6405 + echo '</div>';
4187 6406
4188 - // Provide a helpful description
4189 - 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>';
6407 + // Add modal to WordPress admin footer instead of inline
6408 + add_action('admin_footer', array($this, 'render_sample_instructions_modal'));
4190 6409 }
4191 6410
6411 +// New method to render modal in admin footer
6412 +public function render_sample_instructions_modal() {
6413 + static $modal_rendered = false;
6414 + if ($modal_rendered) return; // Prevent duplicate modals
6415 + $modal_rendered = true;
4192 6416
6417 + echo '<div class="mxchat-instructions-modal-overlay" id="mxchatSampleModal">';
6418 + echo '<div class="mxchat-instructions-modal-content">';
6419 + echo '<div class="mxchat-instructions-modal-header">';
6420 + echo '<h3 class="mxchat-instructions-modal-title">';
6421 + echo '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">';
6422 + echo '<path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/>';
6423 + echo '<path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/>';
6424 + echo '</svg>';
6425 + echo esc_html__('Sample AI Instructions', 'mxchat');
6426 + echo '</h3>';
6427 + echo '<button type="button" class="mxchat-instructions-modal-close" id="mxchatModalClose">';
6428 + echo '<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">';
6429 + echo '<line x1="18" y1="6" x2="6" y2="18"/>';
6430 + echo '<line x1="6" y1="6" x2="18" y2="18"/>';
6431 + echo '</svg>';
6432 + echo '</button>';
6433 + echo '</div>';
6434 + echo '<div class="mxchat-instructions-modal-body">';
6435 + echo '<div class="mxchat-instructions-content">';
6436 + echo esc_html('You are an AI Chatbot assistant for this website. Your main goal is to assist visitors with questions and provide helpful information. Here are your key guidelines:
6437 +
6438 +# Response Style - CRITICALLY IMPORTANT
6439 +- MAXIMUM LENGTH: 1-3 short sentences per response
6440 +- Ultra-concise: Get straight to the answer with no filler
6441 +- No introductions like "Sure!" or "I\'d be happy to help"
6442 +- No phrases like "based on my knowledge" or "according to information"
6443 +- No explanatory text before giving the answer
6444 +- No summaries or repetition
6445 +- Hyperlink all URLs
6446 +- Respond in user\'s language
6447 +- Minor chit chat or conversation is okay, but try to keep it focused on [insert topic]
6448 +
6449 +# Knowledge Base Requirements - PREVENT HALLUCINATIONS
6450 +- ONLY answer using information explicitly provided in OFFICIAL KNOWLEDGE DATABASE CONTENT sections marked with ===== delimiters
6451 +- If required information is NOT in the knowledge database: "I don\'t have enough information in my knowledge base to answer that question accurately."
6452 +- NEVER invent or hallucinate URLs, links, product specs, procedures, dates, statistics, names, contacts, or company information
6453 +- When knowledge base information is unclear or contradictory, acknowledge the limitation rather than guessing
6454 +- Better to admit insufficient information than provide inaccurate answers');
6455 + echo '</div>';
6456 + echo '<button type="button" class="mxchat-instructions-copy-btn" id="mxchatCopyBtn">';
6457 + echo '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">';
6458 + echo '<rect x="9" y="9" width="13" height="13" rx="2" ry="2"/>';
6459 + echo '<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/>';
6460 + echo '</svg>';
6461 + echo esc_html__('Copy Instructions', 'mxchat');
6462 + echo '</button>';
6463 + echo '</div>';
6464 + echo '<div class="mxchat-instructions-modal-footer">';
6465 + echo '<button type="button" class="mxchat-instructions-btn-secondary" id="mxchatCloseBtn">' . esc_html__('Close', 'mxchat') . '</button>';
6466 + echo '</div>';
6467 + echo '</div>';
6468 + echo '</div>';
6469 +}
6470 +
6471 +
4193 6472 public function mxchat_model_callback() {
4194 6473 // Define available models grouped by provider
4195 6474 $models = array(
6475 + esc_html__('OpenRouter (100+ Models)', 'mxchat') => array(
6476 + 'openrouter' => esc_html__('OpenRouter - Select after entering API key', 'mxchat'),
6477 + ),
6478 + esc_html__('Google Gemini Models', 'mxchat') => array(
6479 + 'gemini-2.0-flash' => esc_html__('Gemini 2.0 Flash (Next-Gen Features)', 'mxchat'),
6480 + 'gemini-2.0-flash-lite' => esc_html__('Gemini 2.0 Flash-Lite (Cost-Efficient)', 'mxchat'),
6481 + 'gemini-1.5-pro' => esc_html__('Gemini 1.5 Pro (Complex Reasoning)', 'mxchat'),
6482 + 'gemini-1.5-flash' => esc_html__('Gemini 1.5 Flash (Fast & Versatile)', 'mxchat'),
6483 + ),
6484 + esc_html__('Google Gemini Models', 'mxchat') => array(
6485 + 'gemini-2.0-flash' => esc_html__('Gemini 2.0 Flash (Next-Gen Features)', 'mxchat'),
6486 + 'gemini-2.0-flash-lite' => esc_html__('Gemini 2.0 Flash-Lite (Cost-Efficient)', 'mxchat'),
6487 + 'gemini-1.5-pro' => esc_html__('Gemini 1.5 Pro (Complex Reasoning)', 'mxchat'),
6488 + 'gemini-1.5-flash' => esc_html__('Gemini 1.5 Flash (Fast & Versatile)', 'mxchat'),
6489 + ),
4196 6490 esc_html__('X.AI Models', 'mxchat') => array(
4197 - 'grok-beta' => esc_html__('grok-beta (Early Beta)', 'mxchat'),
4198 - 'grok-2' => esc_html__('Grok 2', 'mxchat')
6491 + 'grok-3-beta' => esc_html__('Grok-3 (Powerful)', 'mxchat'),
6492 + 'grok-3-fast-beta' => esc_html__('Grok-3 Fast (High Performance)', 'mxchat'),
6493 + 'grok-3-mini-beta' => esc_html__('Grok-3 Mini (Affordable)', 'mxchat'),
6494 + 'grok-3-mini-fast-beta' => esc_html__('Grok-3 Mini Fast (Quick Response)', 'mxchat'),
6495 + 'grok-2' => esc_html__('Grok 2', 'mxchat'),
6496 + 'grok-4-0709' => esc_html__('Grok 4 (Latest Flagship)', 'mxchat'),
6497 + 'grok-4-1-fast-reasoning' => esc_html__('Grok 4.1 Fast (Reasoning)', 'mxchat'),
6498 + 'grok-4-1-fast-non-reasoning' => esc_html__('Grok 4.1 Fast (Non-Reasoning)', 'mxchat'),
4199 6499 ),
4200 6500 esc_html__('DeepSeek Models', 'mxchat') => array(
4201 6501 'deepseek-chat' => esc_html__('DeepSeek-V3', 'mxchat'),
4202 6502 ),
4203 6503 esc_html__('Claude Models', 'mxchat') => array(
4204 - 'claude-3-5-sonnet-20241022' => esc_html__('Claude 3.5 Sonnet (Most Intelligent)', 'mxchat'),
4205 - 'claude-3-opus-20240229' => esc_html__('Claude 3 Opus (Highly Complex Tasks)', 'mxchat'),
6504 + 'claude-sonnet-4-5-20250929' => esc_html__('Claude Sonnet 4.5 (Best for Agents & Coding)', 'mxchat'),
6505 + 'claude-opus-4-1-20250805' => esc_html__('Claude Opus 4.1 (Exceptional for Complex Tasks)', 'mxchat'),
6506 + 'claude-haiku-4-5-20251001' => esc_html__('Claude Haiku 4.5 (Fastest & Most Intelligent)', 'mxchat'),
6507 + 'claude-opus-4-20250514' => esc_html__('Claude 4 Opus (Most Capable)', 'mxchat'),
6508 + 'claude-sonnet-4-20250514' => esc_html__('Claude 4 Sonnet (High Performance)', 'mxchat'),
6509 + 'claude-3-7-sonnet-20250219' => esc_html__('Claude 3.7 Sonnet (High Intelligence)', 'mxchat'),
6510 + 'claude-3-opus-20240229' => esc_html__('Claude 3 Opus (Complex Tasks)', 'mxchat'),
4206 6511 'claude-3-sonnet-20240229' => esc_html__('Claude 3 Sonnet (Balanced)', 'mxchat'),
4207 - 'claude-3-haiku-20240307' => esc_html__('Claude 3 Haiku (Fastest)', 'mxchat')
6512 + 'claude-3-haiku-20240307' => esc_html__('Claude 3 Haiku (Fastest)', 'mxchat'),
4208 6513 ),
4209 6514 esc_html__('OpenAI Models', 'mxchat') => array(
6515 + // GPT-5 family
6516 + 'gpt-5.2' => esc_html__('GPT-5.2 (Best General-Purpose & Agentic Model)', 'mxchat'),
6517 + 'gpt-5.1-2025-11-13' => esc_html__('GPT-5.1 (Flagship for Coding & Agentic Tasks)', 'mxchat'),
6518 + 'gpt-5' => esc_html__('GPT-5 (Flagship for Coding, Reasoning & Agents)', 'mxchat'),
6519 + 'gpt-5-mini' => esc_html__('GPT-5 Mini (Faster, Cost-Efficient for Precise Prompts)', 'mxchat'),
6520 + 'gpt-5-nano' => esc_html__('GPT-5 Nano (Fastest & Cheapest for Summarization/Classification)', 'mxchat'),
6521 +
6522 + // Existing OpenAI models
6523 + 'gpt-4.1-2025-04-14' => esc_html__('GPT-4.1 (Flagship for Complex Tasks)', 'mxchat'),
4210 6524 'gpt-4o' => esc_html__('GPT-4o (Recommended)', 'mxchat'),
4211 6525 'gpt-4o-mini' => esc_html__('GPT-4o Mini (Fast and Lightweight)', 'mxchat'),
4212 6526 'gpt-4-turbo' => esc_html__('GPT-4 Turbo (High-Performance)', 'mxchat'),
4213 6527 'gpt-4' => esc_html__('GPT-4 (High Intelligence)', 'mxchat'),
4214 - 'gpt-3.5-turbo' => esc_html__('GPT-3.5 Turbo (Affordable and Fast)', 'mxchat')
4215 - )
6528 + 'gpt-3.5-turbo' => esc_html__('GPT-3.5 Turbo (Affordable and Fast)', 'mxchat'),
6529 + ),
4216 6530 );
4217 6531
4218 6532 // Retrieve the currently selected model from saved options
4219 6533 $selected_model = isset($this->options['model']) ? esc_attr($this->options['model']) : 'gpt-4o';
@@ -4218,9 +6532,9 @@
4218 6532 // Retrieve the currently selected model from saved options
4219 6533 $selected_model = isset($this->options['model']) ? esc_attr($this->options['model']) : 'gpt-4o';
4220 6534
4221 6535 // Begin the select dropdown
4222 - echo '<select id="model" name="model">'; // No array notation in name attribute
6536 + echo '<select id="model" name="model">';
4223 6537
4224 6538 // Iterate over groups of models
4225 6539 foreach ($models as $group_label => $group_models) {
4226 6540 echo '<optgroup label="' . esc_attr($group_label) . '">';
@@ -4225,28 +6539,228 @@
4225 6539 foreach ($models as $group_label => $group_models) {
4226 6540 echo '<optgroup label="' . esc_attr($group_label) . '">';
4227 6541
4228 6542 foreach ($group_models as $model_value => $model_label) {
4229 - // Disable Pro-only models for non-activated users
4230 - $disabled = (!$this->is_activated && ($group_label === esc_html__('X.AI Models', 'mxchat') || $group_label === esc_html__('Claude Models', 'mxchat'))) ? 'disabled' : '';
4231 - $label_suffix = (!$this->is_activated && ($group_label === esc_html__('X.AI Models', 'mxchat') || $group_label === esc_html__('Claude Models', 'mxchat'))) ? esc_html__(' (Pro Only)', 'mxchat') : '';
4232 -
4233 - // Output the option element
4234 - echo '<option value="' . esc_attr($model_value) . '" ' . selected($selected_model, $model_value, false) . ' ' . $disabled . '>' . esc_html($model_label . $label_suffix) . '</option>';
6543 + echo '<option value="' . esc_attr($model_value) . '" ' . selected($selected_model, $model_value, false) . '>' . esc_html($model_label) . '</option>';
4235 6544 }
4236 6545
4237 6546 echo '</optgroup>';
4238 6547 }
4239 6548
4240 - // Close the select dropdown
4241 6549 echo '</select>';
4242 6550
4243 - // Add a description below the dropdown
4244 - echo '<p class="description">' . esc_html__('Select the AI model to use for your chatbot. Pro-only models are marked accordingly.', 'mxchat') . '</p>';
6551 + echo '<p class="description">' . esc_html__('Select the AI model your chatbot will use for chatting.', 'mxchat') . '</p>';
6552 +
6553 + // Add a note for OpenRouter
6554 + echo '<p class="description" id="openrouter-model-note" style="display:none; color: #d63638; font-weight: 500;">';
6555 + echo '<span class="dashicons dashicons-info" style="font-size: 16px; vertical-align: middle;"></span> ';
6556 + echo esc_html__('After entering your OpenRouter API key above, click the button below to load available models.', 'mxchat');
6557 + echo '</p>';
6558 +
6559 + // API Key Status Messages (hidden by default, shown by JS based on selected model)
6560 + $has_openai_key = !empty($this->options['api_key']);
6561 + $has_claude_key = !empty($this->options['claude_api_key']);
6562 + $has_xai_key = !empty($this->options['xai_api_key']);
6563 + $has_deepseek_key = !empty($this->options['deepseek_api_key']);
6564 + $has_gemini_key = !empty($this->options['gemini_api_key']);
6565 + $has_openrouter_key = !empty($this->options['openrouter_api_key']);
6566 +
6567 + // OpenAI/GPT models
6568 + echo '<p class="mxchat-api-status" data-provider="openai" style="display:none;">';
6569 + if ($has_openai_key) {
6570 + echo '<span style="color: #00a32a;">✓ ' . esc_html__('API key for OpenAI detected', 'mxchat') . '</span>';
6571 + } else {
6572 + echo '<span style="color: #d63638;">⚠ ' . esc_html__('No API key for OpenAI detected. Please enter API key in API Keys tab.', 'mxchat') . '</span>';
6573 + }
6574 + echo '</p>';
6575 +
6576 + // Claude models
6577 + echo '<p class="mxchat-api-status" data-provider="claude" style="display:none;">';
6578 + if ($has_claude_key) {
6579 + echo '<span style="color: #00a32a;">✓ ' . esc_html__('API key for Anthropic (Claude) detected', 'mxchat') . '</span>';
6580 + } else {
6581 + echo '<span style="color: #d63638;">⚠ ' . esc_html__('No API key for Anthropic (Claude) detected. Please enter API key in API Keys tab.', 'mxchat') . '</span>';
6582 + }
6583 + echo '</p>';
6584 +
6585 + // X.AI models
6586 + echo '<p class="mxchat-api-status" data-provider="xai" style="display:none;">';
6587 + if ($has_xai_key) {
6588 + echo '<span style="color: #00a32a;">✓ ' . esc_html__('API key for X.AI (Grok) detected', 'mxchat') . '</span>';
6589 + } else {
6590 + echo '<span style="color: #d63638;">⚠ ' . esc_html__('No API key for X.AI (Grok) detected. Please enter API key in API Keys tab.', 'mxchat') . '</span>';
6591 + }
6592 + echo '</p>';
6593 +
6594 + // DeepSeek models
6595 + echo '<p class="mxchat-api-status" data-provider="deepseek" style="display:none;">';
6596 + if ($has_deepseek_key) {
6597 + echo '<span style="color: #00a32a;">✓ ' . esc_html__('API key for DeepSeek detected', 'mxchat') . '</span>';
6598 + } else {
6599 + echo '<span style="color: #d63638;">⚠ ' . esc_html__('No API key for DeepSeek detected. Please enter API key in API Keys tab.', 'mxchat') . '</span>';
6600 + }
6601 + echo '</p>';
6602 +
6603 + // Gemini models
6604 + echo '<p class="mxchat-api-status" data-provider="gemini" style="display:none;">';
6605 + if ($has_gemini_key) {
6606 + echo '<span style="color: #00a32a;">✓ ' . esc_html__('API key for Google Gemini detected', 'mxchat') . '</span>';
6607 + } else {
6608 + echo '<span style="color: #d63638;">⚠ ' . esc_html__('No API key for Google Gemini detected. Please enter API key in API Keys tab.', 'mxchat') . '</span>';
6609 + }
6610 + echo '</p>';
6611 +
6612 + // OpenRouter models
6613 + echo '<p class="mxchat-api-status" data-provider="openrouter" style="display:none;">';
6614 + if ($has_openrouter_key) {
6615 + echo '<span style="color: #00a32a;">✓ ' . esc_html__('API key for OpenRouter detected', 'mxchat') . '</span>';
6616 + } else {
6617 + echo '<span style="color: #d63638;">⚠ ' . esc_html__('No API key for OpenRouter detected. Please enter API key in API Keys tab.', 'mxchat') . '</span>';
6618 + }
6619 + echo '</p>';
6620 +
6621 + // ADD THESE HIDDEN FIELDS RIGHT HERE:
6622 + $openrouter_model = isset($this->options['openrouter_selected_model']) ? esc_attr($this->options['openrouter_selected_model']) : '';
6623 + $openrouter_model_name = isset($this->options['openrouter_selected_model_name']) ? esc_attr($this->options['openrouter_selected_model_name']) : '';
6624 +
6625 + echo '<input type="hidden" id="openrouter_selected_model" name="openrouter_selected_model" value="' . $openrouter_model . '" />';
6626 + echo '<input type="hidden" id="openrouter_selected_model_name" name="openrouter_selected_model_name" value="' . $openrouter_model_name . '" />';
4245 6627 }
4246 6628
6629 +// Update your existing callback method
6630 +public function enable_streaming_toggle_callback() {
6631 + // Get value from options array, default to 'on'
6632 + $enabled = isset($this->options['enable_streaming_toggle']) ? $this->options['enable_streaming_toggle'] : 'on';
6633 + $checked = ($enabled === 'on') ? 'checked' : '';
4247 6634
6635 + echo '<label class="toggle-switch">';
6636 + echo sprintf(
6637 + '<input type="checkbox" id="enable_streaming_toggle" name="enable_streaming_toggle" value="on" %s />',
6638 + esc_attr($checked)
6639 + );
6640 + echo '<span class="slider"></span>';
6641 + echo '</label>';
6642 + echo '<p class="description">' .
6643 + esc_html__('Enable real-time streaming responses for supported models (OpenAI, Claude, DeepSeek, and Grok). When disabled, responses will load all at once.', 'mxchat') .
6644 + '</p>';
4248 6645
6646 + // Test button with better styling
6647 + echo '<div style="margin-top: 15px;">';
6648 + echo '<button type="button" id="mxchat-test-streaming-btn" class="button button-secondary">';
6649 + echo '<span class="dashicons dashicons-admin-tools" style="vertical-align: middle; margin-right: 5px;"></span>';
6650 + echo esc_html__('Test Streaming Compatibility', 'mxchat');
6651 + echo '</button>';
6652 + echo '<p id="mxchat-test-streaming-result" style="margin-top:10px; font-weight: bold;"></p>';
6653 + echo '</div>';
6654 +}
6655 +
6656 +// AJAX handler to fetch OpenRouter models
6657 +public function fetch_openrouter_models() {
6658 + check_ajax_referer('mxchat_fetch_openrouter_models', 'nonce');
6659 +
6660 + $api_key = isset($_POST['api_key']) ? sanitize_text_field($_POST['api_key']) : '';
6661 +
6662 + if (empty($api_key)) {
6663 + wp_send_json_error(array('message' => 'API key is required'));
6664 + }
6665 +
6666 + $response = wp_remote_get('https://openrouter.ai/api/v1/models', array(
6667 + 'headers' => array(
6668 + 'Authorization' => 'Bearer ' . $api_key,
6669 + 'Content-Type' => 'application/json',
6670 + ),
6671 + 'timeout' => 15,
6672 + ));
6673 +
6674 + if (is_wp_error($response)) {
6675 + wp_send_json_error(array('message' => $response->get_error_message()));
6676 + }
6677 +
6678 + $body = wp_remote_retrieve_body($response);
6679 + $data = json_decode($body, true);
6680 +
6681 + if (isset($data['data']) && is_array($data['data'])) {
6682 + // Format the models for the frontend
6683 + $models = array_map(function($model) {
6684 + return array(
6685 + 'id' => $model['id'],
6686 + 'name' => $model['name'] ?? $model['id'],
6687 + 'description' => $model['description'] ?? '',
6688 + 'context_length' => $model['context_length'] ?? 0,
6689 + 'pricing' => array(
6690 + 'prompt' => $model['pricing']['prompt'] ?? 0,
6691 + 'completion' => $model['pricing']['completion'] ?? 0,
6692 + ),
6693 + );
6694 + }, $data['data']);
6695 +
6696 + wp_send_json_success(array('models' => $models));
6697 + } else {
6698 + wp_send_json_error(array('message' => 'Invalid response from OpenRouter'));
6699 + }
6700 +}
6701 +
6702 +// Callback function for embedding model selection
6703 +public function embedding_model_callback() {
6704 + $models = array(
6705 + esc_html__('OpenAI Embeddings', 'mxchat') => array(
6706 + 'text-embedding-3-small' => esc_html__('TE3 Small (1536, Efficient)', 'mxchat'),
6707 + 'text-embedding-ada-002' => esc_html__('Ada 2 (1536, Recommended)', 'mxchat'),
6708 + 'text-embedding-3-large' => esc_html__('TE3 Large (3072, Powerful)', 'mxchat'),
6709 + ),
6710 + esc_html__('Voyage AI Embeddings', 'mxchat') => array(
6711 + 'voyage-3-large' => esc_html__('Voyage-3 Large (2048, Most Capable)', 'mxchat'),
6712 + ),
6713 + esc_html__('Google Gemini Embeddings', 'mxchat') => array(
6714 + 'gemini-embedding-exp-03-07' => esc_html__('Gemini Embedding (1536, Experimental)', 'mxchat'),
6715 + )
6716 + );
6717 + $selected_model = isset($this->options['embedding_model']) ? esc_attr($this->options['embedding_model']) : 'text-embedding-ada-002';
6718 + echo '<select id="embedding_model" name="embedding_model">';
6719 + foreach ($models as $group_label => $group_models) {
6720 + echo '<optgroup label="' . esc_attr($group_label) . '">';
6721 + foreach ($group_models as $model_value => $model_label) {
6722 + echo '<option value="' . esc_attr($model_value) . '" ' . selected($selected_model, $model_value, false) . '>' . esc_html($model_label) . '</option>';
6723 + }
6724 + echo '</optgroup>';
6725 + }
6726 + echo '</select>';
6727 + echo '<p class="description"><span class="red-warning">IMPORTANT:</span> A vector embedding model is required for MxChat to function. Changing models is not recommended; if you do, you must delete all existing knowledge & intent data and reconfigure them.</p>';
6728 +
6729 + // API Key Status Messages for Embedding Models
6730 + $has_openai_key = !empty($this->options['api_key']);
6731 + $has_voyage_key = !empty($this->options['voyage_api_key']);
6732 + $has_gemini_key = !empty($this->options['gemini_api_key']);
6733 +
6734 + // OpenAI Embeddings
6735 + echo '<p class="mxchat-embedding-api-status" data-provider="openai" style="display:none;">';
6736 + if ($has_openai_key) {
6737 + echo '<span style="color: #00a32a;">✓ ' . esc_html__('API key for OpenAI detected', 'mxchat') . '</span>';
6738 + } else {
6739 + echo '<span style="color: #d63638;">⚠ ' . esc_html__('No API key for OpenAI detected. Please enter API key in API Keys tab.', 'mxchat') . '</span>';
6740 + }
6741 + echo '</p>';
6742 +
6743 + // Voyage AI Embeddings
6744 + echo '<p class="mxchat-embedding-api-status" data-provider="voyage" style="display:none;">';
6745 + if ($has_voyage_key) {
6746 + echo '<span style="color: #00a32a;">✓ ' . esc_html__('API key for Voyage AI detected', 'mxchat') . '</span>';
6747 + } else {
6748 + echo '<span style="color: #d63638;">⚠ ' . esc_html__('No API key for Voyage AI detected. Please enter API key in API Keys tab.', 'mxchat') . '</span>';
6749 + }
6750 + echo '</p>';
6751 +
6752 + // Gemini Embeddings
6753 + echo '<p class="mxchat-embedding-api-status" data-provider="gemini" style="display:none;">';
6754 + if ($has_gemini_key) {
6755 + echo '<span style="color: #00a32a;">✓ ' . esc_html__('API key for Google Gemini detected', 'mxchat') . '</span>';
6756 + } else {
6757 + echo '<span style="color: #d63638;">⚠ ' . esc_html__('No API key for Google Gemini detected. Please enter API key in API Keys tab.', 'mxchat') . '</span>';
6758 + }
6759 + echo '</p>';
6760 +}
6761 +
6762 +
4249 6763 public function mxchat_top_bar_title_callback() {
4250 6764 // Retrieve the current value of the top bar title from saved options
4251 6765 $top_bar_title = isset($this->options['top_bar_title']) ? esc_attr($this->options['top_bar_title']) : '';
4252 6766
@@ -4255,9 +6769,18 @@
4255 6769
4256 6770 // Add a description
4257 6771 echo '<p class="description">' . esc_html__('Enter the title text that will appear on the top bar of the chatbot.', 'mxchat') . '</p>';
4258 6772 }
6773 +public function mxchat_ai_agent_text_callback() {
6774 + // Retrieve the current value of the AI agent text from saved options
6775 + $ai_agent_text = isset($this->options['ai_agent_text']) ? esc_attr($this->options['ai_agent_text']) : '';
6776 + // Render the input field
6777 + echo '<input type="text" id="ai_agent_text" name="ai_agent_text" value="' . $ai_agent_text . '" />';
6778 + // Add a description
6779 + echo '<p class="description">' . esc_html__('Enter the text that will appear for AI agents in the status indicator. Default: "AI Agent"', 'mxchat') . '</p>';
6780 +}
4259 6781
6782 +
4260 6783 public function enable_email_block_callback() {
4261 6784 // Load full plugin options array
4262 6785 $all_options = get_option('mxchat_options', []);
4263 6786
@@ -4276,10 +6799,8 @@
4276 6799 echo '</label>';
4277 6800 echo '<p class="description">' . esc_html__('Their email will appear at the top of the transcript. Email form will show for users who are not logged in or have not provided an email within 24h.', 'mxchat') . '</p>';
4278 6801 }
4279 6802
4280 -
4281 -
4282 6803 public function email_blocker_header_content_callback() {
4283 6804 // Load the entire 'mxchat_options' array
4284 6805 $all_options = get_option('mxchat_options', []);
4285 6806
@@ -4287,14 +6808,15 @@
4287 6808 $content = isset($all_options['email_blocker_header_content'])
4288 6809 ? $all_options['email_blocker_header_content']
4289 6810 : '';
4290 6811
4291 - // Render the textarea
6812 + // Render the textarea - IMPORTANT: name should be just "email_blocker_header_content"
4292 6813 echo '<textarea
4293 6814 id="email_blocker_header_content"
4294 6815 name="email_blocker_header_content"
4295 6816 rows="5"
4296 6817 cols="70"
6818 + data-setting="email_blocker_header_content"
4297 6819 >' . esc_textarea($content) . '</textarea>';
4298 6820
4299 6821 echo '<p class="description">';
4300 6822 echo esc_html__('You may enter HTML here, such as &lt;h2&gt;Welcome&lt;/h2&gt; or &lt;p&gt;Let\'s get started&lt;/p&gt;.', 'mxchat');
@@ -4300,9 +6822,8 @@
4300 6822 echo esc_html__('You may enter HTML here, such as &lt;h2&gt;Welcome&lt;/h2&gt; or &lt;p&gt;Let\'s get started&lt;/p&gt;.', 'mxchat');
4301 6823 echo '</p>';
4302 6824 }
4303 6825
4304 -
4305 6826 public function email_blocker_button_text_callback() {
4306 6827 // Load the entire 'mxchat_options' array
4307 6828 $all_options = get_option('mxchat_options', []);
4308 6829
@@ -4318,28 +6839,55 @@
4318 6839 echo esc_html__('Enter the text you want on the submit button, e.g. "Start Chat".', 'mxchat');
4319 6840 echo '</p>';
4320 6841 }
4321 6842
6843 +//Enable name field callback
6844 +public function enable_name_field_callback() {
6845 + // Load full plugin options array
6846 + $all_options = get_option('mxchat_options', []);
6847 + // Get the value, default to 'off'
6848 + $enable_name_field = isset($all_options['enable_name_field']) ? $all_options['enable_name_field'] : 'off';
6849 + // Check if it's 'on'
6850 + $checked = ($enable_name_field === 'on') ? 'checked' : '';
6851 + echo '<label class="toggle-switch">';
6852 + echo sprintf(
6853 + '<input type="checkbox" id="enable_name_field" name="enable_name_field" value="on" %s />',
6854 + esc_attr($checked)
6855 + );
6856 + echo '<span class="slider"></span>';
6857 + echo '</label>';
6858 + echo '<p class="description">' . esc_html__('Enable this to also require users to enter their name along with their email before chatting.', 'mxchat') . '</p>';
6859 +}
6860 +//Name field placeholder callback
6861 +public function name_field_placeholder_callback() {
6862 + $all_options = get_option('mxchat_options', []);
6863 + $placeholder = isset($all_options['name_field_placeholder'])
6864 + ? $all_options['name_field_placeholder']
6865 + : esc_html__('Enter your name', 'mxchat');
4322 6866
6867 + echo '<input type="text" id="name_field_placeholder" name="name_field_placeholder" value="' . esc_attr($placeholder) . '" style="width: 300px;" />';
6868 + echo '<p class="description">';
6869 + echo esc_html__('Placeholder text for the name field.', 'mxchat');
6870 + echo '</p>';
6871 +}
4323 6872
6873 +
6874 +
4324 6875 public function mxchat_intro_message_callback() {
4325 6876 // Load the entire 'mxchat_options' array
4326 6877 $all_options = get_option('mxchat_options', []);
4327 -
4328 6878 // Retrieve the saved intro message or use the default
4329 6879 $default_message = __('Hello! How can I assist you today?', 'mxchat');
4330 6880 $saved_message = isset($all_options['intro_message']) ? $all_options['intro_message'] : $default_message;
4331 -
4332 - // Output the textarea with the saved value
6881 + // Output the textarea with the saved value without escaping HTML
4333 6882 ?>
4334 - <textarea id="intro_message" name="intro_message" rows="5" cols="50"><?php echo esc_textarea($saved_message); ?></textarea>
6883 + <textarea id="intro_message" name="intro_message" rows="5" cols="50"><?php echo $saved_message; ?></textarea>
4335 6884 <p class="description">
4336 - <?php esc_html_e('Enter your message. Line breaks will be preserved.', 'mxchat'); ?>
6885 + <?php esc_html_e('Enter your message. HTML tags and line breaks will be preserved.', 'mxchat'); ?>
4337 6886 </p>
4338 6887 <?php
4339 6888 }
4340 6889
4341 -
4342 6890 public function mxchat_input_copy_callback() {
4343 6891 // Load the entire 'mxchat_options' array
4344 6892 $all_options = get_option('mxchat_options', []);
4345 6893
@@ -4788,12 +7336,25 @@
4788 7336 echo '</div>';
4789 7337 }
4790 7338
4791 7339 public function mxchat_append_to_body_callback() {
7340 + // Fetch fresh options to ensure we have the latest saved values
7341 + $options = get_option('mxchat_options', array());
7342 +
4792 7343 // Get value from options array, default to 'off'
4793 - $append_to_body = isset($this->options['append_to_body']) ? $this->options['append_to_body'] : 'off';
7344 + $append_to_body = isset($options['append_to_body']) ? $options['append_to_body'] : 'off';
4794 7345 $checked = ($append_to_body === 'on') ? 'checked' : '';
4795 7346
7347 + // Get post type visibility settings
7348 + $visibility_mode = isset($options['post_type_visibility_mode']) ? $options['post_type_visibility_mode'] : 'all';
7349 + $visibility_list = isset($options['post_type_visibility_list']) ? $options['post_type_visibility_list'] : array();
7350 + if (!is_array($visibility_list)) {
7351 + $visibility_list = array();
7352 + }
7353 +
7354 + echo '<div class="mxchat-autosave-section">';
7355 +
7356 + // Main toggle
4796 7357 echo '<label class="toggle-switch">';
4797 7358 echo sprintf(
4798 7359 '<input type="checkbox" id="append_to_body" name="append_to_body" value="on" %s />',
4799 7360 esc_attr($checked)
@@ -4803,12 +7364,95 @@
4803 7364 echo '<p class="description">' .
4804 7365 esc_html__('Show chatbot automatically on all pages. When disabled, you can place the chatbot manually using shortcode [mxchat_chatbot floating="yes"].', 'mxchat') .
4805 7366 '</p>';
4806 7367
7368 + // Post Type Visibility Options (only visible when auto-display is ON)
7369 + $display_style = ($append_to_body === 'on') ? '' : 'display: none;';
7370 + echo '<div id="post-type-visibility-options" class="mxchat-sub-options" style="' . esc_attr($display_style) . '">';
7371 +
7372 + echo '<div class="mxchat-post-type-visibility-header">';
7373 + echo '<h4>' . esc_html__('Post Type Visibility', 'mxchat') . '</h4>';
7374 + echo '<p class="description">' . esc_html__('Control which post types display the chatbot.', 'mxchat') . '</p>';
7375 + echo '</div>';
7376 +
7377 + // Mode selector (radio buttons)
7378 + echo '<div class="mxchat-visibility-mode">';
7379 +
7380 + echo '<label class="mxchat-radio-label">';
7381 + echo '<input type="radio" name="post_type_visibility_mode" value="all" ' . checked($visibility_mode, 'all', false) . ' />';
7382 + echo '<span>' . esc_html__('Show on all post types', 'mxchat') . '</span>';
7383 + echo '</label>';
7384 +
7385 + echo '<label class="mxchat-radio-label">';
7386 + echo '<input type="radio" name="post_type_visibility_mode" value="include" ' . checked($visibility_mode, 'include', false) . ' />';
7387 + echo '<span>' . esc_html__('Only show on selected post types', 'mxchat') . '</span>';
7388 + echo '</label>';
7389 +
7390 + echo '<label class="mxchat-radio-label">';
7391 + echo '<input type="radio" name="post_type_visibility_mode" value="exclude" ' . checked($visibility_mode, 'exclude', false) . ' />';
7392 + echo '<span>' . esc_html__('Hide on selected post types', 'mxchat') . '</span>';
7393 + echo '</label>';
7394 +
7395 + echo '</div>';
7396 +
7397 + // Post type checkboxes (only visible when mode is include or exclude)
7398 + $list_display = ($visibility_mode !== 'all') ? '' : 'display: none;';
7399 + echo '<div id="post-type-list" class="mxchat-post-type-list" style="' . esc_attr($list_display) . '">';
7400 +
7401 + // Get all public post types
7402 + $post_types = get_post_types(array('public' => true), 'objects');
7403 +
7404 + foreach ($post_types as $post_type) {
7405 + // Skip attachments
7406 + if ($post_type->name === 'attachment') {
7407 + continue;
7408 + }
7409 +
7410 + $is_checked = in_array($post_type->name, $visibility_list) ? 'checked' : '';
7411 +
7412 + echo '<label class="mxchat-checkbox-label">';
7413 + echo '<input type="checkbox" name="post_type_visibility_list[]" value="' . esc_attr($post_type->name) . '" ' . $is_checked . ' />';
7414 + echo '<span>' . esc_html($post_type->label) . '</span>';
7415 + echo '</label>';
7416 + }
7417 +
7418 + echo '</div>'; // End post-type-list
7419 + echo '</div>'; // End post-type-visibility-options
7420 + echo '</div>'; // End mxchat-autosave-section
4807 7421 }
4808 7422
7423 +public function mxchat_frontend_debugger_callback() {
7424 + $show_debugger = isset($this->options['show_frontend_debugger']) ? $this->options['show_frontend_debugger'] : 'on';
7425 + $checked = ($show_debugger === 'on') ? 'checked' : '';
7426 + echo '<label class="toggle-switch">';
7427 + echo sprintf(
7428 + '<input type="checkbox" id="show_frontend_debugger" name="show_frontend_debugger" value="on" %s />',
7429 + esc_attr($checked)
7430 + );
7431 + echo '<span class="slider"></span>';
7432 + echo '</label>';
7433 + echo '<p class="description">' .
7434 + esc_html__('Show the debug panel on the frontend for administrators. Displays similarity scores, document matches, and query analysis in real-time.', 'mxchat') .
7435 + '</p>';
7436 +}
4809 7437
7438 +public function mxchat_contextual_awareness_callback() {
7439 + // Get value from options array, default to 'off'
7440 + $contextual_awareness = isset($this->options['contextual_awareness_toggle']) ? $this->options['contextual_awareness_toggle'] : 'off';
7441 + $checked = ($contextual_awareness === 'on') ? 'checked' : '';
7442 + echo '<label class="toggle-switch">';
7443 + echo sprintf(
7444 + '<input type="checkbox" id="contextual_awareness_toggle" name="contextual_awareness_toggle" value="on" %s />',
7445 + esc_attr($checked)
7446 + );
7447 + echo '<span class="slider"></span>';
7448 + echo '</label>';
7449 + echo '<p class="description">' .
7450 + esc_html__('Enable contextual awareness to allow the chatbot to understand and reference the current page content. When enabled, the chatbot will have access to the page title, content, and URL for more relevant responses.', 'mxchat') .
7451 + '</p>';
7452 +}
4810 7453
7454 +
4811 7455 public function mxchat_privacy_toggle_callback() {
4812 7456 // Load from mxchat_options array
4813 7457 $options = get_option('mxchat_options', []);
4814 7458
@@ -4909,13 +7553,13 @@
4909 7553 // Render the input field
4910 7554 printf(
4911 7555 '<input type="text" id="popular_question_1" name="popular_question_1" value="%s" placeholder="%s" class="regular-text" />',
4912 7556 esc_attr($popular_question_1),
4913 - esc_attr__('Enter Popular Question 1', 'mxchat')
7557 + esc_attr__('Enter Quick Question 1', 'mxchat')
4914 7558 );
4915 7559
4916 7560 // Add a description for the field
4917 - echo '<p class="description">' . esc_html__('This will be the first popular question in the chatbot.', 'mxchat') . '</p>';
7561 + echo '<p class="description">' . esc_html__('This will be the first Quick Question in the chatbot, displayed above the input field.', 'mxchat') . '</p>';
4918 7562 }
4919 7563
4920 7564
4921 7565 public function mxchat_popular_question_2_callback() {
@@ -4928,13 +7572,13 @@
4928 7572 // Render the input field
4929 7573 printf(
4930 7574 '<input type="text" id="popular_question_2" name="popular_question_2" value="%s" placeholder="%s" class="regular-text" />',
4931 7575 esc_attr($popular_question_2),
4932 - esc_attr__('Enter Popular Question 2', 'mxchat')
7576 + esc_attr__('Enter Quick Question 2', 'mxchat')
4933 7577 );
4934 7578
4935 7579 // Add a description for the field
4936 - echo '<p class="description">' . esc_html__('This will be the second popular question in the chatbot.', 'mxchat') . '</p>';
7580 + echo '<p class="description">' . esc_html__('This will be the second Quick Question in the chatbot.', 'mxchat') . '</p>';
4937 7581 }
4938 7582
4939 7583
4940 7584 public function mxchat_popular_question_3_callback() {
@@ -4947,13 +7591,13 @@
4947 7591 // Render the input field
4948 7592 printf(
4949 7593 '<input type="text" id="popular_question_3" name="popular_question_3" value="%s" placeholder="%s" class="regular-text" />',
4950 7594 esc_attr($popular_question_3),
4951 - esc_attr(__('Enter Popular Question 3', 'mxchat'))
7595 + esc_attr(__('Enter Quick Question 3', 'mxchat'))
4952 7596 );
4953 7597
4954 7598 // Add a description for the field
4955 - echo '<p class="description">' . esc_html__('This will be the third popular question in the chatbot.', 'mxchat') . '</p>';
7599 + echo '<p class="description">' . esc_html__('This will be the third Quick Question in the chatbot.', 'mxchat') . '</p>';
4956 7600 }
4957 7601
4958 7602 public function mxchat_additional_popular_questions_callback() {
4959 7603 $options = get_option('mxchat_options', []);
@@ -4974,9 +7618,9 @@
4974 7618 <button type="button" class="button mxchat-remove-question"
4975 7619 aria-label="%s">%s</button>
4976 7620 </div>',
4977 7621 esc_attr($question),
4978 - esc_attr(sprintf(__('Enter Additional Popular Question %d', 'mxchat'), $index + 4)),
7622 + esc_attr(sprintf(__('Enter Additional Quick Question %d', 'mxchat'), $index + 4)),
4979 7623 $index,
4980 7624 esc_attr(__('Remove question', 'mxchat')),
4981 7625 esc_html__('Remove', 'mxchat')
4982 7626 );
@@ -4991,9 +7635,9 @@
4991 7635 data-question-index="0" />
4992 7636 <button type="button" class="button mxchat-remove-question"
4993 7637 aria-label="%s">%s</button>
4994 7638 </div>',
4995 - esc_attr(__('Enter Additional Popular Question 4', 'mxchat')),
7639 + esc_attr(__('Enter Additional Quick Question 4', 'mxchat')),
4996 7640 esc_attr(__('Remove question', 'mxchat')),
4997 7641 esc_html__('Remove', 'mxchat')
4998 7642 );
4999 7643 }
@@ -5002,9 +7646,9 @@
5002 7646 '<button type="button" class="button mxchat-add-question" aria-label="%s">%s</button>',
5003 7647 esc_attr(__('Add question', 'mxchat')),
5004 7648 esc_html__('Add Question', 'mxchat')
5005 7649 );
5006 - echo '<p class="description">' . esc_html__('Add as many popular questions as you need.', 'mxchat') . '</p>';
7650 + echo '<p class="description">' . esc_html__('Add as many Quick Questions as you need.', 'mxchat') . '</p>';
5007 7651 echo '</div>';
5008 7652 }
5009 7653
5010 7654 public function mxchat_brave_api_key_callback() {
@@ -5016,9 +7660,9 @@
5016 7660 $brave_api_key
5017 7661 );
5018 7662 echo '<button type="button" id="toggleBraveApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5019 7663 echo '</div>';
5020 - echo '<p class="description">' . __('Enter your Brave Search API Key here. (See FAQ for details)', 'mxchat') . '</p>';
7664 + echo '<p class="description">' . __('Required for Brave Search integration. Get your API key from Brave Search API.', 'mxchat') . '</p>';
5021 7665 }
5022 7666
5023 7667 public function mxchat_brave_image_count_callback() {
5024 7668 $brave_image_count = isset($this->options['brave_image_count'])
@@ -5103,141 +7747,118 @@
5103 7747 echo '<p>' . esc_html__('Configure the intent settings for the Chat with PDF feature.', 'mxchat') . '</p>';
5104 7748 }
5105 7749
5106 7750 public function mxchat_chat_toolbar_toggle_callback() {
5107 - // Get chat toolbar toggle value with fallback
5108 - $chat_toolbar_toggle = isset($this->options['chat_toolbar_toggle']) ? $this->options['chat_toolbar_toggle'] : 'off';
5109 - $checked = ($chat_toolbar_toggle === 'on') ? 'checked' : '';
7751 + // Get chat toolbar toggle value with fallback
7752 + $chat_toolbar_toggle = isset($this->options['chat_toolbar_toggle']) ? $this->options['chat_toolbar_toggle'] : 'off';
7753 + $checked = ($chat_toolbar_toggle === 'on') ? 'checked' : '';
5110 7754
5111 - // Check if the plugin is activated (paid feature)
5112 - $disabled = $this->is_activated ? '' : 'disabled';
5113 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
7755 + // Output the toggle switch
7756 + echo '<label class="toggle-switch">';
7757 + echo sprintf(
7758 + '<input type="checkbox" id="chat_toolbar_toggle" name="chat_toolbar_toggle" value="on" %s />',
7759 + esc_attr($checked)
7760 + );
7761 + echo '<span class="slider"></span>';
7762 + echo '</label>';
5114 7763
5115 - echo '<div class="' . esc_attr($class) . '">';
7764 + echo '<p class="description">' . esc_html__('Enable to display the chat toolbar, adding two icons below the chatbot input field for uploading PDF and Word documents (default is hidden).', 'mxchat') . '</p>';
7765 +}
5116 7766
5117 - // Output the toggle switch
5118 - echo '<label class="toggle-switch">';
5119 - echo sprintf(
5120 - '<input type="checkbox" id="chat_toolbar_toggle" name="chat_toolbar_toggle" value="on" %s %s />',
5121 - esc_attr($checked),
5122 - esc_attr($disabled)
5123 - );
5124 - echo '<span class="slider"></span>';
5125 - echo '</label>';
7767 +/**
7768 + * Callback for PDF upload button toggle setting
7769 + */
7770 +public function mxchat_show_pdf_upload_button_callback() {
7771 + // Get toggle value with fallback
7772 + $show_pdf_button = isset($this->options['show_pdf_upload_button']) ? $this->options['show_pdf_upload_button'] : 'on';
7773 + $checked = ($show_pdf_button === 'on') ? 'checked' : '';
5126 7774
5127 - // Pro feature overlay
5128 - if (!$this->is_activated) {
5129 - echo '<div class="pro-feature-overlay">';
5130 - echo '<a href="https://mxchat.ai/" target="_blank">';
5131 - echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
5132 - echo '</a>';
5133 - echo '</div>';
5134 - }
7775 + // Output the toggle switch
7776 + echo '<label class="toggle-switch">';
7777 + echo sprintf(
7778 + '<input type="checkbox" id="show_pdf_upload_button" name="show_pdf_upload_button" value="on" %s />',
7779 + esc_attr($checked)
7780 + );
7781 + echo '<span class="slider"></span>';
7782 + echo '</label>';
5135 7783
5136 - echo '</div>';
5137 - 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>';
7784 + echo '<p class="description">' . esc_html__('Enable to show the PDF upload button in the chatbot toolbar. Disable to hide it.', 'mxchat') . '</p>';
5138 7785 }
5139 7786
7787 +/**
7788 + * Callback for Word upload button toggle setting
7789 + */
7790 +public function mxchat_show_word_upload_button_callback() {
7791 + // Get toggle value with fallback
7792 + $show_word_button = isset($this->options['show_word_upload_button']) ? $this->options['show_word_upload_button'] : 'on';
7793 + $checked = ($show_word_button === 'on') ? 'checked' : '';
5140 7794
7795 + // Output the toggle switch
7796 + echo '<label class="toggle-switch">';
7797 + echo sprintf(
7798 + '<input type="checkbox" id="show_word_upload_button" name="show_word_upload_button" value="on" %s />',
7799 + esc_attr($checked)
7800 + );
7801 + echo '<span class="slider"></span>';
7802 + echo '</label>';
7803 +
7804 + echo '<p class="description">' . esc_html__('Enable to show the Word document upload button in the chatbot toolbar. Disable to hide it.', 'mxchat') . '</p>';
7805 +}
7806 +
5141 7807 public function mxchat_pdf_intent_trigger_text_callback() {
5142 - $disabled = $this->is_activated ? '' : 'disabled';
5143 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
5144 7808 $default_text = __("Please provide the URL to the PDF you'd like to discuss.", 'mxchat');
5145 7809
5146 - echo '<div class="' . esc_attr($class) . '">';
5147 7810 echo sprintf(
5148 7811 '<textarea id="pdf_intent_trigger_text"
5149 7812 name="pdf_intent_trigger_text"
5150 7813 rows="3"
5151 7814 cols="50"
5152 - placeholder="%s"
5153 - %s>%s</textarea>',
7815 + placeholder="%s">%s</textarea>',
5154 7816 esc_attr__('Enter trigger text', 'mxchat'),
5155 - esc_attr($disabled),
5156 7817 isset($this->options['pdf_intent_trigger_text'])
5157 7818 ? esc_textarea($this->options['pdf_intent_trigger_text'])
5158 7819 : esc_textarea($default_text)
5159 7820 );
5160 7821 echo '<p class="description">' . esc_html__('Text displayed when the intent is triggered.', 'mxchat') . '</p>';
5161 -
5162 - if (!$this->is_activated) {
5163 - echo '<div class="pro-feature-overlay">';
5164 - echo '<a href="https://mxchat.ai/" target="_blank">';
5165 - echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
5166 - echo '</a>';
5167 - echo '</div>';
5168 - }
5169 - echo '</div>';
5170 7822 }
5171 7823
5172 7824 public function mxchat_pdf_intent_success_text_callback() {
5173 - $disabled = $this->is_activated ? '' : 'disabled';
5174 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
5175 7825 $default_text = __("I've processed the PDF. What questions do you have about it?", 'mxchat');
5176 7826
5177 - echo '<div class="' . esc_attr($class) . '">';
5178 7827 echo sprintf(
5179 7828 '<textarea id="pdf_intent_success_text"
5180 7829 name="pdf_intent_success_text"
5181 7830 rows="3"
5182 7831 cols="50"
5183 - placeholder="%s"
5184 - %s>%s</textarea>',
7832 + placeholder="%s">%s</textarea>',
5185 7833 esc_attr__('Enter success text', 'mxchat'),
5186 - esc_attr($disabled),
5187 7834 isset($this->options['pdf_intent_success_text'])
5188 7835 ? esc_textarea($this->options['pdf_intent_success_text'])
5189 7836 : esc_textarea($default_text)
5190 7837 );
5191 7838 echo '<p class="description">' . esc_html__('Text displayed when the intent is successful.', 'mxchat') . '</p>';
5192 -
5193 - if (!$this->is_activated) {
5194 - echo '<div class="pro-feature-overlay">';
5195 - echo '<a href="https://mxchat.ai/" target="_blank">';
5196 - echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
5197 - echo '</a>';
5198 - echo '</div>';
5199 - }
5200 - echo '</div>';
5201 7839 }
5202 7840
5203 7841 public function mxchat_pdf_intent_error_text_callback() {
5204 - $disabled = $this->is_activated ? '' : 'disabled';
5205 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
5206 7842 $default_text = __("Sorry, I couldn't process the PDF. Please ensure it's a valid file.", 'mxchat');
5207 7843
5208 - echo '<div class="' . esc_attr($class) . '">';
5209 7844 echo sprintf(
5210 7845 '<textarea id="pdf_intent_error_text"
5211 7846 name="pdf_intent_error_text"
5212 7847 rows="3"
5213 7848 cols="50"
5214 - placeholder="%s"
5215 - %s>%s</textarea>',
7849 + placeholder="%s">%s</textarea>',
5216 7850 esc_attr__('Enter error text', 'mxchat'),
5217 - esc_attr($disabled),
5218 7851 isset($this->options['pdf_intent_error_text'])
5219 7852 ? esc_textarea($this->options['pdf_intent_error_text'])
5220 7853 : esc_textarea($default_text)
5221 7854 );
5222 7855 echo '<p class="description">' . esc_html__('Text displayed when an error occurs during the intent.', 'mxchat') . '</p>';
5223 -
5224 - if (!$this->is_activated) {
5225 - echo '<div class="pro-feature-overlay">';
5226 - echo '<a href="https://mxchat.ai/" target="_blank">';
5227 - echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
5228 - echo '</a>';
5229 - echo '</div>';
5230 - }
5231 - echo '</div>';
5232 7856 }
5233 7857
5234 7858 public function mxchat_pdf_max_pages_callback() {
5235 - $disabled = $this->is_activated ? '' : 'disabled';
5236 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
5237 7859 $max_pages = isset($this->options['pdf_max_pages']) ? intval($this->options['pdf_max_pages']) : 69;
5238 7860
5239 - echo '<div class="' . esc_attr($class) . '">';
5240 7861 echo sprintf(
5241 7862 '<input type="range"
5242 7863 id="pdf_max_pages"
5243 7864 name="pdf_max_pages"
@@ -5243,37 +7864,24 @@
5243 7864 name="pdf_max_pages"
5244 7865 min="1"
5245 7866 max="69"
5246 7867 value="%d"
5247 - %s
5248 7868 class="range-slider" />',
5249 - esc_attr($max_pages),
5250 - esc_attr($disabled)
7869 + esc_attr($max_pages)
5251 7870 );
5252 7871 echo '<span id="pdf_max_pages_output">' . esc_html($max_pages) . '</span>';
5253 7872 echo '<p class="description">' . esc_html__('Set the maximum number of document pages users can upload for processing. (1-69 pages)', 'mxchat') . '</p>';
5254 -
5255 - if (!$this->is_activated) {
5256 - echo '<div class="pro-feature-overlay">';
5257 - echo '<a href="https://mxchat.ai/" target="_blank">';
5258 - echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
5259 - echo '</a>';
5260 - echo '</div>';
5261 - }
5262 - echo '</div>';
5263 7873 }
5264 7874
5265 7875 public function mxchat_live_agent_status_callback() {
5266 - $disabled = $this->is_activated ? '' : 'disabled';
5267 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
5268 - $status = isset($this->options['live_agent_status']) ? $this->options['live_agent_status'] : 'off';
7876 + // Always get fresh options instead of using cached $this->options
7877 + $fresh_options = get_option('mxchat_options');
7878 + $status = isset($fresh_options['live_agent_status']) ? $fresh_options['live_agent_status'] : 'off';
5269 7879
5270 - echo '<div class="' . esc_attr($class) . '">';
5271 7880 echo '<label class="toggle-switch">';
5272 7881 echo sprintf(
5273 - '<input type="checkbox" id="live_agent_status" name="live_agent_status" value="on" %s %s />',
5274 - checked($status, 'on', false),
5275 - esc_attr($disabled)
7882 + '<input type="checkbox" id="live_agent_status" name="live_agent_status" value="on" %s />',
7883 + checked($status, 'on', false)
5276 7884 );
5277 7885 echo '<span class="slider"></span>';
5278 7886 echo '</label>';
5279 7887 echo '<label for="live_agent_status" class="mxchat-status-label">';
@@ -5278,98 +7886,83 @@
5278 7886 echo '</label>';
5279 7887 echo '<label for="live_agent_status" class="mxchat-status-label">';
5280 7888 echo '<span class="status-text">' . ($status === 'on' ? esc_html__('Online', 'mxchat') : esc_html__('Offline', 'mxchat')) . '</span>';
5281 7889 echo '</label>';
5282 -
5283 - if (!$this->is_activated) {
5284 - echo '<div class="pro-feature-overlay">';
5285 - echo '<a href="https://mxchat.ai/" target="_blank"><img src="' . plugin_dir_url(__FILE__) . '../images/pro-only-dark.png" alt="' . esc_attr__('Pro Only', 'mxchat') . '" /></a>';
5286 - echo '</div>';
5287 - }
5288 - echo '</div>';
5289 7890 }
5290 7891
5291 -
5292 -// Away Message Callback
5293 7892 public function mxchat_live_agent_away_message_callback() {
5294 - $disabled = $this->is_activated ? '' : 'disabled';
5295 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
5296 7893 $message = isset($this->options['live_agent_away_message'])
5297 7894 ? $this->options['live_agent_away_message']
5298 7895 : __('Sorry, live agents are currently unavailable. I can continue helping you as an AI assistant.', 'mxchat');
5299 7896
5300 - echo '<div class="' . esc_attr($class) . '">';
5301 7897 printf(
5302 - '<textarea id="live_agent_away_message" name="live_agent_away_message" rows="3" cols="50" %s>%s</textarea>',
5303 - esc_attr($disabled),
7898 + '<textarea id="live_agent_away_message" name="live_agent_away_message" rows="3" cols="50">%s</textarea>',
5304 7899 esc_textarea($message)
5305 7900 );
5306 7901 echo '<p class="description">' . esc_html__('Message shown when live agents are offline.', 'mxchat') . '</p>';
5307 -
5308 - if (!$this->is_activated) {
5309 - echo '<div class="pro-feature-overlay">';
5310 - echo '<a href="https://mxchat.ai/" target="_blank"><img src="' . plugin_dir_url(__FILE__) . '../images/pro-only-dark.png" alt="' . esc_attr__('Pro Only', 'mxchat') . '" /></a>';
5311 - echo '</div>';
5312 - }
5313 - echo '</div>';
5314 7902 }
5315 7903
5316 7904 public function mxchat_live_agent_notification_message_callback() {
5317 - $disabled = $this->is_activated ? '' : 'disabled';
5318 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
5319 7905 $message = isset($this->options['live_agent_notification_message'])
5320 7906 ? $this->options['live_agent_notification_message']
5321 7907 : __('Live agent has been notified.', 'mxchat');
5322 7908
5323 - echo '<div class="' . esc_attr($class) . '">';
5324 7909 printf(
5325 - '<textarea id="live_agent_notification_message" name="live_agent_notification_message" rows="3" cols="50" %s>%s</textarea>',
5326 - esc_attr($disabled),
7910 + '<textarea id="live_agent_notification_message" name="live_agent_notification_message" rows="3" cols="50">%s</textarea>',
5327 7911 esc_textarea($message)
5328 7912 );
5329 7913 echo '<p class="description">' . esc_html__('Message shown when live transfer activated.', 'mxchat') . '</p>';
5330 -
5331 - if (!$this->is_activated) {
5332 - echo '<div class="pro-feature-overlay">';
5333 - echo '<a href="https://mxchat.ai/" target="_blank"><img src="' . plugin_dir_url(__FILE__) . '../images/pro-only-dark.png" alt="' . esc_attr__('Pro Only', 'mxchat') . '" /></a>';
5334 - echo '</div>';
5335 - }
5336 - echo '</div>';
5337 7914 }
5338 7915
5339 7916 public function mxchat_live_agent_webhook_url_callback() {
5340 - $disabled = $this->is_activated ? '' : 'disabled';
5341 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
5342 7917 $webhook_url = isset($this->options['live_agent_webhook_url'])
5343 7918 ? esc_url($this->options['live_agent_webhook_url'])
5344 7919 : esc_url(get_option('live_agent_webhook_url', ''));
5345 7920
5346 - echo '<div class="' . esc_attr($class) . '">';
5347 7921 printf(
5348 - '<input type="password" id="live_agent_webhook_url" name="live_agent_webhook_url" value="%s" class="regular-text" %s />',
5349 - $webhook_url,
5350 - esc_attr($disabled)
7922 + '<input type="password" id="live_agent_webhook_url" name="live_agent_webhook_url" value="%s" class="regular-text" />',
7923 + $webhook_url
5351 7924 );
5352 7925 echo '<button type="button" id="toggleWebhookUrlVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5353 7926 echo '<p class="description">' . esc_html__('Enter your Slack webhook URL for live agent notifications.', 'mxchat') . '</p>';
7927 +}
5354 7928
5355 - if (!$this->is_activated) {
5356 - echo '<div class="pro-feature-overlay">';
5357 - echo '<a href="https://mxchat.ai/" target="_blank"><img src="' . plugin_dir_url(__FILE__) . '../images/pro-only-dark.png" alt="' . esc_attr__('Pro Only', 'mxchat') . '" /></a>';
5358 - echo '</div>';
5359 - }
5360 - echo '</div>';
7929 +public function mxchat_live_agent_secret_key_callback() {
7930 + printf(
7931 + '<input type="password" id="live_agent_secret_key" name="live_agent_secret_key" value="%s" class="regular-text" />',
7932 + isset($this->options['live_agent_secret_key']) ? esc_attr($this->options['live_agent_secret_key']) : ''
7933 + );
7934 + echo '<button type="button" id="toggleSecretKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
7935 + echo '<p class="description">' . esc_html__('Secret key for validating Slack requests. Keep this secure.', 'mxchat') . '</p>';
5361 7936 }
5362 7937
7938 +public function mxchat_live_agent_bot_token_callback() {
7939 + printf(
7940 + '<input type="password" id="live_agent_bot_token" name="live_agent_bot_token" value="%s" class="regular-text" />',
7941 + isset($this->options['live_agent_bot_token']) ? esc_attr($this->options['live_agent_bot_token']) : ''
7942 + );
7943 + echo '<button type="button" id="toggleBotTokenVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
7944 + echo '<p class="description">' . esc_html__('Your Slack Bot OAuth Token (starts with xoxb-). Keep this secure.', 'mxchat') . '</p>';
7945 +}
5363 7946
7947 +public function mxchat_live_agent_user_ids_callback() {
7948 + $user_ids = isset($this->options['live_agent_user_ids'])
7949 + ? esc_textarea($this->options['live_agent_user_ids'])
7950 + : '';
7951 +
7952 + printf(
7953 + '<textarea id="live_agent_user_ids" name="live_agent_user_ids" rows="4" class="large-text">%s</textarea>',
7954 + $user_ids
7955 + );
7956 + echo '<p class="description">' . esc_html__('Enter Slack User IDs of agents who should be automatically invited to chat channels (one per line). Find user IDs in Slack profiles under "More" → "Copy member ID". Example: U1234567890', 'mxchat') . '</p>';
7957 +}
7958 +
5364 7959 public function mxchat_similarity_threshold_callback() {
5365 7960 // Load from mxchat_options array
5366 7961 $options = get_option('mxchat_options', []);
5367 7962
5368 - // Get value with backwards compatibility
5369 - $threshold = isset($options['similarity_threshold'])
5370 - ? $options['similarity_threshold']
5371 - : get_option('mxchat_similarity_threshold', 80);
7963 + // Get value from options array with default of 80
7964 + $threshold = isset($options['similarity_threshold']) ? $options['similarity_threshold'] : 35;
5372 7965
5373 7966 echo '<div class="slider-container">';
5374 7967 echo sprintf(
5375 7968 '<input type="range"
@@ -5374,9 +7967,9 @@
5374 7967 echo sprintf(
5375 7968 '<input type="range"
5376 7969 id="similarity_threshold"
5377 7970 name="similarity_threshold"
5378 - min="70"
7971 + min="20"
5379 7972 max="85"
5380 7973 step="1"
5381 7974 value="%s"
5382 7975 class="range-slider" />',
@@ -5386,186 +7979,199 @@
5386 7979 '<span id="threshold_value" class="range-value">%s</span>',
5387 7980 esc_html($threshold)
5388 7981 );
5389 7982 echo '</div>';
5390 -
5391 7983 echo '<p class="description">';
5392 - echo esc_html__('Set the similarity threshold (recommended: 75) to balance accuracy; too high might limit your bot\'s ability to find relevant knowledge.', 'mxchat');
7984 + echo esc_html__('Adjust similarity threshold for optimal content matching. Too high may limit knowledge retrieval. We highly recommend using the MxChat Debugger found on the left side of your website when logged in as admin while testing the bot.', 'mxchat');
5393 7985 echo '</p>';
5394 7986 }
5395 7987
7988 +public function mxchat_enqueue_admin_assets() {
7989 + // Get plugin version
7990 + $version = MXCHAT_VERSION;
5396 7991
7992 + // Use file modification time for development (remove in production)
7993 + if (defined('WP_DEBUG') && WP_DEBUG) {
7994 + $version = filemtime(plugin_dir_path(__FILE__) . '../mxchat-basic.php');
7995 + }
5397 7996
5398 -public function mxchat_live_agent_secret_key_callback() {
5399 - $disabled = $this->is_activated ? '' : 'disabled';
5400 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
7997 + $current_page = isset($_GET['page']) ? $_GET['page'] : '';
7998 + $plugin_url = plugin_dir_url(__FILE__) . '../';
5401 7999
5402 - echo '<div class="' . esc_attr($class) . '">';
5403 - printf(
5404 - '<input type="password" id="live_agent_secret_key" name="live_agent_secret_key" value="%s" class="regular-text" %s />',
5405 - isset($this->options['live_agent_secret_key']) ? esc_attr($this->options['live_agent_secret_key']) : '',
5406 - esc_attr($disabled)
5407 - );
5408 - echo '<button type="button" id="toggleSecretKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5409 - echo '<p class="description">' . esc_html__('Secret key for validating Slack requests. Keep this secure.', 'mxchat') . '</p>';
8000 + // Only load on MxChat pages
8001 + if (strpos($current_page, 'mxchat') === false) {
8002 + return;
8003 + }
5410 8004
5411 - if (!$this->is_activated) {
5412 - echo '<div class="pro-feature-overlay">';
5413 - echo '<a href="https://mxchat.ai/" target="_blank"><img src="' . plugin_dir_url(__FILE__) . '../images/pro-only-dark.png" alt="' . esc_attr__('Pro Only', 'mxchat') . '" /></a>';
5414 - echo '</div>';
5415 - }
5416 - echo '</div>';
8005 + // Always load these on all MxChat pages
8006 + $this->enqueue_core_admin_assets($plugin_url, $version);
8007 + $this->enqueue_page_specific_assets($current_page, $plugin_url, $version);
8008 + $this->localize_admin_scripts($current_page);
5417 8009 }
8010 +private function enqueue_core_admin_assets($plugin_url, $version) {
8011 + // Core admin styles
8012 + wp_enqueue_style('mxchat-admin-css', $plugin_url . 'css/admin-style.css', array(), $version);
8013 + wp_enqueue_style('mxchat-knowledge-css', $plugin_url . 'css/knowledge-style.css', array(), $version);
5418 8014
5419 -public function mxchat_live_agent_bot_token_callback() {
5420 - $disabled = $this->is_activated ? '' : 'disabled';
5421 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
5422 -
5423 - echo '<div class="' . esc_attr($class) . '">';
5424 - printf(
5425 - '<input type="password" id="live_agent_bot_token" name="live_agent_bot_token" value="%s" class="regular-text" %s />',
5426 - isset($this->options['live_agent_bot_token']) ? esc_attr($this->options['live_agent_bot_token']) : '',
5427 - esc_attr($disabled)
5428 - );
5429 - echo '<button type="button" id="toggleBotTokenVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5430 - echo '<p class="description">' . esc_html__('Your Slack Bot OAuth Token (starts with xoxb-). Keep this secure.', 'mxchat') . '</p>';
5431 -
5432 - if (!$this->is_activated) {
5433 - echo '<div class="pro-feature-overlay">';
5434 - echo '<a href="https://mxchat.ai/" target="_blank"><img src="' . plugin_dir_url(__FILE__) . '../images/pro-only-dark.png" alt="' . esc_attr__('Pro Only', 'mxchat') . '" /></a>';
5435 - echo '</div>';
5436 - }
5437 - echo '</div>';
8015 + // Core admin scripts
8016 + wp_enqueue_script('mxchat-admin-js', $plugin_url . 'js/mxchat-admin.js', array('jquery'), $version, true);
5438 8017 }
8018 +private function enqueue_page_specific_assets($current_page, $plugin_url, $version) {
8019 + switch ($current_page) {
8020 + case 'mxchat-prompts':
8021 + // Knowledge processing page assets
8022 + wp_enqueue_style('mxchat-content-selector-css', $plugin_url . 'css/content-selector.css', array(), $version);
8023 + wp_enqueue_script('mxchat-content-selector-js', $plugin_url . 'js/content-selector.js', array('jquery'), $version, true);
8024 + // Add the knowledge processing script
8025 + wp_enqueue_script('mxchat-knowledge-processing', $plugin_url . 'js/knowledge-processing.js', array('jquery'), $version, true);
8026 + break;
5439 8027
5440 -public function mxchat_enqueue_admin_assets() {
5441 - wp_enqueue_style('wp-color-picker');
8028 + case 'mxchat-transcripts':
8029 + wp_enqueue_style('mxchat-chat-transcripts-css', $plugin_url . 'css/chat-transcripts.css', array(), $version);
8030 + wp_enqueue_script('mxchat-transcripts-js', $plugin_url . 'js/mxchat_transcripts.js', array('jquery'), $version, true);
8031 + break;
5442 8032
5443 - // Get the plugin version or file modification time for cache busting
5444 - $plugin_version = '2.0.3'; // Replace this with your plugin's version
8033 + case 'mxchat-actions':
8034 + wp_enqueue_style('mxchat-intent-css', $plugin_url . 'css/intent-style.css', array(), $version);
8035 + break;
5445 8036
5446 - // File paths
5447 - $color_picker_js_path = plugin_dir_path(__FILE__) . '../js/my-color-picker.js';
5448 - $embedding_check_js_path = plugin_dir_path(__FILE__) . '../js/embedding-check.js';
5449 - $admin_css_path = plugin_dir_path(__FILE__) . '../css/admin-style.css';
5450 - $knowledge_css_path = plugin_dir_path(__FILE__) . '../css/knowledge-style.css';
5451 - $intent_css_path = plugin_dir_path(__FILE__) . '../css/intent-style.css';
5452 - $transcripts_js_path = plugin_dir_path(__FILE__) . '../js/mxchat_transcripts.js';
8037 + case 'mxchat-activation':
8038 + wp_enqueue_script('mxchat-activation-js', $plugin_url . 'js/activation-script.js', array('jquery'), $version, true);
8039 + break;
8040 + default:
8041 + wp_enqueue_script(
8042 + 'mxchat-test-streaming-js',
8043 + $plugin_url . 'js/mxchat-test-streaming.js',
8044 + ['jquery'],
8045 + $version,
8046 + true
8047 + );
5453 8048
5454 - // Check if files exist and get modification times
5455 - $color_picker_version = file_exists($color_picker_js_path) ? filemtime($color_picker_js_path) : $plugin_version;
5456 - $embedding_check_version = file_exists($embedding_check_js_path) ? filemtime($embedding_check_js_path) : $plugin_version;
5457 - $admin_css_version = file_exists($admin_css_path) ? filemtime($admin_css_path) : $plugin_version;
5458 - $knowledge_css_version = file_exists($knowledge_css_path) ? filemtime($knowledge_css_path) : $plugin_version;
5459 - $intent_css_version = file_exists($intent_css_path) ? filemtime($intent_css_path) : $plugin_version;
5460 - $transcripts_js_version = file_exists($transcripts_js_path) ? filemtime($transcripts_js_path) : $plugin_version;
5461 -
5462 - // Enqueue scripts and styles with corrected paths
5463 - wp_enqueue_script(
5464 - 'mxchat-color-picker',
5465 - plugin_dir_url(__FILE__) . '../js/my-color-picker.js',
5466 - array('wp-color-picker'),
5467 - $color_picker_version,
5468 - true
5469 - );
5470 -
5471 - wp_enqueue_script(
5472 - 'mxchat-embedding-check',
5473 - plugin_dir_url(__FILE__) . '../js/embedding-check.js',
5474 - array(),
5475 - $embedding_check_version,
5476 - true
5477 - );
5478 -
5479 - wp_enqueue_script(
5480 - 'mxchat-transcripts-js',
5481 - plugin_dir_url(__FILE__) . '../js/mxchat_transcripts.js',
5482 - array('jquery'),
5483 - $transcripts_js_version,
5484 - true
5485 - );
5486 -
5487 - wp_enqueue_script(
5488 - 'mxchat-admin-js',
5489 - plugin_dir_url(__FILE__) . '../js/mxchat-admin.js',
5490 - array('jquery'),
5491 - $plugin_version,
5492 - true
5493 - );
5494 -
5495 - wp_localize_script('mxchat-admin-js', 'mxchatAdmin', array(
8049 + wp_localize_script('mxchat-test-streaming-js', 'mxchatTestStreamingAjax', [
8050 + 'ajax_url' => admin_url('admin-ajax.php'),
8051 + 'nonce' => wp_create_nonce('mxchat_test_streaming_nonce'),
8052 + 'settings_nonce' => wp_create_nonce('mxchat_save_setting_nonce')
8053 + ]);
8054 + break;
8055 + }
8056 +}
8057 +private function localize_admin_scripts($current_page) {
8058 + // Base localization data for main admin script
8059 + $base_data = array(
5496 8060 'ajax_url' => admin_url('admin-ajax.php'),
8061 + 'nonce' => wp_create_nonce('mxchat_status_nonce'),
8062 + 'admin_url' => admin_url(),
5497 8063 'license_nonce' => wp_create_nonce('mxchat_activate_license_nonce'),
5498 8064 'inline_edit_nonce' => wp_create_nonce('mxchat_save_inline_nonce'),
5499 8065 'setting_nonce' => wp_create_nonce('mxchat_save_setting_nonce'),
5500 - ));
8066 + 'export_nonce' => wp_create_nonce('mxchat_export_transcripts'),
8067 + 'actions_nonce' => wp_create_nonce('mxchat_actions_nonce'),
8068 + 'add_intent_nonce' => wp_create_nonce('mxchat_add_intent_nonce'),
8069 + 'edit_intent_nonce' => wp_create_nonce('mxchat_edit_intent'),
8070 + 'toggle_action_nonce' => wp_create_nonce('mxchat_actions_nonce'),
8071 + 'fetch_openrouter_models_nonce' => wp_create_nonce('mxchat_fetch_openrouter_models'), // ADD THIS LINE
8072 + 'is_activated' => $this->is_activated ? '1' : '0',
8073 + 'status_refresh_interval' => 5000,
8074 + 'prompts_setting_nonce' => wp_create_nonce('mxchat_prompts_setting_nonce'),
8075 + 'ajaxurl' => admin_url('admin-ajax.php')
8076 + );
5501 8077
5502 - // Enqueue the admin CSS
5503 - wp_enqueue_style(
5504 - 'mxchat-admin-css',
5505 - plugin_dir_url(__FILE__) . '../css/admin-style.css',
5506 - array(),
5507 - $admin_css_version
5508 - );
8078 + // Localize main admin script with base data
8079 + wp_localize_script('mxchat-admin-js', 'mxchatAdmin', $base_data);
5509 8080
5510 - wp_enqueue_style(
5511 - 'mxchat-knowledge-css',
5512 - plugin_dir_url(__FILE__) . '../css/knowledge-style.css',
5513 - array(),
5514 - $knowledge_css_version
5515 - );
8081 + // Page-specific localizations
8082 + $this->localize_page_specific_scripts($current_page);
8083 +}
8084 +private function localize_page_specific_scripts($current_page) {
8085 + switch ($current_page) {
8086 + case 'mxchat-prompts':
8087 + // Status updater localization
8088 + wp_localize_script('mxchat-status-updater', 'mxchat_status_data', array(
8089 + 'ajax_url' => admin_url('admin-ajax.php'),
8090 + 'nonce' => wp_create_nonce('mxchat_status_nonce')
8091 + ));
5516 8092
8093 + // Content selector localization
8094 + wp_localize_script('mxchat-content-selector-js', 'mxchatSelector', array(
8095 + 'ajaxurl' => admin_url('admin-ajax.php'),
8096 + 'nonce' => wp_create_nonce('mxchat_content_selector_nonce'),
8097 + 'i18n' => array(
8098 + 'searchPlaceholder' => __('Search posts and pages...', 'mxchat'),
8099 + 'selectAll' => __('Select All', 'mxchat'),
8100 + 'process' => __('Process Selected', 'mxchat'),
8101 + 'cancel' => __('Cancel', 'mxchat'),
8102 + 'noResults' => __('No content found.', 'mxchat')
8103 + )
8104 + ));
5517 8105
5518 - wp_enqueue_style(
5519 - 'mxchat-intent-css',
5520 - plugin_dir_url(__FILE__) . '../css/intent-style.css',
5521 - array(),
5522 - $intent_css_version
5523 - );
8106 + wp_localize_script('mxchat-knowledge-processing', 'mxchatAdmin', array(
8107 + 'ajax_url' => admin_url('admin-ajax.php'),
8108 + 'status_nonce' => wp_create_nonce('mxchat_status_nonce'),
8109 + 'queue_nonce' => wp_create_nonce('mxchat_queue_nonce'),
8110 + 'stop_nonce' => wp_create_nonce('mxchat_stop_processing_action'),
8111 + 'settings_nonce' => wp_create_nonce('mxchat_prompts_setting_nonce'),
8112 + 'setting_nonce' => wp_create_nonce('mxchat_save_setting_nonce'),
8113 + 'admin_url' => admin_url(),
8114 + 'ajaxurl' => admin_url('admin-ajax.php'),
8115 + 'status_refresh_interval' => 2000
8116 + ));
8117 + break;
5524 8118
5525 - // IMPORTANT: Use the same script handle as above for localizing mxchatPromptsAdmin
5526 - wp_localize_script( 'mxchat-admin-js', 'mxchatPromptsAdmin', array(
5527 - 'ajax_url' => admin_url( 'admin-ajax.php' ),
5528 - 'prompts_setting_nonce' => wp_create_nonce( 'mxchat_prompts_setting_nonce' ),
5529 - ) );
8119 + case 'mxchat-activation':
8120 + wp_localize_script('mxchat-activation-js', 'mxchatAdmin', array(
8121 + 'ajax_url' => admin_url('admin-ajax.php'),
8122 + 'license_nonce' => wp_create_nonce('mxchat_activate_license_nonce')
8123 + ));
8124 + break;
5530 8125
5531 - // Localize the script for color picker and settings
5532 - wp_localize_script('mxchat-color-picker', 'mxchatStyleSettings', array(
8126 + case 'mxchat-settings':
8127 + default:
8128 + // Color picker and settings localization
8129 + wp_localize_script('mxchat-color-picker', 'mxchatStyleSettings', array(
8130 + 'ajax_url' => admin_url('admin-ajax.php'),
8131 + 'link_target_toggle' => $this->options['link_target_toggle'] ?? 'off',
8132 + 'user_message_bg_color' => $this->options['user_message_bg_color'] ?? '#fff',
8133 + 'user_message_font_color' => $this->options['user_message_font_color'] ?? '#212121',
8134 + 'bot_message_bg_color' => $this->options['bot_message_bg_color'] ?? '#212121',
8135 + 'bot_message_font_color' => $this->options['bot_message_font_color'] ?? '#fff',
8136 + 'top_bar_bg_color' => $this->options['top_bar_bg_color'] ?? '#212121',
8137 + 'send_button_font_color' => $this->options['send_button_font_color'] ?? '#212121',
8138 + 'close_button_color' => $this->options['close_button_color'] ?? '#fff',
8139 + 'chatbot_background_color' => $this->options['chatbot_background_color'] ?? '#212121',
8140 + 'icon_color' => $this->options['icon_color'] ?? '#fff',
8141 + 'chat_input_font_color' => $this->options['chat_input_font_color'] ?? '#212121',
8142 + 'pre_chat_message' => $this->options['pre_chat_message'] ?? esc_html__('Hey there! Ask me anything!', 'mxchat'),
8143 + 'rate_limit_message' => $this->options['rate_limit_message'] ?? esc_html__('Rate limit exceeded. Please try again later.', 'mxchat'),
8144 + 'loops_api_key' => $this->options['loops_api_key'] ?? '',
8145 + 'loops_mailing_list' => $this->options['loops_mailing_list'] ?? '',
8146 + 'triggered_phrase_response' => $this->options['triggered_phrase_response'] ?? esc_html__('Would you like to join our mailing list? Please provide your email below.', 'mxchat'),
8147 + 'email_capture_response' => $this->options['email_capture_response'] ?? esc_html__('Thank you for providing your email! You\'ve been added to our list.', 'mxchat'),
8148 + 'pdf_intent_trigger_text' => $this->options['pdf_intent_trigger_text'] ?? esc_html__("Please provide the URL to the PDF you'd like to discuss.", 'mxchat'),
8149 + 'pdf_intent_success_text' => $this->options['pdf_intent_success_text'] ?? esc_html__("I've processed the PDF. What questions do you have about it?", 'mxchat'),
8150 + 'pdf_intent_error_text' => $this->options['pdf_intent_error_text'] ?? esc_html__("Sorry, I couldn't process the PDF. Please ensure it's a valid file.", 'mxchat'),
8151 + 'pdf_max_pages' => $this->options['pdf_max_pages'] ?? 69,
8152 + 'live_agent_webhook_url' => $this->options['live_agent_webhook_url'] ?? '',
8153 + 'live_agent_secret_key' => $this->options['live_agent_secret_key'] ?? '',
8154 + 'live_agent_bot_token' => $this->options['live_agent_bot_token'] ?? '',
8155 + 'live_agent_message_bg_color' => $this->options['live_agent_message_bg_color'] ?? '#ffffff',
8156 + 'live_agent_message_font_color' => $this->options['live_agent_message_font_color'] ?? '#333333',
8157 + 'chat_toolbar_toggle' => $this->options['chat_toolbar_toggle'] ?? 'off',
8158 + 'show_pdf_upload_button' => $this->options['show_pdf_upload_button'] ?? 'on',
8159 + 'show_word_upload_button' => $this->options['show_word_upload_button'] ?? 'on',
8160 + 'mode_indicator_bg_color' => $this->options['mode_indicator_bg_color'] ?? '#767676',
8161 + 'mode_indicator_font_color' => $this->options['mode_indicator_font_color'] ?? '#ffffff',
8162 + 'toolbar_icon_color' => $this->options['toolbar_icon_color'] ?? '#212121',
8163 + ));
8164 + break;
8165 + }
8166 +
8167 + // Additional localization that was in the original code
8168 + wp_localize_script('mxchat-admin-js', 'mxchatPromptsAdmin', array(
5533 8169 'ajax_url' => admin_url('admin-ajax.php'),
5534 - 'link_target_toggle' => $this->options['link_target_toggle'] ?? 'off',
5535 - 'user_message_bg_color' => $this->options['user_message_bg_color'] ?? '#fff',
5536 - 'user_message_font_color' => $this->options['user_message_font_color'] ?? '#212121',
5537 - 'bot_message_bg_color' => $this->options['bot_message_bg_color'] ?? '#212121',
5538 - 'bot_message_font_color' => $this->options['bot_message_font_color'] ?? '#fff',
5539 - 'top_bar_bg_color' => $this->options['top_bar_bg_color'] ?? '#212121',
5540 - 'send_button_font_color' => $this->options['send_button_font_color'] ?? '#212121',
5541 - 'close_button_color' => $this->options['close_button_color'] ?? '#fff',
5542 - 'chatbot_background_color' => $this->options['chatbot_background_color'] ?? '#212121',
5543 - 'icon_color' => $this->options['icon_color'] ?? '#fff',
5544 - 'chat_input_font_color' => $this->options['chat_input_font_color'] ?? '#212121',
5545 - 'pre_chat_message' => $this->options['pre_chat_message'] ?? esc_html__('Hey there! Ask me anything!', 'mxchat'),
5546 - 'rate_limit_message' => $this->options['rate_limit_message'] ?? esc_html__('Rate limit exceeded. Please try again later.', 'mxchat'),
5547 - 'loops_api_key' => $this->options['loops_api_key'] ?? '',
5548 - 'loops_mailing_list' => $this->options['loops_mailing_list'] ?? '',
5549 - 'triggered_phrase_response' => $this->options['triggered_phrase_response'] ?? esc_html__('Would you like to join our mailing list? Please provide your email below.', 'mxchat'),
5550 - 'email_capture_response' => $this->options['email_capture_response'] ?? esc_html__('Thank you for providing your email! You\'ve been added to our list.', 'mxchat'),
5551 - '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'),
5552 - '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'),
5553 - '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'),
5554 - 'pdf_max_pages' => $this->options['pdf_max_pages'] ?? 69,
5555 - 'live_agent_webhook_url' => $this->options['live_agent_webhook_url'] ?? '',
5556 - 'live_agent_secret_key' => $this->options['live_agent_secret_key'] ?? '',
5557 - 'live_agent_bot_token' => $this->options['live_agent_bot_token'] ?? '',
5558 - 'live_agent_message_bg_color' => $this->options['live_agent_message_bg_color'] ?? '#ffffff',
5559 - 'live_agent_message_font_color' => $this->options['live_agent_message_font_color'] ?? '#333333',
5560 - 'chat_toolbar_toggle' => $this->options['chat_toolbar_toggle'] ?? 'off',
5561 - 'mode_indicator_bg_color' => $this->options['mode_indicator_bg_color'] ?? '#767676',
5562 - 'mode_indicator_font_color' => $this->options['mode_indicator_font_color'] ?? '#ffffff',
5563 - 'toolbar_icon_color' => $this->options['toolbar_icon_color'] ?? '#212121',
8170 + 'prompts_setting_nonce' => wp_create_nonce('mxchat_prompts_setting_nonce'),
5564 8171 ));
5565 8172 }
5566 8173
5567 -
5568 8174 public function mxchat_sanitize($input) {
5569 8175 $new_input = array();
5570 8176
5571 8177 if (isset($input['api_key'])) {
@@ -5573,9 +8179,9 @@
5573 8179 }
5574 8180
5575 8181 if (isset($input['similarity_threshold'])) {
5576 8182 $new_input['similarity_threshold'] = absint($input['similarity_threshold']); // Ensure it's an integer
5577 - $new_input['similarity_threshold'] = min(max($new_input['similarity_threshold'], 70), 85); // Enforce range
8183 + $new_input['similarity_threshold'] = min(max($new_input['similarity_threshold'], 20), 85); // Enforce range
5578 8184 }
5579 8185
5580 8186 if (isset($input['xai_api_key'])) {
5581 8187 $new_input['xai_api_key'] = sanitize_text_field($input['xai_api_key']);
@@ -5584,12 +8190,24 @@
5584 8190 if (isset($input['claude_api_key'])) {
5585 8191 $new_input['claude_api_key'] = sanitize_text_field($input['claude_api_key']);
5586 8192 }
5587 8193
8194 + if (isset($input['enable_streaming_toggle'])) {
8195 + $new_input['enable_streaming_toggle'] = ($input['enable_streaming_toggle'] === 'on') ? 'on' : 'off';
8196 + } else {
8197 + // If checkbox not checked, it won't be in $input, so set to 'off'
8198 + $new_input['enable_streaming_toggle'] = 'off';
8199 + }
8200 +
8201 +
5588 8202 if (isset($input['deepseek_api_key'])) {
5589 8203 $new_input['deepseek_api_key'] = sanitize_text_field($input['deepseek_api_key']);
5590 8204 }
5591 8205
8206 + if (isset($input['gemini_api_key'])) {
8207 + $new_input['gemini_api_key'] = sanitize_text_field($input['gemini_api_key']);
8208 + }
8209 +
5592 8210 if (isset($input['enable_woocommerce_integration'])) {
5593 8211 $new_input['enable_woocommerce_integration'] = $input['enable_woocommerce_integration'] === 'on' ? 'on' : 'off';
5594 8212 }
5595 8213
@@ -5622,12 +8240,40 @@
5622 8240 if (isset($input['append_to_body'])) {
5623 8241 $new_input['append_to_body'] = $input['append_to_body'] === 'on' ? 'on' : 'off';
5624 8242 }
5625 8243
8244 + // Post type visibility settings
8245 + if (isset($input['post_type_visibility_mode'])) {
8246 + $allowed_modes = array('all', 'include', 'exclude');
8247 + $new_input['post_type_visibility_mode'] = in_array($input['post_type_visibility_mode'], $allowed_modes)
8248 + ? $input['post_type_visibility_mode']
8249 + : 'all';
8250 + }
8251 +
8252 + if (isset($input['post_type_visibility_list'])) {
8253 + if (is_array($input['post_type_visibility_list'])) {
8254 + $new_input['post_type_visibility_list'] = array_map('sanitize_key', $input['post_type_visibility_list']);
8255 + } else {
8256 + $new_input['post_type_visibility_list'] = array();
8257 + }
8258 + }
8259 +
8260 + if (isset($input['contextual_awareness_toggle'])) {
8261 + $new_input['contextual_awareness_toggle'] = $input['contextual_awareness_toggle'] === 'on' ? 'on' : 'off';
8262 +}
8263 +
8264 + if (isset($input['show_frontend_debugger'])) {
8265 + $new_input['show_frontend_debugger'] = $input['show_frontend_debugger'] === 'on' ? 'on' : 'off';
8266 +}
8267 +
5626 8268 if (isset($input['top_bar_title'])) {
5627 8269 $new_input['top_bar_title'] = sanitize_text_field($input['top_bar_title']);
5628 8270 }
5629 8271
8272 + if (isset($input['ai_agent_text'])) {
8273 + $new_input['ai_agent_text'] = sanitize_text_field($input['ai_agent_text']);
8274 + }
8275 +
5630 8276 if (isset($input['enable_email_block'])) {
5631 8277 $new_input['enable_email_block'] = sanitize_text_field($input['enable_email_block']);
5632 8278 }
5633 8279
@@ -5637,11 +8283,20 @@
5637 8283 }
5638 8284 if (isset($input['email_blocker_button_text'])) {
5639 8285 $new_input['email_blocker_button_text'] = sanitize_text_field($input['email_blocker_button_text']);
5640 8286 }
5641 -
8287 + // Sanitize name field toggle
8288 + if (isset($input['enable_name_field'])) {
8289 + $new_input['enable_name_field'] = ($input['enable_name_field'] === 'on') ? 'on' : 'off';
8290 + } else {
8291 + $new_input['enable_name_field'] = 'off';
8292 + }
8293 + // Sanitize name field placeholder
8294 + if (isset($input['name_field_placeholder'])) {
8295 + $new_input['name_field_placeholder'] = sanitize_text_field($input['name_field_placeholder']);
8296 + }
5642 8297 if (isset($input['intro_message'])) {
5643 - $new_input['intro_message'] = sanitize_textarea_field($input['intro_message']); // Changed from sanitize_text_field
8298 + $new_input['intro_message'] = wp_kses_post($input['intro_message']); // Use wp_kses_post instead
5644 8299 }
5645 8300
5646 8301 if (isset($input['input_copy'])) {
5647 8302 $new_input['input_copy'] = sanitize_text_field($input['input_copy']);
@@ -5650,47 +8305,100 @@
5650 8305 if (isset($input['rate_limit_message'])) {
5651 8306 $new_input['rate_limit_message'] = sanitize_text_field($input['rate_limit_message']);
5652 8307 }
5653 8308
5654 - if (isset($input['rate_limit_logged_out'])) {
5655 - $allowed_limits = array('1', '3', '5', '10', '15', '20', '100', 'unlimited'); // Add 'unlimited' to allowed values
5656 - $rate_limit = sanitize_text_field($input['rate_limit_logged_out']);
8309 +// Handle the new rate limits format
8310 +if (isset($input['rate_limits']) && is_array($input['rate_limits'])) {
8311 + $new_input['rate_limits'] = array();
8312 + $allowed_limits = array('1', '3', '5', '10', '15', '20', '50', '100', 'unlimited');
8313 + $allowed_timeframes = array('hourly', 'daily', 'weekly', 'monthly');
5657 8314
5658 - if (in_array($rate_limit, $allowed_limits, true)) {
5659 - $new_input['rate_limit_logged_out'] = $rate_limit;
5660 - } else {
5661 - $new_input['rate_limit_logged_out'] = '10'; // Default for logged-out users
8315 + foreach ($input['rate_limits'] as $role_id => $settings) {
8316 + $new_input['rate_limits'][$role_id] = array();
8317 +
8318 + // Sanitize limit
8319 + if (isset($settings['limit'])) {
8320 + $limit = sanitize_text_field($settings['limit']);
8321 + if (in_array($limit, $allowed_limits, true)) {
8322 + $new_input['rate_limits'][$role_id]['limit'] = $limit;
8323 + } else {
8324 + $new_input['rate_limits'][$role_id]['limit'] = ($role_id === 'logged_out') ? '10' : '100'; // Default
8325 + }
5662 8326 }
5663 - }
5664 8327
5665 - // Handle role rate limits
5666 - if (isset($input['role_rate_limits']) && is_array($input['role_rate_limits'])) {
5667 - $allowed_limits = array('1', '3', '5', '10', '15', '20', '100', 'unlimited');
5668 - $new_input['role_rate_limits'] = array();
5669 -
5670 - foreach ($input['role_rate_limits'] as $role_id => $rate_limit) {
5671 - $rate_limit = sanitize_text_field($rate_limit);
5672 - if (in_array($rate_limit, $allowed_limits, true)) {
5673 - $new_input['role_rate_limits'][$role_id] = $rate_limit;
8328 + // Sanitize timeframe
8329 + if (isset($settings['timeframe'])) {
8330 + $timeframe = sanitize_text_field($settings['timeframe']);
8331 + if (in_array($timeframe, $allowed_timeframes, true)) {
8332 + $new_input['rate_limits'][$role_id]['timeframe'] = $timeframe;
5674 8333 } else {
5675 - $new_input['role_rate_limits'][$role_id] = '100'; // Default
8334 + $new_input['rate_limits'][$role_id]['timeframe'] = 'daily'; // Default
5676 8335 }
5677 8336 }
8337 +
8338 + // Sanitize message
8339 + if (isset($settings['message'])) {
8340 + $new_input['rate_limits'][$role_id]['message'] = sanitize_textarea_field($settings['message']);
8341 + }
5678 8342 }
8343 +}
5679 8344
5680 8345 if (isset($input['pre_chat_message'])) {
5681 8346 $new_input['pre_chat_message'] = sanitize_textarea_field($input['pre_chat_message']);
5682 8347 }
5683 8348
5684 - if (isset($input['model'])) {
8349 + if (isset($input['voyage_api_key'])) {
8350 + $new_input['voyage_api_key'] = sanitize_text_field($input['voyage_api_key']);
8351 + }
8352 +
8353 + // Add to your sanitize function
8354 + if (isset($input['embedding_model'])) {
5685 8355 $allowed_models = array(
5686 - 'grok-beta',
8356 + 'text-embedding-ada-002',
8357 + 'text-embedding-3-small',
8358 + 'text-embedding-3-large',
8359 + 'voyage-3-large',
8360 + 'gemini-embedding-exp-03-07'
8361 + );
8362 + if (in_array($input['embedding_model'], $allowed_models)) {
8363 + $new_input['embedding_model'] = sanitize_text_field($input['embedding_model']);
8364 + }
8365 + }
8366 +
8367 +if (isset($input['model'])) {
8368 + if ($input['model'] === 'openrouter') {
8369 + $new_input['model'] = 'openrouter';
8370 + } else {
8371 + $allowed_models = array(
8372 + 'gemini-2.0-flash',
8373 + 'gemini-2.0-flash-lite',
8374 + 'gemini-1.5-pro',
8375 + 'gemini-1.5-flash',
8376 + 'grok-4-0709',
8377 + 'grok-4-1-fast-reasoning',
8378 + 'grok-4-1-fast-non-reasoning',
8379 + 'grok-3-beta',
8380 + 'grok-3-fast-beta',
8381 + 'grok-3-mini-beta',
8382 + 'grok-3-mini-fast-beta',
5687 8383 'grok-2',
5688 8384 'deepseek-chat',
5689 - 'claude-3-5-sonnet-20241022',
8385 + 'claude-sonnet-4-5-20250929',
8386 + 'claude-opus-4-1-20250805',
8387 + 'claude-haiku-4-5-20251001',
8388 + 'claude-opus-4-20250514',
8389 + 'claude-sonnet-4-20250514',
8390 + 'claude-3-7-sonnet-20250219',
8391 + // REMOVED: 'claude-3-5-sonnet-20241022',
5690 8392 'claude-3-opus-20240229',
5691 8393 'claude-3-sonnet-20240229',
5692 8394 'claude-3-haiku-20240307',
8395 + 'gpt-5.2',
8396 + 'gpt-5.1-2025-11-13',
8397 + 'gpt-5',
8398 + 'gpt-5-mini',
8399 + 'gpt-5-nano',
8400 + 'gpt-4.1-2025-04-14',
5693 8401 'gpt-4o',
5694 8402 'gpt-4o-mini',
5695 8403 'gpt-4-turbo',
5696 8404 'gpt-4',
@@ -5695,13 +8403,33 @@
5695 8403 'gpt-4-turbo',
5696 8404 'gpt-4',
5697 8405 'gpt-3.5-turbo',
5698 8406 );
8407 +
5699 8408 if (in_array($input['model'], $allowed_models)) {
5700 8409 $new_input['model'] = sanitize_text_field($input['model']);
8410 + } else {
8411 + // Fallback for any deprecated model
8412 + $new_input['model'] = 'claude-3-7-sonnet-20250219';
5701 8413 }
5702 8414 }
8415 +}
8416 +
8417 +if (isset($input['openrouter_selected_model'])) {
8418 + // Just sanitize it, don't validate against a whitelist
8419 + $new_input['openrouter_selected_model'] = sanitize_text_field($input['openrouter_selected_model']);
8420 +}
5703 8421
8422 +// ADD THIS:
8423 +if (isset($input['openrouter_selected_model_name'])) {
8424 + $new_input['openrouter_selected_model_name'] = sanitize_text_field($input['openrouter_selected_model_name']);
8425 +}
8426 +
8427 + if (isset($input['openrouter_api_key'])) {
8428 + $new_input['openrouter_api_key'] = sanitize_text_field($input['openrouter_api_key']);
8429 + }
8430 +
8431 +
5704 8432 if (isset($input['close_button_color'])) {
5705 8433 $new_input['close_button_color'] = sanitize_hex_color($input['close_button_color']);
5706 8434 }
5707 8435
@@ -5756,8 +8484,12 @@
5756 8484 if (isset($input['top_bar_bg_color'])) {
5757 8485 $new_input['top_bar_bg_color'] = sanitize_hex_color($input['top_bar_bg_color']);
5758 8486 }
5759 8487
8488 + if (isset($input['quick_questions_toggle_color'])) {
8489 + $new_input['quick_questions_toggle_color'] = sanitize_hex_color($input['quick_questions_toggle_color']);
8490 + }
8491 +
5760 8492 if (isset($input['send_button_font_color'])) {
5761 8493 $new_input['send_button_font_color'] = sanitize_hex_color($input['send_button_font_color']);
5762 8494 }
5763 8495
@@ -5815,15 +8547,14 @@
5815 8547 if (isset($input['loops_mailing_list'])) {
5816 8548 $new_input['loops_mailing_list'] = sanitize_text_field($input['loops_mailing_list']);
5817 8549 }
5818 8550
5819 - // Sanitize Triggered Phrase Response
8551 +// Sanitize Triggered Phrase Response
5820 8552 if (isset($input['triggered_phrase_response'])) {
5821 8553 $new_input['triggered_phrase_response'] = wp_kses_post($input['triggered_phrase_response']);
5822 8554 }
5823 -
5824 8555 if (isset($input['email_capture_response'])) {
5825 - $new_input['email_capture_response'] = sanitize_textarea_field($input['email_capture_response']);
8556 + $new_input['email_capture_response'] = wp_kses_post($input['email_capture_response']);
5826 8557 }
5827 8558
5828 8559 // Sanitize Brave Search Settings
5829 8560 if (isset($input['brave_api_key'])) {
@@ -5856,8 +8587,22 @@
5856 8587 if (isset($input['chat_toolbar_toggle'])) {
5857 8588 $new_input['chat_toolbar_toggle'] = $input['chat_toolbar_toggle'] === 'on' ? 'on' : 'off';
5858 8589 }
5859 8590
8591 + // Sanitize PDF upload button toggle
8592 + if (isset($input['show_pdf_upload_button'])) {
8593 + $new_input['show_pdf_upload_button'] = $input['show_pdf_upload_button'] === 'on' ? 'on' : 'off';
8594 + } else {
8595 + $new_input['show_pdf_upload_button'] = 'off'; // If checkbox is unchecked
8596 + }
8597 +
8598 + // Sanitize Word upload button toggle
8599 + if (isset($input['show_word_upload_button'])) {
8600 + $new_input['show_word_upload_button'] = $input['show_word_upload_button'] === 'on' ? 'on' : 'off';
8601 + } else {
8602 + $new_input['show_word_upload_button'] = 'off'; // If checkbox is unchecked
8603 + }
8604 +
5860 8605 if (isset($input['pdf_intent_trigger_text'])) {
5861 8606 $new_input['pdf_intent_trigger_text'] = sanitize_text_field($input['pdf_intent_trigger_text']);
5862 8607 }
5863 8608
@@ -5887,8 +8632,12 @@
5887 8632 if (isset($input['live_agent_bot_token'])) {
5888 8633 $new_input['live_agent_bot_token'] = sanitize_text_field($input['live_agent_bot_token']);
5889 8634 }
5890 8635
8636 + if (isset($input['live_agent_user_ids'])) {
8637 + $new_input['live_agent_user_ids'] = sanitize_textarea_field($input['live_agent_user_ids']);
8638 + }
8639 +
5891 8640 if (isset($input['live_agent_status'])) {
5892 8641 $new_input['live_agent_status'] = ($input['live_agent_status'] === 'on') ? 'on' : 'off';
5893 8642 }
5894 8643 if (isset($input['live_agent_away_message'])) {
@@ -5911,62 +8660,10 @@
5911 8660 }
5912 8661
5913 8662
5914 8663
5915 -private static function mxchat_extract_main_content($html) {
5916 - if (empty($html)) {
5917 - //error_log('mxchat_extract_main_content: Empty HTML content received');
5918 - return '';
5919 - }
5920 8664
5921 - try {
5922 - $dom = new DOMDocument;
5923 - libxml_use_internal_errors(true); // Suppress HTML parsing errors
5924 8665
5925 - // Simple load of HTML, with @ to suppress warnings
5926 - @$dom->loadHTML($html, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
5927 -
5928 - $xpath = new DOMXPath($dom);
5929 -
5930 - // Simplified selectors focusing on common content areas
5931 - $selectors = [
5932 - '//article',
5933 - '//*[@id="content"]',
5934 - '//*[@class="entry-content"]',
5935 - '//main'
5936 - ];
5937 -
5938 - foreach ($selectors as $selector) {
5939 - $nodes = $xpath->query($selector);
5940 - if ($nodes && $nodes->length > 0) {
5941 - $content = '';
5942 - foreach ($nodes as $node) {
5943 - $content .= $dom->saveHTML($node);
5944 - }
5945 - if (!empty($content)) {
5946 - return $content;
5947 - }
5948 - }
5949 - }
5950 -
5951 - // Fallback: Return the entire body content if no specific selector matches
5952 - $body = $dom->getElementsByTagName('body');
5953 - if ($body->length > 0) {
5954 - return $dom->saveHTML($body->item(0));
5955 - }
5956 -
5957 - // Last resort: return the original HTML
5958 - return $html;
5959 -
5960 - } catch (Exception $e) {
5961 - //error_log('mxchat_extract_main_content error: ' . $e->getMessage());
5962 - return $html; // Return original HTML if parsing fails
5963 - } finally {
5964 - libxml_clear_errors();
5965 - }
5966 -}
5967 -
5968 -
5969 8666 private function mxchat_fetch_loops_mailing_lists($api_key) {
5970 8667 $url = 'https://app.loops.so/api/v1/lists';
5971 8668 $response = wp_remote_get($url, array(
5972 8669 'headers' => array(
@@ -5984,12 +8681,8 @@
5984 8681
5985 8682 return isset($lists) && is_array($lists) ? $lists : array();
5986 8683 }
5987 8684
5988 -
5989 -
5990 -
5991 -
5992 8685 function mxchat_calculate_cosine_similarity($vec1, $vec2) {
5993 8686 if (empty($vec1) || empty($vec2)) {
5994 8687 return 0.0;
5995 8688 }
@@ -6010,8 +8703,441 @@
6010 8703 return $dot_product / (sqrt($norm_a) * sqrt($norm_b));
6011 8704 }
6012 8705 }
6013 8706
8707 +/**
8708 + * Validates nonce and deletes all chat prompts
8709 + */
8710 +public function mxchat_handle_delete_all_prompts() {
8711 + //error_log('=== DELETE ALL DEBUG START ===');
8712 +
8713 + // Verify nonce
8714 + if (!isset($_POST['mxchat_delete_all_prompts_nonce']) || !wp_verify_nonce($_POST['mxchat_delete_all_prompts_nonce'], 'mxchat_delete_all_prompts_action')) {
8715 + //error_log('DEBUG: Nonce verification failed');
8716 + wp_die(__('Nonce verification failed.', 'mxchat'));
8717 + }
8718 +
8719 + // Check permissions
8720 + if (!current_user_can('manage_options')) {
8721 + //error_log('DEBUG: Permission check failed');
8722 + wp_die(__('You do not have sufficient permissions to delete all prompts.', 'mxchat'));
8723 + }
8724 +
8725 + // Get bot_id from POST data
8726 + $bot_id = isset($_POST['bot_id']) ? sanitize_text_field($_POST['bot_id']) : 'default';
8727 + //error_log('DEBUG: Bot ID from POST: ' . $bot_id);
8728 +
8729 + $success = true;
8730 + $error_messages = array();
8731 +
8732 + // Get bot-specific Pinecone configuration
8733 + $pinecone_manager = MxChat_Pinecone_Manager::get_instance();
8734 + $pinecone_options = $pinecone_manager->mxchat_get_bot_pinecone_options($bot_id);
8735 +
8736 + //error_log('DEBUG: Pinecone options retrieved:');
8737 + //error_log(' - Use Pinecone: ' . ($pinecone_options['mxchat_use_pinecone'] ?? 'NOT SET'));
8738 + //error_log(' - API Key: ' . (empty($pinecone_options['mxchat_pinecone_api_key']) ? 'EMPTY' : 'SET'));
8739 + //error_log(' - Host: ' . ($pinecone_options['mxchat_pinecone_host'] ?? 'NOT SET'));
8740 +
8741 + $use_pinecone = ($pinecone_options['mxchat_use_pinecone'] ?? '0') === '1';
8742 +
8743 + if ($use_pinecone && !empty($pinecone_options['mxchat_pinecone_api_key'])) {
8744 + //error_log('[MXCHAT-DELETE] Pinecone is enabled for bot ' . $bot_id . ' - deleting all from Pinecone');
8745 +
8746 + // Delete from bot-specific Pinecone index
8747 + $result = $pinecone_manager->mxchat_delete_all_from_pinecone($pinecone_options);
8748 +
8749 + //error_log('DEBUG: Delete all result: ' . ($result['success'] ? 'SUCCESS' : 'FAILED'));
8750 + if (!$result['success']) {
8751 + //error_log('DEBUG: Delete all error: ' . $result['message']);
8752 + }
8753 +
8754 + if (!$result['success']) {
8755 + $success = false;
8756 + $error_messages[] = $result['message'];
8757 + }
8758 +
8759 + // No cache clearing needed since we removed caching
8760 +
8761 + } else {
8762 + //error_log('[MXCHAT-DELETE] Pinecone not enabled for bot ' . $bot_id . ' - deleting from WordPress database');
8763 +
8764 + // Delete from WordPress database
8765 + global $wpdb;
8766 + $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
8767 +
8768 + // If multi-bot is active and we have a specific bot, delete only that bot's content
8769 + if ($bot_id !== 'default' && class_exists('MxChat_Multi_Bot_Manager')) {
8770 + $result = $wpdb->delete(
8771 + $table_name,
8772 + array('bot_id' => $bot_id),
8773 + array('%s')
8774 + );
8775 + } else {
8776 + // Delete all content for default bot
8777 + $result = $wpdb->query("DELETE FROM {$table_name}");
8778 + }
8779 +
8780 + if ($result === false) {
8781 + $success = false;
8782 + $error_messages[] = 'Failed to delete from WordPress database';
8783 + }
8784 + }
8785 +
8786 + // No cache clearing needed since we removed caching
8787 +
8788 + //error_log('=== DELETE ALL DEBUG END ===');
8789 +
8790 + // Redirect back with a success message and bot_id
8791 + $redirect_url = add_query_arg(array(
8792 + 'page' => 'mxchat-prompts',
8793 + 'bot_id' => $bot_id,
8794 + 'all_deleted' => $success ? 'true' : 'false'
8795 + ), admin_url('admin.php'));
8796 +
8797 + wp_safe_redirect($redirect_url);
8798 + exit;
8799 +}
8800 +
8801 +
8802 +
8803 + /**
8804 + * Handles deletion prompt with nonce validation
8805 + */
8806 + public function mxchat_handle_delete_prompt() {
8807 + // Sanitize and validate nonce
8808 + $nonce = isset($_GET['_wpnonce']) ? sanitize_text_field(wp_unslash($_GET['_wpnonce'])) : '';
8809 + if (empty($nonce) || !wp_verify_nonce($nonce, 'mxchat_delete_prompt_nonce')) {
8810 + wp_die(esc_html__('Nonce verification failed.', 'mxchat'));
8811 + }
8812 +
8813 + // Check permissions
8814 + if (!current_user_can('manage_options')) {
8815 + wp_die(esc_html__('You do not have sufficient permissions to delete prompts.', 'mxchat'));
8816 + }
8817 +
8818 + // Get ID and source parameters
8819 + $id = isset($_GET['id']) ? sanitize_text_field($_GET['id']) : '';
8820 + $source = isset($_GET['source']) ? sanitize_text_field($_GET['source']) : '';
8821 +
8822 + if (empty($id)) {
8823 + wp_die(esc_html__('Invalid prompt ID.', 'mxchat'));
8824 + }
8825 +
8826 + $success = false;
8827 + $error_message = '';
8828 +
8829 + // Check if Pinecone is enabled and determine source automatically if not specified
8830 + $pinecone_options = get_option('mxchat_pinecone_addon_options', array());
8831 + $use_pinecone = ($pinecone_options['mxchat_use_pinecone'] ?? '0') === '1';
8832 +
8833 + // If source is not specified, determine based on Pinecone configuration
8834 + if (empty($source)) {
8835 + $source = $use_pinecone ? 'pinecone' : 'wordpress';
8836 + }
8837 +
8838 + if ($source === 'pinecone' || $use_pinecone) {
8839 + //error_log('[MXCHAT-DELETE] Deleting from Pinecone, ID: ' . $id);
8840 +
8841 + // Handle Pinecone deletion
8842 + if (empty($pinecone_options['mxchat_pinecone_host']) ||
8843 + empty($pinecone_options['mxchat_pinecone_api_key'])) {
8844 + wp_die(esc_html__('Pinecone configuration is missing.', 'mxchat'));
8845 + }
8846 +
8847 + // Delete from Pinecone using the vector ID directly
8848 + $pinecone_manager = MxChat_Pinecone_Manager::get_instance();
8849 + $result = $pinecone_manager->mxchat_delete_from_pinecone_by_vector_id(
8850 + $id,
8851 + $pinecone_options['mxchat_pinecone_api_key'],
8852 + $pinecone_options['mxchat_pinecone_host']
8853 + );
8854 + if ($result['success']) {
8855 + $success = true;
8856 +
8857 + // Remove from vector cache
8858 + $pinecone_manager->mxchat_remove_from_pinecone_vector_cache($id);
8859 + $pinecone_manager->mxchat_remove_from_processed_content_caches($id);
8860 +
8861 + set_transient('mxchat_admin_notice_success',
8862 + esc_html__('Vector deleted successfully from Pinecone.', 'mxchat'), 30);
8863 + } else {
8864 + $error_message = $result['message'];
8865 + set_transient('mxchat_admin_notice_error',
8866 + esc_html__('Failed to delete from Pinecone: ', 'mxchat') . esc_html($error_message), 30);
8867 + }
8868 + } else {
8869 + //error_log('[MXCHAT-DELETE] Deleting from WordPress database, ID: ' . $id);
8870 +
8871 + // Handle WordPress database deletion
8872 + global $wpdb;
8873 + $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
8874 +
8875 + // Clear cache and delete prompt
8876 + wp_cache_delete('prompt_' . $id, 'mxchat_prompts');
8877 +
8878 + $result = $wpdb->delete(
8879 + $table_name,
8880 + array('id' => intval($id)),
8881 + array('%d')
8882 + );
8883 +
8884 + if ($result !== false) {
8885 + $success = true;
8886 + set_transient('mxchat_admin_notice_success',
8887 + esc_html__('Entry deleted successfully.', 'mxchat'), 30);
8888 + } else {
8889 + set_transient('mxchat_admin_notice_error',
8890 + esc_html__('Failed to delete entry from database.', 'mxchat'), 30);
8891 + }
8892 + }
8893 +
8894 + // Redirect back to the prompts page
8895 + wp_safe_redirect(add_query_arg(
8896 + array(
8897 + 'page' => 'mxchat-prompts',
8898 + 'deleted' => $success ? 'true' : 'false'
8899 + ),
8900 + admin_url('admin.php')
8901 + ));
8902 + exit;
8903 + }
8904 +
8905 +
8906 +
8907 +
8908 + /**
8909 + * Averages multiple vectors into a single vector
8910 + */
8911 + private function mxchat_average_vectors($vectors) {
8912 + $vector_length = count($vectors[0]);
8913 + $sum_vector = array_fill(0, $vector_length, 0);
8914 +
8915 + foreach ($vectors as $vector) {
8916 + for ($i = 0; $i < $vector_length; $i++) {
8917 + $sum_vector[$i] += $vector[$i];
8918 + }
8919 + }
8920 +
8921 + // Divide each component by the number of vectors to get the average
8922 + $num_vectors = count($vectors);
8923 + for ($i = 0; $i < $vector_length; $i++) {
8924 + $sum_vector[$i] /= $num_vectors;
8925 + }
8926 +
8927 + return $sum_vector;
8928 + }
8929 +
8930 +
8931 +
8932 +
8933 + /**
8934 + * Generates embeddings from input text for MXChat
8935 + */
8936 + public function mxchat_generate_embedding($text) {
8937 + // Enable detailed logging for debugging
8938 + //error_log('[MXCHAT-EMBED] Starting embedding generation. Text length: ' . strlen($text) . ' bytes');
8939 + //error_log('[MXCHAT-EMBED] Text preview: ' . substr($text, 0, 100) . '...');
8940 +
8941 + $options = get_option('mxchat_options');
8942 + $selected_model = $options['embedding_model'] ?? 'text-embedding-ada-002';
8943 + //error_log('[MXCHAT-EMBED] Selected embedding model: ' . $selected_model);
8944 +
8945 + // Determine provider and endpoint
8946 + if (strpos($selected_model, 'voyage') === 0) {
8947 + $api_key = $options['voyage_api_key'] ?? '';
8948 + $endpoint = 'https://api.voyageai.com/v1/embeddings';
8949 + $provider_name = 'Voyage AI';
8950 + //error_log('[MXCHAT-EMBED] Using Voyage AI API');
8951 + } elseif (strpos($selected_model, 'gemini-embedding') === 0) {
8952 + $api_key = $options['gemini_api_key'] ?? '';
8953 + $endpoint = 'https://generativelanguage.googleapis.com/v1beta/models/' . $selected_model . ':embedContent';
8954 + $provider_name = 'Google Gemini';
8955 + //error_log('[MXCHAT-EMBED] Using Google Gemini API');
8956 + } else {
8957 + $api_key = $options['api_key'] ?? '';
8958 + $endpoint = 'https://api.openai.com/v1/embeddings';
8959 + $provider_name = 'OpenAI';
8960 + //error_log('[MXCHAT-EMBED] Using OpenAI API');
8961 + }
8962 +
8963 + //error_log('[MXCHAT-EMBED] Using endpoint: ' . $endpoint);
8964 +
8965 + if (empty($api_key)) {
8966 + $error_message = sprintf('Missing %s API key. Please configure your API key in the MxChat settings.', $provider_name);
8967 + //error_log('[MXCHAT-EMBED] Error: ' . $error_message);
8968 + return $error_message;
8969 + }
8970 +
8971 + // Check if text is too long (for OpenAI, roughly estimate tokens as words/0.75)
8972 + $estimated_tokens = ceil(str_word_count($text) / 0.75);
8973 + //error_log('[MXCHAT-EMBED] Estimated token count: ~' . $estimated_tokens);
8974 +
8975 + if ($estimated_tokens > 8000 && strpos($selected_model, 'voyage') === false && strpos($selected_model, 'gemini-embedding') === false) {
8976 + //error_log('[MXCHAT-EMBED] Warning: Text may exceed OpenAI token limits (8K for most models)');
8977 + // Consider truncating text here
8978 + }
8979 +
8980 + // Prepare request body based on provider
8981 + if (strpos($selected_model, 'gemini-embedding') === 0) {
8982 + // Gemini API format
8983 + $request_body = array(
8984 + 'model' => 'models/' . $selected_model,
8985 + 'content' => array(
8986 + 'parts' => array(
8987 + array('text' => $text)
8988 + )
8989 + )
8990 + );
8991 +
8992 + // Set output dimensionality to 1536 for consistency with other models
8993 + $request_body['outputDimensionality'] = 1536;
8994 + } else {
8995 + // OpenAI/Voyage API format
8996 + $request_body = array(
8997 + 'model' => $selected_model,
8998 + 'input' => $text
8999 + );
9000 +
9001 + // Add output_dimension for voyage-3-large model
9002 + if ($selected_model === 'voyage-3-large') {
9003 + $request_body['output_dimension'] = 2048;
9004 + }
9005 + }
9006 +
9007 + //error_log('[MXCHAT-EMBED] Request prepared with model: ' . $selected_model);
9008 +
9009 + // Prepare headers based on provider
9010 + if (strpos($selected_model, 'gemini-embedding') === 0) {
9011 + // Gemini uses API key as query parameter
9012 + $endpoint .= '?key=' . $api_key;
9013 + $headers = array(
9014 + 'Content-Type' => 'application/json'
9015 + );
9016 + } else {
9017 + // OpenAI/Voyage use Bearer token
9018 + $headers = array(
9019 + 'Authorization' => 'Bearer ' . $api_key,
9020 + 'Content-Type' => 'application/json'
9021 + );
9022 + }
9023 +
9024 + // Make API request
9025 + //error_log('[MXCHAT-EMBED] Sending API request to: ' . $endpoint);
9026 + $response = wp_remote_post($endpoint, array(
9027 + 'body' => wp_json_encode($request_body),
9028 + 'headers' => $headers,
9029 + 'timeout' => 60 // Increased timeout for large inputs
9030 + ));
9031 +
9032 + // Handle wp_remote_post errors
9033 + if (is_wp_error($response)) {
9034 + $error_message = $response->get_error_message();
9035 + //error_log('[MXCHAT-EMBED] WP Remote Post Error: ' . $error_message);
9036 + return 'Connection error: ' . $error_message;
9037 + }
9038 +
9039 + // Get and check HTTP response code
9040 + $http_code = wp_remote_retrieve_response_code($response);
9041 + //error_log('[MXCHAT-EMBED] API Response Code: ' . $http_code);
9042 +
9043 + if ($http_code !== 200) {
9044 + $error_body = wp_remote_retrieve_body($response);
9045 + //error_log('[MXCHAT-EMBED] API Error Response Body: ' . $error_body);
9046 +
9047 + // Try to parse error for more details
9048 + $error_json = json_decode($error_body, true);
9049 + if (json_last_error() === JSON_ERROR_NONE && isset($error_json['error'])) {
9050 + $error_type = $error_json['error']['type'] ?? 'unknown';
9051 + $error_message = $error_json['error']['message'] ?? 'No message';
9052 + //error_log('[MXCHAT-EMBED] API Error Type: ' . $error_type);
9053 + //error_log('[MXCHAT-EMBED] API Error Message: ' . $error_message);
9054 +
9055 + // Customize error message for common API errors
9056 + if ($error_type === 'invalid_request_error' && strpos($error_message, 'API key') !== false) {
9057 + $error_message = sprintf('Invalid %s API key. Please check your API key in the MxChat settings.', $provider_name);
9058 + } elseif ($error_type === 'authentication_error') {
9059 + $error_message = sprintf('%s authentication failed. Please verify your API key in the MxChat settings.', $provider_name);
9060 + }
9061 +
9062 + //error_log('[MXCHAT-EMBED] Returning error: ' . $error_message);
9063 + return $error_message;
9064 + }
9065 +
9066 + $error_message = sprintf("API Error (HTTP %d): Unable to generate embedding", $http_code);
9067 + //error_log('[MXCHAT-EMBED] Returning error: ' . $error_message);
9068 + return $error_message;
9069 + }
9070 +
9071 + // Parse response body
9072 + $response_body = wp_remote_retrieve_body($response);
9073 + //error_log('[MXCHAT-EMBED] Received response length: ' . strlen($response_body) . ' bytes');
9074 +
9075 + $response_data = json_decode($response_body, true);
9076 +
9077 + if (json_last_error() !== JSON_ERROR_NONE) {
9078 + $error = json_last_error_msg();
9079 + //error_log('[MXCHAT-EMBED] JSON Parse Error: ' . $error);
9080 + //error_log('[MXCHAT-EMBED] Response preview: ' . substr($response_body, 0, 200));
9081 + return "Failed to parse API response: $error";
9082 + }
9083 +
9084 + // Handle different response formats based on provider
9085 + if (strpos($selected_model, 'gemini-embedding') === 0) {
9086 + // Gemini API response format
9087 + if (isset($response_data['embedding']['values'])) {
9088 + $embedding_dimensions = count($response_data['embedding']['values']);
9089 + //error_log('[MXCHAT-EMBED] Successfully extracted Gemini embedding with ' . $embedding_dimensions . ' dimensions');
9090 +
9091 + // Check if embedding dimensions are as expected (should be 1536)
9092 + if ($embedding_dimensions !== 1536) {
9093 + //error_log('[MXCHAT-EMBED] Warning: Unexpected Gemini embedding dimensions: ' . $embedding_dimensions);
9094 + }
9095 +
9096 + return $response_data['embedding']['values'];
9097 + } else {
9098 + //error_log('[MXCHAT-EMBED] Error: No embedding found in Gemini response');
9099 + //error_log('[MXCHAT-EMBED] Response structure: ' . wp_json_encode(array_keys($response_data)));
9100 +
9101 + if (isset($response_data['error'])) {
9102 + $error_message = "Gemini API Error in response: " . wp_json_encode($response_data['error']);
9103 + //error_log('[MXCHAT-EMBED] ' . $error_message);
9104 + return $error_message;
9105 + }
9106 +
9107 + $error_message = "Invalid Gemini API response format: No embedding found";
9108 + //error_log('[MXCHAT-EMBED] ' . $error_message);
9109 + return $error_message;
9110 + }
9111 + } else {
9112 + // OpenAI/Voyage API response format
9113 + if (isset($response_data['data'][0]['embedding'])) {
9114 + $embedding_dimensions = count($response_data['data'][0]['embedding']);
9115 + //error_log('[MXCHAT-EMBED] Successfully extracted embedding with ' . $embedding_dimensions . ' dimensions');
9116 +
9117 + // Check if embedding dimensions are as expected
9118 + if (($selected_model === 'text-embedding-ada-002' && $embedding_dimensions !== 1536) ||
9119 + ($selected_model === 'voyage-3-large' && $embedding_dimensions !== 2048)) {
9120 + //error_log('[MXCHAT-EMBED] Warning: Unexpected embedding dimensions');
9121 + }
9122 +
9123 + return $response_data['data'][0]['embedding'];
9124 + } else {
9125 + //error_log('[MXCHAT-EMBED] Error: No embedding found in response');
9126 + //error_log('[MXCHAT-EMBED] Response structure: ' . wp_json_encode(array_keys($response_data)));
9127 +
9128 + if (isset($response_data['error'])) {
9129 + $error_message = "API Error in response: " . wp_json_encode($response_data['error']);
9130 + //error_log('[MXCHAT-EMBED] ' . $error_message);
9131 + return $error_message;
9132 + }
9133 +
9134 + $error_message = "Invalid API response format: No embedding found";
9135 + //error_log('[MXCHAT-EMBED] ' . $error_message);
9136 + return $error_message;
9137 + }
9138 + }
9139 + }
6014 9140
6015 9141
6016 9142
6017 9143 }