PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 3.0.2
MxChat – AI Chatbot & Content Generation for WordPress v3.0.2
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 +5180 -3151 2.0.53.0.2 View file →
@@ -6,13 +6,31 @@
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 + /**
13 + * Get whether the license is activated
14 + * @return bool
15 + */
16 + public function is_activated() {
17 + return $this->is_activated;
18 + }
19 +
20 + /**
21 + * Get plugin options
22 + * @return array
23 + */
24 + public function get_options() {
25 + return $this->options;
26 + }
27 +
28 + public function __construct($knowledge_manager = null) {
12 29 $this->options = get_option('mxchat_options');
13 30 $this->chat_count = get_option('mxchat_chat_count', 0);
14 31 $this->is_activated = $this->is_license_active();
32 + $this->knowledge_manager = $knowledge_manager;
15 33
16 34 // Initialize default options if they are not set
17 35 if (!$this->options) {
18 36 $this->initialize_default_options();
@@ -23,50 +41,59 @@
23 41 add_action('admin_init', array($this, 'mxchat_page_init'));
24 42 add_action('admin_init', array($this, 'mxchat_prompts_page_init'));
25 43 add_action('admin_enqueue_scripts', array($this, 'mxchat_enqueue_admin_assets'));
26 44 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 45 add_action('admin_post_mxchat_delete_prompt', array($this, 'mxchat_handle_delete_prompt'));
29 46 add_action('wp_ajax_mxchat_fetch_chat_history', array($this, 'mxchat_fetch_chat_history'));
30 47 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'));
48 + add_action('wp_ajax_mxchat_fetch_conversation', array($this, 'mxchat_fetch_conversation'));
32 49 add_action('wp_footer', array($this, 'mxchat_append_chatbot_to_body'));
33 50 add_action('admin_head-mxchat-prompts', array($this, 'mxchat_enqueue_admin_assets'));
34 51 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 52 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 53 add_action('admin_post_mxchat_delete_all_prompts', array($this, 'mxchat_handle_delete_all_prompts'));
39 54 add_action('admin_post_mxchat_add_intent', array($this, 'mxchat_handle_add_intent'));
40 55 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'));
56 + add_action('admin_post_mxchat_edit_intent', array($this, 'mxchat_handle_edit_intent'));
49 57 add_action('wp_ajax_mxchat_export_transcripts', array($this, 'export_chat_transcripts'));
50 - add_action('wp_ajax_mxchat_save_prompts_setting', array($this, 'mxchat_save_prompts_setting_callback'));
51 - if (isset($this->options['enable_woocommerce_integration']) &&
52 - ($this->options['enable_woocommerce_integration'] === '1' ||
53 - $this->options['enable_woocommerce_integration'] === 'on')) {
58 + add_action('admin_init', array($this, 'mxchat_transcripts_page_init'));
59 + add_action('wp_ajax_dismiss_live_agent_notice', array($this, 'dismiss_live_agent_notice'));
60 + add_action('wp_ajax_dismiss_theme_migration_notice', array($this, 'dismiss_theme_migration_notice'));
61 + add_action('mxchat_cleanup_old_transcripts', array($this, 'cleanup_old_transcripts'));
54 62
55 - //error_log('MxChat Admin: WooCommerce integration is enabled, adding hooks');
56 - add_action('wp_insert_post', array($this, 'mxchat_handle_product_change'), 10, 3);
57 - add_action('wp_trash_post', array($this, 'mxchat_handle_product_delete'));
58 - add_action('before_delete_post', array($this, 'mxchat_handle_product_delete'));
59 - }
63 + add_action('admin_init', array($this, 'register_pinecone_settings'));
64 + add_action('admin_init', array($this, 'register_openai_vectorstore_settings'));
65 +
66 + add_action('admin_notices', array($this, 'display_admin_notices'));
67 +
68 + add_action('wp_ajax_mxchat_test_streaming_actual', [$this, 'mxchat_handle_test_streaming_actual']);
69 + add_action('wp_ajax_mxchat_test_streaming', [$this, 'mxchat_handle_test_streaming']); // Keep existing as fallback
70 + add_action('wp_ajax_mxchat_test_vectorstore_connection', array($this, 'mxchat_test_vectorstore_connection'));
71 +
72 + add_action('wp_ajax_mxchat_save_selected_bot', array($this, 'mxchat_save_selected_bot'));
73 + add_action('wp_ajax_mxchat_fetch_openrouter_models', array($this, 'fetch_openrouter_models'));
74 + add_action('wp_ajax_mxchat_get_rag_context', array($this, 'mxchat_get_rag_context'));
75 +
76 + // Actions page AJAX handlers
77 + add_action('wp_ajax_mxchat_fetch_actions_list', array($this, 'mxchat_fetch_actions_list'));
78 + add_action('wp_ajax_mxchat_toggle_action_status', array($this, 'mxchat_toggle_action_status'));
79 + add_action('wp_ajax_mxchat_bulk_delete_actions', array($this, 'mxchat_bulk_delete_actions'));
80 + add_action('wp_ajax_mxchat_add_intent_ajax', array($this, 'mxchat_add_intent_ajax'));
81 + add_action('wp_ajax_mxchat_edit_intent_ajax', array($this, 'mxchat_edit_intent_ajax'));
82 + add_action('wp_ajax_mxchat_delete_intent_ajax', array($this, 'mxchat_delete_intent_ajax'));
83 +
84 + // Slack test connection
85 + add_action('wp_ajax_mxchat_test_slack_connection', array($this, 'mxchat_test_slack_connection'));
60 86 }
61 87
62 - // Method to check if the license is active
63 88 private function is_license_active() {
64 - $license_status = get_option('mxchat_license_status', esc_html__('inactive', 'mxchat'));
65 - return $license_status === esc_html__('active', 'mxchat');
66 - }
89 + // Get the raw value without translation
90 + $license_status = get_option('mxchat_license_status', 'inactive');
67 91
68 - // Initialize default options
92 + // Check against multiple possible values, bypassing translation issues
93 + return ($license_status === 'active' || $license_status === esc_html__('active', 'mxchat'));
94 +}
95 +
69 96 private function initialize_default_options() {
70 97 $default_options = array(
71 98 'api_key' => '',
72 99 'xai_api_key' => '',
@@ -71,25 +98,51 @@
71 98 'api_key' => '',
72 99 'xai_api_key' => '',
73 100 'claude_api_key' => '',
74 101 'deepseek_api_key' => '',
75 - '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:
76 - - Your name is [Chatbot Name].
77 - - 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.
78 - - 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.
79 - - Keep your responses short, concise, and to the point. Provide clear and direct answers suitable for a chatbot interaction.
80 - - 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'),
102 + 'voyage_api_key' => '',
103 + 'gemini_api_key' => '',
104 + 'enable_streaming_toggle' => 'on',
105 + 'enable_web_search' => 'off',
106 + 'embedding_model' => 'text-embedding-ada-002',
107 + '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:
108 +
109 + # Response Style - CRITICALLY IMPORTANT
110 + - MAXIMUM LENGTH: 1-3 short sentences per response
111 + - Ultra-concise: Get straight to the answer with no filler
112 + - No introductions like "Sure!" or "I\'d be happy to help"
113 + - No phrases like "based on my knowledge" or "according to information"
114 + - No explanatory text before giving the answer
115 + - No summaries or repetition
116 + - Hyperlink all URLs
117 + - Respond in user\'s language
118 + - Minor chit chat or conversation is okay, but try to keep it focused on [insert topic]
119 +
120 + # Knowledge Base Requirements - PREVENT HALLUCINATIONS
121 + - ONLY answer questions using information explicitly provided in OFFICIAL KNOWLEDGE DATABASE CONTENT sections marked with ===== delimiters
122 + - If required information is NOT in the knowledge database: "I don\'t have enough information in my knowledge base to answer that question accurately."
123 + - NEVER invent or hallucinate URLs, links, product specs, procedures, dates, statistics, names, contacts, or company information
124 + - When knowledge base information is unclear or contradictory, acknowledge the limitation rather than guessing
125 + - Better to admit insufficient information than provide inaccurate answers',
81 126 'model' => esc_html__('gpt-4o', 'mxchat'),
82 127 'rate_limit_logged_out' => esc_html__('100', 'mxchat'),
83 128 'role_rate_limits' => array(),
84 129 'rate_limit_message' => esc_html__('Rate limit exceeded. Please try again later.', 'mxchat'),
85 130 'enable_email_block' => '',
86 - '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'),
131 + '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'),
87 132 'email_blocker_button_text' => esc_html__('Start Chat', 'mxchat'),
133 + 'enable_name_field' => 'off', // NEW
134 + 'name_field_placeholder' => esc_html__('Enter your name', 'mxchat'), // NEW
88 135 'top_bar_title' => esc_html__('MxChat', 'mxchat'),
89 - 'intro_message' => esc_html__('Hello! How can I assist you today?', 'mxchat'),
136 + 'intro_message' => __('Hello! How can I assist you today?', 'mxchat'),
137 + 'ai_agent_text' => esc_html__('AI Agent', 'mxchat'),
90 138 'input_copy' => esc_html__('How can I assist?', 'mxchat'),
91 139 'append_to_body' => esc_html__('off', 'mxchat'),
140 + 'post_type_visibility_mode' => 'all', // 'all', 'include', 'exclude'
141 + 'post_type_visibility_list' => array(), // Array of post type slugs
142 + 'contextual_awareness_toggle' => 'off',
143 + 'citation_links_toggle' => 'on',
144 + 'show_frontend_debugger' => 'on',
92 145 'close_button_color' => esc_html__('#fff', 'mxchat'),
93 146 'chatbot_bg_color' => esc_html__('#fff', 'mxchat'),
94 147 'user_message_bg_color' => esc_html__('#fff', 'mxchat'),
95 148 'user_message_font_color' => esc_html__('#212121', 'mxchat'),
@@ -106,24 +159,35 @@
106 159
107 160 // New fields for Loops Integration
108 161 'loops_api_key' => '',
109 162 'loops_mailing_list' => '',
110 - 'triggered_phrase_response' => esc_html__('Would you like to join our mailing list? Please provide your email below.', 'mxchat'),
111 - 'email_capture_response' => esc_html__('Thank you for providing your email! You\'ve been added to our list.', 'mxchat'),
163 + 'triggered_phrase_response' => __('Would you like to join our mailing list? Please provide your email below.', 'mxchat'),
164 + 'email_capture_response' => __('Thank you for providing your email! You\'ve been added to our list.', 'mxchat'),
112 165 'popular_question_1' => '',
113 166 'popular_question_2' => '',
114 167 'popular_question_3' => '',
115 - 'pdf_intent_trigger_text' => esc_html__("Please provide the URL to the PDF you'd like to discuss.", 'mxchat'),
116 - 'pdf_intent_success_text' => esc_html__("I've processed the PDF. What questions do you have about it?", 'mxchat'),
117 - 'pdf_intent_error_text' => esc_html__("Sorry, I couldn't process the PDF. Please ensure it's a valid file.", 'mxchat'),
168 + 'pdf_intent_trigger_text' => __("Please provide the URL to the PDF you'd like to discuss.", 'mxchat'),
169 + 'pdf_intent_success_text' => __("I've processed the PDF. What questions do you have about it?", 'mxchat'),
170 + 'pdf_intent_error_text' => __("Sorry, I couldn't process the PDF. Please ensure it's a valid file.", 'mxchat'),
118 171 'pdf_max_pages' => 69,
172 + 'show_pdf_upload_button' => 'on',
173 + 'show_word_upload_button' => 'on',
119 174
120 - // Live Agent Integration
175 + // Live Agent Integration (Slack)
121 176 'live_agent_webhook_url' => '',
122 177 'live_agent_secret_key' => '',
123 178 'live_agent_bot_token' => '',
124 179 'live_agent_message_bg_color' => esc_html__('#ffffff', 'mxchat'),
125 180 'live_agent_message_font_color' => esc_html__('#333333', 'mxchat'),
181 +
182 + // Telegram Integration
183 + 'telegram_status' => 'off',
184 + 'telegram_bot_token' => '',
185 + 'telegram_group_id' => '',
186 + 'telegram_webhook_secret' => '',
187 + 'telegram_notification_message' => __("I've notified a support agent. Please allow a moment for them to respond. If you'd like to continue with AI, just type \"Switch to AI\" at any time.", 'mxchat'),
188 + 'telegram_away_message' => __("I just checked, and it looks like our support team isn't personally available at the moment. If you'd like, you can leave your email address, and they'll get back to you as soon as possible. In the meantime, you can keep chatting with me — just let me know how I can help!", 'mxchat'),
189 +
126 190 'chat_toolbar_toggle' => esc_html__('off', 'mxchat'),
127 191 'mode_indicator_bg_color' => esc_html__('#767676', 'mxchat'),
128 192 'mode_indicator_font_color' => esc_html__('#ffffff', 'mxchat'),
129 193 'toolbar_icon_color' => esc_html__('#212121', 'mxchat'),
@@ -150,10 +214,8 @@
150 214 // Update the $this->options property
151 215 $this->options = $merged_options;
152 216 }
153 217
154 -
155 -
156 218 public function mxchat_add_plugin_page() {
157 219 // Main menu page
158 220 add_menu_page(
159 221 esc_html__('MxChat Settings', 'mxchat'),
@@ -164,8 +226,18 @@
164 226 'dashicons-testimonial',
165 227 6
166 228 );
167 229
230 + // Rename the first submenu item from "MxChat" to "Settings"
231 + add_submenu_page(
232 + 'mxchat-max',
233 + esc_html__('MxChat Settings', 'mxchat'),
234 + esc_html__('Settings', 'mxchat'),
235 + 'manage_options',
236 + 'mxchat-max',
237 + array($this, 'mxchat_create_admin_page')
238 + );
239 +
168 240 // Submenu page for Knowledge
169 241 add_submenu_page(
170 242 'mxchat-max',
171 243 esc_html__('Prompts', 'mxchat'),
@@ -184,30 +256,21 @@
184 256 array($this, 'mxchat_create_transcripts_page')
185 257 );
186 258
187 259 add_submenu_page(
188 - 'mxchat-max',
189 - esc_html__('MxChat Intents', 'mxchat'),
190 - esc_html__('Intents', 'mxchat'),
191 - 'manage_options',
192 - 'mxchat-intents',
193 - array($this, 'mxchat_intents_page_html')
260 + 'mxchat-max',
261 + esc_html__('MxChat Actions', 'mxchat'),
262 + esc_html__('Actions', 'mxchat'),
263 + 'manage_options',
264 + 'mxchat-actions',
265 + array($this, 'mxchat_actions_page_html')
194 266 );
195 267
196 - add_submenu_page(
197 - 'mxchat-max',
198 - esc_html__('Add Ons', 'mxchat'),
199 - esc_html__('Add Ons', 'mxchat'),
200 - 'manage_options',
201 - 'mxchat-addons',
202 - array($this, 'mxchat_create_addons_page')
203 - );
204 -
205 - // Submenu page for Activation Key
268 + // Consolidated Pro & Extensions page (replaces separate Add Ons and Pro Upgrade pages)
206 269 add_submenu_page(
207 270 'mxchat-max',
208 - esc_html__('Pro Upgrade', 'mxchat'),
209 - esc_html__('Pro Upgrade', 'mxchat'),
271 + esc_html__('Pro & Extensions', 'mxchat'),
272 + esc_html__('Pro & Extensions', 'mxchat'),
210 273 'manage_options',
211 274 'mxchat-activation',
212 275 array($this, 'mxchat_create_activation_page')
213 276 );
@@ -218,154 +281,525 @@
218 281 $addons_page = new MxChat_Addons();
219 282 $addons_page->render_page();
220 283 }
221 284
222 -public function mxchat_save_setting_callback() {
223 - check_ajax_referer('mxchat_save_setting_nonce');
224 - if (!current_user_can('manage_options')) {
225 - wp_send_json_error(['message' => esc_html__('Unauthorized', 'mxchat')]);
285 +/**
286 + * Test actual streaming functionality in the WordPress environment
287 + */
288 +public function mxchat_handle_test_streaming_actual() {
289 + check_ajax_referer('mxchat_test_streaming_nonce', 'nonce');
290 +
291 + // Check if headers have already been sent
292 + if (headers_sent()) {
293 + wp_send_json_error(['message' => 'Headers already sent - streaming not possible']);
294 + return;
226 295 }
227 - $name = isset($_POST['name']) ? $_POST['name'] : '';
228 - // Strip slashes from the value before saving
229 - $value = isset($_POST['value']) ? stripslashes($_POST['value']) : '';
230 - if (empty($name)) {
231 - wp_send_json_error(['message' => esc_html__('Invalid field name', 'mxchat')]);
296 +
297 + // Check for required functions
298 + if (!function_exists('curl_init')) {
299 + wp_send_json_error(['message' => 'cURL not available - streaming requires cURL']);
300 + return;
232 301 }
233 - // Load the full options array
302 +
303 + // Get user's selected model and API key
234 304 $options = get_option('mxchat_options', []);
235 - // Handle special cases
236 - switch ($name) {
237 - case 'additional_popular_questions':
238 - $questions = json_decode($value, true); // No need for stripslashes here
239 - if (is_array($questions)) {
240 - $options[$name] = $questions;
241 - // Also update old option for backwards compatibility
242 - update_option('additional_popular_questions', $questions);
243 - }
305 + $selected_model = $options['model'] ?? 'gpt-4o';
306 +
307 + // Get the provider from the model
308 + $model_parts = explode('-', $selected_model);
309 + $provider = strtolower($model_parts[0]);
310 +
311 + // Get the appropriate API key
312 + $api_key = '';
313 + switch ($provider) {
314 + case 'gpt':
315 + case 'o1':
316 + $api_key = $options['api_key'] ?? '';
244 317 break;
245 - case 'user_message_bg_color':
246 - case 'user_message_font_color':
247 - case 'bot_message_bg_color':
248 - case 'bot_message_font_color':
249 - case 'top_bar_bg_color':
250 - case 'send_button_font_color':
251 - case 'chatbot_background_color':
252 - case 'icon_color':
253 - case 'chat_input_font_color':
254 - case 'live_agent_message_bg_color':
255 - case 'live_agent_message_font_color':
256 - case 'mode_indicator_bg_color':
257 - case 'mode_indicator_font_color':
258 - case 'toolbar_icon_color':
259 - // Store color values directly
260 - $options[$name] = $value;
318 + case 'claude':
319 + $api_key = $options['claude_api_key'] ?? '';
261 320 break;
262 - case 'live_agent_status':
263 - // Set the new value
264 - $options[$name] = ($value === 'on') ? 'on' : 'off';
321 + case 'grok':
322 + $api_key = $options['xai_api_key'] ?? '';
265 323 break;
266 - case 'enable_woocommerce_integration':
267 - // Handle values that used to be 1/0
268 - $options[$name] = ($value === 'on' || $value === '1') ? 'on' : 'off';
324 + case 'deepseek':
325 + $api_key = $options['deepseek_api_key'] ?? '';
269 326 break;
327 + case 'gemini':
328 + $api_key = $options['gemini_api_key'] ?? '';
329 + break;
270 330 default:
271 - // Handle role rate limits
272 - if (strpos($name, 'mxchat_options[role_rate_limits]') !== false) {
273 - // Extract role ID from the name
274 - preg_match('/\[role_rate_limits\]\[(.*?)\]/', $name, $matches);
275 - if (isset($matches[1])) {
276 - $role_id = $matches[1];
277 - // Initialize role_rate_limits if it doesn't exist
278 - if (!isset($options['role_rate_limits'])) {
279 - $options['role_rate_limits'] = [];
331 + // Default to OpenAI for unknown models
332 + $api_key = $options['api_key'] ?? '';
333 + $provider = 'gpt';
334 + break;
335 + }
336 +
337 + if (empty($api_key)) {
338 + wp_send_json_error(['message' => "API key not configured for {$provider} provider"]);
339 + return;
340 + }
341 +
342 + // Test streaming with the selected model and provider
343 + try {
344 + $this->perform_streaming_test($provider, $selected_model, $api_key);
345 + } catch (Exception $e) {
346 + wp_send_json_error(['message' => 'Streaming test exception: ' . $e->getMessage()]);
347 + }
348 +}
349 +
350 +/**
351 + * Perform the actual streaming test
352 + */
353 +private function perform_streaming_test($provider, $model, $api_key) {
354 + // Set streaming headers
355 + header('Content-Type: text/event-stream');
356 + header('Cache-Control: no-cache');
357 + header('X-Accel-Buffering: no'); // Disable nginx buffering
358 +
359 + // Prepare test message
360 + $test_message = "Please respond with exactly: 'Streaming test successful!' - send this as a short response for testing.";
361 +
362 + // Configure API request based on provider
363 + $url = '';
364 + $headers = [];
365 + $body = [];
366 +
367 + switch ($provider) {
368 + case 'gpt':
369 + case 'o1':
370 + $url = 'https://api.openai.com/v1/chat/completions';
371 + $headers = [
372 + 'Content-Type: application/json',
373 + 'Authorization: Bearer ' . $api_key
374 + ];
375 + $body = [
376 + 'model' => $model,
377 + 'messages' => [['role' => 'user', 'content' => $test_message]],
378 + 'max_tokens' => 50,
379 + 'temperature' => 0.3,
380 + 'stream' => true
381 + ];
382 + break;
383 +
384 + case 'claude':
385 + $url = 'https://api.anthropic.com/v1/messages';
386 + $headers = [
387 + 'Content-Type: application/json',
388 + 'x-api-key: ' . $api_key,
389 + 'anthropic-version: 2023-06-01'
390 + ];
391 + $body = [
392 + 'model' => $model,
393 + 'messages' => [['role' => 'user', 'content' => $test_message]],
394 + 'max_tokens' => 50,
395 + 'temperature' => 0.3,
396 + 'stream' => true
397 + ];
398 + break;
399 +
400 + case 'grok':
401 + $url = 'https://api.x.ai/v1/chat/completions';
402 + $headers = [
403 + 'Content-Type: application/json',
404 + 'Authorization: Bearer ' . $api_key
405 + ];
406 + $body = [
407 + 'model' => $model,
408 + 'messages' => [['role' => 'user', 'content' => $test_message]],
409 + 'max_tokens' => 50,
410 + 'temperature' => 0.3,
411 + 'stream' => true
412 + ];
413 + break;
414 +
415 + case 'deepseek':
416 + $url = 'https://api.deepseek.com/v1/chat/completions';
417 + $headers = [
418 + 'Content-Type: application/json',
419 + 'Authorization: Bearer ' . $api_key
420 + ];
421 + $body = [
422 + 'model' => $model,
423 + 'messages' => [['role' => 'user', 'content' => $test_message]],
424 + 'max_tokens' => 50,
425 + 'temperature' => 0.3,
426 + 'stream' => true
427 + ];
428 + break;
429 +
430 + default:
431 + echo "data: " . json_encode(['error' => 'Unsupported provider for streaming test: ' . $provider]) . "\n\n";
432 + flush();
433 + return;
434 + }
435 +
436 + // Initialize cURL
437 + $ch = curl_init();
438 + curl_setopt($ch, CURLOPT_URL, $url);
439 + curl_setopt($ch, CURLOPT_RETURNTRANSFER, false);
440 + curl_setopt($ch, CURLOPT_POST, true);
441 + curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
442 + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
443 + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
444 + curl_setopt($ch, CURLOPT_TIMEOUT, 30);
445 + curl_setopt($ch, CURLOPT_WRITEFUNCTION, function($ch, $data) use ($provider) {
446 + return $this->process_streaming_test_data($data, $provider);
447 + });
448 +
449 + // Send initial test message
450 + echo "data: " . json_encode(['content' => '[Starting streaming test...]']) . "\n\n";
451 + flush();
452 +
453 + $result = curl_exec($ch);
454 + $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
455 + $curl_error = curl_error($ch);
456 + curl_close($ch);
457 +
458 + if ($curl_error) {
459 + echo "data: " . json_encode(['error' => 'cURL Error: ' . $curl_error]) . "\n\n";
460 + flush();
461 + return;
462 + }
463 +
464 + if ($http_code !== 200) {
465 + echo "data: " . json_encode(['error' => 'API returned HTTP ' . $http_code]) . "\n\n";
466 + flush();
467 + return;
468 + }
469 +
470 + // Send completion signal
471 + echo "data: [DONE]\n\n";
472 + flush();
473 +}
474 +
475 +/**
476 + * Process streaming data for the test
477 + */
478 +private function process_streaming_test_data($data, $provider) {
479 + static $chunk_count = 0;
480 +
481 + $lines = explode("\n", $data);
482 +
483 + foreach ($lines as $line) {
484 + if (trim($line) === '') {
485 + continue;
486 + }
487 +
488 + // Handle different provider formats
489 + if ($provider === 'claude') {
490 + // Claude uses event: and data: format
491 + if (strpos($line, 'data: ') === 0) {
492 + $json_str = substr($line, 6);
493 + $json = json_decode($json_str, true);
494 +
495 + if (isset($json['type']) && $json['type'] === 'content_block_delta') {
496 + if (isset($json['delta']['text'])) {
497 + $chunk_count++;
498 + echo "data: " . json_encode([
499 + 'content' => $json['delta']['text'],
500 + 'test_chunk' => $chunk_count
501 + ]) . "\n\n";
502 + flush();
280 503 }
281 - // Update the specific role's rate limit
282 - $options['role_rate_limits'][$role_id] = sanitize_text_field($value);
283 - break;
284 504 }
285 505 }
286 - // Handle toggles
287 - if (strpos($name, 'toggle') !== false || in_array($name, [
288 - 'chat_persistence_toggle',
289 - 'privacy_toggle',
290 - 'complianz_toggle',
291 - 'chat_toolbar_toggle' // Removed live_agent_status from here
292 - ])) {
293 - $options[$name] = ($value === 'on') ? 'on' : 'off';
294 - } else {
295 - // Store all other values directly
296 - $options[$name] = $value;
506 + } else {
507 + // OpenAI, X.AI, DeepSeek format
508 + if (strpos($line, 'data: ') === 0) {
509 + $json_str = substr($line, 6);
510 +
511 + if ($json_str === '[DONE]') {
512 + // Don't echo [DONE] here, let the main function handle it
513 + continue;
514 + }
515 +
516 + $json = json_decode($json_str, true);
517 + if (isset($json['choices'][0]['delta']['content'])) {
518 + $chunk_count++;
519 + echo "data: " . json_encode([
520 + 'content' => $json['choices'][0]['delta']['content'],
521 + 'test_chunk' => $chunk_count
522 + ]) . "\n\n";
523 + flush();
524 + }
297 525 }
298 - break;
526 + }
299 527 }
300 - // Save all updates
301 - $updated = update_option('mxchat_options', $options);
302 - // Handle backwards compatibility for certain fields
303 - $legacy_fields = ['brave_api_key', 'brave_image_count', 'brave_safe_search', 'brave_news_count',
304 - 'brave_country', 'brave_language', 'similarity_threshold'];
305 - if (in_array($name, $legacy_fields)) {
306 - // Also update the individual option for backwards compatibility
307 - update_option($name, $value);
528 +
529 + return strlen($data);
530 +}
531 +
532 +/**
533 + * Updated version of your existing test method (keep this as a fallback)
534 + */
535 +public function mxchat_handle_test_streaming() {
536 + check_ajax_referer('mxchat_test_streaming_nonce', 'nonce');
537 +
538 + // Use the actual streaming test instead
539 + $this->mxchat_handle_test_streaming_actual();
540 +}
541 +
542 +
543 +public function register_pinecone_settings() {
544 + register_setting(
545 + 'mxchat_pinecone_addon_options',
546 + 'mxchat_pinecone_addon_options',
547 + array(
548 + 'type' => 'array',
549 + 'sanitize_callback' => array($this, 'sanitize_pinecone_settings'),
550 + 'default' => array(
551 + 'mxchat_use_pinecone' => '0',
552 + 'mxchat_pinecone_api_key' => '',
553 + 'mxchat_pinecone_host' => '',
554 + 'mxchat_pinecone_index' => '',
555 + 'mxchat_pinecone_environment' => ''
556 + )
557 + )
558 + );
559 +}
560 +
561 +public function sanitize_pinecone_settings($input) {
562 + $sanitized = array();
563 +
564 + $sanitized['mxchat_use_pinecone'] = isset($input['mxchat_use_pinecone']) ? '1' : '0';
565 + $sanitized['mxchat_pinecone_api_key'] = sanitize_text_field($input['mxchat_pinecone_api_key'] ?? '');
566 + $sanitized['mxchat_pinecone_host'] = sanitize_text_field($input['mxchat_pinecone_host'] ?? '');
567 + $sanitized['mxchat_pinecone_index'] = sanitize_text_field($input['mxchat_pinecone_index'] ?? '');
568 + $sanitized['mxchat_pinecone_environment'] = sanitize_text_field($input['mxchat_pinecone_environment'] ?? '');
569 +
570 + // Remove https:// from host if present
571 + $sanitized['mxchat_pinecone_host'] = str_replace(['https://', 'http://'], '', $sanitized['mxchat_pinecone_host']);
572 +
573 + return $sanitized;
574 +}
575 +
576 +public function register_openai_vectorstore_settings() {
577 + register_setting(
578 + 'mxchat_openai_vectorstore_options',
579 + 'mxchat_openai_vectorstore_options',
580 + array(
581 + 'type' => 'array',
582 + 'sanitize_callback' => array($this, 'sanitize_openai_vectorstore_settings'),
583 + 'default' => array(
584 + 'mxchat_use_openai_vectorstore' => '0',
585 + 'mxchat_vectorstore_ids' => '',
586 + 'mxchat_vectorstore_max_results' => 5
587 + )
588 + )
589 + );
590 +}
591 +
592 +public function sanitize_openai_vectorstore_settings($input) {
593 + $sanitized = array();
594 +
595 + $sanitized['mxchat_use_openai_vectorstore'] = isset($input['mxchat_use_openai_vectorstore']) ? '1' : '0';
596 + $sanitized['mxchat_vectorstore_ids'] = sanitize_text_field($input['mxchat_vectorstore_ids'] ?? '');
597 + $sanitized['mxchat_vectorstore_max_results'] = absint($input['mxchat_vectorstore_max_results'] ?? 5);
598 +
599 + // Ensure max results is within reasonable range
600 + if ($sanitized['mxchat_vectorstore_max_results'] < 1) {
601 + $sanitized['mxchat_vectorstore_max_results'] = 1;
308 602 }
309 - if ($updated) {
310 - wp_send_json_success(['message' => esc_html__('Setting saved', 'mxchat')]);
603 + if ($sanitized['mxchat_vectorstore_max_results'] > 20) {
604 + $sanitized['mxchat_vectorstore_max_results'] = 20;
605 + }
606 +
607 + return $sanitized;
608 +}
609 +
610 +/**
611 + * AJAX handler to test OpenAI Vector Store connection
612 + */
613 +public function mxchat_test_vectorstore_connection() {
614 + check_ajax_referer('mxchat_admin_nonce', 'nonce');
615 +
616 + if (!current_user_can('manage_options')) {
617 + wp_send_json_error(array('message' => __('Permission denied.', 'mxchat')));
618 + return;
619 + }
620 +
621 + $vectorstore_options = get_option('mxchat_openai_vectorstore_options', array());
622 + $vectorstore_ids = $vectorstore_options['mxchat_vectorstore_ids'] ?? '';
623 + $mxchat_options = get_option('mxchat_options', array());
624 + $api_key = $mxchat_options['api_key'] ?? '';
625 +
626 + if (empty($api_key)) {
627 + wp_send_json_error(array('message' => __('OpenAI API key is not configured.', 'mxchat')));
628 + return;
629 + }
630 +
631 + if (empty($vectorstore_ids)) {
632 + wp_send_json_error(array('message' => __('No Vector Store ID configured.', 'mxchat')));
633 + return;
634 + }
635 +
636 + // Get the first Vector Store ID for testing
637 + $ids_array = array_map('trim', explode(',', $vectorstore_ids));
638 + $test_id = $ids_array[0];
639 +
640 + // Test by retrieving the Vector Store info
641 + $response = wp_remote_get(
642 + 'https://api.openai.com/v1/vector_stores/' . $test_id,
643 + array(
644 + 'headers' => array(
645 + 'Authorization' => 'Bearer ' . $api_key,
646 + 'Content-Type' => 'application/json',
647 + 'OpenAI-Beta' => 'assistants=v2'
648 + ),
649 + 'timeout' => 30
650 + )
651 + );
652 +
653 + if (is_wp_error($response)) {
654 + wp_send_json_error(array('message' => __('Connection failed: ', 'mxchat') . $response->get_error_message()));
655 + return;
656 + }
657 +
658 + $status_code = wp_remote_retrieve_response_code($response);
659 + $body = json_decode(wp_remote_retrieve_body($response), true);
660 +
661 + if ($status_code === 200 && isset($body['id'])) {
662 + $file_count = $body['file_counts']['completed'] ?? 0;
663 + $name = $body['name'] ?? $test_id;
664 + wp_send_json_success(array(
665 + 'message' => sprintf(
666 + __('Connected successfully! Vector Store: %s (%d files)', 'mxchat'),
667 + esc_html($name),
668 + $file_count
669 + )
670 + ));
671 + } elseif ($status_code === 404) {
672 + wp_send_json_error(array('message' => __('Vector Store not found. Please check the ID.', 'mxchat')));
673 + } elseif ($status_code === 401) {
674 + wp_send_json_error(array('message' => __('Invalid API key.', 'mxchat')));
311 675 } else {
312 - wp_send_json_error(['message' => esc_html__('Update failed or no changes', 'mxchat')]);
676 + $error_message = $body['error']['message'] ?? __('Unknown error occurred.', 'mxchat');
677 + wp_send_json_error(array('message' => $error_message));
313 678 }
314 679 }
315 680
316 681 /**
317 - * Handles AJAX auto-save for prompts and auto-sync settings.
682 + * AJAX handler to test Slack connection and validate scopes
318 683 */
319 -public function mxchat_save_prompts_setting_callback() {
320 - // Check the correct nonce for this action.
321 - check_ajax_referer( 'mxchat_prompts_setting_nonce', '_ajax_nonce' );
684 +public function mxchat_test_slack_connection() {
685 + check_ajax_referer('mxchat_admin_nonce', 'nonce');
322 686
323 - if ( ! current_user_can( 'manage_options' ) ) {
324 - wp_send_json_error( [ 'message' => esc_html__( 'Unauthorized', 'mxchat' ) ] );
687 + if (!current_user_can('manage_options')) {
688 + wp_send_json_error(array('message' => __('Permission denied.', 'mxchat')));
689 + return;
325 690 }
326 691
327 - $name = isset( $_POST['name'] ) ? sanitize_text_field( wp_unslash( $_POST['name'] ) ) : '';
328 - $value = isset( $_POST['value'] ) ? sanitize_text_field( wp_unslash( $_POST['value'] ) ) : '';
692 + $bot_token = $this->options['live_agent_bot_token'] ?? '';
329 693
330 - if ( empty( $name ) ) {
331 - wp_send_json_error( [ 'message' => esc_html__( 'Invalid field name', 'mxchat' ) ] );
694 + if (empty($bot_token)) {
695 + wp_send_json_error(array('message' => __('Slack Bot Token is not configured. Please enter your token and save settings first.', 'mxchat')));
696 + return;
332 697 }
333 698
334 - // Handle standalone auto-sync settings.
335 - if ( in_array( $name, [ 'mxchat_auto_sync_posts', 'mxchat_auto_sync_pages' ], true ) ) {
336 - // Convert checkbox value to "1" if checked, "0" if not.
337 - $value = ( $value === 'on' ) ? '1' : '0';
338 - update_option( $name, $value );
339 - wp_send_json_success( [ 'message' => esc_html__( 'Setting saved', 'mxchat' ) ] );
699 + // Test 1: Validate bot token with auth.test
700 + $auth_response = wp_remote_post('https://slack.com/api/auth.test', array(
701 + 'headers' => array(
702 + 'Authorization' => 'Bearer ' . $bot_token,
703 + 'Content-Type' => 'application/json'
704 + ),
705 + 'timeout' => 15
706 + ));
707 +
708 + if (is_wp_error($auth_response)) {
709 + wp_send_json_error(array('message' => __('Connection failed: ', 'mxchat') . $auth_response->get_error_message()));
710 + return;
340 711 }
341 712
342 - // Handle fields stored in the 'mxchat_prompts_options' array.
343 -// Handle fields stored in the 'mxchat_prompts_options' array.
344 -if ( false !== strpos( $name, 'mxchat_prompts_options[' ) ) {
345 - // Extract the key name using regex.
346 - if ( preg_match( '/mxchat_prompts_options\[(.*?)\]/', $name, $matches ) && ! empty( $matches[1] ) ) {
347 - $key = sanitize_text_field( $matches[1] );
348 - $options = get_option( 'mxchat_prompts_options', [] );
713 + $auth_body = json_decode(wp_remote_retrieve_body($auth_response), true);
349 714
350 - // Remove Pinecone-specific settings from the main plugin handling.
351 - if ( 'mxchat_use_pinecone' === $key ) {
352 - // Optionally, you could simply ignore this field or set a default value.
353 - // For example, ensure it's always 0 or remove it entirely:
354 - // $options[ $key ] = '0';
355 - // Or, if you prefer, just return an error so that the main plugin doesn't handle it:
356 - wp_send_json_error( [ 'message' => esc_html__( 'Pinecone settings are now handled in the add-on.', 'mxchat' ) ] );
357 - } else {
358 - // For all other fields, use the existing logic.
359 - $options[ $key ] = ( $value === 'on' || $value === '1' ) ? '1' : $value;
715 + if (!isset($auth_body['ok']) || !$auth_body['ok']) {
716 + $error = $auth_body['error'] ?? 'unknown_error';
717 + $error_messages = array(
718 + 'invalid_auth' => __('Invalid bot token. Please check your token starts with xoxb-', 'mxchat'),
719 + 'not_authed' => __('No authentication token provided.', 'mxchat'),
720 + 'account_inactive' => __('The Slack workspace has been deactivated.', 'mxchat'),
721 + 'token_revoked' => __('The bot token has been revoked. Please generate a new one.', 'mxchat'),
722 + );
723 + $message = $error_messages[$error] ?? sprintf(__('Authentication failed: %s', 'mxchat'), $error);
724 + wp_send_json_error(array('message' => $message));
725 + return;
726 + }
727 +
728 + $team_name = $auth_body['team'] ?? 'Unknown Workspace';
729 + $bot_name = $auth_body['user'] ?? 'Unknown Bot';
730 +
731 + // Test 2: Check if we can list channels (tests channels:read scope)
732 + $channels_response = wp_remote_post('https://slack.com/api/conversations.list', array(
733 + 'headers' => array(
734 + 'Authorization' => 'Bearer ' . $bot_token,
735 + 'Content-Type' => 'application/json'
736 + ),
737 + 'body' => json_encode(array('limit' => 1)),
738 + 'timeout' => 15
739 + ));
740 +
741 + $channels_body = json_decode(wp_remote_retrieve_body($channels_response), true);
742 + $can_read_channels = isset($channels_body['ok']) && $channels_body['ok'];
743 +
744 + // Test 3: Check if we can create channels (tests channels:manage scope)
745 + // We'll just check the error message without actually creating
746 + $create_response = wp_remote_post('https://slack.com/api/conversations.create', array(
747 + 'headers' => array(
748 + 'Authorization' => 'Bearer ' . $bot_token,
749 + 'Content-Type' => 'application/json'
750 + ),
751 + 'body' => json_encode(array('name' => 'mxchat-test-' . time(), 'is_private' => false)),
752 + 'timeout' => 15
753 + ));
754 +
755 + $create_body = json_decode(wp_remote_retrieve_body($create_response), true);
756 +
757 + // If channel was created, delete it immediately
758 + if (isset($create_body['ok']) && $create_body['ok'] && isset($create_body['channel']['id'])) {
759 + wp_remote_post('https://slack.com/api/conversations.archive', array(
760 + 'headers' => array(
761 + 'Authorization' => 'Bearer ' . $bot_token,
762 + 'Content-Type' => 'application/json'
763 + ),
764 + 'body' => json_encode(array('channel' => $create_body['channel']['id'])),
765 + 'timeout' => 15
766 + ));
767 + $can_create_channels = true;
768 + } else {
769 + $create_error = $create_body['error'] ?? '';
770 + // name_taken means we have permission but channel exists
771 + $can_create_channels = ($create_error === 'name_taken' || (isset($create_body['ok']) && $create_body['ok']));
772 +
773 + // Check for missing scope errors
774 + if ($create_error === 'missing_scope') {
775 + $can_create_channels = false;
360 776 }
361 - update_option( 'mxchat_prompts_options', $options );
362 - wp_send_json_success( [ 'message' => esc_html__( 'Setting saved', 'mxchat' ) ] );
363 777 }
364 -}
365 778
779 + // Build result message
780 + $results = array();
781 + $results[] = sprintf(__('Workspace: %s', 'mxchat'), esc_html($team_name));
782 + $results[] = sprintf(__('Bot: %s', 'mxchat'), esc_html($bot_name));
783 + $results[] = '';
784 + $results[] = ($can_read_channels ? '✓' : '✗') . ' ' . __('channels:read - List channels', 'mxchat');
785 + $results[] = ($can_create_channels ? '✓' : '✗') . ' ' . __('channels:manage - Create channels', 'mxchat');
366 786
367 - wp_send_json_error( [ 'message' => esc_html__( 'Field not recognized', 'mxchat' ) ] );
787 + $missing_scopes = array();
788 + if (!$can_read_channels) $missing_scopes[] = 'channels:read';
789 + if (!$can_create_channels) $missing_scopes[] = 'channels:manage';
790 +
791 + if (!empty($missing_scopes)) {
792 + wp_send_json_error(array(
793 + 'message' => implode("\n", $results),
794 + 'missing_scopes' => $missing_scopes,
795 + 'partial' => true
796 + ));
797 + } else {
798 + wp_send_json_success(array(
799 + 'message' => implode("\n", $results)
800 + ));
801 + }
368 802 }
369 803
370 804 public function mxchat_display_admin_notice() {
371 805 // Success notice
@@ -390,209 +824,141 @@
390 824 delete_transient('mxchat_admin_notice_error'); // Clear the transient after displaying
391 825 }
392 826 }
393 827
828 +public function show_live_agent_disabled_banner() {
829 + $show_disabled_notice = get_option('mxchat_show_live_agent_disabled_notice', false);
394 830
395 -
396 -
397 -
398 -public function mxchat_create_admin_page() {
399 -
400 - ?>
401 - <div class="wrap mxchat-admin">
402 - <?php if (!$this->is_activated): ?>
403 - <div class="mxchat-pro-banner">
404 - <p>
405 - <?php echo esc_html__('For a limited time, get lifetime access and save $20 on MxChat Pro!', 'mxchat'); ?>
406 - <a href="https://mxchat.ai/" target="_blank"><?php echo esc_html__('Upgrade to Pro today', 'mxchat'); ?></a>
407 - </p>
408 - </div>
409 - <?php endif; ?>
410 -
411 - <div class="mxchat-agents-banner">
412 - <p>
413 - <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'); ?>
414 - </p>
415 - </div>
416 -
417 - <h2 class="mxchat-nav-tab-wrapper">
418 - <a href="#chatbot" class="mxchat-nav-tab mxchat-nav-tab-active" data-tab="chatbot"><?php echo esc_html__('Chatbot', 'mxchat'); ?></a>
419 - <a href="#embed" class="mxchat-nav-tab" data-tab="embed"><?php echo esc_html__('Integrations', 'mxchat'); ?></a>
420 - <a href="#general" class="mxchat-nav-tab" data-tab="general"><?php echo esc_html__('FAQ', 'mxchat'); ?></a>
421 - </h2>
422 -
423 - <div id="chatbot" class="mxchat-tab-content active">
424 - <div class="mxchat-autosave-section">
425 - <?php do_settings_sections('mxchat-chatbot'); ?>
831 + if ($show_disabled_notice) {
832 + ?>
833 + <div class="mxchat-live-agent-disabled-notice" id="mxchat-disabled-notice">
834 + <div class="mxchat-pro-notification">
835 + <button type="button" class="mxchat-dismiss-btn" onclick="dismissLiveAgentNotice()" aria-label="Dismiss notification">
836 + <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
837 + <line x1="18" y1="6" x2="6" y2="18"></line>
838 + <line x1="6" y1="6" x2="18" y2="18"></line>
839 + </svg>
840 + </button>
841 + <div class="mxchat-live-agent-content">
842 + <h3>🔧 Live Agent Integration Updated!</h3>
843 + <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>
844 + </div>
426 845 </div>
427 846 </div>
847 + <?php
848 + }
849 +}
850 +public function mxchat_create_admin_page() {
851 + $this->add_live_agent_nonce();
852 + $this->add_theme_migration_nonce();
428 853
429 - <div id="embed" class="mxchat-tab-content">
430 - <div class="mxchat-autosave-section">
854 + // Include and render the new sidebar-based settings page
855 + require_once plugin_dir_path(__FILE__) . 'admin-settings-page.php';
856 + mxchat_render_settings_page($this);
857 +}
431 858
432 - <div class="mxchat-settings-section">
433 - <h2><?php echo esc_html__('Loops Settings', 'mxchat'); ?></h2>
434 - <table class="form-table">
435 - <?php do_settings_fields('mxchat-embed', 'mxchat_loops_section'); ?>
436 - </table>
437 - </div>
859 +public function dismiss_live_agent_notice() {
860 + // Add debugging
861 + //error_log('dismiss_live_agent_notice called');
862 + //error_log('POST data: ' . print_r($_POST, true));
438 863
439 - <div class="section-divider"></div>
864 + // Verify nonce
865 + if (!wp_verify_nonce($_POST['nonce'], 'dismiss_live_agent_notice')) {
866 + //error_log('Nonce verification failed');
867 + wp_die('Security check failed');
868 + }
440 869
441 - <div class="mxchat-settings-section">
442 - <h2><?php echo esc_html__('Brave Search Settings', 'mxchat'); ?></h2>
443 - <table class="form-table">
444 - <?php do_settings_fields('mxchat-embed', 'mxchat_brave_section'); ?>
445 - </table>
446 - </div>
870 + // Remove the notice flag
871 + $deleted = delete_option('mxchat_show_live_agent_disabled_notice');
872 + //error_log('Option deleted: ' . ($deleted ? 'yes' : 'no'));
447 873
448 - <div class="section-divider"></div>
874 + wp_send_json_success();
875 +}
876 +public function add_live_agent_nonce() {
877 + if (get_option('mxchat_show_live_agent_disabled_notice', false)) {
878 + // Make sure your admin script is enqueued and localize the data
879 + wp_localize_script('mxchat-admin-js', 'mxchatLiveAgent', array(
880 + 'nonce' => wp_create_nonce('dismiss_live_agent_notice'),
881 + 'ajaxurl' => admin_url('admin-ajax.php')
882 + ));
883 + }
884 +}
449 885
450 - <div class="mxchat-settings-section">
451 - <h2><?php echo esc_html__('Toolbar Settings & Intents', 'mxchat'); ?></h2>
452 - <table class="form-table">
453 - <?php do_settings_fields('mxchat-embed', 'mxchat_pdf_intent_section'); ?>
454 - </table>
455 - </div>
886 +/**
887 + * Show theme migration notice for Pro users with AI-generated themes
888 + * Only shown once - dismissible and stored in options
889 + */
890 +public function show_theme_migration_banner() {
891 + // Only show if Pro is activated
892 + if (!$this->is_activated) {
893 + return;
894 + }
456 895
457 - <div class="section-divider"></div>
896 + // Check if notice should be shown
897 + $show_notice = get_option('mxchat_show_theme_migration_notice', false);
458 898
459 - <!-- Live Agent Settings Section -->
460 - <div class="mxchat-settings-section">
461 - <h2><?php echo esc_html__('Live Agent Settings', 'mxchat'); ?></h2>
462 - <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>
463 - <table class="form-table">
464 - <?php do_settings_fields('mxchat-embed', 'mxchat_live_agent_section'); ?>
465 - </table>
899 + if ($show_notice) {
900 + ?>
901 + <div class="mxchat-theme-migration-notice" id="mxchat-theme-migration-notice">
902 + <div class="mxchat-pro-notification">
903 + <button type="button" class="mxchat-dismiss-btn" onclick="dismissThemeMigrationNotice()" aria-label="Dismiss notification">
904 + <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
905 + <line x1="18" y1="6" x2="6" y2="18"></line>
906 + <line x1="6" y1="6" x2="18" y2="18"></line>
907 + </svg>
908 + </button>
909 + <div class="mxchat-theme-migration-content">
910 + <h3>🎨 AI Theme Migration Required</h3>
911 + <p>If you're using an AI-generated chatbot theme, you'll need to migrate it to match the new CSS structure. Go to <strong>Theme Settings</strong>, select your theme from the sidebar, and click the <strong>Migrate</strong> button.</p>
466 912 </div>
467 913 </div>
468 914 </div>
915 + <?php
916 + }
917 +}
469 918
919 +/**
920 + * Dismiss theme migration notice via AJAX
921 + */
922 +public function dismiss_theme_migration_notice() {
923 + // Verify nonce
924 + if (!wp_verify_nonce($_POST['nonce'], 'dismiss_theme_migration_notice')) {
925 + wp_die('Security check failed');
926 + }
470 927
471 - <div id="general" class="mxchat-tab-content">
472 - <?php do_settings_sections('mxchat-general'); ?>
473 - <p>
474 - <?php echo esc_html__('If you’re having trouble with setup or getting the responses you need, we encourage you to review our', 'mxchat'); ?>
475 - <a href="https://mxchat.ai/documentation/" target="_blank" rel="noopener noreferrer"><?php echo esc_html__('documentation', 'mxchat'); ?></a> <?php echo esc_html__('or', 'mxchat'); ?>
476 - <a href="https://wordpress.org/support/plugin/mxchat-basic/" target="_blank" rel="noopener noreferrer"><?php echo esc_html__('create a support ticket', 'mxchat'); ?></a>.
477 - </p>
478 - <p>
479 - <?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>.
480 - </p>
928 + // Remove the notice flag
929 + delete_option('mxchat_show_theme_migration_notice');
481 930
482 - <div class="faq-item">
483 - <h3><?php echo esc_html__('How does the Claude API integration work?', 'mxchat'); ?></h3>
484 - <p>
485 - <?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'); ?>
486 - </p>
487 - <p>
488 - <?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'); ?>
489 - </p>
490 - <p>
491 - <?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>.
492 - </p>
493 - </div>
931 + wp_send_json_success();
932 +}
494 933
495 - <div class="faq-item">
496 - <h3><?php echo esc_html__('How does the X.AI API integration work?', 'mxchat'); ?></h3>
497 - <p>
498 - <?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'); ?>
499 - </p>
500 - <p>
501 - <?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>.
502 - </p>
503 - </div>
504 -
505 - <div class="faq-item">
506 - <h3><?php echo esc_html__('Do I need an OpenAI API key to use the chatbot?', 'mxchat'); ?></h3>
507 - <p>
508 - <?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'); ?>
509 - </p>
510 - <p>
511 - <?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'); ?>
512 - </p>
513 - </div>
514 -
515 - <div class="faq-item">
516 - <h3><?php echo esc_html__('How do I add the chatbot to my site?', 'mxchat'); ?></h3>
517 - <p>
518 - <?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'); ?>
519 - </p>
520 - </div>
521 -
522 - <div class="faq-item">
523 - <h3><?php echo esc_html__('How does the chatbot use my content to generate responses?', 'mxchat'); ?></h3>
524 - <p>
525 - <?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'); ?>
526 - </p>
527 - </div>
528 -
529 - <div class="faq-item">
530 - <h3><?php echo esc_html__('Why does the chatbot sometimes make up links or information?', 'mxchat'); ?></h3>
531 - <p>
532 - <?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'); ?>
533 - </p>
534 - </div>
535 -
536 - <div class="faq-item">
537 - <h3><?php echo esc_html__('How do intents work in MxChat?', 'mxchat'); ?></h3>
538 - <p>
539 - <?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>.
540 - </p>
541 - </div>
542 -
543 - <div class="faq-item">
544 - <h3><?php echo esc_html__('How does the Complianz integration work?', 'mxchat'); ?></h3>
545 - <p>
546 - <?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'); ?>
547 - </p>
548 - </div>
549 -
550 - <div class="faq-item">
551 - <h3><?php echo esc_html__('How does the WooCommerce integration work?', 'mxchat'); ?></h3>
552 - <p>
553 - <?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'); ?>
554 - </p>
555 - </div>
556 -
557 - <div class="faq-item">
558 - <h3><?php echo esc_html__('Why isn\'t my chatbot responding as expected?', 'mxchat'); ?></h3>
559 - <p>
560 - <?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'); ?>
561 - </p>
562 - <p>
563 - <?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'); ?>
564 - </p>
565 - </div>
566 -
567 - <div class="faq-item">
568 - <h3><?php echo esc_html__('What is Loops, and how do I get an API key?', 'mxchat'); ?></h3>
569 - <p>
570 - <?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'); ?>
571 - </p>
572 - </div>
573 -
574 - </div>
575 - </div>
576 - <?php
934 +/**
935 + * Add nonce for theme migration notice dismiss
936 + */
937 +public function add_theme_migration_nonce() {
938 + if (get_option('mxchat_show_theme_migration_notice', false) && $this->is_activated) {
939 + wp_localize_script('mxchat-admin-js', 'mxchatThemeMigration', array(
940 + 'nonce' => wp_create_nonce('dismiss_theme_migration_notice'),
941 + 'ajaxurl' => admin_url('admin-ajax.php')
942 + ));
943 + }
577 944 }
578 945
579 -
580 946 public function mxchat_create_transcripts_page() {
581 947 global $wpdb;
582 948 $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
583 -
949 +
584 950 // Get basic stats
585 - $total_chats = $wpdb->get_var("SELECT COUNT(DISTINCT session_id) FROM $table_name");
586 - $total_messages = $wpdb->get_var("SELECT COUNT(*) FROM $table_name");
587 -
951 + $total_chats = $wpdb->get_var("SELECT COUNT(DISTINCT session_id) FROM $table_name") ?: 0;
952 + $total_messages = $wpdb->get_var("SELECT COUNT(*) FROM $table_name") ?: 0;
953 +
588 954 // Count unique users with detailed breakdown
589 955 $total_users = $wpdb->get_var("
590 - SELECT COUNT(DISTINCT
591 - CASE
956 + SELECT COUNT(DISTINCT
957 + CASE
592 958 WHEN user_email != '' AND user_email IS NOT NULL THEN user_email
593 959 WHEN user_id != 0 THEN CONCAT('user_', user_id)
594 - WHEN user_identifier NOT LIKE 'Tech-Savvy User'
960 + WHEN user_identifier NOT LIKE 'Tech-Savvy User'
595 961 AND user_identifier NOT LIKE 'Detail-Oriented User'
596 962 AND user_identifier NOT LIKE 'Language Learner'
597 963 AND user_identifier NOT LIKE 'Casual Browser'
598 964 AND user_identifier NOT LIKE 'Policy Enforcer'
@@ -600,11 +966,11 @@
600 966 AND user_identifier NOT LIKE 'Loyalty Member'
601 967 AND user_identifier NOT LIKE 'Gift Buyer'
602 968 AND user_identifier NOT LIKE 'Parent or Caregiver'
603 969 THEN user_identifier
604 - ELSE session_id
970 + ELSE session_id
605 971 END
606 - )
972 + )
607 973 FROM $table_name
608 974 WHERE role != 'assistant'
609 975 ");
610 976
@@ -609,16 +975,16 @@
609 975 ");
610 976
611 977 // Get user type breakdown
612 978 $registered_users = $wpdb->get_var("
613 - SELECT COUNT(DISTINCT user_email)
614 - FROM $table_name
979 + SELECT COUNT(DISTINCT user_email)
980 + FROM $table_name
615 981 WHERE user_email != '' AND user_email IS NOT NULL
616 982 ");
617 983
618 984 $guest_users = $wpdb->get_var("
619 - SELECT COUNT(DISTINCT user_identifier)
620 - FROM $table_name
985 + SELECT COUNT(DISTINCT user_identifier)
986 + FROM $table_name
621 987 WHERE (user_email = '' OR user_email IS NULL)
622 988 AND role != 'assistant'
623 989 AND user_identifier NOT LIKE 'Tech-Savvy User'
624 990 AND user_identifier NOT LIKE 'Detail-Oriented User'
@@ -632,10 +998,10 @@
632 998 ");
633 999
634 1000 // Get agent test messages count
635 1001 $agent_tests = $wpdb->get_var("
636 - SELECT COUNT(DISTINCT session_id)
637 - FROM $table_name
1002 + SELECT COUNT(DISTINCT session_id)
1003 + FROM $table_name
638 1004 WHERE user_identifier IN (
639 1005 'Tech-Savvy User',
640 1006 'Detail-Oriented User',
641 1007 'Language Learner',
@@ -646,86 +1012,392 @@
646 1012 'Gift Buyer',
647 1013 'Parent or Caregiver'
648 1014 )
649 1015 ");
1016 + // Get activity metrics
1017 + $today_chats = $wpdb->get_var("
1018 + SELECT COUNT(DISTINCT session_id)
1019 + FROM $table_name
1020 + WHERE DATE(timestamp) = CURDATE()
1021 + ");
1022 +
1023 + $week_chats = $wpdb->get_var("
1024 + SELECT COUNT(DISTINCT session_id)
1025 + FROM $table_name
1026 + WHERE timestamp >= DATE_SUB(NOW(), INTERVAL 7 DAY)
1027 + ");
1028 +
1029 + $month_chats = $wpdb->get_var("
1030 + SELECT COUNT(DISTINCT session_id)
1031 + FROM $table_name
1032 + WHERE timestamp >= DATE_SUB(NOW(), INTERVAL 30 DAY)
1033 + ");
1034 +
1035 + // Get daily chat data for last 7 days
1036 + $daily_stats = $wpdb->get_results("
1037 + SELECT
1038 + DATE(timestamp) as date,
1039 + COUNT(DISTINCT session_id) as chats,
1040 + COUNT(*) as messages
1041 + FROM $table_name
1042 + WHERE timestamp >= DATE_SUB(NOW(), INTERVAL 7 DAY)
1043 + GROUP BY DATE(timestamp)
1044 + ORDER BY date ASC
1045 + ");
1046 +
1047 + // Get average messages per chat
1048 + $avg_messages = $wpdb->get_var("
1049 + SELECT AVG(message_count)
1050 + FROM (
1051 + SELECT session_id, COUNT(*) as message_count
1052 + FROM $table_name
1053 + GROUP BY session_id
1054 + ) as chat_counts
1055 + ");
1056 + $avg_messages = $avg_messages ? round($avg_messages, 1) : 0;
1057 +
1058 + // Get busiest hour
1059 + $busiest_hour = $wpdb->get_row("
1060 + SELECT HOUR(timestamp) as hour, COUNT(DISTINCT session_id) as chat_count
1061 + FROM $table_name
1062 + GROUP BY HOUR(timestamp)
1063 + ORDER BY chat_count DESC
1064 + LIMIT 1
1065 + ");
1066 +
1067 + // Prepare chart data
1068 + $chart_labels = array();
1069 + $chart_chats = array();
1070 + $chart_messages = array();
1071 +
1072 + // Fill last 7 days with data
1073 + for ($i = 6; $i >= 0; $i--) {
1074 + $date = date('Y-m-d', strtotime("-$i days"));
1075 + $day_name = date('D', strtotime("-$i days"));
1076 + $chart_labels[] = $day_name;
1077 +
1078 + $found = false;
1079 + foreach ($daily_stats as $stat) {
1080 + if ($stat->date === $date) {
1081 + $chart_chats[] = (int)$stat->chats;
1082 + $chart_messages[] = (int)$stat->messages;
1083 + $found = true;
1084 + break;
1085 + }
1086 + }
1087 + if (!$found) {
1088 + $chart_chats[] = 0;
1089 + $chart_messages[] = 0;
1090 + }
1091 + }
1092 +
1093 + // Prepare page data for the template
1094 + $page_data = array(
1095 + 'total_chats' => $total_chats,
1096 + 'total_messages' => $total_messages,
1097 + 'total_users' => $total_users,
1098 + 'registered_users' => $registered_users,
1099 + 'guest_users' => $guest_users,
1100 + 'agent_tests' => $agent_tests,
1101 + 'today_chats' => $today_chats,
1102 + 'week_chats' => $week_chats,
1103 + 'month_chats' => $month_chats,
1104 + 'avg_messages' => $avg_messages,
1105 + 'busiest_hour' => $busiest_hour,
1106 + 'chart_labels' => $chart_labels,
1107 + 'chart_chats' => $chart_chats,
1108 + 'chart_messages' => $chart_messages,
1109 + );
1110 +
1111 + // Include and render the new template
1112 + require_once plugin_dir_path(__FILE__) . 'admin-transcripts-page.php';
1113 + mxchat_render_transcripts_page($this, $page_data);
1114 +}
1115 +
1116 +/**
1117 + * Get chart data for transcripts page
1118 + * Used by both page render and script localization
1119 + *
1120 + * @return array Chart data with labels, chats, and messages arrays
1121 + */
1122 +private function get_transcripts_chart_data() {
1123 + global $wpdb;
1124 + $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
1125 +
1126 + // Get daily chat data for last 7 days - same query as mxchat_transcripts_page()
1127 + $daily_stats = $wpdb->get_results("
1128 + SELECT
1129 + DATE(timestamp) as date,
1130 + COUNT(DISTINCT session_id) as chats,
1131 + COUNT(*) as messages
1132 + FROM $table_name
1133 + WHERE timestamp >= DATE_SUB(NOW(), INTERVAL 7 DAY)
1134 + GROUP BY DATE(timestamp)
1135 + ORDER BY date ASC
1136 + ");
1137 +
1138 + // Prepare chart data
1139 + $chart_labels = array();
1140 + $chart_chats = array();
1141 + $chart_messages = array();
1142 +
1143 + // Fill last 7 days with data - same logic as mxchat_transcripts_page()
1144 + for ($i = 6; $i >= 0; $i--) {
1145 + $date = date('Y-m-d', strtotime("-$i days"));
1146 + $day_name = date('D', strtotime("-$i days"));
1147 + $chart_labels[] = $day_name;
1148 +
1149 + $found = false;
1150 + if ($daily_stats) {
1151 + foreach ($daily_stats as $stat) {
1152 + if ($stat->date === $date) {
1153 + $chart_chats[] = (int)$stat->chats;
1154 + $chart_messages[] = (int)$stat->messages;
1155 + $found = true;
1156 + break;
1157 + }
1158 + }
1159 + }
1160 + if (!$found) {
1161 + $chart_chats[] = 0;
1162 + $chart_messages[] = 0;
1163 + }
1164 + }
1165 +
1166 + return array(
1167 + 'labels' => $chart_labels,
1168 + 'chats' => $chart_chats,
1169 + 'messages' => $chart_messages
1170 + );
1171 +}
1172 +
1173 +public function mxchat_transcripts_notification_section_callback() {
1174 + 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>';
1175 +}
1176 +public function mxchat_enable_notifications_callback() {
1177 + $options = get_option('mxchat_transcripts_options', array());
1178 + $enabled = isset($options['mxchat_enable_notifications']) ? $options['mxchat_enable_notifications'] : 0;
650 1179 ?>
651 - <div class="wrap mxchat-transcripts-wrapper">
652 - <!-- Hero Section -->
653 - <div class="mxchat-transcripts-hero">
654 - <h1 class="mxchat-main-title">
655 - Chat <span class="mxchat-gradient-text">Transcripts</span>
656 - </h1>
657 - <p class="mxchat-hero-subtitle">
658 - <?php esc_html_e('Review and manage your chatbot conversations with detailed message history.', 'mxchat'); ?>
659 - </p>
660 - </div>
661 - <div class="mxchat-content">
662 - <!-- Stats Cards -->
663 - <div class="mxchat-stats-grid">
664 - <div class="mxchat-stat-card">
665 - <div class="stat-icon">💬</div>
666 - <div class="stat-content">
667 - <span class="stat-value"><?php echo esc_html($total_chats); ?></span>
668 - <span class="stat-label"><?php esc_html_e('Total Chats', 'mxchat'); ?></span>
669 - </div>
670 - </div>
671 - <div class="mxchat-stat-card">
672 - <div class="stat-icon">📝</div>
673 - <div class="stat-content">
674 - <span class="stat-value"><?php echo esc_html($total_messages); ?></span>
675 - <span class="stat-label"><?php esc_html_e('Total Messages', 'mxchat'); ?></span>
676 - </div>
677 - </div>
678 - <div class="mxchat-stat-card">
679 - <div class="stat-icon">👥</div>
680 - <div class="stat-content">
681 - <span class="stat-value"><?php echo esc_html($total_users); ?></span>
682 - <span class="stat-label"><?php esc_html_e('Unique Users', 'mxchat'); ?></span>
683 - <span class="stat-sublabel">
684 - <?php
685 - echo sprintf(
686 - esc_html__('%d registered, %d guests, %d agent tests', 'mxchat'),
687 - $registered_users,
688 - $guest_users,
689 - $agent_tests
690 - );
691 - ?>
692 - </span>
693 - </div>
694 - </div>
695 - </div>
696 - <!-- Search and Filter Controls -->
697 - <div class="mxchat-controls-wrapper">
698 - <div class="mxchat-search-box">
699 - <input type="text" id="mxchat-search-transcripts"
700 - placeholder="<?php esc_attr_e('Search transcripts...', 'mxchat'); ?>"
701 - class="regular-text">
702 - </div>
703 - <form id="mxchat-delete-form" method="post">
704 - <?php wp_nonce_field('mxchat_delete_chat_history', 'mxchat_delete_chat_nonce'); ?>
705 - <div class="mxchat-controls">
706 - <button type="button" id="mxchat-export-transcripts" class="mxchat-action-button">
707 - <span class="dashicons dashicons-download"></span>
708 - <?php esc_html_e('Export All Chats', 'mxchat'); ?>
709 - </button>
710 - <button type="button" id="mxchat-select-all-transcripts" class="mxchat-select-button">
711 - <span class="dashicons dashicons-yes-alt"></span>
712 - <span class="button-text"><?php esc_html_e('Select All', 'mxchat'); ?></span>
713 - </button>
714 - <button type="submit" class="button delete-chats-button">
715 - <span class="dashicons dashicons-trash"></span>
716 - <?php esc_html_e('Delete Selected', 'mxchat'); ?>
717 - </button>
718 - </div>
719 - </form>
720 - </div>
721 - <!-- Transcripts Container -->
722 - <div id="mxchat-transcripts"></div>
723 - </div>
724 - </div>
1180 + <label for="mxchat_enable_notifications">
1181 + <input type="checkbox" id="mxchat_enable_notifications"
1182 + name="mxchat_transcripts_options[mxchat_enable_notifications]"
1183 + value="1" <?php checked(1, $enabled); ?>>
1184 + <?php esc_html_e('Send email notification when a new chat session starts', 'mxchat'); ?>
1185 + </label>
1186 + <p class="description">
1187 + <?php esc_html_e('Enable this option to receive email notifications for new chat sessions.', 'mxchat'); ?>
1188 + </p>
725 1189 <?php
726 1190 }
1191 +public function mxchat_notification_email_callback() {
1192 + $options = get_option('mxchat_transcripts_options', array());
1193 + $email = isset($options['mxchat_notification_email']) ? $options['mxchat_notification_email'] : get_option('admin_email');
1194 + ?>
1195 + <input type="email" id="mxchat_notification_email"
1196 + name="mxchat_transcripts_options[mxchat_notification_email]"
1197 + value="<?php echo esc_attr($email); ?>"
1198 + class="regular-text">
1199 + <p class="description">
1200 + <?php esc_html_e('Enter the email address where notifications should be sent. Defaults to the admin email address.', 'mxchat'); ?>
1201 + </p>
1202 + <?php
1203 +}
727 1204
1205 +public function mxchat_auto_delete_transcripts_callback() {
1206 + $options = get_option('mxchat_transcripts_options', array());
1207 + $interval = isset($options['mxchat_auto_delete_transcripts']) ? $options['mxchat_auto_delete_transcripts'] : 'never';
1208 + ?>
1209 + <select id="mxchat_auto_delete_transcripts"
1210 + name="mxchat_transcripts_options[mxchat_auto_delete_transcripts]">
1211 + <option value="never" <?php selected($interval, 'never'); ?>>
1212 + <?php esc_html_e('Never (Keep All Transcripts)', 'mxchat'); ?>
1213 + </option>
1214 + <option value="1week" <?php selected($interval, '1week'); ?>>
1215 + <?php esc_html_e('After 1 Week', 'mxchat'); ?>
1216 + </option>
1217 + <option value="2weeks" <?php selected($interval, '2weeks'); ?>>
1218 + <?php esc_html_e('After 2 Weeks', 'mxchat'); ?>
1219 + </option>
1220 + <option value="1month" <?php selected($interval, '1month'); ?>>
1221 + <?php esc_html_e('After 1 Month', 'mxchat'); ?>
1222 + </option>
1223 + </select>
1224 + <p class="description">
1225 + <?php esc_html_e('Automatically delete old chat transcripts after the selected time period. This helps manage database size and privacy.', 'mxchat'); ?>
1226 + </p>
1227 + <?php
1228 +}
1229 +
1230 +public function mxchat_auto_email_transcript_callback() {
1231 + $options = get_option('mxchat_transcripts_options', array());
1232 + $enabled = isset($options['mxchat_auto_email_transcript_enabled']) ? $options['mxchat_auto_email_transcript_enabled'] : 0;
1233 + $delay = isset($options['mxchat_auto_email_transcript_delay']) ? $options['mxchat_auto_email_transcript_delay'] : '30';
1234 + $require_contact = isset($options['mxchat_auto_email_transcript_require_contact']) ? $options['mxchat_auto_email_transcript_require_contact'] : 0;
1235 + ?>
1236 + <label>
1237 + <input type="checkbox"
1238 + id="mxchat_auto_email_transcript_enabled"
1239 + name="mxchat_transcripts_options[mxchat_auto_email_transcript_enabled]"
1240 + value="1"
1241 + <?php checked($enabled, 1); ?>>
1242 + <?php esc_html_e('Enable Auto-Email of Full Transcript', 'mxchat'); ?>
1243 + </label>
1244 + <br><br>
1245 + <label for="mxchat_auto_email_transcript_delay">
1246 + <?php esc_html_e('Send transcript after:', 'mxchat'); ?>
1247 + </label>
1248 + <select id="mxchat_auto_email_transcript_delay"
1249 + name="mxchat_transcripts_options[mxchat_auto_email_transcript_delay]">
1250 + <option value="15" <?php selected($delay, '15'); ?>>
1251 + <?php esc_html_e('15 minutes', 'mxchat'); ?>
1252 + </option>
1253 + <option value="30" <?php selected($delay, '30'); ?>>
1254 + <?php esc_html_e('30 minutes', 'mxchat'); ?>
1255 + </option>
1256 + <option value="60" <?php selected($delay, '60'); ?>>
1257 + <?php esc_html_e('1 hour', 'mxchat'); ?>
1258 + </option>
1259 + </select>
1260 + <br><br>
1261 + <label>
1262 + <input type="checkbox"
1263 + id="mxchat_auto_email_transcript_require_contact"
1264 + name="mxchat_transcripts_options[mxchat_auto_email_transcript_require_contact]"
1265 + value="1"
1266 + <?php checked($require_contact, 1); ?>>
1267 + <?php esc_html_e('Only send if visitor provided contact info', 'mxchat'); ?>
1268 + </label>
1269 + <p class="description">
1270 + <?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'); ?>
1271 + <br>
1272 + <?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'); ?>
1273 + </p>
1274 + <?php
1275 +}
1276 +
1277 +
1278 +
1279 +public function sanitize_transcripts_options($input) {
1280 + $sanitized = array();
1281 +
1282 + $sanitized['mxchat_enable_notifications'] = isset($input['mxchat_enable_notifications']) ? 1 : 0;
1283 +
1284 + if (isset($input['mxchat_notification_email'])) {
1285 + $sanitized['mxchat_notification_email'] = sanitize_email($input['mxchat_notification_email']);
1286 + if (!is_email($sanitized['mxchat_notification_email'])) {
1287 + add_settings_error(
1288 + 'mxchat_transcripts_options',
1289 + 'invalid_email',
1290 + __('Please enter a valid email address for notifications.', 'mxchat'),
1291 + 'error'
1292 + );
1293 + $sanitized['mxchat_notification_email'] = get_option('admin_email');
1294 + }
1295 + }
1296 +
1297 + // Sanitize auto-delete setting
1298 + $valid_intervals = array('never', '1week', '2weeks', '1month');
1299 + if (isset($input['mxchat_auto_delete_transcripts'])) {
1300 + $sanitized['mxchat_auto_delete_transcripts'] = in_array($input['mxchat_auto_delete_transcripts'], $valid_intervals)
1301 + ? $input['mxchat_auto_delete_transcripts']
1302 + : 'never';
1303 + } else {
1304 + $sanitized['mxchat_auto_delete_transcripts'] = 'never';
1305 + }
1306 +
1307 + // Get old value to check if auto-delete setting changed
1308 + $old_options = get_option('mxchat_transcripts_options');
1309 + $old_interval = isset($old_options['mxchat_auto_delete_transcripts']) ? $old_options['mxchat_auto_delete_transcripts'] : 'never';
1310 +
1311 + // If the auto-delete setting changed, reschedule the cron job
1312 + if ($old_interval !== $sanitized['mxchat_auto_delete_transcripts']) {
1313 + $this->schedule_transcript_cleanup($sanitized['mxchat_auto_delete_transcripts']);
1314 + }
1315 +
1316 + // Sanitize auto-email transcript settings
1317 + $sanitized['mxchat_auto_email_transcript_enabled'] = isset($input['mxchat_auto_email_transcript_enabled']) ? 1 : 0;
1318 +
1319 + $valid_delays = array('15', '30', '60');
1320 + if (isset($input['mxchat_auto_email_transcript_delay'])) {
1321 + $sanitized['mxchat_auto_email_transcript_delay'] = in_array($input['mxchat_auto_email_transcript_delay'], $valid_delays)
1322 + ? $input['mxchat_auto_email_transcript_delay']
1323 + : '30';
1324 + } else {
1325 + $sanitized['mxchat_auto_email_transcript_delay'] = '30';
1326 + }
1327 +
1328 + // Sanitize require contact info setting
1329 + $sanitized['mxchat_auto_email_transcript_require_contact'] = isset($input['mxchat_auto_email_transcript_require_contact']) ? 1 : 0;
1330 +
1331 + return $sanitized;
1332 +}
1333 +
1334 +/**
1335 + * Schedule or unschedule the transcript cleanup cron job
1336 + */
1337 +public function schedule_transcript_cleanup($interval) {
1338 + // Clear any existing scheduled event
1339 + $timestamp = wp_next_scheduled('mxchat_cleanup_old_transcripts');
1340 + if ($timestamp) {
1341 + wp_unschedule_event($timestamp, 'mxchat_cleanup_old_transcripts');
1342 + }
1343 +
1344 + // Schedule new event if not set to "never"
1345 + if ($interval !== 'never') {
1346 + // Schedule to run daily at 3 AM
1347 + $next_run = strtotime('tomorrow 3:00 AM');
1348 + wp_schedule_event($next_run, 'daily', 'mxchat_cleanup_old_transcripts');
1349 + }
1350 +}
1351 +
1352 +/**
1353 + * Delete old transcripts based on the configured interval
1354 + */
1355 +public function cleanup_old_transcripts() {
1356 + $options = get_option('mxchat_transcripts_options', array());
1357 + $interval = isset($options['mxchat_auto_delete_transcripts']) ? $options['mxchat_auto_delete_transcripts'] : 'never';
1358 +
1359 + // If set to never, don't delete anything
1360 + if ($interval === 'never') {
1361 + return;
1362 + }
1363 +
1364 + // Calculate the cutoff date
1365 + $days = 0;
1366 + switch ($interval) {
1367 + case '1week':
1368 + $days = 7;
1369 + break;
1370 + case '2weeks':
1371 + $days = 14;
1372 + break;
1373 + case '1month':
1374 + $days = 30;
1375 + break;
1376 + default:
1377 + return; // Invalid interval, don't delete anything
1378 + }
1379 +
1380 + global $wpdb;
1381 + $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
1382 +
1383 + // Calculate the cutoff timestamp
1384 + $cutoff_date = date('Y-m-d H:i:s', strtotime("-{$days} days"));
1385 +
1386 + // Delete transcripts older than the cutoff date
1387 + $deleted = $wpdb->query(
1388 + $wpdb->prepare(
1389 + "DELETE FROM {$table_name} WHERE created_at < %s",
1390 + $cutoff_date
1391 + )
1392 + );
1393 +
1394 + // Log the cleanup action
1395 + if ($deleted !== false && $deleted > 0) {
1396 + error_log(sprintf('MXChat: Auto-deleted %d transcripts older than %d days', $deleted, $days));
1397 + }
1398 +}
1399 +
728 1400 public function export_chat_transcripts() {
729 1401 if (!current_user_can('manage_options')) {
730 1402 wp_die(esc_html__('You do not have sufficient permissions to access this page.', 'mxchat'));
731 1403 }
@@ -736,10 +1408,10 @@
736 1408 $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
737 1409
738 1410 // Get all transcripts ordered by session and timestamp
739 1411 $results = $wpdb->get_results(
740 - "SELECT session_id, user_email, user_identifier, role, message, timestamp
741 - FROM {$table_name}
1412 + "SELECT session_id, user_email, user_identifier, role, message, timestamp
1413 + FROM {$table_name}
742 1414 ORDER BY session_id, timestamp ASC"
743 1415 );
744 1416
745 1417 if (empty($results)) {
@@ -784,2497 +1456,2488 @@
784 1456 fclose($output);
785 1457 wp_die();
786 1458 }
787 1459
1460 +
788 1461 public function mxchat_fetch_chat_history() {
789 1462 global $wpdb;
790 1463 $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
1464 + $url_clicks_table = $wpdb->prefix . 'mxchat_url_clicks';
791 1465
792 1466 if (!current_user_can('manage_options')) {
793 - wp_die(esc_html__('You do not have sufficient permissions to view this page.', 'mxchat'));
1467 + wp_send_json_error(['message' => 'Insufficient permissions']);
1468 + wp_die();
794 1469 }
795 1470
796 - // First get unique session IDs ordered by most recent message in each session
797 - $session_ids = $wpdb->get_col(
798 - $wpdb->prepare(
799 - "SELECT DISTINCT session_id
800 - FROM {$table_name}
801 - GROUP BY session_id
802 - ORDER BY MAX(timestamp) DESC"
1471 + $page = isset($_POST['page']) ? absint($_POST['page']) : 1;
1472 + $per_page = isset($_POST['per_page']) ? absint($_POST['per_page']) : 50;
1473 + $offset = ($page - 1) * $per_page;
1474 + $search = isset($_POST['search']) ? sanitize_text_field($_POST['search']) : '';
1475 + $sort_order = isset($_POST['sort_order']) && $_POST['sort_order'] === 'asc' ? 'ASC' : 'DESC';
1476 +
1477 + // Build search condition
1478 + $search_condition = '';
1479 + $search_params = [];
1480 + if (!empty($search)) {
1481 + $search_condition = "WHERE (
1482 + session_id LIKE %s
1483 + OR user_email LIKE %s
1484 + OR user_name LIKE %s
1485 + OR user_identifier LIKE %s
1486 + OR message LIKE %s
1487 + )";
1488 + $search_params = array_fill(0, 5, '%' . $wpdb->esc_like($search) . '%');
1489 + }
1490 +
1491 + // Get total count
1492 + $count_query = !empty($search)
1493 + ? $wpdb->prepare("SELECT COUNT(DISTINCT session_id) FROM {$table_name} {$search_condition}", $search_params)
1494 + : "SELECT COUNT(DISTINCT session_id) FROM {$table_name}";
1495 + $total_sessions = (int) $wpdb->get_var($count_query);
1496 +
1497 + // Get session IDs for current page (sorted by newest or oldest)
1498 + $session_query = !empty($search)
1499 + ? $wpdb->prepare(
1500 + "SELECT DISTINCT t.session_id FROM {$table_name} t {$search_condition}
1501 + GROUP BY t.session_id ORDER BY MAX(t.timestamp) {$sort_order} LIMIT %d OFFSET %d",
1502 + array_merge($search_params, [$per_page, $offset])
803 1503 )
804 - );
1504 + : $wpdb->prepare(
1505 + "SELECT DISTINCT session_id FROM {$table_name}
1506 + GROUP BY session_id ORDER BY MAX(timestamp) {$sort_order} LIMIT %d OFFSET %d",
1507 + $per_page, $offset
1508 + );
1509 + $session_ids = $wpdb->get_col($session_query);
805 1510
1511 + $total_pages = ceil($total_sessions / $per_page);
1512 +
806 1513 if (empty($session_ids)) {
807 - wp_die(esc_html__('No chat history available.', 'mxchat'));
1514 + wp_send_json([
1515 + 'success' => true,
1516 + 'sessions' => [],
1517 + 'page' => $page,
1518 + 'total_pages' => 0,
1519 + 'total_sessions' => 0,
1520 + 'showing_start' => 0,
1521 + 'showing_end' => 0
1522 + ]);
1523 + wp_die();
808 1524 }
809 1525
810 - ob_start();
811 - echo '<div class="mxchat-transcript">';
1526 + // Check for optional columns/tables
1527 + $url_table_exists = $wpdb->get_var("SHOW TABLES LIKE '$url_clicks_table'") === $url_clicks_table;
1528 + $originating_columns_exist = !empty($wpdb->get_results("SHOW COLUMNS FROM $table_name LIKE 'originating_page_url'"));
812 1529
813 - // Iterate through sessions from newest to oldest
1530 + // Build session list data
1531 + $sessions = [];
814 1532 foreach ($session_ids as $session_id) {
815 - // Get the email associated with this session (if available)
816 - $email = $wpdb->get_var(
817 - $wpdb->prepare(
818 - "SELECT user_email
819 - FROM {$table_name}
820 - WHERE session_id = %s AND user_email != ''
821 - ORDER BY timestamp ASC
822 - LIMIT 1",
1533 + // Get session metadata
1534 + $session_data = $originating_columns_exist
1535 + ? $wpdb->get_row($wpdb->prepare(
1536 + "SELECT user_email, user_name, user_identifier, originating_page_url, originating_page_title, timestamp
1537 + FROM {$table_name} WHERE session_id = %s ORDER BY timestamp ASC LIMIT 1",
823 1538 $session_id
824 - )
825 - );
826 -
827 - // Debug: Check email retrieval
828 - if (empty($email)) {
829 - //error_log("No email found for session {$session_id}");
830 - } else {
831 - //error_log("Email for session {$session_id}: " . $email);
832 - }
833 -
834 - // Get messages for this session ordered by timestamp
835 - $messages = $wpdb->get_results(
836 - $wpdb->prepare(
837 - "SELECT * FROM {$table_name}
838 - WHERE session_id = %s
839 - ORDER BY timestamp ASC",
1539 + ))
1540 + : $wpdb->get_row($wpdb->prepare(
1541 + "SELECT user_email, user_name, user_identifier, timestamp FROM {$table_name}
1542 + WHERE session_id = %s LIMIT 1",
840 1543 $session_id
841 - )
842 - );
1544 + ));
843 1545
844 - // Start session block
845 - echo '<div class="mxchat-session">';
846 - echo '<div class="mxchat-session-header">';
847 - // Wrap checkbox and session ID in one block
848 - echo '<div class="mxchat-session-id">';
849 - echo '<input type="checkbox" name="delete_session_ids[]" value="' . esc_attr($session_id) . '"> ';
850 - echo '<strong>' . esc_html__('Session ID:', 'mxchat') . '</strong> ' . esc_html($session_id);
851 - echo '</div>';
1546 + // Get message count and latest timestamp
1547 + $message_stats = $wpdb->get_row($wpdb->prepare(
1548 + "SELECT COUNT(*) as count, MAX(timestamp) as latest FROM {$table_name} WHERE session_id = %s",
1549 + $session_id
1550 + ));
852 1551
853 - // Place email directly below the session ID block
854 - if (!empty($email)) {
855 - echo '<div class="mxchat-session-email">';
856 - echo '<strong>' . esc_html__('Email:', 'mxchat') . '</strong> ' . esc_html($email);
857 - echo '</div>';
858 - }
859 - echo '</div>';
1552 + // Get first user message as preview
1553 + $first_user_msg = $wpdb->get_var($wpdb->prepare(
1554 + "SELECT message FROM {$table_name} WHERE session_id = %s AND role = 'user' ORDER BY timestamp ASC LIMIT 1",
1555 + $session_id
1556 + ));
1557 + $preview = $first_user_msg ? wp_trim_words(wp_strip_all_tags(stripslashes($first_user_msg)), 12, '...') : 'No messages';
860 1558
861 - echo '<div class="mxchat-messages">';
1559 + // Build display name
1560 + $user_email = !empty($session_data->user_email) ? $session_data->user_email : '';
1561 + $user_name = !empty($session_data->user_name) ? $session_data->user_name : '';
1562 + $user_identifier = !empty($session_data->user_identifier) ? $session_data->user_identifier : 'Guest';
862 1563
863 - // Display messages for this session
864 - foreach ($messages as $transcript) {
865 - $formatted_timestamp = date_i18n('F j, Y g:i a', strtotime($transcript->timestamp));
1564 + $display_name = $user_name ?: ($user_email ? explode('@', $user_email)[0] : $user_identifier);
1565 + $display_sub = $user_email ?: ('ID: ' . $user_identifier);
866 1566
867 - // Determine message styling
868 - switch ($transcript->role) {
869 - case 'assistant':
870 - case 'bot':
871 - $message_class = 'bot-message';
872 - $display_role = esc_html__('Chatbot', 'mxchat');
873 - break;
874 - case 'user':
875 - $message_class = 'user-message';
876 - $display_role = !empty($transcript->user_identifier)
877 - ? sanitize_text_field($transcript->user_identifier)
878 - : esc_html__('User', 'mxchat');
879 - break;
880 - case 'agent':
881 - $message_class = 'agent-message';
882 - $display_role = esc_html__('Agent', 'mxchat');
883 - break;
884 - default:
885 - $message_class = 'unknown-message';
886 - $display_role = esc_html__('Unknown', 'mxchat');
887 - }
1567 + // Format time - show relative for recent, date for older
1568 + $timestamp = strtotime($message_stats->latest . ' UTC');
1569 + $now = time();
1570 + $diff = $now - $timestamp;
1571 + if ($diff < 3600) {
1572 + $time_display = floor($diff / 60) . 'm ago';
1573 + } elseif ($diff < 86400) {
1574 + $time_display = floor($diff / 3600) . 'h ago';
1575 + } elseif ($diff < 604800) {
1576 + $time_display = floor($diff / 86400) . 'd ago';
1577 + } else {
1578 + $time_display = wp_date('M j', $timestamp);
1579 + }
888 1580
889 - // Process message content
890 - $message_content = wp_kses(
891 - stripslashes($transcript->message),
892 - [
893 - 'b' => [], 'strong' => [], 'i' => [], 'em' => [], 'u' => [],
894 - 'br' => [], 'p' => [], 'ul' => [], 'ol' => [], 'li' => [],
895 - 'a' => ['href' => [], 'title' => []]
896 - ]
897 - );
898 - $message_content = nl2br($message_content);
899 -
900 - // Render message
901 - echo '<div class="mxchat-message ' . esc_attr($message_class) . '">';
902 - echo '<div class="mxchat-message-header">' . esc_html($display_role) . '</div>';
903 - echo '<div class="mxchat-message-content">' . $message_content . '</div>';
904 - echo '<div class="mxchat-timestamp">' . esc_html($formatted_timestamp) . '</div>';
905 - echo '</div>';
1581 + // Get initials for avatar
1582 + $initials = strtoupper(substr($display_name, 0, 2));
1583 + if (strlen($display_name) > 2 && strpos($display_name, ' ') !== false) {
1584 + $parts = explode(' ', $display_name);
1585 + $initials = strtoupper(substr($parts[0], 0, 1) . substr(end($parts), 0, 1));
906 1586 }
907 1587
908 - // Close session block
909 - echo '</div></div>';
1588 + $sessions[] = [
1589 + 'session_id' => $session_id,
1590 + 'display_name' => $display_name,
1591 + 'display_sub' => $display_sub,
1592 + 'initials' => $initials,
1593 + 'preview' => $preview,
1594 + 'message_count' => (int) $message_stats->count,
1595 + 'time_display' => $time_display,
1596 + 'timestamp' => $message_stats->latest
1597 + ];
910 1598 }
911 1599
912 - echo '</div>';
913 - $output = ob_get_clean();
914 - echo $output;
1600 + wp_send_json([
1601 + 'success' => true,
1602 + 'sessions' => $sessions,
1603 + 'page' => $page,
1604 + 'total_pages' => $total_pages,
1605 + 'total_sessions' => $total_sessions,
1606 + 'showing_start' => $offset + 1,
1607 + 'showing_end' => min($offset + $per_page, $total_sessions)
1608 + ]);
915 1609 wp_die();
916 1610 }
917 1611
1612 +/**
1613 + * Fetch single conversation details for split-panel view
1614 + */
1615 +public function mxchat_fetch_conversation() {
1616 + global $wpdb;
1617 + $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
1618 + $url_clicks_table = $wpdb->prefix . 'mxchat_url_clicks';
918 1619
1620 + if (!current_user_can('manage_options')) {
1621 + wp_send_json_error(['message' => 'Insufficient permissions']);
1622 + wp_die();
1623 + }
919 1624
920 -public function mxchat_create_prompts_page() {
921 - global $wpdb;
922 - $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
1625 + $session_id = isset($_POST['session_id']) ? sanitize_text_field($_POST['session_id']) : '';
1626 + if (empty($session_id)) {
1627 + wp_send_json_error(['message' => 'No session ID provided']);
1628 + wp_die();
1629 + }
923 1630
924 - // Display success message if all prompts were deleted
925 - if (isset($_GET['all_deleted']) && $_GET['all_deleted'] === 'true') {
926 - echo '<div class="notice notice-success is-dismissible"><p>' . esc_html__('All knowledge has been deleted successfully.', 'mxchat') . '</p></div>';
927 - }
1631 + // Check for optional columns/tables
1632 + $url_table_exists = $wpdb->get_var("SHOW TABLES LIKE '$url_clicks_table'") === $url_clicks_table;
1633 + $originating_columns_exist = !empty($wpdb->get_results("SHOW COLUMNS FROM $table_name LIKE 'originating_page_url'"));
928 1634
929 - // Set up pagination and search query
930 - $nonce = isset($_GET['_wpnonce']) ? sanitize_text_field($_GET['_wpnonce']) : '';
931 - $search_query = (!empty($nonce) && wp_verify_nonce($nonce, 'mxchat_prompts_search_nonce') && isset($_GET['search'])) ? sanitize_text_field($_GET['search']) : '';
932 - $current_page = isset($_GET['paged']) ? absint($_GET['paged']) : 1;
933 - $per_page = 10;
934 - $offset = ($current_page - 1) * $per_page;
1635 + // Get session metadata
1636 + $session_data = $originating_columns_exist
1637 + ? $wpdb->get_row($wpdb->prepare(
1638 + "SELECT user_email, user_name, user_identifier, originating_page_url, originating_page_title, timestamp
1639 + FROM {$table_name} WHERE session_id = %s ORDER BY timestamp ASC LIMIT 1",
1640 + $session_id
1641 + ))
1642 + : $wpdb->get_row($wpdb->prepare(
1643 + "SELECT user_email, user_name, user_identifier, timestamp FROM {$table_name}
1644 + WHERE session_id = %s LIMIT 1",
1645 + $session_id
1646 + ));
935 1647
936 - // Modify query to handle search input
937 - $sql_search = "";
938 - if ($search_query) {
939 - $sql_search = $wpdb->prepare("WHERE article_content LIKE %s", '%' . $wpdb->esc_like($search_query) . '%');
1648 + if (!$session_data) {
1649 + wp_send_json_error(['message' => 'Session not found']);
1650 + wp_die();
940 1651 }
941 1652
942 - // Retrieve total number of prompts, considering search filter
943 - $total_prompts = $wpdb->get_var("SELECT COUNT(*) FROM {$table_name} {$sql_search}");
944 - $total_pages = ceil($total_prompts / $per_page);
1653 + // Get clicked URLs
1654 + $clicked_urls = [];
1655 + if ($url_table_exists) {
1656 + $url_clicks = $wpdb->get_results($wpdb->prepare(
1657 + "SELECT DISTINCT clicked_url FROM {$url_clicks_table}
1658 + WHERE session_id = %s ORDER BY click_timestamp ASC",
1659 + $session_id
1660 + ));
1661 + foreach ($url_clicks as $click) {
1662 + $clicked_urls[] = $click->clicked_url;
1663 + }
1664 + }
945 1665
946 - // Retrieve prompts from the database
947 - $prompts = $wpdb->get_results(
948 - $wpdb->prepare(
949 - "SELECT * FROM {$table_name} {$sql_search} ORDER BY timestamp DESC LIMIT %d OFFSET %d",
950 - $per_page,
951 - $offset
952 - )
953 - );
1666 + // Get all messages
1667 + $messages = $wpdb->get_results($wpdb->prepare(
1668 + "SELECT * FROM {$table_name} WHERE session_id = %s ORDER BY timestamp ASC",
1669 + $session_id
1670 + ));
954 1671
955 - // Add the pagination links generation here
956 - $page_links = paginate_links(array(
957 - 'base' => add_query_arg(array(
958 - 'paged' => '%#%',
959 - 'search' => urlencode($search_query),
960 - '_wpnonce' => wp_create_nonce('mxchat_prompts_search_nonce')
961 - ), admin_url('admin.php?page=mxchat-prompts')),
962 - 'format' => '',
963 - 'prev_text' => __('&laquo; Previous', 'mxchat'),
964 - 'next_text' => __('Next &raquo;', 'mxchat'),
965 - 'total' => $total_pages,
966 - 'current' => $current_page,
967 - ));
1672 + // Build user info
1673 + $user_email = !empty($session_data->user_email) ? $session_data->user_email : '';
1674 + $user_name = !empty($session_data->user_name) ? $session_data->user_name : '';
1675 + $user_identifier = !empty($session_data->user_identifier) ? $session_data->user_identifier : 'Guest';
968 1676
969 - // Retrieve processing statuses
970 - $pdf_url = get_transient('mxchat_last_pdf_url');
971 - $sitemap_url = get_transient('mxchat_last_sitemap_url');
972 - $pdf_status = $pdf_url ? $this->get_pdf_processing_status($pdf_url) : false;
973 - $sitemap_status = $sitemap_url ? $this->get_sitemap_processing_status($sitemap_url) : false;
1677 + $display_name = $user_name ?: ($user_email ? explode('@', $user_email)[0] : $user_identifier);
1678 + $display_sub = $user_email ?: ('ID: ' . $user_identifier);
974 1679
975 - if ($pdf_status && $pdf_status['status'] === 'complete') {
976 - delete_transient('mxchat_last_pdf_url');
977 - $pdf_status = false;
1680 + // Get initials
1681 + $initials = strtoupper(substr($display_name, 0, 2));
1682 + if (strlen($display_name) > 2 && strpos($display_name, ' ') !== false) {
1683 + $parts = explode(' ', $display_name);
1684 + $initials = strtoupper(substr($parts[0], 0, 1) . substr(end($parts), 0, 1));
978 1685 }
979 - if ($sitemap_status && $sitemap_status['status'] === 'complete') {
980 - delete_transient('mxchat_last_sitemap_url');
981 - $sitemap_status = false;
1686 +
1687 + // Page info
1688 + $page_url = $originating_columns_exist && !empty($session_data->originating_page_url) ? $session_data->originating_page_url : '';
1689 + $page_title = '';
1690 + if ($page_url) {
1691 + $page_title = !empty($session_data->originating_page_title) ? $session_data->originating_page_title : parse_url($page_url, PHP_URL_PATH);
982 1692 }
983 1693
984 - $is_processing = ($pdf_status && ($pdf_status['status'] === 'processing' || $pdf_status['status'] === 'error'))
985 - || ($sitemap_status && ($sitemap_status['status'] === 'processing' || $sitemap_status['status'] === 'error'));
986 - ?>
987 - <div class="wrap mxchat-wrapper">
988 - <!-- Hero Section -->
989 - <div class="mxchat-hero">
990 - <h1 class="mxchat-main-title">
991 - <span class="mxchat-gradient-text">Knowledge Base</span> Manager
992 - </h1>
993 - <p class="mxchat-hero-subtitle">
994 - <?php esc_html_e('Enhance your AI chatbot with custom knowledge. Import, manage, and organize your content to keep responses accurate and relevant.', 'mxchat'); ?>
995 - </p>
996 - </div>
1694 + // Format messages for output
1695 + $formatted_messages = [];
1696 + foreach ($messages as $msg) {
1697 + $is_user = ($msg->role === 'user');
1698 + $is_bot = ($msg->role === 'bot' || $msg->role === 'assistant');
997 1699
998 - <div class="mxchat-content">
999 - <!-- Import Settings Card -->
1000 - <div class="mxchat-card">
1001 - <h2><?php esc_html_e('Knowledge Import Settings', 'mxchat'); ?></h2>
1002 - <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>
1700 + $content = wp_kses(
1701 + stripslashes($msg->message),
1702 + [
1703 + 'b' => [], 'strong' => [], 'i' => [], 'em' => [], 'u' => [],
1704 + 'br' => [], 'p' => [], 'ul' => [], 'ol' => [], 'li' => [],
1705 + 'a' => ['href' => [], 'title' => [], 'target' => []]
1706 + ]
1707 + );
1708 + $formatted_content = $this->format_transcript_message($content);
1003 1709
1004 - <!-- Tab Contents -->
1005 - <div class="mxchat-tab-contents">
1006 - <!-- Default Database Tab -->
1007 - <div id="default-db" class="mxchat-tab-content active">
1008 - <!-- Auto-Sync Settings -->
1009 - <div class="mxchat-settings-section">
1010 - <h3><?php esc_html_e('Auto-Sync Settings', 'mxchat'); ?></h3>
1011 - <p class="mxchat-description">
1012 - <?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'); ?>
1013 - </p>
1014 - <div class="mxchat-autosave-section"> <!-- Add this wrapper -->
1015 - <div class="mxchat-toggle-group">
1016 - <div class="mxchat-toggle-container">
1017 - <label class="mxchat-toggle-switch">
1018 - <input type="checkbox"
1019 - name="mxchat_auto_sync_posts"
1020 - class="mxchat-autosave-field"
1021 - value="1"
1022 - data-nonce="<?php echo wp_create_nonce('mxchat_prompts_setting_nonce'); ?>"
1023 - <?php checked(get_option('mxchat_auto_sync_posts', '0'), '1'); ?>>
1024 - <span class="mxchat-toggle-slider"></span>
1025 - </label>
1026 - <span class="mxchat-toggle-label">
1027 - <?php esc_html_e('Auto-sync Posts', 'mxchat'); ?>
1028 - </span>
1029 - </div>
1030 - <div class="mxchat-toggle-container">
1031 - <label class="mxchat-toggle-switch">
1032 - <input type="checkbox"
1033 - name="mxchat_auto_sync_pages"
1034 - class="mxchat-autosave-field"
1035 - value="1"
1036 - data-nonce="<?php echo wp_create_nonce('mxchat_prompts_setting_nonce'); ?>"
1037 - <?php checked(get_option('mxchat_auto_sync_pages', '0'), '1'); ?>>
1038 - <span class="mxchat-toggle-slider"></span>
1039 - </label>
1040 - <span class="mxchat-toggle-label">
1041 - <?php esc_html_e('Auto-sync Pages', 'mxchat'); ?>
1042 - </span>
1043 - </div>
1044 - </div>
1045 - </div>
1046 - </div>
1710 + $has_rag = $is_bot && !empty($msg->rag_context);
1047 1711
1048 - <!-- Import Methods -->
1049 - <div class="mxchat-import-methods">
1050 - <div class="mxchat-method-card">
1051 - <h4><?php esc_html_e('Sitemap Import', 'mxchat'); ?></h4>
1052 - <p><?php esc_html_e('Import all content from your sitemap automatically. Use a content-specific sub-sitemap, not the sitemap index.', 'mxchat'); ?></p>
1053 - </div>
1054 - <div class="mxchat-method-card">
1055 - <h4><?php esc_html_e('PDF Import', 'mxchat'); ?></h4>
1056 - <p><?php esc_html_e('Import knowledge directly from PDF documents.', 'mxchat'); ?></p>
1057 - </div>
1058 - <div class="mxchat-method-card">
1059 - <h4><?php esc_html_e('Direct URL', 'mxchat'); ?></h4>
1060 - <p><?php esc_html_e('Import content from any specific webpage.', 'mxchat'); ?></p>
1061 - </div>
1062 - <div class="mxchat-method-card">
1063 - <h4><?php esc_html_e('Direct Content', 'mxchat'); ?></h4>
1064 - <p><?php esc_html_e('Directly submit content to be vectorized.', 'mxchat'); ?></p>
1065 - </div>
1066 - </div>
1712 + $formatted_messages[] = [
1713 + 'id' => $msg->id,
1714 + 'role' => $msg->role,
1715 + 'is_user' => $is_user,
1716 + 'is_bot' => $is_bot,
1717 + 'content' => $formatted_content,
1718 + 'timestamp' => wp_date('g:i A', strtotime($msg->timestamp . ' UTC')),
1719 + 'full_timestamp' => wp_date('F j, Y g:i A', strtotime($msg->timestamp . ' UTC')),
1720 + 'has_rag' => $has_rag
1721 + ];
1722 + }
1067 1723
1068 - <!-- Import Form -->
1069 - <?php if (!$is_processing) : ?>
1070 - <div class="mxchat-import-form">
1071 - <form id="mxchat-sitemap-form" method="post"
1072 - action="<?php echo esc_url(admin_url('admin-post.php?action=mxchat_submit_sitemap')); ?>">
1073 - <?php wp_nonce_field('mxchat_submit_sitemap_action', 'mxchat_submit_sitemap_nonce'); ?>
1074 - <div class="mxchat-url-input-group">
1075 - <input type="url"
1076 - name="sitemap_url"
1077 - id="sitemap_url"
1078 - placeholder="<?php esc_attr_e('Enter Sitemap, PDF, or Webpage URL', 'mxchat'); ?>"
1079 - required />
1080 - <button type="submit"
1081 - name="submit_sitemap"
1082 - class="mxchat-button-primary">
1083 - <?php esc_html_e('Import', 'mxchat'); ?>
1084 - </button>
1085 - </div>
1086 - </form>
1087 - </div>
1088 - <?php endif; ?>
1724 + // First message timestamp for "started" display
1725 + $started = !empty($messages) ? wp_date('M j, Y g:i A', strtotime($messages[0]->timestamp . ' UTC')) : '-';
1089 1726
1090 - <!-- Processing Status -->
1091 - <?php if ($pdf_status && $pdf_status['status'] !== 'complete') : ?>
1092 - <div class="mxchat-status-card">
1093 - <div class="mxchat-status-header">
1094 - <h4><?php esc_html_e('PDF Processing Status', 'mxchat'); ?></h4>
1095 - <?php if ($is_processing) : ?>
1096 - <form method="post" class="mxchat-stop-form"
1097 - action="<?php echo esc_url(admin_url('admin-post.php?action=mxchat_stop_processing')); ?>">
1098 - <?php wp_nonce_field('mxchat_stop_processing_action', 'mxchat_stop_processing_nonce'); ?>
1099 - <button type="submit" name="stop_processing" class="mxchat-button-secondary">
1100 - <?php esc_html_e('Stop Processing', 'mxchat'); ?>
1101 - </button>
1102 - </form>
1103 - <?php endif; ?>
1104 - </div>
1105 - <div class="mxchat-progress-bar">
1106 - <div class="mxchat-progress-fill" style="width: <?php echo esc_attr($pdf_status['percentage']); ?>%"></div>
1107 - </div>
1108 - <div class="mxchat-status-details">
1109 - <p><?php printf(
1110 - esc_html__('Progress: %1$d of %2$d pages (%3$d%%)', 'mxchat'),
1111 - absint($pdf_status['processed_pages']),
1112 - absint($pdf_status['total_pages']),
1113 - absint($pdf_status['percentage'])
1114 - ); ?></p>
1115 - <p><?php printf(
1116 - esc_html__('Status: %s', 'mxchat'),
1117 - esc_html(ucfirst($pdf_status['status']))
1118 - ); ?></p>
1119 - <p><?php printf(
1120 - esc_html__('Last update: %s', 'mxchat'),
1121 - esc_html($pdf_status['last_update'])
1122 - ); ?></p>
1123 - </div>
1124 - </div>
1125 - <?php endif; ?>
1727 + wp_send_json([
1728 + 'success' => true,
1729 + 'session_id' => $session_id,
1730 + 'user' => [
1731 + 'name' => $display_name,
1732 + 'sub' => $display_sub,
1733 + 'initials' => $initials,
1734 + 'email' => $user_email,
1735 + 'identifier' => $user_identifier
1736 + ],
1737 + 'page' => [
1738 + 'url' => $page_url,
1739 + 'title' => $page_title
1740 + ],
1741 + 'clicked_urls' => $clicked_urls,
1742 + 'messages' => $formatted_messages,
1743 + 'message_count' => count($messages),
1744 + 'started' => $started
1745 + ]);
1746 + wp_die();
1747 +}
1126 1748
1127 - <?php if ($sitemap_status && $sitemap_status['status'] !== 'complete') : ?>
1128 - <div class="mxchat-status-card">
1129 - <div class="mxchat-status-header">
1130 - <h4><?php esc_html_e('Sitemap Processing Status (Refresh for update)', 'mxchat'); ?></h4>
1131 - <?php if ($is_processing) : ?>
1132 - <form method="post" class="mxchat-stop-form"
1133 - action="<?php echo esc_url(admin_url('admin-post.php?action=mxchat_stop_processing')); ?>">
1134 - <?php wp_nonce_field('mxchat_stop_processing_action', 'mxchat_stop_processing_nonce'); ?>
1135 - <button type="submit" name="stop_processing" class="mxchat-button-secondary">
1136 - <?php esc_html_e('Stop Processing', 'mxchat'); ?>
1137 - </button>
1138 - </form>
1139 - <?php endif; ?>
1140 - </div>
1141 - <div class="mxchat-progress-bar">
1142 - <div class="mxchat-progress-fill" style="width: <?php echo esc_attr($sitemap_status['percentage']); ?>%"></div>
1143 - </div>
1144 - <div class="mxchat-status-details">
1145 - <p><?php printf(
1146 - esc_html__('Progress: %1$d of %2$d URLs (%3$d%%)', 'mxchat'),
1147 - absint($sitemap_status['processed_urls']),
1148 - absint($sitemap_status['total_urls']),
1149 - absint($sitemap_status['percentage'])
1150 - ); ?></p>
1151 - <?php if (!empty($sitemap_status['error']) || !empty($sitemap_status['last_error'])) : ?>
1152 - <div class="mxchat-error-notice">
1153 - <?php if (!empty($sitemap_status['error'])) : ?>
1154 - <p class="error"><?php echo esc_html($sitemap_status['error']); ?></p>
1155 - <?php endif; ?>
1156 - <?php if (!empty($sitemap_status['last_error'])) : ?>
1157 - <p class="last-error"><?php echo esc_html__('Last error:', 'mxchat') . ' ' . esc_html($sitemap_status['last_error']); ?></p>
1158 - <?php endif; ?>
1159 - </div>
1160 - <?php endif; ?>
1161 - </div>
1162 - </div>
1163 - <?php endif; ?>
1164 - </div>
1165 1749
1750 +/**
1751 + * ALTERNATIVE: Simpler helper method using string replacement
1752 + */
1753 +private function highlight_clicked_links($message_content, $clicked_urls) {
1754 + if (empty($clicked_urls)) {
1755 + return wp_kses(
1756 + $message_content,
1757 + [
1758 + 'b' => [], 'strong' => [], 'i' => [], 'em' => [], 'u' => [],
1759 + 'br' => [], 'p' => [], 'ul' => [], 'ol' => [], 'li' => [],
1760 + 'a' => ['href' => [], 'title' => [], 'target' => [], 'class' => []]
1761 + ]
1762 + );
1763 + }
1166 1764
1167 - </div>
1168 - </div>
1765 + // First apply standard sanitization
1766 + $message_content = wp_kses(
1767 + $message_content,
1768 + [
1769 + 'b' => [], 'strong' => [], 'i' => [], 'em' => [], 'u' => [],
1770 + 'br' => [], 'p' => [], 'ul' => [], 'ol' => [], 'li' => [],
1771 + 'a' => ['href' => [], 'title' => [], 'target' => [], 'class' => []]
1772 + ]
1773 + );
1169 1774
1170 - <!-- Direct Content Submission Card -->
1171 -<div class="mxchat-card">
1172 - <div class="mxchat-card-header">
1173 - <h2><?php esc_html_e('Submit Direct Content', 'mxchat'); ?></h2>
1174 - </div>
1175 - <form id="mxchat-content-form" method="post"
1176 - action="<?php echo esc_url(admin_url('admin-post.php?action=mxchat_submit_content')); ?>">
1177 - <?php wp_nonce_field('mxchat_submit_content_action', 'mxchat_submit_content_nonce'); ?>
1178 - <div class="mxchat-form-group">
1179 - <label for="article_content"><?php esc_html_e('Content:', 'mxchat'); ?></label>
1180 - <textarea name="article_content" id="article_content" required
1181 - placeholder="<?php esc_attr_e('Enter your content here...', 'mxchat'); ?>"></textarea>
1182 - </div>
1183 - <div class="mxchat-form-group">
1184 - <label for="article_url"><?php esc_html_e('Source URL (Optional):', 'mxchat'); ?></label>
1185 - <input type="url" name="article_url" id="article_url"
1186 - placeholder="<?php esc_attr_e('Enter source URL', 'mxchat'); ?>">
1187 - </div>
1188 - <button type="submit" name="submit_content" class="mxchat-button-primary">
1189 - <?php esc_html_e('Submit Content', 'mxchat'); ?>
1190 - </button>
1191 - </form>
1192 -</div>
1775 + // Process each clicked URL
1776 + foreach ($clicked_urls as $clicked_url) {
1777 + // Try multiple patterns to catch different link formats
1778 + $patterns = [
1779 + // Standard link format
1780 + '/<a([^>]*href=["\']' . preg_quote($clicked_url, '/') . '["\'][^>]*)>/i',
1781 + // Link with trailing slash
1782 + '/<a([^>]*href=["\']' . preg_quote(rtrim($clicked_url, '/'), '/') . '\/?["\'][^>]*)>/i',
1783 + // Encoded entities version
1784 + '/<a([^>]*href=["\']' . preg_quote(htmlentities($clicked_url), '/') . '["\'][^>]*)>/i',
1785 + ];
1193 1786
1194 -<!-- Knowledge Base Table Card -->
1195 -<div class="mxchat-card">
1196 - <div class="mxchat-card-header">
1197 - <h2><?php esc_html_e('Knowledge Base', 'mxchat'); ?></h2>
1198 - <div class="mxchat-header-actions">
1199 - <!-- Search -->
1200 - <form method="get" id="knowledge-search" class="mxchat-search-form">
1201 - <?php wp_nonce_field('mxchat_prompts_search_nonce'); ?>
1202 - <input type="hidden" name="page" value="mxchat-prompts" />
1203 - <div class="mxchat-search-group">
1204 - <span class="dashicons dashicons-search"></span>
1205 - <input type="text"
1206 - name="search"
1207 - placeholder="<?php esc_attr_e('Search Knowledge', 'mxchat'); ?>"
1208 - value="<?php echo esc_attr($search_query); ?>" />
1209 - </div>
1210 - </form>
1787 + foreach ($patterns as $pattern) {
1788 + if (preg_match($pattern, $message_content)) {
1789 + $message_content = preg_replace_callback(
1790 + $pattern,
1791 + function($matches) {
1792 + $full_match = $matches[0];
1793 + $attributes = $matches[1];
1211 1794
1212 - <!-- Delete All -->
1213 - <form method="post"
1214 - action="<?php echo esc_url(admin_url('admin-post.php?action=mxchat_delete_all_prompts')); ?>"
1215 - class="mxchat-delete-form"
1216 - onsubmit="return confirm('<?php esc_attr_e('Are you sure you want to delete all knowledge? This action cannot be undone.', 'mxchat'); ?>');">
1217 - <?php wp_nonce_field('mxchat_delete_all_prompts_action', 'mxchat_delete_all_prompts_nonce'); ?>
1218 - <button type="submit" name="delete_all_prompts" class="mxchat-button-danger">
1219 - <span class="dashicons dashicons-trash"></span>
1220 - <?php esc_html_e('Delete All', 'mxchat'); ?>
1221 - </button>
1222 - </form>
1223 - </div>
1224 - </div>
1795 + // Check if it already has the class
1796 + if (strpos($full_match, 'mxchat-clicked-link') !== false) {
1797 + return $full_match;
1798 + }
1225 1799
1226 - <!-- Table -->
1227 - <div class="mxchat-table-wrapper">
1228 - <table class="mxchat-records-table">
1229 - <thead>
1230 - <tr>
1231 - <th><?php esc_html_e('ID', 'mxchat'); ?></th>
1232 - <th><?php esc_html_e('Content', 'mxchat'); ?></th>
1233 - <th><?php esc_html_e('Source', 'mxchat'); ?></th>
1234 - <th><?php esc_html_e('Actions', 'mxchat'); ?></th>
1235 - </tr>
1236 - </thead>
1237 - <tbody>
1238 - <?php if ($prompts) : ?>
1239 - <?php foreach ($prompts as $prompt) : ?>
1240 - <tr id="prompt-<?php echo esc_attr($prompt->id); ?>">
1241 - <td><?php echo esc_html($prompt->id); ?></td>
1242 - <td class="mxchat-content-cell">
1243 - <div class="content-view">
1244 - <?php echo wp_kses_post(wpautop(esc_textarea($prompt->article_content))); ?>
1245 - </div>
1246 - <textarea class="content-edit" style="display:none;">
1247 - <?php echo esc_textarea($prompt->article_content); ?>
1248 - </textarea>
1249 - </td>
1250 - <td class="mxchat-url-cell">
1251 - <?php if (!empty($prompt->source_url)) : ?>
1252 - <a href="<?php echo esc_url($prompt->source_url); ?>" target="_blank">
1253 - <span class="dashicons dashicons-external"></span>
1254 - <?php esc_html_e('View Source', 'mxchat'); ?>
1255 - </a>
1256 - <?php else : ?>
1257 - <span class="mxchat-na"><?php esc_html_e('N/A', 'mxchat'); ?></span>
1258 - <?php endif; ?>
1259 - </td>
1260 - <td class="mxchat-actions-cell">
1261 - <button class="mxchat-button-icon edit-button"
1262 - data-id="<?php echo esc_attr($prompt->id); ?>">
1263 - <span class="dashicons dashicons-edit"></span>
1264 - </button>
1265 - <button class="mxchat-button-icon save-button"
1266 - data-id="<?php echo esc_attr($prompt->id); ?>"
1267 - style="display:none;"
1268 - data-nonce="<?php echo wp_create_nonce('mxchat_save_inline_nonce'); ?>">
1269 - <span class="dashicons dashicons-saved"></span>
1270 - </button>
1271 - <a href="<?php echo esc_url(admin_url(
1272 - 'admin-post.php?action=mxchat_delete_prompt&id=' . esc_attr($prompt->id)
1273 - . '&_wpnonce=' . wp_create_nonce('mxchat_delete_prompt_nonce')
1274 - )); ?>"
1275 - class="mxchat-button-icon delete-button"
1276 - onclick="return confirm('<?php esc_attr_e('Are you sure you want to delete this entry?', 'mxchat'); ?>');">
1277 - <span class="dashicons dashicons-trash"></span>
1278 - </a>
1279 - </td>
1280 - </tr>
1281 - <?php endforeach; ?>
1282 - <?php else : ?>
1283 - <tr>
1284 - <td colspan="4" class="mxchat-no-records">
1285 - <?php esc_html_e('No knowledge base entries found.', 'mxchat'); ?>
1286 - </td>
1287 - </tr>
1288 - <?php endif; ?>
1289 - </tbody>
1290 - </table>
1291 - </div>
1800 + // Check if class attribute exists
1801 + if (preg_match('/class=["\']([^"\']*)["\']/', $attributes, $class_matches)) {
1802 + // Add to existing class
1803 + $new_attributes = preg_replace(
1804 + '/class=["\']([^"\']*)["\']/',
1805 + 'class="$1 mxchat-clicked-link"',
1806 + $attributes
1807 + );
1808 + } else {
1809 + // Add new class attribute
1810 + $new_attributes = $attributes . ' class="mxchat-clicked-link"';
1811 + }
1292 1812
1293 - <?php if ($page_links) : ?>
1294 - <div class="mxchat-pagination">
1295 - <?php echo wp_kses_post($page_links); ?>
1296 - </div>
1297 - <?php endif; ?>
1298 -</div>
1299 -</div>
1300 -</div>
1301 -<?php
1302 -}
1813 + // Add title if not present
1814 + if (strpos($new_attributes, 'title=') === false) {
1815 + $new_attributes .= ' title="User clicked this link"';
1816 + }
1303 1817
1818 + return '<a' . $new_attributes . '>';
1819 + },
1820 + $message_content
1821 + );
1304 1822
1305 -public function mxchat_handle_delete_all_prompts() {
1306 - // Verify nonce
1307 - if (!isset($_POST['mxchat_delete_all_prompts_nonce']) || !wp_verify_nonce($_POST['mxchat_delete_all_prompts_nonce'], 'mxchat_delete_all_prompts_action')) {
1308 - wp_die(__('Nonce verification failed.', 'mxchat'));
1823 + // If we found and replaced, break out of the patterns loop
1824 + break;
1825 + }
1826 + }
1309 1827 }
1310 1828
1311 - // Check permissions
1312 - if (!current_user_can('manage_options')) {
1313 - wp_die(__('You do not have sufficient permissions to delete all prompts.', 'mxchat'));
1314 - }
1829 + return $message_content;
1830 +}
1315 1831
1832 +public function mxchat_create_prompts_page() {
1833 + //error_log('=== DEBUG: mxchat_create_prompts_page started ===');
1834 +
1316 1835 global $wpdb;
1317 1836 $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
1318 1837
1319 - // Delete all prompts from the table
1320 - $wpdb->query("DELETE FROM {$table_name}");
1838 + $knowledge_manager = MxChat_Knowledge_Manager::get_instance();
1839 + $pinecone_manager = MxChat_Pinecone_Manager::get_instance();
1321 1840
1322 - // Clear relevant cache
1323 - wp_cache_delete('all_prompts', 'mxchat_prompts');
1841 + // Display success message if all prompts were deleted
1842 + if (isset($_GET['all_deleted']) && $_GET['all_deleted'] === 'true') {
1843 + echo '<div class="notice notice-success is-dismissible"><p>' . esc_html__('All knowledge has been deleted successfully.', 'mxchat') . '</p><button type="button" class="notice-dismiss"><span class="screen-reader-text">' . esc_html__('Dismiss this notice.', 'mxchat') . '</span></button></div>';
1844 + }
1324 1845
1325 - // Redirect back with a success message
1326 - $redirect_url = add_query_arg(array(
1327 - 'page' => 'mxchat-prompts',
1328 - 'all_deleted' => 'true'
1329 - ), admin_url('admin.php'));
1846 + // Set up pagination, search query, and content type filter
1847 + $nonce = isset($_GET['_wpnonce']) ? sanitize_text_field($_GET['_wpnonce']) : '';
1848 + $search_query = (!empty($nonce) && wp_verify_nonce($nonce, 'mxchat_prompts_search_nonce') && isset($_GET['search'])) ? sanitize_text_field($_GET['search']) : '';
1849 + $content_type_filter = isset($_GET['content_type']) ? sanitize_key($_GET['content_type']) : ''; // ADDED 2.5.6
1850 + $current_page = isset($_GET['paged']) ? absint($_GET['paged']) : 1;
1851 + $per_page = 25;
1330 1852
1331 - wp_safe_redirect($redirect_url);
1332 - exit;
1333 -}
1334 -public function mxchat_handle_delete_prompt() {
1335 - // Sanitize and validate nonce
1336 - $nonce = isset($_GET['_wpnonce']) ? sanitize_text_field(wp_unslash($_GET['_wpnonce'])) : '';
1337 - if (empty($nonce) || !wp_verify_nonce($nonce, 'mxchat_delete_prompt_nonce')) {
1338 - wp_die(esc_html__('Nonce verification failed.', 'mxchat'));
1339 - }
1853 + //error_log('DEBUG: Search query: ' . $search_query);
1854 + //error_log('DEBUG: Content type filter: ' . $content_type_filter);
1855 + //error_log('DEBUG: Current page: ' . $current_page);
1856 + //error_log('DEBUG: Per page: ' . $per_page);
1340 1857
1341 - // Check permissions
1342 - if (!current_user_can('manage_options')) {
1343 - wp_die(esc_html__('You do not have sufficient permissions to delete prompts.', 'mxchat'));
1858 + // ================================
1859 + // MULTI-BOT CONFIGURATION
1860 + // ================================
1861 +
1862 + // Check for multi-bot and set up bot selection
1863 + if (class_exists('MxChat_Multi_Bot_Manager')) {
1864 + $multi_bot_manager = MxChat_Multi_Bot_Core_Manager::get_instance();
1865 + $available_bots = $multi_bot_manager->get_available_bots();
1866 +
1867 + // Get saved bot selection (user-specific first, then site-wide default)
1868 + $user_id = get_current_user_id();
1869 + $saved_bot_id = get_user_meta($user_id, 'mxchat_selected_knowledge_bot', true);
1870 + if (empty($saved_bot_id)) {
1871 + $saved_bot_id = get_option('mxchat_current_knowledge_bot', 'default');
1872 + }
1873 +
1874 + // Allow URL override but default to saved selection
1875 + $current_bot_id = isset($_GET['bot_id']) ? sanitize_key($_GET['bot_id']) : $saved_bot_id;
1876 + $multibot_active = true;
1877 + } else {
1878 + $current_bot_id = 'default';
1879 + $multibot_active = false;
1344 1880 }
1345 1881
1346 - // Validate and sanitize ID parameter
1347 - $id = isset($_GET['id']) ? intval($_GET['id']) : 0;
1348 - if ($id <= 0) {
1349 - wp_die(esc_html__('Invalid prompt ID.', 'mxchat'));
1350 - }
1882 + // ================================
1883 + // DATA SOURCE CONFIGURATION
1884 + // ================================
1351 1885
1352 - global $wpdb;
1353 - $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
1886 + // Get bot-specific Pinecone settings
1887 + $pinecone_options = $pinecone_manager->mxchat_get_bot_pinecone_options($current_bot_id);
1888 + $use_pinecone = ($pinecone_options['mxchat_use_pinecone'] ?? '0') === '1';
1889 + $pinecone_api_key = $pinecone_options['mxchat_pinecone_api_key'] ?? '';
1354 1890
1355 - // Clear cache and delete prompt
1356 - wp_cache_delete('prompt_' . $id, 'mxchat_prompts');
1357 - $wpdb->delete($table_name, array('id' => $id), array('%d'));
1891 + // Get OpenAI Vector Store settings
1892 + $vectorstore_options = get_option('mxchat_openai_vectorstore_options', array());
1893 + $use_vectorstore = ($vectorstore_options['mxchat_use_openai_vectorstore'] ?? '0') === '1';
1358 1894
1359 - wp_safe_redirect(add_query_arg(array('page' => 'mxchat-prompts', 'deleted' => 'true'), admin_url('admin.php')));
1360 - exit;
1361 -}
1362 -public function mxchat_generate_embedding($text) {
1363 - $options = get_option('mxchat_options');
1364 - $api_key = $options['api_key'] ?? esc_html__('default_api_key', 'mxchat');
1895 + //error_log('DEBUG: Bot ' . $current_bot_id . ' - Use Pinecone: ' . ($use_pinecone ? 'YES' : 'NO'));
1896 + //error_log('DEBUG: Bot ' . $current_bot_id . ' - Has API Key: ' . (!empty($pinecone_api_key) ? 'YES' : 'NO'));
1897 + //error_log('DEBUG: Bot ' . $current_bot_id . ' - Host: ' . ($pinecone_options['mxchat_pinecone_host'] ?? 'NOT SET'));
1898 + //error_log('DEBUG: Bot ' . $current_bot_id . ' - Namespace: ' . ($pinecone_options['mxchat_pinecone_namespace'] ?? 'NOT SET'));
1365 1899
1366 - $response = wp_remote_post('https://api.openai.com/v1/embeddings', array(
1367 - 'body' => wp_json_encode(array(
1368 - 'model' => esc_html__('text-embedding-ada-002', 'mxchat'),
1369 - 'input' => $text
1370 - )),
1371 - 'headers' => array(
1372 - 'Authorization' => 'Bearer ' . $api_key,
1373 - 'Content-Type' => esc_html__('application/json', 'mxchat')
1374 - ),
1375 - ));
1900 + if ($use_pinecone && !empty($pinecone_api_key)) {
1901 + //error_log('DEBUG: Using PINECONE data source with bot-specific config');
1902 + // PINECONE DATA SOURCE
1903 + $data_source = 'pinecone';
1904 +
1905 + // IMPORTANT: Pass the bot-specific $pinecone_options, not default options!
1906 + // UPDATED 2.5.6: Added content_type_filter parameter
1907 + $records = $pinecone_manager->mxchat_fetch_pinecone_records($pinecone_options, $search_query, $current_page, $per_page, $current_bot_id, $content_type_filter);
1376 1908
1377 - if (is_wp_error($response)) {
1378 - return null;
1379 - }
1909 + // TEMPORARY DEBUG - Add this right after the fetch call
1910 + //error_log('=== DEBUG: Bot switching issue ===');
1911 + //error_log('Current bot ID: ' . $current_bot_id);
1912 + //error_log('Use Pinecone: ' . ($use_pinecone ? 'YES' : 'NO'));
1913 + //error_log('Records returned: ' . count($records['data'] ?? []));
1914 + //error_log('Total records: ' . ($records['total'] ?? 0));
1380 1915
1381 - $response_data = json_decode(wp_remote_retrieve_body($response), true);
1382 - return $response_data['data'][0]['embedding'] ?? null;
1383 - }
1384 -public function mxchat_delete_chat_history() {
1385 - if (!current_user_can('manage_options')) {
1386 - echo wp_json_encode(['error' => esc_html__('You do not have sufficient permissions.', 'mxchat')]);
1387 - wp_die();
1916 + // Check first few records to see their bot_id
1917 + if (!empty($records['data'])) {
1918 + foreach (array_slice($records['data'], 0, 3) as $i => $record) {
1919 + $record_bot_id = $record->bot_id ?? 'NOT_SET';
1920 + //error_log('Record ' . ($i+1) . ' bot_id: ' . $record_bot_id . ', content preview: ' . substr($record->article_content ?? '', 0, 30) . '...');
1921 + }
1388 1922 }
1923 + //error_log('=== END DEBUG ===');
1389 1924
1390 - check_ajax_referer('mxchat_delete_chat_history', 'security');
1925 + $total_records = $records['total'] ?? 0;
1926 + $prompts = $records['data'] ?? array();
1927 + $total_in_database = $records['total_in_database'] ?? 0;
1928 + $showing_recent_only = $records['showing_recent_only'] ?? false;
1391 1929
1392 - global $wpdb;
1393 - $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
1930 + $total_pages = ceil($total_records / $per_page);
1394 1931
1395 - if (isset($_POST['delete_session_ids']) && is_array($_POST['delete_session_ids'])) {
1396 - foreach ($_POST['delete_session_ids'] as $session_id) {
1397 - $session_id_sanitized = sanitize_text_field($session_id);
1932 + } else {
1933 + //error_log('DEBUG: Using WORDPRESS DB data source');
1934 + // WORDPRESS DB DATA SOURCE (your existing logic)
1935 + $data_source = 'wordpress';
1398 1936
1399 - // Clear relevant cache before deletion
1400 - $cache_key = 'chat_session_' . $session_id_sanitized;
1401 - wp_cache_delete($cache_key, 'mxchat_chat_sessions');
1937 + // Initialize these variables for WordPress DB
1938 + $total_in_database = 0;
1939 + $showing_recent_only = false;
1402 1940
1403 - // Perform the deletion
1404 - $wpdb->delete($table_name, ['session_id' => $session_id_sanitized]);
1405 - }
1941 + $offset = ($current_page - 1) * $per_page;
1406 1942
1407 - // Optionally, clear a general cache if you have one
1408 - wp_cache_delete('all_chat_sessions', 'mxchat_chat_sessions');
1943 + // UPDATED 2.5.6: Build WHERE clause for search and content type filtering
1944 + $where_clauses = array();
1945 + $where_values = array();
1409 1946
1410 - echo wp_json_encode(['success' => esc_html__('Selected chat sessions have been deleted.', 'mxchat')]);
1411 - } else {
1412 - echo wp_json_encode(['error' => esc_html__('No chat sessions selected for deletion.', 'mxchat')]);
1947 + if ($search_query) {
1948 + $where_clauses[] = "article_content LIKE %s";
1949 + $where_values[] = '%' . $wpdb->esc_like($search_query) . '%';
1413 1950 }
1414 1951
1415 - wp_die();
1416 - }
1952 + if ($content_type_filter) {
1953 + $where_clauses[] = "content_type = %s";
1954 + $where_values[] = $content_type_filter;
1955 + }
1417 1956
1957 + $sql_where = "";
1958 + if (!empty($where_clauses)) {
1959 + $sql_where = "WHERE " . implode(" AND ", $where_clauses);
1960 + }
1418 1961
1419 -public function mxchat_save_inline_prompt() {
1420 - // Check for nonce security
1421 - check_ajax_referer('mxchat_save_inline_nonce');
1962 + // UPDATED 2.6.3: Count unique entries (by source_url) instead of individual rows
1963 + // This ensures pagination shows X entries per page, not X chunks
1964 + // Entries with empty source_url are counted individually
1965 + if (!empty($where_values)) {
1966 + // Count unique source_urls + count of rows with empty source_url
1967 + $count_query = $wpdb->prepare(
1968 + "SELECT (SELECT COUNT(DISTINCT source_url) FROM {$table_name} {$sql_where} AND source_url != '') +
1969 + (SELECT COUNT(*) FROM {$table_name} {$sql_where} AND (source_url = '' OR source_url IS NULL))",
1970 + array_merge($where_values, $where_values)
1971 + );
1972 + } else {
1973 + $count_query = "SELECT (SELECT COUNT(DISTINCT source_url) FROM {$table_name} WHERE source_url != '') +
1974 + (SELECT COUNT(*) FROM {$table_name} WHERE source_url = '' OR source_url IS NULL)";
1975 + }
1976 + $total_records = $wpdb->get_var($count_query);
1977 + $total_pages = ceil($total_records / $per_page);
1422 1978
1423 - // Verify permissions
1424 - if (!current_user_can('manage_options')) {
1425 - wp_send_json_error(esc_html__('Permission denied.', 'mxchat'));
1426 - return;
1427 - }
1979 + // UPDATED 2.6.3: Get unique source_urls for pagination, then fetch all their rows
1980 + // Step 1: Get the source_urls for this page (distinct URLs ordered by latest timestamp)
1981 + if (!empty($where_values)) {
1982 + $urls_query = $wpdb->prepare(
1983 + "SELECT source_url, MAX(timestamp) as latest_ts FROM {$table_name} {$sql_where}
1984 + GROUP BY source_url ORDER BY latest_ts DESC LIMIT %d OFFSET %d",
1985 + array_merge($where_values, array($per_page, $offset))
1986 + );
1987 + } else {
1988 + $urls_query = $wpdb->prepare(
1989 + "SELECT source_url, MAX(timestamp) as latest_ts FROM {$table_name}
1990 + GROUP BY source_url ORDER BY latest_ts DESC LIMIT %d OFFSET %d",
1991 + $per_page, $offset
1992 + );
1993 + }
1994 + $page_urls = $wpdb->get_results($urls_query);
1428 1995
1429 - global $wpdb;
1430 - $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
1996 + // Step 2: Fetch all rows for these source_urls
1997 + $prompts = array();
1998 + if (!empty($page_urls)) {
1999 + // Build URL order map to preserve newest-first ordering from step 1
2000 + $url_order_map = array();
2001 + $url_list = array();
2002 + $has_empty_url = false;
2003 + $order_index = 0;
2004 + foreach ($page_urls as $url_row) {
2005 + if (empty($url_row->source_url)) {
2006 + $has_empty_url = true;
2007 + $url_order_map['__empty__'] = $order_index++;
2008 + } else {
2009 + $url_list[] = $url_row->source_url;
2010 + $url_order_map[$url_row->source_url] = $order_index++;
2011 + }
2012 + }
1431 2013
1432 - // Validate and sanitize input data
1433 - $prompt_id = isset($_POST['id']) ? absint($_POST['id']) : 0;
1434 - $article_content = isset($_POST['article_content']) ? sanitize_textarea_field($_POST['article_content']) : '';
1435 - $article_url = isset($_POST['article_url']) ? esc_url_raw($_POST['article_url']) : '';
2014 + // Build query to fetch all rows for these URLs
2015 + $url_conditions = array();
2016 + $url_values = array();
1436 2017
1437 - if ($prompt_id > 0 && !empty($article_content)) {
1438 - // Re-generate the embedding vector for the updated content
1439 - $embedding_vector = $this->mxchat_generate_embedding($article_content);
2018 + if (!empty($url_list)) {
2019 + $placeholders = implode(',', array_fill(0, count($url_list), '%s'));
2020 + $url_conditions[] = "source_url IN ($placeholders)";
2021 + $url_values = array_merge($url_values, $url_list);
2022 + }
1440 2023
1441 - if (is_array($embedding_vector)) {
1442 - // Serialize the embedding vector before storing it
1443 - $embedding_vector_serialized = serialize($embedding_vector);
2024 + if ($has_empty_url) {
2025 + $url_conditions[] = "(source_url = '' OR source_url IS NULL)";
2026 + }
1444 2027
1445 - // Update the prompt in the database
1446 - $updated = $wpdb->update(
1447 - $table_name,
1448 - array(
1449 - 'article_content' => $article_content,
1450 - 'embedding_vector' => $embedding_vector_serialized,
1451 - 'source_url' => $article_url,
1452 - ),
1453 - array('id' => $prompt_id),
1454 - array('%s', '%s', '%s'),
1455 - array('%d')
1456 - );
2028 + if (!empty($url_conditions)) {
2029 + $url_where = "WHERE (" . implode(" OR ", $url_conditions) . ")";
1457 2030
1458 - if ($updated !== false) {
1459 - wp_send_json_success();
1460 - } else {
1461 - wp_send_json_error(esc_html__('Database update failed.', 'mxchat'));
1462 - }
1463 - } else {
1464 - wp_send_json_error(esc_html__('Embedding generation failed.', 'mxchat'));
1465 - }
1466 - } else {
1467 - wp_send_json_error(esc_html__('Invalid data.', 'mxchat'));
1468 - }
1469 -}
2031 + // Add original filters back
2032 + if (!empty($where_clauses)) {
2033 + $url_where .= " AND " . implode(" AND ", $where_clauses);
2034 + $url_values = array_merge($url_values, $where_values);
2035 + }
1470 2036
2037 + if (!empty($url_values)) {
2038 + $prompts_query = $wpdb->prepare(
2039 + "SELECT * FROM {$table_name} {$url_where} ORDER BY timestamp DESC",
2040 + $url_values
2041 + );
2042 + } else {
2043 + $prompts_query = "SELECT * FROM {$table_name} {$url_where} ORDER BY timestamp DESC";
2044 + }
1471 2045
2046 + $prompts = $wpdb->get_results($prompts_query);
1472 2047
1473 -// Add this method to handle post updates
1474 -public function handle_post_update($post_id, $post, $update) {
1475 - // Basic validation checks
1476 - if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE || wp_is_post_revision($post_id)) {
1477 - return;
1478 - }
2048 + // Sort prompts by the original URL order (newest first), then by timestamp within each URL group
2049 + usort($prompts, function($a, $b) use ($url_order_map) {
2050 + $url_a = empty($a->source_url) ? '__empty__' : $a->source_url;
2051 + $url_b = empty($b->source_url) ? '__empty__' : $b->source_url;
2052 + $order_a = $url_order_map[$url_a] ?? PHP_INT_MAX;
2053 + $order_b = $url_order_map[$url_b] ?? PHP_INT_MAX;
1479 2054
1480 - // Only process published content
1481 - if (!in_array($post->post_status, array('publish'))) {
1482 - return;
1483 - }
2055 + // First sort by URL order (newest URLs first)
2056 + if ($order_a !== $order_b) {
2057 + return $order_a - $order_b;
2058 + }
1484 2059
1485 - // Check if sync is enabled for this post type
1486 - $post_type = $post->post_type;
1487 - if (!in_array($post_type, array('post', 'page'))) {
1488 - return;
2060 + // Within same URL, sort by timestamp DESC (newest chunks first)
2061 + return strtotime($b->timestamp) - strtotime($a->timestamp);
2062 + });
2063 + }
2064 + }
1489 2065 }
1490 2066
1491 - $sync_option = ($post_type === 'post') ? 'mxchat_auto_sync_posts' : 'mxchat_auto_sync_pages';
1492 - if (get_option($sync_option) != '1') {
1493 - return;
1494 - }
2067 + // ================================
2068 + // PAGINATION GENERATION
2069 + // ================================
2070 +
2071 + // Generate pagination links
2072 + if ($total_pages > 1) {
2073 + // Build a clean base URL with only necessary parameters
2074 + $pagination_args = array('page' => 'mxchat-prompts');
1495 2075
1496 - // Prepare content and URL
1497 - $content = wp_strip_all_tags($post->post_content);
1498 - $url = get_permalink($post_id);
2076 + // Preserve bot_id parameter if multi-bot is active
2077 + if ($multibot_active && !empty($current_bot_id) && $current_bot_id !== 'default') {
2078 + $pagination_args['bot_id'] = $current_bot_id;
2079 + }
1499 2080
1500 - // Generate embedding vector
1501 - $embedding_vector = $this->mxchat_generate_embedding($content);
1502 - if (!$embedding_vector) {
1503 - return;
1504 - }
2081 + // Preserve search query and nonce if present
2082 + if ($search_query) {
2083 + $pagination_args['search'] = $search_query;
2084 + if (!empty($nonce)) {
2085 + $pagination_args['_wpnonce'] = $nonce;
2086 + }
2087 + }
1505 2088
1506 - // Check for Pinecone addon and its settings
1507 - $pinecone_settings = get_option('mxchat_pinecone_addon_options');
1508 - $use_pinecone = false;
2089 + // Preserve content type filter if present
2090 + if ($content_type_filter) {
2091 + $pagination_args['content_type'] = $content_type_filter;
2092 + }
1509 2093
1510 - if ($pinecone_settings && is_array($pinecone_settings)) {
1511 - // Check if Pinecone is enabled and all required settings are present
1512 - $use_pinecone = (
1513 - isset($pinecone_settings['mxchat_use_pinecone']) &&
1514 - $pinecone_settings['mxchat_use_pinecone'] === '1' &&
1515 - !empty($pinecone_settings['mxchat_pinecone_api_key']) &&
1516 - !empty($pinecone_settings['mxchat_pinecone_host']) &&
1517 - !empty($pinecone_settings['mxchat_pinecone_index']) &&
1518 - !empty($pinecone_settings['mxchat_pinecone_environment'])
1519 - );
1520 - }
2094 + // Build clean base URL
2095 + $base_url = add_query_arg($pagination_args, admin_url('admin.php'));
1521 2096
1522 - if ($use_pinecone) {
1523 - // Use Pinecone
1524 - $pinecone_result = $this->store_in_pinecone_main(
1525 - $embedding_vector,
1526 - $content,
1527 - $url,
1528 - $pinecone_settings['mxchat_pinecone_api_key'],
1529 - $pinecone_settings['mxchat_pinecone_environment'],
1530 - $pinecone_settings['mxchat_pinecone_index']
1531 - );
1532 -
1533 - if (!$pinecone_result['success']) {
1534 - //error_log('MXChat: Pinecone sync failed - falling back to WordPress DB');
1535 - $this->store_in_wordpress_db($content, $url, $embedding_vector);
1536 - }
2097 + $page_links = paginate_links(array(
2098 + 'base' => $base_url . '%_%',
2099 + 'format' => '&paged=%#%',
2100 + 'prev_text' => __('&laquo; Previous', 'mxchat'),
2101 + 'next_text' => __('Next &raquo;', 'mxchat'),
2102 + 'total' => $total_pages,
2103 + 'current' => $current_page,
2104 + 'type' => 'plain',
2105 + ));
1537 2106 } else {
1538 - // Fallback to WordPress DB storage
1539 - $this->store_in_wordpress_db($content, $url, $embedding_vector);
2107 + $page_links = '';
1540 2108 }
1541 -}
1542 -// Modified storage function to add type field
1543 -private function store_in_pinecone_main($embedding_vector, $content, $url, $api_key, $environment, $index_name, $vector_id = null) {
1544 - $vector_id = $vector_id ?: md5($url);
1545 - $options = get_option('mxchat_pinecone_addon_options');
1546 - $host = $options['mxchat_pinecone_host'] ?? '';
1547 2109
1548 - if (empty($host)) {
1549 - return array(
1550 - 'success' => false,
1551 - 'message' => 'Pinecone host is not configured. Please set the host in your settings.'
1552 - );
1553 - }
2110 + // ================================
2111 + // PROCESSING STATUS RETRIEVAL
2112 + // ================================
1554 2113
1555 - // Determine if this is a product URL
1556 - $is_product = (strpos($url, '/product/') !== false || strpos($url, '/shop/') !== false);
2114 + // Retrieve processing statuses using queue-based method
2115 + $processing_statuses = $knowledge_manager->mxchat_get_processing_statuses();
2116 + $pdf_status = $processing_statuses['pdf_status'];
2117 + $sitemap_status = $processing_statuses['sitemap_status'];
2118 + $is_processing = $processing_statuses['is_processing'];
1557 2119
1558 - $api_endpoint = "https://{$host}/vectors/upsert";
1559 - $request_body = array(
1560 - 'vectors' => array(
1561 - array(
1562 - 'id' => $vector_id,
1563 - 'values' => $embedding_vector,
1564 - 'metadata' => array(
1565 - 'text' => $content,
1566 - 'source_url' => $url,
1567 - 'type' => $is_product ? 'product' : 'content',
1568 - 'last_updated' => time()
1569 - )
1570 - )
1571 - )
1572 - );
2120 + //error_log('=== DEBUG: mxchat_create_prompts_page data preparation completed ===');
1573 2121
1574 - $response = wp_remote_post($api_endpoint, array(
1575 - 'headers' => array(
1576 - 'Api-Key' => $api_key,
1577 - 'accept' => 'application/json',
1578 - 'content-type' => 'application/json'
1579 - ),
1580 - 'body' => wp_json_encode($request_body),
1581 - 'timeout' => 30,
1582 - 'data_format' => 'body'
1583 - ));
2122 + // ================================
2123 + // RENDER PAGE WITH NEW SIDEBAR LAYOUT
2124 + // ================================
1584 2125
1585 - if (is_wp_error($response)) {
1586 - return array(
1587 - 'success' => false,
1588 - 'message' => $response->get_error_message()
1589 - );
1590 - }
2126 + // Include the new knowledge page template
2127 + require_once plugin_dir_path(__FILE__) . 'admin-knowledge-page.php';
1591 2128
1592 - $response_code = wp_remote_retrieve_response_code($response);
1593 - if ($response_code !== 200) {
1594 - $body = wp_remote_retrieve_body($response);
1595 - return array(
1596 - 'success' => false,
1597 - 'message' => sprintf(
1598 - 'Pinecone API error (HTTP %d): %s',
1599 - $response_code,
1600 - $body
1601 - )
1602 - );
1603 - }
2129 + // Package all page data for the render function
2130 + $page_data = array(
2131 + 'prompts' => $prompts,
2132 + 'total_records' => $total_records,
2133 + 'total_pages' => $total_pages,
2134 + 'current_page' => $current_page,
2135 + 'per_page' => $per_page,
2136 + 'page_links' => $page_links,
2137 + 'search_query' => $search_query,
2138 + 'content_type_filter' => $content_type_filter,
2139 + 'data_source' => $data_source,
2140 + 'use_pinecone' => $use_pinecone,
2141 + 'use_vectorstore' => $use_vectorstore,
2142 + 'multibot_active' => $multibot_active,
2143 + 'current_bot_id' => $current_bot_id,
2144 + 'pdf_status' => $pdf_status,
2145 + 'sitemap_status' => $sitemap_status,
2146 + 'is_processing' => $is_processing,
2147 + 'total_in_database' => $total_in_database ?? 0,
2148 + 'showing_recent_only' => $showing_recent_only ?? false,
2149 + );
1604 2150
1605 - return array(
1606 - 'success' => true,
1607 - 'message' => 'Successfully stored in Pinecone'
1608 - );
2151 + // Render the new sidebar-based page
2152 + mxchat_render_knowledge_page($this, $knowledge_manager, $page_data);
1609 2153 }
1610 -// Helper method for WordPress DB storage
1611 -private function store_in_wordpress_db($content, $url, $embedding_vector) {
1612 - global $wpdb;
1613 - $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
1614 2154
1615 - $existing_entry = $wpdb->get_row(
1616 - $wpdb->prepare("SELECT id FROM $table_name WHERE source_url = %s", $url)
1617 - );
1618 -
1619 - if ($existing_entry) {
1620 - $wpdb->update(
1621 - $table_name,
1622 - array(
1623 - 'article_content' => $content,
1624 - 'embedding_vector' => serialize($embedding_vector),
1625 - 'timestamp' => current_time('mysql')
1626 - ),
1627 - array('id' => $existing_entry->id),
1628 - array('%s', '%s', '%s')
2155 +/**
2156 + * Get bot-specific Pinecone configuration
2157 + * Used in the knowledge retrieval functions
2158 + */
2159 +private function get_bot_pinecone_config($bot_id = 'default') {
2160 + //error_log("MXCHAT DEBUG: get_bot_pinecone_config called for bot: " . $bot_id);
2161 +
2162 + // If default bot or multi-bot add-on not active, use default Pinecone config
2163 + if ($bot_id === 'default' || !class_exists('MxChat_Multi_Bot_Manager')) {
2164 + //error_log("MXCHAT DEBUG: Using default Pinecone config (no multi-bot or bot is 'default')");
2165 + $addon_options = get_option('mxchat_pinecone_addon_options', array());
2166 + $config = array(
2167 + 'use_pinecone' => (isset($addon_options['mxchat_use_pinecone']) && $addon_options['mxchat_use_pinecone'] === '1'),
2168 + 'api_key' => $addon_options['mxchat_pinecone_api_key'] ?? '',
2169 + 'host' => $addon_options['mxchat_pinecone_host'] ?? '',
2170 + 'namespace' => $addon_options['mxchat_pinecone_namespace'] ?? ''
1629 2171 );
2172 + //error_log("MXCHAT DEBUG: Default config - use_pinecone: " . ($config['use_pinecone'] ? 'true' : 'false'));
2173 + return $config;
2174 + }
2175 +
2176 + //error_log("MXCHAT DEBUG: Calling filter 'mxchat_get_bot_pinecone_config' for bot: " . $bot_id);
2177 +
2178 + // Hook for multi-bot add-on to provide bot-specific Pinecone config
2179 + $bot_pinecone_config = apply_filters('mxchat_get_bot_pinecone_config', array(), $bot_id);
2180 +
2181 + if (!empty($bot_pinecone_config)) {
2182 + //error_log("MXCHAT DEBUG: Got bot-specific config from filter");
2183 + //error_log(" - use_pinecone: " . (isset($bot_pinecone_config['use_pinecone']) ? ($bot_pinecone_config['use_pinecone'] ? 'true' : 'false') : 'not set'));
2184 + //error_log(" - host: " . ($bot_pinecone_config['host'] ?? 'not set'));
2185 + //error_log(" - namespace: " . ($bot_pinecone_config['namespace'] ?? 'not set'));
1630 2186 } else {
1631 - $wpdb->insert(
1632 - $table_name,
1633 - array(
1634 - 'article_content' => $content,
1635 - 'source_url' => $url,
1636 - 'embedding_vector' => serialize($embedding_vector),
1637 - 'timestamp' => current_time('mysql')
1638 - ),
1639 - array('%s', '%s', '%s', '%s')
1640 - );
2187 + //error_log("MXCHAT DEBUG: Filter returned empty config!");
1641 2188 }
2189 +
2190 + return is_array($bot_pinecone_config) ? $bot_pinecone_config : array();
1642 2191 }
1643 -/**
1644 - * Handle deletion of posts and pages from both Pinecone and WordPress DB
1645 - *
1646 - * @param int $post_id The ID of the post being deleted
1647 - * @return void
1648 - */
1649 -public function mxchat_handle_post_delete($post_id) {
1650 - // Get post data before it's deleted
1651 - $post = get_post($post_id);
1652 2192
1653 - // Basic validation
1654 - if (!$post || wp_is_post_revision($post_id)) {
1655 - return;
2193 +public function mxchat_delete_chat_history() {
2194 + if (!current_user_can('manage_options')) {
2195 + echo wp_json_encode(['error' => esc_html__('You do not have sufficient permissions.', 'mxchat')]);
2196 + wp_die();
1656 2197 }
2198 + check_ajax_referer('mxchat_delete_chat_history', 'security');
2199 + global $wpdb;
2200 + $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
1657 2201
1658 - // Check post type
1659 - $post_type = $post->post_type;
1660 - if (!in_array($post_type, array('post', 'page'))) {
1661 - return;
1662 - }
2202 + if (isset($_POST['delete_session_ids']) && is_array($_POST['delete_session_ids'])) {
2203 + $deleted_count = 0;
1663 2204
1664 - // Check if sync is enabled for this post type
1665 - $sync_option = ($post_type === 'post') ? 'mxchat_auto_sync_posts' : 'mxchat_auto_sync_pages';
1666 - if (get_option($sync_option) != '1') {
1667 - return;
1668 - }
2205 + foreach ($_POST['delete_session_ids'] as $session_id) {
2206 + $session_id_sanitized = sanitize_text_field($session_id);
1669 2207
1670 - // Get the URL before post is deleted
1671 - $source_url = get_permalink($post_id);
1672 - if (!$source_url) {
1673 - //error_log('MXChat: Failed to get permalink for post ' . $post_id);
1674 - return;
1675 - }
2208 + // Clear relevant cache before deletion
2209 + $cache_key = 'chat_session_' . $session_id_sanitized;
2210 + wp_cache_delete($cache_key, 'mxchat_chat_sessions');
1676 2211
1677 - // Check for Pinecone addon and its settings
1678 - $pinecone_settings = get_option('mxchat_pinecone_addon_options');
1679 - $use_pinecone = false;
1680 - if ($pinecone_settings && is_array($pinecone_settings)) {
1681 - $use_pinecone = (
1682 - isset($pinecone_settings['mxchat_use_pinecone']) &&
1683 - $pinecone_settings['mxchat_use_pinecone'] === '1' &&
1684 - !empty($pinecone_settings['mxchat_pinecone_api_key']) &&
1685 - !empty($pinecone_settings['mxchat_pinecone_host']) &&
1686 - !empty($pinecone_settings['mxchat_pinecone_index']) &&
1687 - !empty($pinecone_settings['mxchat_pinecone_environment'])
1688 - );
1689 - }
2212 + // Perform the deletion from the database table
2213 + $wpdb->delete($table_name, ['session_id' => $session_id_sanitized]);
1690 2214
1691 - $deletion_successful = false;
2215 + // Delete the corresponding option entry from wp_options table
2216 + delete_option("mxchat_history_" . $session_id_sanitized);
1692 2217
1693 - if ($use_pinecone) {
1694 - try {
1695 - $pinecone_result = $this->delete_from_pinecone(
1696 - array($source_url),
1697 - $pinecone_settings['mxchat_pinecone_api_key'],
1698 - $pinecone_settings['mxchat_pinecone_environment'],
1699 - $pinecone_settings['mxchat_pinecone_index']
1700 - );
2218 + // Delete any associated metadata options
2219 + delete_option("mxchat_email_" . $session_id_sanitized);
2220 + delete_option("mxchat_agent_name_" . $session_id_sanitized);
1701 2221
1702 - if (!$pinecone_result['success']) {
1703 - //error_log('MXChat: Pinecone deletion failed for URL: ' . $source_url . ' - ' . $pinecone_result['message']);
1704 - } else {
1705 - $deletion_successful = true;
1706 - }
1707 - } catch (Exception $e) {
1708 - //error_log('MXChat: Exception during Pinecone deletion - ' . $e->getMessage());
2222 + $deleted_count++;
1709 2223 }
1710 - }
1711 2224
1712 - // Always attempt WordPress DB deletion, regardless of Pinecone status
1713 - try {
1714 - global $wpdb;
1715 - $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
2225 + // Optionally, clear a general cache if you have one
2226 + wp_cache_delete('all_chat_sessions', 'mxchat_chat_sessions');
1716 2227
1717 - $result = $wpdb->delete(
1718 - $table_name,
1719 - array('source_url' => $source_url),
1720 - array('%s')
1721 - );
1722 -
1723 - if ($result === false) {
1724 - //error_log('MXChat: WordPress DB deletion failed for URL: ' . $source_url);
1725 - } else {
1726 - $deletion_successful = true;
1727 - }
1728 - } catch (Exception $e) {
1729 - //error_log('MXChat: Exception during WordPress DB deletion - ' . $e->getMessage());
2228 + echo wp_json_encode([
2229 + 'success' => sprintf(
2230 + esc_html__('%d chat session(s) have been deleted from all storage locations.', 'mxchat'),
2231 + $deleted_count
2232 + )
2233 + ]);
2234 + } else {
2235 + echo wp_json_encode(['error' => esc_html__('No chat sessions selected for deletion.', 'mxchat')]);
1730 2236 }
1731 2237
1732 - if (!$deletion_successful) {
1733 - //error_log('MXChat: Complete deletion failure for post ID: ' . $post_id . ' URL: ' . $source_url);
1734 - }
2238 + wp_die();
1735 2239 }
1736 2240
1737 -
1738 -public function mxchat_handle_content_submission() {
1739 - // Check if the form was submitted and the user has permission.
1740 - if (!isset($_POST['submit_content']) || !current_user_can('manage_options')) {
1741 - return;
2241 +/**
2242 + * Format transcript message content with markdown processing
2243 + * Converts markdown links, bold, italic, code blocks, and plain URLs to HTML
2244 + */
2245 +private function format_transcript_message($text) {
2246 + if (empty($text)) {
2247 + return '';
1742 2248 }
1743 2249
1744 - // Verify the nonce.
1745 - $nonce = isset($_POST['mxchat_submit_content_nonce']) ? sanitize_text_field(wp_unslash($_POST['mxchat_submit_content_nonce'])) : '';
1746 - if (!wp_verify_nonce($nonce, 'mxchat_submit_content_action')) {
1747 - wp_die(esc_html__('Nonce verification failed.', 'mxchat'));
1748 - }
2250 + // Normalize line endings and clean up excessive whitespace
2251 + $text = str_replace("\r\n", "\n", $text);
2252 + $text = str_replace("\r", "\n", $text);
1749 2253
1750 - // Sanitize the inputs.
1751 - $article_content = sanitize_textarea_field($_POST['article_content']);
1752 - $article_url = isset($_POST['article_url']) ? esc_url_raw($_POST['article_url']) : '';
2254 + // Clean up existing <br> tags that may have been saved (legacy data)
2255 + // Convert <br>, <br/>, <br /> back to newlines for consistent processing
2256 + $text = preg_replace('/<br\s*\/?>\s*/i', "\n", $text);
1753 2257
1754 - // Generate the embedding vector.
1755 - $embedding_vector = $this->mxchat_generate_embedding($article_content);
1756 - if (!is_array($embedding_vector)) {
1757 - set_transient('mxchat_admin_notice_error',
1758 - esc_html__('Embedding generation failed. Please ensure your API key is correct and try again.', 'mxchat'),
1759 - 30
1760 - );
1761 - wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
1762 - exit;
1763 - }
2258 + // Collapse 3+ consecutive newlines to just 2 (paragraph break)
2259 + $text = preg_replace('/\n{3,}/', "\n\n", $text);
1764 2260
1765 - // Store in the WordPress database.
1766 - global $wpdb;
1767 - $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
2261 + // Process markdown headers (# Header)
2262 + $text = preg_replace_callback('/^(#{1,6})\s+(.+)$/m', function($matches) {
2263 + $level = strlen($matches[1]);
2264 + $content = esc_html(trim($matches[2]));
2265 + return "<h{$level}>{$content}</h{$level}>";
2266 + }, $text);
1768 2267
1769 - // Ensure the source_url column exists.
1770 - if ($wpdb->get_var($wpdb->prepare("SHOW COLUMNS FROM {$table_name} LIKE %s", 'source_url')) != 'source_url') {
1771 - $wpdb->query($wpdb->prepare("ALTER TABLE {$table_name} ADD source_url VARCHAR(255) DEFAULT ''"));
1772 - }
2268 + // Process code blocks with triple backticks
2269 + $text = preg_replace_callback('/```(\w+)?\n?([\s\S]*?)```/', function($matches) {
2270 + $language = !empty($matches[1]) ? ' class="language-' . esc_attr($matches[1]) . '"' : '';
2271 + $code = esc_html($matches[2]);
2272 + return "<pre><code{$language}>{$code}</code></pre>";
2273 + }, $text);
1773 2274
1774 - // Serialize and store the embedding vector.
1775 - $embedding_vector_serialized = serialize($embedding_vector);
1776 - $inserted = $wpdb->insert(
1777 - $table_name,
1778 - array(
1779 - 'article_content' => $article_content,
1780 - 'embedding_vector' => $embedding_vector_serialized,
1781 - 'source_url' => $article_url,
1782 - ),
1783 - array('%s', '%s', '%s')
1784 - );
2275 + // Process inline code with single backticks
2276 + $text = preg_replace('/`([^`]+)`/', '<code>$1</code>', $text);
1785 2277
1786 - if ($inserted === false) {
1787 - set_transient('mxchat_admin_notice_error',
1788 - esc_html__('Error inserting content into the database. Please try again.', 'mxchat'),
1789 - 30
1790 - );
1791 - } else {
1792 - set_transient('mxchat_admin_notice_success',
1793 - esc_html__('Content successfully submitted!', 'mxchat'),
1794 - 30
1795 - );
1796 - }
2278 + // Process bold text **text** or __text__
2279 + $text = preg_replace('/\*\*(.+?)\*\*/', '<strong>$1</strong>', $text);
2280 + $text = preg_replace('/__(.+?)__/', '<strong>$1</strong>', $text);
1797 2281
1798 - wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
1799 - exit;
1800 -}
2282 + // Process italic text *text* or _text_ (but not if part of URL)
2283 + $text = preg_replace('/(?<![*_\w])\*([^*]+)\*(?![*\w])/', '<em>$1</em>', $text);
2284 + $text = preg_replace('/(?<![*_\w])_([^_]+)_(?![*\w])/', '<em>$1</em>', $text);
1801 2285
1802 -private function is_pdf_url($url, $response) {
1803 - $content_type = wp_remote_retrieve_header($response, 'content-type');
1804 - $file_extension = strtolower(pathinfo($url, PATHINFO_EXTENSION));
2286 + // Process markdown links [text](url)
2287 + $text = preg_replace_callback('/\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/', function($matches) {
2288 + $link_text = esc_html($matches[1]);
2289 + $url = esc_url($matches[2]);
2290 + return "<a href=\"{$url}\" target=\"_blank\" rel=\"noopener\">{$link_text}</a>";
2291 + }, $text);
1805 2292
1806 - return strpos($content_type, 'pdf') !== false || $file_extension === 'pdf';
1807 -}
1808 -private function handle_pdf_for_knowledge_base($pdf_url, $response) {
1809 - if (!current_user_can('manage_options')) {
1810 - //error_log(esc_html__('Unauthorized PDF processing attempt', 'mxchat'));
1811 - return false;
1812 - }
2293 + // Process citation-style brackets [URL]
2294 + $text = preg_replace_callback('/\[(https?:\/\/[^\]]+)\]/', function($matches) {
2295 + $url = esc_url($matches[1]);
2296 + return "<a href=\"{$url}\" target=\"_blank\" rel=\"noopener\">{$url}</a>";
2297 + }, $text);
1813 2298
1814 - $pdf_url = esc_url_raw($pdf_url);
1815 - $upload_dir = wp_upload_dir();
2299 + // Process standalone URLs (not already in links)
2300 + $text = preg_replace_callback(
2301 + '/(?<!href="|">)(https?:\/\/[^\s<>"]+)(?![^<]*<\/a>)/',
2302 + function($matches) {
2303 + $url = esc_url($matches[1]);
2304 + // Truncate display URL if too long
2305 + $display = strlen($matches[1]) > 50 ? substr($matches[1], 0, 47) . '...' : $matches[1];
2306 + return "<a href=\"{$url}\" target=\"_blank\" rel=\"noopener\">{$display}</a>";
2307 + },
2308 + $text
2309 + );
1816 2310
1817 - if (isset($upload_dir['error']) && $upload_dir['error'] !== false) {
1818 - //error_log(sprintf(esc_html__('Upload directory error: %s', 'mxchat'), esc_html($upload_dir['error'])));
1819 - return false;
1820 - }
2311 + // Process mailto links
2312 + $text = preg_replace_callback('/\[([^\]]+)\]\((mailto:[^)]+)\)/', function($matches) {
2313 + $link_text = esc_html($matches[1]);
2314 + $mailto = esc_url($matches[2]);
2315 + return "<a href=\"{$mailto}\">{$link_text}</a>";
2316 + }, $text);
1821 2317
1822 - $pdf_filename = sanitize_file_name('mxchat_kb_' . time() . '.pdf');
1823 - $pdf_path = trailingslashit($upload_dir['path']) . $pdf_filename;
2318 + // Convert paragraphs: split by double newlines, wrap in <p> tags
2319 + // This creates proper paragraph structure instead of excessive <br> tags
2320 + $paragraphs = preg_split('/\n\n+/', $text);
1824 2321
1825 - $response_body = wp_remote_retrieve_body($response);
1826 - if (empty($response_body)) {
1827 - //error_log(esc_html__('Empty PDF response body', 'mxchat'));
1828 - return false;
1829 - }
2322 + // Filter out empty paragraphs but preserve content like "0"
2323 + $paragraphs = array_values(array_filter(array_map('trim', $paragraphs), function($p) {
2324 + return $p !== '';
2325 + }));
1830 2326
1831 - if (!wp_mkdir_p(dirname($pdf_path))) {
1832 - //error_log(sprintf(esc_html__('Failed to create directory for PDF: %s', 'mxchat'), esc_html($pdf_path)));
1833 - return false;
2327 + if (empty($paragraphs)) {
2328 + // No content after filtering
2329 + return '';
2330 + } elseif (count($paragraphs) > 1) {
2331 + // Multiple paragraphs - wrap each in <p> tags, convert single newlines to <br>
2332 + $formatted_paragraphs = array_map(function($p) {
2333 + return nl2br($p);
2334 + }, $paragraphs);
2335 + $text = '<p>' . implode('</p><p>', $formatted_paragraphs) . '</p>';
2336 + } else {
2337 + // Single paragraph - just convert newlines to <br>
2338 + $text = nl2br($paragraphs[0]);
1834 2339 }
1835 2340
1836 - try {
1837 - file_put_contents($pdf_path, $response_body);
2341 + return $text;
2342 +}
1838 2343
1839 - if (!file_exists($pdf_path)) {
1840 - throw new Exception(__('Failed to save PDF file', 'mxchat'));
1841 - }
2344 +/**
2345 + * AJAX handler to fetch RAG context for a specific message
2346 + * Used by the transcript viewer to show retrieved documents
2347 + */
2348 +public function mxchat_get_rag_context() {
2349 + // Check permissions
2350 + if (!current_user_can('manage_options')) {
2351 + wp_send_json_error(['message' => esc_html__('You do not have sufficient permissions.', 'mxchat')]);
2352 + wp_die();
2353 + }
1842 2354
1843 - $parser = new \Smalot\PdfParser\Parser();
1844 - $pdf = $parser->parseFile($pdf_path);
1845 - $total_pages = absint(count($pdf->getPages()));
2355 + // Validate message ID
2356 + if (!isset($_POST['message_id']) || empty($_POST['message_id'])) {
2357 + wp_send_json_error(['message' => esc_html__('Message ID is required.', 'mxchat')]);
2358 + wp_die();
2359 + }
1846 2360
1847 - if ($total_pages < 1) {
1848 - throw new Exception(__('Invalid PDF: no pages found', 'mxchat'));
1849 - }
2361 + $message_id = absint($_POST['message_id']);
1850 2362
1851 - wp_schedule_single_event(time(), 'mxchat_process_pdf_pages', array(
1852 - 'pdf_path' => $pdf_path,
1853 - 'pdf_url' => $pdf_url,
1854 - 'total_pages' => $total_pages,
1855 - 'batch_size' => absint(15),
1856 - 'batch_pause' => absint(10)
1857 - ));
2363 + global $wpdb;
2364 + $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
1858 2365
1859 - $status_data = array(
1860 - 'total_pages' => $total_pages,
1861 - 'processed_pages' => 0,
1862 - 'status' => 'processing',
1863 - 'last_update' => time()
1864 - );
2366 + // Fetch the RAG context for this message
2367 + $result = $wpdb->get_row($wpdb->prepare(
2368 + "SELECT rag_context FROM {$table_name} WHERE id = %d",
2369 + $message_id
2370 + ));
1865 2371
1866 - set_transient(
1867 - sanitize_key('mxchat_pdf_status_' . md5($pdf_url)),
1868 - array_map('sanitize_text_field', $status_data),
1869 - DAY_IN_SECONDS
1870 - );
2372 + if (!$result || empty($result->rag_context)) {
2373 + wp_send_json_error(['message' => esc_html__('No RAG context found for this message.', 'mxchat')]);
2374 + wp_die();
2375 + }
1871 2376
1872 - return __('scheduled', 'mxchat');
2377 + // Decode the JSON data
2378 + $rag_context = json_decode($result->rag_context, true);
1873 2379
1874 - } catch (Exception $e) {
1875 - //error_log(sprintf(esc_html__('Error preparing PDF for processing: %s', 'mxchat'), esc_html($e->getMessage())));
1876 - if (file_exists($pdf_path)) {
1877 - wp_delete_file($pdf_path);
1878 - }
1879 - return false;
2380 + if (json_last_error() !== JSON_ERROR_NONE) {
2381 + wp_send_json_error(['message' => esc_html__('Invalid RAG context data.', 'mxchat')]);
2382 + wp_die();
1880 2383 }
2384 +
2385 + wp_send_json_success($rag_context);
2386 + wp_die();
1881 2387 }
1882 -public static function process_pdf_pages_cron($pdf_path, $pdf_url, $total_pages, $batch_size, $batch_pause) {
1883 - // Validate inputs
1884 - $pdf_path = sanitize_text_field($pdf_path);
1885 - $pdf_url = esc_url_raw($pdf_url);
1886 - $total_pages = absint($total_pages);
1887 - $batch_size = absint($batch_size);
1888 - $batch_pause = absint($batch_pause);
1889 2388
1890 - try {
1891 - if (!file_exists($pdf_path)) {
1892 - throw new Exception(sprintf('PDF file not found at path: %s', esc_html($pdf_path)));
1893 - }
2389 +public function display_admin_notices() {
2390 + // Check if we're on a MXChat admin page
2391 + $screen = get_current_screen();
2392 + if (!$screen || strpos($screen->base, 'mxchat') === false) {
2393 + return;
2394 + }
1894 2395
1895 - $parser = new \Smalot\PdfParser\Parser();
1896 - $pdf = $parser->parseFile($pdf_path);
1897 - $pages = $pdf->getPages();
2396 + $dismiss_button = '<button type="button" class="notice-dismiss"><span class="screen-reader-text">' . esc_html__('Dismiss this notice.', 'mxchat') . '</span></button>';
1898 2397
1899 - // Get current progress
1900 - $status_key = sanitize_key('mxchat_pdf_status_' . md5($pdf_url));
1901 - $status = get_transient($status_key);
2398 + // Check for error notices
2399 + $error_notice = get_transient('mxchat_admin_notice_error');
2400 + if ($error_notice) {
2401 + echo '<div class="notice notice-error is-dismissible"><p>' . wp_kses_post($error_notice) . '</p>' . $dismiss_button . '</div>';
2402 + delete_transient('mxchat_admin_notice_error');
2403 + }
1902 2404
1903 - if (!$status || !is_array($status)) {
1904 - throw new Exception('Invalid status data retrieved from transient');
1905 - }
2405 + // Check for success notices
2406 + $success_notice = get_transient('mxchat_admin_notice_success');
2407 + if ($success_notice) {
2408 + echo '<div class="notice notice-success is-dismissible"><p>' . wp_kses_post($success_notice) . '</p>' . $dismiss_button . '</div>';
2409 + delete_transient('mxchat_admin_notice_success');
2410 + }
1906 2411
1907 - $start_page = absint($status['processed_pages']);
1908 - $end_page = min($start_page + $batch_size, $total_pages);
2412 + // Check for info notices
2413 + $info_notice = get_transient('mxchat_admin_notice_info');
2414 + if ($info_notice) {
2415 + echo '<div class="notice notice-info is-dismissible"><p>' . wp_kses_post($info_notice) . '</p>' . $dismiss_button . '</div>';
2416 + delete_transient('mxchat_admin_notice_info');
2417 + }
2418 +}
1909 2419
1910 - $instance = new self(); // Create an instance of the class
1911 2420
1912 - for ($i = $start_page; $i < $end_page; $i++) {
1913 - $text = $pages[$i]->getText();
1914 - $sanitized_content = $instance->mxchat_sanitize_content_for_api($text); // Call via instance
2421 +public function mxchat_create_activation_page() {
2422 + // Include the new Pro & Extensions page template
2423 + require_once plugin_dir_path(__FILE__) . 'admin-pro-page.php';
1915 2424
1916 - if (!empty($sanitized_content)) {
1917 - $embedding_vector = $instance->mxchat_generate_embedding($sanitized_content); // Call via instance
1918 - if (is_array($embedding_vector)) {
1919 - $metadata = array(
1920 - 'document_type' => 'pdf',
1921 - 'total_pages' => $total_pages,
1922 - 'current_page' => $i + 1,
1923 - 'prev_page' => $i > 0 ? $i : null,
1924 - 'next_page' => $i < ($total_pages - 1) ? $i + 2 : null,
1925 - 'source_url' => $pdf_url
1926 - );
2425 + // Get addons configuration from the MxChat_Addons class
2426 + require_once plugin_dir_path(__FILE__) . 'class-mxchat-addons.php';
2427 + $addons_instance = new MxChat_Addons();
2428 + $addons_config = $addons_instance->get_addons_config();
1927 2429
1928 - $content_with_metadata = wp_json_encode($metadata) . "\n---\n" . $sanitized_content;
1929 - $page_url = esc_url($pdf_url . "#page=" . ($i + 1));
2430 + // Render the consolidated Pro & Extensions page
2431 + mxchat_render_pro_page($this, $addons_config);
2432 +}
1930 2433
1931 - $options = get_option('mxchat_options');
1932 - MxChat_Utils::submit_content_to_db($content_with_metadata, $page_url, $options['api_key']);
1933 - }
1934 - }
2434 +/**
2435 + * Check if current activation is linked to a domain
2436 + * This checks YOUR website's database, not the user's local database
2437 + */
2438 +public function is_current_activation_linked($domain) {
2439 + $license_key = get_option('mxchat_activation_key');
2440 + $email = get_option('mxchat_pro_email');
1935 2441
1936 - // Update progress with sanitized data
1937 - $status['processed_pages'] = absint($i + 1);
1938 - $status['last_update'] = time();
1939 - set_transient($status_key, array_map('sanitize_text_field', $status), DAY_IN_SECONDS);
1940 - }
2442 + if (empty($license_key) || empty($email)) {
2443 + return false;
2444 + }
1941 2445
1942 - // Schedule next batch if needed
1943 - if ($end_page < $total_pages) {
1944 - wp_schedule_single_event(time() + $batch_pause, 'mxchat_process_pdf_pages', array(
1945 - 'pdf_path' => $pdf_path,
1946 - 'pdf_url' => $pdf_url,
1947 - 'total_pages' => $total_pages,
1948 - 'batch_size' => $batch_size,
1949 - 'batch_pause' => $batch_pause
1950 - ));
1951 - } else {
1952 - // Processing complete
1953 - $status['status'] = 'complete';
1954 - set_transient($status_key, array_map('sanitize_text_field', $status), DAY_IN_SECONDS);
1955 - if (file_exists($pdf_path)) {
1956 - wp_delete_file($pdf_path);
1957 - }
1958 - }
2446 + // Check with YOUR website's API
2447 + $response = wp_remote_post('https://mxchat.ai/mxchat-api/check-domain', array(
2448 + 'body' => array(
2449 + 'license_key' => $license_key,
2450 + 'email' => $email,
2451 + 'domain' => $domain
2452 + ),
2453 + 'timeout' => 10,
2454 + 'sslverify' => false
2455 + ));
1959 2456
1960 - } catch (\Exception $e) {
1961 - $status['status'] = 'error';
1962 - $status['error'] = sanitize_text_field($e->getMessage());
1963 - set_transient($status_key, array_map('sanitize_text_field', $status), DAY_IN_SECONDS);
1964 - if (file_exists($pdf_path)) {
1965 - wp_delete_file($pdf_path);
1966 - }
1967 - }
1968 -}
1969 -public function get_pdf_processing_status($pdf_url) {
1970 - $pdf_url = esc_url_raw($pdf_url);
1971 - $status = get_transient(sanitize_key('mxchat_pdf_status_' . md5($pdf_url)));
1972 -
1973 - if (!$status || !is_array($status)) {
2457 + if (is_wp_error($response)) {
1974 2458 return false;
1975 2459 }
1976 2460
1977 - return array(
1978 - 'total_pages' => absint($status['total_pages']),
1979 - 'processed_pages' => absint($status['processed_pages']),
1980 - 'percentage' => ($status['total_pages'] > 0)
1981 - ? round((absint($status['processed_pages']) / absint($status['total_pages'])) * 100)
1982 - : 0,
1983 - 'status' => sanitize_text_field($status['status']),
1984 - 'last_update' => human_time_diff(absint($status['last_update']), time()) . ' ' . esc_html__('ago', 'mxchat')
1985 - );
2461 + $body = json_decode(wp_remote_retrieve_body($response), true);
2462 + return isset($body['success']) && $body['success'] && isset($body['data']['linked']) && $body['data']['linked'];
1986 2463 }
1987 -public function mxchat_handle_sitemap_submission() {
1988 - // Check if the form was submitted and verify permissions
1989 - if (!isset($_POST['submit_sitemap']) || !current_user_can('manage_options')) {
1990 - wp_die(esc_html__('Unauthorized access', 'mxchat'));
1991 - }
1992 2464
1993 - // Verify nonce
1994 - check_admin_referer('mxchat_submit_sitemap_action', 'mxchat_submit_sitemap_nonce');
1995 2465
1996 - // Validate URL
1997 - if (!isset($_POST['sitemap_url']) || empty($_POST['sitemap_url'])) {
1998 - set_transient('mxchat_admin_notice_error',
1999 - esc_html__('Please provide a valid URL.', 'mxchat'),
2000 - 30
2001 - );
2002 - wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
2003 - exit;
2466 +public function mxchat_actions_page_html() {
2467 + if (!current_user_can('manage_options')) {
2468 + return;
2004 2469 }
2005 2470
2006 - $submitted_url = esc_url_raw($_POST['sitemap_url']);
2007 - $response = wp_remote_get($submitted_url, array('timeout' => 30));
2471 + global $wpdb;
2472 + $table_name = $wpdb->prefix . 'mxchat_intents';
2008 2473
2009 - if (is_wp_error($response) || wp_remote_retrieve_response_code($response) !== 200) {
2010 - $error_message = is_wp_error($response) ? $response->get_error_message() : __('Failed to fetch URL', 'mxchat');
2011 - set_transient('mxchat_admin_notice_error',
2012 - sprintf(
2013 - esc_html__('Failed to fetch the URL: %s', 'mxchat'),
2014 - esc_html($error_message)
2015 - ),
2016 - 30
2017 - );
2018 - wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
2019 - exit;
2020 - }
2474 + // Get stats for dashboard
2475 + $total_actions = $wpdb->get_var("SELECT COUNT(*) FROM $table_name");
2476 + $enabled_actions = $wpdb->get_var("SELECT COUNT(*) FROM $table_name WHERE enabled = 1");
2477 + $disabled_actions = $total_actions - $enabled_actions;
2021 2478
2022 - $content_type = wp_remote_retrieve_header($response, 'content-type');
2023 - $body_content = wp_remote_retrieve_body($response);
2479 + // Get unique action types count
2480 + $action_types_count = $wpdb->get_var("SELECT COUNT(DISTINCT callback_function) FROM $table_name");
2024 2481
2025 - if (empty($body_content)) {
2026 - set_transient('mxchat_admin_notice_error',
2027 - esc_html__('Empty response received from URL.', 'mxchat'),
2028 - 30
2029 - );
2030 - wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
2031 - exit;
2482 + // Get action type distribution
2483 + $type_distribution_raw = $wpdb->get_results("SELECT callback_function, COUNT(*) as count FROM $table_name GROUP BY callback_function ORDER BY count DESC LIMIT 10");
2484 + $available_callbacks = $this->mxchat_get_available_callbacks();
2485 + $callback_groups = $this->mxchat_get_available_callbacks(true, true);
2486 +
2487 + $action_type_distribution = array();
2488 + foreach ($type_distribution_raw as $row) {
2489 + $label = isset($available_callbacks[$row->callback_function]['label'])
2490 + ? $available_callbacks[$row->callback_function]['label']
2491 + : $row->callback_function;
2492 + $action_type_distribution[$label] = $row->count;
2032 2493 }
2033 2494
2034 - // Handle PDF URL
2035 - if ($this->is_pdf_url($submitted_url, $response)) {
2036 - $result = $this->handle_pdf_for_knowledge_base($submitted_url, $response);
2495 + // Prepare page data
2496 + $page_data = array(
2497 + 'total_actions' => $total_actions,
2498 + 'enabled_actions' => $enabled_actions,
2499 + 'disabled_actions' => $disabled_actions,
2500 + 'action_types_count' => $action_types_count,
2501 + 'action_type_distribution' => $action_type_distribution,
2502 + 'available_callbacks' => $available_callbacks,
2503 + 'callback_groups' => $callback_groups,
2504 + );
2037 2505
2038 - if ($result === 'scheduled') {
2039 - set_transient(
2040 - 'mxchat_last_pdf_url',
2041 - sanitize_text_field($submitted_url),
2042 - DAY_IN_SECONDS
2043 - );
2044 - set_transient('mxchat_admin_notice_info',
2045 - esc_html__('PDF processing has started in the background. You can check the progress in the Knowledge Base section.', 'mxchat'),
2046 - 30
2047 - );
2048 - } else {
2049 - set_transient('mxchat_admin_notice_error',
2050 - esc_html__('Failed to start PDF processing. Please try again.', 'mxchat'),
2051 - 30
2052 - );
2053 - }
2506 + // Include and render the new template
2507 + require_once plugin_dir_path(__FILE__) . 'admin-actions-page.php';
2508 + mxchat_render_actions_page($this, $page_data);
2509 +}
2054 2510
2055 - wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
2056 - exit;
2057 - }
2511 +/**
2512 + * LEGACY HTML - Preserved below for reference, to be removed in future update
2513 + */
2514 +function mxchat_actions_page_legacy_html() {
2515 + // This function is deprecated and no longer used
2516 + // The new template is in includes/admin-actions-page.php
2517 + ?>
2518 + <div class="wrap mxchat-wrapper">
2519 + <!-- Hero Section -->
2520 + <div class="mxchat-hero">
2521 + <h1 class="mxchat-main-title">
2522 + <span class="mxchat-gradient-text">Actions</span> Manager
2523 + </h1>
2524 + <p class="mxchat-hero-subtitle">
2525 + <?php esc_html_e('Create and manage custom actions to enhance your chatbot\'s capabilities.', 'mxchat'); ?>
2526 + </p>
2527 + </div>
2058 2528
2059 - // Handle Sitemap XML
2060 - if (strpos($content_type, 'xml') !== false || strpos($body_content, '<urlset') !== false) {
2061 - libxml_use_internal_errors(true);
2062 - $xml = simplexml_load_string($body_content);
2063 - $xml_errors = libxml_get_errors();
2064 - libxml_clear_errors();
2529 + <!-- Actions Header with Search and Filter -->
2530 + <div class="mxchat-actions-header">
2531 + <div class="mxchat-actions-filters">
2532 + <form method="get" class="mxchat-search-form">
2533 + <input type="hidden" name="page" value="mxchat-actions">
2534 + <div class="mxchat-search-group">
2535 + <span class="dashicons dashicons-search"></span>
2536 + <input type="text" name="s" class="mxchat-search-input"
2537 + placeholder="<?php esc_attr_e('Search Actions', 'mxchat'); ?>"
2538 + value="<?php echo esc_attr($search_term); ?>">
2539 + </div>
2540 + <select name="callback_filter" class="mxchat-action-filter">
2541 + <option value=""><?php esc_html_e('All Action Types', 'mxchat'); ?></option>
2542 + <?php foreach ($available_callbacks as $function => $callback_data) :
2543 + $label = $callback_data['label']; ?>
2544 + <option value="<?php echo esc_attr($function); ?>"
2545 + <?php selected($callback_filter, $function); ?>>
2546 + <?php echo esc_html($label); ?>
2547 + </option>
2548 + <?php endforeach; ?>
2549 + </select>
2550 + <button type="submit" class="mxchat-button-secondary">
2551 + <?php esc_html_e('Filter', 'mxchat'); ?>
2552 + </button>
2553 + </form>
2554 + </div>
2555 + <div class="mxchat-actions-controls">
2556 + <button type="button" id="mxchat-add-action-btn" class="mxchat-button-primary">
2557 + <span class="dashicons dashicons-plus-alt"></span>
2558 + <?php esc_html_e('Add New Action', 'mxchat'); ?>
2559 + </button>
2560 + </div>
2561 + </div>
2065 2562
2066 - if ($xml === false || !empty($xml_errors)) {
2067 - set_transient('mxchat_admin_notice_error',
2068 - esc_html__('Invalid sitemap XML. Please provide a valid sitemap.', 'mxchat'),
2069 - 30
2070 - );
2071 - wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
2072 - exit;
2073 - }
2563 + <!-- Actions Grid Layout - All actions in a single grid -->
2564 + <div class="mxchat-actions-grid">
2565 + <div class="mxchat-cards-container">
2566 + <?php if (!empty($actions)) : ?>
2567 + <?php foreach ($actions as $action) :
2568 + $callback_function = $action->callback_function;
2569 + $callback_label = isset($available_callbacks[$callback_function]['label'])
2570 + ? $available_callbacks[$callback_function]['label']
2571 + : $callback_function;
2572 + $threshold_value = isset($action->similarity_threshold)
2573 + ? round($action->similarity_threshold * 100)
2574 + : 85;
2074 2575
2075 - $result = $this->handle_sitemap_for_knowledge_base($xml, $submitted_url);
2576 + // Check if this is a form action
2577 + $is_form_action = strpos($action->intent_label, 'Form ') === 0;
2076 2578
2077 - if ($result === 'scheduled') {
2078 - set_transient(
2079 - 'mxchat_last_sitemap_url',
2080 - sanitize_text_field($submitted_url),
2081 - DAY_IN_SECONDS
2082 - );
2083 - set_transient('mxchat_admin_notice_info',
2084 - esc_html__('Sitemap processing has started in the background. You can check the progress in the Knowledge Base section.', 'mxchat'),
2085 - 30
2086 - );
2087 - } else {
2088 - set_transient('mxchat_admin_notice_error',
2089 - esc_html__('Failed to start sitemap processing. Please try again.', 'mxchat'),
2090 - 30
2091 - );
2092 - }
2579 + // Get action status (enabled/disabled) - default to true if column doesn't exist
2580 + $is_enabled = isset($action->enabled) ? (bool)$action->enabled : true;
2581 +
2582 + // Get enabled bots for display
2583 + $enabled_bots = [];
2584 + if (isset($action->enabled_bots) && !empty($action->enabled_bots)) {
2585 + $enabled_bots = json_decode($action->enabled_bots, true);
2586 + if (!is_array($enabled_bots)) {
2587 + $enabled_bots = ['default'];
2588 + }
2589 + } else {
2590 + $enabled_bots = ['default']; // Backward compatibility
2591 + }
2592 + ?>
2593 + <div class="mxchat-action-card <?php echo $is_form_action ? 'mxchat-form-action' : ''; ?>">
2594 + <div class="mxchat-card-header">
2595 + <div class="mxchat-card-title"><?php echo esc_html($action->intent_label); ?></div>
2596 + <div class="mxchat-card-toggle">
2597 + <label class="mxchat-switch">
2598 + <input type="checkbox" class="mxchat-action-toggle"
2599 + data-action-id="<?php echo esc_attr($action->id); ?>"
2600 + <?php checked($is_enabled); ?>>
2601 + <span class="mxchat-slider round"></span>
2602 + </label>
2603 + </div>
2604 + </div>
2093 2605
2094 - wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
2095 - exit;
2096 - }
2606 + <div class="mxchat-card-body">
2607 + <div class="mxchat-card-description">
2608 + <strong><?php esc_html_e('Type:', 'mxchat'); ?></strong>
2609 + <?php echo esc_html($callback_label); ?>
2610 + </div>
2097 2611
2098 - // Handle Regular URL
2099 - $page_content = $this->mxchat_extract_main_content($body_content);
2100 - $sanitized_content = $this->mxchat_sanitize_content_for_api($page_content);
2612 + <div class="mxchat-card-phrases">
2613 + <strong><?php esc_html_e('Trigger phrases:', 'mxchat'); ?></strong>
2614 + <div class="mxchat-phrases-preview">
2615 + <?php
2616 + // Check if the helper function exists, otherwise use a simple substring
2617 + if (method_exists($this, 'get_trimmed_phrases')) {
2618 + echo esc_html($this->get_trimmed_phrases($action->phrases));
2619 + } else {
2620 + echo esc_html(strlen($action->phrases) > 100 ?
2621 + substr($action->phrases, 0, 97) . '...' :
2622 + $action->phrases);
2623 + }
2624 + ?>
2625 + </div>
2626 + </div>
2101 2627
2102 - if (empty($sanitized_content)) {
2103 - set_transient('mxchat_admin_notice_error',
2104 - esc_html__('No valid content found on the provided URL.', 'mxchat'),
2105 - 30
2106 - );
2107 - wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
2108 - exit;
2109 - }
2628 + <div class="mxchat-threshold-control">
2629 + <div class="mxchat-threshold-label">
2630 + <?php esc_html_e('Similarity Threshold:', 'mxchat'); ?>
2631 + <span class="mxchat-threshold-value"><?php echo esc_html($threshold_value); ?>%</span>
2632 + </div>
2633 + </div>
2634 +
2635 + <!-- Bot Availability Display (Read-only) -->
2636 + <div class="mxchat-card-bots">
2637 + <strong><?php esc_html_e('Assigned bots:', 'mxchat'); ?></strong>
2638 + <div class="mxchat-bot-badges">
2639 + <?php
2640 + foreach ($enabled_bots as $bot_id) {
2641 + if ($bot_id === 'default') {
2642 + echo '<span class="mxchat-bot-badge">' . esc_html__('Default Bot', 'mxchat') . '</span>';
2643 + } else {
2644 + // Try to get bot name from multi-bot manager
2645 + if (class_exists('MxChat_Multi_Bot_Core_Manager')) {
2646 + $multi_bot_manager = MxChat_Multi_Bot_Core_Manager::get_instance();
2647 + $available_bots = $multi_bot_manager->get_available_bots();
2648 + $bot_name = isset($available_bots[$bot_id]) ? $available_bots[$bot_id] : $bot_id;
2649 + echo '<span class="mxchat-bot-badge">' . esc_html($bot_name) . '</span>';
2650 + } else {
2651 + echo '<span class="mxchat-bot-badge">' . esc_html($bot_id) . '</span>';
2652 + }
2653 + }
2654 + }
2655 + ?>
2656 + </div>
2657 + </div>
2658 + </div>
2110 2659
2111 - $embedding_vector = $this->mxchat_generate_embedding($sanitized_content);
2112 - if (is_array($embedding_vector)) {
2113 - MxChat_Utils::submit_content_to_db(
2114 - $sanitized_content,
2115 - $submitted_url,
2116 - $this->options['api_key']
2117 - );
2118 - set_transient('mxchat_admin_notice_success',
2119 - esc_html__('URL content successfully submitted!', 'mxchat'),
2120 - 30
2121 - );
2122 - } else {
2123 - set_transient('mxchat_admin_notice_error',
2124 - esc_html__('Failed to generate embedding for the URL content. Please check your API key and try again.', 'mxchat'),
2125 - 30
2126 - );
2127 - }
2660 + <div class="mxchat-card-footer">
2661 + <?php
2662 + // Check if it's a form action
2663 + $is_form_action = preg_match('/Form (\d+)/', $action->intent_label, $form_matches);
2128 2664
2129 - wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
2130 - exit;
2131 -}
2132 -private function handle_sitemap_for_knowledge_base($xml, $sitemap_url) {
2133 - if (!current_user_can('manage_options')) {
2134 - //error_log(esc_html__('Unauthorized sitemap processing attempt', 'mxchat'));
2135 - return false;
2136 - }
2665 + // Check if it's a recommendation flow action
2666 + $is_flow_action = preg_match('/Recommendation Flow (\d+)/', $action->intent_label, $flow_matches);
2137 2667
2138 - try {
2139 - $sitemap_url = esc_url_raw($sitemap_url);
2668 + if ($is_form_action) {
2669 + $form_id = isset($form_matches[1]) ? $form_matches[1] : '';
2670 + ?>
2671 + <a href="<?php echo esc_url(admin_url('admin.php?page=mxchat-forms&action=edit&form_id=' . $form_id)); ?>"
2672 + class="mxchat-button-primary">
2673 + <span class="dashicons dashicons-feedback"></span>
2674 + <?php esc_html_e('Edit Form', 'mxchat'); ?>
2675 + </a>
2676 + <?php } elseif ($is_flow_action) {
2677 + ?>
2678 + <a href="<?php echo esc_url(admin_url('admin.php?page=mxchat-smart-recommender')); ?>"
2679 + class="mxchat-button-primary">
2680 + <span class="dashicons dashicons-list-view"></span>
2681 + <?php esc_html_e('Manage Flows', 'mxchat'); ?>
2682 + </a>
2683 + <?php } else { ?>
2684 + <button type="button"
2685 + class="mxchat-button-secondary mxchat-edit-button"
2686 + data-action-id="<?php echo esc_attr($action->id); ?>"
2687 + data-phrases="<?php echo esc_attr($action->phrases); ?>"
2688 + data-label="<?php echo esc_attr($action->intent_label); ?>"
2689 + data-threshold="<?php echo esc_attr(round($action->similarity_threshold * 100)); ?>"
2690 + data-callback-function="<?php echo esc_attr($action->callback_function); ?>"
2691 + data-enabled-bots="<?php echo esc_attr(json_encode($enabled_bots)); ?>">
2692 + <span class="dashicons dashicons-edit"></span>
2693 + <?php esc_html_e('Edit', 'mxchat'); ?>
2694 + </button>
2695 + <form method="post"
2696 + action="<?php echo esc_url(admin_url('admin-post.php')); ?>"
2697 + class="mxchat-delete-form"
2698 + onsubmit="return confirm('<?php esc_attr_e('Are you sure you want to delete this action?', 'mxchat'); ?>');">
2699 + <?php wp_nonce_field('mxchat_delete_intent_nonce'); ?>
2700 + <input type="hidden" name="action" value="mxchat_delete_intent">
2701 + <input type="hidden" name="intent_id" value="<?php echo esc_attr($action->id); ?>">
2702 + <button type="submit" class="mxchat-button-text mxchat-delete-button">
2703 + <span class="dashicons dashicons-trash"></span>
2704 + <?php esc_html_e('Delete', 'mxchat'); ?>
2705 + </button>
2706 + </form>
2707 + <?php } ?>
2708 + </div>
2709 + </div>
2710 + <?php endforeach; ?>
2711 + <?php else : ?>
2712 + <!-- If no actions found -->
2713 + <div class="mxchat-no-actions">
2714 + <div class="mxchat-empty-state">
2715 + <span class="dashicons dashicons-format-chat"></span>
2716 + <h2><?php esc_html_e('No actions found', 'mxchat'); ?></h2>
2717 + <p><?php esc_html_e('Get started by creating your first action to enhance your chatbot.', 'mxchat'); ?></p>
2718 + <button type="button" id="mxchat-create-first-action" class="mxchat-button-primary">
2719 + <?php esc_html_e('Create Your First Action', 'mxchat'); ?>
2720 + </button>
2721 + </div>
2722 + </div>
2723 + <?php endif; ?>
2724 + </div>
2725 + </div>
2140 2726
2141 - if (!$xml || !is_object($xml)) {
2142 - throw new Exception(__('Invalid XML object provided', 'mxchat'));
2143 - }
2727 + <?php if ($total_pages > 1) : ?>
2728 + <div class="mxchat-pagination">
2729 + <?php
2730 + echo paginate_links(array(
2731 + 'base' => add_query_arg('paged', '%#%'),
2732 + 'format' => '',
2733 + 'prev_text' => __('&laquo; Previous', 'mxchat'),
2734 + 'next_text' => __('Next &raquo;', 'mxchat'),
2735 + 'total' => $total_pages,
2736 + 'current' => $page
2737 + ));
2738 + ?>
2739 + </div>
2740 + <?php endif; ?>
2144 2741
2145 - $urls = [];
2146 - foreach ($xml->url as $url_element) {
2147 - $url = esc_url_raw((string)$url_element->loc);
2148 - if ($url) {
2149 - $urls[] = $url;
2150 - }
2151 - }
2742 +<!-- Add/Edit Action Modal with Step-Based Approach -->
2743 +<!-- Complete Modal HTML with Defined Groups Variable -->
2744 +<div id="mxchat-action-modal" class="mxchat-modal" style="display: none;">
2745 + <div class="mxchat-modal-content">
2746 + <span class="mxchat-modal-close">&times;</span>
2152 2747
2153 - $total_urls = absint(count($urls));
2748 + <form id="mxchat-action-form" method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>">
2749 + <!-- Dynamic nonce field -->
2750 + <div id="action-nonce-container">
2751 + <?php wp_nonce_field('mxchat_add_intent_nonce', 'add_intent_nonce'); ?>
2752 + </div>
2753 + <input type="hidden" name="action" id="form_action_type" value="mxchat_add_intent">
2754 + <input type="hidden" name="intent_id" id="edit_action_id" value="">
2755 + <input type="hidden" name="callback_function" id="callback_function" value="">
2154 2756
2155 - if ($total_urls < 1) {
2156 - throw new Exception(__('No valid URLs found in sitemap', 'mxchat'));
2157 - }
2757 + <!-- Step 1: Action Type Selection -->
2758 + <div id="mxchat-action-step-1" class="mxchat-action-step active">
2759 + <div class="mxchat-step-indicator">
2760 + <div class="mxchat-step-number">1</div>
2761 + <div class="mxchat-step-title"><?php esc_html_e('Select Action Type', 'mxchat'); ?></div>
2762 + </div>
2158 2763
2159 - wp_schedule_single_event(time(), 'mxchat_process_sitemap_urls', array(
2160 - 'urls' => $urls,
2161 - 'sitemap_url' => $sitemap_url,
2162 - 'total_urls' => $total_urls,
2163 - 'batch_size' => absint(10),
2164 - 'batch_pause' => absint(5)
2165 - ));
2764 + <div id="mxchat-action-type-selector" class="mxchat-action-type-selector">
2765 + <div class="mxchat-action-type-search">
2766 + <span class="dashicons dashicons-search"></span>
2767 + <input type="text" id="action-type-search" placeholder="<?php esc_attr_e('Search action types...', 'mxchat'); ?>" class="mxchat-action-type-search-input">
2768 + </div>
2166 2769
2167 - $status_data = array(
2168 - 'total_urls' => $total_urls,
2169 - 'processed_urls' => 0,
2170 - 'status' => 'processing',
2171 - 'last_update' => time()
2172 - );
2770 + <?php
2771 + // Get the callbacks - IMPORTANT: Define the $groups variable here
2772 + $groups = $this->mxchat_get_available_callbacks(true, true);
2773 + ?>
2173 2774
2174 - set_transient(
2175 - sanitize_key('mxchat_sitemap_status_' . md5($sitemap_url)),
2176 - array_map('sanitize_text_field', $status_data),
2177 - DAY_IN_SECONDS
2178 - );
2775 + <div class="mxchat-action-type-categories">
2776 + <button type="button" class="mxchat-category-button active" data-category="all"><?php esc_html_e('All', 'mxchat'); ?></button>
2777 + <?php
2778 + // Get unique categories from the defined groups
2779 + foreach ($groups as $group_label => $group_callbacks) :
2780 + $category_slug = sanitize_title($group_label);
2781 + ?>
2782 + <button type="button" class="mxchat-category-button" data-category="<?php echo esc_attr($category_slug); ?>"><?php echo esc_html($group_label); ?></button>
2783 + <?php endforeach; ?>
2784 + </div>
2179 2785
2180 - return __('scheduled', 'mxchat');
2786 + <div class="mxchat-action-types-grid">
2787 + <?php
2788 + // Generate action cards from available callbacks
2789 + foreach ($groups as $group_label => $group_callbacks) :
2790 + $category_slug = sanitize_title($group_label);
2181 2791
2182 - } catch (\Exception $e) {
2183 - //error_log(sprintf(esc_html__('Error preparing sitemap for processing: %s', 'mxchat'), esc_html($e->getMessage())));
2184 - return false;
2185 - }
2186 -}
2187 -public static function process_sitemap_urls_cron($urls, $sitemap_url, $total_urls, $batch_size, $batch_pause) {
2188 - // Validate inputs
2189 - $sitemap_url = esc_url_raw($sitemap_url);
2190 - $total_urls = absint($total_urls);
2191 - $batch_size = absint($batch_size);
2192 - $batch_pause = absint($batch_pause);
2792 + foreach ($group_callbacks as $function => $data) :
2793 + $label = $data['label'];
2794 + $pro_only = $data['pro_only'];
2795 + $icon = isset($data['icon']) ? $data['icon'] : 'admin-generic';
2796 + $description = isset($data['description']) ? $data['description'] : '';
2797 + $is_addon = isset($data['addon']) && $data['addon'] !== false;
2798 + $addon_name = isset($data['addon_name']) ? $data['addon_name'] : '';
2799 + $is_installed = isset($data['installed']) ? $data['installed'] : true;
2193 2800
2194 - if (!is_array($urls) || empty($urls)) {
2195 - return;
2196 - }
2801 + // Determine card status and styling
2802 + $card_class = 'mxchat-action-type-card';
2803 + $icon_class = 'mxchat-action-type-icon';
2804 + $status_badge = '';
2197 2805
2198 - try {
2199 - $status_key = sanitize_key('mxchat_sitemap_status_' . md5($sitemap_url));
2200 - $status = get_transient($status_key);
2806 + if ($pro_only && !$this->is_activated) {
2807 + // Pro feature but no Pro license
2808 + $icon_class .= ' pro-feature';
2809 + $status_badge = '<span class="mxchat-pro-badge">' . esc_html__('Pro', 'mxchat') . '</span>';
2810 + }
2201 2811
2202 - if (!$status || !is_array($status)) {
2203 - throw new Exception('Invalid status data retrieved from transient');
2204 - }
2812 + if ($is_addon && !$is_installed) {
2813 + // Add-on not installed
2814 + $card_class .= ' not-installed';
2815 + $status_badge .= '<span class="mxchat-addon-badge">' . esc_html__('Add-on Required', 'mxchat') . '</span>';
2816 + }
2205 2817
2206 - $start_url = absint($status['processed_urls']);
2207 - $end_url = min($start_url + $batch_size, $total_urls);
2208 - $instance = new self();
2818 + // Default description if none provided
2819 + if (empty($description)) {
2820 + $description = sprintf(
2821 + esc_html__('Use the %s action in your chatbot', 'mxchat'),
2822 + $label
2823 + );
2824 + }
2825 + ?>
2826 + <div class="<?php echo esc_attr($card_class); ?>"
2827 + data-category="<?php echo esc_attr($category_slug); ?>"
2828 + data-value="<?php echo esc_attr($function); ?>"
2829 + data-label="<?php echo esc_attr($label); ?>"
2830 + data-pro="<?php echo $pro_only ? 'true' : 'false'; ?>"
2831 + data-addon="<?php echo esc_attr($is_addon ? $data['addon'] : ''); ?>"
2832 + data-installed="<?php echo $is_installed ? 'true' : 'false'; ?>">
2833 + <div class="<?php echo esc_attr($icon_class); ?>">
2834 + <span class="dashicons dashicons-<?php echo esc_attr($icon); ?>"></span>
2835 + </div>
2836 + <div class="mxchat-action-type-info">
2837 + <h4><?php echo esc_html($label); ?></h4>
2838 + <p><?php echo esc_html($description); ?></p>
2839 + <?php if (!empty($status_badge)) : ?>
2840 + <?php echo $status_badge; ?>
2841 + <?php endif; ?>
2209 2842
2210 - // Track failures in batch
2211 - $batch_stats = [
2212 - 'processed' => 0,
2213 - 'failed' => 0,
2214 - 'last_error' => ''
2215 - ];
2843 + <?php if ($is_addon && !$is_installed) : ?>
2844 + <div class="mxchat-addon-info">
2845 + <?php echo esc_html(sprintf(
2846 + __('Requires %s', 'mxchat'),
2847 + $addon_name
2848 + )); ?>
2849 + </div>
2850 + <?php endif; ?>
2851 + </div>
2852 + </div>
2853 + <?php endforeach;
2854 + endforeach; ?>
2855 + </div>
2856 + </div>
2216 2857
2217 - for ($i = $start_url; $i < $end_url; $i++) {
2218 - $page_url = esc_url_raw($urls[$i]);
2219 - $page_response = wp_remote_get($page_url);
2858 + <div class="mxchat-modal-actions">
2859 + <button type="button" class="mxchat-button-secondary mxchat-modal-cancel">
2860 + <?php esc_html_e('Cancel', 'mxchat'); ?>
2861 + </button>
2862 + </div>
2863 + </div>
2220 2864
2221 - if (is_wp_error($page_response) || wp_remote_retrieve_response_code($page_response) !== 200) {
2222 - $batch_stats['failed']++;
2223 - continue;
2224 - }
2865 + <!-- Step 2: Action Configuration -->
2866 + <div id="mxchat-action-step-2" class="mxchat-action-step">
2867 + <div class="mxchat-step-indicator">
2868 + <div class="mxchat-step-number">2</div>
2869 + <div class="mxchat-step-title"><?php esc_html_e('Configure Action', 'mxchat'); ?></div>
2870 + </div>
2225 2871
2226 - $page_html = wp_remote_retrieve_body($page_response);
2227 - $page_content = $instance->mxchat_extract_main_content($page_html);
2228 - $sanitized_content = $instance->mxchat_sanitize_content_for_api($page_content);
2872 + <div class="mxchat-selected-action">
2873 + <button type="button" class="mxchat-back-button" id="mxchat-back-to-step-1">
2874 + <span class="dashicons dashicons-arrow-left-alt"></span>
2875 + <?php esc_html_e('Back to Action Types', 'mxchat'); ?>
2876 + </button>
2877 + <div class="mxchat-selected-action-info">
2878 + <div id="selected-action-icon" class="mxchat-action-type-icon">
2879 + <span class="dashicons dashicons-admin-generic"></span>
2880 + </div>
2881 + <div class="mxchat-selected-action-details">
2882 + <h3 id="selected-action-title"><?php esc_html_e('Selected Action', 'mxchat'); ?></h3>
2883 + <p id="selected-action-description"><?php esc_html_e('Configure this action for your chatbot', 'mxchat'); ?></p>
2884 + </div>
2885 + </div>
2886 + </div>
2229 2887
2230 - if (!empty($sanitized_content)) {
2231 - $embedding_vector = $instance->mxchat_generate_embedding($sanitized_content);
2888 + <div class="mxchat-form-group">
2889 + <label for="intent_label">
2890 + <?php esc_html_e('Action Label (For your reference only)', 'mxchat'); ?>
2891 + </label>
2892 + <input name="intent_label" type="text" id="intent_label" required
2893 + class="mxchat-intent-input"
2894 + placeholder="<?php esc_attr_e('Example: Newsletter Signup', 'mxchat'); ?>">
2895 + </div>
2232 2896
2233 - if (is_array($embedding_vector)) {
2234 - $options = get_option('mxchat_options');
2235 - $submission_result = MxChat_Utils::submit_content_to_db($sanitized_content, $page_url, $options['api_key']);
2897 + <div class="mxchat-form-group">
2898 + <label for="phrases">
2899 + <?php esc_html_e('Trigger Phrases (comma-separated)', 'mxchat'); ?>
2900 + </label>
2901 + <textarea name="phrases" id="action_phrases" rows="5" required
2902 + class="mxchat-intent-textarea"
2903 + placeholder="<?php esc_attr_e('Example: sign me up, subscribe me, I want to join, add me to the newsletter', 'mxchat'); ?>"></textarea>
2904 + </div>
2236 2905
2237 - if (is_wp_error($submission_result)) {
2238 - $batch_stats['failed']++;
2239 - $batch_stats['last_error'] = $submission_result->get_error_message();
2240 - continue;
2241 - }
2906 + <div class="mxchat-form-group">
2907 + <label for="similarity_threshold">
2908 + <?php esc_html_e('Similarity Threshold', 'mxchat'); ?>
2909 + <span class="mxchat-threshold-value-display">85%</span>
2910 + </label>
2911 + <div class="mxchat-slider-group modal-slider">
2912 + <input type="range"
2913 + name="similarity_threshold"
2914 + id="similarity_threshold"
2915 + min="10"
2916 + max="95"
2917 + value="85"
2918 + class="mxchat-intent-slider"
2919 + oninput="document.querySelector('.mxchat-threshold-value-display').textContent = this.value + '%'">
2920 + </div>
2921 + <div class="mxchat-threshold-hint">
2922 + <?php esc_html_e('Lower values (10-30) make the action trigger more easily. Higher values (70-95) require more exact matches.', 'mxchat'); ?>
2923 + </div>
2924 + </div>
2242 2925
2243 - $batch_stats['processed']++;
2244 - } else {
2245 - $batch_stats['failed']++;
2246 - $batch_stats['last_error'] = 'Failed to generate embedding. Please check OpenAI API key.';
2247 - }
2248 - }
2926 + <!-- Bot Selection Section -->
2927 + <div class="mxchat-form-group">
2928 + <label for="enabled_bots">
2929 + <?php esc_html_e('Which bots should we enable this action for?', 'mxchat'); ?>
2930 + </label>
2931 + <div class="mxchat-bot-selector">
2932 + <div class="mxchat-bot-option">
2933 + <label class="mxchat-checkbox-label">
2934 + <input type="checkbox"
2935 + name="enabled_bots[]"
2936 + value="default"
2937 + id="bot_default"
2938 + checked="checked">
2939 + <span class="mxchat-checkmark"></span>
2940 + <?php esc_html_e('Default Bot', 'mxchat'); ?>
2941 + </label>
2942 + </div>
2943 +
2944 + <?php if (class_exists('MxChat_Multi_Bot_Core_Manager')) :
2945 + $multi_bot_manager = MxChat_Multi_Bot_Core_Manager::get_instance();
2946 + $available_bots = $multi_bot_manager->get_available_bots();
2947 +
2948 + foreach ($available_bots as $bot_id => $bot_name) :
2949 + if ($bot_id === 'default') continue; // Skip default, already shown above
2950 + ?>
2951 + <div class="mxchat-bot-option">
2952 + <label class="mxchat-checkbox-label">
2953 + <input type="checkbox"
2954 + name="enabled_bots[]"
2955 + value="<?php echo esc_attr($bot_id); ?>"
2956 + id="bot_<?php echo esc_attr($bot_id); ?>">
2957 + <span class="mxchat-checkmark"></span>
2958 + <?php echo esc_html($bot_name); ?>
2959 + </label>
2960 + </div>
2961 + <?php
2962 + endforeach;
2963 + endif; ?>
2964 + </div>
2965 + </div>
2249 2966
2250 - $status['processed_urls'] = absint($i + 1);
2251 - $status['last_update'] = time();
2252 - $status['failed_urls'] = absint($status['failed_urls'] ?? 0) + $batch_stats['failed'];
2253 - $status['last_error'] = $batch_stats['last_error'];
2967 + <div class="mxchat-modal-actions">
2968 + <button type="button" class="mxchat-button-secondary mxchat-modal-cancel">
2969 + <?php esc_html_e('Cancel', 'mxchat'); ?>
2970 + </button>
2971 + <button type="submit" class="mxchat-button-primary" id="mxchat-save-action-btn">
2972 + <?php esc_html_e('Save Action', 'mxchat'); ?>
2973 + </button>
2974 + </div>
2975 + </div>
2976 + </form>
2977 + </div>
2978 +</div>
2254 2979
2255 - set_transient($status_key, array_map('sanitize_text_field', $status), DAY_IN_SECONDS);
2256 - }
2980 +<div id="mxchat-action-loading" class="mxchat-action-loading" style="display: none;">
2981 + <div class="mxchat-action-loading-spinner"></div>
2982 + <div class="mxchat-action-loading-text">
2983 + <?php esc_html_e('Saving action, please wait...', 'mxchat'); ?>
2984 + </div>
2985 +</div>
2986 + </div><!-- .mxchat-wrapper -->
2987 + <?php
2988 +}
2989 +private function get_trimmed_phrases($phrases, $max_length = 100) {
2990 + if (strlen($phrases) <= $max_length) {
2991 + return $phrases;
2992 + }
2257 2993
2258 - // If all URLs in this batch failed, stop processing
2259 - if ($batch_stats['processed'] === 0 && $batch_stats['failed'] > 0) {
2260 - $status['status'] = 'error';
2261 - $status['error'] = sprintf(
2262 - 'Processing stopped: %d consecutive failures. Last error: %s',
2263 - $batch_stats['failed'],
2264 - $batch_stats['last_error']
2265 - );
2266 - set_transient($status_key, array_map('sanitize_text_field', $status), DAY_IN_SECONDS);
2267 - return;
2268 - }
2994 + $trimmed = substr($phrases, 0, $max_length);
2995 + $last_comma = strrpos($trimmed, ',');
2269 2996
2270 - if ($end_url < $total_urls) {
2271 - wp_schedule_single_event(time() + $batch_pause, 'mxchat_process_sitemap_urls', array(
2272 - 'urls' => array_map('esc_url_raw', $urls),
2273 - 'sitemap_url' => $sitemap_url,
2274 - 'total_urls' => $total_urls,
2275 - 'batch_size' => $batch_size,
2276 - 'batch_pause' => $batch_pause,
2277 - ));
2278 - } else {
2279 - $status['status'] = 'complete';
2280 - set_transient($status_key, array_map('sanitize_text_field', $status), DAY_IN_SECONDS);
2281 - }
2282 - } catch (\Exception $e) {
2283 - $status['status'] = 'error';
2284 - $status['error'] = sanitize_text_field($e->getMessage());
2285 - set_transient($status_key, array_map('sanitize_text_field', $status), DAY_IN_SECONDS);
2997 + if ($last_comma !== false) {
2998 + $trimmed = substr($trimmed, 0, $last_comma);
2286 2999 }
3000 +
3001 + return $trimmed . '...';
2287 3002 }
3003 +public function mxchat_add_enabled_column_to_intents() {
3004 + global $wpdb;
3005 + $table_name = $wpdb->prefix . 'mxchat_intents';
2288 3006
2289 -public function get_sitemap_processing_status($sitemap_url) {
2290 - $sitemap_url = esc_url_raw($sitemap_url);
2291 - $status = get_transient(sanitize_key('mxchat_sitemap_status_' . md5($sitemap_url)));
3007 + // Check if the column already exists
3008 + $columns = $wpdb->get_results("SHOW COLUMNS FROM $table_name LIKE 'enabled'");
2292 3009
2293 - if (!$status || !is_array($status)) {
2294 - return false;
3010 + if (empty($columns)) {
3011 + // Add the column with default value of 1 (enabled)
3012 + $wpdb->query("ALTER TABLE $table_name ADD COLUMN enabled TINYINT(1) NOT NULL DEFAULT 1");
2295 3013 }
2296 -
2297 - return array(
2298 - 'total_urls' => absint($status['total_urls']),
2299 - 'processed_urls' => absint($status['processed_urls']),
2300 - 'failed_urls' => absint($status['failed_urls'] ?? 0),
2301 - 'percentage' => ($status['total_urls'] > 0)
2302 - ? round((absint($status['processed_urls']) / absint($status['total_urls'])) * 100)
2303 - : 0,
2304 - 'status' => sanitize_text_field($status['status']),
2305 - 'last_update' => human_time_diff(absint($status['last_update']), time()) . ' ' . esc_html__('ago', 'mxchat'),
2306 - 'error' => isset($status['error']) ? sanitize_text_field($status['error']) : '',
2307 - 'last_error' => isset($status['last_error']) ? sanitize_text_field($status['last_error']) : ''
2308 - );
2309 3014 }
2310 3015
2311 -public function mxchat_stop_processing() {
2312 - // Verify permissions
2313 - if (!current_user_can('manage_options')) {
2314 - wp_die(esc_html__('Unauthorized access', 'mxchat'));
3016 +public function mxchat_handle_delete_intent() {
3017 + if ( ! current_user_can( 'manage_options' ) ) {
3018 + wp_die( esc_html__('Unauthorized user', 'mxchat') );
2315 3019 }
2316 3020
2317 - // Verify nonce
2318 - check_admin_referer('mxchat_stop_processing_action', 'mxchat_stop_processing_nonce');
3021 + check_admin_referer('mxchat_delete_intent_nonce');
2319 3022
2320 - // Get the last sitemap URL and clear its transient
2321 - $sitemap_url = get_transient('mxchat_last_sitemap_url');
2322 - if ($sitemap_url) {
2323 - delete_transient('mxchat_sitemap_status_' . md5($sitemap_url));
2324 - delete_transient('mxchat_last_sitemap_url');
2325 - }
3023 + if (isset($_POST['intent_id'])) {
3024 + global $wpdb;
3025 + $table_name = $wpdb->prefix . 'mxchat_intents';
3026 + $intent_id = intval($_POST['intent_id']);
2326 3027
2327 - // Get the last PDF URL and clear its transient
2328 - $pdf_url = get_transient('mxchat_last_pdf_url');
2329 - if ($pdf_url) {
2330 - delete_transient('mxchat_pdf_status_' . md5($pdf_url));
2331 - delete_transient('mxchat_last_pdf_url');
3028 + $wpdb->delete($table_name, ['id' => $intent_id], ['%d']);
2332 3029 }
2333 3030
2334 - // Unschedule any pending sitemap events
2335 - $timestamp = wp_next_scheduled('mxchat_process_sitemap_urls');
2336 - if ($timestamp) {
2337 - wp_unschedule_event($timestamp, 'mxchat_process_sitemap_urls');
2338 - }
2339 -
2340 - // Redirect back with a success message
2341 - set_transient('mxchat_admin_notice_success',
2342 - esc_html__('Processing has been stopped successfully.', 'mxchat'),
2343 - 30
2344 - );
2345 - wp_safe_redirect(admin_url('admin.php?page=mxchat-prompts'));
3031 + wp_safe_redirect(admin_url('admin.php?page=mxchat-actions'));
2346 3032 exit;
2347 3033 }
2348 -private function mxchat_sanitize_content_for_api($content) {
2349 - // Remove script, style tags, and HTML comments
2350 - $content = preg_replace('/<script\b[^>]*>(.*?)<\/script>/is', '', $content);
2351 - $content = preg_replace('/<style\b[^>]*>(.*?)<\/style>/is', '', $content);
2352 - $content = preg_replace('/<!--(.|\s)*?-->/', '', $content);
3034 +public function mxchat_handle_edit_intent() {
3035 + // Security checks (nonce and permissions)
3036 + if (!current_user_can('manage_options')) {
3037 + wp_die(esc_html__('Unauthorized user', 'mxchat'));
3038 + }
3039 + check_admin_referer('mxchat_edit_intent');
2353 3040
2354 - // Remove all HTML tags and decode HTML entities
2355 - $content = wp_strip_all_tags($content);
2356 - $content = html_entity_decode($content, ENT_QUOTES | ENT_HTML5);
3041 + // Get POST data
3042 + $intent_id = isset($_POST['intent_id']) ? absint($_POST['intent_id']) : 0;
3043 + $intent_label = isset($_POST['intent_label']) ? sanitize_text_field($_POST['intent_label']) : '';
3044 + $phrases_input = isset($_POST['phrases']) ? sanitize_textarea_field($_POST['phrases']) : '';
3045 + $threshold_percentage = isset($_POST['similarity_threshold']) ? intval($_POST['similarity_threshold']) : 85;
3046 + $similarity_threshold = min(95, max(10, $threshold_percentage)) / 100; // Convert to 0.10–0.95
2357 3047
2358 - // Trim and normalize whitespace
2359 - $content = trim(preg_replace('/\s+/', ' ', $content));
3048 + // Handle enabled_bots
3049 + $enabled_bots = isset($_POST['enabled_bots']) ? $_POST['enabled_bots'] : array('default');
3050 + $enabled_bots = array_map('sanitize_text_field', $enabled_bots);
3051 +
3052 + // Ensure default is always included for backward compatibility
3053 + if (!in_array('default', $enabled_bots)) {
3054 + $enabled_bots[] = 'default';
3055 + }
3056 +
3057 + $enabled_bots_json = json_encode($enabled_bots);
2360 3058
2361 - return $content;
2362 -}
3059 + // Validate inputs
3060 + if (!$intent_id || empty($intent_label) || empty($phrases_input)) {
3061 + $this->handle_embedding_error(__('Invalid input. Please ensure all fields are filled out.', 'mxchat'));
3062 + return;
3063 + }
2363 3064
3065 + $phrases_array = array_map('sanitize_text_field', array_filter(array_map('trim', explode(',', $phrases_input))));
3066 + if (empty($phrases_array)) {
3067 + $this->handle_embedding_error(__('Please enter at least one valid phrase.', 'mxchat'));
3068 + return;
3069 + }
2364 3070
3071 + // Generate embeddings with improved error handling
3072 + $vectors = [];
3073 + $failed_phrases = [];
2365 3074
3075 + foreach ($phrases_array as $phrase) {
3076 + $embedding_vector = $this->mxchat_generate_embedding($phrase, $this->options['api_key']);
3077 + if (is_array($embedding_vector)) {
3078 + $vectors[] = $embedding_vector;
3079 + } else {
3080 + $failed_phrases[] = $phrase;
3081 + }
3082 + }
2366 3083
2367 -public function mxchat_handle_product_change($post_id, $post, $update) {
2368 - if ($post->post_type !== 'product') {
3084 + if (!empty($failed_phrases)) {
3085 + $this->handle_embedding_error(
3086 + sprintf(
3087 + __('Error generating embeddings for phrases: %s. Check your embedding API.', 'mxchat'),
3088 + implode(', ', $failed_phrases)
3089 + )
3090 + );
2369 3091 return;
2370 3092 }
2371 3093
2372 - if ($post->post_status === 'publish') {
2373 - add_action('shutdown', function() use ($post_id) {
2374 - $product = wc_get_product($post_id);
2375 - if ($product) {
2376 - $this->mxchat_store_product_embedding($product);
2377 - }
2378 - });
3094 + if (empty($vectors)) {
3095 + $this->handle_embedding_error(__('No valid embeddings generated. Please check your phrases.', 'mxchat'));
3096 + return;
2379 3097 }
2380 -}
2381 3098
2382 -private function mxchat_store_product_embedding($product) {
2383 - if (!isset($this->options['enable_woocommerce_integration']) ||
2384 - !in_array($this->options['enable_woocommerce_integration'], ['1', 'on'])) {
2385 - return;
2386 - }
3099 + $combined_vector = $this->mxchat_average_vectors($vectors);
3100 + $serialized_vector = maybe_serialize($combined_vector);
2387 3101
2388 - $source_url = get_permalink($product->get_id());
3102 + // Update the database
3103 + global $wpdb;
3104 + $table_name = $wpdb->prefix . 'mxchat_intents';
2389 3105
2390 - // Build description
2391 - $regular_price = $product->get_regular_price();
2392 - $sale_price = $product->get_sale_price();
2393 - $description = $product->get_description() . "\n\n" .
2394 - esc_html__('Short Description:', 'mxchat') . " " . $product->get_short_description() . "\n" .
2395 - esc_html__('Price:', 'mxchat') . " " . $regular_price . "\n" .
2396 - esc_html__('Sale Price:', 'mxchat') . " " . ($sale_price ?: esc_html__('N/A', 'mxchat')) . "\n" .
2397 - esc_html__('SKU:', 'mxchat') . " " . $product->get_sku();
3106 + $result = $wpdb->update(
3107 + $table_name,
3108 + array(
3109 + 'intent_label' => $intent_label,
3110 + 'phrases' => implode(', ', $phrases_array),
3111 + 'embedding_vector' => $serialized_vector,
3112 + 'similarity_threshold' => $similarity_threshold,
3113 + 'enabled_bots' => $enabled_bots_json // Include enabled_bots in update
3114 + ),
3115 + array('id' => $intent_id),
3116 + array('%s', '%s', '%s', '%f', '%s'), // Format: string, string, string, float, string
3117 + array('%d') // Where format: integer
3118 + );
2398 3119
2399 - // Generate embedding vector
2400 - $embedding_vector = $this->mxchat_generate_embedding($description);
2401 - if (!is_array($embedding_vector)) {
3120 + if (false === $result) {
3121 + $this->handle_embedding_error(__('Failed to update action in database.', 'mxchat'));
2402 3122 return;
2403 3123 }
2404 3124
2405 - // Check for Pinecone addon and its settings
2406 - $pinecone_settings = get_option('mxchat_pinecone_addon_options');
2407 - $use_pinecone = false;
3125 + // Set success message and redirect
3126 + set_transient('mxchat_admin_notice_success', __('Intent updated successfully!', 'mxchat'), 60);
2408 3127
2409 - if ($pinecone_settings && is_array($pinecone_settings)) {
2410 - $use_pinecone = (
2411 - isset($pinecone_settings['mxchat_use_pinecone']) &&
2412 - $pinecone_settings['mxchat_use_pinecone'] === '1' &&
2413 - !empty($pinecone_settings['mxchat_pinecone_api_key']) &&
2414 - !empty($pinecone_settings['mxchat_pinecone_host']) &&
2415 - !empty($pinecone_settings['mxchat_pinecone_index']) &&
2416 - !empty($pinecone_settings['mxchat_pinecone_environment'])
3128 + $redirect_url = add_query_arg(
3129 + array(
3130 + 'page' => 'mxchat-actions'
3131 + ),
3132 + admin_url('admin.php')
3133 + );
3134 + wp_safe_redirect($redirect_url);
3135 + exit;
3136 +}
3137 +public function mxchat_handle_add_intent() {
3138 + if (!current_user_can('manage_options')) {
3139 + wp_die(esc_html__('Unauthorized user', 'mxchat'));
3140 + }
3141 + check_admin_referer('mxchat_add_intent_nonce');
3142 + global $wpdb;
3143 + $table_name = $wpdb->prefix . 'mxchat_intents';
3144 +
3145 + // Sanitize and get form data
3146 + $intent_label = isset($_POST['intent_label']) ? sanitize_text_field($_POST['intent_label']) : '';
3147 + $phrases_input = isset($_POST['phrases']) ? sanitize_textarea_field($_POST['phrases']) : '';
3148 + $callback_function = isset($_POST['callback_function']) ? sanitize_text_field($_POST['callback_function']) : '';
3149 +
3150 + // Get similarity threshold from form (convert percentage to decimal)
3151 + $similarity_threshold = isset($_POST['similarity_threshold']) ? floatval($_POST['similarity_threshold']) / 100 : 0.85;
3152 +
3153 + // Handle enabled_bots
3154 + $enabled_bots = isset($_POST['enabled_bots']) ? $_POST['enabled_bots'] : array('default');
3155 + $enabled_bots = array_map('sanitize_text_field', $enabled_bots);
3156 +
3157 + // Ensure default is always included for backward compatibility with existing actions
3158 + if (!in_array('default', $enabled_bots)) {
3159 + $enabled_bots[] = 'default';
3160 + }
3161 +
3162 + $enabled_bots_json = json_encode($enabled_bots);
3163 +
3164 + // Validate required fields
3165 + if (empty($intent_label) || empty($callback_function) || empty($phrases_input)) {
3166 + $this->handle_embedding_error(__('Invalid input. Please ensure all fields are filled out.', 'mxchat'));
3167 + return;
3168 + }
3169 +
3170 + // Validate callback function
3171 + $available_callbacks = $this->mxchat_get_available_callbacks();
3172 + if (!array_key_exists($callback_function, $available_callbacks)) {
3173 + $this->handle_embedding_error(__('Invalid callback function selected.', 'mxchat'));
3174 + return;
3175 + }
3176 +
3177 + // Check Pro requirements
3178 + $is_pro_only = $available_callbacks[$callback_function]['pro_only'];
3179 + if ($is_pro_only && !$this->is_activated) {
3180 + $this->handle_embedding_error(__('This callback function is available in the Pro version only.', 'mxchat'));
3181 + return;
3182 + }
3183 +
3184 + // Process phrases
3185 + $phrases_array = array_map('sanitize_text_field', array_filter(array_map('trim', explode(',', $phrases_input))));
3186 + if (empty($phrases_array)) {
3187 + $this->handle_embedding_error(__('Please enter at least one valid phrase.', 'mxchat'));
3188 + return;
3189 + }
3190 +
3191 + // Generate embeddings with improved error handling
3192 + $vectors = [];
3193 + $failed_phrases = [];
3194 + foreach ($phrases_array as $phrase) {
3195 + $embedding_vector = $this->mxchat_generate_embedding($phrase, $this->options['api_key']);
3196 + if (is_array($embedding_vector)) {
3197 + $vectors[] = $embedding_vector;
3198 + } else {
3199 + $failed_phrases[] = $phrase;
3200 + }
3201 + }
3202 +
3203 + // Check for embedding failures
3204 + if (!empty($failed_phrases)) {
3205 + $this->handle_embedding_error(
3206 + sprintf(
3207 + __('Error generating embeddings for phrases: %s. Check your embedding API.', 'mxchat'),
3208 + implode(', ', $failed_phrases)
3209 + )
2417 3210 );
3211 + return;
2418 3212 }
2419 -
2420 - if ($use_pinecone) {
2421 - $result = $this->store_in_pinecone_main(
2422 - $embedding_vector,
2423 - $description,
2424 - $source_url,
2425 - $pinecone_settings['mxchat_pinecone_api_key'],
2426 - $pinecone_settings['mxchat_pinecone_environment'],
2427 - $pinecone_settings['mxchat_pinecone_index']
2428 - );
2429 -
2430 - if (!$result['success']) {
2431 - // Fallback to WordPress DB if Pinecone storage fails
2432 - $this->store_in_wordpress_db($description, $source_url, $embedding_vector);
2433 - }
2434 - } else {
2435 - // Use WordPress DB storage
2436 - $this->store_in_wordpress_db($description, $source_url, $embedding_vector);
3213 +
3214 + if (empty($vectors)) {
3215 + $this->handle_embedding_error(__('No valid embeddings generated. Please check your phrases.', 'mxchat'));
3216 + return;
2437 3217 }
3218 +
3219 + // Create combined vector and insert into database
3220 + $combined_vector = $this->mxchat_average_vectors($vectors);
3221 + $serialized_vector = maybe_serialize($combined_vector);
3222 +
3223 + $result = $wpdb->insert($table_name, [
3224 + 'intent_label' => $intent_label,
3225 + 'phrases' => implode(', ', $phrases_array),
3226 + 'embedding_vector' => $serialized_vector,
3227 + 'callback_function' => $callback_function,
3228 + 'similarity_threshold' => $similarity_threshold,
3229 + 'enabled_bots' => $enabled_bots_json // NEW field
3230 + ]);
3231 +
3232 + if ($result === false) {
3233 + $this->handle_embedding_error(__('Database error: ', 'mxchat') . $wpdb->last_error);
3234 + return;
3235 + }
3236 +
3237 + // Set success message and redirect
3238 + set_transient('mxchat_admin_notice_success', __('New intent added successfully!', 'mxchat'), 60);
3239 + wp_safe_redirect(admin_url('admin.php?page=mxchat-actions'));
3240 + exit;
2438 3241 }
2439 3242
2440 -public function mxchat_handle_product_delete($post_id) {
2441 - if (get_post_type($post_id) !== 'product') {
2442 - return;
2443 - }
2444 3243
2445 - $source_url = get_permalink($post_id);
2446 3244
2447 - // Check for Pinecone addon and its settings
2448 - $pinecone_settings = get_option('mxchat_pinecone_addon_options');
2449 - $use_pinecone = false;
2450 3245
2451 - if ($pinecone_settings && is_array($pinecone_settings)) {
2452 - $use_pinecone = (
2453 - isset($pinecone_settings['mxchat_use_pinecone']) &&
2454 - $pinecone_settings['mxchat_use_pinecone'] === '1' &&
2455 - !empty($pinecone_settings['mxchat_pinecone_api_key']) &&
2456 - !empty($pinecone_settings['mxchat_pinecone_host']) &&
2457 - !empty($pinecone_settings['mxchat_pinecone_index']) &&
2458 - !empty($pinecone_settings['mxchat_pinecone_environment'])
2459 - );
2460 - }
3246 +private function handle_embedding_error($message, $redirect = true) {
3247 + // Store the error message in the existing transient
3248 + set_transient('mxchat_admin_notice_error', $message, 60);
2461 3249
2462 - if ($use_pinecone) {
2463 - $this->delete_from_pinecone(
2464 - array($source_url),
2465 - $pinecone_settings['mxchat_pinecone_api_key'],
2466 - $pinecone_settings['mxchat_pinecone_environment'],
2467 - $pinecone_settings['mxchat_pinecone_index']
3250 + if ($redirect) {
3251 + // Redirect back to the actions page
3252 + $redirect_url = add_query_arg(
3253 + array(
3254 + 'page' => 'mxchat-actions'
3255 + ),
3256 + admin_url('admin.php')
2468 3257 );
2469 - } else {
2470 - // Fallback to WordPress DB deletion
2471 - global $wpdb;
2472 - $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
2473 - $wpdb->delete($table_name, array('source_url' => $source_url), array('%s'));
3258 + wp_safe_redirect($redirect_url);
3259 + exit;
2474 3260 }
2475 3261 }
2476 3262
2477 -
2478 3263 /**
2479 - * Delete vectors from Pinecone by source URL
2480 - *
2481 - * @param array $urls Array of source URLs to delete
2482 - * @param string $api_key Pinecone API key
2483 - * @param string $environment Pinecone environment
2484 - * @param string $index_name Pinecone index name
2485 - * @return array Associative array with 'success' boolean and 'message' string
3264 + * AJAX handler to fetch actions list for the new split-panel UI
2486 3265 */
2487 -private function delete_from_pinecone($urls, $api_key, $environment, $index_name) {
2488 - // Get the Pinecone host from options (matching your store_in_pinecone_main pattern)
2489 - $options = get_option('mxchat_pinecone_addon_options');
2490 - $host = $options['mxchat_pinecone_host'] ?? '';
3266 +public function mxchat_fetch_actions_list() {
3267 + check_ajax_referer('mxchat_actions_nonce', 'security');
2491 3268
2492 - if (empty($host)) {
2493 - return array(
2494 - 'success' => false,
2495 - 'message' => 'Pinecone host is not configured. Please set the host in your settings.'
2496 - );
3269 + if (!current_user_can('manage_options')) {
3270 + wp_send_json_error(__('Unauthorized', 'mxchat'));
2497 3271 }
2498 3272
2499 - // Build API endpoint using the configured host
2500 - $api_endpoint = "https://{$host}/vectors/delete";
3273 + global $wpdb;
3274 + $table_name = $wpdb->prefix . 'mxchat_intents';
2501 3275
2502 - // Create vector IDs from URLs (matching your store method's ID generation)
2503 - $vector_ids = array_map('md5', $urls);
3276 + $page = isset($_POST['page']) ? max(1, intval($_POST['page'])) : 1;
3277 + $per_page = isset($_POST['per_page']) ? min(100, max(1, intval($_POST['per_page']))) : 50;
3278 + $offset = ($page - 1) * $per_page;
3279 + $search = isset($_POST['search']) ? sanitize_text_field($_POST['search']) : '';
3280 + $callback_filter = isset($_POST['callback_filter']) ? sanitize_text_field($_POST['callback_filter']) : '';
3281 + $sort_order = isset($_POST['sort_order']) && $_POST['sort_order'] === 'asc' ? 'ASC' : 'DESC';
2504 3282
2505 - // Prepare the delete request body
2506 - $request_body = array(
2507 - 'ids' => $vector_ids,
2508 - 'filter' => array(
2509 - 'source_url' => array(
2510 - '$in' => $urls
2511 - )
2512 - )
2513 - );
3283 + // Build WHERE clause
3284 + $where = '1=1';
3285 + $params = array();
2514 3286
2515 - // Make the deletion request
2516 - $response = wp_remote_post($api_endpoint, array(
2517 - 'headers' => array(
2518 - 'Api-Key' => $api_key,
2519 - 'accept' => 'application/json',
2520 - 'content-type' => 'application/json'
2521 - ),
2522 - 'body' => wp_json_encode($request_body),
2523 - 'timeout' => 30,
2524 - 'data_format' => 'body'
2525 - ));
2526 -
2527 - // Handle WordPress HTTP API errors
2528 - if (is_wp_error($response)) {
2529 - return array(
2530 - 'success' => false,
2531 - 'message' => $response->get_error_message()
2532 - );
3287 + if ($search) {
3288 + $search_like = '%' . $wpdb->esc_like($search) . '%';
3289 + $where .= ' AND (intent_label LIKE %s OR phrases LIKE %s)';
3290 + $params[] = $search_like;
3291 + $params[] = $search_like;
2533 3292 }
2534 3293
2535 - // Check response status
2536 - $response_code = wp_remote_retrieve_response_code($response);
2537 - if ($response_code !== 200) {
2538 - $body = wp_remote_retrieve_body($response);
2539 - return array(
2540 - 'success' => false,
2541 - 'message' => sprintf(
2542 - 'Pinecone API error (HTTP %d): %s',
2543 - $response_code,
2544 - $body
2545 - )
2546 - );
3294 + if ($callback_filter) {
3295 + $where .= ' AND callback_function = %s';
3296 + $params[] = $callback_filter;
2547 3297 }
2548 3298
2549 - // Parse response body
2550 - $body = wp_remote_retrieve_body($response);
2551 - $response_data = json_decode($body, true);
2552 -
2553 - // Final validation of the response
2554 - if (json_last_error() !== JSON_ERROR_NONE) {
2555 - return array(
2556 - 'success' => false,
2557 - 'message' => 'Failed to parse Pinecone response: ' . json_last_error_msg()
2558 - );
3299 + // Get total count
3300 + $count_query = "SELECT COUNT(*) FROM $table_name WHERE $where";
3301 + if (!empty($params)) {
3302 + $count_query = $wpdb->prepare($count_query, $params);
2559 3303 }
3304 + $total_actions = $wpdb->get_var($count_query);
2560 3305
2561 - return array(
2562 - 'success' => true,
2563 - 'message' => sprintf('Successfully deleted %d vectors from Pinecone', count($vector_ids))
2564 - );
2565 -}
3306 + // Get actions
3307 + $query = "SELECT * FROM $table_name WHERE $where ORDER BY id $sort_order LIMIT %d OFFSET %d";
3308 + $all_params = array_merge($params, array($per_page, $offset));
3309 + $actions = $wpdb->get_results($wpdb->prepare($query, $all_params));
2566 3310
3311 + // Get available callbacks for labels/icons
3312 + $available_callbacks = $this->mxchat_get_available_callbacks();
2567 3313
3314 + // Format actions for response
3315 + $formatted_actions = array();
3316 + foreach ($actions as $action) {
3317 + $callback_data = isset($available_callbacks[$action->callback_function])
3318 + ? $available_callbacks[$action->callback_function]
3319 + : array('label' => $action->callback_function, 'icon' => 'admin-generic');
2568 3320
3321 + $enabled_bots = json_decode($action->enabled_bots, true);
3322 + if (!is_array($enabled_bots)) {
3323 + $enabled_bots = array('default');
3324 + }
2569 3325
3326 + $formatted_actions[] = array(
3327 + 'id' => intval($action->id),
3328 + 'label' => $action->intent_label,
3329 + 'phrases' => $action->phrases,
3330 + 'callback_function' => $action->callback_function,
3331 + 'callback_label' => $callback_data['label'],
3332 + 'icon' => isset($callback_data['icon']) ? $callback_data['icon'] : 'admin-generic',
3333 + 'threshold' => round($action->similarity_threshold * 100),
3334 + 'enabled' => (bool) $action->enabled,
3335 + 'enabled_bots' => $enabled_bots,
3336 + );
3337 + }
2570 3338
2571 -public function mxchat_create_activation_page() {
2572 - $license_status = get_option('mxchat_license_status', 'inactive');
2573 - $license_error = get_option('mxchat_license_error', '');
2574 - ?>
2575 - <div class="wrap mxchat-admin-activation">
2576 - <div class="mxchat-pro-hero">
2577 - <h1 class="pro-title">
2578 - <span class="pro-gradient-text">Activate</span> MxChat Pro
2579 - </h1>
2580 - <p class="pro-subtitle">
2581 - <?php esc_html_e('Enter your license key to unlock premium features, advanced AI capabilities, and priority support.', 'mxchat'); ?>
2582 - </p>
2583 - </div>
3339 + $total_pages = ceil($total_actions / $per_page);
3340 + $showing_start = $total_actions > 0 ? $offset + 1 : 0;
3341 + $showing_end = min($offset + $per_page, $total_actions);
2584 3342
2585 - <?php if ($license_status === 'inactive' && !empty($license_error)): ?>
2586 - <div class="error notice">
2587 - <p><?php echo esc_html($license_error); ?></p>
2588 - </div>
2589 - <?php endif; ?>
3343 + wp_send_json_success(array(
3344 + 'actions' => $formatted_actions,
3345 + 'page' => $page,
3346 + 'per_page' => $per_page,
3347 + 'total_actions' => intval($total_actions),
3348 + 'total_pages' => $total_pages,
3349 + 'showing_start' => $showing_start,
3350 + 'showing_end' => $showing_end,
3351 + ));
3352 +}
2590 3353
2591 - <form id="mxchat-activation-form" class="mxchat-pro-form" style="<?php echo $license_status === 'active' ? 'display: none;' : ''; ?>">
2592 - <div class="mxchat-pro-form-container">
2593 - <table class="form-table">
2594 - <tr valign="top">
2595 - <th scope="row"><?php esc_html_e('Email Address', 'mxchat'); ?></th>
2596 - <td>
2597 - <input type="email" id="mxchat_pro_email" name="mxchat_pro_email" value="<?php echo esc_attr(get_option('mxchat_pro_email')); ?>" class="regular-text mxchat-pro-input" required />
2598 - </td>
2599 - </tr>
2600 - <tr valign="top">
2601 - <th scope="row"><?php esc_html_e('Activation Key', 'mxchat'); ?></th>
2602 - <td>
2603 - <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 />
2604 - </td>
2605 - </tr>
2606 - </table>
2607 - <?php if ($license_status !== 'active'): ?>
2608 - <div class="mxchat-pro-button-container">
2609 - <button type="submit" id="activate_license_button" class="button button-primary mxchat-pro-button"><?php esc_html_e('Activate License', 'mxchat'); ?></button>
2610 - <div id="mxchat-activation-spinner" class="mxchat-activation-spinner" style="display: none;"></div>
2611 - </div>
2612 - <?php endif; ?>
2613 - </div>
2614 - </form>
2615 - <!-- License Status Display -->
2616 - <div class="mxchat-pro-status">
2617 - <h3><?php esc_html_e('License Status:', 'mxchat'); ?>
2618 - <span id="mxchat-license-status" class="mxchat-status-badge <?php echo $license_status; ?>">
2619 - <?php echo $license_status === 'active' ? esc_html__('Active', 'mxchat') : esc_html__('Inactive', 'mxchat'); ?>
2620 - </span>
2621 - </h3>
2622 - </div>
3354 +/**
3355 + * AJAX handler to toggle action enabled status
3356 + */
3357 +public function mxchat_toggle_action_status() {
3358 + check_ajax_referer('mxchat_actions_nonce', 'security');
2623 3359
2624 - </div>
2625 - <?php
2626 -}
2627 -
2628 -public function mxchat_intents_page_html() {
2629 3360 if (!current_user_can('manage_options')) {
2630 - return;
3361 + wp_send_json_error(__('Unauthorized', 'mxchat'));
2631 3362 }
2632 3363
2633 - // Keep existing data fetching logic
2634 - global $wpdb;
2635 - $table_name = $wpdb->prefix . 'mxchat_intents';
2636 - $page = isset($_GET['paged']) ? max(1, intval($_GET['paged'])) : 1;
2637 - $per_page = 20;
2638 - $offset = ($page - 1) * $per_page;
3364 + $action_id = isset($_POST['action_id']) ? intval($_POST['action_id']) : 0;
3365 + $enabled = isset($_POST['enabled']) ? intval($_POST['enabled']) : 0;
2639 3366
2640 - // Success message
2641 - if (isset($_GET['updated']) && $_GET['updated'] === 'true') {
2642 - echo '<div class="notice notice-success is-dismissible"><p>' .
2643 - esc_html__('Intent updated successfully.', 'mxchat') .
2644 - '</p></div>';
3367 + if (!$action_id) {
3368 + wp_send_json_error(__('Invalid action ID', 'mxchat'));
2645 3369 }
2646 3370
2647 - // Filtering logic
2648 - $where = '1=1';
2649 - $search_term = isset($_GET['s']) ? trim($_GET['s']) : '';
2650 - $callback_filter = isset($_GET['callback_filter']) ? sanitize_text_field($_GET['callback_filter']) : '';
3371 + global $wpdb;
3372 + $table_name = $wpdb->prefix . 'mxchat_intents';
2651 3373
2652 - if ($search_term) {
2653 - $search_term_like = '%' . $wpdb->esc_like($search_term) . '%';
2654 - $where .= $wpdb->prepare(' AND (intent_label LIKE %s OR phrases LIKE %s)',
2655 - $search_term_like, $search_term_like);
2656 - }
3374 + $result = $wpdb->update(
3375 + $table_name,
3376 + array('enabled' => $enabled ? 1 : 0),
3377 + array('id' => $action_id),
3378 + array('%d'),
3379 + array('%d')
3380 + );
2657 3381
2658 - if ($callback_filter) {
2659 - $where .= $wpdb->prepare(' AND callback_function = %s', $callback_filter);
3382 + if ($result === false) {
3383 + wp_send_json_error(__('Failed to update action status', 'mxchat'));
2660 3384 }
2661 3385
2662 - // Pagination
2663 - $total_intents = $wpdb->get_var("SELECT COUNT(*) FROM $table_name WHERE $where");
2664 - $total_pages = ceil($total_intents / $per_page);
3386 + wp_send_json_success(array('enabled' => (bool) $enabled));
3387 +}
2665 3388
2666 - // Get intents
2667 - $intents = $wpdb->get_results($wpdb->prepare(
2668 - "SELECT * FROM $table_name WHERE $where LIMIT %d OFFSET %d",
2669 - $per_page, $offset
2670 - ));
3389 +/**
3390 + * AJAX handler to bulk delete actions
3391 + */
3392 +public function mxchat_bulk_delete_actions() {
3393 + check_ajax_referer('mxchat_delete_intent_nonce', 'security');
2671 3394
2672 - // Get callbacks
2673 - $available_callbacks = $this->mxchat_get_available_callbacks();
3395 + if (!current_user_can('manage_options')) {
3396 + wp_send_json_error(__('Unauthorized', 'mxchat'));
3397 + }
2674 3398
2675 - ?>
2676 - <div class="wrap mxchat-wrapper">
2677 - <!-- Hero Section -->
2678 - <div class="mxchat-hero">
2679 - <h1 class="mxchat-main-title">
2680 - <span class="mxchat-gradient-text">Intent</span> Manager
2681 - </h1>
2682 - <p class="mxchat-hero-subtitle">
2683 - <?php esc_html_e('Create and manage custom intents to enhance your chatbots understanding and response capabilities.', 'mxchat'); ?>
2684 - </p>
2685 - </div>
3399 + $action_ids = isset($_POST['action_ids']) ? array_map('intval', (array) $_POST['action_ids']) : array();
2686 3400
2687 - <div class="mxchat-content">
2688 - <!-- Add Intent Card -->
2689 - <div class="mxchat-card">
2690 - <div class="mxchat-card-header">
2691 - <h2><?php esc_html_e('Add New Intent', 'mxchat'); ?></h2>
2692 - </div>
3401 + if (empty($action_ids)) {
3402 + wp_send_json_error(__('No actions selected', 'mxchat'));
3403 + }
2693 3404
2694 - <div class="mxchat-intent-documentation">
2695 - <p>
2696 - <?php esc_html_e('We highly encourage users to quickly read our ', 'mxchat'); ?>
2697 - <a href="https://mxchat.ai/documentation/#intents" target="_blank" rel="noopener noreferrer">
2698 - <?php esc_html_e('documentation', 'mxchat'); ?>
2699 - </a>
2700 - <?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'); ?>
2701 - </p>
2702 - </div>
3405 + global $wpdb;
3406 + $table_name = $wpdb->prefix . 'mxchat_intents';
2703 3407
2704 - <form id="mxchat-add-intent-form" method="post"
2705 - action="<?php echo esc_url(admin_url('admin-post.php')); ?>">
2706 - <input type="hidden" name="action" value="mxchat_add_intent">
2707 - <?php wp_nonce_field('mxchat_add_intent_nonce'); ?>
3408 + $placeholders = implode(',', array_fill(0, count($action_ids), '%d'));
3409 + $query = $wpdb->prepare("DELETE FROM $table_name WHERE id IN ($placeholders)", $action_ids);
3410 + $result = $wpdb->query($query);
2708 3411
2709 - <div class="mxchat-form-group">
2710 - <label for="intent_label">
2711 - <?php esc_html_e('Intent Label (For your reference only)', 'mxchat'); ?>
2712 - </label>
2713 - <input name="intent_label" type="text" id="intent_label" required
2714 - class="mxchat-intent-input"
2715 - placeholder="<?php esc_attr_e('Example Email Capture: Newsletter Signup', 'mxchat'); ?>">
2716 - </div>
3412 + if ($result === false) {
3413 + wp_send_json_error(__('Failed to delete actions', 'mxchat'));
3414 + }
2717 3415
2718 - <div class="mxchat-form-group">
2719 - <label for="phrases">
2720 - <?php esc_html_e('Phrases (comma-separated)', 'mxchat'); ?>
2721 - </label>
2722 - <textarea name="phrases" id="phrases" rows="5" required
2723 - class="mxchat-intent-textarea"
2724 - 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>
2725 - </div>
3416 + wp_send_json_success(array('deleted' => $result));
3417 +}
2726 3418
2727 - <div class="mxchat-form-group">
2728 - <label for="callback_function">
2729 - <?php esc_html_e('Callback Function', 'mxchat'); ?>
2730 - </label>
2731 - <select name="callback_function" id="callback_function"
2732 - class="mxchat-intent-select" required>
2733 - <option value="">
2734 - <?php esc_html_e('Select a Callback', 'mxchat'); ?>
2735 - </option>
2736 - <?php
2737 - $groups = $this->mxchat_get_available_callbacks(true);
2738 - foreach ($groups as $group_label => $group_callbacks) :
2739 - echo '<optgroup label="' . esc_attr($group_label) . '">';
2740 - foreach ($group_callbacks as $function => $data) :
2741 - $label = $data['label'];
2742 - $pro_only = $data['pro_only'];
2743 - $disabled = (!$this->is_activated && $pro_only) ? 'disabled' : '';
2744 - $label_suffix = (!$this->is_activated && $pro_only) ? ' (Pro Only)' : '';
2745 - ?>
2746 - <option value="<?php echo esc_attr($function); ?>"
2747 - <?php echo $disabled; ?>>
2748 - <?php echo esc_html($label . $label_suffix); ?>
2749 - </option>
2750 - <?php
2751 - endforeach;
2752 - echo '</optgroup>';
2753 - endforeach;
2754 - ?>
2755 - </select>
2756 - </div>
3419 +/**
3420 + * AJAX handler to add a new intent/action
3421 + */
3422 +public function mxchat_add_intent_ajax() {
3423 + check_ajax_referer('mxchat_add_intent_nonce', 'security');
2757 3424
2758 - <button type="submit" class="mxchat-button-primary">
2759 - <?php esc_html_e('Add Intent', 'mxchat'); ?>
2760 - </button>
2761 - </form>
2762 - </div>
3425 + if (!current_user_can('manage_options')) {
3426 + wp_send_json_error(__('Unauthorized', 'mxchat'));
3427 + }
2763 3428
2764 - <!-- Manage Intents Card -->
2765 - <div class="mxchat-card">
2766 - <div class="mxchat-card-header">
2767 - <h2><?php esc_html_e('Manage Intents', 'mxchat'); ?></h2>
2768 - <div class="mxchat-header-actions">
2769 - <form method="get" class="mxchat-search-form">
2770 - <input type="hidden" name="page" value="mxchat-intents">
2771 - <div class="mxchat-search-group">
2772 - <span class="dashicons dashicons-search"></span>
2773 - <input type="text" name="s"
2774 - placeholder="<?php esc_attr_e('Search Intents', 'mxchat'); ?>"
2775 - value="<?php echo esc_attr($search_term); ?>">
2776 - <select name="callback_filter" class="mxchat-intent-filter">
2777 - <option value="">
2778 - <?php esc_html_e('All Callbacks', 'mxchat'); ?>
2779 - </option>
2780 - <?php foreach ($available_callbacks as $function => $callback_data) :
2781 - $label = $callback_data['label']; ?>
2782 - <option value="<?php echo esc_attr($function); ?>"
2783 - <?php selected($callback_filter, $function); ?>>
2784 - <?php echo esc_html($label); ?>
2785 - </option>
2786 - <?php endforeach; ?>
2787 - </select>
2788 - <button type="submit" class="mxchat-button-secondary">
2789 - <?php esc_html_e('Filter', 'mxchat'); ?>
2790 - </button>
2791 - </div>
2792 - </form>
2793 - </div>
2794 - </div>
3429 + global $wpdb;
3430 + $table_name = $wpdb->prefix . 'mxchat_intents';
2795 3431
2796 - <div class="mxchat-table-wrapper">
2797 - <table class="mxchat-records-table">
2798 - <thead>
2799 - <tr>
2800 - <th><?php esc_html_e('Intent Label', 'mxchat'); ?></th>
2801 - <th><?php esc_html_e('Phrases', 'mxchat'); ?></th>
2802 - <th><?php esc_html_e('Callback Function', 'mxchat'); ?></th>
2803 - <th><?php esc_html_e('Similarity Threshold', 'mxchat'); ?></th>
2804 - <th><?php esc_html_e('Actions', 'mxchat'); ?></th>
2805 - </tr>
2806 - </thead>
2807 - <tbody>
2808 - <?php if ($intents) :
2809 - foreach ($intents as $intent) :
2810 - $callback_function = $intent->callback_function;
2811 - $callback_label = isset($available_callbacks[$callback_function]['label'])
2812 - ? $available_callbacks[$callback_function]['label']
2813 - : $callback_function;
2814 - $threshold_value = isset($intent->similarity_threshold)
2815 - ? round($intent->similarity_threshold * 100)
2816 - : 85;
2817 -
2818 - // Check if this is a form intent (its intent_label starts with "Form ")
2819 - $is_form_intent = strpos($intent->intent_label, 'Form ') === 0;
2820 - ?>
2821 - <tr<?php echo $is_form_intent ? ' class="mxchat-form-intent"' : ''; ?>>
2822 - <td>
2823 - <?php
2824 - if ($is_form_intent) {
2825 - // Attempt to extract the form ID from the intent_label.
2826 - preg_match('/Form (\d+)/', $intent->intent_label, $matches);
2827 - $form_id = isset($matches[1]) ? intval($matches[1]) : 0;
2828 - if ($form_id) {
2829 - global $wpdb;
2830 - $forms_table = $wpdb->prefix . 'mxchat_forms';
2831 - $form = $wpdb->get_row($wpdb->prepare("SELECT title FROM $forms_table WHERE id = %d", $form_id));
2832 - if ($form && !empty($form->title)) {
2833 - echo esc_html($form->title);
2834 - } else {
2835 - echo esc_html($intent->intent_label);
2836 - }
2837 - } else {
2838 - echo esc_html($intent->intent_label);
2839 - }
2840 - echo ' <span class="mxchat-badge">Form Intent</span>';
2841 - } else {
2842 - echo esc_html($intent->intent_label);
2843 - }
2844 - ?>
2845 - </td>
2846 - <td class="mxchat-content-cell">
2847 - <?php echo esc_html($intent->phrases); ?>
2848 - </td>
2849 - <td><?php echo esc_html($callback_label); ?></td>
2850 - <td>
2851 - <!-- Similarity threshold adjustment (shown for all intents) -->
2852 - <form method="post"
2853 - action="<?php echo esc_url(admin_url('admin-post.php')); ?>"
2854 - class="mxchat-threshold-form">
2855 - <?php wp_nonce_field('mxchat_update_intent_threshold_nonce'); ?>
2856 - <input type="hidden" name="action"
2857 - value="mxchat_update_intent_threshold">
2858 - <input type="hidden" name="intent_id"
2859 - value="<?php echo esc_attr($intent->id); ?>">
2860 - <div class="mxchat-slider-group">
2861 - <input type="range"
2862 - name="intent_threshold"
2863 - id="intent_threshold_<?php echo esc_attr($intent->id); ?>"
2864 - min="70"
2865 - max="95"
2866 - value="<?php echo esc_attr($threshold_value); ?>"
2867 - class="mxchat-intent-slider"
2868 - oninput="document.getElementById('threshold_output_<?php echo esc_attr($intent->id); ?>').value = this.value + '%'">
2869 - <output id="threshold_output_<?php echo esc_attr($intent->id); ?>"
2870 - class="mxchat-intent-output">
2871 - <?php echo esc_html($threshold_value); ?>%
2872 - </output>
2873 - </div>
2874 - <button type="submit" class="mxchat-button-icon">
2875 - <span class="dashicons dashicons-saved"></span>
2876 - </button>
2877 - </form>
2878 - </td>
2879 - <td class="mxchat-actions-cell">
2880 - <?php if (!$is_form_intent) : ?>
2881 - <!-- Standard intents: allow edit and delete -->
2882 - <button type="button"
2883 - class="mxchat-button-icon mxchat-edit-button"
2884 - data-intent-id="<?php echo esc_attr($intent->id); ?>"
2885 - data-phrases="<?php echo esc_attr($intent->phrases); ?>">
2886 - <span class="dashicons dashicons-edit"></span>
2887 - </button>
2888 - <form method="post"
2889 - action="<?php echo esc_url(admin_url('admin-post.php')); ?>"
2890 - class="mxchat-delete-form"
2891 - onsubmit="return confirm('<?php esc_attr_e('Are you sure you want to delete this intent?', 'mxchat'); ?>');">
2892 - <?php wp_nonce_field('mxchat_delete_intent_nonce'); ?>
2893 - <input type="hidden" name="action" value="mxchat_delete_intent">
2894 - <input type="hidden" name="intent_id"
2895 - value="<?php echo esc_attr($intent->id); ?>">
2896 - <button type="submit" class="mxchat-button-icon">
2897 - <span class="dashicons dashicons-trash"></span>
2898 - </button>
2899 - </form>
2900 - <?php else : ?>
2901 - <!-- Form intents: show manage in forms button -->
2902 - <?php
2903 - preg_match('/Form (\d+)/', $intent->intent_label, $matches);
2904 - $form_id = isset($matches[1]) ? $matches[1] : '';
2905 - ?>
2906 - <a href="<?php echo esc_url(admin_url('admin.php?page=mxchat-forms&action=edit&form_id=' . $form_id)); ?>"
2907 - class="mxchat-button-secondary">
2908 - <?php esc_html_e('Manage in Forms', 'mxchat'); ?>
2909 - </a>
2910 - <?php endif; ?>
2911 - </td>
2912 - </tr>
2913 - <?php endforeach;
2914 - else : ?>
2915 - <tr>
2916 - <td colspan="5" class="mxchat-no-records">
2917 - <?php esc_html_e('No intents found.', 'mxchat'); ?>
2918 - </td>
2919 - </tr>
2920 - <?php endif; ?>
2921 - </tbody>
2922 - </table>
2923 - </div>
3432 + // Sanitize input
3433 + $intent_label = isset($_POST['intent_label']) ? sanitize_text_field($_POST['intent_label']) : '';
3434 + $phrases_input = isset($_POST['phrases']) ? sanitize_textarea_field($_POST['phrases']) : '';
3435 + $callback_function = isset($_POST['callback_function']) ? sanitize_text_field($_POST['callback_function']) : '';
3436 + $similarity_threshold = isset($_POST['similarity_threshold']) ? floatval($_POST['similarity_threshold']) / 100 : 0.85;
3437 + $enabled_bots = isset($_POST['enabled_bots']) ? array_map('sanitize_text_field', (array) $_POST['enabled_bots']) : array('default');
2924 3438
2925 - <?php if ($total_pages > 1) : ?>
2926 - <div class="mxchat-pagination">
2927 - <?php
2928 - echo paginate_links(array(
2929 - 'base' => add_query_arg('paged', '%#%'),
2930 - 'format' => '',
2931 - 'prev_text' => __('&laquo; Previous', 'mxchat'),
2932 - 'next_text' => __('Next &raquo;', 'mxchat'),
2933 - 'total' => $total_pages,
2934 - 'current' => $page
2935 - ));
2936 - ?>
2937 - </div>
2938 - <?php endif; ?>
2939 - </div>
2940 - </div>
3439 + // Validate
3440 + if (empty($intent_label) || empty($phrases_input) || empty($callback_function)) {
3441 + wp_send_json_error(__('Please fill in all required fields.', 'mxchat'));
3442 + }
2941 3443
2942 - <!-- Edit Modal -->
2943 -<div id="mxchat-edit-modal" class="mxchat-modal" style="display: none;">
2944 - <div class="mxchat-modal-content">
2945 - <span class="mxchat-modal-close">&times;</span>
2946 - <div class="mxchat-card-header">
2947 - <h2><?php esc_html_e('Edit Intent Phrases', 'mxchat'); ?></h2>
2948 - </div>
3444 + // Process phrases
3445 + $phrases_array = array_filter(array_map('trim', preg_split('/[\n,]+/', $phrases_input)));
3446 + if (empty($phrases_array)) {
3447 + wp_send_json_error(__('Please provide at least one trigger phrase.', 'mxchat'));
3448 + }
2949 3449
2950 - <form id="mxchat-edit-intent-form" method="post"
2951 - action="<?php echo esc_url(admin_url('admin-post.php')); ?>">
2952 - <?php wp_nonce_field('mxchat_edit_intent_nonce'); ?>
2953 - <input type="hidden" name="action" value="mxchat_edit_intent">
2954 - <input type="hidden" name="intent_id" id="edit_intent_id">
3450 + // Generate embedding (combine phrases into single string for embedding)
3451 + $embedding_vector = $this->mxchat_generate_embedding(implode(' ', $phrases_array));
3452 + if (is_wp_error($embedding_vector)) {
3453 + // Fallback: store without embedding
3454 + $serialized_vector = null;
3455 + } else {
3456 + $serialized_vector = maybe_serialize($embedding_vector);
3457 + }
2955 3458
2956 - <div class="mxchat-form-group">
2957 - <label for="edit_phrases">
2958 - <?php esc_html_e('Phrases (comma-separated)', 'mxchat'); ?>
2959 - </label>
2960 - <textarea name="phrases"
2961 - id="edit_phrases"
2962 - rows="5"
2963 - class="mxchat-intent-textarea"
2964 - required></textarea>
2965 - </div>
3459 + // Ensure default bot is included
3460 + if (!in_array('default', $enabled_bots)) {
3461 + $enabled_bots[] = 'default';
3462 + }
3463 + $enabled_bots_json = json_encode($enabled_bots);
2966 3464
2967 - <div class="mxchat-modal-actions">
2968 - <button type="submit" class="mxchat-button-primary">
2969 - <?php esc_html_e('Update Phrases', 'mxchat'); ?>
2970 - </button>
2971 - <button type="button" class="mxchat-button-secondary mxchat-modal-cancel">
2972 - <?php esc_html_e('Cancel', 'mxchat'); ?>
2973 - </button>
2974 - </div>
2975 - </form>
2976 - </div>
2977 -</div>
3465 + // Insert
3466 + $result = $wpdb->insert(
3467 + $table_name,
3468 + array(
3469 + 'intent_label' => $intent_label,
3470 + 'phrases' => implode(', ', $phrases_array),
3471 + 'embedding_vector' => $serialized_vector,
3472 + 'similarity_threshold' => $similarity_threshold,
3473 + 'callback_function' => $callback_function,
3474 + 'enabled' => 1,
3475 + 'enabled_bots' => $enabled_bots_json,
3476 + )
3477 + );
2978 3478
2979 - </div><!-- .mxchat-content -->
2980 - </div><!-- .mxchat-wrapper -->
3479 + if ($result === false) {
3480 + wp_send_json_error(__('Failed to add action to database.', 'mxchat'));
3481 + }
2981 3482
2982 - <div id="mxchat-intent-loading" class="mxchat-intent-loading" style="display: none;">
2983 - <div class="mxchat-intent-loading-spinner"></div>
2984 - <div class="mxchat-intent-loading-text">
2985 - <?php esc_html_e('Saving intent, please wait...', 'mxchat'); ?>
2986 - </div>
2987 - </div>
2988 -<?php
3483 + wp_send_json_success(array('id' => $wpdb->insert_id));
2989 3484 }
2990 3485
2991 -
2992 -
2993 3486 /**
2994 - * Handle editing of intent phrases
2995 - *
2996 - * @since 1.0.0
2997 - * @return void
3487 + * AJAX handler to edit an existing intent/action
2998 3488 */
2999 -public function handle_edit_intent() {
3000 - // Verify nonce and user capabilities
3001 - if (!isset($_POST['_wpnonce']) || !wp_verify_nonce($_POST['_wpnonce'], 'mxchat_edit_intent_nonce')) {
3002 - wp_die(esc_html__('Security check failed.', 'mxchat'));
3003 - }
3489 +public function mxchat_edit_intent_ajax() {
3490 + check_ajax_referer('mxchat_edit_intent', 'security');
3004 3491
3005 3492 if (!current_user_can('manage_options')) {
3006 - wp_die(esc_html__('You do not have permission to perform this action.', 'mxchat'));
3493 + wp_send_json_error(__('Unauthorized', 'mxchat'));
3007 3494 }
3008 3495
3009 - // Validate and sanitize input
3010 - $intent_id = isset($_POST['intent_id']) ? absint($_POST['intent_id']) : 0;
3011 - if (!$intent_id) {
3012 - wp_die(esc_html__('Invalid intent ID.', 'mxchat'));
3013 - }
3496 + global $wpdb;
3497 + $table_name = $wpdb->prefix . 'mxchat_intents';
3014 3498
3499 + // Sanitize input
3500 + $intent_id = isset($_POST['intent_id']) ? intval($_POST['intent_id']) : 0;
3501 + $intent_label = isset($_POST['intent_label']) ? sanitize_text_field($_POST['intent_label']) : '';
3015 3502 $phrases_input = isset($_POST['phrases']) ? sanitize_textarea_field($_POST['phrases']) : '';
3016 - if (empty($phrases_input)) {
3017 - wp_die(esc_html__('Phrases cannot be empty.', 'mxchat'));
3503 + $callback_function = isset($_POST['callback_function']) ? sanitize_text_field($_POST['callback_function']) : '';
3504 + $similarity_threshold = isset($_POST['similarity_threshold']) ? floatval($_POST['similarity_threshold']) / 100 : 0.85;
3505 + $enabled_bots = isset($_POST['enabled_bots']) ? array_map('sanitize_text_field', (array) $_POST['enabled_bots']) : array('default');
3506 +
3507 + // Validate
3508 + if (!$intent_id || empty($intent_label) || empty($phrases_input)) {
3509 + wp_send_json_error(__('Please fill in all required fields.', 'mxchat'));
3018 3510 }
3019 3511
3020 - // Process phrases the same way as in intent creation
3021 - $phrases_array = array_map('sanitize_text_field', array_filter(array_map('trim', explode(',', $phrases_input))));
3022 -
3512 + // Process phrases
3513 + $phrases_array = array_filter(array_map('trim', preg_split('/[\n,]+/', $phrases_input)));
3023 3514 if (empty($phrases_array)) {
3024 - wp_die(esc_html__('Please enter at least one valid phrase.', 'mxchat'));
3515 + wp_send_json_error(__('Please provide at least one trigger phrase.', 'mxchat'));
3025 3516 }
3026 3517
3027 - // Generate embeddings and combine them
3028 - $vectors = [];
3029 - foreach ($phrases_array as $phrase) {
3030 - $embedding_vector = $this->mxchat_generate_embedding($phrase, $this->options['api_key']);
3031 - if (is_array($embedding_vector)) {
3032 - $vectors[] = $embedding_vector;
3033 - } else {
3034 - wp_die(esc_html__('Error generating embedding for phrase: ', 'mxchat') . esc_html($phrase));
3035 - }
3518 + // Generate new embedding (combine phrases into single string for embedding)
3519 + $embedding_vector = $this->mxchat_generate_embedding(implode(' ', $phrases_array));
3520 + if (is_wp_error($embedding_vector)) {
3521 + // Keep existing embedding
3522 + $serialized_vector = null;
3523 + $update_data = array(
3524 + 'intent_label' => $intent_label,
3525 + 'phrases' => implode(', ', $phrases_array),
3526 + 'similarity_threshold' => $similarity_threshold,
3527 + 'callback_function' => $callback_function,
3528 + 'enabled_bots' => json_encode($enabled_bots),
3529 + );
3530 + } else {
3531 + $serialized_vector = maybe_serialize($embedding_vector);
3532 + $update_data = array(
3533 + 'intent_label' => $intent_label,
3534 + 'phrases' => implode(', ', $phrases_array),
3535 + 'embedding_vector' => $serialized_vector,
3536 + 'similarity_threshold' => $similarity_threshold,
3537 + 'callback_function' => $callback_function,
3538 + 'enabled_bots' => json_encode($enabled_bots),
3539 + );
3036 3540 }
3037 3541
3038 - if (empty($vectors)) {
3039 - wp_die(esc_html__('No valid embeddings generated. Please check your phrases.', 'mxchat'));
3542 + // Ensure default bot is included
3543 + if (!in_array('default', $enabled_bots)) {
3544 + $enabled_bots[] = 'default';
3040 3545 }
3546 + $update_data['enabled_bots'] = json_encode($enabled_bots);
3041 3547
3042 - // Create combined vector just like in intent creation
3043 - $combined_vector = $this->mxchat_average_vectors($vectors);
3044 - $serialized_vector = maybe_serialize($combined_vector);
3045 -
3046 - global $wpdb;
3047 - $table_name = $wpdb->prefix . 'mxchat_intents';
3048 -
3049 - // Update the intent with both new phrases and the combined vector
3050 3548 $result = $wpdb->update(
3051 3549 $table_name,
3052 - array(
3053 - 'phrases' => implode(', ', $phrases_array),
3054 - 'embedding_vector' => $serialized_vector
3055 - ),
3550 + $update_data,
3056 3551 array('id' => $intent_id),
3057 - array('%s', '%s'),
3552 + null,
3058 3553 array('%d')
3059 3554 );
3060 3555
3061 - if (false === $result) {
3062 - wp_die(esc_html__('Failed to update intent.', 'mxchat'));
3556 + if ($result === false) {
3557 + wp_send_json_error(__('Failed to update action.', 'mxchat'));
3063 3558 }
3064 3559
3065 - // Redirect back to the intents page with a success message
3066 - $redirect_url = add_query_arg(
3067 - array(
3068 - 'page' => 'mxchat-intents',
3069 - 'updated' => 'true'
3070 - ),
3071 - admin_url('admin.php')
3072 - );
3560 + wp_send_json_success(array('updated' => true));
3561 +}
3073 3562
3074 - wp_safe_redirect($redirect_url);
3075 - exit;
3076 -}
3077 -public function mxchat_handle_update_intent_threshold() {
3078 - if ( ! current_user_can( 'manage_options' ) ) {
3079 - wp_die( esc_html__('Unauthorized user', 'mxchat') );
3563 +/**
3564 + * AJAX handler to delete a single intent/action
3565 + */
3566 +public function mxchat_delete_intent_ajax() {
3567 + check_ajax_referer('mxchat_delete_intent_nonce', 'security');
3568 +
3569 + if (!current_user_can('manage_options')) {
3570 + wp_send_json_error(__('Unauthorized', 'mxchat'));
3080 3571 }
3081 3572
3082 - check_admin_referer('mxchat_update_intent_threshold_nonce');
3573 + $intent_id = isset($_POST['intent_id']) ? intval($_POST['intent_id']) : 0;
3083 3574
3084 - if (isset($_POST['intent_id'], $_POST['intent_threshold'])) {
3085 - global $wpdb;
3086 - $table_name = $wpdb->prefix . 'mxchat_intents';
3087 - $intent_id = intval($_POST['intent_id']);
3088 - $threshold_percentage = max(70, min(95, intval($_POST['intent_threshold'])));
3089 - $similarity_threshold = $threshold_percentage / 100;
3575 + if (!$intent_id) {
3576 + wp_send_json_error(__('Invalid action ID', 'mxchat'));
3577 + }
3090 3578
3091 - $wpdb->update(
3092 - $table_name,
3093 - ['similarity_threshold' => $similarity_threshold],
3094 - ['id' => $intent_id],
3095 - ['%f'],
3096 - ['%d']
3097 - );
3579 + global $wpdb;
3580 + $table_name = $wpdb->prefix . 'mxchat_intents';
3581 +
3582 + $result = $wpdb->delete($table_name, array('id' => $intent_id), array('%d'));
3583 +
3584 + if ($result === false) {
3585 + wp_send_json_error(__('Failed to delete action', 'mxchat'));
3098 3586 }
3099 3587
3100 - wp_safe_redirect(admin_url('admin.php?page=mxchat-intents'));
3101 - exit;
3588 + wp_send_json_success(array('deleted' => true));
3102 3589 }
3103 3590
3104 -public function mxchat_handle_add_intent() {
3105 - if ( ! current_user_can( 'manage_options' ) ) {
3106 - wp_die( esc_html__('Unauthorized user', 'mxchat') );
3591 +
3592 +/**
3593 + * Enhanced get_available_callbacks function with form action exclusion
3594 + *
3595 + * @param bool $grouped Whether to return callbacks grouped by category
3596 + * @param bool $include_all Whether to include all potential actions (even if add-on not installed)
3597 + * @return array Callbacks data with icons, descriptions and availability status
3598 + */
3599 +private function mxchat_get_available_callbacks($grouped = false, $include_all = true) {
3600 + // Load WordPress plugin functions if needed
3601 + if (!function_exists('get_plugins')) {
3602 + require_once ABSPATH . 'wp-admin/includes/plugin.php';
3107 3603 }
3108 3604
3109 - check_admin_referer('mxchat_add_intent_nonce');
3605 + // Get active plugins
3606 + $active_plugins = get_option('active_plugins', array());
3110 3607
3111 - global $wpdb;
3112 - $table_name = $wpdb->prefix . 'mxchat_intents';
3608 + // Functions to exclude from the action selector only if Pro is activated
3609 + // If user doesn't have Pro, show these so they can see what they're missing
3610 + $excluded_when_pro_active_functions = array(
3611 + 'mxchat_handle_form_collection' // Forms add-on action
3612 + );
3113 3613
3114 - $intent_label = isset($_POST['intent_label']) ? sanitize_text_field($_POST['intent_label']) : '';
3115 - $phrases_input = isset($_POST['phrases']) ? sanitize_textarea_field($_POST['phrases']) : '';
3116 - $callback_function = isset($_POST['callback_function']) ? sanitize_text_field($_POST['callback_function']) : '';
3117 - $default_threshold = 0.85;
3614 + // Always excluded functions (regardless of Pro status)
3615 + $always_excluded_functions = array();
3118 3616
3119 - if (empty($intent_label) || empty($callback_function) || empty($phrases_input)) {
3120 - wp_die( esc_html__('Invalid input. Please ensure all fields are filled out.', 'mxchat') );
3617 + // Combine exclusion lists based on Pro activation status
3618 + $excluded_functions = $always_excluded_functions;
3619 + if ($this->is_activated) {
3620 + // Only exclude add-on managed functions if Pro is active
3621 + $excluded_functions = array_merge($excluded_functions, $excluded_when_pro_active_functions);
3121 3622 }
3122 3623
3123 - $available_callbacks = $this->mxchat_get_available_callbacks();
3624 + // Define add-on plugin files and their corresponding action functions
3625 + $addon_plugins = array(
3626 + 'mxchat-woo/mxchat-woo.php' => array(
3627 + 'functions' => array(
3628 + 'mxchat_handle_product_recommendations',
3629 + 'mxchat_handle_order_history',
3630 + 'mxchat_show_product_card',
3631 + 'mxchat_add_to_cart',
3632 + 'mxchat_checkout_redirect'
3633 + ),
3634 + 'name' => __('WooCommerce Add-on', 'mxchat'),
3635 + 'pro_required' => true
3636 + ),
3637 + 'mxchat-perplexity/mxchat-perplexity.php' => array(
3638 + 'functions' => array('mxchat_perplexity_research'),
3639 + 'name' => __('Perplexity Add-on', 'mxchat'),
3640 + 'pro_required' => true
3641 + ),
3642 + 'mxchat-forms/mxchat-forms.php' => array(
3643 + 'functions' => array('mxchat_handle_form_collection'),
3644 + 'name' => __('Forms Add-on', 'mxchat'),
3645 + 'pro_required' => true
3646 + ),
3647 + // Add other add-ons and their functions here
3648 + );
3124 3649
3125 - if (!array_key_exists($callback_function, $available_callbacks)) {
3126 - wp_die( esc_html__('Invalid callback function selected.', 'mxchat') );
3127 - }
3650 + // Get the functions that are provided by active add-ons
3651 + $addon_provided_functions = array();
3652 + $addon_function_mapping = array(); // Maps functions to their add-on info
3128 3653
3129 - $is_pro_only = $available_callbacks[$callback_function]['pro_only'];
3130 - if ($is_pro_only && !$this->is_activated) {
3131 - wp_die( esc_html__('This callback function is available in the Pro version only.', 'mxchat') );
3132 - }
3654 + // Check which add-ons are active
3655 + foreach ($addon_plugins as $plugin_file => $addon_info) {
3656 + $is_active = in_array($plugin_file, $active_plugins);
3133 3657
3134 - $phrases_array = array_map('sanitize_text_field', array_filter(array_map('trim', explode(',', $phrases_input))));
3658 + // For each function in this addon
3659 + foreach ($addon_info['functions'] as $function) {
3660 + // Consider a function installed only if:
3661 + // 1. The add-on is active AND
3662 + // 2. Either it doesn't require Pro OR Pro is activated
3663 + $is_installed = $is_active && (!$addon_info['pro_required'] || $this->is_activated);
3135 3664
3136 - if (empty($phrases_array)) {
3137 - wp_die( esc_html__('Please enter at least one valid phrase.', 'mxchat') );
3138 - }
3665 + // If the add-on is installed, mark this function as provided by an add-on
3666 + if ($is_installed) {
3667 + $addon_provided_functions[] = $function;
3668 + }
3139 3669
3140 - $vectors = [];
3141 - foreach ($phrases_array as $phrase) {
3142 - $embedding_vector = $this->mxchat_generate_embedding($phrase, $this->options['api_key']);
3143 - if (is_array($embedding_vector)) {
3144 - $vectors[] = $embedding_vector;
3145 - } else {
3146 - wp_die( esc_html__('Error generating embedding for phrase: ', 'mxchat') . esc_html($phrase) );
3670 + // Store addon info for this function regardless of installation status
3671 + $addon_function_mapping[$function] = array(
3672 + 'addon' => basename(dirname($plugin_file)),
3673 + 'addon_name' => $addon_info['name'],
3674 + 'pro_required' => $addon_info['pro_required'],
3675 + 'is_active' => $is_active,
3676 + 'is_installed' => $is_installed
3677 + );
3147 3678 }
3148 3679 }
3149 3680
3150 - if (!empty($vectors)) {
3151 - $combined_vector = $this->mxchat_average_vectors($vectors);
3152 - $serialized_vector = maybe_serialize($combined_vector);
3681 + // Core callbacks - always available in the base plugin
3682 + $core_callbacks = array(
3683 + 'mxchat_handle_email_capture' => array(
3684 + 'label' => __('Loops Email Capture', 'mxchat'),
3685 + 'pro_only' => false,
3686 + 'group' => __('Customer Engagement', 'mxchat'),
3687 + 'icon' => 'email-alt',
3688 + 'description' => __('Collect visitor emails for your mailing list in Loops', 'mxchat'),
3689 + 'addon' => false, // Not from an add-on
3690 + 'installed' => true // Always installed with base plugin
3691 + ),
3692 + 'mxchat_handle_search_request' => array(
3693 + 'label' => __('Brave Web Search', 'mxchat'),
3694 + 'pro_only' => false,
3695 + 'group' => __('Search Features', 'mxchat'),
3696 + 'icon' => 'search',
3697 + 'description' => __('Let users search the web directly from the chat', 'mxchat'),
3698 + 'addon' => false,
3699 + 'installed' => true
3700 + ),
3701 + 'mxchat_handle_image_search_request' => array(
3702 + 'label' => __('Brave Image Search', 'mxchat'),
3703 + 'pro_only' => false,
3704 + 'group' => __('Search Features', 'mxchat'),
3705 + 'icon' => 'format-image',
3706 + 'description' => __('Search and display images in the chat conversation', 'mxchat'),
3707 + 'addon' => false,
3708 + 'installed' => true
3709 + ),
3710 + // Pro core features - check is_activated property
3711 + 'mxchat_generate_image' => array(
3712 + 'label' => __('Generate Image', 'mxchat'),
3713 + 'pro_only' => false,
3714 + 'group' => __('Other Features', 'mxchat'),
3715 + 'icon' => 'art',
3716 + 'description' => __('Create images with DALL-E 3 from OpenAI (requires OpenAI API key)', 'mxchat'),
3717 + 'addon' => false,
3718 + 'installed' => true
3719 + ),
3720 + 'mxchat_handle_pdf_discussion' => array(
3721 + 'label' => __('Chat with PDF', 'mxchat'),
3722 + 'pro_only' => false,
3723 + 'group' => __('Other Features', 'mxchat'),
3724 + 'icon' => 'media-document',
3725 + 'description' => __('Answer questions about uploaded PDF documents', 'mxchat'),
3726 + 'addon' => false,
3727 + 'installed' => true
3728 + ),
3729 + 'mxchat_live_agent_handover' => array(
3730 + 'label' => __('Slack Live Agent', 'mxchat'),
3731 + 'pro_only' => false,
3732 + 'group' => __('Customer Engagement', 'mxchat'),
3733 + 'icon' => 'admin-users',
3734 + 'description' => __('Transfer conversation to a human support agent on Slack', 'mxchat'),
3735 + 'addon' => false,
3736 + 'installed' => true
3737 + ),
3738 + 'mxchat_telegram_live_agent_handover' => array(
3739 + 'label' => __('Telegram Live Agent', 'mxchat'),
3740 + 'pro_only' => false,
3741 + 'group' => __('Customer Engagement', 'mxchat'),
3742 + 'icon' => 'format-chat',
3743 + 'description' => __('Transfer conversation to a human support agent on Telegram', 'mxchat'),
3744 + 'addon' => false,
3745 + 'installed' => true
3746 + ),
3747 + 'mxchat_handle_switch_to_chatbot_intent' => array(
3748 + 'label' => __('Back to Chatbot', 'mxchat'),
3749 + 'pro_only' => false,
3750 + 'group' => __('Customer Engagement', 'mxchat'),
3751 + 'icon' => 'backup',
3752 + 'description' => __('Return from live agent mode to AI chatbot', 'mxchat'),
3753 + 'addon' => false,
3754 + 'installed' => true
3755 + ),
3756 + );
3153 3757
3154 - $result = $wpdb->insert($table_name, [
3155 - 'intent_label' => $intent_label,
3156 - 'phrases' => implode(', ', $phrases_array),
3157 - 'embedding_vector' => $serialized_vector,
3158 - 'callback_function' => $callback_function,
3159 - 'similarity_threshold' => $default_threshold,
3160 - ]);
3758 + // Add-on callbacks with placeholders - only include if the add-on is NOT active
3759 + $addon_callbacks = array(
3760 + // WooCommerce Add-on
3761 + 'mxchat_handle_product_recommendations' => array(
3762 + 'label' => __('Product Recommendations', 'mxchat'),
3763 + 'pro_only' => true,
3764 + 'group' => __('WooCommerce Features', 'mxchat'),
3765 + 'icon' => 'cart',
3766 + 'description' => __('Suggest products based on customer preferences', 'mxchat'),
3767 + ),
3768 + 'mxchat_handle_order_history' => array(
3769 + 'label' => __('Order History', 'mxchat'),
3770 + 'pro_only' => true,
3771 + 'group' => __('WooCommerce Features', 'mxchat'),
3772 + 'icon' => 'clipboard',
3773 + 'description' => __('Allow customers to check their order status', 'mxchat'),
3774 + ),
3775 + 'mxchat_show_product_card' => array(
3776 + 'label' => __('Show Product Card', 'mxchat'),
3777 + 'pro_only' => true,
3778 + 'group' => __('WooCommerce Features', 'mxchat'),
3779 + 'icon' => 'products',
3780 + 'description' => __('Display product information in the chat', 'mxchat'),
3781 + ),
3782 + 'mxchat_add_to_cart' => array(
3783 + 'label' => __('Add to Cart', 'mxchat'),
3784 + 'pro_only' => true,
3785 + 'group' => __('WooCommerce Features', 'mxchat'),
3786 + 'icon' => 'plus-alt',
3787 + 'description' => __('Add products to cart directly from chat', 'mxchat'),
3788 + ),
3789 + 'mxchat_checkout_redirect' => array(
3790 + 'label' => __('Proceed to Checkout', 'mxchat'),
3791 + 'pro_only' => true,
3792 + 'group' => __('WooCommerce Features', 'mxchat'),
3793 + 'icon' => 'arrow-right-alt',
3794 + 'description' => __('Redirect customer to checkout page', 'mxchat'),
3795 + ),
3161 3796
3162 - if ($result === false) {
3163 - wp_die( esc_html__('Database error: ', 'mxchat') . esc_html($wpdb->last_error) );
3164 - }
3165 - } else {
3166 - wp_die( esc_html__('No valid embeddings generated. Please check your phrases.', 'mxchat') );
3167 - }
3797 + // Perplexity Add-on
3798 + 'mxchat_perplexity_research' => array(
3799 + 'label' => __('Perplexity Research', 'mxchat'),
3800 + 'pro_only' => true,
3801 + 'group' => __('Search Features', 'mxchat'),
3802 + 'icon' => 'book-alt',
3803 + 'description' => __('Allows the chatbot to search the web for accurate, up-to-date answers', 'mxchat'),
3804 + ),
3168 3805
3169 - wp_safe_redirect(admin_url('admin.php?page=mxchat-intents'));
3170 - exit;
3171 -}
3806 + // Forms Add-on (only shown when Pro is not activated)
3807 + 'mxchat_handle_form_collection' => array(
3808 + 'label' => __('Form Collection', 'mxchat'),
3809 + 'pro_only' => true,
3810 + 'group' => __('Form Features', 'mxchat'),
3811 + 'icon' => 'feedback',
3812 + 'description' => __('Collect user information through custom forms in chat', 'mxchat'),
3813 + ),
3814 + );
3172 3815
3816 + // Enhance add-on callbacks with installation status and addon info
3817 + foreach ($addon_callbacks as $function => $data) {
3818 + if (isset($addon_function_mapping[$function])) {
3819 + $addon_info = $addon_function_mapping[$function];
3173 3820
3821 + $addon_callbacks[$function]['addon'] = $addon_info['addon'];
3822 + $addon_callbacks[$function]['addon_name'] = $addon_info['addon_name'];
3823 + $addon_callbacks[$function]['installed'] = $addon_info['is_installed'];
3174 3824
3825 + // Set pro_only based on add-on configuration
3826 + $addon_callbacks[$function]['pro_only'] = $addon_info['pro_required'];
3827 + } else {
3828 + $addon_callbacks[$function]['addon'] = 'unknown';
3829 + $addon_callbacks[$function]['addon_name'] = __('Unknown Add-on', 'mxchat');
3830 + $addon_callbacks[$function]['installed'] = false;
3831 + }
3832 + }
3175 3833
3834 + // Initialize callbacks with core features
3835 + $callbacks = $core_callbacks;
3176 3836
3837 + // Get callbacks from active add-ons
3838 + $active_addon_callbacks = apply_filters('mxchat_available_callbacks', array());
3177 3839
3178 -private function mxchat_get_available_callbacks($grouped = false) {
3179 - $callbacks = [
3180 - 'mxchat_handle_email_capture' => [
3181 - 'label' => __('Email Capture', 'mxchat'),
3182 - 'pro_only' => false,
3183 - 'group' => __('Customer Engagement', 'mxchat'),
3184 - ],
3185 - 'mxchat_generate_image' => [
3186 - 'label' => __('Generate Image', 'mxchat'),
3187 - 'pro_only' => true,
3188 - 'group' => __('Other Features', 'mxchat'),
3189 - ],
3190 - 'mxchat_handle_search_request' => [
3191 - 'label' => __('Brave Web Search', 'mxchat'),
3192 - 'pro_only' => false,
3193 - 'group' => __('Search Features', 'mxchat'),
3194 - ],
3195 - 'mxchat_handle_image_search_request' => [
3196 - 'label' => __('Brave Image Search', 'mxchat'),
3197 - 'pro_only' => false,
3198 - 'group' => __('Search Features', 'mxchat'),
3199 - ],
3200 - 'mxchat_handle_pdf_discussion' => [
3201 - 'label' => __('Chat with PDF', 'mxchat'),
3202 - 'pro_only' => true,
3203 - 'group' => __('Other Features', 'mxchat'),
3204 - ],
3205 - 'mxchat_live_agent_handover' => [
3206 - 'label' => __('Live Agent', 'mxchat'),
3207 - 'pro_only' => true,
3208 - 'group' => __('Customer Engagement', 'mxchat'),
3209 - ],
3210 - 'mxchat_handle_switch_to_chatbot_intent' => [
3211 - 'label' => __('Back to Chatbot', 'mxchat'),
3212 - 'pro_only' => true,
3213 - 'group' => __('Customer Engagement', 'mxchat'),
3214 - ],
3215 - ];
3840 + // Add placeholder callbacks only for add-ons that aren't active
3841 + if ($include_all) {
3842 + foreach ($addon_callbacks as $function => $data) {
3843 + // Skip placeholders for functions provided by active add-ons
3844 + if (in_array($function, $addon_provided_functions)) {
3845 + continue;
3846 + }
3216 3847
3217 - // Allow other plugins to add their callbacks
3218 - $callbacks = apply_filters('mxchat_available_callbacks', $callbacks);
3848 + // Skip excluded functions
3849 + if (in_array($function, $excluded_functions)) {
3850 + continue;
3851 + }
3219 3852
3220 - // Return grouped structure if requested
3221 - if ($grouped) {
3222 - $grouped_callbacks = [];
3223 - foreach ($callbacks as $key => $data) {
3224 - $group_label = $data['group'] ?? __('Other Features', 'mxchat');
3225 - $grouped_callbacks[$group_label][$key] = [
3226 - 'label' => $data['label'],
3227 - 'pro_only' => $data['pro_only'],
3228 - ];
3853 + // Add the placeholder
3854 + $callbacks[$function] = $data;
3229 3855 }
3230 - return $grouped_callbacks;
3231 3856 }
3232 - return $callbacks;
3233 -}
3234 3857
3858 + // Add callbacks from active add-ons (will override placeholders)
3859 + foreach ($active_addon_callbacks as $function => $data) {
3860 + // Skip excluded functions
3861 + if (in_array($function, $excluded_functions)) {
3862 + continue;
3863 + }
3235 3864
3236 -private function mxchat_average_vectors($vectors) {
3237 - $vector_length = count($vectors[0]);
3238 - $sum_vector = array_fill(0, $vector_length, 0);
3865 + // Always include callbacks from add-ons
3866 + $callbacks[$function] = $data;
3239 3867
3240 - foreach ($vectors as $vector) {
3241 - for ($i = 0; $i < $vector_length; $i++) {
3242 - $sum_vector[$i] += $vector[$i];
3868 + // Ensure they have the proper add-on info
3869 + if (isset($addon_function_mapping[$function])) {
3870 + $addon_info = $addon_function_mapping[$function];
3871 + $callbacks[$function]['addon'] = $addon_info['addon'];
3872 + $callbacks[$function]['addon_name'] = $addon_info['addon_name'];
3873 + $callbacks[$function]['installed'] = $addon_info['is_installed'];
3874 + $callbacks[$function]['pro_only'] = $addon_info['pro_required'];
3243 3875 }
3244 3876 }
3245 3877
3246 - // Divide each component by the number of vectors to get the average
3247 - $num_vectors = count($vectors);
3248 - for ($i = 0; $i < $vector_length; $i++) {
3249 - $sum_vector[$i] /= $num_vectors;
3878 + // Just before returning callbacks, sort them to prioritize free features
3879 + if (!$grouped) {
3880 + // Create temporary arrays for sorting
3881 + $free_callbacks = array();
3882 + $pro_callbacks = array();
3883 +
3884 + // Split callbacks into free and pro
3885 + foreach ($callbacks as $key => $data) {
3886 + if (isset($data['pro_only']) && $data['pro_only']) {
3887 + $pro_callbacks[$key] = $data;
3888 + } else {
3889 + $free_callbacks[$key] = $data;
3890 + }
3891 + }
3892 +
3893 + // Merge with free callbacks first
3894 + $callbacks = array_merge($free_callbacks, $pro_callbacks);
3250 3895 }
3251 3896
3252 - return $sum_vector;
3253 -}
3897 + // Return grouped structure if requested
3898 + if ($grouped) {
3899 + $grouped_callbacks = array();
3900 + foreach ($callbacks as $key => $data) {
3901 + $group_label = isset($data['group']) ? $data['group'] : __('Other Features', 'mxchat');
3254 3902
3255 -public function mxchat_handle_delete_intent() {
3256 - if ( ! current_user_can( 'manage_options' ) ) {
3257 - wp_die( esc_html__('Unauthorized user', 'mxchat') );
3258 - }
3903 + // Ensure we carry forward all the new fields in grouped mode
3904 + $callback_data = array(
3905 + 'label' => $data['label'],
3906 + 'pro_only' => isset($data['pro_only']) ? $data['pro_only'] : false,
3907 + 'icon' => isset($data['icon']) ? $data['icon'] : 'admin-generic',
3908 + 'description' => isset($data['description']) ? $data['description'] : __('Custom action for your chatbot', 'mxchat'),
3909 + 'addon' => isset($data['addon']) ? $data['addon'] : false,
3910 + 'addon_name' => isset($data['addon_name']) ? $data['addon_name'] : '',
3911 + 'installed' => isset($data['installed']) ? $data['installed'] : true
3912 + );
3259 3913
3260 - check_admin_referer('mxchat_delete_intent_nonce');
3914 + $grouped_callbacks[$group_label][$key] = $callback_data;
3915 + }
3261 3916
3262 - if (isset($_POST['intent_id'])) {
3263 - global $wpdb;
3264 - $table_name = $wpdb->prefix . 'mxchat_intents';
3265 - $intent_id = intval($_POST['intent_id']);
3917 + // Sort within each group to prioritize free features
3918 + foreach ($grouped_callbacks as $group => $items) {
3919 + $free_items = array();
3920 + $pro_items = array();
3266 3921
3267 - $wpdb->delete($table_name, ['id' => $intent_id], ['%d']);
3922 + foreach ($items as $key => $data) {
3923 + if (isset($data['pro_only']) && $data['pro_only']) {
3924 + $pro_items[$key] = $data;
3925 + } else {
3926 + $free_items[$key] = $data;
3927 + }
3928 + }
3929 +
3930 + $grouped_callbacks[$group] = array_merge($free_items, $pro_items);
3931 + }
3932 +
3933 + return $grouped_callbacks;
3268 3934 }
3269 3935
3270 - wp_safe_redirect(admin_url('admin.php?page=mxchat-intents'));
3271 - exit;
3936 + return $callbacks;
3272 3937 }
3273 3938
3274 -
3275 3939 public function mxchat_page_init() {
3276 - // Register settings
3277 3940 register_setting(
3278 3941 'mxchat_option_group',
3279 3942 'mxchat_options',
3280 3943 array($this, 'mxchat_sanitize')
@@ -3285,10 +3948,10 @@
3285 3948 'mxchat_similarity_threshold',
3286 3949 array(
3287 3950 'type' => 'number',
3288 3951 'sanitize_callback' => function($value) {
3289 - $value = absint($value); // Ensure it's an integer
3290 - return min(max($value, 70), 85); // Enforce range
3952 + $value = absint($value);
3953 + return min(max($value, 20), 95);
3291 3954 },
3292 3955 'default' => 80,
3293 3956 )
3294 3957 );
@@ -3300,8 +3963,106 @@
3300 3963 null,
3301 3964 'mxchat-chatbot'
3302 3965 );
3303 3966
3967 + // API Keys Settings Section
3968 + add_settings_section(
3969 + 'mxchat_api_keys_section',
3970 + esc_html__('API Keys', 'mxchat'),
3971 + array($this, 'mxchat_api_keys_section_callback'),
3972 + 'mxchat-api-keys'
3973 + );
3974 +
3975 + // OpenAI API Key
3976 + add_settings_field(
3977 + 'api_key',
3978 + esc_html__('OpenAI API Key', 'mxchat'),
3979 + array($this, 'api_key_callback'),
3980 + 'mxchat-api-keys',
3981 + 'mxchat_api_keys_section'
3982 + );
3983 +
3984 + // X.AI API Key
3985 + add_settings_field(
3986 + 'xai_api_key',
3987 + esc_html__('X.AI API Key', 'mxchat'),
3988 + array($this, 'xai_api_key_callback'),
3989 + 'mxchat-api-keys',
3990 + 'mxchat_api_keys_section'
3991 + );
3992 +
3993 + // Claude API Key
3994 + add_settings_field(
3995 + 'claude_api_key',
3996 + esc_html__('Claude API Key', 'mxchat'),
3997 + array($this, 'claude_api_key_callback'),
3998 + 'mxchat-api-keys',
3999 + 'mxchat_api_keys_section'
4000 + );
4001 +
4002 + // DeepSeek API Key
4003 + add_settings_field(
4004 + 'deepseek_api_key',
4005 + esc_html__('DeepSeek API Key', 'mxchat'),
4006 + array($this, 'deepseek_api_key_callback'),
4007 + 'mxchat-api-keys',
4008 + 'mxchat_api_keys_section'
4009 + );
4010 +
4011 + // Google Gemini API Key
4012 + add_settings_field(
4013 + 'gemini_api_key',
4014 + esc_html__('Google Gemini API Key', 'mxchat'),
4015 + array($this, 'gemini_api_key_callback'),
4016 + 'mxchat-api-keys',
4017 + 'mxchat_api_keys_section'
4018 + );
4019 +
4020 + // Voyage AI API Key
4021 + add_settings_field(
4022 + 'voyage_api_key',
4023 + esc_html__('Voyage AI API Key', 'mxchat'),
4024 + array($this, 'voyage_api_key_callback'),
4025 + 'mxchat-api-keys',
4026 + 'mxchat_api_keys_section'
4027 + );
4028 +
4029 + // OpenRouter API Key
4030 + add_settings_field(
4031 + 'openrouter_api_key',
4032 + esc_html__('OpenRouter API Key', 'mxchat'),
4033 + array($this, 'openrouter_api_key_callback'),
4034 + 'mxchat-api-keys',
4035 + 'mxchat_api_keys_section'
4036 + );
4037 +
4038 + // Loops API Key
4039 + add_settings_field(
4040 + 'loops_api_key',
4041 + esc_html__('Loops API Key', 'mxchat'),
4042 + array($this, 'mxchat_loops_api_key_callback'),
4043 + 'mxchat-api-keys',
4044 + 'mxchat_api_keys_section'
4045 + );
4046 +
4047 + // Brave Search API Key
4048 + add_settings_field(
4049 + 'brave_api_key',
4050 + __('Brave API Key', 'mxchat'),
4051 + array($this, 'mxchat_brave_api_key_callback'),
4052 + 'mxchat-api-keys',
4053 + 'mxchat_api_keys_section'
4054 + );
4055 +
4056 + // Frontend Debugger Toggle
4057 + add_settings_field(
4058 + 'show_frontend_debugger',
4059 + esc_html__('Frontend Debugger', 'mxchat'),
4060 + array($this, 'mxchat_frontend_debugger_callback'),
4061 + 'mxchat-chatbot',
4062 + 'mxchat_chatbot_section'
4063 + );
4064 +
3304 4065 // Similarity Threshold Slider
3305 4066 add_settings_field(
3306 4067 'similarity_threshold', // Field ID
3307 4068 esc_html__('Similarity Threshold', 'mxchat'), // Field title
@@ -3317,38 +4078,49 @@
3317 4078 'mxchat-chatbot',
3318 4079 'mxchat_chatbot_section'
3319 4080 );
3320 4081
4082 + add_settings_field(
4083 + 'contextual_awareness_toggle',
4084 + esc_html__('Contextual Awareness', 'mxchat'),
4085 + array($this, 'mxchat_contextual_awareness_callback'),
4086 + 'mxchat-chatbot',
4087 + 'mxchat_chatbot_section'
4088 + );
3321 4089
3322 - // Existing fields...
3323 4090 add_settings_field(
3324 - 'api_key',
3325 - esc_html__('OpenAI API Key', 'mxchat'),
3326 - array($this, 'api_key_callback'),
4091 + 'citation_links_toggle',
4092 + esc_html__('Citation Links', 'mxchat'),
4093 + array($this, 'mxchat_citation_links_toggle_callback'),
3327 4094 'mxchat-chatbot',
3328 4095 'mxchat_chatbot_section'
3329 4096 );
3330 4097
3331 4098 add_settings_field(
3332 - 'xai_api_key',
3333 - esc_html__('X.AI API Key', 'mxchat'),
3334 - array($this, 'xai_api_key_callback'),
4099 + 'enable_streaming_toggle',
4100 + esc_html__('Enable Streaming', 'mxchat'),
4101 + array($this, 'enable_streaming_toggle_callback'),
3335 4102 'mxchat-chatbot',
3336 - 'mxchat_chatbot_section'
4103 + 'mxchat_chatbot_section', // Same section as your working toggle
4104 + array(
4105 + 'class' => 'mxchat-setting-row streaming-setting',
4106 + 'style' => 'display: none;' // Hidden by default, shown when OpenAI/Claude selected
4107 + )
3337 4108 );
3338 4109
4110 +
3339 4111 add_settings_field(
3340 - 'claude_api_key',
3341 - esc_html__('Claude API Key', 'mxchat'),
3342 - array($this, 'claude_api_key_callback'),
4112 + 'model',
4113 + esc_html__('Chat Model', 'mxchat'),
4114 + array($this, 'mxchat_model_callback'),
3343 4115 'mxchat-chatbot',
3344 4116 'mxchat_chatbot_section'
3345 4117 );
3346 4118
3347 4119 add_settings_field(
3348 - 'deepseek_api_key',
3349 - esc_html__('DeepSeek API Key', 'mxchat'),
3350 - array($this, 'deepseek_api_key_callback'),
4120 + 'embedding_model',
4121 + esc_html__('Embedding Model', 'mxchat'),
4122 + array($this, 'embedding_model_callback'),
3351 4123 'mxchat-chatbot',
3352 4124 'mxchat_chatbot_section'
3353 4125 );
3354 4126
@@ -3359,20 +4131,21 @@
3359 4131 'mxchat-chatbot',
3360 4132 'mxchat_chatbot_section'
3361 4133 );
3362 4134
4135 +
3363 4136 add_settings_field(
3364 - 'model',
3365 - esc_html__('Model', 'mxchat'),
3366 - array($this, 'mxchat_model_callback'),
4137 + 'top_bar_title',
4138 + esc_html__('Top Bar Title', 'mxchat'),
4139 + array($this, 'mxchat_top_bar_title_callback'),
3367 4140 'mxchat-chatbot',
3368 4141 'mxchat_chatbot_section'
3369 4142 );
3370 4143
3371 4144 add_settings_field(
3372 - 'top_bar_title',
3373 - esc_html__('Top Bar Title', 'mxchat'),
3374 - array($this, 'mxchat_top_bar_title_callback'),
4145 + 'ai_agent_text',
4146 + esc_html__('AI Agent Text', 'mxchat'),
4147 + array($this, 'mxchat_ai_agent_text_callback'),
3375 4148 'mxchat-chatbot',
3376 4149 'mxchat_chatbot_section'
3377 4150 );
3378 4151
@@ -3400,48 +4173,40 @@
3400 4173 'mxchat_chatbot_section'
3401 4174 );
3402 4175
3403 4176 add_settings_field(
3404 - 'intro_message',
3405 - esc_html__('Introductory Message', 'mxchat'),
3406 - array($this, 'mxchat_intro_message_callback'),
4177 + 'enable_name_field',
4178 + esc_html__('Require Name Field', 'mxchat'),
4179 + array($this, 'enable_name_field_callback'),
3407 4180 'mxchat-chatbot',
3408 4181 'mxchat_chatbot_section'
3409 4182 );
3410 4183
3411 4184 add_settings_field(
3412 - 'input_copy',
3413 - esc_html__('Input Copy', 'mxchat'),
3414 - array($this, 'mxchat_input_copy_callback'),
4185 + 'name_field_placeholder',
4186 + esc_html__('Name Field Placeholder', 'mxchat'),
4187 + array($this, 'name_field_placeholder_callback'),
3415 4188 'mxchat-chatbot',
3416 4189 'mxchat_chatbot_section'
3417 4190 );
3418 4191
3419 4192 add_settings_field(
3420 - 'rate_limit_logged_out',
3421 - __('Rate Limit for Logged-out Users', 'mxchat'),
3422 - array($this, 'mxchat_rate_limit_logged_out_callback'),
4193 + 'intro_message',
4194 + esc_html__('Introductory Message', 'mxchat'),
4195 + array($this, 'mxchat_intro_message_callback'),
3423 4196 'mxchat-chatbot',
3424 4197 'mxchat_chatbot_section'
3425 4198 );
3426 4199
3427 4200 add_settings_field(
3428 - 'rate_limit_roles',
3429 - __('Rate Limits by User Role', 'mxchat'),
3430 - array($this, 'mxchat_rate_limit_roles_callback'),
4201 + 'input_copy',
4202 + esc_html__('Input Copy', 'mxchat'),
4203 + array($this, 'mxchat_input_copy_callback'),
3431 4204 'mxchat-chatbot',
3432 4205 'mxchat_chatbot_section'
3433 4206 );
3434 4207
3435 4208 add_settings_field(
3436 - 'rate_limit_message',
3437 - esc_html__('Rate Limit Message', 'mxchat'),
3438 - array($this, 'mxchat_rate_limit_message_callback'),
3439 - 'mxchat-chatbot',
3440 - 'mxchat_chatbot_section'
3441 - );
3442 -
3443 - add_settings_field(
3444 4209 'pre_chat_message',
3445 4210 esc_html__('Chat Teaser Pop-up', 'mxchat'),
3446 4211 array($this, 'mxchat_pre_chat_message_callback'),
3447 4212 'mxchat-chatbot',
@@ -3512,8 +4277,16 @@
3512 4277 'mxchat_chatbot_section'
3513 4278 );
3514 4279
3515 4280
4281 + add_settings_field(
4282 + 'rate_limits',
4283 + __('Rate Limits Settings', 'mxchat'),
4284 + array($this, 'mxchat_rate_limits_callback'),
4285 + 'mxchat-chatbot',
4286 + 'mxchat_chatbot_section'
4287 + );
4288 +
3516 4289 // Loops Settings Section
3517 4290 add_settings_section(
3518 4291 'mxchat_loops_section',
3519 4292 esc_html__('Loops Settings', 'mxchat'),
@@ -3520,18 +4293,10 @@
3520 4293 null,
3521 4294 'mxchat-embed'
3522 4295 );
3523 4296
3524 - // Loops Settings Fields
4297 + // Loops Settings Fields (API Key moved to API Keys tab)
3525 4298 add_settings_field(
3526 - 'loops_api_key',
3527 - esc_html__('Loops API Key', 'mxchat'),
3528 - array($this, 'mxchat_loops_api_key_callback'),
3529 - 'mxchat-embed',
3530 - 'mxchat_loops_section'
3531 - );
3532 -
3533 - add_settings_field(
3534 4299 'loops_mailing_list',
3535 4300 esc_html__('Loops Mailing List', 'mxchat'),
3536 4301 array($this, 'mxchat_loops_mailing_list_callback'),
3537 4302 'mxchat-embed',
@@ -3561,17 +4326,10 @@
3561 4326 array($this, 'mxchat_brave_section_callback'),
3562 4327 'mxchat-embed'
3563 4328 );
3564 4329
4330 + // Brave API Key moved to API Keys tab
3565 4331 add_settings_field(
3566 - 'brave_api_key',
3567 - __('Brave API Key', 'mxchat'),
3568 - array($this, 'mxchat_brave_api_key_callback'),
3569 - 'mxchat-embed',
3570 - 'mxchat_brave_section'
3571 - );
3572 -
3573 - add_settings_field(
3574 4332 'brave_image_count',
3575 4333 __('Number of Images to Return', 'mxchat'),
3576 4334 array($this, 'mxchat_brave_image_count_callback'),
3577 4335 'mxchat-embed',
@@ -3625,9 +4383,27 @@
3625 4383 'mxchat-embed',
3626 4384 'mxchat_pdf_intent_section'
3627 4385 );
3628 4386
4387 + // PDF Upload Button Toggle
3629 4388 add_settings_field(
4389 + 'show_pdf_upload_button',
4390 + __('Show PDF Upload Button', 'mxchat'),
4391 + array($this, 'mxchat_show_pdf_upload_button_callback'),
4392 + 'mxchat-embed',
4393 + 'mxchat_pdf_intent_section'
4394 + );
4395 +
4396 + // Word Upload Button Toggle
4397 + add_settings_field(
4398 + 'show_word_upload_button',
4399 + __('Show Word Upload Button', 'mxchat'),
4400 + array($this, 'mxchat_show_word_upload_button_callback'),
4401 + 'mxchat-embed',
4402 + 'mxchat_pdf_intent_section'
4403 + );
4404 +
4405 + add_settings_field(
3630 4406 'pdf_intent_trigger_text',
3631 4407 __('Intent Trigger Text', 'mxchat'),
3632 4408 array($this, 'mxchat_pdf_intent_trigger_text_callback'),
3633 4409 'mxchat-embed',
@@ -3692,8 +4468,16 @@
3692 4468 'mxchat_live_agent_section'
3693 4469 );
3694 4470
3695 4471 add_settings_field(
4472 + 'live_agent_user_ids',
4473 + __('Slack Agent User IDs', 'mxchat'),
4474 + array($this, 'mxchat_live_agent_user_ids_callback'),
4475 + 'mxchat-embed',
4476 + 'mxchat_live_agent_section'
4477 + );
4478 +
4479 + add_settings_field(
3696 4480 'live_agent_webhook_url',
3697 4481 __('Slack Webhook URL', 'mxchat'),
3698 4482 array($this, 'mxchat_live_agent_webhook_url_callback'),
3699 4483 'mxchat-embed',
@@ -3716,15 +4500,68 @@
3716 4500 'mxchat-embed',
3717 4501 'mxchat_live_agent_section'
3718 4502 );
3719 4503
4504 + // Telegram Integration Section
4505 + add_settings_section(
4506 + 'mxchat_telegram_section',
4507 + __('Telegram Settings', 'mxchat'),
4508 + array($this, 'mxchat_telegram_section_callback'),
4509 + 'mxchat-embed'
4510 + );
3720 4511
4512 + add_settings_field(
4513 + 'telegram_status',
4514 + __('Live Agent Status', 'mxchat'),
4515 + array($this, 'mxchat_telegram_status_callback'),
4516 + 'mxchat-embed',
4517 + 'mxchat_telegram_section'
4518 + );
3721 4519
4520 + add_settings_field(
4521 + 'telegram_notification_message',
4522 + __('Notification Message', 'mxchat'),
4523 + array($this, 'mxchat_telegram_notification_message_callback'),
4524 + 'mxchat-embed',
4525 + 'mxchat_telegram_section'
4526 + );
3722 4527
4528 + add_settings_field(
4529 + 'telegram_away_message',
4530 + __('Away Message', 'mxchat'),
4531 + array($this, 'mxchat_telegram_away_message_callback'),
4532 + 'mxchat-embed',
4533 + 'mxchat_telegram_section'
4534 + );
4535 +
4536 + add_settings_field(
4537 + 'telegram_bot_token',
4538 + __('Telegram Bot Token', 'mxchat'),
4539 + array($this, 'mxchat_telegram_bot_token_callback'),
4540 + 'mxchat-embed',
4541 + 'mxchat_telegram_section'
4542 + );
4543 +
4544 + add_settings_field(
4545 + 'telegram_group_id',
4546 + __('Telegram Group ID', 'mxchat'),
4547 + array($this, 'mxchat_telegram_group_id_callback'),
4548 + 'mxchat-embed',
4549 + 'mxchat_telegram_section'
4550 + );
4551 +
4552 + add_settings_field(
4553 + 'telegram_webhook_secret',
4554 + __('Webhook Secret Token', 'mxchat'),
4555 + array($this, 'mxchat_telegram_webhook_secret_callback'),
4556 + 'mxchat-embed',
4557 + 'mxchat_telegram_section'
4558 + );
4559 +
3723 4560 // General Settings Section
3724 4561 add_settings_section(
3725 4562 'mxchat_general_section',
3726 - esc_html__('Frequently Asked Questions (FAQ)', 'mxchat'),
4563 + esc_html__('YouTube Tutorials', 'mxchat'),
3727 4564 null,
3728 4565 'mxchat-general'
3729 4566 );
3730 4567 }
@@ -3729,9 +4566,8 @@
3729 4566 );
3730 4567 }
3731 4568
3732 4569 public function mxchat_prompts_page_init() {
3733 - // Register all settings as a single array
3734 4570 register_setting(
3735 4571 'mxchat_prompts_options',
3736 4572 'mxchat_prompts_options',
3737 4573 array(
@@ -3743,18 +4579,74 @@
3743 4579 'mxchat_use_pinecone' => 0,
3744 4580 'mxchat_pinecone_api_key' => '',
3745 4581 'mxchat_pinecone_environment' => '',
3746 4582 'mxchat_pinecone_index' => '',
3747 - 'mxchat_pinecone_host' => '', // Add this line
4583 + 'mxchat_pinecone_host' => '',
3748 4584 ),
3749 4585 'sanitize_callback' => array($this, 'sanitize_prompts_options'),
3750 4586 )
3751 4587 );
3752 4588
3753 - // Add settings saved message
3754 4589 add_action('admin_notices', array($this, 'sync_settings_notice'));
3755 4590 }
3756 4591
4592 +public function mxchat_transcripts_page_init() {
4593 + register_setting(
4594 + 'mxchat_transcripts_options',
4595 + 'mxchat_transcripts_options',
4596 + array(
4597 + 'type' => 'array',
4598 + 'description' => __('MXChat Transcripts Notification Settings', 'mxchat'),
4599 + 'default' => array(
4600 + 'mxchat_enable_notifications' => 0,
4601 + 'mxchat_notification_email' => get_option('admin_email'),
4602 + 'mxchat_auto_delete_transcripts' => 'never',
4603 + ),
4604 + 'sanitize_callback' => array($this, 'sanitize_transcripts_options'),
4605 + )
4606 + );
4607 +
4608 + add_settings_section(
4609 + 'mxchat_transcripts_notification_section',
4610 + esc_html__('Chat Notification Settings', 'mxchat'),
4611 + array($this, 'mxchat_transcripts_notification_section_callback'),
4612 + 'mxchat-transcripts'
4613 + );
4614 +
4615 + add_settings_field(
4616 + 'mxchat_enable_notifications',
4617 + esc_html__('Enable Chat Notifications', 'mxchat'),
4618 + array($this, 'mxchat_enable_notifications_callback'),
4619 + 'mxchat-transcripts',
4620 + 'mxchat_transcripts_notification_section'
4621 + );
4622 +
4623 + add_settings_field(
4624 + 'mxchat_notification_email',
4625 + esc_html__('Notification Email Address', 'mxchat'),
4626 + array($this, 'mxchat_notification_email_callback'),
4627 + 'mxchat-transcripts',
4628 + 'mxchat_transcripts_notification_section'
4629 + );
4630 +
4631 + add_settings_field(
4632 + 'mxchat_auto_delete_transcripts',
4633 + esc_html__('Auto-Delete Old Transcripts', 'mxchat'),
4634 + array($this, 'mxchat_auto_delete_transcripts_callback'),
4635 + 'mxchat-transcripts',
4636 + 'mxchat_transcripts_notification_section'
4637 + );
4638 +
4639 + add_settings_field(
4640 + 'mxchat_auto_email_transcript',
4641 + esc_html__('Auto-Email Full Transcript', 'mxchat'),
4642 + array($this, 'mxchat_auto_email_transcript_callback'),
4643 + 'mxchat-transcripts',
4644 + 'mxchat_transcripts_notification_section'
4645 + );
4646 +}
4647 +
4648 +
3757 4649 /**
3758 4650 * Sanitize all prompts options
3759 4651 *
3760 4652 * @param array $input The unsanitized options array
@@ -3813,9 +4705,8 @@
3813 4705
3814 4706 return $sanitized;
3815 4707 }
3816 4708
3817 -
3818 4709 public function sync_settings_notice() {
3819 4710 // Only show notice on our plugin page
3820 4711 if (!isset($_GET['page']) || $_GET['page'] !== 'mxchat-prompts') {
3821 4712 return;
@@ -3826,8 +4717,9 @@
3826 4717
3827 4718 ?>
3828 4719 <div class="notice notice-success is-dismissible">
3829 4720 <p><?php esc_html_e('Sync settings updated successfully.', 'mxchat'); ?></p>
4721 + <button type="button" class="notice-dismiss"><span class="screen-reader-text"><?php esc_html_e('Dismiss this notice.', 'mxchat'); ?></span></button>
3830 4722 </div>
3831 4723 <?php
3832 4724
3833 4725 }
@@ -3836,139 +4728,129 @@
3836 4728 public function sanitize_sync_setting($input) {
3837 4729 return (bool)$input ? __('1', 'mxchat') : __('', 'mxchat');
3838 4730 }
3839 4731
4732 +public function mxchat_rate_limits_callback() {
4733 + $all_options = get_option('mxchat_options', []);
3840 4734
4735 + // Define available rate limits
4736 + $rate_limits = array('1', '3', '5', '10', '15', '20', '50', '100', 'unlimited');
3841 4737
3842 -public function mxchat_handle_activate_license() {
3843 - // Check nonce
3844 - if (!check_ajax_referer('mxchat_activate_license_nonce', 'security', false)) {
3845 - wp_send_json_error(esc_html__('Invalid security token', 'mxchat'));
3846 - return;
3847 - }
4738 + // Define available timeframes
4739 + $timeframes = array(
4740 + 'hourly' => __('Per Hour', 'mxchat'),
4741 + 'daily' => __('Per Day', 'mxchat'),
4742 + 'weekly' => __('Per Week', 'mxchat'),
4743 + 'monthly' => __('Per Month', 'mxchat')
4744 + );
3848 4745
3849 - // Verify user capabilities
3850 - if (!current_user_can('manage_options')) {
3851 - wp_send_json_error(esc_html__('Unauthorized access', 'mxchat'));
3852 - return;
3853 - }
4746 + // Get all roles plus a "logged_out" pseudo-role
4747 + $roles = wp_roles()->get_names();
4748 + $roles['logged_out'] = __('Logged Out Users', 'mxchat');
3854 4749
3855 - $license_key = isset($_POST['mxchat_activation_key']) ? sanitize_text_field($_POST['mxchat_activation_key']) : '';
3856 - $customer_email = isset($_POST['mxchat_pro_email']) ? sanitize_email($_POST['mxchat_pro_email']) : '';
4750 + // Start the wrapper
4751 + echo '<div class="pro-feature-wrapper active">';
4752 + echo '<div class="mxchat-rate-limits-container">';
3857 4753
3858 - if (empty($license_key) || empty($customer_email)) {
3859 - wp_send_json_error(esc_html__('Email or License Key is missing', 'mxchat'));
3860 - return;
3861 - }
4754 + echo '<p class="description" style="margin-bottom: 20px;">' .
4755 + 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') .
4756 + '</p>';
3862 4757
3863 - $product_id = 'MxChatPRO';
3864 - $response = wp_remote_get(
3865 - add_query_arg(
3866 - array(
3867 - 'wc-api' => 'software-api',
3868 - 'request' => 'activation',
3869 - 'email' => $customer_email,
3870 - 'license_key' => $license_key,
3871 - 'product_id' => $product_id
3872 - ),
3873 - 'http://mxchat.ai/'
3874 - )
3875 - );
4758 + // Add markdown link documentation
4759 + echo '<div class="notice notice-info inline" style="margin-bottom: 20px; padding: 10px;">';
4760 + echo '<p><strong>' . esc_html__('Markdown Links Supported:', 'mxchat') . '</strong></p>';
4761 + echo '<p>' . esc_html__('You can include clickable links in your custom messages using markdown syntax:', 'mxchat') . '</p>';
4762 + echo '<ul style="margin-left: 20px;">';
4763 + echo '<li><code>[Link text](https://example.com)</code> - Creates a clickable link</li>';
4764 + echo '<li><code>[Visit our pricing](https://example.com/pricing)</code> - Link with custom text</li>';
4765 + echo '<li><code>Plain URLs like https://example.com will also become clickable</code></li>';
4766 + echo '</ul>';
4767 + echo '</div>';
3876 4768
3877 - if (is_wp_error($response)) {
3878 - wp_send_json_error(esc_html__('Activation failed due to a server error: ', 'mxchat') . $response->get_error_message());
3879 - return;
3880 - }
4769 + // Output the controls for each role
4770 + foreach ($roles as $role_id => $role_name) {
4771 + // Get saved options or defaults
4772 + $default_limit = ($role_id === 'logged_out') ? '10' : '100';
4773 + $default_timeframe = 'daily';
4774 + $default_message = __('Rate limit exceeded. Please try again later.', 'mxchat');
3881 4775
3882 - $body = wp_remote_retrieve_body($response);
3883 - $data = json_decode($body);
4776 + $selected_limit = isset($all_options['rate_limits'][$role_id]['limit'])
4777 + ? $all_options['rate_limits'][$role_id]['limit']
4778 + : $default_limit;
3884 4779
3885 - if ($data && isset($data->activated) && $data->activated) {
3886 - update_option('mxchat_license_status', 'active');
3887 - update_option('mxchat_pro_email', $customer_email);
3888 - update_option('mxchat_activation_key', $license_key);
3889 - wp_send_json_success(array('message' => esc_html__('License activated successfully', 'mxchat')));
3890 - } else {
3891 - $error_message = isset($data->error) ? $data->error : esc_html__('Activation failed', 'mxchat');
3892 - update_option('mxchat_license_status', 'inactive');
3893 - update_option('mxchat_license_error', $error_message);
3894 - wp_send_json_error($error_message);
3895 - }
3896 -}
4780 + $selected_timeframe = isset($all_options['rate_limits'][$role_id]['timeframe'])
4781 + ? $all_options['rate_limits'][$role_id]['timeframe']
4782 + : $default_timeframe;
3897 4783
4784 + $custom_message = isset($all_options['rate_limits'][$role_id]['message'])
4785 + ? $all_options['rate_limits'][$role_id]['message']
4786 + : $default_message;
3898 4787
3899 -public function mxchat_rate_limit_logged_out_callback() {
3900 - // Load the entire 'mxchat_options' array
3901 - $all_options = get_option('mxchat_options', []);
4788 + // Output the row
4789 + echo '<div class="mxchat-rate-limit-row mxchat-autosave-section">';
3902 4790
3903 - // Retrieve the saved rate limit or use the default value
3904 - $default_rate_limit = '10';
3905 - $selected_rate_limit = isset($all_options['rate_limit_logged_out']) ? $all_options['rate_limit_logged_out'] : $default_rate_limit;
4791 + // Role label
4792 + echo '<div class="mxchat-rate-limit-role">' . esc_html($role_name) . '</div>';
3906 4793
3907 - // Define available rate limits
3908 - $rate_limits = array('1', '3', '5', '10', '15', '20', '100', 'unlimited');
4794 + // Controls section
4795 + echo '<div class="mxchat-rate-limit-controls-wrapper">';
3909 4796
3910 - // Output the dropdown
3911 - echo '<div class="pro-feature-wrapper active">';
3912 - echo '<select id="rate_limit_logged_out" name="rate_limit_logged_out">';
3913 - foreach ($rate_limits as $limit) {
3914 - echo '<option value="' . esc_attr($limit) . '" ' . selected($selected_rate_limit, $limit, false) . '>' . esc_html($limit) . '</option>';
3915 - }
3916 - echo '</select>';
3917 - 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>';
3918 - echo '</div>';
3919 -}
4797 + // Rate limit and timeframe controls
4798 + echo '<div class="mxchat-rate-limit-controls">';
3920 4799
3921 -// Add this new callback function
3922 -public function mxchat_rate_limit_roles_callback() {
3923 - $all_options = get_option('mxchat_options', []);
3924 - $roles = wp_roles()->get_names();
3925 - $rate_limits = array('1', '3', '5', '10', '15', '20', '100', 'unlimited');
4800 + // Limit dropdown
4801 + echo '<div>';
4802 + echo '<label for="rate_limits_' . esc_attr($role_id) . '_limit">' . esc_html__('Limit:', 'mxchat') . '</label>';
4803 + echo '<select
4804 + id="rate_limits_' . esc_attr($role_id) . '_limit"
4805 + name="mxchat_options[rate_limits][' . esc_attr($role_id) . '][limit]"
4806 + class="mxchat-autosave-field">';
4807 + foreach ($rate_limits as $limit) {
4808 + echo '<option value="' . esc_attr($limit) . '" ' . selected($selected_limit, $limit, false) . '>' . esc_html($limit) . '</option>';
4809 + }
4810 + echo '</select>';
4811 + echo '</div>';
3926 4812
3927 - echo '<div class="pro-feature-wrapper active mxchat-autosave-section">';
3928 -
3929 - foreach ($roles as $role_id => $role_name) {
3930 - $default_rate_limit = '100';
3931 - $selected_rate_limit = isset($all_options['role_rate_limits'][$role_id])
3932 - ? $all_options['role_rate_limits'][$role_id]
3933 - : $default_rate_limit;
3934 -
3935 - echo '<div style="margin-bottom: 10px;">';
3936 - echo '<label style="display: inline-block; width: 150px;">' . esc_html($role_name) . ':</label>';
4813 + // Timeframe dropdown
4814 + echo '<div>';
4815 + echo '<label for="rate_limits_' . esc_attr($role_id) . '_timeframe">' . esc_html__('Timeframe:', 'mxchat') . '</label>';
3937 4816 echo '<select
3938 - id="role_rate_limits_' . esc_attr($role_id) . '"
3939 - name="mxchat_options[role_rate_limits][' . esc_attr($role_id) . ']"
4817 + id="rate_limits_' . esc_attr($role_id) . '_timeframe"
4818 + name="mxchat_options[rate_limits][' . esc_attr($role_id) . '][timeframe]"
3940 4819 class="mxchat-autosave-field">';
3941 - foreach ($rate_limits as $limit) {
3942 - echo '<option value="' . esc_attr($limit) . '" ' . selected($selected_rate_limit, $limit, false) . '>' . esc_html($limit) . '</option>';
4820 + foreach ($timeframes as $value => $label) {
4821 + echo '<option value="' . esc_attr($value) . '" ' . selected($selected_timeframe, $value, false) . '>' . esc_html($label) . '</option>';
3943 4822 }
3944 4823 echo '</select>';
3945 4824 echo '</div>';
3946 - }
3947 4825
3948 - 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>';
3949 - echo '</div>';
3950 -}
4826 + echo '</div>'; // End controls
3951 4827
3952 -public function mxchat_rate_limit_message_callback() {
3953 - // Load the entire 'mxchat_options' array
3954 - $all_options = get_option('mxchat_options', []);
4828 + // Custom message textarea
4829 + echo '<div class="mxchat-rate-limit-message">';
4830 + echo '<label for="rate_limits_' . esc_attr($role_id) . '_message">' . esc_html__('Custom Message:', 'mxchat') . '</label>';
4831 + echo '<textarea
4832 + id="rate_limits_' . esc_attr($role_id) . '_message"
4833 + name="mxchat_options[rate_limits][' . esc_attr($role_id) . '][message]"
4834 + class="mxchat-autosave-field"
4835 + placeholder="' . esc_attr__('Enter custom message when rate limit is exceeded', 'mxchat') . '">' .
4836 + esc_textarea($custom_message) .
4837 + '</textarea>';
4838 + echo '<p class="description">' .
4839 + esc_html__('Example: Rate limit reached! [Visit our pricing page](https://example.com/pricing) to upgrade.', 'mxchat') .
4840 + '</p>';
4841 + echo '</div>'; // End message
3955 4842
3956 - // Retrieve the saved message or use the default value
3957 - $default_message = esc_html__('Rate limit exceeded. Please try again later.', 'mxchat');
3958 - $rate_limit_message = isset($all_options['rate_limit_message']) ? $all_options['rate_limit_message'] : $default_message;
4843 + echo '</div>'; // End controls wrapper
3959 4844
3960 - // Output the textarea
3961 - echo '<div class="pro-feature-wrapper active">';
3962 - printf(
3963 - '<textarea id="rate_limit_message" name="rate_limit_message" rows="3" cols="50">%s</textarea>',
3964 - esc_textarea($rate_limit_message)
3965 - );
3966 - echo '<p class="description">' . esc_html__('This message will be displayed when a user exceeds the rate limit.', 'mxchat') . '</p>';
3967 - echo '</div>';
4845 + echo '</div>'; // End row
4846 + }
4847 +
4848 + echo '</div>'; // End container
4849 +
4850 + echo '</div>'; // End pro-feature-wrapper
3968 4851 }
3969 4852
3970 -
3971 4853 private function mxchat_add_option_field($id, $title, $callback = '') {
3972 4854 add_settings_field(
3973 4855 $id,
3974 4856 __($title, 'mxchat'),
@@ -3978,94 +4860,105 @@
3978 4860 $id === 'model' ? ['label_for' => 'model'] : []
3979 4861 );
3980 4862 }
3981 4863
4864 +// API Keys Section Callback
4865 +public function mxchat_api_keys_section_callback() {
4866 + 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>';
4867 +}
4868 +
4869 +// OpenAI API Key
3982 4870 public function api_key_callback() {
3983 - // Retrieve from your stored 'api_key' in the mxchat_options array
3984 4871 $apiKey = isset($this->options['api_key']) ? esc_attr($this->options['api_key']) : '';
3985 4872
3986 - // Notice we changed the name to "api_key" (no array notation).
3987 - echo '<input type="password" id="api_key" name="api_key" value="' . $apiKey . '" class="regular-text" />';
4873 + echo '<div class="api-key-wrapper">';
4874 + echo '<input type="password" id="api_key" name="api_key" value="' . $apiKey . '" class="regular-text" autocomplete="off" />';
3988 4875 echo '<button type="button" id="toggleApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
3989 - echo '<p class="description">' . wp_kses_post(__('The <strong>OpenAI API key is required even when using other models</strong> as it is used for vector embedding functionality. You must add credits to your OpenAI billing account before use.', 'mxchat')) . '</p>';
4876 + echo '<p class="description">' . esc_html__('Required for OpenAI GPT models and OpenAI embeddings. Get your API key from OpenAI Platform.', 'mxchat') . '</p>';
4877 + echo '</div>';
3990 4878 }
4879 +
4880 +// X.AI API Key
3991 4881 public function xai_api_key_callback() {
3992 - // Check if the feature is activated (paid feature)
3993 - $disabled = $this->is_activated ? '' : 'disabled'; // Disable input if not activated
3994 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive'; // CSS class to style the wrapper based on activation status
3995 -
3996 - // Retrieve the X.AI API key value
3997 4882 $xaiApiKey = isset($this->options['xai_api_key']) ? esc_attr($this->options['xai_api_key']) : '';
3998 4883
3999 - // Render the input field for the X.AI API key
4000 - echo '<div class="' . esc_attr($class) . '">';
4001 - printf(
4002 - '<input type="password" id="xai_api_key" name="xai_api_key" value="%s" class="regular-text" %s />',
4003 - $xaiApiKey,
4004 - $disabled
4005 - );
4884 + echo '<div class="api-key-wrapper">';
4885 + echo '<input type="password" id="xai_api_key" name="xai_api_key" value="' . $xaiApiKey . '" class="regular-text" autocomplete="off" />';
4006 4886 echo '<button type="button" id="toggleXaiApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
4007 -
4008 - // If the feature is not activated, show the overlay with a "Pro Only" message
4009 - if (!$this->is_activated) {
4010 - echo '<div class="pro-feature-overlay">';
4011 - 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>';
4012 - echo '</div>';
4013 - }
4014 -
4887 + echo '<p class="description">' . esc_html__('Required for X.AI Grok models. Get your API key from X.AI Console.', 'mxchat') . '</p>';
4015 4888 echo '</div>';
4016 4889 }
4890 +// Claude API Key
4017 4891 public function claude_api_key_callback() {
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 - // Retrieve the Claude API key value directly like the others
4023 4892 $claudeApiKey = isset($this->options['claude_api_key']) ? esc_attr($this->options['claude_api_key']) : '';
4024 -
4025 - // Use direct name field like the other working API keys
4026 - echo '<div class="' . esc_attr($class) . '">';
4027 - printf(
4028 - '<input type="password" id="claude_api_key" name="claude_api_key" value="%s" class="regular-text" %s />',
4029 - $claudeApiKey,
4030 - $disabled
4031 - );
4893 +
4894 + echo '<div class="api-key-wrapper">';
4895 + echo '<input type="password" id="claude_api_key" name="claude_api_key" value="' . $claudeApiKey . '" class="regular-text" autocomplete="off" />';
4032 4896 echo '<button type="button" id="toggleClaudeApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
4033 -
4034 - if (!$this->is_activated) {
4035 - echo '<div class="pro-feature-overlay">';
4036 - 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>';
4037 - echo '</div>';
4038 - }
4897 + echo '<p class="description">' . esc_html__('Required for Anthropic Claude models. Get your API key from Anthropic Console.', 'mxchat') . '</p>';
4039 4898 echo '</div>';
4040 4899 }
4900 +
4901 +// DeepSeek API Key
4041 4902 public function deepseek_api_key_callback() {
4042 - // Retrieve from your stored 'deepseek_api_key' in the mxchat_options array
4043 4903 $apiKey = isset($this->options['deepseek_api_key']) ? esc_attr($this->options['deepseek_api_key']) : '';
4044 4904
4045 - // Notice we changed the name to "deepseek_api_key" (no array notation).
4046 - echo '<input type="password" id="deepseek_api_key" name="deepseek_api_key" value="' . $apiKey . '" class="regular-text" />';
4905 + echo '<div class="api-key-wrapper">';
4906 + echo '<input type="password" id="deepseek_api_key" name="deepseek_api_key" value="' . $apiKey . '" class="regular-text" autocomplete="off" />';
4047 4907 echo '<button type="button" id="toggleDeepSeekApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
4908 + echo '<p class="description">' . esc_html__('Required for DeepSeek models. Get your API key from DeepSeek Platform.', 'mxchat') . '</p>';
4909 + echo '</div>';
4048 4910 }
4049 4911
4912 +// Gemini API Key
4913 +public function gemini_api_key_callback() {
4914 + $geminiApiKey = isset($this->options['gemini_api_key']) ? esc_attr($this->options['gemini_api_key']) : '';
4050 4915
4916 + echo '<div class="api-key-wrapper">';
4917 + echo '<input type="password" id="gemini_api_key" name="gemini_api_key" value="' . $geminiApiKey . '" class="regular-text" autocomplete="off" />';
4918 + echo '<button type="button" id="toggleGeminiApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
4919 + echo '<p class="description">' . esc_html__('Required for Google Gemini models and embeddings. Get your API key from Google AI Studio.', 'mxchat') . '</p>';
4920 + echo '</div>';
4921 +}
4051 4922
4052 4923
4924 +// OpenRouter API Key
4925 +public function openrouter_api_key_callback() {
4926 + $openrouterApiKey = isset($this->options['openrouter_api_key']) ? esc_attr($this->options['openrouter_api_key']) : '';
4927 + echo '<div class="api-key-wrapper">';
4928 + echo '<input type="password" id="openrouter_api_key" name="openrouter_api_key" value="' . $openrouterApiKey . '" class="regular-text" autocomplete="off" />';
4929 + echo '<button type="button" id="toggleOpenRouterApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
4930 + echo '<p class="description">' . esc_html__('Required for OpenRouter models. Get your API key from OpenRouter.ai', 'mxchat') . '</p>';
4931 + echo '</div>';
4932 +}
4053 4933
4934 +// Voyage API Key
4935 +public function voyage_api_key_callback() {
4936 + $apiKey = isset($this->options['voyage_api_key']) ? esc_attr($this->options['voyage_api_key']) : '';
4937 +
4938 + echo '<div class="api-key-wrapper">';
4939 + echo '<input type="password" id="voyage_api_key" name="voyage_api_key" value="' . $apiKey . '" class="regular-text" autocomplete="off" />';
4940 + echo '<button type="button" id="toggleVoyageAPIKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
4941 + echo '<p class="description">' . esc_html__('Required for Voyage AI embedding models. Get your API key from Voyage AI.', 'mxchat') . '</p>';
4942 + echo '</div>';
4943 +}
4944 +
4054 4945 public function mxchat_loops_api_key_callback() {
4055 - // Support both old and new format
4056 4946 $loops_api_key = isset($this->options['loops_api_key']) ? esc_attr($this->options['loops_api_key']) : '';
4057 4947
4948 + // Hidden fields to "trap" autofill
4949 + echo '<input type="text" style="display:none" autocomplete="username" />';
4950 + echo '<input type="password" style="display:none" autocomplete="current-password" />';
4951 +
4058 4952 echo '<div class="api-key-wrapper">';
4059 4953 echo sprintf(
4060 - '<input type="password" id="loops_api_key" name="loops_api_key" value="%s" class="regular-text" />',
4954 + '<input type="password" id="loops_api_key" name="loops_api_key" value="%s" class="regular-text" autocomplete="new-password" />',
4061 4955 $loops_api_key
4062 4956 );
4063 4957 echo '<button type="button" id="toggleLoopsApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
4064 4958 echo '</div>';
4065 - echo '<p class="description">' . esc_html__('Enter your Loops API Key here. (See FAQ for details)', 'mxchat') . '</p>';
4959 + echo '<p class="description">' . esc_html__('Required for Loops email integration. Get your API key from Loops.so', 'mxchat') . '</p>';
4066 4960 }
4067 -
4068 4961 public function mxchat_loops_mailing_list_callback() {
4069 4962 // Add error handling and type checking
4070 4963 $loops_api_key = '';
4071 4964 $selected_list = '';
@@ -4083,8 +4976,12 @@
4083 4976 if (!empty($loops_api_key)) {
4084 4977 $lists = $this->mxchat_fetch_loops_mailing_lists($loops_api_key);
4085 4978 if (is_array($lists) && !empty($lists)) {
4086 4979 echo '<select id="loops_mailing_list" name="loops_mailing_list">';
4980 +
4981 + // Add a default "Select a list" option
4982 + echo '<option value="" ' . selected($selected_list, '', false) . '>' . esc_html__('Select a list', 'mxchat') . '</option>';
4983 +
4087 4984 foreach ($lists as $list) {
4088 4985 if (is_array($list) && isset($list['id']) && isset($list['name'])) {
4089 4986 echo sprintf(
4090 4987 '<option value="%s" %s>%s</option>',
@@ -4094,8 +4991,9 @@
4094 4991 );
4095 4992 }
4096 4993 }
4097 4994 echo '</select>';
4995 + echo '<p class="description">' . esc_html__('Please select a mailing list to use with Loops.', 'mxchat') . '</p>';
4098 4996 } else {
4099 4997 echo '<p class="description">' . esc_html__('No lists found. Please verify your API Key.', 'mxchat') . '</p>';
4100 4998 }
4101 4999 } else {
@@ -4106,14 +5004,13 @@
4106 5004 $default_response = __('Would you like to join our mailing list? Please provide your email below.', 'mxchat');
4107 5005 $triggered_response = isset($this->options['triggered_phrase_response'])
4108 5006 ? $this->options['triggered_phrase_response']
4109 5007 : $default_response;
4110 -
4111 5008 echo sprintf(
4112 5009 '<textarea id="triggered_phrase_response" name="triggered_phrase_response" rows="3" cols="50">%s</textarea>',
4113 5010 esc_textarea($triggered_response)
4114 5011 );
4115 - 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>';
5012 + 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>';
4116 5013 }
4117 5014
4118 5015 public function mxchat_email_capture_response_callback() {
4119 5016 $default_response = __('Thank you for providing your email! You\'ve been added to our list.', 'mxchat');
@@ -4119,16 +5016,14 @@
4119 5016 $default_response = __('Thank you for providing your email! You\'ve been added to our list.', 'mxchat');
4120 5017 $email_capture_response = isset($this->options['email_capture_response'])
4121 5018 ? $this->options['email_capture_response']
4122 5019 : $default_response;
4123 -
4124 5020 echo sprintf(
4125 5021 '<textarea id="email_capture_response" name="email_capture_response" rows="3" cols="50">%s</textarea>',
4126 5022 esc_textarea($email_capture_response)
4127 5023 );
4128 - echo '<p class="description">' . esc_html__('Enter the message to send when a user provides their email.', 'mxchat') . '</p>';
5024 + 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>';
4129 5025 }
4130 -
4131 5026 public function mxchat_pre_chat_message_callback() {
4132 5027 // Load the entire 'mxchat_options' array
4133 5028 $all_options = get_option('mxchat_options', []);
4134 5029
@@ -4143,49 +5038,152 @@
4143 5038 );
4144 5039 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>';
4145 5040 }
4146 5041
4147 -
4148 -
4149 5042 // Callback for AI Instructions textarea
4150 5043 public function system_prompt_instructions_callback() {
4151 5044 // Retrieve the current value of the system prompt instructions
4152 5045 $instructions = isset($this->options['system_prompt_instructions']) ? esc_textarea($this->options['system_prompt_instructions']) : '';
4153 -
4154 5046 // Render the textarea field
4155 5047 printf(
4156 5048 '<textarea id="system_prompt_instructions" name="system_prompt_instructions" rows="5" cols="50">%s</textarea>',
4157 5049 $instructions
4158 5050 );
5051 + // Provide a helpful description with sample instructions button
5052 + 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>';
5053 + echo '<div class="mxchat-instructions-container">';
5054 + echo '<button type="button" class="mxchat-instructions-btn" id="mxchatViewSampleBtn">';
5055 + 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">';
5056 + echo '<path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/>';
5057 + echo '<path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/>';
5058 + echo '</svg>';
5059 + echo esc_html__('View Sample Instructions', 'mxchat');
5060 + echo '</button>';
5061 + echo '</div>';
4159 5062
4160 - // Provide a helpful description
4161 - 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>';
5063 + // Add modal to WordPress admin footer instead of inline
5064 + add_action('admin_footer', array($this, 'render_sample_instructions_modal'));
4162 5065 }
4163 5066
5067 +// New method to render modal in admin footer
5068 +public function render_sample_instructions_modal() {
5069 + static $modal_rendered = false;
5070 + if ($modal_rendered) return; // Prevent duplicate modals
5071 + $modal_rendered = true;
4164 5072
5073 + echo '<div class="mxchat-instructions-modal-overlay" id="mxchatSampleModal">';
5074 + echo '<div class="mxchat-instructions-modal-content">';
5075 + echo '<div class="mxchat-instructions-modal-header">';
5076 + echo '<h3 class="mxchat-instructions-modal-title">';
5077 + 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">';
5078 + echo '<path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/>';
5079 + echo '<path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/>';
5080 + echo '</svg>';
5081 + echo esc_html__('Sample AI Instructions', 'mxchat');
5082 + echo '</h3>';
5083 + echo '<button type="button" class="mxchat-instructions-modal-close" id="mxchatModalClose">';
5084 + 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">';
5085 + echo '<line x1="18" y1="6" x2="6" y2="18"/>';
5086 + echo '<line x1="6" y1="6" x2="18" y2="18"/>';
5087 + echo '</svg>';
5088 + echo '</button>';
5089 + echo '</div>';
5090 + echo '<div class="mxchat-instructions-modal-body">';
5091 + echo '<div class="mxchat-instructions-content">';
5092 + 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:
5093 +
5094 +# Response Style - CRITICALLY IMPORTANT
5095 +- MAXIMUM LENGTH: 1-3 short sentences per response
5096 +- Ultra-concise: Get straight to the answer with no filler
5097 +- No introductions like "Sure!" or "I\'d be happy to help"
5098 +- No phrases like "based on my knowledge" or "according to information"
5099 +- No explanatory text before giving the answer
5100 +- No summaries or repetition
5101 +- Hyperlink all URLs
5102 +- Respond in user\'s language
5103 +- Minor chit chat or conversation is okay, but try to keep it focused on [insert topic]
5104 +
5105 +# Knowledge Base Requirements - PREVENT HALLUCINATIONS
5106 +- ONLY answer using information explicitly provided in OFFICIAL KNOWLEDGE DATABASE CONTENT sections marked with ===== delimiters
5107 +- If required information is NOT in the knowledge database: "I don\'t have enough information in my knowledge base to answer that question accurately."
5108 +- NEVER invent or hallucinate URLs, links, product specs, procedures, dates, statistics, names, contacts, or company information
5109 +- When knowledge base information is unclear or contradictory, acknowledge the limitation rather than guessing
5110 +- Better to admit insufficient information than provide inaccurate answers');
5111 + echo '</div>';
5112 + echo '<button type="button" class="mxchat-instructions-copy-btn" id="mxchatCopyBtn">';
5113 + 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">';
5114 + echo '<rect x="9" y="9" width="13" height="13" rx="2" ry="2"/>';
5115 + echo '<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/>';
5116 + echo '</svg>';
5117 + echo esc_html__('Copy Instructions', 'mxchat');
5118 + echo '</button>';
5119 + echo '</div>';
5120 + echo '<div class="mxchat-instructions-modal-footer">';
5121 + echo '<button type="button" class="mxchat-instructions-btn-secondary" id="mxchatCloseBtn">' . esc_html__('Close', 'mxchat') . '</button>';
5122 + echo '</div>';
5123 + echo '</div>';
5124 + echo '</div>';
5125 +}
5126 +
5127 +
4165 5128 public function mxchat_model_callback() {
4166 5129 // Define available models grouped by provider
4167 5130 $models = array(
5131 + esc_html__('OpenRouter (100+ Models)', 'mxchat') => array(
5132 + 'openrouter' => esc_html__('OpenRouter - Select after entering API key', 'mxchat'),
5133 + ),
5134 + esc_html__('Google Gemini Models', 'mxchat') => array(
5135 + 'gemini-2.0-flash' => esc_html__('Gemini 2.0 Flash (Next-Gen Features)', 'mxchat'),
5136 + 'gemini-2.0-flash-lite' => esc_html__('Gemini 2.0 Flash-Lite (Cost-Efficient)', 'mxchat'),
5137 + 'gemini-1.5-pro' => esc_html__('Gemini 1.5 Pro (Complex Reasoning)', 'mxchat'),
5138 + 'gemini-1.5-flash' => esc_html__('Gemini 1.5 Flash (Fast & Versatile)', 'mxchat'),
5139 + ),
5140 + esc_html__('Google Gemini Models', 'mxchat') => array(
5141 + 'gemini-2.0-flash' => esc_html__('Gemini 2.0 Flash (Next-Gen Features)', 'mxchat'),
5142 + 'gemini-2.0-flash-lite' => esc_html__('Gemini 2.0 Flash-Lite (Cost-Efficient)', 'mxchat'),
5143 + 'gemini-1.5-pro' => esc_html__('Gemini 1.5 Pro (Complex Reasoning)', 'mxchat'),
5144 + 'gemini-1.5-flash' => esc_html__('Gemini 1.5 Flash (Fast & Versatile)', 'mxchat'),
5145 + ),
4168 5146 esc_html__('X.AI Models', 'mxchat') => array(
4169 - 'grok-beta' => esc_html__('grok-beta (Early Beta)', 'mxchat'),
4170 - 'grok-2' => esc_html__('Grok 2', 'mxchat')
5147 + 'grok-3-beta' => esc_html__('Grok-3 (Powerful)', 'mxchat'),
5148 + 'grok-3-fast-beta' => esc_html__('Grok-3 Fast (High Performance)', 'mxchat'),
5149 + 'grok-3-mini-beta' => esc_html__('Grok-3 Mini (Affordable)', 'mxchat'),
5150 + 'grok-3-mini-fast-beta' => esc_html__('Grok-3 Mini Fast (Quick Response)', 'mxchat'),
5151 + 'grok-2' => esc_html__('Grok 2', 'mxchat'),
5152 + 'grok-4-0709' => esc_html__('Grok 4 (Latest Flagship)', 'mxchat'),
5153 + 'grok-4-1-fast-reasoning' => esc_html__('Grok 4.1 Fast (Reasoning)', 'mxchat'),
5154 + 'grok-4-1-fast-non-reasoning' => esc_html__('Grok 4.1 Fast (Non-Reasoning)', 'mxchat'),
4171 5155 ),
4172 5156 esc_html__('DeepSeek Models', 'mxchat') => array(
4173 5157 'deepseek-chat' => esc_html__('DeepSeek-V3', 'mxchat'),
4174 5158 ),
4175 5159 esc_html__('Claude Models', 'mxchat') => array(
4176 - 'claude-3-5-sonnet-20241022' => esc_html__('Claude 3.5 Sonnet (Most Intelligent)', 'mxchat'),
4177 - 'claude-3-opus-20240229' => esc_html__('Claude 3 Opus (Highly Complex Tasks)', 'mxchat'),
5160 + 'claude-sonnet-4-5-20250929' => esc_html__('Claude Sonnet 4.5 (Best for Agents & Coding)', 'mxchat'),
5161 + 'claude-opus-4-1-20250805' => esc_html__('Claude Opus 4.1 (Exceptional for Complex Tasks)', 'mxchat'),
5162 + 'claude-haiku-4-5-20251001' => esc_html__('Claude Haiku 4.5 (Fastest & Most Intelligent)', 'mxchat'),
5163 + 'claude-opus-4-20250514' => esc_html__('Claude 4 Opus (Most Capable)', 'mxchat'),
5164 + 'claude-sonnet-4-20250514' => esc_html__('Claude 4 Sonnet (High Performance)', 'mxchat'),
5165 + 'claude-3-7-sonnet-20250219' => esc_html__('Claude 3.7 Sonnet (High Intelligence)', 'mxchat'),
5166 + 'claude-3-opus-20240229' => esc_html__('Claude 3 Opus (Complex Tasks)', 'mxchat'),
4178 5167 'claude-3-sonnet-20240229' => esc_html__('Claude 3 Sonnet (Balanced)', 'mxchat'),
4179 - 'claude-3-haiku-20240307' => esc_html__('Claude 3 Haiku (Fastest)', 'mxchat')
5168 + 'claude-3-haiku-20240307' => esc_html__('Claude 3 Haiku (Fastest)', 'mxchat'),
4180 5169 ),
4181 5170 esc_html__('OpenAI Models', 'mxchat') => array(
5171 + // GPT-5 family
5172 + 'gpt-5.2' => esc_html__('GPT-5.2 (Best General-Purpose & Agentic Model)', 'mxchat'),
5173 + 'gpt-5.1-2025-11-13' => esc_html__('GPT-5.1 (Flagship for Coding & Agentic Tasks)', 'mxchat'),
5174 + 'gpt-5' => esc_html__('GPT-5 (Flagship for Coding, Reasoning & Agents)', 'mxchat'),
5175 + 'gpt-5-mini' => esc_html__('GPT-5 Mini (Faster, Cost-Efficient for Precise Prompts)', 'mxchat'),
5176 + 'gpt-5-nano' => esc_html__('GPT-5 Nano (Fastest & Cheapest for Summarization/Classification)', 'mxchat'),
5177 +
5178 + // Existing OpenAI models
5179 + 'gpt-4.1-2025-04-14' => esc_html__('GPT-4.1 (Flagship for Complex Tasks)', 'mxchat'),
4182 5180 'gpt-4o' => esc_html__('GPT-4o (Recommended)', 'mxchat'),
4183 5181 'gpt-4o-mini' => esc_html__('GPT-4o Mini (Fast and Lightweight)', 'mxchat'),
4184 5182 'gpt-4-turbo' => esc_html__('GPT-4 Turbo (High-Performance)', 'mxchat'),
4185 5183 'gpt-4' => esc_html__('GPT-4 (High Intelligence)', 'mxchat'),
4186 - 'gpt-3.5-turbo' => esc_html__('GPT-3.5 Turbo (Affordable and Fast)', 'mxchat')
4187 - )
5184 + 'gpt-3.5-turbo' => esc_html__('GPT-3.5 Turbo (Affordable and Fast)', 'mxchat'),
5185 + ),
4188 5186 );
4189 5187
4190 5188 // Retrieve the currently selected model from saved options
4191 5189 $selected_model = isset($this->options['model']) ? esc_attr($this->options['model']) : 'gpt-4o';
@@ -4190,9 +5188,9 @@
4190 5188 // Retrieve the currently selected model from saved options
4191 5189 $selected_model = isset($this->options['model']) ? esc_attr($this->options['model']) : 'gpt-4o';
4192 5190
4193 5191 // Begin the select dropdown
4194 - echo '<select id="model" name="model">'; // No array notation in name attribute
5192 + echo '<select id="model" name="model">';
4195 5193
4196 5194 // Iterate over groups of models
4197 5195 foreach ($models as $group_label => $group_models) {
4198 5196 echo '<optgroup label="' . esc_attr($group_label) . '">';
@@ -4197,28 +5195,273 @@
4197 5195 foreach ($models as $group_label => $group_models) {
4198 5196 echo '<optgroup label="' . esc_attr($group_label) . '">';
4199 5197
4200 5198 foreach ($group_models as $model_value => $model_label) {
4201 - // Disable Pro-only models for non-activated users
4202 - $disabled = (!$this->is_activated && ($group_label === esc_html__('X.AI Models', 'mxchat') || $group_label === esc_html__('Claude Models', 'mxchat'))) ? 'disabled' : '';
4203 - $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') : '';
4204 -
4205 - // Output the option element
4206 - echo '<option value="' . esc_attr($model_value) . '" ' . selected($selected_model, $model_value, false) . ' ' . $disabled . '>' . esc_html($model_label . $label_suffix) . '</option>';
5199 + echo '<option value="' . esc_attr($model_value) . '" ' . selected($selected_model, $model_value, false) . '>' . esc_html($model_label) . '</option>';
4207 5200 }
4208 5201
4209 5202 echo '</optgroup>';
4210 5203 }
4211 5204
4212 - // Close the select dropdown
4213 5205 echo '</select>';
4214 5206
4215 - // Add a description below the dropdown
4216 - echo '<p class="description">' . esc_html__('Select the AI model to use for your chatbot. Pro-only models are marked accordingly.', 'mxchat') . '</p>';
5207 + echo '<p class="description">' . esc_html__('Select the AI model your chatbot will use for chatting.', 'mxchat') . '</p>';
5208 +
5209 + // Add a note for OpenRouter
5210 + echo '<p class="description" id="openrouter-model-note" style="display:none; color: #d63638; font-weight: 500;">';
5211 + echo '<span class="dashicons dashicons-info" style="font-size: 16px; vertical-align: middle;"></span> ';
5212 + echo esc_html__('After entering your OpenRouter API key above, click the button below to load available models.', 'mxchat');
5213 + echo '</p>';
5214 +
5215 + // API Key Status Messages (hidden by default, shown by JS based on selected model)
5216 + $has_openai_key = !empty($this->options['api_key']);
5217 + $has_claude_key = !empty($this->options['claude_api_key']);
5218 + $has_xai_key = !empty($this->options['xai_api_key']);
5219 + $has_deepseek_key = !empty($this->options['deepseek_api_key']);
5220 + $has_gemini_key = !empty($this->options['gemini_api_key']);
5221 + $has_openrouter_key = !empty($this->options['openrouter_api_key']);
5222 +
5223 + // OpenAI/GPT models
5224 + echo '<p class="mxchat-api-status" data-provider="openai" style="display:none;">';
5225 + if ($has_openai_key) {
5226 + echo '<span style="color: #00a32a;">✓ ' . esc_html__('API key for OpenAI detected', 'mxchat') . '</span>';
5227 + } else {
5228 + echo '<span style="color: #d63638;">⚠ ' . esc_html__('No API key for OpenAI detected. Please enter API key in API Keys tab.', 'mxchat') . '</span>';
5229 + }
5230 + echo '</p>';
5231 +
5232 + // Claude models
5233 + echo '<p class="mxchat-api-status" data-provider="claude" style="display:none;">';
5234 + if ($has_claude_key) {
5235 + echo '<span style="color: #00a32a;">✓ ' . esc_html__('API key for Anthropic (Claude) detected', 'mxchat') . '</span>';
5236 + } else {
5237 + echo '<span style="color: #d63638;">⚠ ' . esc_html__('No API key for Anthropic (Claude) detected. Please enter API key in API Keys tab.', 'mxchat') . '</span>';
5238 + }
5239 + echo '</p>';
5240 +
5241 + // X.AI models
5242 + echo '<p class="mxchat-api-status" data-provider="xai" style="display:none;">';
5243 + if ($has_xai_key) {
5244 + echo '<span style="color: #00a32a;">✓ ' . esc_html__('API key for X.AI (Grok) detected', 'mxchat') . '</span>';
5245 + } else {
5246 + 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>';
5247 + }
5248 + echo '</p>';
5249 +
5250 + // DeepSeek models
5251 + echo '<p class="mxchat-api-status" data-provider="deepseek" style="display:none;">';
5252 + if ($has_deepseek_key) {
5253 + echo '<span style="color: #00a32a;">✓ ' . esc_html__('API key for DeepSeek detected', 'mxchat') . '</span>';
5254 + } else {
5255 + echo '<span style="color: #d63638;">⚠ ' . esc_html__('No API key for DeepSeek detected. Please enter API key in API Keys tab.', 'mxchat') . '</span>';
5256 + }
5257 + echo '</p>';
5258 +
5259 + // Gemini models
5260 + echo '<p class="mxchat-api-status" data-provider="gemini" style="display:none;">';
5261 + if ($has_gemini_key) {
5262 + echo '<span style="color: #00a32a;">✓ ' . esc_html__('API key for Google Gemini detected', 'mxchat') . '</span>';
5263 + } else {
5264 + echo '<span style="color: #d63638;">⚠ ' . esc_html__('No API key for Google Gemini detected. Please enter API key in API Keys tab.', 'mxchat') . '</span>';
5265 + }
5266 + echo '</p>';
5267 +
5268 + // OpenRouter models
5269 + echo '<p class="mxchat-api-status" data-provider="openrouter" style="display:none;">';
5270 + if ($has_openrouter_key) {
5271 + echo '<span style="color: #00a32a;">✓ ' . esc_html__('API key for OpenRouter detected', 'mxchat') . '</span>';
5272 + } else {
5273 + echo '<span style="color: #d63638;">⚠ ' . esc_html__('No API key for OpenRouter detected. Please enter API key in API Keys tab.', 'mxchat') . '</span>';
5274 + }
5275 + echo '</p>';
5276 +
5277 + // ADD THESE HIDDEN FIELDS RIGHT HERE:
5278 + $openrouter_model = isset($this->options['openrouter_selected_model']) ? esc_attr($this->options['openrouter_selected_model']) : '';
5279 + $openrouter_model_name = isset($this->options['openrouter_selected_model_name']) ? esc_attr($this->options['openrouter_selected_model_name']) : '';
5280 +
5281 + echo '<input type="hidden" id="openrouter_selected_model" name="openrouter_selected_model" value="' . $openrouter_model . '" />';
5282 + echo '<input type="hidden" id="openrouter_selected_model_name" name="openrouter_selected_model_name" value="' . $openrouter_model_name . '" />';
4217 5283 }
4218 5284
5285 +// Update your existing callback method
5286 +public function enable_streaming_toggle_callback() {
5287 + // Get value from options array, default to 'on'
5288 + $enabled = isset($this->options['enable_streaming_toggle']) ? $this->options['enable_streaming_toggle'] : 'on';
5289 + $checked = ($enabled === 'on') ? 'checked' : '';
4219 5290
5291 + echo '<label class="toggle-switch">';
5292 + echo sprintf(
5293 + '<input type="checkbox" id="enable_streaming_toggle" name="enable_streaming_toggle" value="on" %s />',
5294 + esc_attr($checked)
5295 + );
5296 + echo '<span class="slider"></span>';
5297 + echo '</label>';
5298 + echo '<p class="description">' .
5299 + esc_html__('Enable real-time streaming responses for supported models (OpenAI, Claude, DeepSeek, and Grok). When disabled, responses will load all at once.', 'mxchat') .
5300 + '</p>';
4220 5301
5302 + // Test button with better styling
5303 + echo '<div style="margin-top: 15px;">';
5304 + echo '<button type="button" id="mxchat-test-streaming-btn" class="button button-secondary">';
5305 + echo '<span class="dashicons dashicons-admin-tools" style="vertical-align: middle; margin-right: 5px;"></span>';
5306 + echo esc_html__('Test Streaming Compatibility', 'mxchat');
5307 + echo '</button>';
5308 + echo '<p id="mxchat-test-streaming-result" style="margin-top:10px; font-weight: bold;"></p>';
5309 + echo '</div>';
5310 +}
5311 +
5312 +// Web Search toggle callback
5313 +public function enable_web_search_toggle_callback() {
5314 + // Get value from options array, default to 'off'
5315 + $enabled = isset($this->options['enable_web_search']) ? $this->options['enable_web_search'] : 'off';
5316 + $checked = ($enabled === 'on') ? 'checked' : '';
5317 +
5318 + // Get current model to determine if we should show/enable the toggle
5319 + $current_model = isset($this->options['model']) ? $this->options['model'] : 'gpt-4o';
5320 +
5321 + // Models that DON'T support web search (per OpenAI docs)
5322 + // gpt-5 with minimal reasoning is handled at API level, gpt-4.1-nano doesn't support it
5323 + $unsupported_models = array('gpt-4.1-nano');
5324 +
5325 + // Check if current model is an OpenAI model that supports web search
5326 + $openai_models = array(
5327 + 'gpt-5.2', 'gpt-5.1-2025-11-13', 'gpt-5', 'gpt-5-mini', 'gpt-5-nano',
5328 + 'gpt-4.1-2025-04-14', 'gpt-4o', 'gpt-4o-mini', 'gpt-4-turbo', 'gpt-4', 'gpt-3.5-turbo'
5329 + );
5330 +
5331 + $is_openai = in_array($current_model, $openai_models);
5332 + $is_supported = $is_openai && !in_array($current_model, $unsupported_models);
5333 +
5334 + // Wrapper div with data attributes for JS to show/hide
5335 + echo '<div id="web-search-toggle-wrapper" data-openai-models="' . esc_attr(implode(',', $openai_models)) . '" data-unsupported-models="' . esc_attr(implode(',', $unsupported_models)) . '"' . (!$is_supported ? ' style="display:none;"' : '') . '>';
5336 +
5337 + echo '<label class="toggle-switch">';
5338 + echo sprintf(
5339 + '<input type="checkbox" id="enable_web_search" name="enable_web_search" value="on" %s />',
5340 + esc_attr($checked)
5341 + );
5342 + echo '<span class="slider"></span>';
5343 + echo '</label>';
5344 + echo '<p class="description">' .
5345 + esc_html__('Allow the chatbot to search the web for up-to-date information when answering questions. Uses OpenAI\'s web search tool via the Responses API.', 'mxchat') .
5346 + '</p>';
5347 +
5348 + echo '</div>';
5349 +
5350 + // Message shown when non-OpenAI model is selected
5351 + echo '<p id="web-search-unavailable-message" class="description" style="color: #666;' . ($is_supported ? ' display:none;' : '') . '">';
5352 + echo '<span class="dashicons dashicons-info" style="font-size: 16px; vertical-align: middle; margin-right: 4px;"></span>';
5353 + echo esc_html__('Web search is only available for OpenAI models.', 'mxchat');
5354 + echo '</p>';
5355 +}
5356 +
5357 +// AJAX handler to fetch OpenRouter models
5358 +public function fetch_openrouter_models() {
5359 + check_ajax_referer('mxchat_fetch_openrouter_models', 'nonce');
5360 +
5361 + $api_key = isset($_POST['api_key']) ? sanitize_text_field($_POST['api_key']) : '';
5362 +
5363 + if (empty($api_key)) {
5364 + wp_send_json_error(array('message' => 'API key is required'));
5365 + }
5366 +
5367 + $response = wp_remote_get('https://openrouter.ai/api/v1/models', array(
5368 + 'headers' => array(
5369 + 'Authorization' => 'Bearer ' . $api_key,
5370 + 'Content-Type' => 'application/json',
5371 + ),
5372 + 'timeout' => 15,
5373 + ));
5374 +
5375 + if (is_wp_error($response)) {
5376 + wp_send_json_error(array('message' => $response->get_error_message()));
5377 + }
5378 +
5379 + $body = wp_remote_retrieve_body($response);
5380 + $data = json_decode($body, true);
5381 +
5382 + if (isset($data['data']) && is_array($data['data'])) {
5383 + // Format the models for the frontend
5384 + $models = array_map(function($model) {
5385 + return array(
5386 + 'id' => $model['id'],
5387 + 'name' => $model['name'] ?? $model['id'],
5388 + 'description' => $model['description'] ?? '',
5389 + 'context_length' => $model['context_length'] ?? 0,
5390 + 'pricing' => array(
5391 + 'prompt' => $model['pricing']['prompt'] ?? 0,
5392 + 'completion' => $model['pricing']['completion'] ?? 0,
5393 + ),
5394 + );
5395 + }, $data['data']);
5396 +
5397 + wp_send_json_success(array('models' => $models));
5398 + } else {
5399 + wp_send_json_error(array('message' => 'Invalid response from OpenRouter'));
5400 + }
5401 +}
5402 +
5403 +// Callback function for embedding model selection
5404 +public function embedding_model_callback() {
5405 + $models = array(
5406 + esc_html__('OpenAI Embeddings', 'mxchat') => array(
5407 + 'text-embedding-3-small' => esc_html__('TE3 Small (1536, Efficient)', 'mxchat'),
5408 + 'text-embedding-ada-002' => esc_html__('Ada 2 (1536, Recommended)', 'mxchat'),
5409 + 'text-embedding-3-large' => esc_html__('TE3 Large (3072, Powerful)', 'mxchat'),
5410 + ),
5411 + esc_html__('Voyage AI Embeddings', 'mxchat') => array(
5412 + 'voyage-3-large' => esc_html__('Voyage-3 Large (2048, Most Capable)', 'mxchat'),
5413 + ),
5414 + esc_html__('Google Gemini Embeddings', 'mxchat') => array(
5415 + 'gemini-embedding-exp-03-07' => esc_html__('Gemini Embedding (1536, Experimental)', 'mxchat'),
5416 + )
5417 + );
5418 + $selected_model = isset($this->options['embedding_model']) ? esc_attr($this->options['embedding_model']) : 'text-embedding-ada-002';
5419 + echo '<select id="embedding_model" name="embedding_model">';
5420 + foreach ($models as $group_label => $group_models) {
5421 + echo '<optgroup label="' . esc_attr($group_label) . '">';
5422 + foreach ($group_models as $model_value => $model_label) {
5423 + echo '<option value="' . esc_attr($model_value) . '" ' . selected($selected_model, $model_value, false) . '>' . esc_html($model_label) . '</option>';
5424 + }
5425 + echo '</optgroup>';
5426 + }
5427 + echo '</select>';
5428 + 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>';
5429 +
5430 + // API Key Status Messages for Embedding Models
5431 + $has_openai_key = !empty($this->options['api_key']);
5432 + $has_voyage_key = !empty($this->options['voyage_api_key']);
5433 + $has_gemini_key = !empty($this->options['gemini_api_key']);
5434 +
5435 + // OpenAI Embeddings
5436 + echo '<p class="mxchat-embedding-api-status" data-provider="openai" style="display:none;">';
5437 + if ($has_openai_key) {
5438 + echo '<span style="color: #00a32a;">✓ ' . esc_html__('API key for OpenAI detected', 'mxchat') . '</span>';
5439 + } else {
5440 + echo '<span style="color: #d63638;">⚠ ' . esc_html__('No API key for OpenAI detected. Please enter API key in API Keys tab.', 'mxchat') . '</span>';
5441 + }
5442 + echo '</p>';
5443 +
5444 + // Voyage AI Embeddings
5445 + echo '<p class="mxchat-embedding-api-status" data-provider="voyage" style="display:none;">';
5446 + if ($has_voyage_key) {
5447 + echo '<span style="color: #00a32a;">✓ ' . esc_html__('API key for Voyage AI detected', 'mxchat') . '</span>';
5448 + } else {
5449 + echo '<span style="color: #d63638;">⚠ ' . esc_html__('No API key for Voyage AI detected. Please enter API key in API Keys tab.', 'mxchat') . '</span>';
5450 + }
5451 + echo '</p>';
5452 +
5453 + // Gemini Embeddings
5454 + echo '<p class="mxchat-embedding-api-status" data-provider="gemini" style="display:none;">';
5455 + if ($has_gemini_key) {
5456 + echo '<span style="color: #00a32a;">✓ ' . esc_html__('API key for Google Gemini detected', 'mxchat') . '</span>';
5457 + } else {
5458 + echo '<span style="color: #d63638;">⚠ ' . esc_html__('No API key for Google Gemini detected. Please enter API key in API Keys tab.', 'mxchat') . '</span>';
5459 + }
5460 + echo '</p>';
5461 +}
5462 +
5463 +
4221 5464 public function mxchat_top_bar_title_callback() {
4222 5465 // Retrieve the current value of the top bar title from saved options
4223 5466 $top_bar_title = isset($this->options['top_bar_title']) ? esc_attr($this->options['top_bar_title']) : '';
4224 5467
@@ -4227,9 +5470,18 @@
4227 5470
4228 5471 // Add a description
4229 5472 echo '<p class="description">' . esc_html__('Enter the title text that will appear on the top bar of the chatbot.', 'mxchat') . '</p>';
4230 5473 }
5474 +public function mxchat_ai_agent_text_callback() {
5475 + // Retrieve the current value of the AI agent text from saved options
5476 + $ai_agent_text = isset($this->options['ai_agent_text']) ? esc_attr($this->options['ai_agent_text']) : '';
5477 + // Render the input field
5478 + echo '<input type="text" id="ai_agent_text" name="ai_agent_text" value="' . $ai_agent_text . '" />';
5479 + // Add a description
5480 + echo '<p class="description">' . esc_html__('Enter the text that will appear for AI agents in the status indicator. Default: "AI Agent"', 'mxchat') . '</p>';
5481 +}
4231 5482
5483 +
4232 5484 public function enable_email_block_callback() {
4233 5485 // Load full plugin options array
4234 5486 $all_options = get_option('mxchat_options', []);
4235 5487
@@ -4248,10 +5500,8 @@
4248 5500 echo '</label>';
4249 5501 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>';
4250 5502 }
4251 5503
4252 -
4253 -
4254 5504 public function email_blocker_header_content_callback() {
4255 5505 // Load the entire 'mxchat_options' array
4256 5506 $all_options = get_option('mxchat_options', []);
4257 5507
@@ -4259,14 +5509,15 @@
4259 5509 $content = isset($all_options['email_blocker_header_content'])
4260 5510 ? $all_options['email_blocker_header_content']
4261 5511 : '';
4262 5512
4263 - // Render the textarea
5513 + // Render the textarea - IMPORTANT: name should be just "email_blocker_header_content"
4264 5514 echo '<textarea
4265 5515 id="email_blocker_header_content"
4266 5516 name="email_blocker_header_content"
4267 5517 rows="5"
4268 5518 cols="70"
5519 + data-setting="email_blocker_header_content"
4269 5520 >' . esc_textarea($content) . '</textarea>';
4270 5521
4271 5522 echo '<p class="description">';
4272 5523 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');
@@ -4272,9 +5523,8 @@
4272 5523 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');
4273 5524 echo '</p>';
4274 5525 }
4275 5526
4276 -
4277 5527 public function email_blocker_button_text_callback() {
4278 5528 // Load the entire 'mxchat_options' array
4279 5529 $all_options = get_option('mxchat_options', []);
4280 5530
@@ -4290,28 +5540,55 @@
4290 5540 echo esc_html__('Enter the text you want on the submit button, e.g. "Start Chat".', 'mxchat');
4291 5541 echo '</p>';
4292 5542 }
4293 5543
5544 +//Enable name field callback
5545 +public function enable_name_field_callback() {
5546 + // Load full plugin options array
5547 + $all_options = get_option('mxchat_options', []);
5548 + // Get the value, default to 'off'
5549 + $enable_name_field = isset($all_options['enable_name_field']) ? $all_options['enable_name_field'] : 'off';
5550 + // Check if it's 'on'
5551 + $checked = ($enable_name_field === 'on') ? 'checked' : '';
5552 + echo '<label class="toggle-switch">';
5553 + echo sprintf(
5554 + '<input type="checkbox" id="enable_name_field" name="enable_name_field" value="on" %s />',
5555 + esc_attr($checked)
5556 + );
5557 + echo '<span class="slider"></span>';
5558 + echo '</label>';
5559 + echo '<p class="description">' . esc_html__('Enable this to also require users to enter their name along with their email before chatting.', 'mxchat') . '</p>';
5560 +}
5561 +//Name field placeholder callback
5562 +public function name_field_placeholder_callback() {
5563 + $all_options = get_option('mxchat_options', []);
5564 + $placeholder = isset($all_options['name_field_placeholder'])
5565 + ? $all_options['name_field_placeholder']
5566 + : esc_html__('Enter your name', 'mxchat');
4294 5567
5568 + echo '<input type="text" id="name_field_placeholder" name="name_field_placeholder" value="' . esc_attr($placeholder) . '" style="width: 300px;" />';
5569 + echo '<p class="description">';
5570 + echo esc_html__('Placeholder text for the name field.', 'mxchat');
5571 + echo '</p>';
5572 +}
4295 5573
5574 +
5575 +
4296 5576 public function mxchat_intro_message_callback() {
4297 5577 // Load the entire 'mxchat_options' array
4298 5578 $all_options = get_option('mxchat_options', []);
4299 -
4300 5579 // Retrieve the saved intro message or use the default
4301 5580 $default_message = __('Hello! How can I assist you today?', 'mxchat');
4302 5581 $saved_message = isset($all_options['intro_message']) ? $all_options['intro_message'] : $default_message;
4303 -
4304 - // Output the textarea with the saved value
5582 + // Output the textarea with the saved value without escaping HTML
4305 5583 ?>
4306 - <textarea id="intro_message" name="intro_message" rows="5" cols="50"><?php echo esc_textarea($saved_message); ?></textarea>
5584 + <textarea id="intro_message" name="intro_message" rows="5" cols="50"><?php echo $saved_message; ?></textarea>
4307 5585 <p class="description">
4308 - <?php esc_html_e('Enter your message. Line breaks will be preserved.', 'mxchat'); ?>
5586 + <?php esc_html_e('Enter your message. HTML tags and line breaks will be preserved.', 'mxchat'); ?>
4309 5587 </p>
4310 5588 <?php
4311 5589 }
4312 5590
4313 -
4314 5591 public function mxchat_input_copy_callback() {
4315 5592 // Load the entire 'mxchat_options' array
4316 5593 $all_options = get_option('mxchat_options', []);
4317 5594
@@ -4760,12 +6037,25 @@
4760 6037 echo '</div>';
4761 6038 }
4762 6039
4763 6040 public function mxchat_append_to_body_callback() {
6041 + // Fetch fresh options to ensure we have the latest saved values
6042 + $options = get_option('mxchat_options', array());
6043 +
4764 6044 // Get value from options array, default to 'off'
4765 - $append_to_body = isset($this->options['append_to_body']) ? $this->options['append_to_body'] : 'off';
6045 + $append_to_body = isset($options['append_to_body']) ? $options['append_to_body'] : 'off';
4766 6046 $checked = ($append_to_body === 'on') ? 'checked' : '';
4767 6047
6048 + // Get post type visibility settings
6049 + $visibility_mode = isset($options['post_type_visibility_mode']) ? $options['post_type_visibility_mode'] : 'all';
6050 + $visibility_list = isset($options['post_type_visibility_list']) ? $options['post_type_visibility_list'] : array();
6051 + if (!is_array($visibility_list)) {
6052 + $visibility_list = array();
6053 + }
6054 +
6055 + echo '<div class="mxchat-autosave-section">';
6056 +
6057 + // Main toggle
4768 6058 echo '<label class="toggle-switch">';
4769 6059 echo sprintf(
4770 6060 '<input type="checkbox" id="append_to_body" name="append_to_body" value="on" %s />',
4771 6061 esc_attr($checked)
@@ -4775,12 +6065,105 @@
4775 6065 echo '<p class="description">' .
4776 6066 esc_html__('Show chatbot automatically on all pages. When disabled, you can place the chatbot manually using shortcode [mxchat_chatbot floating="yes"].', 'mxchat') .
4777 6067 '</p>';
4778 6068
6069 + // Post Type Visibility Options (only visible when auto-display is ON)
6070 + $display_style = ($append_to_body === 'on') ? '' : 'display: none;';
6071 + echo '<div id="post-type-visibility-options" class="mxchat-sub-options" style="' . esc_attr($display_style) . '">';
6072 +
6073 + echo '<div class="mxchat-post-type-visibility-header">';
6074 + echo '<h4>' . esc_html__('Post Type Visibility', 'mxchat') . '</h4>';
6075 + echo '<p class="description">' . esc_html__('Control which post types display the chatbot.', 'mxchat') . '</p>';
6076 + echo '</div>';
6077 +
6078 + // Mode selector (radio buttons)
6079 + echo '<div class="mxchat-visibility-mode">';
6080 +
6081 + echo '<label class="mxchat-radio-label">';
6082 + echo '<input type="radio" name="post_type_visibility_mode" value="all" ' . checked($visibility_mode, 'all', false) . ' />';
6083 + echo '<span>' . esc_html__('Show on all post types', 'mxchat') . '</span>';
6084 + echo '</label>';
6085 +
6086 + echo '<label class="mxchat-radio-label">';
6087 + echo '<input type="radio" name="post_type_visibility_mode" value="include" ' . checked($visibility_mode, 'include', false) . ' />';
6088 + echo '<span>' . esc_html__('Only show on selected post types', 'mxchat') . '</span>';
6089 + echo '</label>';
6090 +
6091 + echo '<label class="mxchat-radio-label">';
6092 + echo '<input type="radio" name="post_type_visibility_mode" value="exclude" ' . checked($visibility_mode, 'exclude', false) . ' />';
6093 + echo '<span>' . esc_html__('Hide on selected post types', 'mxchat') . '</span>';
6094 + echo '</label>';
6095 +
6096 + echo '</div>';
6097 +
6098 + // Post type checkboxes (only visible when mode is include or exclude)
6099 + $list_display = ($visibility_mode !== 'all') ? '' : 'display: none;';
6100 + echo '<div id="post-type-list" class="mxchat-post-type-list" style="' . esc_attr($list_display) . '">';
6101 +
6102 + // Get all public post types
6103 + $post_types = get_post_types(array('public' => true), 'objects');
6104 +
6105 + foreach ($post_types as $post_type) {
6106 + // Skip attachments
6107 + if ($post_type->name === 'attachment') {
6108 + continue;
6109 + }
6110 +
6111 + $is_checked = in_array($post_type->name, $visibility_list) ? 'checked' : '';
6112 +
6113 + echo '<label class="mxchat-checkbox-label">';
6114 + echo '<input type="checkbox" name="post_type_visibility_list[]" value="' . esc_attr($post_type->name) . '" ' . $is_checked . ' />';
6115 + echo '<span>' . esc_html($post_type->label) . '</span>';
6116 + echo '</label>';
6117 + }
6118 +
6119 + echo '</div>'; // End post-type-list
6120 + echo '</div>'; // End post-type-visibility-options
6121 + echo '</div>'; // End mxchat-autosave-section
4779 6122 }
4780 6123
6124 +public function mxchat_frontend_debugger_callback() {
6125 + $show_debugger = isset($this->options['show_frontend_debugger']) ? $this->options['show_frontend_debugger'] : 'on';
6126 + $checked = ($show_debugger === 'on') ? 'checked' : '';
6127 + echo '<label class="toggle-switch">';
6128 + echo sprintf(
6129 + '<input type="checkbox" id="show_frontend_debugger" name="show_frontend_debugger" value="on" %s />',
6130 + esc_attr($checked)
6131 + );
6132 + echo '<span class="slider"></span>';
6133 + echo '</label>';
6134 +}
4781 6135
6136 +public function mxchat_contextual_awareness_callback() {
6137 + // Get value from options array, default to 'off'
6138 + $contextual_awareness = isset($this->options['contextual_awareness_toggle']) ? $this->options['contextual_awareness_toggle'] : 'off';
6139 + $checked = ($contextual_awareness === 'on') ? 'checked' : '';
6140 + echo '<label class="toggle-switch">';
6141 + echo sprintf(
6142 + '<input type="checkbox" id="contextual_awareness_toggle" name="contextual_awareness_toggle" value="on" %s />',
6143 + esc_attr($checked)
6144 + );
6145 + echo '<span class="slider"></span>';
6146 + echo '</label>';
6147 +}
4782 6148
6149 +public function mxchat_citation_links_toggle_callback() {
6150 + // Get value from options array, default to 'on' (enabled by default)
6151 + $citation_links = isset($this->options['citation_links_toggle']) ? $this->options['citation_links_toggle'] : 'on';
6152 + $checked = ($citation_links === 'on') ? 'checked' : '';
6153 + echo '<label class="toggle-switch">';
6154 + echo sprintf(
6155 + '<input type="checkbox" id="citation_links_toggle" name="citation_links_toggle" value="on" %s />',
6156 + esc_attr($checked)
6157 + );
6158 + echo '<span class="slider"></span>';
6159 + echo '</label>';
6160 + // Warning message - displayed via callback since wrapper uses esc_html()
6161 + echo '<p class="description" style="margin-top: 8px; color: #d63638; font-weight: 500;">' .
6162 + esc_html__('⚠️ Important: If disabled, you must also remove any instructions about including links or citations from your AI Behavior/System Prompt settings. Otherwise, the AI may fabricate URLs.', 'mxchat') .
6163 + '</p>';
6164 +}
6165 +
4783 6166 public function mxchat_privacy_toggle_callback() {
4784 6167 // Load from mxchat_options array
4785 6168 $options = get_option('mxchat_options', []);
4786 6169
@@ -4975,9 +6358,8 @@
4975 6358 esc_attr(__('Add question', 'mxchat')),
4976 6359 esc_html__('Add Question', 'mxchat')
4977 6360 );
4978 6361 echo '<p class="description">' . esc_html__('Add as many Quick Questions as you need.', 'mxchat') . '</p>';
4979 - echo '</div>';
4980 6362 }
4981 6363
4982 6364 public function mxchat_brave_api_key_callback() {
4983 6365 $brave_api_key = isset($this->options['brave_api_key']) ? esc_attr($this->options['brave_api_key']) : '';
@@ -4988,9 +6370,9 @@
4988 6370 $brave_api_key
4989 6371 );
4990 6372 echo '<button type="button" id="toggleBraveApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
4991 6373 echo '</div>';
4992 - echo '<p class="description">' . __('Enter your Brave Search API Key here. (See FAQ for details)', 'mxchat') . '</p>';
6374 + echo '<p class="description">' . __('Required for Brave Search integration. Get your API key from Brave Search API.', 'mxchat') . '</p>';
4993 6375 }
4994 6376
4995 6377 public function mxchat_brave_image_count_callback() {
4996 6378 $brave_image_count = isset($this->options['brave_image_count'])
@@ -5075,141 +6457,118 @@
5075 6457 echo '<p>' . esc_html__('Configure the intent settings for the Chat with PDF feature.', 'mxchat') . '</p>';
5076 6458 }
5077 6459
5078 6460 public function mxchat_chat_toolbar_toggle_callback() {
5079 - // Get chat toolbar toggle value with fallback
5080 - $chat_toolbar_toggle = isset($this->options['chat_toolbar_toggle']) ? $this->options['chat_toolbar_toggle'] : 'off';
5081 - $checked = ($chat_toolbar_toggle === 'on') ? 'checked' : '';
6461 + // Get chat toolbar toggle value with fallback
6462 + $chat_toolbar_toggle = isset($this->options['chat_toolbar_toggle']) ? $this->options['chat_toolbar_toggle'] : 'off';
6463 + $checked = ($chat_toolbar_toggle === 'on') ? 'checked' : '';
5082 6464
5083 - // Check if the plugin is activated (paid feature)
5084 - $disabled = $this->is_activated ? '' : 'disabled';
5085 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6465 + // Output the toggle switch
6466 + echo '<label class="toggle-switch">';
6467 + echo sprintf(
6468 + '<input type="checkbox" id="chat_toolbar_toggle" name="chat_toolbar_toggle" value="on" %s />',
6469 + esc_attr($checked)
6470 + );
6471 + echo '<span class="slider"></span>';
6472 + echo '</label>';
5086 6473
5087 - echo '<div class="' . esc_attr($class) . '">';
6474 + 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>';
6475 +}
5088 6476
5089 - // Output the toggle switch
5090 - echo '<label class="toggle-switch">';
5091 - echo sprintf(
5092 - '<input type="checkbox" id="chat_toolbar_toggle" name="chat_toolbar_toggle" value="on" %s %s />',
5093 - esc_attr($checked),
5094 - esc_attr($disabled)
5095 - );
5096 - echo '<span class="slider"></span>';
5097 - echo '</label>';
6477 +/**
6478 + * Callback for PDF upload button toggle setting
6479 + */
6480 +public function mxchat_show_pdf_upload_button_callback() {
6481 + // Get toggle value with fallback
6482 + $show_pdf_button = isset($this->options['show_pdf_upload_button']) ? $this->options['show_pdf_upload_button'] : 'on';
6483 + $checked = ($show_pdf_button === 'on') ? 'checked' : '';
5098 6484
5099 - // Pro feature overlay
5100 - if (!$this->is_activated) {
5101 - echo '<div class="pro-feature-overlay">';
5102 - echo '<a href="https://mxchat.ai/" target="_blank">';
5103 - echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
5104 - echo '</a>';
5105 - echo '</div>';
5106 - }
6485 + // Output the toggle switch
6486 + echo '<label class="toggle-switch">';
6487 + echo sprintf(
6488 + '<input type="checkbox" id="show_pdf_upload_button" name="show_pdf_upload_button" value="on" %s />',
6489 + esc_attr($checked)
6490 + );
6491 + echo '<span class="slider"></span>';
6492 + echo '</label>';
5107 6493
5108 - echo '</div>';
5109 - 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>';
6494 + echo '<p class="description">' . esc_html__('Enable to show the PDF upload button in the chatbot toolbar. Disable to hide it.', 'mxchat') . '</p>';
5110 6495 }
5111 6496
6497 +/**
6498 + * Callback for Word upload button toggle setting
6499 + */
6500 +public function mxchat_show_word_upload_button_callback() {
6501 + // Get toggle value with fallback
6502 + $show_word_button = isset($this->options['show_word_upload_button']) ? $this->options['show_word_upload_button'] : 'on';
6503 + $checked = ($show_word_button === 'on') ? 'checked' : '';
5112 6504
6505 + // Output the toggle switch
6506 + echo '<label class="toggle-switch">';
6507 + echo sprintf(
6508 + '<input type="checkbox" id="show_word_upload_button" name="show_word_upload_button" value="on" %s />',
6509 + esc_attr($checked)
6510 + );
6511 + echo '<span class="slider"></span>';
6512 + echo '</label>';
6513 +
6514 + echo '<p class="description">' . esc_html__('Enable to show the Word document upload button in the chatbot toolbar. Disable to hide it.', 'mxchat') . '</p>';
6515 +}
6516 +
5113 6517 public function mxchat_pdf_intent_trigger_text_callback() {
5114 - $disabled = $this->is_activated ? '' : 'disabled';
5115 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
5116 6518 $default_text = __("Please provide the URL to the PDF you'd like to discuss.", 'mxchat');
5117 6519
5118 - echo '<div class="' . esc_attr($class) . '">';
5119 6520 echo sprintf(
5120 6521 '<textarea id="pdf_intent_trigger_text"
5121 6522 name="pdf_intent_trigger_text"
5122 6523 rows="3"
5123 6524 cols="50"
5124 - placeholder="%s"
5125 - %s>%s</textarea>',
6525 + placeholder="%s">%s</textarea>',
5126 6526 esc_attr__('Enter trigger text', 'mxchat'),
5127 - esc_attr($disabled),
5128 6527 isset($this->options['pdf_intent_trigger_text'])
5129 6528 ? esc_textarea($this->options['pdf_intent_trigger_text'])
5130 6529 : esc_textarea($default_text)
5131 6530 );
5132 6531 echo '<p class="description">' . esc_html__('Text displayed when the intent is triggered.', 'mxchat') . '</p>';
5133 -
5134 - if (!$this->is_activated) {
5135 - echo '<div class="pro-feature-overlay">';
5136 - echo '<a href="https://mxchat.ai/" target="_blank">';
5137 - echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
5138 - echo '</a>';
5139 - echo '</div>';
5140 - }
5141 - echo '</div>';
5142 6532 }
5143 6533
5144 6534 public function mxchat_pdf_intent_success_text_callback() {
5145 - $disabled = $this->is_activated ? '' : 'disabled';
5146 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
5147 6535 $default_text = __("I've processed the PDF. What questions do you have about it?", 'mxchat');
5148 6536
5149 - echo '<div class="' . esc_attr($class) . '">';
5150 6537 echo sprintf(
5151 6538 '<textarea id="pdf_intent_success_text"
5152 6539 name="pdf_intent_success_text"
5153 6540 rows="3"
5154 6541 cols="50"
5155 - placeholder="%s"
5156 - %s>%s</textarea>',
6542 + placeholder="%s">%s</textarea>',
5157 6543 esc_attr__('Enter success text', 'mxchat'),
5158 - esc_attr($disabled),
5159 6544 isset($this->options['pdf_intent_success_text'])
5160 6545 ? esc_textarea($this->options['pdf_intent_success_text'])
5161 6546 : esc_textarea($default_text)
5162 6547 );
5163 6548 echo '<p class="description">' . esc_html__('Text displayed when the intent is successful.', 'mxchat') . '</p>';
5164 -
5165 - if (!$this->is_activated) {
5166 - echo '<div class="pro-feature-overlay">';
5167 - echo '<a href="https://mxchat.ai/" target="_blank">';
5168 - echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
5169 - echo '</a>';
5170 - echo '</div>';
5171 - }
5172 - echo '</div>';
5173 6549 }
5174 6550
5175 6551 public function mxchat_pdf_intent_error_text_callback() {
5176 - $disabled = $this->is_activated ? '' : 'disabled';
5177 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
5178 6552 $default_text = __("Sorry, I couldn't process the PDF. Please ensure it's a valid file.", 'mxchat');
5179 6553
5180 - echo '<div class="' . esc_attr($class) . '">';
5181 6554 echo sprintf(
5182 6555 '<textarea id="pdf_intent_error_text"
5183 6556 name="pdf_intent_error_text"
5184 6557 rows="3"
5185 6558 cols="50"
5186 - placeholder="%s"
5187 - %s>%s</textarea>',
6559 + placeholder="%s">%s</textarea>',
5188 6560 esc_attr__('Enter error text', 'mxchat'),
5189 - esc_attr($disabled),
5190 6561 isset($this->options['pdf_intent_error_text'])
5191 6562 ? esc_textarea($this->options['pdf_intent_error_text'])
5192 6563 : esc_textarea($default_text)
5193 6564 );
5194 6565 echo '<p class="description">' . esc_html__('Text displayed when an error occurs during the intent.', 'mxchat') . '</p>';
5195 -
5196 - if (!$this->is_activated) {
5197 - echo '<div class="pro-feature-overlay">';
5198 - echo '<a href="https://mxchat.ai/" target="_blank">';
5199 - echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
5200 - echo '</a>';
5201 - echo '</div>';
5202 - }
5203 - echo '</div>';
5204 6566 }
5205 6567
5206 6568 public function mxchat_pdf_max_pages_callback() {
5207 - $disabled = $this->is_activated ? '' : 'disabled';
5208 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
5209 6569 $max_pages = isset($this->options['pdf_max_pages']) ? intval($this->options['pdf_max_pages']) : 69;
5210 6570
5211 - echo '<div class="' . esc_attr($class) . '">';
5212 6571 echo sprintf(
5213 6572 '<input type="range"
5214 6573 id="pdf_max_pages"
5215 6574 name="pdf_max_pages"
@@ -5215,37 +6574,24 @@
5215 6574 name="pdf_max_pages"
5216 6575 min="1"
5217 6576 max="69"
5218 6577 value="%d"
5219 - %s
5220 6578 class="range-slider" />',
5221 - esc_attr($max_pages),
5222 - esc_attr($disabled)
6579 + esc_attr($max_pages)
5223 6580 );
5224 6581 echo '<span id="pdf_max_pages_output">' . esc_html($max_pages) . '</span>';
5225 6582 echo '<p class="description">' . esc_html__('Set the maximum number of document pages users can upload for processing. (1-69 pages)', 'mxchat') . '</p>';
5226 -
5227 - if (!$this->is_activated) {
5228 - echo '<div class="pro-feature-overlay">';
5229 - echo '<a href="https://mxchat.ai/" target="_blank">';
5230 - echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
5231 - echo '</a>';
5232 - echo '</div>';
5233 - }
5234 - echo '</div>';
5235 6583 }
5236 6584
5237 6585 public function mxchat_live_agent_status_callback() {
5238 - $disabled = $this->is_activated ? '' : 'disabled';
5239 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
5240 - $status = isset($this->options['live_agent_status']) ? $this->options['live_agent_status'] : 'off';
6586 + // Always get fresh options instead of using cached $this->options
6587 + $fresh_options = get_option('mxchat_options');
6588 + $status = isset($fresh_options['live_agent_status']) ? $fresh_options['live_agent_status'] : 'off';
5241 6589
5242 - echo '<div class="' . esc_attr($class) . '">';
5243 6590 echo '<label class="toggle-switch">';
5244 6591 echo sprintf(
5245 - '<input type="checkbox" id="live_agent_status" name="live_agent_status" value="on" %s %s />',
5246 - checked($status, 'on', false),
5247 - esc_attr($disabled)
6592 + '<input type="checkbox" id="live_agent_status" name="live_agent_status" value="on" %s />',
6593 + checked($status, 'on', false)
5248 6594 );
5249 6595 echo '<span class="slider"></span>';
5250 6596 echo '</label>';
5251 6597 echo '<label for="live_agent_status" class="mxchat-status-label">';
@@ -5250,98 +6596,167 @@
5250 6596 echo '</label>';
5251 6597 echo '<label for="live_agent_status" class="mxchat-status-label">';
5252 6598 echo '<span class="status-text">' . ($status === 'on' ? esc_html__('Online', 'mxchat') : esc_html__('Offline', 'mxchat')) . '</span>';
5253 6599 echo '</label>';
5254 -
5255 - if (!$this->is_activated) {
5256 - echo '<div class="pro-feature-overlay">';
5257 - 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>';
5258 - echo '</div>';
5259 - }
5260 - echo '</div>';
5261 6600 }
5262 6601
5263 -
5264 -// Away Message Callback
5265 6602 public function mxchat_live_agent_away_message_callback() {
5266 - $disabled = $this->is_activated ? '' : 'disabled';
5267 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
5268 6603 $message = isset($this->options['live_agent_away_message'])
5269 6604 ? $this->options['live_agent_away_message']
5270 6605 : __('Sorry, live agents are currently unavailable. I can continue helping you as an AI assistant.', 'mxchat');
5271 6606
5272 - echo '<div class="' . esc_attr($class) . '">';
5273 6607 printf(
5274 - '<textarea id="live_agent_away_message" name="live_agent_away_message" rows="3" cols="50" %s>%s</textarea>',
5275 - esc_attr($disabled),
6608 + '<textarea id="live_agent_away_message" name="live_agent_away_message" rows="3" cols="50">%s</textarea>',
5276 6609 esc_textarea($message)
5277 6610 );
5278 6611 echo '<p class="description">' . esc_html__('Message shown when live agents are offline.', 'mxchat') . '</p>';
5279 -
5280 - if (!$this->is_activated) {
5281 - echo '<div class="pro-feature-overlay">';
5282 - 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>';
5283 - echo '</div>';
5284 - }
5285 - echo '</div>';
5286 6612 }
5287 6613
5288 6614 public function mxchat_live_agent_notification_message_callback() {
5289 - $disabled = $this->is_activated ? '' : 'disabled';
5290 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
5291 6615 $message = isset($this->options['live_agent_notification_message'])
5292 6616 ? $this->options['live_agent_notification_message']
5293 6617 : __('Live agent has been notified.', 'mxchat');
5294 6618
5295 - echo '<div class="' . esc_attr($class) . '">';
5296 6619 printf(
5297 - '<textarea id="live_agent_notification_message" name="live_agent_notification_message" rows="3" cols="50" %s>%s</textarea>',
5298 - esc_attr($disabled),
6620 + '<textarea id="live_agent_notification_message" name="live_agent_notification_message" rows="3" cols="50">%s</textarea>',
5299 6621 esc_textarea($message)
5300 6622 );
5301 6623 echo '<p class="description">' . esc_html__('Message shown when live transfer activated.', 'mxchat') . '</p>';
5302 -
5303 - if (!$this->is_activated) {
5304 - echo '<div class="pro-feature-overlay">';
5305 - 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>';
5306 - echo '</div>';
5307 - }
5308 - echo '</div>';
5309 6624 }
5310 6625
5311 6626 public function mxchat_live_agent_webhook_url_callback() {
5312 - $disabled = $this->is_activated ? '' : 'disabled';
5313 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
5314 6627 $webhook_url = isset($this->options['live_agent_webhook_url'])
5315 6628 ? esc_url($this->options['live_agent_webhook_url'])
5316 6629 : esc_url(get_option('live_agent_webhook_url', ''));
5317 6630
5318 - echo '<div class="' . esc_attr($class) . '">';
5319 6631 printf(
5320 - '<input type="password" id="live_agent_webhook_url" name="live_agent_webhook_url" value="%s" class="regular-text" %s />',
5321 - $webhook_url,
5322 - esc_attr($disabled)
6632 + '<input type="password" id="live_agent_webhook_url" name="live_agent_webhook_url" value="%s" class="regular-text" />',
6633 + $webhook_url
5323 6634 );
5324 6635 echo '<button type="button" id="toggleWebhookUrlVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5325 6636 echo '<p class="description">' . esc_html__('Enter your Slack webhook URL for live agent notifications.', 'mxchat') . '</p>';
6637 +}
5326 6638
5327 - if (!$this->is_activated) {
5328 - echo '<div class="pro-feature-overlay">';
5329 - 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>';
5330 - echo '</div>';
6639 +public function mxchat_live_agent_secret_key_callback() {
6640 + printf(
6641 + '<input type="password" id="live_agent_secret_key" name="live_agent_secret_key" value="%s" class="regular-text" />',
6642 + isset($this->options['live_agent_secret_key']) ? esc_attr($this->options['live_agent_secret_key']) : ''
6643 + );
6644 + echo '<button type="button" id="toggleSecretKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
6645 + echo '<p class="description">' . esc_html__('Secret key for validating Slack requests. Keep this secure.', 'mxchat') . '</p>';
6646 +}
6647 +
6648 +public function mxchat_live_agent_bot_token_callback() {
6649 + printf(
6650 + '<input type="password" id="live_agent_bot_token" name="live_agent_bot_token" value="%s" class="regular-text" />',
6651 + isset($this->options['live_agent_bot_token']) ? esc_attr($this->options['live_agent_bot_token']) : ''
6652 + );
6653 + echo '<button type="button" id="toggleBotTokenVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
6654 + echo '<p class="description">' . esc_html__('Your Slack Bot OAuth Token (starts with xoxb-). Keep this secure.', 'mxchat') . '</p>';
6655 +}
6656 +
6657 +public function mxchat_live_agent_user_ids_callback() {
6658 + $user_ids = isset($this->options['live_agent_user_ids'])
6659 + ? esc_textarea($this->options['live_agent_user_ids'])
6660 + : '';
6661 +
6662 + printf(
6663 + '<textarea id="live_agent_user_ids" name="live_agent_user_ids" rows="4" class="large-text">%s</textarea>',
6664 + $user_ids
6665 + );
6666 + 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>';
6667 +}
6668 +
6669 +/**
6670 + * Telegram Integration Callbacks
6671 + */
6672 +public function mxchat_telegram_section_callback() {
6673 + echo '<p>' . esc_html__('Configure Telegram integration for live agent support.', 'mxchat') . '</p>';
6674 +}
6675 +
6676 +public function mxchat_telegram_status_callback() {
6677 + $fresh_options = get_option('mxchat_options');
6678 + $status = isset($fresh_options['telegram_status']) ? $fresh_options['telegram_status'] : 'off';
6679 +
6680 + echo '<label class="toggle-switch">';
6681 + echo sprintf(
6682 + '<input type="checkbox" id="telegram_status" name="telegram_status" value="on" %s />',
6683 + checked($status, 'on', false)
6684 + );
6685 + echo '<span class="slider"></span>';
6686 + echo '</label>';
6687 + echo '<label for="telegram_status" class="mxchat-status-label">';
6688 + echo '<span class="status-text">' . ($status === 'on' ? esc_html__('Online', 'mxchat') : esc_html__('Offline', 'mxchat')) . '</span>';
6689 + echo '</label>';
6690 +}
6691 +
6692 +public function mxchat_telegram_notification_message_callback() {
6693 + $message = isset($this->options['telegram_notification_message'])
6694 + ? $this->options['telegram_notification_message']
6695 + : __("I've notified a support agent. Please allow a moment for them to respond.", 'mxchat');
6696 +
6697 + printf(
6698 + '<textarea id="telegram_notification_message" name="telegram_notification_message" rows="3" cols="50">%s</textarea>',
6699 + esc_textarea($message)
6700 + );
6701 + echo '<p class="description">' . esc_html__('Message shown when live transfer activated.', 'mxchat') . '</p>';
6702 +}
6703 +
6704 +public function mxchat_telegram_away_message_callback() {
6705 + $message = isset($this->options['telegram_away_message'])
6706 + ? $this->options['telegram_away_message']
6707 + : __('Sorry, live agents are currently unavailable. I can continue helping you as an AI assistant.', 'mxchat');
6708 +
6709 + printf(
6710 + '<textarea id="telegram_away_message" name="telegram_away_message" rows="3" cols="50">%s</textarea>',
6711 + esc_textarea($message)
6712 + );
6713 + echo '<p class="description">' . esc_html__('Message shown when live agents are offline.', 'mxchat') . '</p>';
6714 +}
6715 +
6716 +public function mxchat_telegram_bot_token_callback() {
6717 + printf(
6718 + '<input type="password" id="telegram_bot_token" name="telegram_bot_token" value="%s" class="regular-text" />',
6719 + isset($this->options['telegram_bot_token']) ? esc_attr($this->options['telegram_bot_token']) : ''
6720 + );
6721 + echo '<button type="button" class="button button-secondary" onclick="var f=document.getElementById(\'telegram_bot_token\'); if(f.type===\'password\'){f.type=\'text\';this.textContent=\'' . esc_js(__('Hide', 'mxchat')) . '\';}else{f.type=\'password\';this.textContent=\'' . esc_js(__('Show', 'mxchat')) . '\';}">' . esc_html__('Show', 'mxchat') . '</button>';
6722 + echo '<p class="description">' . esc_html__('Your Telegram Bot Token from @BotFather (e.g., 123456789:ABCdefGhIJKlmNoPQRsTUVwxyZ).', 'mxchat') . '</p>';
6723 +}
6724 +
6725 +public function mxchat_telegram_group_id_callback() {
6726 + printf(
6727 + '<input type="text" id="telegram_group_id" name="telegram_group_id" value="%s" class="regular-text" />',
6728 + isset($this->options['telegram_group_id']) ? esc_attr($this->options['telegram_group_id']) : ''
6729 + );
6730 + echo '<p class="description">' . esc_html__('Your Telegram supergroup ID (starts with -100). The group must have forum topics enabled.', 'mxchat') . '</p>';
6731 +}
6732 +
6733 +public function mxchat_telegram_webhook_secret_callback() {
6734 + $secret = isset($this->options['telegram_webhook_secret']) ? $this->options['telegram_webhook_secret'] : '';
6735 +
6736 + // Auto-generate secret if empty
6737 + if (empty($secret)) {
6738 + $secret = wp_generate_password(64, false, false);
6739 + $options = get_option('mxchat_options', []);
6740 + $options['telegram_webhook_secret'] = $secret;
6741 + update_option('mxchat_options', $options);
6742 + $this->options['telegram_webhook_secret'] = $secret;
5331 6743 }
5332 - echo '</div>';
6744 +
6745 + printf(
6746 + '<input type="password" id="telegram_webhook_secret" name="telegram_webhook_secret" value="%s" class="regular-text" />',
6747 + esc_attr($secret)
6748 + );
6749 + echo '<button type="button" class="button button-secondary" onclick="var f=document.getElementById(\'telegram_webhook_secret\'); if(f.type===\'password\'){f.type=\'text\';this.textContent=\'' . esc_js(__('Hide', 'mxchat')) . '\';}else{f.type=\'password\';this.textContent=\'' . esc_js(__('Show', 'mxchat')) . '\';}">' . esc_html__('Show', 'mxchat') . '</button>';
6750 + echo '<p class="description">' . esc_html__('Secret token for webhook verification. Use this when setting up your Telegram webhook with the secret_token parameter.', 'mxchat') . '</p>';
5333 6751 }
5334 6752
5335 -
5336 6753 public function mxchat_similarity_threshold_callback() {
5337 6754 // Load from mxchat_options array
5338 6755 $options = get_option('mxchat_options', []);
5339 6756
5340 - // Get value with backwards compatibility
5341 - $threshold = isset($options['similarity_threshold'])
5342 - ? $options['similarity_threshold']
5343 - : get_option('mxchat_similarity_threshold', 80);
6757 + // Get value from options array with default of 80
6758 + $threshold = isset($options['similarity_threshold']) ? $options['similarity_threshold'] : 35;
5344 6759
5345 6760 echo '<div class="slider-container">';
5346 6761 echo sprintf(
5347 6762 '<input type="range"
@@ -5346,9 +6761,9 @@
5346 6761 echo sprintf(
5347 6762 '<input type="range"
5348 6763 id="similarity_threshold"
5349 6764 name="similarity_threshold"
5350 - min="70"
6765 + min="20"
5351 6766 max="85"
5352 6767 step="1"
5353 6768 value="%s"
5354 6769 class="range-slider" />',
@@ -5358,194 +6773,258 @@
5358 6773 '<span id="threshold_value" class="range-value">%s</span>',
5359 6774 esc_html($threshold)
5360 6775 );
5361 6776 echo '</div>';
5362 -
5363 6777 echo '<p class="description">';
5364 - 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');
6778 + 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');
5365 6779 echo '</p>';
5366 6780 }
5367 6781
6782 +public function mxchat_enqueue_admin_assets() {
6783 + // Get plugin version
6784 + $version = MXCHAT_VERSION;
5368 6785
6786 + // Use file modification time for development (remove in production)
6787 + if (defined('WP_DEBUG') && WP_DEBUG) {
6788 + $version = filemtime(plugin_dir_path(__FILE__) . '../mxchat-basic.php');
6789 + }
5369 6790
5370 -public function mxchat_live_agent_secret_key_callback() {
5371 - $disabled = $this->is_activated ? '' : 'disabled';
5372 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6791 + $current_page = isset($_GET['page']) ? $_GET['page'] : '';
6792 + $plugin_url = plugin_dir_url(__FILE__) . '../';
5373 6793
5374 - echo '<div class="' . esc_attr($class) . '">';
5375 - printf(
5376 - '<input type="password" id="live_agent_secret_key" name="live_agent_secret_key" value="%s" class="regular-text" %s />',
5377 - isset($this->options['live_agent_secret_key']) ? esc_attr($this->options['live_agent_secret_key']) : '',
5378 - esc_attr($disabled)
5379 - );
5380 - echo '<button type="button" id="toggleSecretKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5381 - echo '<p class="description">' . esc_html__('Secret key for validating Slack requests. Keep this secure.', 'mxchat') . '</p>';
6794 + // Only load on MxChat pages
6795 + if (strpos($current_page, 'mxchat') === false) {
6796 + return;
6797 + }
5382 6798
5383 - if (!$this->is_activated) {
5384 - echo '<div class="pro-feature-overlay">';
5385 - 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>';
5386 - echo '</div>';
5387 - }
5388 - echo '</div>';
6799 + // Always load these on all MxChat pages
6800 + $this->enqueue_core_admin_assets($plugin_url, $version);
6801 + $this->enqueue_page_specific_assets($current_page, $plugin_url, $version);
6802 + $this->localize_admin_scripts($current_page);
5389 6803 }
6804 +private function enqueue_core_admin_assets($plugin_url, $version) {
6805 + // Core admin styles
6806 + wp_enqueue_style('mxchat-admin-css', $plugin_url . 'css/admin-style.css', array(), $version);
6807 + wp_enqueue_style('mxchat-knowledge-css', $plugin_url . 'css/knowledge-style.css', array(), $version);
5390 6808
5391 -public function mxchat_live_agent_bot_token_callback() {
5392 - $disabled = $this->is_activated ? '' : 'disabled';
5393 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6809 + // New sidebar navigation styles (for main settings page)
6810 + wp_enqueue_style('mxchat-admin-sidebar-css', $plugin_url . 'css/admin-sidebar.css', array(), $version);
5394 6811
5395 - echo '<div class="' . esc_attr($class) . '">';
5396 - printf(
5397 - '<input type="password" id="live_agent_bot_token" name="live_agent_bot_token" value="%s" class="regular-text" %s />',
5398 - isset($this->options['live_agent_bot_token']) ? esc_attr($this->options['live_agent_bot_token']) : '',
5399 - esc_attr($disabled)
5400 - );
5401 - echo '<button type="button" id="toggleBotTokenVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5402 - echo '<p class="description">' . esc_html__('Your Slack Bot OAuth Token (starts with xoxb-). Keep this secure.', 'mxchat') . '</p>';
5403 -
5404 - if (!$this->is_activated) {
5405 - echo '<div class="pro-feature-overlay">';
5406 - 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>';
5407 - echo '</div>';
5408 - }
5409 - echo '</div>';
6812 + // Core admin scripts
6813 + wp_enqueue_script('mxchat-admin-js', $plugin_url . 'js/mxchat-admin.js', array('jquery'), $version, true);
5410 6814 }
6815 +private function enqueue_page_specific_assets($current_page, $plugin_url, $version) {
6816 + switch ($current_page) {
6817 + case 'mxchat-prompts':
6818 + // Knowledge processing page assets
6819 + wp_enqueue_style('mxchat-content-selector-css', $plugin_url . 'css/content-selector.css', array(), $version);
6820 + wp_enqueue_script('mxchat-content-selector-js', $plugin_url . 'js/content-selector.js', array('jquery'), $version, true);
6821 + // Add the knowledge processing script (common script needed for WordPress dismiss functionality)
6822 + wp_enqueue_script('mxchat-knowledge-processing', $plugin_url . 'js/knowledge-processing.js', array('jquery', 'common'), $version, true);
6823 + break;
5411 6824
5412 -public function mxchat_enqueue_admin_assets() {
5413 - wp_enqueue_style('wp-color-picker');
6825 + case 'mxchat-transcripts':
6826 + // Load admin sidebar CSS first (shared styles for sidebar navigation)
6827 + wp_enqueue_style('mxchat-admin-sidebar-css', $plugin_url . 'css/admin-sidebar.css', array(), $version);
6828 + // Load transcripts-specific styles
6829 + wp_enqueue_style('mxchat-chat-transcripts-css', $plugin_url . 'css/chat-transcripts.css', array('mxchat-admin-sidebar-css'), $version);
6830 + wp_enqueue_script('mxchat-transcripts-js', $plugin_url . 'js/mxchat_transcripts.js', array('jquery'), $version, true);
6831 + break;
5414 6832
5415 - // Get the plugin version or file modification time for cache busting
5416 - $plugin_version = '2.0.5'; // Replace this with your plugin's version
6833 + case 'mxchat-actions':
6834 + // Load admin sidebar CSS first (shared styles for sidebar navigation)
6835 + wp_enqueue_style('mxchat-admin-sidebar-css', $plugin_url . 'css/admin-sidebar.css', array(), $version);
6836 + // Load actions-specific styles
6837 + wp_enqueue_style('mxchat-actions-css', $plugin_url . 'css/actions.css', array('mxchat-admin-sidebar-css'), $version);
6838 + wp_enqueue_script('mxchat-actions-js', $plugin_url . 'js/mxchat_actions.js', array('jquery'), $version, true);
5417 6839
5418 - // File paths
5419 - $color_picker_js_path = plugin_dir_path(__FILE__) . '../js/my-color-picker.js';
5420 - $embedding_check_js_path = plugin_dir_path(__FILE__) . '../js/embedding-check.js';
5421 - $admin_css_path = plugin_dir_path(__FILE__) . '../css/admin-style.css';
5422 - $knowledge_css_path = plugin_dir_path(__FILE__) . '../css/knowledge-style.css';
5423 - $intent_css_path = plugin_dir_path(__FILE__) . '../css/intent-style.css';
6840 + // Localize script data for actions page
6841 + $is_activated = get_option('mxchat_pro_license_status') === 'active';
6842 + wp_localize_script('mxchat-actions-js', 'mxchActionsData', array(
6843 + 'ajaxUrl' => admin_url('admin-ajax.php'),
6844 + 'nonce' => wp_create_nonce('mxchat_actions_nonce'),
6845 + 'addNonce' => wp_create_nonce('mxchat_add_intent_nonce'),
6846 + 'editNonce' => wp_create_nonce('mxchat_edit_intent'),
6847 + 'deleteNonce' => wp_create_nonce('mxchat_delete_intent_nonce'),
6848 + 'toggleNonce' => wp_create_nonce('mxchat_actions_nonce'),
6849 + 'isActivated' => $is_activated,
6850 + 'i18n' => array(
6851 + 'confirmDelete' => __('Are you sure you want to delete this action?', 'mxchat'),
6852 + 'confirmBulkDelete' => __('Are you sure you want to delete the selected actions?', 'mxchat'),
6853 + 'saving' => __('Saving...', 'mxchat'),
6854 + 'saved' => __('Saved successfully!', 'mxchat'),
6855 + 'error' => __('An error occurred. Please try again.', 'mxchat'),
6856 + 'proRequired' => __('This feature requires MxChat Pro.', 'mxchat'),
6857 + 'addonRequired' => __('This feature requires an add-on.', 'mxchat')
6858 + )
6859 + ));
6860 + break;
5424 6861
5425 - // Check if files exist and get modification times
5426 - $color_picker_version = file_exists($color_picker_js_path) ? filemtime($color_picker_js_path) : $plugin_version;
5427 - $embedding_check_version = file_exists($embedding_check_js_path) ? filemtime($embedding_check_js_path) : $plugin_version;
5428 - $admin_css_version = file_exists($admin_css_path) ? filemtime($admin_css_path) : $plugin_version;
5429 - $knowledge_css_version = file_exists($knowledge_css_path) ? filemtime($knowledge_css_path) : $plugin_version;
5430 - $intent_css_version = file_exists($intent_css_path) ? filemtime($intent_css_path) : $plugin_version;
6862 + case 'mxchat-activation':
6863 + // Load admin sidebar CSS first (shared styles for sidebar navigation)
6864 + wp_enqueue_style('mxchat-admin-sidebar-css', $plugin_url . 'css/admin-sidebar.css', array(), $version);
6865 + // Load pro page-specific styles
6866 + wp_enqueue_style('mxchat-pro-css', $plugin_url . 'css/admin-pro.css', array('mxchat-admin-sidebar-css'), $version);
6867 + // Load pro page JavaScript
6868 + wp_enqueue_script('mxchat-pro-js', $plugin_url . 'js/mxchat_pro.js', array('jquery'), $version, true);
6869 + // Load activation script for license activation/deactivation
6870 + wp_enqueue_script('mxchat-activation-js', $plugin_url . 'js/activation-script.js', array('jquery'), $version, true);
6871 + break;
6872 + default:
6873 + wp_enqueue_script(
6874 + 'mxchat-test-streaming-js',
6875 + $plugin_url . 'js/mxchat-test-streaming.js',
6876 + ['jquery'],
6877 + $version,
6878 + true
6879 + );
5431 6880
5432 - // Enqueue scripts and styles with corrected paths
5433 - wp_enqueue_script(
5434 - 'mxchat-color-picker',
5435 - plugin_dir_url(__FILE__) . '../js/my-color-picker.js',
5436 - array('wp-color-picker'),
5437 - $color_picker_version,
5438 - true
5439 - );
5440 -
5441 - wp_enqueue_script(
5442 - 'mxchat-embedding-check',
5443 - plugin_dir_url(__FILE__) . '../js/embedding-check.js',
5444 - array(),
5445 - $embedding_check_version,
5446 - true
5447 - );
5448 -
5449 - wp_enqueue_script(
5450 - 'mxchat-admin-js',
5451 - plugin_dir_url(__FILE__) . '../js/mxchat-admin.js',
5452 - array('jquery'),
5453 - $plugin_version,
5454 - true
5455 - );
5456 -
5457 - wp_localize_script('mxchat-admin-js', 'mxchatAdmin', array(
6881 + wp_localize_script('mxchat-test-streaming-js', 'mxchatTestStreamingAjax', [
6882 + 'ajax_url' => admin_url('admin-ajax.php'),
6883 + 'nonce' => wp_create_nonce('mxchat_test_streaming_nonce'),
6884 + 'settings_nonce' => wp_create_nonce('mxchat_save_setting_nonce')
6885 + ]);
6886 + break;
6887 + }
6888 +}
6889 +private function localize_admin_scripts($current_page) {
6890 + // Base localization data for main admin script
6891 + $base_data = array(
5458 6892 'ajax_url' => admin_url('admin-ajax.php'),
6893 + 'nonce' => wp_create_nonce('mxchat_admin_nonce'),
6894 + 'admin_url' => admin_url(),
5459 6895 'license_nonce' => wp_create_nonce('mxchat_activate_license_nonce'),
5460 6896 'inline_edit_nonce' => wp_create_nonce('mxchat_save_inline_nonce'),
5461 6897 'setting_nonce' => wp_create_nonce('mxchat_save_setting_nonce'),
5462 6898 'export_nonce' => wp_create_nonce('mxchat_export_transcripts'),
5463 - ));
6899 + 'actions_nonce' => wp_create_nonce('mxchat_actions_nonce'),
6900 + 'add_intent_nonce' => wp_create_nonce('mxchat_add_intent_nonce'),
6901 + 'edit_intent_nonce' => wp_create_nonce('mxchat_edit_intent'),
6902 + 'toggle_action_nonce' => wp_create_nonce('mxchat_actions_nonce'),
6903 + 'fetch_openrouter_models_nonce' => wp_create_nonce('mxchat_fetch_openrouter_models'),
6904 + 'is_activated' => $this->is_activated ? '1' : '0',
6905 + 'status_refresh_interval' => 5000,
6906 + 'prompts_setting_nonce' => wp_create_nonce('mxchat_prompts_setting_nonce'),
6907 + 'ajaxurl' => admin_url('admin-ajax.php')
6908 + );
5464 6909
5465 - // Enqueue the admin CSS
5466 - wp_enqueue_style(
5467 - 'mxchat-admin-css',
5468 - plugin_dir_url(__FILE__) . '../css/admin-style.css',
5469 - array(),
5470 - $admin_css_version
5471 - );
5472 -
5473 - if (isset($_GET['page']) && $_GET['page'] === 'mxchat-transcripts') {
5474 - wp_enqueue_style(
5475 - 'mxchat-chat-transcripts-css',
5476 - plugin_dir_url(__FILE__) . '../css/chat-transcripts.css',
5477 - array(),
5478 - $transcripts_css_version
5479 - );
5480 -
5481 - wp_enqueue_script(
5482 - 'mxchat-transcripts-js',
5483 - plugin_dir_url(__FILE__) . '../js/mxchat_transcripts.js',
5484 - array('jquery'),
5485 - $transcripts_js_version,
5486 - true
5487 - );
5488 - }
5489 -
5490 - wp_enqueue_style(
5491 - 'mxchat-knowledge-css',
5492 - plugin_dir_url(__FILE__) . '../css/knowledge-style.css',
5493 - array(),
5494 - $knowledge_css_version
5495 - );
6910 + // Localize main admin script with base data
6911 + wp_localize_script('mxchat-admin-js', 'mxchatAdmin', $base_data);
5496 6912
6913 + // Page-specific localizations
6914 + $this->localize_page_specific_scripts($current_page);
6915 +}
6916 +private function localize_page_specific_scripts($current_page) {
6917 + switch ($current_page) {
6918 + case 'mxchat-prompts':
6919 + // Status updater localization
6920 + wp_localize_script('mxchat-status-updater', 'mxchat_status_data', array(
6921 + 'ajax_url' => admin_url('admin-ajax.php'),
6922 + 'nonce' => wp_create_nonce('mxchat_status_nonce')
6923 + ));
5497 6924
5498 - wp_enqueue_style(
5499 - 'mxchat-intent-css',
5500 - plugin_dir_url(__FILE__) . '../css/intent-style.css',
5501 - array(),
5502 - $intent_css_version
5503 - );
6925 + // Content selector localization
6926 + wp_localize_script('mxchat-content-selector-js', 'mxchatSelector', array(
6927 + 'ajaxurl' => admin_url('admin-ajax.php'),
6928 + 'nonce' => wp_create_nonce('mxchat_content_selector_nonce'),
6929 + 'i18n' => array(
6930 + 'searchPlaceholder' => __('Search posts and pages...', 'mxchat'),
6931 + 'selectAll' => __('Select All', 'mxchat'),
6932 + 'process' => __('Process Selected', 'mxchat'),
6933 + 'cancel' => __('Cancel', 'mxchat'),
6934 + 'noResults' => __('No content found.', 'mxchat')
6935 + )
6936 + ));
5504 6937
5505 - // IMPORTANT: Use the same script handle as above for localizing mxchatPromptsAdmin
5506 - wp_localize_script( 'mxchat-admin-js', 'mxchatPromptsAdmin', array(
5507 - 'ajax_url' => admin_url( 'admin-ajax.php' ),
5508 - 'prompts_setting_nonce' => wp_create_nonce( 'mxchat_prompts_setting_nonce' ),
5509 - ) );
6938 + wp_localize_script('mxchat-knowledge-processing', 'mxchatAdmin', array(
6939 + 'ajax_url' => admin_url('admin-ajax.php'),
6940 + 'status_nonce' => wp_create_nonce('mxchat_status_nonce'),
6941 + 'queue_nonce' => wp_create_nonce('mxchat_queue_nonce'),
6942 + 'stop_nonce' => wp_create_nonce('mxchat_stop_processing_action'),
6943 + 'settings_nonce' => wp_create_nonce('mxchat_prompts_setting_nonce'),
6944 + 'setting_nonce' => wp_create_nonce('mxchat_save_setting_nonce'),
6945 + 'entries_nonce' => wp_create_nonce('mxchat_entries_nonce'),
6946 + 'admin_url' => admin_url(),
6947 + 'ajaxurl' => admin_url('admin-ajax.php'),
6948 + 'status_refresh_interval' => 2000,
6949 + 'bot_id' => isset($_GET['bot_id']) ? sanitize_text_field($_GET['bot_id']) : 'default'
6950 + ));
6951 + break;
5510 6952
5511 - // Localize the script for color picker and settings
5512 - wp_localize_script('mxchat-color-picker', 'mxchatStyleSettings', array(
6953 + case 'mxchat-activation':
6954 + wp_localize_script('mxchat-activation-js', 'mxchatAdmin', array(
6955 + 'ajax_url' => admin_url('admin-ajax.php'),
6956 + 'license_nonce' => wp_create_nonce('mxchat_activate_license_nonce')
6957 + ));
6958 + break;
6959 +
6960 + case 'mxchat-transcripts':
6961 + // Get chart data for localization
6962 + $chart_data = $this->get_transcripts_chart_data();
6963 +
6964 + wp_localize_script('mxchat-transcripts-js', 'mxchatAdmin', array(
6965 + 'ajax_url' => admin_url('admin-ajax.php'),
6966 + 'export_nonce' => wp_create_nonce('mxchat_export_transcripts'),
6967 + 'delete_nonce' => wp_create_nonce('mxchat_delete_chat_history'),
6968 + 'setting_nonce' => wp_create_nonce('mxchat_save_setting_nonce')
6969 + ));
6970 +
6971 + // Localize chart data separately - use array_values to ensure proper JSON array encoding
6972 + wp_localize_script('mxchat-transcripts-js', 'mxchatChartData', array(
6973 + 'labels' => array_values($chart_data['labels']),
6974 + 'chats' => array_values($chart_data['chats']),
6975 + 'messages' => array_values($chart_data['messages'])
6976 + ));
6977 + break;
6978 +
6979 + case 'mxchat-settings':
6980 + default:
6981 + // Color picker and settings localization
6982 + wp_localize_script('mxchat-color-picker', 'mxchatStyleSettings', array(
6983 + 'ajax_url' => admin_url('admin-ajax.php'),
6984 + 'link_target_toggle' => $this->options['link_target_toggle'] ?? 'off',
6985 + 'user_message_bg_color' => $this->options['user_message_bg_color'] ?? '#fff',
6986 + 'user_message_font_color' => $this->options['user_message_font_color'] ?? '#212121',
6987 + 'bot_message_bg_color' => $this->options['bot_message_bg_color'] ?? '#212121',
6988 + 'bot_message_font_color' => $this->options['bot_message_font_color'] ?? '#fff',
6989 + 'top_bar_bg_color' => $this->options['top_bar_bg_color'] ?? '#212121',
6990 + 'send_button_font_color' => $this->options['send_button_font_color'] ?? '#212121',
6991 + 'close_button_color' => $this->options['close_button_color'] ?? '#fff',
6992 + 'chatbot_background_color' => $this->options['chatbot_background_color'] ?? '#212121',
6993 + 'icon_color' => $this->options['icon_color'] ?? '#fff',
6994 + 'chat_input_font_color' => $this->options['chat_input_font_color'] ?? '#212121',
6995 + 'pre_chat_message' => $this->options['pre_chat_message'] ?? esc_html__('Hey there! Ask me anything!', 'mxchat'),
6996 + 'rate_limit_message' => $this->options['rate_limit_message'] ?? esc_html__('Rate limit exceeded. Please try again later.', 'mxchat'),
6997 + 'loops_api_key' => $this->options['loops_api_key'] ?? '',
6998 + 'loops_mailing_list' => $this->options['loops_mailing_list'] ?? '',
6999 + 'triggered_phrase_response' => $this->options['triggered_phrase_response'] ?? esc_html__('Would you like to join our mailing list? Please provide your email below.', 'mxchat'),
7000 + 'email_capture_response' => $this->options['email_capture_response'] ?? esc_html__('Thank you for providing your email! You\'ve been added to our list.', 'mxchat'),
7001 + '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'),
7002 + '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'),
7003 + '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'),
7004 + 'pdf_max_pages' => $this->options['pdf_max_pages'] ?? 69,
7005 + 'live_agent_webhook_url' => $this->options['live_agent_webhook_url'] ?? '',
7006 + 'live_agent_secret_key' => $this->options['live_agent_secret_key'] ?? '',
7007 + 'live_agent_bot_token' => $this->options['live_agent_bot_token'] ?? '',
7008 + 'live_agent_message_bg_color' => $this->options['live_agent_message_bg_color'] ?? '#ffffff',
7009 + 'live_agent_message_font_color' => $this->options['live_agent_message_font_color'] ?? '#333333',
7010 + 'chat_toolbar_toggle' => $this->options['chat_toolbar_toggle'] ?? 'off',
7011 + 'show_pdf_upload_button' => $this->options['show_pdf_upload_button'] ?? 'on',
7012 + 'show_word_upload_button' => $this->options['show_word_upload_button'] ?? 'on',
7013 + 'mode_indicator_bg_color' => $this->options['mode_indicator_bg_color'] ?? '#767676',
7014 + 'mode_indicator_font_color' => $this->options['mode_indicator_font_color'] ?? '#ffffff',
7015 + 'toolbar_icon_color' => $this->options['toolbar_icon_color'] ?? '#212121',
7016 + ));
7017 + break;
7018 + }
7019 +
7020 + // Additional localization that was in the original code
7021 + wp_localize_script('mxchat-admin-js', 'mxchatPromptsAdmin', array(
5513 7022 'ajax_url' => admin_url('admin-ajax.php'),
5514 - 'link_target_toggle' => $this->options['link_target_toggle'] ?? 'off',
5515 - 'user_message_bg_color' => $this->options['user_message_bg_color'] ?? '#fff',
5516 - 'user_message_font_color' => $this->options['user_message_font_color'] ?? '#212121',
5517 - 'bot_message_bg_color' => $this->options['bot_message_bg_color'] ?? '#212121',
5518 - 'bot_message_font_color' => $this->options['bot_message_font_color'] ?? '#fff',
5519 - 'top_bar_bg_color' => $this->options['top_bar_bg_color'] ?? '#212121',
5520 - 'send_button_font_color' => $this->options['send_button_font_color'] ?? '#212121',
5521 - 'close_button_color' => $this->options['close_button_color'] ?? '#fff',
5522 - 'chatbot_background_color' => $this->options['chatbot_background_color'] ?? '#212121',
5523 - 'icon_color' => $this->options['icon_color'] ?? '#fff',
5524 - 'chat_input_font_color' => $this->options['chat_input_font_color'] ?? '#212121',
5525 - 'pre_chat_message' => $this->options['pre_chat_message'] ?? esc_html__('Hey there! Ask me anything!', 'mxchat'),
5526 - 'rate_limit_message' => $this->options['rate_limit_message'] ?? esc_html__('Rate limit exceeded. Please try again later.', 'mxchat'),
5527 - 'loops_api_key' => $this->options['loops_api_key'] ?? '',
5528 - 'loops_mailing_list' => $this->options['loops_mailing_list'] ?? '',
5529 - 'triggered_phrase_response' => $this->options['triggered_phrase_response'] ?? esc_html__('Would you like to join our mailing list? Please provide your email below.', 'mxchat'),
5530 - 'email_capture_response' => $this->options['email_capture_response'] ?? esc_html__('Thank you for providing your email! You\'ve been added to our list.', 'mxchat'),
5531 - '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'),
5532 - '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'),
5533 - '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'),
5534 - 'pdf_max_pages' => $this->options['pdf_max_pages'] ?? 69,
5535 - 'live_agent_webhook_url' => $this->options['live_agent_webhook_url'] ?? '',
5536 - 'live_agent_secret_key' => $this->options['live_agent_secret_key'] ?? '',
5537 - 'live_agent_bot_token' => $this->options['live_agent_bot_token'] ?? '',
5538 - 'live_agent_message_bg_color' => $this->options['live_agent_message_bg_color'] ?? '#ffffff',
5539 - 'live_agent_message_font_color' => $this->options['live_agent_message_font_color'] ?? '#333333',
5540 - 'chat_toolbar_toggle' => $this->options['chat_toolbar_toggle'] ?? 'off',
5541 - 'mode_indicator_bg_color' => $this->options['mode_indicator_bg_color'] ?? '#767676',
5542 - 'mode_indicator_font_color' => $this->options['mode_indicator_font_color'] ?? '#ffffff',
5543 - 'toolbar_icon_color' => $this->options['toolbar_icon_color'] ?? '#212121',
7023 + 'prompts_setting_nonce' => wp_create_nonce('mxchat_prompts_setting_nonce'),
5544 7024 ));
5545 7025 }
5546 7026
5547 -
5548 7027 public function mxchat_sanitize($input) {
5549 7028 $new_input = array();
5550 7029
5551 7030 if (isset($input['api_key'])) {
@@ -5553,9 +7032,9 @@
5553 7032 }
5554 7033
5555 7034 if (isset($input['similarity_threshold'])) {
5556 7035 $new_input['similarity_threshold'] = absint($input['similarity_threshold']); // Ensure it's an integer
5557 - $new_input['similarity_threshold'] = min(max($new_input['similarity_threshold'], 70), 85); // Enforce range
7036 + $new_input['similarity_threshold'] = min(max($new_input['similarity_threshold'], 20), 85); // Enforce range
5558 7037 }
5559 7038
5560 7039 if (isset($input['xai_api_key'])) {
5561 7040 $new_input['xai_api_key'] = sanitize_text_field($input['xai_api_key']);
@@ -5564,12 +7043,30 @@
5564 7043 if (isset($input['claude_api_key'])) {
5565 7044 $new_input['claude_api_key'] = sanitize_text_field($input['claude_api_key']);
5566 7045 }
5567 7046
7047 + if (isset($input['enable_streaming_toggle'])) {
7048 + $new_input['enable_streaming_toggle'] = ($input['enable_streaming_toggle'] === 'on') ? 'on' : 'off';
7049 + } else {
7050 + // If checkbox not checked, it won't be in $input, so set to 'off'
7051 + $new_input['enable_streaming_toggle'] = 'off';
7052 + }
7053 +
7054 + if (isset($input['enable_web_search'])) {
7055 + $new_input['enable_web_search'] = ($input['enable_web_search'] === 'on') ? 'on' : 'off';
7056 + } else {
7057 + // If checkbox not checked, it won't be in $input, so set to 'off'
7058 + $new_input['enable_web_search'] = 'off';
7059 + }
7060 +
5568 7061 if (isset($input['deepseek_api_key'])) {
5569 7062 $new_input['deepseek_api_key'] = sanitize_text_field($input['deepseek_api_key']);
5570 7063 }
5571 7064
7065 + if (isset($input['gemini_api_key'])) {
7066 + $new_input['gemini_api_key'] = sanitize_text_field($input['gemini_api_key']);
7067 + }
7068 +
5572 7069 if (isset($input['enable_woocommerce_integration'])) {
5573 7070 $new_input['enable_woocommerce_integration'] = $input['enable_woocommerce_integration'] === 'on' ? 'on' : 'off';
5574 7071 }
5575 7072
@@ -5602,12 +7099,44 @@
5602 7099 if (isset($input['append_to_body'])) {
5603 7100 $new_input['append_to_body'] = $input['append_to_body'] === 'on' ? 'on' : 'off';
5604 7101 }
5605 7102
7103 + // Post type visibility settings
7104 + if (isset($input['post_type_visibility_mode'])) {
7105 + $allowed_modes = array('all', 'include', 'exclude');
7106 + $new_input['post_type_visibility_mode'] = in_array($input['post_type_visibility_mode'], $allowed_modes)
7107 + ? $input['post_type_visibility_mode']
7108 + : 'all';
7109 + }
7110 +
7111 + if (isset($input['post_type_visibility_list'])) {
7112 + if (is_array($input['post_type_visibility_list'])) {
7113 + $new_input['post_type_visibility_list'] = array_map('sanitize_key', $input['post_type_visibility_list']);
7114 + } else {
7115 + $new_input['post_type_visibility_list'] = array();
7116 + }
7117 + }
7118 +
7119 + if (isset($input['contextual_awareness_toggle'])) {
7120 + $new_input['contextual_awareness_toggle'] = $input['contextual_awareness_toggle'] === 'on' ? 'on' : 'off';
7121 +}
7122 +
7123 + if (isset($input['show_frontend_debugger'])) {
7124 + $new_input['show_frontend_debugger'] = $input['show_frontend_debugger'] === 'on' ? 'on' : 'off';
7125 +}
7126 +
7127 + if (isset($input['citation_links_toggle'])) {
7128 + $new_input['citation_links_toggle'] = $input['citation_links_toggle'] === 'on' ? 'on' : 'off';
7129 +}
7130 +
5606 7131 if (isset($input['top_bar_title'])) {
5607 7132 $new_input['top_bar_title'] = sanitize_text_field($input['top_bar_title']);
5608 7133 }
5609 7134
7135 + if (isset($input['ai_agent_text'])) {
7136 + $new_input['ai_agent_text'] = sanitize_text_field($input['ai_agent_text']);
7137 + }
7138 +
5610 7139 if (isset($input['enable_email_block'])) {
5611 7140 $new_input['enable_email_block'] = sanitize_text_field($input['enable_email_block']);
5612 7141 }
5613 7142
@@ -5617,11 +7146,20 @@
5617 7146 }
5618 7147 if (isset($input['email_blocker_button_text'])) {
5619 7148 $new_input['email_blocker_button_text'] = sanitize_text_field($input['email_blocker_button_text']);
5620 7149 }
5621 -
7150 + // Sanitize name field toggle
7151 + if (isset($input['enable_name_field'])) {
7152 + $new_input['enable_name_field'] = ($input['enable_name_field'] === 'on') ? 'on' : 'off';
7153 + } else {
7154 + $new_input['enable_name_field'] = 'off';
7155 + }
7156 + // Sanitize name field placeholder
7157 + if (isset($input['name_field_placeholder'])) {
7158 + $new_input['name_field_placeholder'] = sanitize_text_field($input['name_field_placeholder']);
7159 + }
5622 7160 if (isset($input['intro_message'])) {
5623 - $new_input['intro_message'] = sanitize_textarea_field($input['intro_message']); // Changed from sanitize_text_field
7161 + $new_input['intro_message'] = wp_kses_post($input['intro_message']); // Use wp_kses_post instead
5624 7162 }
5625 7163
5626 7164 if (isset($input['input_copy'])) {
5627 7165 $new_input['input_copy'] = sanitize_text_field($input['input_copy']);
@@ -5630,47 +7168,100 @@
5630 7168 if (isset($input['rate_limit_message'])) {
5631 7169 $new_input['rate_limit_message'] = sanitize_text_field($input['rate_limit_message']);
5632 7170 }
5633 7171
5634 - if (isset($input['rate_limit_logged_out'])) {
5635 - $allowed_limits = array('1', '3', '5', '10', '15', '20', '100', 'unlimited'); // Add 'unlimited' to allowed values
5636 - $rate_limit = sanitize_text_field($input['rate_limit_logged_out']);
7172 +// Handle the new rate limits format
7173 +if (isset($input['rate_limits']) && is_array($input['rate_limits'])) {
7174 + $new_input['rate_limits'] = array();
7175 + $allowed_limits = array('1', '3', '5', '10', '15', '20', '50', '100', 'unlimited');
7176 + $allowed_timeframes = array('hourly', 'daily', 'weekly', 'monthly');
5637 7177
5638 - if (in_array($rate_limit, $allowed_limits, true)) {
5639 - $new_input['rate_limit_logged_out'] = $rate_limit;
5640 - } else {
5641 - $new_input['rate_limit_logged_out'] = '10'; // Default for logged-out users
7178 + foreach ($input['rate_limits'] as $role_id => $settings) {
7179 + $new_input['rate_limits'][$role_id] = array();
7180 +
7181 + // Sanitize limit
7182 + if (isset($settings['limit'])) {
7183 + $limit = sanitize_text_field($settings['limit']);
7184 + if (in_array($limit, $allowed_limits, true)) {
7185 + $new_input['rate_limits'][$role_id]['limit'] = $limit;
7186 + } else {
7187 + $new_input['rate_limits'][$role_id]['limit'] = ($role_id === 'logged_out') ? '10' : '100'; // Default
7188 + }
5642 7189 }
5643 - }
5644 7190
5645 - // Handle role rate limits
5646 - if (isset($input['role_rate_limits']) && is_array($input['role_rate_limits'])) {
5647 - $allowed_limits = array('1', '3', '5', '10', '15', '20', '100', 'unlimited');
5648 - $new_input['role_rate_limits'] = array();
5649 -
5650 - foreach ($input['role_rate_limits'] as $role_id => $rate_limit) {
5651 - $rate_limit = sanitize_text_field($rate_limit);
5652 - if (in_array($rate_limit, $allowed_limits, true)) {
5653 - $new_input['role_rate_limits'][$role_id] = $rate_limit;
7191 + // Sanitize timeframe
7192 + if (isset($settings['timeframe'])) {
7193 + $timeframe = sanitize_text_field($settings['timeframe']);
7194 + if (in_array($timeframe, $allowed_timeframes, true)) {
7195 + $new_input['rate_limits'][$role_id]['timeframe'] = $timeframe;
5654 7196 } else {
5655 - $new_input['role_rate_limits'][$role_id] = '100'; // Default
7197 + $new_input['rate_limits'][$role_id]['timeframe'] = 'daily'; // Default
5656 7198 }
5657 7199 }
7200 +
7201 + // Sanitize message
7202 + if (isset($settings['message'])) {
7203 + $new_input['rate_limits'][$role_id]['message'] = sanitize_textarea_field($settings['message']);
7204 + }
5658 7205 }
7206 +}
5659 7207
5660 7208 if (isset($input['pre_chat_message'])) {
5661 7209 $new_input['pre_chat_message'] = sanitize_textarea_field($input['pre_chat_message']);
5662 7210 }
5663 7211
5664 - if (isset($input['model'])) {
7212 + if (isset($input['voyage_api_key'])) {
7213 + $new_input['voyage_api_key'] = sanitize_text_field($input['voyage_api_key']);
7214 + }
7215 +
7216 + // Add to your sanitize function
7217 + if (isset($input['embedding_model'])) {
5665 7218 $allowed_models = array(
5666 - 'grok-beta',
7219 + 'text-embedding-ada-002',
7220 + 'text-embedding-3-small',
7221 + 'text-embedding-3-large',
7222 + 'voyage-3-large',
7223 + 'gemini-embedding-exp-03-07'
7224 + );
7225 + if (in_array($input['embedding_model'], $allowed_models)) {
7226 + $new_input['embedding_model'] = sanitize_text_field($input['embedding_model']);
7227 + }
7228 + }
7229 +
7230 +if (isset($input['model'])) {
7231 + if ($input['model'] === 'openrouter') {
7232 + $new_input['model'] = 'openrouter';
7233 + } else {
7234 + $allowed_models = array(
7235 + 'gemini-2.0-flash',
7236 + 'gemini-2.0-flash-lite',
7237 + 'gemini-1.5-pro',
7238 + 'gemini-1.5-flash',
7239 + 'grok-4-0709',
7240 + 'grok-4-1-fast-reasoning',
7241 + 'grok-4-1-fast-non-reasoning',
7242 + 'grok-3-beta',
7243 + 'grok-3-fast-beta',
7244 + 'grok-3-mini-beta',
7245 + 'grok-3-mini-fast-beta',
5667 7246 'grok-2',
5668 7247 'deepseek-chat',
5669 - 'claude-3-5-sonnet-20241022',
7248 + 'claude-sonnet-4-5-20250929',
7249 + 'claude-opus-4-1-20250805',
7250 + 'claude-haiku-4-5-20251001',
7251 + 'claude-opus-4-20250514',
7252 + 'claude-sonnet-4-20250514',
7253 + 'claude-3-7-sonnet-20250219',
7254 + // REMOVED: 'claude-3-5-sonnet-20241022',
5670 7255 'claude-3-opus-20240229',
5671 7256 'claude-3-sonnet-20240229',
5672 7257 'claude-3-haiku-20240307',
7258 + 'gpt-5.2',
7259 + 'gpt-5.1-2025-11-13',
7260 + 'gpt-5',
7261 + 'gpt-5-mini',
7262 + 'gpt-5-nano',
7263 + 'gpt-4.1-2025-04-14',
5673 7264 'gpt-4o',
5674 7265 'gpt-4o-mini',
5675 7266 'gpt-4-turbo',
5676 7267 'gpt-4',
@@ -5675,13 +7266,33 @@
5675 7266 'gpt-4-turbo',
5676 7267 'gpt-4',
5677 7268 'gpt-3.5-turbo',
5678 7269 );
7270 +
5679 7271 if (in_array($input['model'], $allowed_models)) {
5680 7272 $new_input['model'] = sanitize_text_field($input['model']);
7273 + } else {
7274 + // Fallback for any deprecated model
7275 + $new_input['model'] = 'claude-3-7-sonnet-20250219';
5681 7276 }
5682 7277 }
7278 +}
7279 +
7280 +if (isset($input['openrouter_selected_model'])) {
7281 + // Just sanitize it, don't validate against a whitelist
7282 + $new_input['openrouter_selected_model'] = sanitize_text_field($input['openrouter_selected_model']);
7283 +}
5683 7284
7285 +// ADD THIS:
7286 +if (isset($input['openrouter_selected_model_name'])) {
7287 + $new_input['openrouter_selected_model_name'] = sanitize_text_field($input['openrouter_selected_model_name']);
7288 +}
7289 +
7290 + if (isset($input['openrouter_api_key'])) {
7291 + $new_input['openrouter_api_key'] = sanitize_text_field($input['openrouter_api_key']);
7292 + }
7293 +
7294 +
5684 7295 if (isset($input['close_button_color'])) {
5685 7296 $new_input['close_button_color'] = sanitize_hex_color($input['close_button_color']);
5686 7297 }
5687 7298
@@ -5736,8 +7347,12 @@
5736 7347 if (isset($input['top_bar_bg_color'])) {
5737 7348 $new_input['top_bar_bg_color'] = sanitize_hex_color($input['top_bar_bg_color']);
5738 7349 }
5739 7350
7351 + if (isset($input['quick_questions_toggle_color'])) {
7352 + $new_input['quick_questions_toggle_color'] = sanitize_hex_color($input['quick_questions_toggle_color']);
7353 + }
7354 +
5740 7355 if (isset($input['send_button_font_color'])) {
5741 7356 $new_input['send_button_font_color'] = sanitize_hex_color($input['send_button_font_color']);
5742 7357 }
5743 7358
@@ -5795,15 +7410,14 @@
5795 7410 if (isset($input['loops_mailing_list'])) {
5796 7411 $new_input['loops_mailing_list'] = sanitize_text_field($input['loops_mailing_list']);
5797 7412 }
5798 7413
5799 - // Sanitize Triggered Phrase Response
7414 +// Sanitize Triggered Phrase Response
5800 7415 if (isset($input['triggered_phrase_response'])) {
5801 7416 $new_input['triggered_phrase_response'] = wp_kses_post($input['triggered_phrase_response']);
5802 7417 }
5803 -
5804 7418 if (isset($input['email_capture_response'])) {
5805 - $new_input['email_capture_response'] = sanitize_textarea_field($input['email_capture_response']);
7419 + $new_input['email_capture_response'] = wp_kses_post($input['email_capture_response']);
5806 7420 }
5807 7421
5808 7422 // Sanitize Brave Search Settings
5809 7423 if (isset($input['brave_api_key'])) {
@@ -5836,8 +7450,22 @@
5836 7450 if (isset($input['chat_toolbar_toggle'])) {
5837 7451 $new_input['chat_toolbar_toggle'] = $input['chat_toolbar_toggle'] === 'on' ? 'on' : 'off';
5838 7452 }
5839 7453
7454 + // Sanitize PDF upload button toggle
7455 + if (isset($input['show_pdf_upload_button'])) {
7456 + $new_input['show_pdf_upload_button'] = $input['show_pdf_upload_button'] === 'on' ? 'on' : 'off';
7457 + } else {
7458 + $new_input['show_pdf_upload_button'] = 'off'; // If checkbox is unchecked
7459 + }
7460 +
7461 + // Sanitize Word upload button toggle
7462 + if (isset($input['show_word_upload_button'])) {
7463 + $new_input['show_word_upload_button'] = $input['show_word_upload_button'] === 'on' ? 'on' : 'off';
7464 + } else {
7465 + $new_input['show_word_upload_button'] = 'off'; // If checkbox is unchecked
7466 + }
7467 +
5840 7468 if (isset($input['pdf_intent_trigger_text'])) {
5841 7469 $new_input['pdf_intent_trigger_text'] = sanitize_text_field($input['pdf_intent_trigger_text']);
5842 7470 }
5843 7471
@@ -5867,8 +7495,12 @@
5867 7495 if (isset($input['live_agent_bot_token'])) {
5868 7496 $new_input['live_agent_bot_token'] = sanitize_text_field($input['live_agent_bot_token']);
5869 7497 }
5870 7498
7499 + if (isset($input['live_agent_user_ids'])) {
7500 + $new_input['live_agent_user_ids'] = sanitize_textarea_field($input['live_agent_user_ids']);
7501 + }
7502 +
5871 7503 if (isset($input['live_agent_status'])) {
5872 7504 $new_input['live_agent_status'] = ($input['live_agent_status'] === 'on') ? 'on' : 'off';
5873 7505 }
5874 7506 if (isset($input['live_agent_away_message'])) {
@@ -5877,8 +7509,28 @@
5877 7509 if (isset($input['live_agent_notification_message'])) {
5878 7510 $new_input['live_agent_notification_message'] = sanitize_textarea_field($input['live_agent_notification_message']);
5879 7511 }
5880 7512
7513 + // Telegram Integration
7514 + if (isset($input['telegram_status'])) {
7515 + $new_input['telegram_status'] = ($input['telegram_status'] === 'on') ? 'on' : 'off';
7516 + }
7517 + if (isset($input['telegram_bot_token'])) {
7518 + $new_input['telegram_bot_token'] = sanitize_text_field($input['telegram_bot_token']);
7519 + }
7520 + if (isset($input['telegram_group_id'])) {
7521 + $new_input['telegram_group_id'] = sanitize_text_field($input['telegram_group_id']);
7522 + }
7523 + if (isset($input['telegram_webhook_secret'])) {
7524 + $new_input['telegram_webhook_secret'] = sanitize_text_field($input['telegram_webhook_secret']);
7525 + }
7526 + if (isset($input['telegram_notification_message'])) {
7527 + $new_input['telegram_notification_message'] = sanitize_textarea_field($input['telegram_notification_message']);
7528 + }
7529 + if (isset($input['telegram_away_message'])) {
7530 + $new_input['telegram_away_message'] = sanitize_textarea_field($input['telegram_away_message']);
7531 + }
7532 +
5881 7533 return $new_input;
5882 7534 }
5883 7535
5884 7536
@@ -5891,62 +7543,10 @@
5891 7543 }
5892 7544
5893 7545
5894 7546
5895 -private static function mxchat_extract_main_content($html) {
5896 - if (empty($html)) {
5897 - //error_log('mxchat_extract_main_content: Empty HTML content received');
5898 - return '';
5899 - }
5900 7547
5901 - try {
5902 - $dom = new DOMDocument;
5903 - libxml_use_internal_errors(true); // Suppress HTML parsing errors
5904 7548
5905 - // Simple load of HTML, with @ to suppress warnings
5906 - @$dom->loadHTML($html, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
5907 -
5908 - $xpath = new DOMXPath($dom);
5909 -
5910 - // Simplified selectors focusing on common content areas
5911 - $selectors = [
5912 - '//article',
5913 - '//*[@id="content"]',
5914 - '//*[@class="entry-content"]',
5915 - '//main'
5916 - ];
5917 -
5918 - foreach ($selectors as $selector) {
5919 - $nodes = $xpath->query($selector);
5920 - if ($nodes && $nodes->length > 0) {
5921 - $content = '';
5922 - foreach ($nodes as $node) {
5923 - $content .= $dom->saveHTML($node);
5924 - }
5925 - if (!empty($content)) {
5926 - return $content;
5927 - }
5928 - }
5929 - }
5930 -
5931 - // Fallback: Return the entire body content if no specific selector matches
5932 - $body = $dom->getElementsByTagName('body');
5933 - if ($body->length > 0) {
5934 - return $dom->saveHTML($body->item(0));
5935 - }
5936 -
5937 - // Last resort: return the original HTML
5938 - return $html;
5939 -
5940 - } catch (Exception $e) {
5941 - //error_log('mxchat_extract_main_content error: ' . $e->getMessage());
5942 - return $html; // Return original HTML if parsing fails
5943 - } finally {
5944 - libxml_clear_errors();
5945 - }
5946 -}
5947 -
5948 -
5949 7549 private function mxchat_fetch_loops_mailing_lists($api_key) {
5950 7550 $url = 'https://app.loops.so/api/v1/lists';
5951 7551 $response = wp_remote_get($url, array(
5952 7552 'headers' => array(
@@ -5964,12 +7564,8 @@
5964 7564
5965 7565 return isset($lists) && is_array($lists) ? $lists : array();
5966 7566 }
5967 7567
5968 -
5969 -
5970 -
5971 -
5972 7568 function mxchat_calculate_cosine_similarity($vec1, $vec2) {
5973 7569 if (empty($vec1) || empty($vec2)) {
5974 7570 return 0.0;
5975 7571 }
@@ -5990,8 +7586,441 @@
5990 7586 return $dot_product / (sqrt($norm_a) * sqrt($norm_b));
5991 7587 }
5992 7588 }
5993 7589
7590 +/**
7591 + * Validates nonce and deletes all chat prompts
7592 + */
7593 +public function mxchat_handle_delete_all_prompts() {
7594 + //error_log('=== DELETE ALL DEBUG START ===');
7595 +
7596 + // Verify nonce
7597 + if (!isset($_POST['mxchat_delete_all_prompts_nonce']) || !wp_verify_nonce($_POST['mxchat_delete_all_prompts_nonce'], 'mxchat_delete_all_prompts_action')) {
7598 + //error_log('DEBUG: Nonce verification failed');
7599 + wp_die(__('Nonce verification failed.', 'mxchat'));
7600 + }
7601 +
7602 + // Check permissions
7603 + if (!current_user_can('manage_options')) {
7604 + //error_log('DEBUG: Permission check failed');
7605 + wp_die(__('You do not have sufficient permissions to delete all prompts.', 'mxchat'));
7606 + }
7607 +
7608 + // Get bot_id from POST data
7609 + $bot_id = isset($_POST['bot_id']) ? sanitize_text_field($_POST['bot_id']) : 'default';
7610 + //error_log('DEBUG: Bot ID from POST: ' . $bot_id);
7611 +
7612 + $success = true;
7613 + $error_messages = array();
7614 +
7615 + // Get bot-specific Pinecone configuration
7616 + $pinecone_manager = MxChat_Pinecone_Manager::get_instance();
7617 + $pinecone_options = $pinecone_manager->mxchat_get_bot_pinecone_options($bot_id);
7618 +
7619 + //error_log('DEBUG: Pinecone options retrieved:');
7620 + //error_log(' - Use Pinecone: ' . ($pinecone_options['mxchat_use_pinecone'] ?? 'NOT SET'));
7621 + //error_log(' - API Key: ' . (empty($pinecone_options['mxchat_pinecone_api_key']) ? 'EMPTY' : 'SET'));
7622 + //error_log(' - Host: ' . ($pinecone_options['mxchat_pinecone_host'] ?? 'NOT SET'));
7623 +
7624 + $use_pinecone = ($pinecone_options['mxchat_use_pinecone'] ?? '0') === '1';
7625 +
7626 + if ($use_pinecone && !empty($pinecone_options['mxchat_pinecone_api_key'])) {
7627 + //error_log('[MXCHAT-DELETE] Pinecone is enabled for bot ' . $bot_id . ' - deleting all from Pinecone');
7628 +
7629 + // Delete from bot-specific Pinecone index
7630 + $result = $pinecone_manager->mxchat_delete_all_from_pinecone($pinecone_options);
7631 +
7632 + //error_log('DEBUG: Delete all result: ' . ($result['success'] ? 'SUCCESS' : 'FAILED'));
7633 + if (!$result['success']) {
7634 + //error_log('DEBUG: Delete all error: ' . $result['message']);
7635 + }
7636 +
7637 + if (!$result['success']) {
7638 + $success = false;
7639 + $error_messages[] = $result['message'];
7640 + }
7641 +
7642 + // No cache clearing needed since we removed caching
7643 +
7644 + } else {
7645 + //error_log('[MXCHAT-DELETE] Pinecone not enabled for bot ' . $bot_id . ' - deleting from WordPress database');
7646 +
7647 + // Delete from WordPress database
7648 + global $wpdb;
7649 + $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
7650 +
7651 + // If multi-bot is active and we have a specific bot, delete only that bot's content
7652 + if ($bot_id !== 'default' && class_exists('MxChat_Multi_Bot_Manager')) {
7653 + $result = $wpdb->delete(
7654 + $table_name,
7655 + array('bot_id' => $bot_id),
7656 + array('%s')
7657 + );
7658 + } else {
7659 + // Delete all content for default bot
7660 + $result = $wpdb->query("DELETE FROM {$table_name}");
7661 + }
7662 +
7663 + if ($result === false) {
7664 + $success = false;
7665 + $error_messages[] = 'Failed to delete from WordPress database';
7666 + }
7667 + }
7668 +
7669 + // No cache clearing needed since we removed caching
7670 +
7671 + //error_log('=== DELETE ALL DEBUG END ===');
7672 +
7673 + // Redirect back with a success message and bot_id
7674 + $redirect_url = add_query_arg(array(
7675 + 'page' => 'mxchat-prompts',
7676 + 'bot_id' => $bot_id,
7677 + 'all_deleted' => $success ? 'true' : 'false'
7678 + ), admin_url('admin.php'));
7679 +
7680 + wp_safe_redirect($redirect_url);
7681 + exit;
7682 +}
7683 +
7684 +
7685 +
7686 + /**
7687 + * Handles deletion prompt with nonce validation
7688 + */
7689 + public function mxchat_handle_delete_prompt() {
7690 + // Sanitize and validate nonce
7691 + $nonce = isset($_GET['_wpnonce']) ? sanitize_text_field(wp_unslash($_GET['_wpnonce'])) : '';
7692 + if (empty($nonce) || !wp_verify_nonce($nonce, 'mxchat_delete_prompt_nonce')) {
7693 + wp_die(esc_html__('Nonce verification failed.', 'mxchat'));
7694 + }
7695 +
7696 + // Check permissions
7697 + if (!current_user_can('manage_options')) {
7698 + wp_die(esc_html__('You do not have sufficient permissions to delete prompts.', 'mxchat'));
7699 + }
7700 +
7701 + // Get ID and source parameters
7702 + $id = isset($_GET['id']) ? sanitize_text_field($_GET['id']) : '';
7703 + $source = isset($_GET['source']) ? sanitize_text_field($_GET['source']) : '';
7704 +
7705 + if (empty($id)) {
7706 + wp_die(esc_html__('Invalid prompt ID.', 'mxchat'));
7707 + }
7708 +
7709 + $success = false;
7710 + $error_message = '';
7711 +
7712 + // Check if Pinecone is enabled and determine source automatically if not specified
7713 + $pinecone_options = get_option('mxchat_pinecone_addon_options', array());
7714 + $use_pinecone = ($pinecone_options['mxchat_use_pinecone'] ?? '0') === '1';
7715 +
7716 + // If source is not specified, determine based on Pinecone configuration
7717 + if (empty($source)) {
7718 + $source = $use_pinecone ? 'pinecone' : 'wordpress';
7719 + }
7720 +
7721 + if ($source === 'pinecone' || $use_pinecone) {
7722 + //error_log('[MXCHAT-DELETE] Deleting from Pinecone, ID: ' . $id);
7723 +
7724 + // Handle Pinecone deletion
7725 + if (empty($pinecone_options['mxchat_pinecone_host']) ||
7726 + empty($pinecone_options['mxchat_pinecone_api_key'])) {
7727 + wp_die(esc_html__('Pinecone configuration is missing.', 'mxchat'));
7728 + }
7729 +
7730 + // Delete from Pinecone using the vector ID directly
7731 + $pinecone_manager = MxChat_Pinecone_Manager::get_instance();
7732 + $result = $pinecone_manager->mxchat_delete_from_pinecone_by_vector_id(
7733 + $id,
7734 + $pinecone_options['mxchat_pinecone_api_key'],
7735 + $pinecone_options['mxchat_pinecone_host']
7736 + );
7737 + if ($result['success']) {
7738 + $success = true;
7739 +
7740 + // Remove from vector cache
7741 + $pinecone_manager->mxchat_remove_from_pinecone_vector_cache($id);
7742 + $pinecone_manager->mxchat_remove_from_processed_content_caches($id);
7743 +
7744 + set_transient('mxchat_admin_notice_success',
7745 + esc_html__('Vector deleted successfully from Pinecone.', 'mxchat'), 30);
7746 + } else {
7747 + $error_message = $result['message'];
7748 + set_transient('mxchat_admin_notice_error',
7749 + esc_html__('Failed to delete from Pinecone: ', 'mxchat') . esc_html($error_message), 30);
7750 + }
7751 + } else {
7752 + //error_log('[MXCHAT-DELETE] Deleting from WordPress database, ID: ' . $id);
7753 +
7754 + // Handle WordPress database deletion
7755 + global $wpdb;
7756 + $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
7757 +
7758 + // Clear cache and delete prompt
7759 + wp_cache_delete('prompt_' . $id, 'mxchat_prompts');
7760 +
7761 + $result = $wpdb->delete(
7762 + $table_name,
7763 + array('id' => intval($id)),
7764 + array('%d')
7765 + );
7766 +
7767 + if ($result !== false) {
7768 + $success = true;
7769 + set_transient('mxchat_admin_notice_success',
7770 + esc_html__('Entry deleted successfully.', 'mxchat'), 30);
7771 + } else {
7772 + set_transient('mxchat_admin_notice_error',
7773 + esc_html__('Failed to delete entry from database.', 'mxchat'), 30);
7774 + }
7775 + }
7776 +
7777 + // Redirect back to the prompts page
7778 + wp_safe_redirect(add_query_arg(
7779 + array(
7780 + 'page' => 'mxchat-prompts',
7781 + 'deleted' => $success ? 'true' : 'false'
7782 + ),
7783 + admin_url('admin.php')
7784 + ));
7785 + exit;
7786 + }
7787 +
7788 +
7789 +
7790 +
7791 + /**
7792 + * Averages multiple vectors into a single vector
7793 + */
7794 + private function mxchat_average_vectors($vectors) {
7795 + $vector_length = count($vectors[0]);
7796 + $sum_vector = array_fill(0, $vector_length, 0);
7797 +
7798 + foreach ($vectors as $vector) {
7799 + for ($i = 0; $i < $vector_length; $i++) {
7800 + $sum_vector[$i] += $vector[$i];
7801 + }
7802 + }
7803 +
7804 + // Divide each component by the number of vectors to get the average
7805 + $num_vectors = count($vectors);
7806 + for ($i = 0; $i < $vector_length; $i++) {
7807 + $sum_vector[$i] /= $num_vectors;
7808 + }
7809 +
7810 + return $sum_vector;
7811 + }
7812 +
7813 +
7814 +
7815 +
7816 + /**
7817 + * Generates embeddings from input text for MXChat
7818 + */
7819 + public function mxchat_generate_embedding($text) {
7820 + // Enable detailed logging for debugging
7821 + //error_log('[MXCHAT-EMBED] Starting embedding generation. Text length: ' . strlen($text) . ' bytes');
7822 + //error_log('[MXCHAT-EMBED] Text preview: ' . substr($text, 0, 100) . '...');
7823 +
7824 + $options = get_option('mxchat_options');
7825 + $selected_model = $options['embedding_model'] ?? 'text-embedding-ada-002';
7826 + //error_log('[MXCHAT-EMBED] Selected embedding model: ' . $selected_model);
7827 +
7828 + // Determine provider and endpoint
7829 + if (strpos($selected_model, 'voyage') === 0) {
7830 + $api_key = $options['voyage_api_key'] ?? '';
7831 + $endpoint = 'https://api.voyageai.com/v1/embeddings';
7832 + $provider_name = 'Voyage AI';
7833 + //error_log('[MXCHAT-EMBED] Using Voyage AI API');
7834 + } elseif (strpos($selected_model, 'gemini-embedding') === 0) {
7835 + $api_key = $options['gemini_api_key'] ?? '';
7836 + $endpoint = 'https://generativelanguage.googleapis.com/v1beta/models/' . $selected_model . ':embedContent';
7837 + $provider_name = 'Google Gemini';
7838 + //error_log('[MXCHAT-EMBED] Using Google Gemini API');
7839 + } else {
7840 + $api_key = $options['api_key'] ?? '';
7841 + $endpoint = 'https://api.openai.com/v1/embeddings';
7842 + $provider_name = 'OpenAI';
7843 + //error_log('[MXCHAT-EMBED] Using OpenAI API');
7844 + }
7845 +
7846 + //error_log('[MXCHAT-EMBED] Using endpoint: ' . $endpoint);
7847 +
7848 + if (empty($api_key)) {
7849 + $error_message = sprintf('Missing %s API key. Please configure your API key in the MxChat settings.', $provider_name);
7850 + //error_log('[MXCHAT-EMBED] Error: ' . $error_message);
7851 + return $error_message;
7852 + }
7853 +
7854 + // Check if text is too long (for OpenAI, roughly estimate tokens as words/0.75)
7855 + $estimated_tokens = ceil(str_word_count($text) / 0.75);
7856 + //error_log('[MXCHAT-EMBED] Estimated token count: ~' . $estimated_tokens);
7857 +
7858 + if ($estimated_tokens > 8000 && strpos($selected_model, 'voyage') === false && strpos($selected_model, 'gemini-embedding') === false) {
7859 + //error_log('[MXCHAT-EMBED] Warning: Text may exceed OpenAI token limits (8K for most models)');
7860 + // Consider truncating text here
7861 + }
7862 +
7863 + // Prepare request body based on provider
7864 + if (strpos($selected_model, 'gemini-embedding') === 0) {
7865 + // Gemini API format
7866 + $request_body = array(
7867 + 'model' => 'models/' . $selected_model,
7868 + 'content' => array(
7869 + 'parts' => array(
7870 + array('text' => $text)
7871 + )
7872 + )
7873 + );
7874 +
7875 + // Set output dimensionality to 1536 for consistency with other models
7876 + $request_body['outputDimensionality'] = 1536;
7877 + } else {
7878 + // OpenAI/Voyage API format
7879 + $request_body = array(
7880 + 'model' => $selected_model,
7881 + 'input' => $text
7882 + );
7883 +
7884 + // Add output_dimension for voyage-3-large model
7885 + if ($selected_model === 'voyage-3-large') {
7886 + $request_body['output_dimension'] = 2048;
7887 + }
7888 + }
7889 +
7890 + //error_log('[MXCHAT-EMBED] Request prepared with model: ' . $selected_model);
7891 +
7892 + // Prepare headers based on provider
7893 + if (strpos($selected_model, 'gemini-embedding') === 0) {
7894 + // Gemini uses API key as query parameter
7895 + $endpoint .= '?key=' . $api_key;
7896 + $headers = array(
7897 + 'Content-Type' => 'application/json'
7898 + );
7899 + } else {
7900 + // OpenAI/Voyage use Bearer token
7901 + $headers = array(
7902 + 'Authorization' => 'Bearer ' . $api_key,
7903 + 'Content-Type' => 'application/json'
7904 + );
7905 + }
7906 +
7907 + // Make API request
7908 + //error_log('[MXCHAT-EMBED] Sending API request to: ' . $endpoint);
7909 + $response = wp_remote_post($endpoint, array(
7910 + 'body' => wp_json_encode($request_body),
7911 + 'headers' => $headers,
7912 + 'timeout' => 60 // Increased timeout for large inputs
7913 + ));
7914 +
7915 + // Handle wp_remote_post errors
7916 + if (is_wp_error($response)) {
7917 + $error_message = $response->get_error_message();
7918 + //error_log('[MXCHAT-EMBED] WP Remote Post Error: ' . $error_message);
7919 + return 'Connection error: ' . $error_message;
7920 + }
7921 +
7922 + // Get and check HTTP response code
7923 + $http_code = wp_remote_retrieve_response_code($response);
7924 + //error_log('[MXCHAT-EMBED] API Response Code: ' . $http_code);
7925 +
7926 + if ($http_code !== 200) {
7927 + $error_body = wp_remote_retrieve_body($response);
7928 + //error_log('[MXCHAT-EMBED] API Error Response Body: ' . $error_body);
7929 +
7930 + // Try to parse error for more details
7931 + $error_json = json_decode($error_body, true);
7932 + if (json_last_error() === JSON_ERROR_NONE && isset($error_json['error'])) {
7933 + $error_type = $error_json['error']['type'] ?? 'unknown';
7934 + $error_message = $error_json['error']['message'] ?? 'No message';
7935 + //error_log('[MXCHAT-EMBED] API Error Type: ' . $error_type);
7936 + //error_log('[MXCHAT-EMBED] API Error Message: ' . $error_message);
7937 +
7938 + // Customize error message for common API errors
7939 + if ($error_type === 'invalid_request_error' && strpos($error_message, 'API key') !== false) {
7940 + $error_message = sprintf('Invalid %s API key. Please check your API key in the MxChat settings.', $provider_name);
7941 + } elseif ($error_type === 'authentication_error') {
7942 + $error_message = sprintf('%s authentication failed. Please verify your API key in the MxChat settings.', $provider_name);
7943 + }
7944 +
7945 + //error_log('[MXCHAT-EMBED] Returning error: ' . $error_message);
7946 + return $error_message;
7947 + }
7948 +
7949 + $error_message = sprintf("API Error (HTTP %d): Unable to generate embedding", $http_code);
7950 + //error_log('[MXCHAT-EMBED] Returning error: ' . $error_message);
7951 + return $error_message;
7952 + }
7953 +
7954 + // Parse response body
7955 + $response_body = wp_remote_retrieve_body($response);
7956 + //error_log('[MXCHAT-EMBED] Received response length: ' . strlen($response_body) . ' bytes');
7957 +
7958 + $response_data = json_decode($response_body, true);
7959 +
7960 + if (json_last_error() !== JSON_ERROR_NONE) {
7961 + $error = json_last_error_msg();
7962 + //error_log('[MXCHAT-EMBED] JSON Parse Error: ' . $error);
7963 + //error_log('[MXCHAT-EMBED] Response preview: ' . substr($response_body, 0, 200));
7964 + return "Failed to parse API response: $error";
7965 + }
7966 +
7967 + // Handle different response formats based on provider
7968 + if (strpos($selected_model, 'gemini-embedding') === 0) {
7969 + // Gemini API response format
7970 + if (isset($response_data['embedding']['values'])) {
7971 + $embedding_dimensions = count($response_data['embedding']['values']);
7972 + //error_log('[MXCHAT-EMBED] Successfully extracted Gemini embedding with ' . $embedding_dimensions . ' dimensions');
7973 +
7974 + // Check if embedding dimensions are as expected (should be 1536)
7975 + if ($embedding_dimensions !== 1536) {
7976 + //error_log('[MXCHAT-EMBED] Warning: Unexpected Gemini embedding dimensions: ' . $embedding_dimensions);
7977 + }
7978 +
7979 + return $response_data['embedding']['values'];
7980 + } else {
7981 + //error_log('[MXCHAT-EMBED] Error: No embedding found in Gemini response');
7982 + //error_log('[MXCHAT-EMBED] Response structure: ' . wp_json_encode(array_keys($response_data)));
7983 +
7984 + if (isset($response_data['error'])) {
7985 + $error_message = "Gemini API Error in response: " . wp_json_encode($response_data['error']);
7986 + //error_log('[MXCHAT-EMBED] ' . $error_message);
7987 + return $error_message;
7988 + }
7989 +
7990 + $error_message = "Invalid Gemini API response format: No embedding found";
7991 + //error_log('[MXCHAT-EMBED] ' . $error_message);
7992 + return $error_message;
7993 + }
7994 + } else {
7995 + // OpenAI/Voyage API response format
7996 + if (isset($response_data['data'][0]['embedding'])) {
7997 + $embedding_dimensions = count($response_data['data'][0]['embedding']);
7998 + //error_log('[MXCHAT-EMBED] Successfully extracted embedding with ' . $embedding_dimensions . ' dimensions');
7999 +
8000 + // Check if embedding dimensions are as expected
8001 + if (($selected_model === 'text-embedding-ada-002' && $embedding_dimensions !== 1536) ||
8002 + ($selected_model === 'voyage-3-large' && $embedding_dimensions !== 2048)) {
8003 + //error_log('[MXCHAT-EMBED] Warning: Unexpected embedding dimensions');
8004 + }
8005 +
8006 + return $response_data['data'][0]['embedding'];
8007 + } else {
8008 + //error_log('[MXCHAT-EMBED] Error: No embedding found in response');
8009 + //error_log('[MXCHAT-EMBED] Response structure: ' . wp_json_encode(array_keys($response_data)));
8010 +
8011 + if (isset($response_data['error'])) {
8012 + $error_message = "API Error in response: " . wp_json_encode($response_data['error']);
8013 + //error_log('[MXCHAT-EMBED] ' . $error_message);
8014 + return $error_message;
8015 + }
8016 +
8017 + $error_message = "Invalid API response format: No embedding found";
8018 + //error_log('[MXCHAT-EMBED] ' . $error_message);
8019 + return $error_message;
8020 + }
8021 + }
8022 + }
5994 8023
5995 8024
5996 8025
5997 8026 }