PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 3.0.7
MxChat – AI Chatbot & Content Generation for WordPress v3.0.7
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 +5632 -3208 2.0.63.0.7 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,63 @@
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'));
86 +
87 + // Translation handlers
88 + add_action('wp_ajax_mxchat_translate_messages', array($this, 'mxchat_translate_messages'));
89 + add_action('wp_ajax_mxchat_get_transcript_translation', array($this, 'mxchat_get_transcript_translation'));
60 90 }
61 91
62 - // Method to check if the license is active
63 92 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 - }
93 + // Get the raw value without translation
94 + $license_status = get_option('mxchat_license_status', 'inactive');
67 95
68 - // Initialize default options
96 + // Check against multiple possible values, bypassing translation issues
97 + return ($license_status === 'active' || $license_status === esc_html__('active', 'mxchat'));
98 +}
99 +
69 100 private function initialize_default_options() {
70 101 $default_options = array(
71 102 'api_key' => '',
72 103 'xai_api_key' => '',
@@ -72,26 +103,50 @@
72 103 'xai_api_key' => '',
73 104 'claude_api_key' => '',
74 105 'deepseek_api_key' => '',
75 106 'voyage_api_key' => '',
107 + 'gemini_api_key' => '',
108 + 'enable_streaming_toggle' => 'on',
109 + 'enable_web_search' => 'off',
76 110 'embedding_model' => 'text-embedding-ada-002',
77 - '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:
78 - - Your name is [Chatbot Name].
79 - - 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.
80 - - 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.
81 - - Keep your responses short, concise, and to the point. Provide clear and direct answers suitable for a chatbot interaction.
82 - - 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'),
83 - 'model' => esc_html__('gpt-4o', 'mxchat'),
111 + '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:
112 +
113 + # Response Style - CRITICALLY IMPORTANT
114 + - MAXIMUM LENGTH: 1-3 short sentences per response
115 + - Ultra-concise: Get straight to the answer with no filler
116 + - No introductions like "Sure!" or "I\'d be happy to help"
117 + - No phrases like "based on my knowledge" or "according to information"
118 + - No explanatory text before giving the answer
119 + - No summaries or repetition
120 + - Hyperlink all URLs
121 + - Respond in user\'s language
122 + - Minor chit chat or conversation is okay, but try to keep it focused on [insert topic]
123 +
124 + # Knowledge Base Requirements - PREVENT HALLUCINATIONS
125 + - ONLY answer questions using information explicitly provided in OFFICIAL KNOWLEDGE DATABASE CONTENT sections marked with ===== delimiters
126 + - If required information is NOT in the knowledge database: "I don\'t have enough information in my knowledge base to answer that question accurately."
127 + - NEVER invent or hallucinate URLs, links, product specs, procedures, dates, statistics, names, contacts, or company information
128 + - When knowledge base information is unclear or contradictory, acknowledge the limitation rather than guessing
129 + - Better to admit insufficient information than provide inaccurate answers',
130 + 'model' => esc_html__('gpt-5.1-chat-latest', 'mxchat'),
84 131 'rate_limit_logged_out' => esc_html__('100', 'mxchat'),
85 132 'role_rate_limits' => array(),
86 133 'rate_limit_message' => esc_html__('Rate limit exceeded. Please try again later.', 'mxchat'),
87 134 'enable_email_block' => '',
88 - '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'),
135 + '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'),
89 136 'email_blocker_button_text' => esc_html__('Start Chat', 'mxchat'),
137 + 'enable_name_field' => 'off', // NEW
138 + 'name_field_placeholder' => esc_html__('Enter your name', 'mxchat'), // NEW
90 139 'top_bar_title' => esc_html__('MxChat', 'mxchat'),
91 - 'intro_message' => esc_html__('Hello! How can I assist you today?', 'mxchat'),
140 + 'intro_message' => __('Hello! How can I assist you today?', 'mxchat'),
141 + 'ai_agent_text' => esc_html__('AI Agent', 'mxchat'),
92 142 'input_copy' => esc_html__('How can I assist?', 'mxchat'),
93 143 'append_to_body' => esc_html__('off', 'mxchat'),
144 + 'post_type_visibility_mode' => 'all', // 'all', 'include', 'exclude'
145 + 'post_type_visibility_list' => array(), // Array of post type slugs
146 + 'contextual_awareness_toggle' => 'off',
147 + 'citation_links_toggle' => 'on',
148 + 'show_frontend_debugger' => 'on',
94 149 'close_button_color' => esc_html__('#fff', 'mxchat'),
95 150 'chatbot_bg_color' => esc_html__('#fff', 'mxchat'),
96 151 'user_message_bg_color' => esc_html__('#fff', 'mxchat'),
97 152 'user_message_font_color' => esc_html__('#212121', 'mxchat'),
@@ -108,28 +163,42 @@
108 163
109 164 // New fields for Loops Integration
110 165 'loops_api_key' => '',
111 166 'loops_mailing_list' => '',
112 - 'triggered_phrase_response' => esc_html__('Would you like to join our mailing list? Please provide your email below.', 'mxchat'),
113 - 'email_capture_response' => esc_html__('Thank you for providing your email! You\'ve been added to our list.', 'mxchat'),
167 + 'triggered_phrase_response' => __('Would you like to join our mailing list? Please provide your email below.', 'mxchat'),
168 + 'email_capture_response' => __('Thank you for providing your email! You\'ve been added to our list.', 'mxchat'),
114 169 'popular_question_1' => '',
115 170 'popular_question_2' => '',
116 171 'popular_question_3' => '',
117 - 'pdf_intent_trigger_text' => esc_html__("Please provide the URL to the PDF you'd like to discuss.", 'mxchat'),
118 - 'pdf_intent_success_text' => esc_html__("I've processed the PDF. What questions do you have about it?", 'mxchat'),
119 - 'pdf_intent_error_text' => esc_html__("Sorry, I couldn't process the PDF. Please ensure it's a valid file.", 'mxchat'),
172 + 'pdf_intent_trigger_text' => __("Please provide the URL to the PDF you'd like to discuss.", 'mxchat'),
173 + 'pdf_intent_success_text' => __("I've processed the PDF. What questions do you have about it?", 'mxchat'),
174 + 'pdf_intent_error_text' => __("Sorry, I couldn't process the PDF. Please ensure it's a valid file.", 'mxchat'),
120 175 'pdf_max_pages' => 69,
176 + 'show_pdf_upload_button' => 'on',
177 + 'show_word_upload_button' => 'on',
121 178
122 - // Live Agent Integration
179 + // Live Agent Integration (Slack)
123 180 'live_agent_webhook_url' => '',
124 181 'live_agent_secret_key' => '',
125 182 'live_agent_bot_token' => '',
126 183 'live_agent_message_bg_color' => esc_html__('#ffffff', 'mxchat'),
127 184 'live_agent_message_font_color' => esc_html__('#333333', 'mxchat'),
185 +
186 + // Telegram Integration
187 + 'telegram_status' => 'off',
188 + 'telegram_bot_token' => '',
189 + 'telegram_group_id' => '',
190 + 'telegram_webhook_secret' => '',
191 + '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'),
192 + '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'),
193 +
128 194 'chat_toolbar_toggle' => esc_html__('off', 'mxchat'),
129 195 'mode_indicator_bg_color' => esc_html__('#767676', 'mxchat'),
130 196 'mode_indicator_font_color' => esc_html__('#ffffff', 'mxchat'),
131 197 'toolbar_icon_color' => esc_html__('#212121', 'mxchat'),
198 +
199 + // Optimization settings
200 + 'script_loading_strategy' => 'default',
132 201 );
133 202
134 203
135 204 // Merge existing options with defaults
@@ -152,10 +221,8 @@
152 221 // Update the $this->options property
153 222 $this->options = $merged_options;
154 223 }
155 224
156 -
157 -
158 225 public function mxchat_add_plugin_page() {
159 226 // Main menu page
160 227 add_menu_page(
161 228 esc_html__('MxChat Settings', 'mxchat'),
@@ -166,8 +233,18 @@
166 233 'dashicons-testimonial',
167 234 6
168 235 );
169 236
237 + // Rename the first submenu item from "MxChat" to "Settings"
238 + add_submenu_page(
239 + 'mxchat-max',
240 + esc_html__('MxChat Settings', 'mxchat'),
241 + esc_html__('Settings', 'mxchat'),
242 + 'manage_options',
243 + 'mxchat-max',
244 + array($this, 'mxchat_create_admin_page')
245 + );
246 +
170 247 // Submenu page for Knowledge
171 248 add_submenu_page(
172 249 'mxchat-max',
173 250 esc_html__('Prompts', 'mxchat'),
@@ -187,29 +264,20 @@
187 264 );
188 265
189 266 add_submenu_page(
190 267 'mxchat-max',
191 - esc_html__('MxChat Intents', 'mxchat'),
192 - esc_html__('Intents', 'mxchat'),
268 + esc_html__('MxChat Actions', 'mxchat'),
269 + esc_html__('Actions', 'mxchat'),
193 270 'manage_options',
194 - 'mxchat-intents',
195 - array($this, 'mxchat_intents_page_html')
271 + 'mxchat-actions',
272 + array($this, 'mxchat_actions_page_html')
196 273 );
197 274
198 - add_submenu_page(
199 - 'mxchat-max',
200 - esc_html__('Add Ons', 'mxchat'),
201 - esc_html__('Add Ons', 'mxchat'),
202 - 'manage_options',
203 - 'mxchat-addons',
204 - array($this, 'mxchat_create_addons_page')
205 - );
206 -
207 - // Submenu page for Activation Key
275 + // Consolidated Pro & Extensions page (replaces separate Add Ons and Pro Upgrade pages)
208 276 add_submenu_page(
209 277 'mxchat-max',
210 - esc_html__('Pro Upgrade', 'mxchat'),
211 - esc_html__('Pro Upgrade', 'mxchat'),
278 + esc_html__('Pro & Extensions', 'mxchat'),
279 + esc_html__('Pro & Extensions', 'mxchat'),
212 280 'manage_options',
213 281 'mxchat-activation',
214 282 array($this, 'mxchat_create_activation_page')
215 283 );
@@ -220,154 +288,525 @@
220 288 $addons_page = new MxChat_Addons();
221 289 $addons_page->render_page();
222 290 }
223 291
224 -public function mxchat_save_setting_callback() {
225 - check_ajax_referer('mxchat_save_setting_nonce');
226 - if (!current_user_can('manage_options')) {
227 - wp_send_json_error(['message' => esc_html__('Unauthorized', 'mxchat')]);
292 +/**
293 + * Test actual streaming functionality in the WordPress environment
294 + */
295 +public function mxchat_handle_test_streaming_actual() {
296 + check_ajax_referer('mxchat_test_streaming_nonce', 'nonce');
297 +
298 + // Check if headers have already been sent
299 + if (headers_sent()) {
300 + wp_send_json_error(['message' => 'Headers already sent - streaming not possible']);
301 + return;
228 302 }
229 - $name = isset($_POST['name']) ? $_POST['name'] : '';
230 - // Strip slashes from the value before saving
231 - $value = isset($_POST['value']) ? stripslashes($_POST['value']) : '';
232 - if (empty($name)) {
233 - wp_send_json_error(['message' => esc_html__('Invalid field name', 'mxchat')]);
303 +
304 + // Check for required functions
305 + if (!function_exists('curl_init')) {
306 + wp_send_json_error(['message' => 'cURL not available - streaming requires cURL']);
307 + return;
234 308 }
235 - // Load the full options array
309 +
310 + // Get user's selected model and API key
236 311 $options = get_option('mxchat_options', []);
237 - // Handle special cases
238 - switch ($name) {
239 - case 'additional_popular_questions':
240 - $questions = json_decode($value, true); // No need for stripslashes here
241 - if (is_array($questions)) {
242 - $options[$name] = $questions;
243 - // Also update old option for backwards compatibility
244 - update_option('additional_popular_questions', $questions);
245 - }
312 + $selected_model = $options['model'] ?? 'gpt-5.1-chat-latest';
313 +
314 + // Get the provider from the model
315 + $model_parts = explode('-', $selected_model);
316 + $provider = strtolower($model_parts[0]);
317 +
318 + // Get the appropriate API key
319 + $api_key = '';
320 + switch ($provider) {
321 + case 'gpt':
322 + case 'o1':
323 + $api_key = $options['api_key'] ?? '';
246 324 break;
247 - case 'user_message_bg_color':
248 - case 'user_message_font_color':
249 - case 'bot_message_bg_color':
250 - case 'bot_message_font_color':
251 - case 'top_bar_bg_color':
252 - case 'send_button_font_color':
253 - case 'chatbot_background_color':
254 - case 'icon_color':
255 - case 'chat_input_font_color':
256 - case 'live_agent_message_bg_color':
257 - case 'live_agent_message_font_color':
258 - case 'mode_indicator_bg_color':
259 - case 'mode_indicator_font_color':
260 - case 'toolbar_icon_color':
261 - // Store color values directly
262 - $options[$name] = $value;
325 + case 'claude':
326 + $api_key = $options['claude_api_key'] ?? '';
263 327 break;
264 - case 'live_agent_status':
265 - // Set the new value
266 - $options[$name] = ($value === 'on') ? 'on' : 'off';
328 + case 'grok':
329 + $api_key = $options['xai_api_key'] ?? '';
267 330 break;
268 - case 'enable_woocommerce_integration':
269 - // Handle values that used to be 1/0
270 - $options[$name] = ($value === 'on' || $value === '1') ? 'on' : 'off';
331 + case 'deepseek':
332 + $api_key = $options['deepseek_api_key'] ?? '';
271 333 break;
334 + case 'gemini':
335 + $api_key = $options['gemini_api_key'] ?? '';
336 + break;
272 337 default:
273 - // Handle role rate limits
274 - if (strpos($name, 'mxchat_options[role_rate_limits]') !== false) {
275 - // Extract role ID from the name
276 - preg_match('/\[role_rate_limits\]\[(.*?)\]/', $name, $matches);
277 - if (isset($matches[1])) {
278 - $role_id = $matches[1];
279 - // Initialize role_rate_limits if it doesn't exist
280 - if (!isset($options['role_rate_limits'])) {
281 - $options['role_rate_limits'] = [];
338 + // Default to OpenAI for unknown models
339 + $api_key = $options['api_key'] ?? '';
340 + $provider = 'gpt';
341 + break;
342 + }
343 +
344 + if (empty($api_key)) {
345 + wp_send_json_error(['message' => "API key not configured for {$provider} provider"]);
346 + return;
347 + }
348 +
349 + // Test streaming with the selected model and provider
350 + try {
351 + $this->perform_streaming_test($provider, $selected_model, $api_key);
352 + } catch (Exception $e) {
353 + wp_send_json_error(['message' => 'Streaming test exception: ' . $e->getMessage()]);
354 + }
355 +}
356 +
357 +/**
358 + * Perform the actual streaming test
359 + */
360 +private function perform_streaming_test($provider, $model, $api_key) {
361 + // Set streaming headers
362 + header('Content-Type: text/event-stream');
363 + header('Cache-Control: no-cache');
364 + header('X-Accel-Buffering: no'); // Disable nginx buffering
365 +
366 + // Prepare test message
367 + $test_message = "Please respond with exactly: 'Streaming test successful!' - send this as a short response for testing.";
368 +
369 + // Configure API request based on provider
370 + $url = '';
371 + $headers = [];
372 + $body = [];
373 +
374 + switch ($provider) {
375 + case 'gpt':
376 + case 'o1':
377 + $url = 'https://api.openai.com/v1/chat/completions';
378 + $headers = [
379 + 'Content-Type: application/json',
380 + 'Authorization: Bearer ' . $api_key
381 + ];
382 + $body = [
383 + 'model' => $model,
384 + 'messages' => [['role' => 'user', 'content' => $test_message]],
385 + 'max_tokens' => 50,
386 + 'temperature' => 0.3,
387 + 'stream' => true
388 + ];
389 + break;
390 +
391 + case 'claude':
392 + $url = 'https://api.anthropic.com/v1/messages';
393 + $headers = [
394 + 'Content-Type: application/json',
395 + 'x-api-key: ' . $api_key,
396 + 'anthropic-version: 2023-06-01'
397 + ];
398 + $body = [
399 + 'model' => $model,
400 + 'messages' => [['role' => 'user', 'content' => $test_message]],
401 + 'max_tokens' => 50,
402 + 'temperature' => 0.3,
403 + 'stream' => true
404 + ];
405 + break;
406 +
407 + case 'grok':
408 + $url = 'https://api.x.ai/v1/chat/completions';
409 + $headers = [
410 + 'Content-Type: application/json',
411 + 'Authorization: Bearer ' . $api_key
412 + ];
413 + $body = [
414 + 'model' => $model,
415 + 'messages' => [['role' => 'user', 'content' => $test_message]],
416 + 'max_tokens' => 50,
417 + 'temperature' => 0.3,
418 + 'stream' => true
419 + ];
420 + break;
421 +
422 + case 'deepseek':
423 + $url = 'https://api.deepseek.com/v1/chat/completions';
424 + $headers = [
425 + 'Content-Type: application/json',
426 + 'Authorization: Bearer ' . $api_key
427 + ];
428 + $body = [
429 + 'model' => $model,
430 + 'messages' => [['role' => 'user', 'content' => $test_message]],
431 + 'max_tokens' => 50,
432 + 'temperature' => 0.3,
433 + 'stream' => true
434 + ];
435 + break;
436 +
437 + default:
438 + echo "data: " . json_encode(['error' => 'Unsupported provider for streaming test: ' . $provider]) . "\n\n";
439 + flush();
440 + return;
441 + }
442 +
443 + // Initialize cURL
444 + $ch = curl_init();
445 + curl_setopt($ch, CURLOPT_URL, $url);
446 + curl_setopt($ch, CURLOPT_RETURNTRANSFER, false);
447 + curl_setopt($ch, CURLOPT_POST, true);
448 + curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
449 + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
450 + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
451 + curl_setopt($ch, CURLOPT_TIMEOUT, 30);
452 + curl_setopt($ch, CURLOPT_WRITEFUNCTION, function($ch, $data) use ($provider) {
453 + return $this->process_streaming_test_data($data, $provider);
454 + });
455 +
456 + // Send initial test message
457 + echo "data: " . json_encode(['content' => '[Starting streaming test...]']) . "\n\n";
458 + flush();
459 +
460 + $result = curl_exec($ch);
461 + $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
462 + $curl_error = curl_error($ch);
463 + curl_close($ch);
464 +
465 + if ($curl_error) {
466 + echo "data: " . json_encode(['error' => 'cURL Error: ' . $curl_error]) . "\n\n";
467 + flush();
468 + return;
469 + }
470 +
471 + if ($http_code !== 200) {
472 + echo "data: " . json_encode(['error' => 'API returned HTTP ' . $http_code]) . "\n\n";
473 + flush();
474 + return;
475 + }
476 +
477 + // Send completion signal
478 + echo "data: [DONE]\n\n";
479 + flush();
480 +}
481 +
482 +/**
483 + * Process streaming data for the test
484 + */
485 +private function process_streaming_test_data($data, $provider) {
486 + static $chunk_count = 0;
487 +
488 + $lines = explode("\n", $data);
489 +
490 + foreach ($lines as $line) {
491 + if (trim($line) === '') {
492 + continue;
493 + }
494 +
495 + // Handle different provider formats
496 + if ($provider === 'claude') {
497 + // Claude uses event: and data: format
498 + if (strpos($line, 'data: ') === 0) {
499 + $json_str = substr($line, 6);
500 + $json = json_decode($json_str, true);
501 +
502 + if (isset($json['type']) && $json['type'] === 'content_block_delta') {
503 + if (isset($json['delta']['text'])) {
504 + $chunk_count++;
505 + echo "data: " . json_encode([
506 + 'content' => $json['delta']['text'],
507 + 'test_chunk' => $chunk_count
508 + ]) . "\n\n";
509 + flush();
282 510 }
283 - // Update the specific role's rate limit
284 - $options['role_rate_limits'][$role_id] = sanitize_text_field($value);
285 - break;
286 511 }
287 512 }
288 - // Handle toggles
289 - if (strpos($name, 'toggle') !== false || in_array($name, [
290 - 'chat_persistence_toggle',
291 - 'privacy_toggle',
292 - 'complianz_toggle',
293 - 'chat_toolbar_toggle' // Removed live_agent_status from here
294 - ])) {
295 - $options[$name] = ($value === 'on') ? 'on' : 'off';
296 - } else {
297 - // Store all other values directly
298 - $options[$name] = $value;
513 + } else {
514 + // OpenAI, X.AI, DeepSeek format
515 + if (strpos($line, 'data: ') === 0) {
516 + $json_str = substr($line, 6);
517 +
518 + if ($json_str === '[DONE]') {
519 + // Don't echo [DONE] here, let the main function handle it
520 + continue;
521 + }
522 +
523 + $json = json_decode($json_str, true);
524 + if (isset($json['choices'][0]['delta']['content'])) {
525 + $chunk_count++;
526 + echo "data: " . json_encode([
527 + 'content' => $json['choices'][0]['delta']['content'],
528 + 'test_chunk' => $chunk_count
529 + ]) . "\n\n";
530 + flush();
531 + }
299 532 }
300 - break;
533 + }
301 534 }
302 - // Save all updates
303 - $updated = update_option('mxchat_options', $options);
304 - // Handle backwards compatibility for certain fields
305 - $legacy_fields = ['brave_api_key', 'brave_image_count', 'brave_safe_search', 'brave_news_count',
306 - 'brave_country', 'brave_language', 'similarity_threshold'];
307 - if (in_array($name, $legacy_fields)) {
308 - // Also update the individual option for backwards compatibility
309 - update_option($name, $value);
535 +
536 + return strlen($data);
537 +}
538 +
539 +/**
540 + * Updated version of your existing test method (keep this as a fallback)
541 + */
542 +public function mxchat_handle_test_streaming() {
543 + check_ajax_referer('mxchat_test_streaming_nonce', 'nonce');
544 +
545 + // Use the actual streaming test instead
546 + $this->mxchat_handle_test_streaming_actual();
547 +}
548 +
549 +
550 +public function register_pinecone_settings() {
551 + register_setting(
552 + 'mxchat_pinecone_addon_options',
553 + 'mxchat_pinecone_addon_options',
554 + array(
555 + 'type' => 'array',
556 + 'sanitize_callback' => array($this, 'sanitize_pinecone_settings'),
557 + 'default' => array(
558 + 'mxchat_use_pinecone' => '0',
559 + 'mxchat_pinecone_api_key' => '',
560 + 'mxchat_pinecone_host' => '',
561 + 'mxchat_pinecone_index' => '',
562 + 'mxchat_pinecone_environment' => ''
563 + )
564 + )
565 + );
566 +}
567 +
568 +public function sanitize_pinecone_settings($input) {
569 + $sanitized = array();
570 +
571 + $sanitized['mxchat_use_pinecone'] = isset($input['mxchat_use_pinecone']) ? '1' : '0';
572 + $sanitized['mxchat_pinecone_api_key'] = sanitize_text_field($input['mxchat_pinecone_api_key'] ?? '');
573 + $sanitized['mxchat_pinecone_host'] = sanitize_text_field($input['mxchat_pinecone_host'] ?? '');
574 + $sanitized['mxchat_pinecone_index'] = sanitize_text_field($input['mxchat_pinecone_index'] ?? '');
575 + $sanitized['mxchat_pinecone_environment'] = sanitize_text_field($input['mxchat_pinecone_environment'] ?? '');
576 +
577 + // Remove https:// from host if present
578 + $sanitized['mxchat_pinecone_host'] = str_replace(['https://', 'http://'], '', $sanitized['mxchat_pinecone_host']);
579 +
580 + return $sanitized;
581 +}
582 +
583 +public function register_openai_vectorstore_settings() {
584 + register_setting(
585 + 'mxchat_openai_vectorstore_options',
586 + 'mxchat_openai_vectorstore_options',
587 + array(
588 + 'type' => 'array',
589 + 'sanitize_callback' => array($this, 'sanitize_openai_vectorstore_settings'),
590 + 'default' => array(
591 + 'mxchat_use_openai_vectorstore' => '0',
592 + 'mxchat_vectorstore_ids' => '',
593 + 'mxchat_vectorstore_max_results' => 5
594 + )
595 + )
596 + );
597 +}
598 +
599 +public function sanitize_openai_vectorstore_settings($input) {
600 + $sanitized = array();
601 +
602 + $sanitized['mxchat_use_openai_vectorstore'] = isset($input['mxchat_use_openai_vectorstore']) ? '1' : '0';
603 + $sanitized['mxchat_vectorstore_ids'] = sanitize_text_field($input['mxchat_vectorstore_ids'] ?? '');
604 + $sanitized['mxchat_vectorstore_max_results'] = absint($input['mxchat_vectorstore_max_results'] ?? 5);
605 +
606 + // Ensure max results is within reasonable range
607 + if ($sanitized['mxchat_vectorstore_max_results'] < 1) {
608 + $sanitized['mxchat_vectorstore_max_results'] = 1;
310 609 }
311 - if ($updated) {
312 - wp_send_json_success(['message' => esc_html__('Setting saved', 'mxchat')]);
610 + if ($sanitized['mxchat_vectorstore_max_results'] > 20) {
611 + $sanitized['mxchat_vectorstore_max_results'] = 20;
612 + }
613 +
614 + return $sanitized;
615 +}
616 +
617 +/**
618 + * AJAX handler to test OpenAI Vector Store connection
619 + */
620 +public function mxchat_test_vectorstore_connection() {
621 + check_ajax_referer('mxchat_admin_nonce', 'nonce');
622 +
623 + if (!current_user_can('manage_options')) {
624 + wp_send_json_error(array('message' => __('Permission denied.', 'mxchat')));
625 + return;
626 + }
627 +
628 + $vectorstore_options = get_option('mxchat_openai_vectorstore_options', array());
629 + $vectorstore_ids = $vectorstore_options['mxchat_vectorstore_ids'] ?? '';
630 + $mxchat_options = get_option('mxchat_options', array());
631 + $api_key = $mxchat_options['api_key'] ?? '';
632 +
633 + if (empty($api_key)) {
634 + wp_send_json_error(array('message' => __('OpenAI API key is not configured.', 'mxchat')));
635 + return;
636 + }
637 +
638 + if (empty($vectorstore_ids)) {
639 + wp_send_json_error(array('message' => __('No Vector Store ID configured.', 'mxchat')));
640 + return;
641 + }
642 +
643 + // Get the first Vector Store ID for testing
644 + $ids_array = array_map('trim', explode(',', $vectorstore_ids));
645 + $test_id = $ids_array[0];
646 +
647 + // Test by retrieving the Vector Store info
648 + $response = wp_remote_get(
649 + 'https://api.openai.com/v1/vector_stores/' . $test_id,
650 + array(
651 + 'headers' => array(
652 + 'Authorization' => 'Bearer ' . $api_key,
653 + 'Content-Type' => 'application/json',
654 + 'OpenAI-Beta' => 'assistants=v2'
655 + ),
656 + 'timeout' => 30
657 + )
658 + );
659 +
660 + if (is_wp_error($response)) {
661 + wp_send_json_error(array('message' => __('Connection failed: ', 'mxchat') . $response->get_error_message()));
662 + return;
663 + }
664 +
665 + $status_code = wp_remote_retrieve_response_code($response);
666 + $body = json_decode(wp_remote_retrieve_body($response), true);
667 +
668 + if ($status_code === 200 && isset($body['id'])) {
669 + $file_count = $body['file_counts']['completed'] ?? 0;
670 + $name = $body['name'] ?? $test_id;
671 + wp_send_json_success(array(
672 + 'message' => sprintf(
673 + __('Connected successfully! Vector Store: %s (%d files)', 'mxchat'),
674 + esc_html($name),
675 + $file_count
676 + )
677 + ));
678 + } elseif ($status_code === 404) {
679 + wp_send_json_error(array('message' => __('Vector Store not found. Please check the ID.', 'mxchat')));
680 + } elseif ($status_code === 401) {
681 + wp_send_json_error(array('message' => __('Invalid API key.', 'mxchat')));
313 682 } else {
314 - wp_send_json_error(['message' => esc_html__('Update failed or no changes', 'mxchat')]);
683 + $error_message = $body['error']['message'] ?? __('Unknown error occurred.', 'mxchat');
684 + wp_send_json_error(array('message' => $error_message));
315 685 }
316 686 }
317 687
318 688 /**
319 - * Handles AJAX auto-save for prompts and auto-sync settings.
689 + * AJAX handler to test Slack connection and validate scopes
320 690 */
321 -public function mxchat_save_prompts_setting_callback() {
322 - // Check the correct nonce for this action.
323 - check_ajax_referer( 'mxchat_prompts_setting_nonce', '_ajax_nonce' );
691 +public function mxchat_test_slack_connection() {
692 + check_ajax_referer('mxchat_admin_nonce', 'nonce');
324 693
325 - if ( ! current_user_can( 'manage_options' ) ) {
326 - wp_send_json_error( [ 'message' => esc_html__( 'Unauthorized', 'mxchat' ) ] );
694 + if (!current_user_can('manage_options')) {
695 + wp_send_json_error(array('message' => __('Permission denied.', 'mxchat')));
696 + return;
327 697 }
328 698
329 - $name = isset( $_POST['name'] ) ? sanitize_text_field( wp_unslash( $_POST['name'] ) ) : '';
330 - $value = isset( $_POST['value'] ) ? sanitize_text_field( wp_unslash( $_POST['value'] ) ) : '';
699 + $bot_token = $this->options['live_agent_bot_token'] ?? '';
331 700
332 - if ( empty( $name ) ) {
333 - wp_send_json_error( [ 'message' => esc_html__( 'Invalid field name', 'mxchat' ) ] );
701 + if (empty($bot_token)) {
702 + wp_send_json_error(array('message' => __('Slack Bot Token is not configured. Please enter your token and save settings first.', 'mxchat')));
703 + return;
334 704 }
335 705
336 - // Handle standalone auto-sync settings.
337 - if ( in_array( $name, [ 'mxchat_auto_sync_posts', 'mxchat_auto_sync_pages' ], true ) ) {
338 - // Convert checkbox value to "1" if checked, "0" if not.
339 - $value = ( $value === 'on' ) ? '1' : '0';
340 - update_option( $name, $value );
341 - wp_send_json_success( [ 'message' => esc_html__( 'Setting saved', 'mxchat' ) ] );
706 + // Test 1: Validate bot token with auth.test
707 + $auth_response = wp_remote_post('https://slack.com/api/auth.test', array(
708 + 'headers' => array(
709 + 'Authorization' => 'Bearer ' . $bot_token,
710 + 'Content-Type' => 'application/json'
711 + ),
712 + 'timeout' => 15
713 + ));
714 +
715 + if (is_wp_error($auth_response)) {
716 + wp_send_json_error(array('message' => __('Connection failed: ', 'mxchat') . $auth_response->get_error_message()));
717 + return;
342 718 }
343 719
344 - // Handle fields stored in the 'mxchat_prompts_options' array.
345 -// Handle fields stored in the 'mxchat_prompts_options' array.
346 -if ( false !== strpos( $name, 'mxchat_prompts_options[' ) ) {
347 - // Extract the key name using regex.
348 - if ( preg_match( '/mxchat_prompts_options\[(.*?)\]/', $name, $matches ) && ! empty( $matches[1] ) ) {
349 - $key = sanitize_text_field( $matches[1] );
350 - $options = get_option( 'mxchat_prompts_options', [] );
720 + $auth_body = json_decode(wp_remote_retrieve_body($auth_response), true);
351 721
352 - // Remove Pinecone-specific settings from the main plugin handling.
353 - if ( 'mxchat_use_pinecone' === $key ) {
354 - // Optionally, you could simply ignore this field or set a default value.
355 - // For example, ensure it's always 0 or remove it entirely:
356 - // $options[ $key ] = '0';
357 - // Or, if you prefer, just return an error so that the main plugin doesn't handle it:
358 - wp_send_json_error( [ 'message' => esc_html__( 'Pinecone settings are now handled in the add-on.', 'mxchat' ) ] );
359 - } else {
360 - // For all other fields, use the existing logic.
361 - $options[ $key ] = ( $value === 'on' || $value === '1' ) ? '1' : $value;
722 + if (!isset($auth_body['ok']) || !$auth_body['ok']) {
723 + $error = $auth_body['error'] ?? 'unknown_error';
724 + $error_messages = array(
725 + 'invalid_auth' => __('Invalid bot token. Please check your token starts with xoxb-', 'mxchat'),
726 + 'not_authed' => __('No authentication token provided.', 'mxchat'),
727 + 'account_inactive' => __('The Slack workspace has been deactivated.', 'mxchat'),
728 + 'token_revoked' => __('The bot token has been revoked. Please generate a new one.', 'mxchat'),
729 + );
730 + $message = $error_messages[$error] ?? sprintf(__('Authentication failed: %s', 'mxchat'), $error);
731 + wp_send_json_error(array('message' => $message));
732 + return;
733 + }
734 +
735 + $team_name = $auth_body['team'] ?? 'Unknown Workspace';
736 + $bot_name = $auth_body['user'] ?? 'Unknown Bot';
737 +
738 + // Test 2: Check if we can list channels (tests channels:read scope)
739 + $channels_response = wp_remote_post('https://slack.com/api/conversations.list', array(
740 + 'headers' => array(
741 + 'Authorization' => 'Bearer ' . $bot_token,
742 + 'Content-Type' => 'application/json'
743 + ),
744 + 'body' => json_encode(array('limit' => 1)),
745 + 'timeout' => 15
746 + ));
747 +
748 + $channels_body = json_decode(wp_remote_retrieve_body($channels_response), true);
749 + $can_read_channels = isset($channels_body['ok']) && $channels_body['ok'];
750 +
751 + // Test 3: Check if we can create channels (tests channels:manage scope)
752 + // We'll just check the error message without actually creating
753 + $create_response = wp_remote_post('https://slack.com/api/conversations.create', array(
754 + 'headers' => array(
755 + 'Authorization' => 'Bearer ' . $bot_token,
756 + 'Content-Type' => 'application/json'
757 + ),
758 + 'body' => json_encode(array('name' => 'mxchat-test-' . time(), 'is_private' => false)),
759 + 'timeout' => 15
760 + ));
761 +
762 + $create_body = json_decode(wp_remote_retrieve_body($create_response), true);
763 +
764 + // If channel was created, delete it immediately
765 + if (isset($create_body['ok']) && $create_body['ok'] && isset($create_body['channel']['id'])) {
766 + wp_remote_post('https://slack.com/api/conversations.archive', array(
767 + 'headers' => array(
768 + 'Authorization' => 'Bearer ' . $bot_token,
769 + 'Content-Type' => 'application/json'
770 + ),
771 + 'body' => json_encode(array('channel' => $create_body['channel']['id'])),
772 + 'timeout' => 15
773 + ));
774 + $can_create_channels = true;
775 + } else {
776 + $create_error = $create_body['error'] ?? '';
777 + // name_taken means we have permission but channel exists
778 + $can_create_channels = ($create_error === 'name_taken' || (isset($create_body['ok']) && $create_body['ok']));
779 +
780 + // Check for missing scope errors
781 + if ($create_error === 'missing_scope') {
782 + $can_create_channels = false;
362 783 }
363 - update_option( 'mxchat_prompts_options', $options );
364 - wp_send_json_success( [ 'message' => esc_html__( 'Setting saved', 'mxchat' ) ] );
365 784 }
366 -}
367 785
786 + // Build result message
787 + $results = array();
788 + $results[] = sprintf(__('Workspace: %s', 'mxchat'), esc_html($team_name));
789 + $results[] = sprintf(__('Bot: %s', 'mxchat'), esc_html($bot_name));
790 + $results[] = '';
791 + $results[] = ($can_read_channels ? '✓' : '✗') . ' ' . __('channels:read - List channels', 'mxchat');
792 + $results[] = ($can_create_channels ? '✓' : '✗') . ' ' . __('channels:manage - Create channels', 'mxchat');
368 793
369 - wp_send_json_error( [ 'message' => esc_html__( 'Field not recognized', 'mxchat' ) ] );
794 + $missing_scopes = array();
795 + if (!$can_read_channels) $missing_scopes[] = 'channels:read';
796 + if (!$can_create_channels) $missing_scopes[] = 'channels:manage';
797 +
798 + if (!empty($missing_scopes)) {
799 + wp_send_json_error(array(
800 + 'message' => implode("\n", $results),
801 + 'missing_scopes' => $missing_scopes,
802 + 'partial' => true
803 + ));
804 + } else {
805 + wp_send_json_success(array(
806 + 'message' => implode("\n", $results)
807 + ));
808 + }
370 809 }
371 810
372 811 public function mxchat_display_admin_notice() {
373 812 // Success notice
@@ -392,201 +831,133 @@
392 831 delete_transient('mxchat_admin_notice_error'); // Clear the transient after displaying
393 832 }
394 833 }
395 834
835 +public function show_live_agent_disabled_banner() {
836 + $show_disabled_notice = get_option('mxchat_show_live_agent_disabled_notice', false);
396 837
397 -
398 -
399 -
400 -public function mxchat_create_admin_page() {
401 -
402 - ?>
403 - <div class="wrap mxchat-admin">
404 - <?php if (!$this->is_activated): ?>
405 - <div class="mxchat-pro-banner">
406 - <p>
407 - <?php echo esc_html__('For a limited time, get lifetime access and save $20 on MxChat Pro!', 'mxchat'); ?>
408 - <a href="https://mxchat.ai/" target="_blank"><?php echo esc_html__('Upgrade to Pro today', 'mxchat'); ?></a>
409 - </p>
410 - </div>
411 - <?php endif; ?>
412 -
413 - <div class="mxchat-agents-banner">
414 - <p>
415 - <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'); ?>
416 - </p>
417 - </div>
418 -
419 - <h2 class="mxchat-nav-tab-wrapper">
420 - <a href="#chatbot" class="mxchat-nav-tab mxchat-nav-tab-active" data-tab="chatbot"><?php echo esc_html__('Chatbot', 'mxchat'); ?></a>
421 - <a href="#embed" class="mxchat-nav-tab" data-tab="embed"><?php echo esc_html__('Integrations', 'mxchat'); ?></a>
422 - <a href="#general" class="mxchat-nav-tab" data-tab="general"><?php echo esc_html__('FAQ', 'mxchat'); ?></a>
423 - </h2>
424 -
425 - <div id="chatbot" class="mxchat-tab-content active">
426 - <div class="mxchat-autosave-section">
427 - <?php do_settings_sections('mxchat-chatbot'); ?>
838 + if ($show_disabled_notice) {
839 + ?>
840 + <div class="mxchat-live-agent-disabled-notice" id="mxchat-disabled-notice">
841 + <div class="mxchat-pro-notification">
842 + <button type="button" class="mxchat-dismiss-btn" onclick="dismissLiveAgentNotice()" aria-label="Dismiss notification">
843 + <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
844 + <line x1="18" y1="6" x2="6" y2="18"></line>
845 + <line x1="6" y1="6" x2="18" y2="18"></line>
846 + </svg>
847 + </button>
848 + <div class="mxchat-live-agent-content">
849 + <h3>🔧 Live Agent Integration Updated!</h3>
850 + <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>
851 + </div>
428 852 </div>
429 853 </div>
854 + <?php
855 + }
856 +}
857 +public function mxchat_create_admin_page() {
858 + $this->add_live_agent_nonce();
859 + $this->add_theme_migration_nonce();
430 860
431 - <div id="embed" class="mxchat-tab-content">
432 - <div class="mxchat-autosave-section">
861 + // Include and render the new sidebar-based settings page
862 + require_once plugin_dir_path(__FILE__) . 'admin-settings-page.php';
863 + mxchat_render_settings_page($this);
864 +}
433 865
434 - <div class="mxchat-settings-section">
435 - <h2><?php echo esc_html__('Loops Settings', 'mxchat'); ?></h2>
436 - <table class="form-table">
437 - <?php do_settings_fields('mxchat-embed', 'mxchat_loops_section'); ?>
438 - </table>
439 - </div>
866 +public function dismiss_live_agent_notice() {
867 + // Add debugging
868 + //error_log('dismiss_live_agent_notice called');
869 + //error_log('POST data: ' . print_r($_POST, true));
440 870
441 - <div class="section-divider"></div>
871 + // Verify nonce
872 + if (!wp_verify_nonce($_POST['nonce'], 'dismiss_live_agent_notice')) {
873 + //error_log('Nonce verification failed');
874 + wp_die('Security check failed');
875 + }
442 876
443 - <div class="mxchat-settings-section">
444 - <h2><?php echo esc_html__('Brave Search Settings', 'mxchat'); ?></h2>
445 - <table class="form-table">
446 - <?php do_settings_fields('mxchat-embed', 'mxchat_brave_section'); ?>
447 - </table>
448 - </div>
877 + // Remove the notice flag
878 + $deleted = delete_option('mxchat_show_live_agent_disabled_notice');
879 + //error_log('Option deleted: ' . ($deleted ? 'yes' : 'no'));
449 880
450 - <div class="section-divider"></div>
881 + wp_send_json_success();
882 +}
883 +public function add_live_agent_nonce() {
884 + if (get_option('mxchat_show_live_agent_disabled_notice', false)) {
885 + // Make sure your admin script is enqueued and localize the data
886 + wp_localize_script('mxchat-admin-js', 'mxchatLiveAgent', array(
887 + 'nonce' => wp_create_nonce('dismiss_live_agent_notice'),
888 + 'ajaxurl' => admin_url('admin-ajax.php')
889 + ));
890 + }
891 +}
451 892
452 - <div class="mxchat-settings-section">
453 - <h2><?php echo esc_html__('Toolbar Settings & Intents', 'mxchat'); ?></h2>
454 - <table class="form-table">
455 - <?php do_settings_fields('mxchat-embed', 'mxchat_pdf_intent_section'); ?>
456 - </table>
457 - </div>
893 +/**
894 + * Show theme migration notice for Pro users with AI-generated themes
895 + * Only shown once - dismissible and stored in options
896 + */
897 +public function show_theme_migration_banner() {
898 + // Only show if Pro is activated
899 + if (!$this->is_activated) {
900 + return;
901 + }
458 902
459 - <div class="section-divider"></div>
903 + // Check if notice should be shown
904 + $show_notice = get_option('mxchat_show_theme_migration_notice', false);
460 905
461 - <!-- Live Agent Settings Section -->
462 - <div class="mxchat-settings-section">
463 - <h2><?php echo esc_html__('Live Agent Settings', 'mxchat'); ?></h2>
464 - <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>
465 - <table class="form-table">
466 - <?php do_settings_fields('mxchat-embed', 'mxchat_live_agent_section'); ?>
467 - </table>
906 + if ($show_notice) {
907 + ?>
908 + <div class="mxchat-theme-migration-notice" id="mxchat-theme-migration-notice">
909 + <div class="mxchat-pro-notification">
910 + <button type="button" class="mxchat-dismiss-btn" onclick="dismissThemeMigrationNotice()" aria-label="Dismiss notification">
911 + <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
912 + <line x1="18" y1="6" x2="6" y2="18"></line>
913 + <line x1="6" y1="6" x2="18" y2="18"></line>
914 + </svg>
915 + </button>
916 + <div class="mxchat-theme-migration-content">
917 + <h3>🎨 AI Theme Migration Required</h3>
918 + <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>
468 919 </div>
469 920 </div>
470 921 </div>
922 + <?php
923 + }
924 +}
471 925
926 +/**
927 + * Dismiss theme migration notice via AJAX
928 + */
929 +public function dismiss_theme_migration_notice() {
930 + // Verify nonce
931 + if (!wp_verify_nonce($_POST['nonce'], 'dismiss_theme_migration_notice')) {
932 + wp_die('Security check failed');
933 + }
472 934
473 - <div id="general" class="mxchat-tab-content">
474 - <?php do_settings_sections('mxchat-general'); ?>
475 - <p>
476 - <?php echo esc_html__('If you’re having trouble with setup or getting the responses you need, we encourage you to review our', 'mxchat'); ?>
477 - <a href="https://mxchat.ai/documentation/" target="_blank" rel="noopener noreferrer"><?php echo esc_html__('documentation', 'mxchat'); ?></a> <?php echo esc_html__('or', 'mxchat'); ?>
478 - <a href="https://wordpress.org/support/plugin/mxchat-basic/" target="_blank" rel="noopener noreferrer"><?php echo esc_html__('create a support ticket', 'mxchat'); ?></a>.
479 - </p>
480 - <p>
481 - <?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>.
482 - </p>
935 + // Remove the notice flag
936 + delete_option('mxchat_show_theme_migration_notice');
483 937
484 - <div class="faq-item">
485 - <h3><?php echo esc_html__('How does the Claude API integration work?', 'mxchat'); ?></h3>
486 - <p>
487 - <?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'); ?>
488 - </p>
489 - <p>
490 - <?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'); ?>
491 - </p>
492 - <p>
493 - <?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>.
494 - </p>
495 - </div>
938 + wp_send_json_success();
939 +}
496 940
497 - <div class="faq-item">
498 - <h3><?php echo esc_html__('How does the X.AI API integration work?', 'mxchat'); ?></h3>
499 - <p>
500 - <?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'); ?>
501 - </p>
502 - <p>
503 - <?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>.
504 - </p>
505 - </div>
506 -
507 - <div class="faq-item">
508 - <h3><?php echo esc_html__('Do I need an OpenAI API key to use the chatbot?', 'mxchat'); ?></h3>
509 - <p>
510 - <?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'); ?>
511 - </p>
512 - <p>
513 - <?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'); ?>
514 - </p>
515 - </div>
516 -
517 - <div class="faq-item">
518 - <h3><?php echo esc_html__('How do I add the chatbot to my site?', 'mxchat'); ?></h3>
519 - <p>
520 - <?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'); ?>
521 - </p>
522 - </div>
523 -
524 - <div class="faq-item">
525 - <h3><?php echo esc_html__('How does the chatbot use my content to generate responses?', 'mxchat'); ?></h3>
526 - <p>
527 - <?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'); ?>
528 - </p>
529 - </div>
530 -
531 - <div class="faq-item">
532 - <h3><?php echo esc_html__('Why does the chatbot sometimes make up links or information?', 'mxchat'); ?></h3>
533 - <p>
534 - <?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'); ?>
535 - </p>
536 - </div>
537 -
538 - <div class="faq-item">
539 - <h3><?php echo esc_html__('How do intents work in MxChat?', 'mxchat'); ?></h3>
540 - <p>
541 - <?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>.
542 - </p>
543 - </div>
544 -
545 - <div class="faq-item">
546 - <h3><?php echo esc_html__('How does the Complianz integration work?', 'mxchat'); ?></h3>
547 - <p>
548 - <?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'); ?>
549 - </p>
550 - </div>
551 -
552 - <div class="faq-item">
553 - <h3><?php echo esc_html__('How does the WooCommerce integration work?', 'mxchat'); ?></h3>
554 - <p>
555 - <?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'); ?>
556 - </p>
557 - </div>
558 -
559 - <div class="faq-item">
560 - <h3><?php echo esc_html__('Why isn\'t my chatbot responding as expected?', 'mxchat'); ?></h3>
561 - <p>
562 - <?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'); ?>
563 - </p>
564 - <p>
565 - <?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'); ?>
566 - </p>
567 - </div>
568 -
569 - <div class="faq-item">
570 - <h3><?php echo esc_html__('What is Loops, and how do I get an API key?', 'mxchat'); ?></h3>
571 - <p>
572 - <?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'); ?>
573 - </p>
574 - </div>
575 -
576 - </div>
577 - </div>
578 - <?php
941 +/**
942 + * Add nonce for theme migration notice dismiss
943 + */
944 +public function add_theme_migration_nonce() {
945 + if (get_option('mxchat_show_theme_migration_notice', false) && $this->is_activated) {
946 + wp_localize_script('mxchat-admin-js', 'mxchatThemeMigration', array(
947 + 'nonce' => wp_create_nonce('dismiss_theme_migration_notice'),
948 + 'ajaxurl' => admin_url('admin-ajax.php')
949 + ));
950 + }
579 951 }
580 952
581 -
582 953 public function mxchat_create_transcripts_page() {
583 954 global $wpdb;
584 955 $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
585 956
586 957 // Get basic stats
587 - $total_chats = $wpdb->get_var("SELECT COUNT(DISTINCT session_id) FROM $table_name");
588 - $total_messages = $wpdb->get_var("SELECT COUNT(*) FROM $table_name");
958 + $total_chats = $wpdb->get_var("SELECT COUNT(DISTINCT session_id) FROM $table_name") ?: 0;
959 + $total_messages = $wpdb->get_var("SELECT COUNT(*) FROM $table_name") ?: 0;
589 960
590 961 // Count unique users with detailed breakdown
591 962 $total_users = $wpdb->get_var("
592 963 SELECT COUNT(DISTINCT
@@ -648,86 +1019,412 @@
648 1019 'Gift Buyer',
649 1020 'Parent or Caregiver'
650 1021 )
651 1022 ");
1023 + // Get activity metrics
1024 + $today_chats = $wpdb->get_var("
1025 + SELECT COUNT(DISTINCT session_id)
1026 + FROM $table_name
1027 + WHERE DATE(timestamp) = CURDATE()
1028 + ");
1029 +
1030 + $week_chats = $wpdb->get_var("
1031 + SELECT COUNT(DISTINCT session_id)
1032 + FROM $table_name
1033 + WHERE timestamp >= DATE_SUB(NOW(), INTERVAL 7 DAY)
1034 + ");
1035 +
1036 + $month_chats = $wpdb->get_var("
1037 + SELECT COUNT(DISTINCT session_id)
1038 + FROM $table_name
1039 + WHERE timestamp >= DATE_SUB(NOW(), INTERVAL 30 DAY)
1040 + ");
1041 +
1042 + // Get daily chat data for last 7 days
1043 + $daily_stats = $wpdb->get_results("
1044 + SELECT
1045 + DATE(timestamp) as date,
1046 + COUNT(DISTINCT session_id) as chats,
1047 + COUNT(*) as messages
1048 + FROM $table_name
1049 + WHERE timestamp >= DATE_SUB(NOW(), INTERVAL 7 DAY)
1050 + GROUP BY DATE(timestamp)
1051 + ORDER BY date ASC
1052 + ");
1053 +
1054 + // Get average messages per chat
1055 + $avg_messages = $wpdb->get_var("
1056 + SELECT AVG(message_count)
1057 + FROM (
1058 + SELECT session_id, COUNT(*) as message_count
1059 + FROM $table_name
1060 + GROUP BY session_id
1061 + ) as chat_counts
1062 + ");
1063 + $avg_messages = $avg_messages ? round($avg_messages, 1) : 0;
1064 +
1065 + // Get busiest hour
1066 + $busiest_hour = $wpdb->get_row("
1067 + SELECT HOUR(timestamp) as hour, COUNT(DISTINCT session_id) as chat_count
1068 + FROM $table_name
1069 + GROUP BY HOUR(timestamp)
1070 + ORDER BY chat_count DESC
1071 + LIMIT 1
1072 + ");
1073 +
1074 + // Prepare chart data
1075 + $chart_labels = array();
1076 + $chart_chats = array();
1077 + $chart_messages = array();
1078 +
1079 + // Fill last 7 days with data
1080 + for ($i = 6; $i >= 0; $i--) {
1081 + $date = date('Y-m-d', strtotime("-$i days"));
1082 + $day_name = date('D', strtotime("-$i days"));
1083 + $chart_labels[] = $day_name;
1084 +
1085 + $found = false;
1086 + foreach ($daily_stats as $stat) {
1087 + if ($stat->date === $date) {
1088 + $chart_chats[] = (int)$stat->chats;
1089 + $chart_messages[] = (int)$stat->messages;
1090 + $found = true;
1091 + break;
1092 + }
1093 + }
1094 + if (!$found) {
1095 + $chart_chats[] = 0;
1096 + $chart_messages[] = 0;
1097 + }
1098 + }
1099 +
1100 + // Prepare page data for the template
1101 + $page_data = array(
1102 + 'total_chats' => $total_chats,
1103 + 'total_messages' => $total_messages,
1104 + 'total_users' => $total_users,
1105 + 'registered_users' => $registered_users,
1106 + 'guest_users' => $guest_users,
1107 + 'agent_tests' => $agent_tests,
1108 + 'today_chats' => $today_chats,
1109 + 'week_chats' => $week_chats,
1110 + 'month_chats' => $month_chats,
1111 + 'avg_messages' => $avg_messages,
1112 + 'busiest_hour' => $busiest_hour,
1113 + 'chart_labels' => $chart_labels,
1114 + 'chart_chats' => $chart_chats,
1115 + 'chart_messages' => $chart_messages,
1116 + );
1117 +
1118 + // Include and render the new template
1119 + require_once plugin_dir_path(__FILE__) . 'admin-transcripts-page.php';
1120 + mxchat_render_transcripts_page($this, $page_data);
1121 +}
1122 +
1123 +/**
1124 + * Get chart data for transcripts page
1125 + * Used by both page render and script localization
1126 + *
1127 + * @return array Chart data with labels, chats, and messages arrays
1128 + */
1129 +private function get_transcripts_chart_data() {
1130 + global $wpdb;
1131 + $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
1132 +
1133 + // Get daily chat data for last 7 days - same query as mxchat_transcripts_page()
1134 + $daily_stats = $wpdb->get_results("
1135 + SELECT
1136 + DATE(timestamp) as date,
1137 + COUNT(DISTINCT session_id) as chats,
1138 + COUNT(*) as messages
1139 + FROM $table_name
1140 + WHERE timestamp >= DATE_SUB(NOW(), INTERVAL 7 DAY)
1141 + GROUP BY DATE(timestamp)
1142 + ORDER BY date ASC
1143 + ");
1144 +
1145 + // Prepare chart data
1146 + $chart_labels = array();
1147 + $chart_chats = array();
1148 + $chart_messages = array();
1149 +
1150 + // Fill last 7 days with data - same logic as mxchat_transcripts_page()
1151 + for ($i = 6; $i >= 0; $i--) {
1152 + $date = date('Y-m-d', strtotime("-$i days"));
1153 + $day_name = date('D', strtotime("-$i days"));
1154 + $chart_labels[] = $day_name;
1155 +
1156 + $found = false;
1157 + if ($daily_stats) {
1158 + foreach ($daily_stats as $stat) {
1159 + if ($stat->date === $date) {
1160 + $chart_chats[] = (int)$stat->chats;
1161 + $chart_messages[] = (int)$stat->messages;
1162 + $found = true;
1163 + break;
1164 + }
1165 + }
1166 + }
1167 + if (!$found) {
1168 + $chart_chats[] = 0;
1169 + $chart_messages[] = 0;
1170 + }
1171 + }
1172 +
1173 + return array(
1174 + 'labels' => $chart_labels,
1175 + 'chats' => $chart_chats,
1176 + 'messages' => $chart_messages
1177 + );
1178 +}
1179 +
1180 +public function mxchat_transcripts_notification_section_callback() {
1181 + 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>';
1182 +}
1183 +public function mxchat_enable_notifications_callback() {
1184 + $options = get_option('mxchat_transcripts_options', array());
1185 + $enabled = isset($options['mxchat_enable_notifications']) ? $options['mxchat_enable_notifications'] : 0;
652 1186 ?>
653 - <div class="wrap mxchat-transcripts-wrapper">
654 - <!-- Hero Section -->
655 - <div class="mxchat-transcripts-hero">
656 - <h1 class="mxchat-main-title">
657 - Chat <span class="mxchat-gradient-text">Transcripts</span>
658 - </h1>
659 - <p class="mxchat-hero-subtitle">
660 - <?php esc_html_e('Review and manage your chatbot conversations with detailed message history.', 'mxchat'); ?>
661 - </p>
662 - </div>
663 - <div class="mxchat-content">
664 - <!-- Stats Cards -->
665 - <div class="mxchat-stats-grid">
666 - <div class="mxchat-stat-card">
667 - <div class="stat-icon">💬</div>
668 - <div class="stat-content">
669 - <span class="stat-value"><?php echo esc_html($total_chats); ?></span>
670 - <span class="stat-label"><?php esc_html_e('Total Chats', 'mxchat'); ?></span>
671 - </div>
672 - </div>
673 - <div class="mxchat-stat-card">
674 - <div class="stat-icon">📝</div>
675 - <div class="stat-content">
676 - <span class="stat-value"><?php echo esc_html($total_messages); ?></span>
677 - <span class="stat-label"><?php esc_html_e('Total Messages', 'mxchat'); ?></span>
678 - </div>
679 - </div>
680 - <div class="mxchat-stat-card">
681 - <div class="stat-icon">👥</div>
682 - <div class="stat-content">
683 - <span class="stat-value"><?php echo esc_html($total_users); ?></span>
684 - <span class="stat-label"><?php esc_html_e('Unique Users', 'mxchat'); ?></span>
685 - <span class="stat-sublabel">
686 - <?php
687 - echo sprintf(
688 - esc_html__('%d registered, %d guests, %d agent tests', 'mxchat'),
689 - $registered_users,
690 - $guest_users,
691 - $agent_tests
692 - );
693 - ?>
694 - </span>
695 - </div>
696 - </div>
697 - </div>
698 - <!-- Search and Filter Controls -->
699 - <div class="mxchat-controls-wrapper">
700 - <div class="mxchat-search-box">
701 - <input type="text" id="mxchat-search-transcripts"
702 - placeholder="<?php esc_attr_e('Search transcripts...', 'mxchat'); ?>"
703 - class="regular-text">
704 - </div>
705 - <form id="mxchat-delete-form" method="post">
706 - <?php wp_nonce_field('mxchat_delete_chat_history', 'mxchat_delete_chat_nonce'); ?>
707 - <div class="mxchat-controls">
708 - <button type="button" id="mxchat-export-transcripts" class="mxchat-action-button">
709 - <span class="dashicons dashicons-download"></span>
710 - <?php esc_html_e('Export All Chats', 'mxchat'); ?>
711 - </button>
712 - <button type="button" id="mxchat-select-all-transcripts" class="mxchat-select-button">
713 - <span class="dashicons dashicons-yes-alt"></span>
714 - <span class="button-text"><?php esc_html_e('Select All', 'mxchat'); ?></span>
715 - </button>
716 - <button type="submit" class="button delete-chats-button">
717 - <span class="dashicons dashicons-trash"></span>
718 - <?php esc_html_e('Delete Selected', 'mxchat'); ?>
719 - </button>
720 - </div>
721 - </form>
722 - </div>
723 - <!-- Transcripts Container -->
724 - <div id="mxchat-transcripts"></div>
725 - </div>
726 - </div>
1187 + <label for="mxchat_enable_notifications">
1188 + <input type="checkbox" id="mxchat_enable_notifications"
1189 + name="mxchat_transcripts_options[mxchat_enable_notifications]"
1190 + value="1" <?php checked(1, $enabled); ?>>
1191 + <?php esc_html_e('Send email notification when a new chat session starts', 'mxchat'); ?>
1192 + </label>
1193 + <p class="description">
1194 + <?php esc_html_e('Enable this option to receive email notifications for new chat sessions.', 'mxchat'); ?>
1195 + </p>
727 1196 <?php
728 1197 }
1198 +public function mxchat_notification_email_callback() {
1199 + $options = get_option('mxchat_transcripts_options', array());
1200 + $email = isset($options['mxchat_notification_email']) ? $options['mxchat_notification_email'] : get_option('admin_email');
1201 + ?>
1202 + <input type="email" id="mxchat_notification_email"
1203 + name="mxchat_transcripts_options[mxchat_notification_email]"
1204 + value="<?php echo esc_attr($email); ?>"
1205 + class="regular-text">
1206 + <p class="description">
1207 + <?php esc_html_e('Enter the email address where notifications should be sent. Defaults to the admin email address.', 'mxchat'); ?>
1208 + </p>
1209 + <?php
1210 +}
729 1211
1212 +public function mxchat_auto_delete_transcripts_callback() {
1213 + $options = get_option('mxchat_transcripts_options', array());
1214 + $interval = isset($options['mxchat_auto_delete_transcripts']) ? $options['mxchat_auto_delete_transcripts'] : 'never';
1215 + ?>
1216 + <select id="mxchat_auto_delete_transcripts"
1217 + name="mxchat_transcripts_options[mxchat_auto_delete_transcripts]">
1218 + <option value="never" <?php selected($interval, 'never'); ?>>
1219 + <?php esc_html_e('Never (Keep All Transcripts)', 'mxchat'); ?>
1220 + </option>
1221 + <option value="1week" <?php selected($interval, '1week'); ?>>
1222 + <?php esc_html_e('After 1 Week', 'mxchat'); ?>
1223 + </option>
1224 + <option value="2weeks" <?php selected($interval, '2weeks'); ?>>
1225 + <?php esc_html_e('After 2 Weeks', 'mxchat'); ?>
1226 + </option>
1227 + <option value="1month" <?php selected($interval, '1month'); ?>>
1228 + <?php esc_html_e('After 1 Month', 'mxchat'); ?>
1229 + </option>
1230 + </select>
1231 + <p class="description">
1232 + <?php esc_html_e('Automatically delete old chat transcripts after the selected time period. This helps manage database size and privacy.', 'mxchat'); ?>
1233 + </p>
1234 + <?php
1235 +}
1236 +
1237 +public function mxchat_auto_email_transcript_callback() {
1238 + $options = get_option('mxchat_transcripts_options', array());
1239 + $enabled = isset($options['mxchat_auto_email_transcript_enabled']) ? $options['mxchat_auto_email_transcript_enabled'] : 0;
1240 + $delay = isset($options['mxchat_auto_email_transcript_delay']) ? $options['mxchat_auto_email_transcript_delay'] : '30';
1241 + $require_contact = isset($options['mxchat_auto_email_transcript_require_contact']) ? $options['mxchat_auto_email_transcript_require_contact'] : 0;
1242 + ?>
1243 + <label>
1244 + <input type="checkbox"
1245 + id="mxchat_auto_email_transcript_enabled"
1246 + name="mxchat_transcripts_options[mxchat_auto_email_transcript_enabled]"
1247 + value="1"
1248 + <?php checked($enabled, 1); ?>>
1249 + <?php esc_html_e('Enable Auto-Email of Full Transcript', 'mxchat'); ?>
1250 + </label>
1251 + <br><br>
1252 + <label for="mxchat_auto_email_transcript_delay">
1253 + <?php esc_html_e('Send transcript after:', 'mxchat'); ?>
1254 + </label>
1255 + <select id="mxchat_auto_email_transcript_delay"
1256 + name="mxchat_transcripts_options[mxchat_auto_email_transcript_delay]">
1257 + <option value="15" <?php selected($delay, '15'); ?>>
1258 + <?php esc_html_e('15 minutes', 'mxchat'); ?>
1259 + </option>
1260 + <option value="30" <?php selected($delay, '30'); ?>>
1261 + <?php esc_html_e('30 minutes', 'mxchat'); ?>
1262 + </option>
1263 + <option value="60" <?php selected($delay, '60'); ?>>
1264 + <?php esc_html_e('1 hour', 'mxchat'); ?>
1265 + </option>
1266 + </select>
1267 + <br><br>
1268 + <label>
1269 + <input type="checkbox"
1270 + id="mxchat_auto_email_transcript_require_contact"
1271 + name="mxchat_transcripts_options[mxchat_auto_email_transcript_require_contact]"
1272 + value="1"
1273 + <?php checked($require_contact, 1); ?>>
1274 + <?php esc_html_e('Only send if visitor provided contact info', 'mxchat'); ?>
1275 + </label>
1276 + <p class="description">
1277 + <?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'); ?>
1278 + <br>
1279 + <?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'); ?>
1280 + </p>
1281 + <?php
1282 +}
1283 +
1284 +
1285 +
1286 +public function sanitize_transcripts_options($input) {
1287 + $sanitized = array();
1288 +
1289 + $sanitized['mxchat_enable_notifications'] = isset($input['mxchat_enable_notifications']) ? 1 : 0;
1290 +
1291 + if (isset($input['mxchat_notification_email'])) {
1292 + $sanitized['mxchat_notification_email'] = sanitize_email($input['mxchat_notification_email']);
1293 + if (!is_email($sanitized['mxchat_notification_email'])) {
1294 + add_settings_error(
1295 + 'mxchat_transcripts_options',
1296 + 'invalid_email',
1297 + __('Please enter a valid email address for notifications.', 'mxchat'),
1298 + 'error'
1299 + );
1300 + $sanitized['mxchat_notification_email'] = get_option('admin_email');
1301 + }
1302 + }
1303 +
1304 + // Sanitize auto-delete setting
1305 + $valid_intervals = array('never', '1week', '2weeks', '1month');
1306 + if (isset($input['mxchat_auto_delete_transcripts'])) {
1307 + $sanitized['mxchat_auto_delete_transcripts'] = in_array($input['mxchat_auto_delete_transcripts'], $valid_intervals)
1308 + ? $input['mxchat_auto_delete_transcripts']
1309 + : 'never';
1310 + } else {
1311 + $sanitized['mxchat_auto_delete_transcripts'] = 'never';
1312 + }
1313 +
1314 + // Get old value to check if auto-delete setting changed
1315 + $old_options = get_option('mxchat_transcripts_options');
1316 + $old_interval = isset($old_options['mxchat_auto_delete_transcripts']) ? $old_options['mxchat_auto_delete_transcripts'] : 'never';
1317 +
1318 + // If the auto-delete setting changed, reschedule the cron job
1319 + if ($old_interval !== $sanitized['mxchat_auto_delete_transcripts']) {
1320 + $this->schedule_transcript_cleanup($sanitized['mxchat_auto_delete_transcripts']);
1321 + }
1322 +
1323 + // Sanitize auto-email transcript settings
1324 + $sanitized['mxchat_auto_email_transcript_enabled'] = isset($input['mxchat_auto_email_transcript_enabled']) ? 1 : 0;
1325 +
1326 + $valid_delays = array('15', '30', '60');
1327 + if (isset($input['mxchat_auto_email_transcript_delay'])) {
1328 + $sanitized['mxchat_auto_email_transcript_delay'] = in_array($input['mxchat_auto_email_transcript_delay'], $valid_delays)
1329 + ? $input['mxchat_auto_email_transcript_delay']
1330 + : '30';
1331 + } else {
1332 + $sanitized['mxchat_auto_email_transcript_delay'] = '30';
1333 + }
1334 +
1335 + // Sanitize require contact info setting
1336 + $sanitized['mxchat_auto_email_transcript_require_contact'] = isset($input['mxchat_auto_email_transcript_require_contact']) ? 1 : 0;
1337 +
1338 + return $sanitized;
1339 +}
1340 +
1341 +/**
1342 + * Schedule or unschedule the transcript cleanup cron job
1343 + */
1344 +public function schedule_transcript_cleanup($interval) {
1345 + // Clear any existing scheduled event
1346 + $timestamp = wp_next_scheduled('mxchat_cleanup_old_transcripts');
1347 + if ($timestamp) {
1348 + wp_unschedule_event($timestamp, 'mxchat_cleanup_old_transcripts');
1349 + }
1350 +
1351 + // Schedule new event if not set to "never"
1352 + if ($interval !== 'never') {
1353 + // Schedule to run daily at 3 AM
1354 + $next_run = strtotime('tomorrow 3:00 AM');
1355 + wp_schedule_event($next_run, 'daily', 'mxchat_cleanup_old_transcripts');
1356 + }
1357 +}
1358 +
1359 +/**
1360 + * Delete old transcripts based on the configured interval
1361 + */
1362 +public function cleanup_old_transcripts() {
1363 + $options = get_option('mxchat_transcripts_options', array());
1364 + $interval = isset($options['mxchat_auto_delete_transcripts']) ? $options['mxchat_auto_delete_transcripts'] : 'never';
1365 +
1366 + // If set to never, don't delete anything
1367 + if ($interval === 'never') {
1368 + return;
1369 + }
1370 +
1371 + // Calculate the cutoff date
1372 + $days = 0;
1373 + switch ($interval) {
1374 + case '1week':
1375 + $days = 7;
1376 + break;
1377 + case '2weeks':
1378 + $days = 14;
1379 + break;
1380 + case '1month':
1381 + $days = 30;
1382 + break;
1383 + default:
1384 + return; // Invalid interval, don't delete anything
1385 + }
1386 +
1387 + global $wpdb;
1388 + $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
1389 + $translations_table = $wpdb->prefix . 'mxchat_transcript_translations';
1390 +
1391 + // Calculate the cutoff timestamp
1392 + $cutoff_date = date('Y-m-d H:i:s', strtotime("-{$days} days"));
1393 +
1394 + // First, get the session IDs that will be deleted (to clean up translations too)
1395 + $sessions_to_delete = $wpdb->get_col(
1396 + $wpdb->prepare(
1397 + "SELECT DISTINCT session_id FROM {$table_name} WHERE timestamp < %s",
1398 + $cutoff_date
1399 + )
1400 + );
1401 +
1402 + // Delete transcripts older than the cutoff date
1403 + $deleted = $wpdb->query(
1404 + $wpdb->prepare(
1405 + "DELETE FROM {$table_name} WHERE timestamp < %s",
1406 + $cutoff_date
1407 + )
1408 + );
1409 +
1410 + // Also delete any translations for the deleted sessions
1411 + if (!empty($sessions_to_delete) && $wpdb->get_var("SHOW TABLES LIKE '$translations_table'") === $translations_table) {
1412 + $placeholders = implode(',', array_fill(0, count($sessions_to_delete), '%s'));
1413 + $wpdb->query(
1414 + $wpdb->prepare(
1415 + "DELETE FROM {$translations_table} WHERE session_id IN ($placeholders)",
1416 + $sessions_to_delete
1417 + )
1418 + );
1419 + }
1420 +
1421 + // Log the cleanup action
1422 + if ($deleted !== false && $deleted > 0) {
1423 + error_log(sprintf('MXChat: Auto-deleted %d transcripts older than %d days', $deleted, $days));
1424 + }
1425 +}
1426 +
730 1427 public function export_chat_transcripts() {
731 1428 if (!current_user_can('manage_options')) {
732 1429 wp_die(esc_html__('You do not have sufficient permissions to access this page.', 'mxchat'));
733 1430 }
@@ -786,2556 +1483,2967 @@
786 1483 fclose($output);
787 1484 wp_die();
788 1485 }
789 1486
790 -public function mxchat_fetch_chat_history() {
791 - global $wpdb;
792 - $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
793 -
1487 +/**
1488 + * Handle translation of chat messages via AJAX
1489 + */
1490 +public function mxchat_translate_messages() {
794 1491 if (!current_user_can('manage_options')) {
795 - wp_die(esc_html__('You do not have sufficient permissions to view this page.', 'mxchat'));
1492 + wp_send_json_error(['error' => 'Insufficient permissions']);
1493 + wp_die();
796 1494 }
797 1495
798 - // First get unique session IDs ordered by most recent message in each session
799 - $session_ids = $wpdb->get_col(
800 - $wpdb->prepare(
801 - "SELECT DISTINCT session_id
802 - FROM {$table_name}
803 - GROUP BY session_id
804 - ORDER BY MAX(timestamp) DESC"
805 - )
806 - );
1496 + $session_id = isset($_POST['session_id']) ? sanitize_text_field($_POST['session_id']) : '';
1497 + $target_lang = isset($_POST['target_lang']) ? sanitize_text_field($_POST['target_lang']) : 'en';
1498 + $messages_json = isset($_POST['messages']) ? wp_unslash($_POST['messages']) : '[]';
1499 + $messages = json_decode($messages_json, true);
807 1500
808 - if (empty($session_ids)) {
809 - wp_die(esc_html__('No chat history available.', 'mxchat'));
1501 + if (empty($session_id)) {
1502 + wp_send_json_error(['error' => 'No session ID provided']);
1503 + wp_die();
810 1504 }
811 1505
812 - ob_start();
813 - echo '<div class="mxchat-transcript">';
1506 + if (empty($messages) || !is_array($messages)) {
1507 + wp_send_json_error(['error' => 'No messages to translate']);
1508 + wp_die();
1509 + }
814 1510
815 - // Iterate through sessions from newest to oldest
816 - foreach ($session_ids as $session_id) {
817 - // Get the email associated with this session (if available)
818 - $email = $wpdb->get_var(
819 - $wpdb->prepare(
820 - "SELECT user_email
821 - FROM {$table_name}
822 - WHERE session_id = %s AND user_email != ''
823 - ORDER BY timestamp ASC
824 - LIMIT 1",
825 - $session_id
826 - )
827 - );
1511 + // Language names for prompting
1512 + $languages = [
1513 + 'en' => 'English',
1514 + 'es' => 'Spanish',
1515 + 'fr' => 'French',
1516 + 'de' => 'German',
1517 + 'it' => 'Italian',
1518 + 'pt' => 'Portuguese',
1519 + 'nl' => 'Dutch',
1520 + 'ru' => 'Russian',
1521 + 'zh' => 'Chinese',
1522 + 'ja' => 'Japanese',
1523 + 'ko' => 'Korean',
1524 + 'ar' => 'Arabic',
1525 + 'hi' => 'Hindi',
1526 + 'tr' => 'Turkish',
1527 + 'pl' => 'Polish',
1528 + 'vi' => 'Vietnamese',
1529 + 'th' => 'Thai',
1530 + 'id' => 'Indonesian',
1531 + 'sv' => 'Swedish',
1532 + 'da' => 'Danish'
1533 + ];
828 1534
829 - // Debug: Check email retrieval
830 - if (empty($email)) {
831 - //error_log("No email found for session {$session_id}");
832 - } else {
833 - //error_log("Email for session {$session_id}: " . $email);
1535 + $target_lang_name = isset($languages[$target_lang]) ? $languages[$target_lang] : 'English';
1536 +
1537 + // Build combined text for translation (numbered for parsing)
1538 + $numbered_messages = [];
1539 + foreach ($messages as $i => $msg) {
1540 + $content = isset($msg['content']) ? trim($msg['content']) : '';
1541 + if (!empty($content)) {
1542 + $numbered_messages[] = "[MSG" . $i . "]" . $content . "[/MSG" . $i . "]";
834 1543 }
1544 + }
835 1545
836 - // Get messages for this session ordered by timestamp
837 - $messages = $wpdb->get_results(
838 - $wpdb->prepare(
839 - "SELECT * FROM {$table_name}
840 - WHERE session_id = %s
841 - ORDER BY timestamp ASC",
842 - $session_id
843 - )
844 - );
1546 + if (empty($numbered_messages)) {
1547 + wp_send_json_error(['error' => 'No valid messages to translate']);
1548 + wp_die();
1549 + }
845 1550
846 - // Start session block
847 - echo '<div class="mxchat-session">';
848 - echo '<div class="mxchat-session-header">';
849 - // Wrap checkbox and session ID in one block
850 - echo '<div class="mxchat-session-id">';
851 - echo '<input type="checkbox" name="delete_session_ids[]" value="' . esc_attr($session_id) . '"> ';
852 - echo '<strong>' . esc_html__('Session ID:', 'mxchat') . '</strong> ' . esc_html($session_id);
853 - echo '</div>';
1551 + $combined_text = implode("\n\n", $numbered_messages);
854 1552
855 - // Place email directly below the session ID block
856 - if (!empty($email)) {
857 - echo '<div class="mxchat-session-email">';
858 - echo '<strong>' . esc_html__('Email:', 'mxchat') . '</strong> ' . esc_html($email);
859 - echo '</div>';
1553 + // Prepare the translation prompt
1554 + $system_prompt = "You are a translator. Translate the following messages to {$target_lang_name}. Keep the [MSG#] and [/MSG#] tags exactly as they are - only translate the content between them. Maintain the original formatting, line breaks, and any HTML tags. Return ONLY the translated messages with the tags, no explanations.";
1555 +
1556 + // Get user's selected model and determine provider
1557 + $options = get_option('mxchat_options', []);
1558 + $selected_model = $options['model'] ?? 'gpt-5.1-chat-latest';
1559 +
1560 + // Check if using OpenRouter
1561 + if ($selected_model === 'openrouter') {
1562 + $provider = 'openrouter';
1563 + $selected_model = $options['openrouter_selected_model'] ?? '';
1564 + $api_key = $options['openrouter_api_key'] ?? '';
1565 +
1566 + if (empty($selected_model)) {
1567 + wp_send_json_error(['error' => 'No OpenRouter model selected']);
1568 + wp_die();
860 1569 }
861 - echo '</div>';
1570 + } else {
1571 + // Determine provider from model name
1572 + $model_parts = explode('-', $selected_model);
1573 + $provider = strtolower($model_parts[0]);
862 1574
863 - echo '<div class="mxchat-messages">';
1575 + // Get the appropriate API key based on provider
1576 + $api_key = '';
1577 + switch ($provider) {
1578 + case 'gpt':
1579 + case 'o1':
1580 + $api_key = $options['api_key'] ?? '';
1581 + break;
1582 + case 'claude':
1583 + $api_key = $options['claude_api_key'] ?? '';
1584 + break;
1585 + case 'grok':
1586 + $api_key = $options['xai_api_key'] ?? '';
1587 + break;
1588 + case 'deepseek':
1589 + $api_key = $options['deepseek_api_key'] ?? '';
1590 + break;
1591 + case 'gemini':
1592 + $api_key = $options['gemini_api_key'] ?? '';
1593 + break;
1594 + default:
1595 + // Default to OpenAI
1596 + $api_key = $options['api_key'] ?? '';
1597 + $provider = 'gpt';
1598 + break;
1599 + }
1600 + }
864 1601
865 - // Display messages for this session
866 - foreach ($messages as $transcript) {
867 - $formatted_timestamp = date_i18n('F j, Y g:i a', strtotime($transcript->timestamp));
1602 + if (empty($api_key)) {
1603 + wp_send_json_error(['error' => 'No API key configured for ' . $provider]);
1604 + wp_die();
1605 + }
868 1606
869 - // Determine message styling
870 - switch ($transcript->role) {
871 - case 'assistant':
872 - case 'bot':
873 - $message_class = 'bot-message';
874 - $display_role = esc_html__('Chatbot', 'mxchat');
875 - break;
876 - case 'user':
877 - $message_class = 'user-message';
878 - $display_role = !empty($transcript->user_identifier)
879 - ? sanitize_text_field($transcript->user_identifier)
880 - : esc_html__('User', 'mxchat');
881 - break;
882 - case 'agent':
883 - $message_class = 'agent-message';
884 - $display_role = esc_html__('Agent', 'mxchat');
885 - break;
886 - default:
887 - $message_class = 'unknown-message';
888 - $display_role = esc_html__('Unknown', 'mxchat');
889 - }
1607 + // Make API request based on provider
1608 + $response = $this->translate_with_provider($provider, $selected_model, $api_key, $system_prompt, $combined_text);
890 1609
891 - // Process message content
892 - $message_content = wp_kses(
893 - stripslashes($transcript->message),
894 - [
895 - 'b' => [], 'strong' => [], 'i' => [], 'em' => [], 'u' => [],
896 - 'br' => [], 'p' => [], 'ul' => [], 'ol' => [], 'li' => [],
897 - 'a' => ['href' => [], 'title' => []]
898 - ]
899 - );
900 - $message_content = nl2br($message_content);
1610 + if (is_wp_error($response)) {
1611 + wp_send_json_error(['error' => $response->get_error_message()]);
1612 + wp_die();
1613 + }
901 1614
902 - // Render message
903 - echo '<div class="mxchat-message ' . esc_attr($message_class) . '">';
904 - echo '<div class="mxchat-message-header">' . esc_html($display_role) . '</div>';
905 - echo '<div class="mxchat-message-content">' . $message_content . '</div>';
906 - echo '<div class="mxchat-timestamp">' . esc_html($formatted_timestamp) . '</div>';
907 - echo '</div>';
1615 + // Parse the response to extract translated messages
1616 + $translations = [];
1617 + foreach ($messages as $msg) {
1618 + $index = $msg['index'];
1619 + $pattern = '/\[MSG' . $index . '\](.*?)\[\/MSG' . $index . '\]/s';
1620 + if (preg_match($pattern, $response, $matches)) {
1621 + $translations[] = [
1622 + 'index' => $index,
1623 + 'translated' => trim($matches[1])
1624 + ];
908 1625 }
1626 + }
909 1627
910 - // Close session block
911 - echo '</div></div>';
1628 + // Save translations to database
1629 + if (!empty($translations)) {
1630 + $this->save_transcript_translation($session_id, $target_lang, $translations);
912 1631 }
913 1632
914 - echo '</div>';
915 - $output = ob_get_clean();
916 - echo $output;
1633 + wp_send_json(['success' => true, 'translations' => $translations, 'language' => $target_lang]);
917 1634 wp_die();
918 1635 }
919 1636
1637 +/**
1638 + * Save transcript translation to database
1639 + */
1640 +private function save_transcript_translation($session_id, $language_code, $translations) {
1641 + global $wpdb;
1642 + $table_name = $wpdb->prefix . 'mxchat_transcript_translations';
920 1643
1644 + // Check if table exists, create if not
1645 + if ($wpdb->get_var("SHOW TABLES LIKE '$table_name'") !== $table_name) {
1646 + mxchat_create_translations_table();
1647 + }
921 1648
922 -public function mxchat_create_prompts_page() {
923 - global $wpdb;
924 - $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
1649 + $now = current_time('mysql');
1650 + $translations_json = wp_json_encode($translations);
925 1651
926 - // Display success message if all prompts were deleted
927 - if (isset($_GET['all_deleted']) && $_GET['all_deleted'] === 'true') {
928 - echo '<div class="notice notice-success is-dismissible"><p>' . esc_html__('All knowledge has been deleted successfully.', 'mxchat') . '</p></div>';
1652 + // Use REPLACE to insert or update
1653 + $wpdb->query($wpdb->prepare(
1654 + "REPLACE INTO $table_name (session_id, language_code, translations, created_at, updated_at)
1655 + VALUES (%s, %s, %s, %s, %s)",
1656 + $session_id,
1657 + $language_code,
1658 + $translations_json,
1659 + $now,
1660 + $now
1661 + ));
1662 +}
1663 +
1664 +/**
1665 + * Get saved translation for a session
1666 + */
1667 +public function mxchat_get_transcript_translation() {
1668 + if (!current_user_can('manage_options')) {
1669 + wp_send_json_error(['error' => 'Insufficient permissions']);
1670 + wp_die();
929 1671 }
930 1672
931 - // Set up pagination and search query
932 - $nonce = isset($_GET['_wpnonce']) ? sanitize_text_field($_GET['_wpnonce']) : '';
933 - $search_query = (!empty($nonce) && wp_verify_nonce($nonce, 'mxchat_prompts_search_nonce') && isset($_GET['search'])) ? sanitize_text_field($_GET['search']) : '';
934 - $current_page = isset($_GET['paged']) ? absint($_GET['paged']) : 1;
935 - $per_page = 10;
936 - $offset = ($current_page - 1) * $per_page;
1673 + $session_id = isset($_POST['session_id']) ? sanitize_text_field($_POST['session_id']) : '';
937 1674
938 - // Modify query to handle search input
939 - $sql_search = "";
940 - if ($search_query) {
941 - $sql_search = $wpdb->prepare("WHERE article_content LIKE %s", '%' . $wpdb->esc_like($search_query) . '%');
1675 + if (empty($session_id)) {
1676 + wp_send_json_error(['error' => 'No session ID provided']);
1677 + wp_die();
942 1678 }
943 1679
944 - // Retrieve total number of prompts, considering search filter
945 - $total_prompts = $wpdb->get_var("SELECT COUNT(*) FROM {$table_name} {$sql_search}");
946 - $total_pages = ceil($total_prompts / $per_page);
1680 + global $wpdb;
1681 + $table_name = $wpdb->prefix . 'mxchat_transcript_translations';
947 1682
948 - // Retrieve prompts from the database
949 - $prompts = $wpdb->get_results(
950 - $wpdb->prepare(
951 - "SELECT * FROM {$table_name} {$sql_search} ORDER BY timestamp DESC LIMIT %d OFFSET %d",
952 - $per_page,
953 - $offset
954 - )
955 - );
1683 + // Check if table exists
1684 + if ($wpdb->get_var("SHOW TABLES LIKE '$table_name'") !== $table_name) {
1685 + wp_send_json(['success' => true, 'has_translation' => false]);
1686 + wp_die();
1687 + }
956 1688
957 - // Add the pagination links generation here
958 - $page_links = paginate_links(array(
959 - 'base' => add_query_arg(array(
960 - 'paged' => '%#%',
961 - 'search' => urlencode($search_query),
962 - '_wpnonce' => wp_create_nonce('mxchat_prompts_search_nonce')
963 - ), admin_url('admin.php?page=mxchat-prompts')),
964 - 'format' => '',
965 - 'prev_text' => __('&laquo; Previous', 'mxchat'),
966 - 'next_text' => __('Next &raquo;', 'mxchat'),
967 - 'total' => $total_pages,
968 - 'current' => $current_page,
1689 + // Get the most recent translation for this session
1690 + $result = $wpdb->get_row($wpdb->prepare(
1691 + "SELECT language_code, translations FROM $table_name WHERE session_id = %s ORDER BY updated_at DESC LIMIT 1",
1692 + $session_id
969 1693 ));
970 1694
971 - // Retrieve processing statuses
972 - $pdf_url = get_transient('mxchat_last_pdf_url');
973 - $sitemap_url = get_transient('mxchat_last_sitemap_url');
974 - $pdf_status = $pdf_url ? $this->get_pdf_processing_status($pdf_url) : false;
975 - $sitemap_status = $sitemap_url ? $this->get_sitemap_processing_status($sitemap_url) : false;
1695 + if ($result) {
1696 + $translations = json_decode($result->translations, true);
1697 + wp_send_json([
1698 + 'success' => true,
1699 + 'has_translation' => true,
1700 + 'language' => $result->language_code,
1701 + 'translations' => $translations
1702 + ]);
1703 + } else {
1704 + wp_send_json(['success' => true, 'has_translation' => false]);
1705 + }
1706 + wp_die();
1707 +}
976 1708
977 - if ($pdf_status && $pdf_status['status'] === 'complete') {
978 - delete_transient('mxchat_last_pdf_url');
979 - $pdf_status = false;
1709 +/**
1710 + * Translate text using the user's selected provider and model
1711 + */
1712 +private function translate_with_provider($provider, $model, $api_key, $system_prompt, $text) {
1713 + switch ($provider) {
1714 + case 'claude':
1715 + return $this->translate_with_claude($api_key, $model, $system_prompt, $text);
1716 + case 'grok':
1717 + return $this->translate_with_xai($api_key, $model, $system_prompt, $text);
1718 + case 'deepseek':
1719 + return $this->translate_with_deepseek($api_key, $model, $system_prompt, $text);
1720 + case 'gemini':
1721 + return $this->translate_with_gemini($api_key, $model, $system_prompt, $text);
1722 + case 'openrouter':
1723 + return $this->translate_with_openrouter($api_key, $model, $system_prompt, $text);
1724 + case 'gpt':
1725 + case 'o1':
1726 + default:
1727 + return $this->translate_with_openai($api_key, $model, $system_prompt, $text);
980 1728 }
981 - if ($sitemap_status && $sitemap_status['status'] === 'complete') {
982 - delete_transient('mxchat_last_sitemap_url');
983 - $sitemap_status = false;
984 - }
1729 +}
985 1730
986 - $is_processing = ($pdf_status && ($pdf_status['status'] === 'processing' || $pdf_status['status'] === 'error'))
987 - || ($sitemap_status && ($sitemap_status['status'] === 'processing' || $sitemap_status['status'] === 'error'));
988 - ?>
989 - <div class="wrap mxchat-wrapper">
990 - <!-- Hero Section -->
991 - <div class="mxchat-hero">
992 - <h1 class="mxchat-main-title">
993 - <span class="mxchat-gradient-text">Knowledge Base</span> Manager
994 - </h1>
995 - <p class="mxchat-hero-subtitle">
996 - <?php esc_html_e('Enhance your AI chatbot with custom knowledge. Import, manage, and organize your content to keep responses accurate and relevant.', 'mxchat'); ?>
997 - </p>
998 - </div>
1731 +/**
1732 + * Translate text using OpenAI API
1733 + */
1734 +private function translate_with_openai($api_key, $model, $system_prompt, $text) {
1735 + $response = wp_remote_post('https://api.openai.com/v1/chat/completions', [
1736 + 'timeout' => 60,
1737 + 'headers' => [
1738 + 'Authorization' => 'Bearer ' . $api_key,
1739 + 'Content-Type' => 'application/json'
1740 + ],
1741 + 'body' => wp_json_encode([
1742 + 'model' => $model,
1743 + 'messages' => [
1744 + ['role' => 'system', 'content' => $system_prompt],
1745 + ['role' => 'user', 'content' => $text]
1746 + ],
1747 + 'temperature' => 0.3
1748 + ])
1749 + ]);
999 1750
1000 - <div class="mxchat-content">
1001 - <!-- Import Settings Card -->
1002 - <div class="mxchat-card">
1003 - <h2><?php esc_html_e('Knowledge Import Settings', 'mxchat'); ?></h2>
1004 - <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>
1751 + if (is_wp_error($response)) {
1752 + return $response;
1753 + }
1005 1754
1006 - <!-- Tab Contents -->
1007 - <div class="mxchat-tab-contents">
1008 - <!-- Default Database Tab -->
1009 - <div id="default-db" class="mxchat-tab-content active">
1010 - <!-- Auto-Sync Settings -->
1011 - <div class="mxchat-settings-section">
1012 - <h3><?php esc_html_e('Auto-Sync Settings', 'mxchat'); ?></h3>
1013 - <p class="mxchat-description">
1014 - <?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'); ?>
1015 - </p>
1016 - <div class="mxchat-autosave-section"> <!-- Add this wrapper -->
1017 - <div class="mxchat-toggle-group">
1018 - <div class="mxchat-toggle-container">
1019 - <label class="mxchat-toggle-switch">
1020 - <input type="checkbox"
1021 - name="mxchat_auto_sync_posts"
1022 - class="mxchat-autosave-field"
1023 - value="1"
1024 - data-nonce="<?php echo wp_create_nonce('mxchat_prompts_setting_nonce'); ?>"
1025 - <?php checked(get_option('mxchat_auto_sync_posts', '0'), '1'); ?>>
1026 - <span class="mxchat-toggle-slider"></span>
1027 - </label>
1028 - <span class="mxchat-toggle-label">
1029 - <?php esc_html_e('Auto-sync Posts', 'mxchat'); ?>
1030 - </span>
1031 - </div>
1032 - <div class="mxchat-toggle-container">
1033 - <label class="mxchat-toggle-switch">
1034 - <input type="checkbox"
1035 - name="mxchat_auto_sync_pages"
1036 - class="mxchat-autosave-field"
1037 - value="1"
1038 - data-nonce="<?php echo wp_create_nonce('mxchat_prompts_setting_nonce'); ?>"
1039 - <?php checked(get_option('mxchat_auto_sync_pages', '0'), '1'); ?>>
1040 - <span class="mxchat-toggle-slider"></span>
1041 - </label>
1042 - <span class="mxchat-toggle-label">
1043 - <?php esc_html_e('Auto-sync Pages', 'mxchat'); ?>
1044 - </span>
1045 - </div>
1046 - </div>
1047 - </div>
1048 - </div>
1755 + $body = json_decode(wp_remote_retrieve_body($response), true);
1049 1756
1050 - <!-- Import Methods -->
1051 - <div class="mxchat-import-methods">
1052 - <div class="mxchat-method-card">
1053 - <h4><?php esc_html_e('Sitemap Import', 'mxchat'); ?></h4>
1054 - <p><span class="red-warning">IMPORTANT:</span> <?php esc_html_e('Use a content-specific sub-sitemap, not the sitemap index.', 'mxchat'); ?></p> </div>
1055 - <div class="mxchat-method-card">
1056 - <h4><?php esc_html_e('PDF Import', 'mxchat'); ?></h4>
1057 - <p><?php esc_html_e('Import knowledge directly from PDF documents.', 'mxchat'); ?></p>
1058 - </div>
1059 - <div class="mxchat-method-card">
1060 - <h4><?php esc_html_e('Direct URL', 'mxchat'); ?></h4>
1061 - <p><?php esc_html_e('Import content from any specific webpage.', 'mxchat'); ?></p>
1062 - </div>
1063 - <div class="mxchat-method-card">
1064 - <h4><?php esc_html_e('Direct Content', 'mxchat'); ?></h4>
1065 - <p><?php esc_html_e('Directly submit content to be vectorized.', 'mxchat'); ?></p>
1066 - </div>
1067 - </div>
1757 + if (isset($body['error'])) {
1758 + return new WP_Error('api_error', $body['error']['message']);
1759 + }
1068 1760
1069 - <!-- Import Form -->
1070 - <?php if (!$is_processing) : ?>
1071 - <div class="mxchat-import-form">
1072 - <form id="mxchat-sitemap-form" method="post"
1073 - action="<?php echo esc_url(admin_url('admin-post.php?action=mxchat_submit_sitemap')); ?>">
1074 - <?php wp_nonce_field('mxchat_submit_sitemap_action', 'mxchat_submit_sitemap_nonce'); ?>
1075 - <div class="mxchat-url-input-group">
1076 - <input type="url"
1077 - name="sitemap_url"
1078 - id="sitemap_url"
1079 - placeholder="<?php esc_attr_e('Enter Sitemap, PDF, or Webpage URL', 'mxchat'); ?>"
1080 - required />
1081 - <button type="submit"
1082 - name="submit_sitemap"
1083 - class="mxchat-button-primary">
1084 - <?php esc_html_e('Import', 'mxchat'); ?>
1085 - </button>
1086 - </div>
1087 - </form>
1088 - </div>
1089 - <?php endif; ?>
1761 + if (isset($body['choices'][0]['message']['content'])) {
1762 + return $body['choices'][0]['message']['content'];
1763 + }
1090 1764
1091 - <!-- Processing Status -->
1092 - <?php if ($pdf_status && $pdf_status['status'] !== 'complete') : ?>
1093 - <div class="mxchat-status-card">
1094 - <div class="mxchat-status-header">
1095 - <h4><?php esc_html_e('PDF Processing Status', 'mxchat'); ?></h4>
1096 - <?php if ($is_processing) : ?>
1097 - <form method="post" class="mxchat-stop-form"
1098 - action="<?php echo esc_url(admin_url('admin-post.php?action=mxchat_stop_processing')); ?>">
1099 - <?php wp_nonce_field('mxchat_stop_processing_action', 'mxchat_stop_processing_nonce'); ?>
1100 - <button type="submit" name="stop_processing" class="mxchat-button-secondary">
1101 - <?php esc_html_e('Stop Processing', 'mxchat'); ?>
1102 - </button>
1103 - </form>
1104 - <?php endif; ?>
1105 - </div>
1106 - <div class="mxchat-progress-bar">
1107 - <div class="mxchat-progress-fill" style="width: <?php echo esc_attr($pdf_status['percentage']); ?>%"></div>
1108 - </div>
1109 - <div class="mxchat-status-details">
1110 - <p><?php printf(
1111 - esc_html__('Progress: %1$d of %2$d pages (%3$d%%)', 'mxchat'),
1112 - absint($pdf_status['processed_pages']),
1113 - absint($pdf_status['total_pages']),
1114 - absint($pdf_status['percentage'])
1115 - ); ?></p>
1116 - <p><?php printf(
1117 - esc_html__('Status: %s', 'mxchat'),
1118 - esc_html(ucfirst($pdf_status['status']))
1119 - ); ?></p>
1120 - <p><?php printf(
1121 - esc_html__('Last update: %s', 'mxchat'),
1122 - esc_html($pdf_status['last_update'])
1123 - ); ?></p>
1124 - </div>
1125 - </div>
1126 - <?php endif; ?>
1765 + return new WP_Error('api_error', 'Invalid API response');
1766 +}
1127 1767
1128 - <?php if ($sitemap_status && $sitemap_status['status'] !== 'complete') : ?>
1129 - <div class="mxchat-status-card">
1130 - <div class="mxchat-status-header">
1131 - <h4><?php esc_html_e('Sitemap Processing Status (Refresh for update)', 'mxchat'); ?></h4>
1132 - <?php if ($is_processing) : ?>
1133 - <form method="post" class="mxchat-stop-form"
1134 - action="<?php echo esc_url(admin_url('admin-post.php?action=mxchat_stop_processing')); ?>">
1135 - <?php wp_nonce_field('mxchat_stop_processing_action', 'mxchat_stop_processing_nonce'); ?>
1136 - <button type="submit" name="stop_processing" class="mxchat-button-secondary">
1137 - <?php esc_html_e('Stop Processing', 'mxchat'); ?>
1138 - </button>
1139 - </form>
1140 - <?php endif; ?>
1141 - </div>
1142 - <div class="mxchat-progress-bar">
1143 - <div class="mxchat-progress-fill" style="width: <?php echo esc_attr($sitemap_status['percentage']); ?>%"></div>
1144 - </div>
1145 - <div class="mxchat-status-details">
1146 - <p><?php printf(
1147 - esc_html__('Progress: %1$d of %2$d URLs (%3$d%%)', 'mxchat'),
1148 - absint($sitemap_status['processed_urls']),
1149 - absint($sitemap_status['total_urls']),
1150 - absint($sitemap_status['percentage'])
1151 - ); ?></p>
1152 - <?php if (!empty($sitemap_status['error']) || !empty($sitemap_status['last_error'])) : ?>
1153 - <div class="mxchat-error-notice">
1154 - <?php if (!empty($sitemap_status['error'])) : ?>
1155 - <p class="error"><?php echo esc_html($sitemap_status['error']); ?></p>
1156 - <?php endif; ?>
1157 - <?php if (!empty($sitemap_status['last_error'])) : ?>
1158 - <p class="last-error"><?php echo esc_html__('Last error:', 'mxchat') . ' ' . esc_html($sitemap_status['last_error']); ?></p>
1159 - <?php endif; ?>
1160 - </div>
1161 - <?php endif; ?>
1162 - </div>
1163 - </div>
1164 - <?php endif; ?>
1165 - </div>
1768 +/**
1769 + * Translate text using Claude API
1770 + */
1771 +private function translate_with_claude($api_key, $model, $system_prompt, $text) {
1772 + $response = wp_remote_post('https://api.anthropic.com/v1/messages', [
1773 + 'timeout' => 60,
1774 + 'headers' => [
1775 + 'x-api-key' => $api_key,
1776 + 'anthropic-version' => '2023-06-01',
1777 + 'Content-Type' => 'application/json'
1778 + ],
1779 + 'body' => wp_json_encode([
1780 + 'model' => $model,
1781 + 'max_tokens' => 4096,
1782 + 'system' => $system_prompt,
1783 + 'messages' => [
1784 + ['role' => 'user', 'content' => $text]
1785 + ]
1786 + ])
1787 + ]);
1166 1788
1789 + if (is_wp_error($response)) {
1790 + return $response;
1791 + }
1167 1792
1168 - </div>
1169 - </div>
1793 + $body = json_decode(wp_remote_retrieve_body($response), true);
1170 1794
1171 - <!-- Direct Content Submission Card -->
1172 -<div class="mxchat-card">
1173 - <div class="mxchat-card-header">
1174 - <h2><?php esc_html_e('Submit Direct Content', 'mxchat'); ?></h2>
1175 - </div>
1176 - <form id="mxchat-content-form" method="post"
1177 - action="<?php echo esc_url(admin_url('admin-post.php?action=mxchat_submit_content')); ?>">
1178 - <?php wp_nonce_field('mxchat_submit_content_action', 'mxchat_submit_content_nonce'); ?>
1179 - <div class="mxchat-form-group">
1180 - <label for="article_content"><?php esc_html_e('Content:', 'mxchat'); ?></label>
1181 - <textarea name="article_content" id="article_content" required
1182 - placeholder="<?php esc_attr_e('Enter your content here...', 'mxchat'); ?>"></textarea>
1183 - </div>
1184 - <div class="mxchat-form-group">
1185 - <label for="article_url"><?php esc_html_e('Source URL (Optional):', 'mxchat'); ?></label>
1186 - <input type="url" name="article_url" id="article_url"
1187 - placeholder="<?php esc_attr_e('Enter source URL', 'mxchat'); ?>">
1188 - </div>
1189 - <button type="submit" name="submit_content" class="mxchat-button-primary">
1190 - <?php esc_html_e('Submit Content', 'mxchat'); ?>
1191 - </button>
1192 - </form>
1193 -</div>
1795 + if (isset($body['error'])) {
1796 + return new WP_Error('api_error', $body['error']['message']);
1797 + }
1194 1798
1195 -<!-- Knowledge Base Table Card -->
1196 -<div class="mxchat-card">
1197 - <div class="mxchat-card-header">
1198 - <h2><?php esc_html_e('Knowledge Base', 'mxchat'); ?></h2>
1199 - <div class="mxchat-header-actions">
1200 - <!-- Search -->
1201 - <form method="get" id="knowledge-search" class="mxchat-search-form">
1202 - <?php wp_nonce_field('mxchat_prompts_search_nonce'); ?>
1203 - <input type="hidden" name="page" value="mxchat-prompts" />
1204 - <div class="mxchat-search-group">
1205 - <span class="dashicons dashicons-search"></span>
1206 - <input type="text"
1207 - name="search"
1208 - placeholder="<?php esc_attr_e('Search Knowledge', 'mxchat'); ?>"
1209 - value="<?php echo esc_attr($search_query); ?>" />
1210 - </div>
1211 - </form>
1799 + if (isset($body['content'][0]['text'])) {
1800 + return $body['content'][0]['text'];
1801 + }
1212 1802
1213 - <!-- Delete All -->
1214 - <form method="post"
1215 - action="<?php echo esc_url(admin_url('admin-post.php?action=mxchat_delete_all_prompts')); ?>"
1216 - class="mxchat-delete-form"
1217 - onsubmit="return confirm('<?php esc_attr_e('Are you sure you want to delete all knowledge? This action cannot be undone.', 'mxchat'); ?>');">
1218 - <?php wp_nonce_field('mxchat_delete_all_prompts_action', 'mxchat_delete_all_prompts_nonce'); ?>
1219 - <button type="submit" name="delete_all_prompts" class="mxchat-button-danger">
1220 - <span class="dashicons dashicons-trash"></span>
1221 - <?php esc_html_e('Delete All', 'mxchat'); ?>
1222 - </button>
1223 - </form>
1224 - </div>
1225 - </div>
1803 + return new WP_Error('api_error', 'Invalid API response');
1804 +}
1226 1805
1227 - <!-- Table -->
1228 - <div class="mxchat-table-wrapper">
1229 - <table class="mxchat-records-table">
1230 - <thead>
1231 - <tr>
1232 - <th><?php esc_html_e('ID', 'mxchat'); ?></th>
1233 - <th><?php esc_html_e('Content', 'mxchat'); ?></th>
1234 - <th><?php esc_html_e('Source', 'mxchat'); ?></th>
1235 - <th><?php esc_html_e('Actions', 'mxchat'); ?></th>
1236 - </tr>
1237 - </thead>
1238 - <tbody>
1239 - <?php if ($prompts) : ?>
1240 - <?php foreach ($prompts as $prompt) : ?>
1241 - <tr id="prompt-<?php echo esc_attr($prompt->id); ?>">
1242 - <td><?php echo esc_html($prompt->id); ?></td>
1243 - <td class="mxchat-content-cell">
1244 - <div class="content-view">
1245 - <?php echo wp_kses_post(wpautop(esc_textarea($prompt->article_content))); ?>
1246 - </div>
1247 - <textarea class="content-edit" style="display:none;">
1248 - <?php echo esc_textarea($prompt->article_content); ?>
1249 - </textarea>
1250 - </td>
1251 - <td class="mxchat-url-cell">
1252 - <?php if (!empty($prompt->source_url)) : ?>
1253 - <a href="<?php echo esc_url($prompt->source_url); ?>" target="_blank">
1254 - <span class="dashicons dashicons-external"></span>
1255 - <?php esc_html_e('View Source', 'mxchat'); ?>
1256 - </a>
1257 - <?php else : ?>
1258 - <span class="mxchat-na"><?php esc_html_e('N/A', 'mxchat'); ?></span>
1259 - <?php endif; ?>
1260 - </td>
1261 - <td class="mxchat-actions-cell">
1262 - <button class="mxchat-button-icon edit-button"
1263 - data-id="<?php echo esc_attr($prompt->id); ?>">
1264 - <span class="dashicons dashicons-edit"></span>
1265 - </button>
1266 - <button class="mxchat-button-icon save-button"
1267 - data-id="<?php echo esc_attr($prompt->id); ?>"
1268 - style="display:none;"
1269 - data-nonce="<?php echo wp_create_nonce('mxchat_save_inline_nonce'); ?>">
1270 - <span class="dashicons dashicons-saved"></span>
1271 - </button>
1272 - <a href="<?php echo esc_url(admin_url(
1273 - 'admin-post.php?action=mxchat_delete_prompt&id=' . esc_attr($prompt->id)
1274 - . '&_wpnonce=' . wp_create_nonce('mxchat_delete_prompt_nonce')
1275 - )); ?>"
1276 - class="mxchat-button-icon delete-button"
1277 - onclick="return confirm('<?php esc_attr_e('Are you sure you want to delete this entry?', 'mxchat'); ?>');">
1278 - <span class="dashicons dashicons-trash"></span>
1279 - </a>
1280 - </td>
1281 - </tr>
1282 - <?php endforeach; ?>
1283 - <?php else : ?>
1284 - <tr>
1285 - <td colspan="4" class="mxchat-no-records">
1286 - <?php esc_html_e('No knowledge base entries found.', 'mxchat'); ?>
1287 - </td>
1288 - </tr>
1289 - <?php endif; ?>
1290 - </tbody>
1291 - </table>
1292 - </div>
1806 +/**
1807 + * Translate text using xAI (Grok) API
1808 + */
1809 +private function translate_with_xai($api_key, $model, $system_prompt, $text) {
1810 + $response = wp_remote_post('https://api.x.ai/v1/chat/completions', [
1811 + 'timeout' => 60,
1812 + 'headers' => [
1813 + 'Authorization' => 'Bearer ' . $api_key,
1814 + 'Content-Type' => 'application/json'
1815 + ],
1816 + 'body' => wp_json_encode([
1817 + 'model' => $model,
1818 + 'messages' => [
1819 + ['role' => 'system', 'content' => $system_prompt],
1820 + ['role' => 'user', 'content' => $text]
1821 + ],
1822 + 'temperature' => 0.3
1823 + ])
1824 + ]);
1293 1825
1294 - <?php if ($page_links) : ?>
1295 - <div class="mxchat-pagination">
1296 - <?php echo wp_kses_post($page_links); ?>
1297 - </div>
1298 - <?php endif; ?>
1299 -</div>
1300 -</div>
1301 -</div>
1302 -<?php
1303 -}
1826 + if (is_wp_error($response)) {
1827 + return $response;
1828 + }
1304 1829
1830 + $body = json_decode(wp_remote_retrieve_body($response), true);
1305 1831
1306 -public function mxchat_handle_delete_all_prompts() {
1307 - // Verify nonce
1308 - if (!isset($_POST['mxchat_delete_all_prompts_nonce']) || !wp_verify_nonce($_POST['mxchat_delete_all_prompts_nonce'], 'mxchat_delete_all_prompts_action')) {
1309 - wp_die(__('Nonce verification failed.', 'mxchat'));
1832 + if (isset($body['error'])) {
1833 + return new WP_Error('api_error', $body['error']['message']);
1310 1834 }
1311 1835
1312 - // Check permissions
1313 - if (!current_user_can('manage_options')) {
1314 - wp_die(__('You do not have sufficient permissions to delete all prompts.', 'mxchat'));
1836 + if (isset($body['choices'][0]['message']['content'])) {
1837 + return $body['choices'][0]['message']['content'];
1315 1838 }
1316 1839
1317 - global $wpdb;
1318 - $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
1840 + return new WP_Error('api_error', 'Invalid API response');
1841 +}
1319 1842
1320 - // Delete all prompts from the table
1321 - $wpdb->query("DELETE FROM {$table_name}");
1843 +/**
1844 + * Translate text using DeepSeek API
1845 + */
1846 +private function translate_with_deepseek($api_key, $model, $system_prompt, $text) {
1847 + $response = wp_remote_post('https://api.deepseek.com/v1/chat/completions', [
1848 + 'timeout' => 60,
1849 + 'headers' => [
1850 + 'Authorization' => 'Bearer ' . $api_key,
1851 + 'Content-Type' => 'application/json'
1852 + ],
1853 + 'body' => wp_json_encode([
1854 + 'model' => $model,
1855 + 'messages' => [
1856 + ['role' => 'system', 'content' => $system_prompt],
1857 + ['role' => 'user', 'content' => $text]
1858 + ],
1859 + 'temperature' => 0.3
1860 + ])
1861 + ]);
1322 1862
1323 - // Clear relevant cache
1324 - wp_cache_delete('all_prompts', 'mxchat_prompts');
1863 + if (is_wp_error($response)) {
1864 + return $response;
1865 + }
1325 1866
1326 - // Redirect back with a success message
1327 - $redirect_url = add_query_arg(array(
1328 - 'page' => 'mxchat-prompts',
1329 - 'all_deleted' => 'true'
1330 - ), admin_url('admin.php'));
1867 + $body = json_decode(wp_remote_retrieve_body($response), true);
1331 1868
1332 - wp_safe_redirect($redirect_url);
1333 - exit;
1334 -}
1335 -public function mxchat_handle_delete_prompt() {
1336 - // Sanitize and validate nonce
1337 - $nonce = isset($_GET['_wpnonce']) ? sanitize_text_field(wp_unslash($_GET['_wpnonce'])) : '';
1338 - if (empty($nonce) || !wp_verify_nonce($nonce, 'mxchat_delete_prompt_nonce')) {
1339 - wp_die(esc_html__('Nonce verification failed.', 'mxchat'));
1869 + if (isset($body['error'])) {
1870 + return new WP_Error('api_error', $body['error']['message']);
1340 1871 }
1341 1872
1342 - // Check permissions
1343 - if (!current_user_can('manage_options')) {
1344 - wp_die(esc_html__('You do not have sufficient permissions to delete prompts.', 'mxchat'));
1873 + if (isset($body['choices'][0]['message']['content'])) {
1874 + return $body['choices'][0]['message']['content'];
1345 1875 }
1346 1876
1347 - // Validate and sanitize ID parameter
1348 - $id = isset($_GET['id']) ? intval($_GET['id']) : 0;
1349 - if ($id <= 0) {
1350 - wp_die(esc_html__('Invalid prompt ID.', 'mxchat'));
1351 - }
1352 -
1353 - global $wpdb;
1354 - $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
1355 -
1356 - // Clear cache and delete prompt
1357 - wp_cache_delete('prompt_' . $id, 'mxchat_prompts');
1358 - $wpdb->delete($table_name, array('id' => $id), array('%d'));
1359 -
1360 - wp_safe_redirect(add_query_arg(array('page' => 'mxchat-prompts', 'deleted' => 'true'), admin_url('admin.php')));
1361 - exit;
1877 + return new WP_Error('api_error', 'Invalid API response');
1362 1878 }
1363 1879
1880 +/**
1881 + * Translate text using Google Gemini API
1882 + */
1883 +private function translate_with_gemini($api_key, $model, $system_prompt, $text) {
1884 + $url = 'https://generativelanguage.googleapis.com/v1beta/models/' . $model . ':generateContent?key=' . $api_key;
1364 1885
1365 -public function mxchat_generate_embedding($text) {
1366 - //error_log('Starting embedding generation for text: ' . substr($text, 0, 100) . '...');
1886 + $response = wp_remote_post($url, [
1887 + 'timeout' => 60,
1888 + 'headers' => [
1889 + 'Content-Type' => 'application/json'
1890 + ],
1891 + 'body' => wp_json_encode([
1892 + 'contents' => [
1893 + [
1894 + 'parts' => [
1895 + ['text' => $system_prompt . "\n\n" . $text]
1896 + ]
1897 + ]
1898 + ],
1899 + 'generationConfig' => [
1900 + 'temperature' => 0.3
1901 + ]
1902 + ])
1903 + ]);
1367 1904
1368 - $options = get_option('mxchat_options');
1369 - $selected_model = $options['embedding_model'] ?? 'text-embedding-ada-002';
1370 - //error_log('Selected embedding model: ' . $selected_model);
1905 + if (is_wp_error($response)) {
1906 + return $response;
1907 + }
1371 1908
1372 - // Determine provider and endpoint
1373 - if (strpos($selected_model, 'voyage') === 0) {
1374 - $api_key = $options['voyage_api_key'] ?? '';
1375 - $endpoint = 'https://api.voyageai.com/v1/embeddings';
1376 - } else {
1377 - $api_key = $options['api_key'] ?? '';
1378 - $endpoint = 'https://api.openai.com/v1/embeddings';
1379 - }
1380 - //error_log('Using endpoint: ' . $endpoint);
1909 + $body = json_decode(wp_remote_retrieve_body($response), true);
1381 1910
1382 - if (empty($api_key)) {
1383 - //error_log('Error: API key not configured');
1384 - return null;
1911 + if (isset($body['error'])) {
1912 + return new WP_Error('api_error', $body['error']['message']);
1385 1913 }
1386 1914
1387 - // Prepare request body
1388 - $request_body = array(
1389 - 'model' => $selected_model,
1390 - 'input' => $text
1391 - );
1392 -
1393 - // Add output_dimension for voyage-3-large model
1394 - if ($selected_model === 'voyage-3-large') {
1395 - $request_body['output_dimension'] = 2048;
1915 + if (isset($body['candidates'][0]['content']['parts'][0]['text'])) {
1916 + return $body['candidates'][0]['content']['parts'][0]['text'];
1396 1917 }
1397 1918
1398 - //error_log('Request body prepared: ' . wp_json_encode($request_body));
1919 + return new WP_Error('api_error', 'Invalid API response');
1920 +}
1399 1921
1400 - // Make API request
1401 - $response = wp_remote_post($endpoint, array(
1402 - 'body' => wp_json_encode($request_body),
1403 - 'headers' => array(
1922 +/**
1923 + * Translate text using OpenRouter API
1924 + */
1925 +private function translate_with_openrouter($api_key, $model, $system_prompt, $text) {
1926 + $response = wp_remote_post('https://openrouter.ai/api/v1/chat/completions', [
1927 + 'timeout' => 60,
1928 + 'headers' => [
1404 1929 'Authorization' => 'Bearer ' . $api_key,
1405 - 'Content-Type' => 'application/json'
1406 - ),
1407 - 'timeout' => 30
1408 - ));
1930 + 'Content-Type' => 'application/json',
1931 + 'HTTP-Referer' => home_url(),
1932 + 'X-Title' => 'MxChat Translation'
1933 + ],
1934 + 'body' => wp_json_encode([
1935 + 'model' => $model,
1936 + 'messages' => [
1937 + ['role' => 'system', 'content' => $system_prompt],
1938 + ['role' => 'user', 'content' => $text]
1939 + ],
1940 + 'temperature' => 0.3
1941 + ])
1942 + ]);
1409 1943
1410 - // Handle wp_remote_post errors
1411 1944 if (is_wp_error($response)) {
1412 - //error_log('WP Remote Post Error: ' . $response->get_error_message());
1413 - return null;
1945 + return $response;
1414 1946 }
1415 1947
1416 - // Get and check HTTP response code
1417 - $http_code = wp_remote_retrieve_response_code($response);
1418 - //error_log('API Response Code: ' . $http_code);
1948 + $body = json_decode(wp_remote_retrieve_body($response), true);
1419 1949
1420 - if ($http_code !== 200) {
1421 - //error_log('API Error Response Body: ' . wp_remote_retrieve_body($response));
1422 - return null;
1950 + if (isset($body['error'])) {
1951 + return new WP_Error('api_error', $body['error']['message']);
1423 1952 }
1424 1953
1425 - // Parse response body
1426 - $response_data = json_decode(wp_remote_retrieve_body($response), true);
1427 -
1428 - if (json_last_error() !== JSON_ERROR_NONE) {
1429 - //error_log('JSON Parse Error: ' . json_last_error_msg());
1430 - return null;
1954 + if (isset($body['choices'][0]['message']['content'])) {
1955 + return $body['choices'][0]['message']['content'];
1431 1956 }
1432 1957
1433 - // Both APIs use the same structure, so we can extract the embedding the same way
1434 - if (isset($response_data['data'][0]['embedding'])) {
1435 - //error_log('Successfully extracted embedding');
1436 - //error_log('Embedding dimension: ' . count($response_data['data'][0]['embedding']));
1437 - return $response_data['data'][0]['embedding'];
1438 - } else {
1439 - //error_log('Error: No embedding found in response. Response structure: ' . wp_json_encode($response_data));
1440 - return null;
1441 - }
1958 + return new WP_Error('api_error', 'Invalid API response');
1442 1959 }
1443 1960
1961 +public function mxchat_fetch_chat_history() {
1962 + global $wpdb;
1963 + $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
1964 + $url_clicks_table = $wpdb->prefix . 'mxchat_url_clicks';
1444 1965
1445 -public function mxchat_delete_chat_history() {
1446 - if (!current_user_can('manage_options')) {
1447 - echo wp_json_encode(['error' => esc_html__('You do not have sufficient permissions.', 'mxchat')]);
1448 - wp_die();
1449 - }
1966 + if (!current_user_can('manage_options')) {
1967 + wp_send_json_error(['message' => 'Insufficient permissions']);
1968 + wp_die();
1969 + }
1450 1970
1451 - check_ajax_referer('mxchat_delete_chat_history', 'security');
1971 + $page = isset($_POST['page']) ? absint($_POST['page']) : 1;
1972 + $per_page = isset($_POST['per_page']) ? absint($_POST['per_page']) : 50;
1973 + $offset = ($page - 1) * $per_page;
1974 + $search = isset($_POST['search']) ? sanitize_text_field($_POST['search']) : '';
1975 + $sort_order = isset($_POST['sort_order']) && $_POST['sort_order'] === 'asc' ? 'ASC' : 'DESC';
1452 1976
1453 - global $wpdb;
1454 - $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
1977 + // Build search condition
1978 + $search_condition = '';
1979 + $search_params = [];
1980 + if (!empty($search)) {
1981 + $search_condition = "WHERE (
1982 + session_id LIKE %s
1983 + OR user_email LIKE %s
1984 + OR user_name LIKE %s
1985 + OR user_identifier LIKE %s
1986 + OR message LIKE %s
1987 + )";
1988 + $search_params = array_fill(0, 5, '%' . $wpdb->esc_like($search) . '%');
1989 + }
1455 1990
1456 - if (isset($_POST['delete_session_ids']) && is_array($_POST['delete_session_ids'])) {
1457 - foreach ($_POST['delete_session_ids'] as $session_id) {
1458 - $session_id_sanitized = sanitize_text_field($session_id);
1991 + // Get total count
1992 + $count_query = !empty($search)
1993 + ? $wpdb->prepare("SELECT COUNT(DISTINCT session_id) FROM {$table_name} {$search_condition}", $search_params)
1994 + : "SELECT COUNT(DISTINCT session_id) FROM {$table_name}";
1995 + $total_sessions = (int) $wpdb->get_var($count_query);
1459 1996
1460 - // Clear relevant cache before deletion
1461 - $cache_key = 'chat_session_' . $session_id_sanitized;
1462 - wp_cache_delete($cache_key, 'mxchat_chat_sessions');
1997 + // Get session IDs for current page (sorted by newest or oldest)
1998 + $session_query = !empty($search)
1999 + ? $wpdb->prepare(
2000 + "SELECT DISTINCT t.session_id FROM {$table_name} t {$search_condition}
2001 + GROUP BY t.session_id ORDER BY MAX(t.timestamp) {$sort_order} LIMIT %d OFFSET %d",
2002 + array_merge($search_params, [$per_page, $offset])
2003 + )
2004 + : $wpdb->prepare(
2005 + "SELECT DISTINCT session_id FROM {$table_name}
2006 + GROUP BY session_id ORDER BY MAX(timestamp) {$sort_order} LIMIT %d OFFSET %d",
2007 + $per_page, $offset
2008 + );
2009 + $session_ids = $wpdb->get_col($session_query);
1463 2010
1464 - // Perform the deletion
1465 - $wpdb->delete($table_name, ['session_id' => $session_id_sanitized]);
1466 - }
2011 + $total_pages = ceil($total_sessions / $per_page);
1467 2012
1468 - // Optionally, clear a general cache if you have one
1469 - wp_cache_delete('all_chat_sessions', 'mxchat_chat_sessions');
1470 -
1471 - echo wp_json_encode(['success' => esc_html__('Selected chat sessions have been deleted.', 'mxchat')]);
1472 - } else {
1473 - echo wp_json_encode(['error' => esc_html__('No chat sessions selected for deletion.', 'mxchat')]);
1474 - }
1475 -
2013 + if (empty($session_ids)) {
2014 + wp_send_json([
2015 + 'success' => true,
2016 + 'sessions' => [],
2017 + 'page' => $page,
2018 + 'total_pages' => 0,
2019 + 'total_sessions' => 0,
2020 + 'showing_start' => 0,
2021 + 'showing_end' => 0
2022 + ]);
1476 2023 wp_die();
1477 2024 }
1478 2025
2026 + // Check for optional columns/tables
2027 + $url_table_exists = $wpdb->get_var("SHOW TABLES LIKE '$url_clicks_table'") === $url_clicks_table;
2028 + $originating_columns_exist = !empty($wpdb->get_results("SHOW COLUMNS FROM $table_name LIKE 'originating_page_url'"));
1479 2029
1480 -public function mxchat_save_inline_prompt() {
1481 - // Check for nonce security
1482 - check_ajax_referer('mxchat_save_inline_nonce');
2030 + // Build session list data
2031 + $sessions = [];
2032 + foreach ($session_ids as $session_id) {
2033 + // Get session metadata
2034 + $session_data = $originating_columns_exist
2035 + ? $wpdb->get_row($wpdb->prepare(
2036 + "SELECT user_email, user_name, user_identifier, originating_page_url, originating_page_title, timestamp
2037 + FROM {$table_name} WHERE session_id = %s ORDER BY timestamp ASC LIMIT 1",
2038 + $session_id
2039 + ))
2040 + : $wpdb->get_row($wpdb->prepare(
2041 + "SELECT user_email, user_name, user_identifier, timestamp FROM {$table_name}
2042 + WHERE session_id = %s LIMIT 1",
2043 + $session_id
2044 + ));
1483 2045
1484 - // Verify permissions
1485 - if (!current_user_can('manage_options')) {
1486 - wp_send_json_error(esc_html__('Permission denied.', 'mxchat'));
1487 - return;
1488 - }
2046 + // Get message count and latest timestamp
2047 + $message_stats = $wpdb->get_row($wpdb->prepare(
2048 + "SELECT COUNT(*) as count, MAX(timestamp) as latest FROM {$table_name} WHERE session_id = %s",
2049 + $session_id
2050 + ));
1489 2051
1490 - global $wpdb;
1491 - $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
2052 + // Get first user message as preview
2053 + $first_user_msg = $wpdb->get_var($wpdb->prepare(
2054 + "SELECT message FROM {$table_name} WHERE session_id = %s AND role = 'user' ORDER BY timestamp ASC LIMIT 1",
2055 + $session_id
2056 + ));
2057 + $preview = $first_user_msg ? wp_trim_words(wp_strip_all_tags(stripslashes($first_user_msg)), 12, '...') : 'No messages';
1492 2058
1493 - // Validate and sanitize input data
1494 - $prompt_id = isset($_POST['id']) ? absint($_POST['id']) : 0;
1495 - $article_content = isset($_POST['article_content']) ? sanitize_textarea_field($_POST['article_content']) : '';
1496 - $article_url = isset($_POST['article_url']) ? esc_url_raw($_POST['article_url']) : '';
2059 + // Build display name
2060 + $user_email = !empty($session_data->user_email) ? $session_data->user_email : '';
2061 + $user_name = !empty($session_data->user_name) ? $session_data->user_name : '';
2062 + $user_identifier = !empty($session_data->user_identifier) ? $session_data->user_identifier : 'Guest';
1497 2063
1498 - if ($prompt_id > 0 && !empty($article_content)) {
1499 - // Re-generate the embedding vector for the updated content
1500 - $embedding_vector = $this->mxchat_generate_embedding($article_content);
2064 + $display_name = $user_name ?: ($user_email ? explode('@', $user_email)[0] : $user_identifier);
2065 + $display_sub = $user_email ?: ('ID: ' . $user_identifier);
1501 2066
1502 - if (is_array($embedding_vector)) {
1503 - // Serialize the embedding vector before storing it
1504 - $embedding_vector_serialized = serialize($embedding_vector);
2067 + // Format time - show relative for recent, date for older
2068 + $timestamp = strtotime($message_stats->latest . ' UTC');
2069 + $now = time();
2070 + $diff = $now - $timestamp;
2071 + if ($diff < 3600) {
2072 + $time_display = floor($diff / 60) . 'm ago';
2073 + } elseif ($diff < 86400) {
2074 + $time_display = floor($diff / 3600) . 'h ago';
2075 + } elseif ($diff < 604800) {
2076 + $time_display = floor($diff / 86400) . 'd ago';
2077 + } else {
2078 + $time_display = wp_date('M j', $timestamp);
2079 + }
1505 2080
1506 - // Update the prompt in the database
1507 - $updated = $wpdb->update(
1508 - $table_name,
1509 - array(
1510 - 'article_content' => $article_content,
1511 - 'embedding_vector' => $embedding_vector_serialized,
1512 - 'source_url' => $article_url,
1513 - ),
1514 - array('id' => $prompt_id),
1515 - array('%s', '%s', '%s'),
1516 - array('%d')
1517 - );
2081 + // Get initials for avatar
2082 + $initials = strtoupper(substr($display_name, 0, 2));
2083 + if (strlen($display_name) > 2 && strpos($display_name, ' ') !== false) {
2084 + $parts = explode(' ', $display_name);
2085 + $initials = strtoupper(substr($parts[0], 0, 1) . substr(end($parts), 0, 1));
2086 + }
1518 2087
1519 - if ($updated !== false) {
1520 - wp_send_json_success();
1521 - } else {
1522 - wp_send_json_error(esc_html__('Database update failed.', 'mxchat'));
1523 - }
1524 - } else {
1525 - wp_send_json_error(esc_html__('Embedding generation failed.', 'mxchat'));
1526 - }
1527 - } else {
1528 - wp_send_json_error(esc_html__('Invalid data.', 'mxchat'));
2088 + $sessions[] = [
2089 + 'session_id' => $session_id,
2090 + 'display_name' => $display_name,
2091 + 'display_sub' => $display_sub,
2092 + 'initials' => $initials,
2093 + 'preview' => $preview,
2094 + 'message_count' => (int) $message_stats->count,
2095 + 'time_display' => $time_display,
2096 + 'timestamp' => $message_stats->latest
2097 + ];
1529 2098 }
2099 +
2100 + wp_send_json([
2101 + 'success' => true,
2102 + 'sessions' => $sessions,
2103 + 'page' => $page,
2104 + 'total_pages' => $total_pages,
2105 + 'total_sessions' => $total_sessions,
2106 + 'showing_start' => $offset + 1,
2107 + 'showing_end' => min($offset + $per_page, $total_sessions)
2108 + ]);
2109 + wp_die();
1530 2110 }
1531 2111
2112 +/**
2113 + * Fetch single conversation details for split-panel view
2114 + */
2115 +public function mxchat_fetch_conversation() {
2116 + global $wpdb;
2117 + $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
2118 + $url_clicks_table = $wpdb->prefix . 'mxchat_url_clicks';
1532 2119
2120 + if (!current_user_can('manage_options')) {
2121 + wp_send_json_error(['message' => 'Insufficient permissions']);
2122 + wp_die();
2123 + }
1533 2124
1534 -// Add this method to handle post updates
1535 -public function handle_post_update($post_id, $post, $update) {
1536 - // Basic validation checks
1537 - if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE || wp_is_post_revision($post_id)) {
1538 - return;
2125 + $session_id = isset($_POST['session_id']) ? sanitize_text_field($_POST['session_id']) : '';
2126 + if (empty($session_id)) {
2127 + wp_send_json_error(['message' => 'No session ID provided']);
2128 + wp_die();
1539 2129 }
1540 2130
1541 - // Only process published content
1542 - if (!in_array($post->post_status, array('publish'))) {
1543 - return;
1544 - }
2131 + // Check for optional columns/tables
2132 + $url_table_exists = $wpdb->get_var("SHOW TABLES LIKE '$url_clicks_table'") === $url_clicks_table;
2133 + $originating_columns_exist = !empty($wpdb->get_results("SHOW COLUMNS FROM $table_name LIKE 'originating_page_url'"));
1545 2134
1546 - // Check if sync is enabled for this post type
1547 - $post_type = $post->post_type;
1548 - if (!in_array($post_type, array('post', 'page'))) {
1549 - return;
1550 - }
2135 + // Get session metadata
2136 + $session_data = $originating_columns_exist
2137 + ? $wpdb->get_row($wpdb->prepare(
2138 + "SELECT user_email, user_name, user_identifier, originating_page_url, originating_page_title, timestamp
2139 + FROM {$table_name} WHERE session_id = %s ORDER BY timestamp ASC LIMIT 1",
2140 + $session_id
2141 + ))
2142 + : $wpdb->get_row($wpdb->prepare(
2143 + "SELECT user_email, user_name, user_identifier, timestamp FROM {$table_name}
2144 + WHERE session_id = %s LIMIT 1",
2145 + $session_id
2146 + ));
1551 2147
1552 - $sync_option = ($post_type === 'post') ? 'mxchat_auto_sync_posts' : 'mxchat_auto_sync_pages';
1553 - if (get_option($sync_option) != '1') {
1554 - return;
2148 + if (!$session_data) {
2149 + wp_send_json_error(['message' => 'Session not found']);
2150 + wp_die();
1555 2151 }
1556 2152
1557 - // Prepare content and URL
1558 - $content = wp_strip_all_tags($post->post_content);
1559 - $url = get_permalink($post_id);
1560 -
1561 - // Generate embedding vector
1562 - $embedding_vector = $this->mxchat_generate_embedding($content);
1563 - if (!$embedding_vector) {
1564 - return;
2153 + // Get clicked URLs
2154 + $clicked_urls = [];
2155 + if ($url_table_exists) {
2156 + $url_clicks = $wpdb->get_results($wpdb->prepare(
2157 + "SELECT DISTINCT clicked_url FROM {$url_clicks_table}
2158 + WHERE session_id = %s ORDER BY click_timestamp ASC",
2159 + $session_id
2160 + ));
2161 + foreach ($url_clicks as $click) {
2162 + $clicked_urls[] = $click->clicked_url;
2163 + }
1565 2164 }
1566 2165
1567 - // Check for Pinecone addon and its settings
1568 - $pinecone_settings = get_option('mxchat_pinecone_addon_options');
1569 - $use_pinecone = false;
2166 + // Get all messages
2167 + $messages = $wpdb->get_results($wpdb->prepare(
2168 + "SELECT * FROM {$table_name} WHERE session_id = %s ORDER BY timestamp ASC",
2169 + $session_id
2170 + ));
1570 2171
1571 - if ($pinecone_settings && is_array($pinecone_settings)) {
1572 - // Check if Pinecone is enabled and all required settings are present
1573 - $use_pinecone = (
1574 - isset($pinecone_settings['mxchat_use_pinecone']) &&
1575 - $pinecone_settings['mxchat_use_pinecone'] === '1' &&
1576 - !empty($pinecone_settings['mxchat_pinecone_api_key']) &&
1577 - !empty($pinecone_settings['mxchat_pinecone_host']) &&
1578 - !empty($pinecone_settings['mxchat_pinecone_index']) &&
1579 - !empty($pinecone_settings['mxchat_pinecone_environment'])
1580 - );
1581 - }
2172 + // Build user info
2173 + $user_email = !empty($session_data->user_email) ? $session_data->user_email : '';
2174 + $user_name = !empty($session_data->user_name) ? $session_data->user_name : '';
2175 + $user_identifier = !empty($session_data->user_identifier) ? $session_data->user_identifier : 'Guest';
1582 2176
1583 - if ($use_pinecone) {
1584 - // Use Pinecone
1585 - $pinecone_result = $this->store_in_pinecone_main(
1586 - $embedding_vector,
1587 - $content,
1588 - $url,
1589 - $pinecone_settings['mxchat_pinecone_api_key'],
1590 - $pinecone_settings['mxchat_pinecone_environment'],
1591 - $pinecone_settings['mxchat_pinecone_index']
1592 - );
2177 + $display_name = $user_name ?: ($user_email ? explode('@', $user_email)[0] : $user_identifier);
2178 + $display_sub = $user_email ?: ('ID: ' . $user_identifier);
1593 2179
1594 - if (!$pinecone_result['success']) {
1595 - //error_log('MXChat: Pinecone sync failed - falling back to WordPress DB');
1596 - $this->store_in_wordpress_db($content, $url, $embedding_vector);
1597 - }
1598 - } else {
1599 - // Fallback to WordPress DB storage
1600 - $this->store_in_wordpress_db($content, $url, $embedding_vector);
2180 + // Get initials
2181 + $initials = strtoupper(substr($display_name, 0, 2));
2182 + if (strlen($display_name) > 2 && strpos($display_name, ' ') !== false) {
2183 + $parts = explode(' ', $display_name);
2184 + $initials = strtoupper(substr($parts[0], 0, 1) . substr(end($parts), 0, 1));
1601 2185 }
1602 -}
1603 -// Modified storage function to add type field
1604 -private function store_in_pinecone_main($embedding_vector, $content, $url, $api_key, $environment, $index_name, $vector_id = null) {
1605 - $vector_id = $vector_id ?: md5($url);
1606 - $options = get_option('mxchat_pinecone_addon_options');
1607 - $host = $options['mxchat_pinecone_host'] ?? '';
1608 2186
1609 - if (empty($host)) {
1610 - return array(
1611 - 'success' => false,
1612 - 'message' => 'Pinecone host is not configured. Please set the host in your settings.'
1613 - );
2187 + // Page info
2188 + $page_url = $originating_columns_exist && !empty($session_data->originating_page_url) ? $session_data->originating_page_url : '';
2189 + $page_title = '';
2190 + if ($page_url) {
2191 + $page_title = !empty($session_data->originating_page_title) ? $session_data->originating_page_title : parse_url($page_url, PHP_URL_PATH);
1614 2192 }
1615 2193
1616 - // Determine if this is a product URL
1617 - $is_product = (strpos($url, '/product/') !== false || strpos($url, '/shop/') !== false);
2194 + // Format messages for output
2195 + $formatted_messages = [];
2196 + foreach ($messages as $msg) {
2197 + $is_user = ($msg->role === 'user');
2198 + $is_bot = ($msg->role === 'bot' || $msg->role === 'assistant');
1618 2199
1619 - $api_endpoint = "https://{$host}/vectors/upsert";
1620 - $request_body = array(
1621 - 'vectors' => array(
1622 - array(
1623 - 'id' => $vector_id,
1624 - 'values' => $embedding_vector,
1625 - 'metadata' => array(
1626 - 'text' => $content,
1627 - 'source_url' => $url,
1628 - 'type' => $is_product ? 'product' : 'content',
1629 - 'last_updated' => time()
1630 - )
1631 - )
1632 - )
1633 - );
2200 + $content = wp_kses(
2201 + stripslashes($msg->message),
2202 + [
2203 + 'b' => [], 'strong' => [], 'i' => [], 'em' => [], 'u' => [],
2204 + 'br' => [], 'p' => [], 'ul' => [], 'ol' => [], 'li' => [],
2205 + 'a' => ['href' => [], 'title' => [], 'target' => []]
2206 + ]
2207 + );
2208 + $formatted_content = $this->format_transcript_message($content);
1634 2209
1635 - $response = wp_remote_post($api_endpoint, array(
1636 - 'headers' => array(
1637 - 'Api-Key' => $api_key,
1638 - 'accept' => 'application/json',
1639 - 'content-type' => 'application/json'
1640 - ),
1641 - 'body' => wp_json_encode($request_body),
1642 - 'timeout' => 30,
1643 - 'data_format' => 'body'
1644 - ));
2210 + $has_rag = $is_bot && !empty($msg->rag_context);
1645 2211
1646 - if (is_wp_error($response)) {
1647 - return array(
1648 - 'success' => false,
1649 - 'message' => $response->get_error_message()
1650 - );
2212 + $formatted_messages[] = [
2213 + 'id' => $msg->id,
2214 + 'role' => $msg->role,
2215 + 'is_user' => $is_user,
2216 + 'is_bot' => $is_bot,
2217 + 'content' => $formatted_content,
2218 + 'timestamp' => wp_date('g:i A', strtotime($msg->timestamp . ' UTC')),
2219 + 'full_timestamp' => wp_date('F j, Y g:i A', strtotime($msg->timestamp . ' UTC')),
2220 + 'has_rag' => $has_rag
2221 + ];
1651 2222 }
1652 2223
1653 - $response_code = wp_remote_retrieve_response_code($response);
1654 - if ($response_code !== 200) {
1655 - $body = wp_remote_retrieve_body($response);
1656 - return array(
1657 - 'success' => false,
1658 - 'message' => sprintf(
1659 - 'Pinecone API error (HTTP %d): %s',
1660 - $response_code,
1661 - $body
1662 - )
1663 - );
1664 - }
2224 + // First message timestamp for "started" display
2225 + $started = !empty($messages) ? wp_date('M j, Y g:i A', strtotime($messages[0]->timestamp . ' UTC')) : '-';
1665 2226
1666 - return array(
2227 + wp_send_json([
1667 2228 'success' => true,
1668 - 'message' => 'Successfully stored in Pinecone'
1669 - );
2229 + 'session_id' => $session_id,
2230 + 'user' => [
2231 + 'name' => $display_name,
2232 + 'sub' => $display_sub,
2233 + 'initials' => $initials,
2234 + 'email' => $user_email,
2235 + 'identifier' => $user_identifier
2236 + ],
2237 + 'page' => [
2238 + 'url' => $page_url,
2239 + 'title' => $page_title
2240 + ],
2241 + 'clicked_urls' => $clicked_urls,
2242 + 'messages' => $formatted_messages,
2243 + 'message_count' => count($messages),
2244 + 'started' => $started
2245 + ]);
2246 + wp_die();
1670 2247 }
1671 -// Helper method for WordPress DB storage
1672 -private function store_in_wordpress_db($content, $url, $embedding_vector) {
1673 - global $wpdb;
1674 - $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
1675 2248
1676 - $existing_entry = $wpdb->get_row(
1677 - $wpdb->prepare("SELECT id FROM $table_name WHERE source_url = %s", $url)
1678 - );
1679 2249
1680 - if ($existing_entry) {
1681 - $wpdb->update(
1682 - $table_name,
1683 - array(
1684 - 'article_content' => $content,
1685 - 'embedding_vector' => serialize($embedding_vector),
1686 - 'timestamp' => current_time('mysql')
1687 - ),
1688 - array('id' => $existing_entry->id),
1689 - array('%s', '%s', '%s')
2250 +/**
2251 + * ALTERNATIVE: Simpler helper method using string replacement
2252 + */
2253 +private function highlight_clicked_links($message_content, $clicked_urls) {
2254 + if (empty($clicked_urls)) {
2255 + return wp_kses(
2256 + $message_content,
2257 + [
2258 + 'b' => [], 'strong' => [], 'i' => [], 'em' => [], 'u' => [],
2259 + 'br' => [], 'p' => [], 'ul' => [], 'ol' => [], 'li' => [],
2260 + 'a' => ['href' => [], 'title' => [], 'target' => [], 'class' => []]
2261 + ]
1690 2262 );
1691 - } else {
1692 - $wpdb->insert(
1693 - $table_name,
1694 - array(
1695 - 'article_content' => $content,
1696 - 'source_url' => $url,
1697 - 'embedding_vector' => serialize($embedding_vector),
1698 - 'timestamp' => current_time('mysql')
1699 - ),
1700 - array('%s', '%s', '%s', '%s')
1701 - );
1702 2263 }
1703 -}
1704 -/**
1705 - * Handle deletion of posts and pages from both Pinecone and WordPress DB
1706 - *
1707 - * @param int $post_id The ID of the post being deleted
1708 - * @return void
1709 - */
1710 -public function mxchat_handle_post_delete($post_id) {
1711 - // Get post data before it's deleted
1712 - $post = get_post($post_id);
1713 2264
1714 - // Basic validation
1715 - if (!$post || wp_is_post_revision($post_id)) {
1716 - return;
1717 - }
2265 + // First apply standard sanitization
2266 + $message_content = wp_kses(
2267 + $message_content,
2268 + [
2269 + 'b' => [], 'strong' => [], 'i' => [], 'em' => [], 'u' => [],
2270 + 'br' => [], 'p' => [], 'ul' => [], 'ol' => [], 'li' => [],
2271 + 'a' => ['href' => [], 'title' => [], 'target' => [], 'class' => []]
2272 + ]
2273 + );
1718 2274
1719 - // Check post type
1720 - $post_type = $post->post_type;
1721 - if (!in_array($post_type, array('post', 'page'))) {
1722 - return;
1723 - }
2275 + // Process each clicked URL
2276 + foreach ($clicked_urls as $clicked_url) {
2277 + // Try multiple patterns to catch different link formats
2278 + $patterns = [
2279 + // Standard link format
2280 + '/<a([^>]*href=["\']' . preg_quote($clicked_url, '/') . '["\'][^>]*)>/i',
2281 + // Link with trailing slash
2282 + '/<a([^>]*href=["\']' . preg_quote(rtrim($clicked_url, '/'), '/') . '\/?["\'][^>]*)>/i',
2283 + // Encoded entities version
2284 + '/<a([^>]*href=["\']' . preg_quote(htmlentities($clicked_url), '/') . '["\'][^>]*)>/i',
2285 + ];
1724 2286
1725 - // Check if sync is enabled for this post type
1726 - $sync_option = ($post_type === 'post') ? 'mxchat_auto_sync_posts' : 'mxchat_auto_sync_pages';
1727 - if (get_option($sync_option) != '1') {
1728 - return;
1729 - }
2287 + foreach ($patterns as $pattern) {
2288 + if (preg_match($pattern, $message_content)) {
2289 + $message_content = preg_replace_callback(
2290 + $pattern,
2291 + function($matches) {
2292 + $full_match = $matches[0];
2293 + $attributes = $matches[1];
1730 2294
1731 - // Get the URL before post is deleted
1732 - $source_url = get_permalink($post_id);
1733 - if (!$source_url) {
1734 - //error_log('MXChat: Failed to get permalink for post ' . $post_id);
1735 - return;
1736 - }
2295 + // Check if it already has the class
2296 + if (strpos($full_match, 'mxchat-clicked-link') !== false) {
2297 + return $full_match;
2298 + }
1737 2299
1738 - // Check for Pinecone addon and its settings
1739 - $pinecone_settings = get_option('mxchat_pinecone_addon_options');
1740 - $use_pinecone = false;
1741 - if ($pinecone_settings && is_array($pinecone_settings)) {
1742 - $use_pinecone = (
1743 - isset($pinecone_settings['mxchat_use_pinecone']) &&
1744 - $pinecone_settings['mxchat_use_pinecone'] === '1' &&
1745 - !empty($pinecone_settings['mxchat_pinecone_api_key']) &&
1746 - !empty($pinecone_settings['mxchat_pinecone_host']) &&
1747 - !empty($pinecone_settings['mxchat_pinecone_index']) &&
1748 - !empty($pinecone_settings['mxchat_pinecone_environment'])
1749 - );
1750 - }
2300 + // Check if class attribute exists
2301 + if (preg_match('/class=["\']([^"\']*)["\']/', $attributes, $class_matches)) {
2302 + // Add to existing class
2303 + $new_attributes = preg_replace(
2304 + '/class=["\']([^"\']*)["\']/',
2305 + 'class="$1 mxchat-clicked-link"',
2306 + $attributes
2307 + );
2308 + } else {
2309 + // Add new class attribute
2310 + $new_attributes = $attributes . ' class="mxchat-clicked-link"';
2311 + }
1751 2312
1752 - $deletion_successful = false;
2313 + // Add title if not present
2314 + if (strpos($new_attributes, 'title=') === false) {
2315 + $new_attributes .= ' title="User clicked this link"';
2316 + }
1753 2317
1754 - if ($use_pinecone) {
1755 - try {
1756 - $pinecone_result = $this->delete_from_pinecone(
1757 - array($source_url),
1758 - $pinecone_settings['mxchat_pinecone_api_key'],
1759 - $pinecone_settings['mxchat_pinecone_environment'],
1760 - $pinecone_settings['mxchat_pinecone_index']
1761 - );
2318 + return '<a' . $new_attributes . '>';
2319 + },
2320 + $message_content
2321 + );
1762 2322
1763 - if (!$pinecone_result['success']) {
1764 - //error_log('MXChat: Pinecone deletion failed for URL: ' . $source_url . ' - ' . $pinecone_result['message']);
1765 - } else {
1766 - $deletion_successful = true;
2323 + // If we found and replaced, break out of the patterns loop
2324 + break;
1767 2325 }
1768 - } catch (Exception $e) {
1769 - //error_log('MXChat: Exception during Pinecone deletion - ' . $e->getMessage());
1770 2326 }
1771 2327 }
1772 2328
1773 - // Always attempt WordPress DB deletion, regardless of Pinecone status
1774 - try {
1775 - global $wpdb;
1776 - $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
1777 -
1778 - $result = $wpdb->delete(
1779 - $table_name,
1780 - array('source_url' => $source_url),
1781 - array('%s')
1782 - );
1783 -
1784 - if ($result === false) {
1785 - //error_log('MXChat: WordPress DB deletion failed for URL: ' . $source_url);
1786 - } else {
1787 - $deletion_successful = true;
1788 - }
1789 - } catch (Exception $e) {
1790 - //error_log('MXChat: Exception during WordPress DB deletion - ' . $e->getMessage());
1791 - }
1792 -
1793 - if (!$deletion_successful) {
1794 - //error_log('MXChat: Complete deletion failure for post ID: ' . $post_id . ' URL: ' . $source_url);
1795 - }
2329 + return $message_content;
1796 2330 }
1797 2331
2332 +public function mxchat_create_prompts_page() {
2333 + //error_log('=== DEBUG: mxchat_create_prompts_page started ===');
1798 2334
1799 -public function mxchat_handle_content_submission() {
1800 - // Check if the form was submitted and the user has permission.
1801 - if (!isset($_POST['submit_content']) || !current_user_can('manage_options')) {
1802 - return;
1803 - }
1804 -
1805 - // Verify the nonce.
1806 - $nonce = isset($_POST['mxchat_submit_content_nonce']) ? sanitize_text_field(wp_unslash($_POST['mxchat_submit_content_nonce'])) : '';
1807 - if (!wp_verify_nonce($nonce, 'mxchat_submit_content_action')) {
1808 - wp_die(esc_html__('Nonce verification failed.', 'mxchat'));
1809 - }
1810 -
1811 - // Sanitize the inputs.
1812 - $article_content = sanitize_textarea_field($_POST['article_content']);
1813 - $article_url = isset($_POST['article_url']) ? esc_url_raw($_POST['article_url']) : '';
1814 -
1815 - // Generate the embedding vector.
1816 - $embedding_vector = $this->mxchat_generate_embedding($article_content);
1817 - if (!is_array($embedding_vector)) {
1818 - set_transient('mxchat_admin_notice_error',
1819 - esc_html__('Embedding generation failed. Please ensure your API key is correct and try again.', 'mxchat'),
1820 - 30
1821 - );
1822 - wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
1823 - exit;
1824 - }
1825 -
1826 - // Store in the WordPress database.
1827 2335 global $wpdb;
1828 2336 $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
1829 2337
1830 - // Ensure the source_url column exists.
1831 - if ($wpdb->get_var($wpdb->prepare("SHOW COLUMNS FROM {$table_name} LIKE %s", 'source_url')) != 'source_url') {
1832 - $wpdb->query($wpdb->prepare("ALTER TABLE {$table_name} ADD source_url VARCHAR(255) DEFAULT ''"));
1833 - }
2338 + $knowledge_manager = MxChat_Knowledge_Manager::get_instance();
2339 + $pinecone_manager = MxChat_Pinecone_Manager::get_instance();
1834 2340
1835 - // Serialize and store the embedding vector.
1836 - $embedding_vector_serialized = serialize($embedding_vector);
1837 - $inserted = $wpdb->insert(
1838 - $table_name,
1839 - array(
1840 - 'article_content' => $article_content,
1841 - 'embedding_vector' => $embedding_vector_serialized,
1842 - 'source_url' => $article_url,
1843 - ),
1844 - array('%s', '%s', '%s')
1845 - );
1846 -
1847 - if ($inserted === false) {
1848 - set_transient('mxchat_admin_notice_error',
1849 - esc_html__('Error inserting content into the database. Please try again.', 'mxchat'),
1850 - 30
1851 - );
1852 - } else {
1853 - set_transient('mxchat_admin_notice_success',
1854 - esc_html__('Content successfully submitted!', 'mxchat'),
1855 - 30
1856 - );
2341 + // Display success message if all prompts were deleted
2342 + if (isset($_GET['all_deleted']) && $_GET['all_deleted'] === 'true') {
2343 + 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>';
1857 2344 }
1858 2345
1859 - wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
1860 - exit;
1861 -}
2346 + // Set up pagination, search query, and content type filter
2347 + $nonce = isset($_GET['_wpnonce']) ? sanitize_text_field($_GET['_wpnonce']) : '';
2348 + $search_query = (!empty($nonce) && wp_verify_nonce($nonce, 'mxchat_prompts_search_nonce') && isset($_GET['search'])) ? sanitize_text_field($_GET['search']) : '';
2349 + $content_type_filter = isset($_GET['content_type']) ? sanitize_key($_GET['content_type']) : ''; // ADDED 2.5.6
2350 + $current_page = isset($_GET['paged']) ? absint($_GET['paged']) : 1;
2351 + $per_page = 25;
1862 2352
1863 -private function is_pdf_url($url, $response) {
1864 - $content_type = wp_remote_retrieve_header($response, 'content-type');
1865 - $file_extension = strtolower(pathinfo($url, PATHINFO_EXTENSION));
2353 + //error_log('DEBUG: Search query: ' . $search_query);
2354 + //error_log('DEBUG: Content type filter: ' . $content_type_filter);
2355 + //error_log('DEBUG: Current page: ' . $current_page);
2356 + //error_log('DEBUG: Per page: ' . $per_page);
1866 2357
1867 - return strpos($content_type, 'pdf') !== false || $file_extension === 'pdf';
1868 -}
1869 -private function handle_pdf_for_knowledge_base($pdf_url, $response) {
1870 - if (!current_user_can('manage_options')) {
1871 - //error_log(esc_html__('Unauthorized PDF processing attempt', 'mxchat'));
1872 - return false;
2358 + // ================================
2359 + // MULTI-BOT CONFIGURATION
2360 + // ================================
2361 +
2362 + // Check for multi-bot and set up bot selection
2363 + if (class_exists('MxChat_Multi_Bot_Manager')) {
2364 + $multi_bot_manager = MxChat_Multi_Bot_Core_Manager::get_instance();
2365 + $available_bots = $multi_bot_manager->get_available_bots();
2366 +
2367 + // Get saved bot selection (user-specific first, then site-wide default)
2368 + $user_id = get_current_user_id();
2369 + $saved_bot_id = get_user_meta($user_id, 'mxchat_selected_knowledge_bot', true);
2370 + if (empty($saved_bot_id)) {
2371 + $saved_bot_id = get_option('mxchat_current_knowledge_bot', 'default');
2372 + }
2373 +
2374 + // Allow URL override but default to saved selection
2375 + $current_bot_id = isset($_GET['bot_id']) ? sanitize_key($_GET['bot_id']) : $saved_bot_id;
2376 + $multibot_active = true;
2377 + } else {
2378 + $current_bot_id = 'default';
2379 + $multibot_active = false;
1873 2380 }
1874 2381
1875 - $pdf_url = esc_url_raw($pdf_url);
1876 - $upload_dir = wp_upload_dir();
2382 + // ================================
2383 + // DATA SOURCE CONFIGURATION
2384 + // ================================
1877 2385
1878 - if (isset($upload_dir['error']) && $upload_dir['error'] !== false) {
1879 - //error_log(sprintf(esc_html__('Upload directory error: %s', 'mxchat'), esc_html($upload_dir['error'])));
1880 - return false;
1881 - }
2386 + // Get bot-specific Pinecone settings
2387 + $pinecone_options = $pinecone_manager->mxchat_get_bot_pinecone_options($current_bot_id);
2388 + $use_pinecone = ($pinecone_options['mxchat_use_pinecone'] ?? '0') === '1';
2389 + $pinecone_api_key = $pinecone_options['mxchat_pinecone_api_key'] ?? '';
1882 2390
1883 - $pdf_filename = sanitize_file_name('mxchat_kb_' . time() . '.pdf');
1884 - $pdf_path = trailingslashit($upload_dir['path']) . $pdf_filename;
2391 + // Get OpenAI Vector Store settings
2392 + $vectorstore_options = get_option('mxchat_openai_vectorstore_options', array());
2393 + $use_vectorstore = ($vectorstore_options['mxchat_use_openai_vectorstore'] ?? '0') === '1';
1885 2394
1886 - $response_body = wp_remote_retrieve_body($response);
1887 - if (empty($response_body)) {
1888 - //error_log(esc_html__('Empty PDF response body', 'mxchat'));
1889 - return false;
1890 - }
2395 + //error_log('DEBUG: Bot ' . $current_bot_id . ' - Use Pinecone: ' . ($use_pinecone ? 'YES' : 'NO'));
2396 + //error_log('DEBUG: Bot ' . $current_bot_id . ' - Has API Key: ' . (!empty($pinecone_api_key) ? 'YES' : 'NO'));
2397 + //error_log('DEBUG: Bot ' . $current_bot_id . ' - Host: ' . ($pinecone_options['mxchat_pinecone_host'] ?? 'NOT SET'));
2398 + //error_log('DEBUG: Bot ' . $current_bot_id . ' - Namespace: ' . ($pinecone_options['mxchat_pinecone_namespace'] ?? 'NOT SET'));
1891 2399
1892 - if (!wp_mkdir_p(dirname($pdf_path))) {
1893 - //error_log(sprintf(esc_html__('Failed to create directory for PDF: %s', 'mxchat'), esc_html($pdf_path)));
1894 - return false;
1895 - }
2400 + if ($use_pinecone && !empty($pinecone_api_key)) {
2401 + //error_log('DEBUG: Using PINECONE data source with bot-specific config');
2402 + // PINECONE DATA SOURCE
2403 + $data_source = 'pinecone';
2404 +
2405 + // IMPORTANT: Pass the bot-specific $pinecone_options, not default options!
2406 + // UPDATED 2.5.6: Added content_type_filter parameter
2407 + $records = $pinecone_manager->mxchat_fetch_pinecone_records($pinecone_options, $search_query, $current_page, $per_page, $current_bot_id, $content_type_filter);
1896 2408
1897 - try {
1898 - file_put_contents($pdf_path, $response_body);
2409 + // TEMPORARY DEBUG - Add this right after the fetch call
2410 + //error_log('=== DEBUG: Bot switching issue ===');
2411 + //error_log('Current bot ID: ' . $current_bot_id);
2412 + //error_log('Use Pinecone: ' . ($use_pinecone ? 'YES' : 'NO'));
2413 + //error_log('Records returned: ' . count($records['data'] ?? []));
2414 + //error_log('Total records: ' . ($records['total'] ?? 0));
1899 2415
1900 - if (!file_exists($pdf_path)) {
1901 - throw new Exception(__('Failed to save PDF file', 'mxchat'));
2416 + // Check first few records to see their bot_id
2417 + if (!empty($records['data'])) {
2418 + foreach (array_slice($records['data'], 0, 3) as $i => $record) {
2419 + $record_bot_id = $record->bot_id ?? 'NOT_SET';
2420 + //error_log('Record ' . ($i+1) . ' bot_id: ' . $record_bot_id . ', content preview: ' . substr($record->article_content ?? '', 0, 30) . '...');
2421 + }
1902 2422 }
2423 + //error_log('=== END DEBUG ===');
1903 2424
1904 - $parser = new \Smalot\PdfParser\Parser();
1905 - $pdf = $parser->parseFile($pdf_path);
1906 - $total_pages = absint(count($pdf->getPages()));
2425 + $total_records = $records['total'] ?? 0;
2426 + $prompts = $records['data'] ?? array();
2427 + $total_in_database = $records['total_in_database'] ?? 0;
2428 + $showing_recent_only = $records['showing_recent_only'] ?? false;
1907 2429
1908 - if ($total_pages < 1) {
1909 - throw new Exception(__('Invalid PDF: no pages found', 'mxchat'));
1910 - }
2430 + $total_pages = ceil($total_records / $per_page);
1911 2431
1912 - wp_schedule_single_event(time(), 'mxchat_process_pdf_pages', array(
1913 - 'pdf_path' => $pdf_path,
1914 - 'pdf_url' => $pdf_url,
1915 - 'total_pages' => $total_pages,
1916 - 'batch_size' => absint(15),
1917 - 'batch_pause' => absint(10)
1918 - ));
2432 + } else {
2433 + //error_log('DEBUG: Using WORDPRESS DB data source');
2434 + // WORDPRESS DB DATA SOURCE (your existing logic)
2435 + $data_source = 'wordpress';
1919 2436
1920 - $status_data = array(
1921 - 'total_pages' => $total_pages,
1922 - 'processed_pages' => 0,
1923 - 'status' => 'processing',
1924 - 'last_update' => time()
1925 - );
2437 + // Initialize these variables for WordPress DB
2438 + $total_in_database = 0;
2439 + $showing_recent_only = false;
1926 2440
1927 - set_transient(
1928 - sanitize_key('mxchat_pdf_status_' . md5($pdf_url)),
1929 - array_map('sanitize_text_field', $status_data),
1930 - DAY_IN_SECONDS
1931 - );
2441 + $offset = ($current_page - 1) * $per_page;
1932 2442
1933 - return __('scheduled', 'mxchat');
2443 + // UPDATED 2.5.6: Build WHERE clause for search and content type filtering
2444 + $where_clauses = array();
2445 + $where_values = array();
1934 2446
1935 - } catch (Exception $e) {
1936 - //error_log(sprintf(esc_html__('Error preparing PDF for processing: %s', 'mxchat'), esc_html($e->getMessage())));
1937 - if (file_exists($pdf_path)) {
1938 - wp_delete_file($pdf_path);
2447 + if ($search_query) {
2448 + $where_clauses[] = "article_content LIKE %s";
2449 + $where_values[] = '%' . $wpdb->esc_like($search_query) . '%';
1939 2450 }
1940 - return false;
1941 - }
1942 -}
1943 -public static function process_pdf_pages_cron($pdf_path, $pdf_url, $total_pages, $batch_size, $batch_pause) {
1944 - // Validate inputs
1945 - $pdf_path = sanitize_text_field($pdf_path);
1946 - $pdf_url = esc_url_raw($pdf_url);
1947 - $total_pages = absint($total_pages);
1948 - $batch_size = absint($batch_size);
1949 - $batch_pause = absint($batch_pause);
1950 2451
1951 - try {
1952 - if (!file_exists($pdf_path)) {
1953 - throw new Exception(sprintf('PDF file not found at path: %s', esc_html($pdf_path)));
2452 + if ($content_type_filter) {
2453 + $where_clauses[] = "content_type = %s";
2454 + $where_values[] = $content_type_filter;
1954 2455 }
1955 2456
1956 - $parser = new \Smalot\PdfParser\Parser();
1957 - $pdf = $parser->parseFile($pdf_path);
1958 - $pages = $pdf->getPages();
2457 + $sql_where = "";
2458 + if (!empty($where_clauses)) {
2459 + $sql_where = "WHERE " . implode(" AND ", $where_clauses);
2460 + }
1959 2461
1960 - // Get current progress
1961 - $status_key = sanitize_key('mxchat_pdf_status_' . md5($pdf_url));
1962 - $status = get_transient($status_key);
2462 + // UPDATED 2.6.3: Count unique entries (by source_url) instead of individual rows
2463 + // This ensures pagination shows X entries per page, not X chunks
2464 + // Entries with empty source_url are counted individually
2465 + if (!empty($where_values)) {
2466 + // Count unique source_urls + count of rows with empty source_url
2467 + $count_query = $wpdb->prepare(
2468 + "SELECT (SELECT COUNT(DISTINCT source_url) FROM {$table_name} {$sql_where} AND source_url != '') +
2469 + (SELECT COUNT(*) FROM {$table_name} {$sql_where} AND (source_url = '' OR source_url IS NULL))",
2470 + array_merge($where_values, $where_values)
2471 + );
2472 + } else {
2473 + $count_query = "SELECT (SELECT COUNT(DISTINCT source_url) FROM {$table_name} WHERE source_url != '') +
2474 + (SELECT COUNT(*) FROM {$table_name} WHERE source_url = '' OR source_url IS NULL)";
2475 + }
2476 + $total_records = $wpdb->get_var($count_query);
2477 + $total_pages = ceil($total_records / $per_page);
1963 2478
1964 - if (!$status || !is_array($status)) {
1965 - throw new Exception('Invalid status data retrieved from transient');
2479 + // UPDATED 2.6.3: Get unique source_urls for pagination, then fetch all their rows
2480 + // Step 1: Get the source_urls for this page (distinct URLs ordered by latest timestamp)
2481 + if (!empty($where_values)) {
2482 + $urls_query = $wpdb->prepare(
2483 + "SELECT source_url, MAX(timestamp) as latest_ts FROM {$table_name} {$sql_where}
2484 + GROUP BY source_url ORDER BY latest_ts DESC LIMIT %d OFFSET %d",
2485 + array_merge($where_values, array($per_page, $offset))
2486 + );
2487 + } else {
2488 + $urls_query = $wpdb->prepare(
2489 + "SELECT source_url, MAX(timestamp) as latest_ts FROM {$table_name}
2490 + GROUP BY source_url ORDER BY latest_ts DESC LIMIT %d OFFSET %d",
2491 + $per_page, $offset
2492 + );
1966 2493 }
2494 + $page_urls = $wpdb->get_results($urls_query);
1967 2495
1968 - $start_page = absint($status['processed_pages']);
1969 - $end_page = min($start_page + $batch_size, $total_pages);
2496 + // Step 2: Fetch all rows for these source_urls
2497 + $prompts = array();
2498 + if (!empty($page_urls)) {
2499 + // Build URL order map to preserve newest-first ordering from step 1
2500 + $url_order_map = array();
2501 + $url_list = array();
2502 + $has_empty_url = false;
2503 + $order_index = 0;
2504 + foreach ($page_urls as $url_row) {
2505 + if (empty($url_row->source_url)) {
2506 + $has_empty_url = true;
2507 + $url_order_map['__empty__'] = $order_index++;
2508 + } else {
2509 + $url_list[] = $url_row->source_url;
2510 + $url_order_map[$url_row->source_url] = $order_index++;
2511 + }
2512 + }
1970 2513
1971 - $instance = new self(); // Create an instance of the class
2514 + // Build query to fetch all rows for these URLs
2515 + $url_conditions = array();
2516 + $url_values = array();
1972 2517
1973 - for ($i = $start_page; $i < $end_page; $i++) {
1974 - $text = $pages[$i]->getText();
1975 - $sanitized_content = $instance->mxchat_sanitize_content_for_api($text); // Call via instance
2518 + if (!empty($url_list)) {
2519 + $placeholders = implode(',', array_fill(0, count($url_list), '%s'));
2520 + $url_conditions[] = "source_url IN ($placeholders)";
2521 + $url_values = array_merge($url_values, $url_list);
2522 + }
1976 2523
1977 - if (!empty($sanitized_content)) {
1978 - $embedding_vector = $instance->mxchat_generate_embedding($sanitized_content); // Call via instance
1979 - if (is_array($embedding_vector)) {
1980 - $metadata = array(
1981 - 'document_type' => 'pdf',
1982 - 'total_pages' => $total_pages,
1983 - 'current_page' => $i + 1,
1984 - 'prev_page' => $i > 0 ? $i : null,
1985 - 'next_page' => $i < ($total_pages - 1) ? $i + 2 : null,
1986 - 'source_url' => $pdf_url
1987 - );
2524 + if ($has_empty_url) {
2525 + $url_conditions[] = "(source_url = '' OR source_url IS NULL)";
2526 + }
1988 2527
1989 - $content_with_metadata = wp_json_encode($metadata) . "\n---\n" . $sanitized_content;
1990 - $page_url = esc_url($pdf_url . "#page=" . ($i + 1));
2528 + if (!empty($url_conditions)) {
2529 + $url_where = "WHERE (" . implode(" OR ", $url_conditions) . ")";
1991 2530
1992 - $options = get_option('mxchat_options');
1993 - MxChat_Utils::submit_content_to_db($content_with_metadata, $page_url, $options['api_key']);
2531 + // Add original filters back
2532 + if (!empty($where_clauses)) {
2533 + $url_where .= " AND " . implode(" AND ", $where_clauses);
2534 + $url_values = array_merge($url_values, $where_values);
1994 2535 }
1995 - }
1996 2536
1997 - // Update progress with sanitized data
1998 - $status['processed_pages'] = absint($i + 1);
1999 - $status['last_update'] = time();
2000 - set_transient($status_key, array_map('sanitize_text_field', $status), DAY_IN_SECONDS);
2001 - }
2537 + if (!empty($url_values)) {
2538 + $prompts_query = $wpdb->prepare(
2539 + "SELECT * FROM {$table_name} {$url_where} ORDER BY timestamp DESC",
2540 + $url_values
2541 + );
2542 + } else {
2543 + $prompts_query = "SELECT * FROM {$table_name} {$url_where} ORDER BY timestamp DESC";
2544 + }
2002 2545
2003 - // Schedule next batch if needed
2004 - if ($end_page < $total_pages) {
2005 - wp_schedule_single_event(time() + $batch_pause, 'mxchat_process_pdf_pages', array(
2006 - 'pdf_path' => $pdf_path,
2007 - 'pdf_url' => $pdf_url,
2008 - 'total_pages' => $total_pages,
2009 - 'batch_size' => $batch_size,
2010 - 'batch_pause' => $batch_pause
2011 - ));
2012 - } else {
2013 - // Processing complete
2014 - $status['status'] = 'complete';
2015 - set_transient($status_key, array_map('sanitize_text_field', $status), DAY_IN_SECONDS);
2016 - if (file_exists($pdf_path)) {
2017 - wp_delete_file($pdf_path);
2018 - }
2019 - }
2546 + $prompts = $wpdb->get_results($prompts_query);
2020 2547
2021 - } catch (\Exception $e) {
2022 - $status['status'] = 'error';
2023 - $status['error'] = sanitize_text_field($e->getMessage());
2024 - set_transient($status_key, array_map('sanitize_text_field', $status), DAY_IN_SECONDS);
2025 - if (file_exists($pdf_path)) {
2026 - wp_delete_file($pdf_path);
2027 - }
2028 - }
2029 -}
2030 -public function get_pdf_processing_status($pdf_url) {
2031 - $pdf_url = esc_url_raw($pdf_url);
2032 - $status = get_transient(sanitize_key('mxchat_pdf_status_' . md5($pdf_url)));
2548 + // Sort prompts by the original URL order (newest first), then by timestamp within each URL group
2549 + usort($prompts, function($a, $b) use ($url_order_map) {
2550 + $url_a = empty($a->source_url) ? '__empty__' : $a->source_url;
2551 + $url_b = empty($b->source_url) ? '__empty__' : $b->source_url;
2552 + $order_a = $url_order_map[$url_a] ?? PHP_INT_MAX;
2553 + $order_b = $url_order_map[$url_b] ?? PHP_INT_MAX;
2033 2554
2034 - if (!$status || !is_array($status)) {
2035 - return false;
2036 - }
2555 + // First sort by URL order (newest URLs first)
2556 + if ($order_a !== $order_b) {
2557 + return $order_a - $order_b;
2558 + }
2037 2559
2038 - return array(
2039 - 'total_pages' => absint($status['total_pages']),
2040 - 'processed_pages' => absint($status['processed_pages']),
2041 - 'percentage' => ($status['total_pages'] > 0)
2042 - ? round((absint($status['processed_pages']) / absint($status['total_pages'])) * 100)
2043 - : 0,
2044 - 'status' => sanitize_text_field($status['status']),
2045 - 'last_update' => human_time_diff(absint($status['last_update']), time()) . ' ' . esc_html__('ago', 'mxchat')
2046 - );
2047 -}
2048 -public function mxchat_handle_sitemap_submission() {
2049 - // Check if the form was submitted and verify permissions
2050 - if (!isset($_POST['submit_sitemap']) || !current_user_can('manage_options')) {
2051 - wp_die(esc_html__('Unauthorized access', 'mxchat'));
2560 + // Within same URL, sort by timestamp DESC (newest chunks first)
2561 + return strtotime($b->timestamp) - strtotime($a->timestamp);
2562 + });
2563 + }
2564 + }
2052 2565 }
2053 2566
2054 - // Verify nonce
2055 - check_admin_referer('mxchat_submit_sitemap_action', 'mxchat_submit_sitemap_nonce');
2567 + // ================================
2568 + // PAGINATION GENERATION
2569 + // ================================
2570 +
2571 + // Generate pagination links
2572 + if ($total_pages > 1) {
2573 + // Build a clean base URL with only necessary parameters
2574 + $pagination_args = array('page' => 'mxchat-prompts');
2056 2575
2057 - // Validate URL
2058 - if (!isset($_POST['sitemap_url']) || empty($_POST['sitemap_url'])) {
2059 - set_transient('mxchat_admin_notice_error',
2060 - esc_html__('Please provide a valid URL.', 'mxchat'),
2061 - 30
2062 - );
2063 - wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
2064 - exit;
2065 - }
2576 + // Preserve bot_id parameter if multi-bot is active
2577 + if ($multibot_active && !empty($current_bot_id) && $current_bot_id !== 'default') {
2578 + $pagination_args['bot_id'] = $current_bot_id;
2579 + }
2066 2580
2067 - $submitted_url = esc_url_raw($_POST['sitemap_url']);
2068 - $response = wp_remote_get($submitted_url, array('timeout' => 30));
2581 + // Preserve search query and nonce if present
2582 + if ($search_query) {
2583 + $pagination_args['search'] = $search_query;
2584 + if (!empty($nonce)) {
2585 + $pagination_args['_wpnonce'] = $nonce;
2586 + }
2587 + }
2069 2588
2070 - if (is_wp_error($response) || wp_remote_retrieve_response_code($response) !== 200) {
2071 - $error_message = is_wp_error($response) ? $response->get_error_message() : __('Failed to fetch URL', 'mxchat');
2072 - set_transient('mxchat_admin_notice_error',
2073 - sprintf(
2074 - esc_html__('Failed to fetch the URL: %s', 'mxchat'),
2075 - esc_html($error_message)
2076 - ),
2077 - 30
2078 - );
2079 - wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
2080 - exit;
2081 - }
2589 + // Preserve content type filter if present
2590 + if ($content_type_filter) {
2591 + $pagination_args['content_type'] = $content_type_filter;
2592 + }
2082 2593
2083 - $content_type = wp_remote_retrieve_header($response, 'content-type');
2084 - $body_content = wp_remote_retrieve_body($response);
2594 + // Build clean base URL
2595 + $base_url = add_query_arg($pagination_args, admin_url('admin.php'));
2085 2596
2086 - if (empty($body_content)) {
2087 - set_transient('mxchat_admin_notice_error',
2088 - esc_html__('Empty response received from URL.', 'mxchat'),
2089 - 30
2090 - );
2091 - wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
2092 - exit;
2597 + $page_links = paginate_links(array(
2598 + 'base' => $base_url . '%_%',
2599 + 'format' => '&paged=%#%',
2600 + 'prev_text' => __('&laquo; Previous', 'mxchat'),
2601 + 'next_text' => __('Next &raquo;', 'mxchat'),
2602 + 'total' => $total_pages,
2603 + 'current' => $current_page,
2604 + 'type' => 'plain',
2605 + ));
2606 + } else {
2607 + $page_links = '';
2093 2608 }
2094 2609
2095 - // Handle PDF URL
2096 - if ($this->is_pdf_url($submitted_url, $response)) {
2097 - $result = $this->handle_pdf_for_knowledge_base($submitted_url, $response);
2610 + // ================================
2611 + // PROCESSING STATUS RETRIEVAL
2612 + // ================================
2098 2613
2099 - if ($result === 'scheduled') {
2100 - set_transient(
2101 - 'mxchat_last_pdf_url',
2102 - sanitize_text_field($submitted_url),
2103 - DAY_IN_SECONDS
2104 - );
2105 - set_transient('mxchat_admin_notice_info',
2106 - esc_html__('PDF processing has started in the background. You can check the progress in the Knowledge Base section.', 'mxchat'),
2107 - 30
2108 - );
2109 - } else {
2110 - set_transient('mxchat_admin_notice_error',
2111 - esc_html__('Failed to start PDF processing. Please try again.', 'mxchat'),
2112 - 30
2113 - );
2114 - }
2614 + // Retrieve processing statuses using queue-based method
2615 + $processing_statuses = $knowledge_manager->mxchat_get_processing_statuses();
2616 + $pdf_status = $processing_statuses['pdf_status'];
2617 + $sitemap_status = $processing_statuses['sitemap_status'];
2618 + $is_processing = $processing_statuses['is_processing'];
2115 2619
2116 - wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
2117 - exit;
2118 - }
2620 + //error_log('=== DEBUG: mxchat_create_prompts_page data preparation completed ===');
2119 2621
2120 - // Handle Sitemap XML
2121 - if (strpos($content_type, 'xml') !== false || strpos($body_content, '<urlset') !== false) {
2122 - libxml_use_internal_errors(true);
2123 - $xml = simplexml_load_string($body_content);
2124 - $xml_errors = libxml_get_errors();
2125 - libxml_clear_errors();
2622 + // ================================
2623 + // RENDER PAGE WITH NEW SIDEBAR LAYOUT
2624 + // ================================
2126 2625
2127 - if ($xml === false || !empty($xml_errors)) {
2128 - set_transient('mxchat_admin_notice_error',
2129 - esc_html__('Invalid sitemap XML. Please provide a valid sitemap.', 'mxchat'),
2130 - 30
2131 - );
2132 - wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
2133 - exit;
2134 - }
2626 + // Include the new knowledge page template
2627 + require_once plugin_dir_path(__FILE__) . 'admin-knowledge-page.php';
2135 2628
2136 - $result = $this->handle_sitemap_for_knowledge_base($xml, $submitted_url);
2629 + // Package all page data for the render function
2630 + $page_data = array(
2631 + 'prompts' => $prompts,
2632 + 'total_records' => $total_records,
2633 + 'total_pages' => $total_pages,
2634 + 'current_page' => $current_page,
2635 + 'per_page' => $per_page,
2636 + 'page_links' => $page_links,
2637 + 'search_query' => $search_query,
2638 + 'content_type_filter' => $content_type_filter,
2639 + 'data_source' => $data_source,
2640 + 'use_pinecone' => $use_pinecone,
2641 + 'use_vectorstore' => $use_vectorstore,
2642 + 'multibot_active' => $multibot_active,
2643 + 'current_bot_id' => $current_bot_id,
2644 + 'pdf_status' => $pdf_status,
2645 + 'sitemap_status' => $sitemap_status,
2646 + 'is_processing' => $is_processing,
2647 + 'total_in_database' => $total_in_database ?? 0,
2648 + 'showing_recent_only' => $showing_recent_only ?? false,
2649 + );
2137 2650
2138 - if ($result === 'scheduled') {
2139 - set_transient(
2140 - 'mxchat_last_sitemap_url',
2141 - sanitize_text_field($submitted_url),
2142 - DAY_IN_SECONDS
2143 - );
2144 - set_transient('mxchat_admin_notice_info',
2145 - esc_html__('Sitemap processing has started in the background. You can check the progress in the Knowledge Base section.', 'mxchat'),
2146 - 30
2147 - );
2148 - } else {
2149 - set_transient('mxchat_admin_notice_error',
2150 - esc_html__('Failed to start sitemap processing. Please try again.', 'mxchat'),
2151 - 30
2152 - );
2153 - }
2651 + // Render the new sidebar-based page
2652 + mxchat_render_knowledge_page($this, $knowledge_manager, $page_data);
2653 +}
2154 2654
2155 - wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
2156 - exit;
2157 - }
2158 -
2159 - // Handle Regular URL
2160 - $page_content = $this->mxchat_extract_main_content($body_content);
2161 - $sanitized_content = $this->mxchat_sanitize_content_for_api($page_content);
2162 -
2163 - if (empty($sanitized_content)) {
2164 - set_transient('mxchat_admin_notice_error',
2165 - esc_html__('No valid content found on the provided URL.', 'mxchat'),
2166 - 30
2655 +/**
2656 + * Get bot-specific Pinecone configuration
2657 + * Used in the knowledge retrieval functions
2658 + */
2659 +private function get_bot_pinecone_config($bot_id = 'default') {
2660 + //error_log("MXCHAT DEBUG: get_bot_pinecone_config called for bot: " . $bot_id);
2661 +
2662 + // If default bot or multi-bot add-on not active, use default Pinecone config
2663 + if ($bot_id === 'default' || !class_exists('MxChat_Multi_Bot_Manager')) {
2664 + //error_log("MXCHAT DEBUG: Using default Pinecone config (no multi-bot or bot is 'default')");
2665 + $addon_options = get_option('mxchat_pinecone_addon_options', array());
2666 + $config = array(
2667 + 'use_pinecone' => (isset($addon_options['mxchat_use_pinecone']) && $addon_options['mxchat_use_pinecone'] === '1'),
2668 + 'api_key' => $addon_options['mxchat_pinecone_api_key'] ?? '',
2669 + 'host' => $addon_options['mxchat_pinecone_host'] ?? '',
2670 + 'namespace' => $addon_options['mxchat_pinecone_namespace'] ?? ''
2167 2671 );
2168 - wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
2169 - exit;
2672 + //error_log("MXCHAT DEBUG: Default config - use_pinecone: " . ($config['use_pinecone'] ? 'true' : 'false'));
2673 + return $config;
2170 2674 }
2171 -
2172 - $embedding_vector = $this->mxchat_generate_embedding($sanitized_content);
2173 - if (is_array($embedding_vector)) {
2174 - MxChat_Utils::submit_content_to_db(
2175 - $sanitized_content,
2176 - $submitted_url,
2177 - $this->options['api_key']
2178 - );
2179 - set_transient('mxchat_admin_notice_success',
2180 - esc_html__('URL content successfully submitted!', 'mxchat'),
2181 - 30
2182 - );
2675 +
2676 + //error_log("MXCHAT DEBUG: Calling filter 'mxchat_get_bot_pinecone_config' for bot: " . $bot_id);
2677 +
2678 + // Hook for multi-bot add-on to provide bot-specific Pinecone config
2679 + $bot_pinecone_config = apply_filters('mxchat_get_bot_pinecone_config', array(), $bot_id);
2680 +
2681 + if (!empty($bot_pinecone_config)) {
2682 + //error_log("MXCHAT DEBUG: Got bot-specific config from filter");
2683 + //error_log(" - use_pinecone: " . (isset($bot_pinecone_config['use_pinecone']) ? ($bot_pinecone_config['use_pinecone'] ? 'true' : 'false') : 'not set'));
2684 + //error_log(" - host: " . ($bot_pinecone_config['host'] ?? 'not set'));
2685 + //error_log(" - namespace: " . ($bot_pinecone_config['namespace'] ?? 'not set'));
2183 2686 } else {
2184 - set_transient('mxchat_admin_notice_error',
2185 - esc_html__('Failed to generate embedding for the URL content. Please check your API key and try again.', 'mxchat'),
2186 - 30
2187 - );
2687 + //error_log("MXCHAT DEBUG: Filter returned empty config!");
2188 2688 }
2689 +
2690 + return is_array($bot_pinecone_config) ? $bot_pinecone_config : array();
2691 +}
2189 2692
2190 - wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
2191 - exit;
2192 -}
2193 -private function handle_sitemap_for_knowledge_base($xml, $sitemap_url) {
2693 +public function mxchat_delete_chat_history() {
2194 2694 if (!current_user_can('manage_options')) {
2195 - //error_log(esc_html__('Unauthorized sitemap processing attempt', 'mxchat'));
2196 - return false;
2695 + echo wp_json_encode(['error' => esc_html__('You do not have sufficient permissions.', 'mxchat')]);
2696 + wp_die();
2197 2697 }
2698 + check_ajax_referer('mxchat_delete_chat_history', 'security');
2699 + global $wpdb;
2700 + $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
2198 2701
2199 - try {
2200 - $sitemap_url = esc_url_raw($sitemap_url);
2702 + if (isset($_POST['delete_session_ids']) && is_array($_POST['delete_session_ids'])) {
2703 + $deleted_count = 0;
2704 + $translations_table = $wpdb->prefix . 'mxchat_transcript_translations';
2201 2705
2202 - if (!$xml || !is_object($xml)) {
2203 - throw new Exception(__('Invalid XML object provided', 'mxchat'));
2204 - }
2706 + foreach ($_POST['delete_session_ids'] as $session_id) {
2707 + $session_id_sanitized = sanitize_text_field($session_id);
2205 2708
2206 - $urls = [];
2207 - foreach ($xml->url as $url_element) {
2208 - $url = esc_url_raw((string)$url_element->loc);
2209 - if ($url) {
2210 - $urls[] = $url;
2211 - }
2212 - }
2709 + // Clear relevant cache before deletion
2710 + $cache_key = 'chat_session_' . $session_id_sanitized;
2711 + wp_cache_delete($cache_key, 'mxchat_chat_sessions');
2213 2712
2214 - $total_urls = absint(count($urls));
2713 + // Perform the deletion from the database table
2714 + $wpdb->delete($table_name, ['session_id' => $session_id_sanitized]);
2215 2715
2216 - if ($total_urls < 1) {
2217 - throw new Exception(__('No valid URLs found in sitemap', 'mxchat'));
2218 - }
2716 + // Delete any saved translations for this session
2717 + if ($wpdb->get_var("SHOW TABLES LIKE '$translations_table'") === $translations_table) {
2718 + $wpdb->delete($translations_table, ['session_id' => $session_id_sanitized]);
2719 + }
2219 2720
2220 - wp_schedule_single_event(time(), 'mxchat_process_sitemap_urls', array(
2221 - 'urls' => $urls,
2222 - 'sitemap_url' => $sitemap_url,
2223 - 'total_urls' => $total_urls,
2224 - 'batch_size' => absint(10),
2225 - 'batch_pause' => absint(5)
2226 - ));
2721 + // Delete the corresponding option entry from wp_options table
2722 + delete_option("mxchat_history_" . $session_id_sanitized);
2227 2723
2228 - $status_data = array(
2229 - 'total_urls' => $total_urls,
2230 - 'processed_urls' => 0,
2231 - 'status' => 'processing',
2232 - 'last_update' => time()
2233 - );
2724 + // Delete any associated metadata options
2725 + delete_option("mxchat_email_" . $session_id_sanitized);
2726 + delete_option("mxchat_agent_name_" . $session_id_sanitized);
2234 2727
2235 - set_transient(
2236 - sanitize_key('mxchat_sitemap_status_' . md5($sitemap_url)),
2237 - array_map('sanitize_text_field', $status_data),
2238 - DAY_IN_SECONDS
2239 - );
2728 + $deleted_count++;
2729 + }
2240 2730
2241 - return __('scheduled', 'mxchat');
2731 + // Optionally, clear a general cache if you have one
2732 + wp_cache_delete('all_chat_sessions', 'mxchat_chat_sessions');
2242 2733
2243 - } catch (\Exception $e) {
2244 - //error_log(sprintf(esc_html__('Error preparing sitemap for processing: %s', 'mxchat'), esc_html($e->getMessage())));
2245 - return false;
2734 + echo wp_json_encode([
2735 + 'success' => sprintf(
2736 + esc_html__('%d chat session(s) have been deleted from all storage locations.', 'mxchat'),
2737 + $deleted_count
2738 + )
2739 + ]);
2740 + } else {
2741 + echo wp_json_encode(['error' => esc_html__('No chat sessions selected for deletion.', 'mxchat')]);
2246 2742 }
2743 +
2744 + wp_die();
2247 2745 }
2248 -public static function process_sitemap_urls_cron($urls, $sitemap_url, $total_urls, $batch_size, $batch_pause) {
2249 - // Validate inputs
2250 - $sitemap_url = esc_url_raw($sitemap_url);
2251 - $total_urls = absint($total_urls);
2252 - $batch_size = absint($batch_size);
2253 - $batch_pause = absint($batch_pause);
2254 2746
2255 - if (!is_array($urls) || empty($urls)) {
2256 - return;
2747 +/**
2748 + * Format transcript message content with markdown processing
2749 + * Converts markdown links, bold, italic, code blocks, and plain URLs to HTML
2750 + */
2751 +private function format_transcript_message($text) {
2752 + if (empty($text)) {
2753 + return '';
2257 2754 }
2258 2755
2259 - try {
2260 - $status_key = sanitize_key('mxchat_sitemap_status_' . md5($sitemap_url));
2261 - $status = get_transient($status_key);
2756 + // Normalize line endings and clean up excessive whitespace
2757 + $text = str_replace("\r\n", "\n", $text);
2758 + $text = str_replace("\r", "\n", $text);
2262 2759
2263 - if (!$status || !is_array($status)) {
2264 - throw new Exception('Invalid status data retrieved from transient');
2265 - }
2760 + // Clean up existing <br> tags that may have been saved (legacy data)
2761 + // Convert <br>, <br/>, <br /> back to newlines for consistent processing
2762 + $text = preg_replace('/<br\s*\/?>\s*/i', "\n", $text);
2266 2763
2267 - $start_url = absint($status['processed_urls']);
2268 - $end_url = min($start_url + $batch_size, $total_urls);
2269 - $instance = new self();
2764 + // Collapse 3+ consecutive newlines to just 2 (paragraph break)
2765 + $text = preg_replace('/\n{3,}/', "\n\n", $text);
2270 2766
2271 - // Track failures in batch
2272 - $batch_stats = [
2273 - 'processed' => 0,
2274 - 'failed' => 0,
2275 - 'last_error' => ''
2276 - ];
2767 + // Process markdown headers (# Header)
2768 + $text = preg_replace_callback('/^(#{1,6})\s+(.+)$/m', function($matches) {
2769 + $level = strlen($matches[1]);
2770 + $content = esc_html(trim($matches[2]));
2771 + return "<h{$level}>{$content}</h{$level}>";
2772 + }, $text);
2277 2773
2278 - for ($i = $start_url; $i < $end_url; $i++) {
2279 - $page_url = esc_url_raw($urls[$i]);
2280 - $page_response = wp_remote_get($page_url);
2774 + // Process code blocks with triple backticks
2775 + $text = preg_replace_callback('/```(\w+)?\n?([\s\S]*?)```/', function($matches) {
2776 + $language = !empty($matches[1]) ? ' class="language-' . esc_attr($matches[1]) . '"' : '';
2777 + $code = esc_html($matches[2]);
2778 + return "<pre><code{$language}>{$code}</code></pre>";
2779 + }, $text);
2281 2780
2282 - if (is_wp_error($page_response) || wp_remote_retrieve_response_code($page_response) !== 200) {
2283 - $batch_stats['failed']++;
2284 - continue;
2285 - }
2781 + // Process inline code with single backticks
2782 + $text = preg_replace('/`([^`]+)`/', '<code>$1</code>', $text);
2286 2783
2287 - $page_html = wp_remote_retrieve_body($page_response);
2288 - $page_content = $instance->mxchat_extract_main_content($page_html);
2289 - $sanitized_content = $instance->mxchat_sanitize_content_for_api($page_content);
2784 + // Process bold text **text** or __text__
2785 + $text = preg_replace('/\*\*(.+?)\*\*/', '<strong>$1</strong>', $text);
2786 + $text = preg_replace('/__(.+?)__/', '<strong>$1</strong>', $text);
2290 2787
2291 - if (!empty($sanitized_content)) {
2292 - $embedding_vector = $instance->mxchat_generate_embedding($sanitized_content);
2788 + // Process italic text *text* or _text_ (but not if part of URL)
2789 + $text = preg_replace('/(?<![*_\w])\*([^*]+)\*(?![*\w])/', '<em>$1</em>', $text);
2790 + $text = preg_replace('/(?<![*_\w])_([^_]+)_(?![*\w])/', '<em>$1</em>', $text);
2293 2791
2294 - if (is_array($embedding_vector)) {
2295 - $options = get_option('mxchat_options');
2296 - $submission_result = MxChat_Utils::submit_content_to_db($sanitized_content, $page_url, $options['api_key']);
2792 + // Process markdown links [text](url)
2793 + $text = preg_replace_callback('/\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/', function($matches) {
2794 + $link_text = esc_html($matches[1]);
2795 + $url = esc_url($matches[2]);
2796 + return "<a href=\"{$url}\" target=\"_blank\" rel=\"noopener\">{$link_text}</a>";
2797 + }, $text);
2297 2798
2298 - if (is_wp_error($submission_result)) {
2299 - $batch_stats['failed']++;
2300 - $batch_stats['last_error'] = $submission_result->get_error_message();
2301 - continue;
2302 - }
2799 + // Process citation-style brackets [URL]
2800 + $text = preg_replace_callback('/\[(https?:\/\/[^\]]+)\]/', function($matches) {
2801 + $url = esc_url($matches[1]);
2802 + return "<a href=\"{$url}\" target=\"_blank\" rel=\"noopener\">{$url}</a>";
2803 + }, $text);
2303 2804
2304 - $batch_stats['processed']++;
2305 - } else {
2306 - $batch_stats['failed']++;
2307 - $batch_stats['last_error'] = 'Failed to generate embedding. Please check embedding API key.';
2308 - }
2309 - }
2805 + // Process standalone URLs (not already in links)
2806 + $text = preg_replace_callback(
2807 + '/(?<!href="|">)(https?:\/\/[^\s<>"]+)(?![^<]*<\/a>)/',
2808 + function($matches) {
2809 + $url = esc_url($matches[1]);
2810 + // Truncate display URL if too long
2811 + $display = strlen($matches[1]) > 50 ? substr($matches[1], 0, 47) . '...' : $matches[1];
2812 + return "<a href=\"{$url}\" target=\"_blank\" rel=\"noopener\">{$display}</a>";
2813 + },
2814 + $text
2815 + );
2310 2816
2311 - $status['processed_urls'] = absint($i + 1);
2312 - $status['last_update'] = time();
2313 - $status['failed_urls'] = absint($status['failed_urls'] ?? 0) + $batch_stats['failed'];
2314 - $status['last_error'] = $batch_stats['last_error'];
2817 + // Process mailto links
2818 + $text = preg_replace_callback('/\[([^\]]+)\]\((mailto:[^)]+)\)/', function($matches) {
2819 + $link_text = esc_html($matches[1]);
2820 + $mailto = esc_url($matches[2]);
2821 + return "<a href=\"{$mailto}\">{$link_text}</a>";
2822 + }, $text);
2315 2823
2316 - set_transient($status_key, array_map('sanitize_text_field', $status), DAY_IN_SECONDS);
2317 - }
2824 + // Convert paragraphs: split by double newlines, wrap in <p> tags
2825 + // This creates proper paragraph structure instead of excessive <br> tags
2826 + $paragraphs = preg_split('/\n\n+/', $text);
2318 2827
2319 - // If all URLs in this batch failed, stop processing
2320 - if ($batch_stats['processed'] === 0 && $batch_stats['failed'] > 0) {
2321 - $status['status'] = 'error';
2322 - $status['error'] = sprintf(
2323 - 'Processing stopped: %d consecutive failures. Last error: %s',
2324 - $batch_stats['failed'],
2325 - $batch_stats['last_error']
2326 - );
2327 - set_transient($status_key, array_map('sanitize_text_field', $status), DAY_IN_SECONDS);
2328 - return;
2329 - }
2828 + // Filter out empty paragraphs but preserve content like "0"
2829 + $paragraphs = array_values(array_filter(array_map('trim', $paragraphs), function($p) {
2830 + return $p !== '';
2831 + }));
2330 2832
2331 - if ($end_url < $total_urls) {
2332 - wp_schedule_single_event(time() + $batch_pause, 'mxchat_process_sitemap_urls', array(
2333 - 'urls' => array_map('esc_url_raw', $urls),
2334 - 'sitemap_url' => $sitemap_url,
2335 - 'total_urls' => $total_urls,
2336 - 'batch_size' => $batch_size,
2337 - 'batch_pause' => $batch_pause,
2338 - ));
2339 - } else {
2340 - $status['status'] = 'complete';
2341 - set_transient($status_key, array_map('sanitize_text_field', $status), DAY_IN_SECONDS);
2342 - }
2343 - } catch (\Exception $e) {
2344 - $status['status'] = 'error';
2345 - $status['error'] = sanitize_text_field($e->getMessage());
2346 - set_transient($status_key, array_map('sanitize_text_field', $status), DAY_IN_SECONDS);
2833 + if (empty($paragraphs)) {
2834 + // No content after filtering
2835 + return '';
2836 + } elseif (count($paragraphs) > 1) {
2837 + // Multiple paragraphs - wrap each in <p> tags, convert single newlines to <br>
2838 + $formatted_paragraphs = array_map(function($p) {
2839 + return nl2br($p);
2840 + }, $paragraphs);
2841 + $text = '<p>' . implode('</p><p>', $formatted_paragraphs) . '</p>';
2842 + } else {
2843 + // Single paragraph - just convert newlines to <br>
2844 + $text = nl2br($paragraphs[0]);
2347 2845 }
2348 -}
2349 2846
2350 -public function get_sitemap_processing_status($sitemap_url) {
2351 - $sitemap_url = esc_url_raw($sitemap_url);
2352 - $status = get_transient(sanitize_key('mxchat_sitemap_status_' . md5($sitemap_url)));
2353 -
2354 - if (!$status || !is_array($status)) {
2355 - return false;
2356 - }
2357 -
2358 - return array(
2359 - 'total_urls' => absint($status['total_urls']),
2360 - 'processed_urls' => absint($status['processed_urls']),
2361 - 'failed_urls' => absint($status['failed_urls'] ?? 0),
2362 - 'percentage' => ($status['total_urls'] > 0)
2363 - ? round((absint($status['processed_urls']) / absint($status['total_urls'])) * 100)
2364 - : 0,
2365 - 'status' => sanitize_text_field($status['status']),
2366 - 'last_update' => human_time_diff(absint($status['last_update']), time()) . ' ' . esc_html__('ago', 'mxchat'),
2367 - 'error' => isset($status['error']) ? sanitize_text_field($status['error']) : '',
2368 - 'last_error' => isset($status['last_error']) ? sanitize_text_field($status['last_error']) : ''
2369 - );
2847 + return $text;
2370 2848 }
2371 2849
2372 -public function mxchat_stop_processing() {
2373 - // Verify permissions
2850 +/**
2851 + * AJAX handler to fetch RAG context for a specific message
2852 + * Used by the transcript viewer to show retrieved documents
2853 + */
2854 +public function mxchat_get_rag_context() {
2855 + // Check permissions
2374 2856 if (!current_user_can('manage_options')) {
2375 - wp_die(esc_html__('Unauthorized access', 'mxchat'));
2857 + wp_send_json_error(['message' => esc_html__('You do not have sufficient permissions.', 'mxchat')]);
2858 + wp_die();
2376 2859 }
2377 2860
2378 - // Verify nonce
2379 - check_admin_referer('mxchat_stop_processing_action', 'mxchat_stop_processing_nonce');
2380 -
2381 - // Get the last sitemap URL and clear its transient
2382 - $sitemap_url = get_transient('mxchat_last_sitemap_url');
2383 - if ($sitemap_url) {
2384 - delete_transient('mxchat_sitemap_status_' . md5($sitemap_url));
2385 - delete_transient('mxchat_last_sitemap_url');
2861 + // Validate message ID
2862 + if (!isset($_POST['message_id']) || empty($_POST['message_id'])) {
2863 + wp_send_json_error(['message' => esc_html__('Message ID is required.', 'mxchat')]);
2864 + wp_die();
2386 2865 }
2387 2866
2388 - // Get the last PDF URL and clear its transient
2389 - $pdf_url = get_transient('mxchat_last_pdf_url');
2390 - if ($pdf_url) {
2391 - delete_transient('mxchat_pdf_status_' . md5($pdf_url));
2392 - delete_transient('mxchat_last_pdf_url');
2393 - }
2867 + $message_id = absint($_POST['message_id']);
2394 2868
2395 - // Unschedule any pending sitemap events
2396 - $timestamp = wp_next_scheduled('mxchat_process_sitemap_urls');
2397 - if ($timestamp) {
2398 - wp_unschedule_event($timestamp, 'mxchat_process_sitemap_urls');
2399 - }
2869 + global $wpdb;
2870 + $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
2400 2871
2401 - // Redirect back with a success message
2402 - set_transient('mxchat_admin_notice_success',
2403 - esc_html__('Processing has been stopped successfully.', 'mxchat'),
2404 - 30
2405 - );
2406 - wp_safe_redirect(admin_url('admin.php?page=mxchat-prompts'));
2407 - exit;
2408 -}
2409 -private function mxchat_sanitize_content_for_api($content) {
2410 - // Remove script, style tags, and HTML comments
2411 - $content = preg_replace('/<script\b[^>]*>(.*?)<\/script>/is', '', $content);
2412 - $content = preg_replace('/<style\b[^>]*>(.*?)<\/style>/is', '', $content);
2413 - $content = preg_replace('/<!--(.|\s)*?-->/', '', $content);
2872 + // Fetch the RAG context for this message
2873 + $result = $wpdb->get_row($wpdb->prepare(
2874 + "SELECT rag_context FROM {$table_name} WHERE id = %d",
2875 + $message_id
2876 + ));
2414 2877
2415 - // Remove all HTML tags and decode HTML entities
2416 - $content = wp_strip_all_tags($content);
2417 - $content = html_entity_decode($content, ENT_QUOTES | ENT_HTML5);
2878 + if (!$result || empty($result->rag_context)) {
2879 + wp_send_json_error(['message' => esc_html__('No RAG context found for this message.', 'mxchat')]);
2880 + wp_die();
2881 + }
2418 2882
2419 - // Trim and normalize whitespace
2420 - $content = trim(preg_replace('/\s+/', ' ', $content));
2883 + // Decode the JSON data
2884 + $rag_context = json_decode($result->rag_context, true);
2421 2885
2422 - return $content;
2423 -}
2424 -
2425 -
2426 -
2427 -
2428 -public function mxchat_handle_product_change($post_id, $post, $update) {
2429 - if ($post->post_type !== 'product') {
2430 - return;
2886 + if (json_last_error() !== JSON_ERROR_NONE) {
2887 + wp_send_json_error(['message' => esc_html__('Invalid RAG context data.', 'mxchat')]);
2888 + wp_die();
2431 2889 }
2432 2890
2433 - if ($post->post_status === 'publish') {
2434 - add_action('shutdown', function() use ($post_id) {
2435 - $product = wc_get_product($post_id);
2436 - if ($product) {
2437 - $this->mxchat_store_product_embedding($product);
2438 - }
2439 - });
2440 - }
2891 + wp_send_json_success($rag_context);
2892 + wp_die();
2441 2893 }
2442 2894
2443 -private function mxchat_store_product_embedding($product) {
2444 - if (!isset($this->options['enable_woocommerce_integration']) ||
2445 - !in_array($this->options['enable_woocommerce_integration'], ['1', 'on'])) {
2895 +public function display_admin_notices() {
2896 + // Check if we're on a MXChat admin page
2897 + $screen = get_current_screen();
2898 + if (!$screen || strpos($screen->base, 'mxchat') === false) {
2446 2899 return;
2447 2900 }
2448 2901
2449 - $source_url = get_permalink($product->get_id());
2902 + $dismiss_button = '<button type="button" class="notice-dismiss"><span class="screen-reader-text">' . esc_html__('Dismiss this notice.', 'mxchat') . '</span></button>';
2450 2903
2451 - // Build description
2452 - $regular_price = $product->get_regular_price();
2453 - $sale_price = $product->get_sale_price();
2454 - $description = $product->get_description() . "\n\n" .
2455 - esc_html__('Short Description:', 'mxchat') . " " . $product->get_short_description() . "\n" .
2456 - esc_html__('Price:', 'mxchat') . " " . $regular_price . "\n" .
2457 - esc_html__('Sale Price:', 'mxchat') . " " . ($sale_price ?: esc_html__('N/A', 'mxchat')) . "\n" .
2458 - esc_html__('SKU:', 'mxchat') . " " . $product->get_sku();
2459 -
2460 - // Generate embedding vector
2461 - $embedding_vector = $this->mxchat_generate_embedding($description);
2462 - if (!is_array($embedding_vector)) {
2463 - return;
2904 + // Check for error notices
2905 + $error_notice = get_transient('mxchat_admin_notice_error');
2906 + if ($error_notice) {
2907 + echo '<div class="notice notice-error is-dismissible"><p>' . wp_kses_post($error_notice) . '</p>' . $dismiss_button . '</div>';
2908 + delete_transient('mxchat_admin_notice_error');
2464 2909 }
2465 2910
2466 - // Check for Pinecone addon and its settings
2467 - $pinecone_settings = get_option('mxchat_pinecone_addon_options');
2468 - $use_pinecone = false;
2469 -
2470 - if ($pinecone_settings && is_array($pinecone_settings)) {
2471 - $use_pinecone = (
2472 - isset($pinecone_settings['mxchat_use_pinecone']) &&
2473 - $pinecone_settings['mxchat_use_pinecone'] === '1' &&
2474 - !empty($pinecone_settings['mxchat_pinecone_api_key']) &&
2475 - !empty($pinecone_settings['mxchat_pinecone_host']) &&
2476 - !empty($pinecone_settings['mxchat_pinecone_index']) &&
2477 - !empty($pinecone_settings['mxchat_pinecone_environment'])
2478 - );
2911 + // Check for success notices
2912 + $success_notice = get_transient('mxchat_admin_notice_success');
2913 + if ($success_notice) {
2914 + echo '<div class="notice notice-success is-dismissible"><p>' . wp_kses_post($success_notice) . '</p>' . $dismiss_button . '</div>';
2915 + delete_transient('mxchat_admin_notice_success');
2479 2916 }
2480 2917
2481 - if ($use_pinecone) {
2482 - $result = $this->store_in_pinecone_main(
2483 - $embedding_vector,
2484 - $description,
2485 - $source_url,
2486 - $pinecone_settings['mxchat_pinecone_api_key'],
2487 - $pinecone_settings['mxchat_pinecone_environment'],
2488 - $pinecone_settings['mxchat_pinecone_index']
2489 - );
2490 -
2491 - if (!$result['success']) {
2492 - // Fallback to WordPress DB if Pinecone storage fails
2493 - $this->store_in_wordpress_db($description, $source_url, $embedding_vector);
2494 - }
2495 - } else {
2496 - // Use WordPress DB storage
2497 - $this->store_in_wordpress_db($description, $source_url, $embedding_vector);
2918 + // Check for info notices
2919 + $info_notice = get_transient('mxchat_admin_notice_info');
2920 + if ($info_notice) {
2921 + echo '<div class="notice notice-info is-dismissible"><p>' . wp_kses_post($info_notice) . '</p>' . $dismiss_button . '</div>';
2922 + delete_transient('mxchat_admin_notice_info');
2498 2923 }
2499 2924 }
2500 2925
2501 -public function mxchat_handle_product_delete($post_id) {
2502 - if (get_post_type($post_id) !== 'product') {
2503 - return;
2504 - }
2505 2926
2506 - $source_url = get_permalink($post_id);
2927 +public function mxchat_create_activation_page() {
2928 + // Include the new Pro & Extensions page template
2929 + require_once plugin_dir_path(__FILE__) . 'admin-pro-page.php';
2507 2930
2508 - // Check for Pinecone addon and its settings
2509 - $pinecone_settings = get_option('mxchat_pinecone_addon_options');
2510 - $use_pinecone = false;
2931 + // Get addons configuration from the MxChat_Addons class
2932 + require_once plugin_dir_path(__FILE__) . 'class-mxchat-addons.php';
2933 + $addons_instance = new MxChat_Addons();
2934 + $addons_config = $addons_instance->get_addons_config();
2511 2935
2512 - if ($pinecone_settings && is_array($pinecone_settings)) {
2513 - $use_pinecone = (
2514 - isset($pinecone_settings['mxchat_use_pinecone']) &&
2515 - $pinecone_settings['mxchat_use_pinecone'] === '1' &&
2516 - !empty($pinecone_settings['mxchat_pinecone_api_key']) &&
2517 - !empty($pinecone_settings['mxchat_pinecone_host']) &&
2518 - !empty($pinecone_settings['mxchat_pinecone_index']) &&
2519 - !empty($pinecone_settings['mxchat_pinecone_environment'])
2520 - );
2521 - }
2522 -
2523 - if ($use_pinecone) {
2524 - $this->delete_from_pinecone(
2525 - array($source_url),
2526 - $pinecone_settings['mxchat_pinecone_api_key'],
2527 - $pinecone_settings['mxchat_pinecone_environment'],
2528 - $pinecone_settings['mxchat_pinecone_index']
2529 - );
2530 - } else {
2531 - // Fallback to WordPress DB deletion
2532 - global $wpdb;
2533 - $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
2534 - $wpdb->delete($table_name, array('source_url' => $source_url), array('%s'));
2535 - }
2936 + // Render the consolidated Pro & Extensions page
2937 + mxchat_render_pro_page($this, $addons_config);
2536 2938 }
2537 2939
2538 -
2539 2940 /**
2540 - * Delete vectors from Pinecone by source URL
2541 - *
2542 - * @param array $urls Array of source URLs to delete
2543 - * @param string $api_key Pinecone API key
2544 - * @param string $environment Pinecone environment
2545 - * @param string $index_name Pinecone index name
2546 - * @return array Associative array with 'success' boolean and 'message' string
2941 + * Check if current activation is linked to a domain
2942 + * This checks YOUR website's database, not the user's local database
2547 2943 */
2548 -private function delete_from_pinecone($urls, $api_key, $environment, $index_name) {
2549 - // Get the Pinecone host from options (matching your store_in_pinecone_main pattern)
2550 - $options = get_option('mxchat_pinecone_addon_options');
2551 - $host = $options['mxchat_pinecone_host'] ?? '';
2944 +public function is_current_activation_linked($domain) {
2945 + $license_key = get_option('mxchat_activation_key');
2946 + $email = get_option('mxchat_pro_email');
2552 2947
2553 - if (empty($host)) {
2554 - return array(
2555 - 'success' => false,
2556 - 'message' => 'Pinecone host is not configured. Please set the host in your settings.'
2557 - );
2948 + if (empty($license_key) || empty($email)) {
2949 + return false;
2558 2950 }
2559 2951
2560 - // Build API endpoint using the configured host
2561 - $api_endpoint = "https://{$host}/vectors/delete";
2562 -
2563 - // Create vector IDs from URLs (matching your store method's ID generation)
2564 - $vector_ids = array_map('md5', $urls);
2565 -
2566 - // Prepare the delete request body
2567 - $request_body = array(
2568 - 'ids' => $vector_ids,
2569 - 'filter' => array(
2570 - 'source_url' => array(
2571 - '$in' => $urls
2572 - )
2573 - )
2574 - );
2575 -
2576 - // Make the deletion request
2577 - $response = wp_remote_post($api_endpoint, array(
2578 - 'headers' => array(
2579 - 'Api-Key' => $api_key,
2580 - 'accept' => 'application/json',
2581 - 'content-type' => 'application/json'
2952 + // Check with YOUR website's API
2953 + $response = wp_remote_post('https://mxchat.ai/mxchat-api/check-domain', array(
2954 + 'body' => array(
2955 + 'license_key' => $license_key,
2956 + 'email' => $email,
2957 + 'domain' => $domain
2582 2958 ),
2583 - 'body' => wp_json_encode($request_body),
2584 - 'timeout' => 30,
2585 - 'data_format' => 'body'
2959 + 'timeout' => 10,
2960 + 'sslverify' => false
2586 2961 ));
2587 2962
2588 - // Handle WordPress HTTP API errors
2589 2963 if (is_wp_error($response)) {
2590 - return array(
2591 - 'success' => false,
2592 - 'message' => $response->get_error_message()
2593 - );
2964 + return false;
2594 2965 }
2595 2966
2596 - // Check response status
2597 - $response_code = wp_remote_retrieve_response_code($response);
2598 - if ($response_code !== 200) {
2599 - $body = wp_remote_retrieve_body($response);
2600 - return array(
2601 - 'success' => false,
2602 - 'message' => sprintf(
2603 - 'Pinecone API error (HTTP %d): %s',
2604 - $response_code,
2605 - $body
2606 - )
2607 - );
2608 - }
2609 -
2610 - // Parse response body
2611 - $body = wp_remote_retrieve_body($response);
2612 - $response_data = json_decode($body, true);
2613 -
2614 - // Final validation of the response
2615 - if (json_last_error() !== JSON_ERROR_NONE) {
2616 - return array(
2617 - 'success' => false,
2618 - 'message' => 'Failed to parse Pinecone response: ' . json_last_error_msg()
2619 - );
2620 - }
2621 -
2622 - return array(
2623 - 'success' => true,
2624 - 'message' => sprintf('Successfully deleted %d vectors from Pinecone', count($vector_ids))
2625 - );
2967 + $body = json_decode(wp_remote_retrieve_body($response), true);
2968 + return isset($body['success']) && $body['success'] && isset($body['data']['linked']) && $body['data']['linked'];
2626 2969 }
2627 2970
2628 2971
2629 -
2630 -
2631 -
2632 -public function mxchat_create_activation_page() {
2633 - $license_status = get_option('mxchat_license_status', 'inactive');
2634 - $license_error = get_option('mxchat_license_error', '');
2635 - ?>
2636 - <div class="wrap mxchat-admin-activation">
2637 - <div class="mxchat-pro-hero">
2638 - <h1 class="pro-title">
2639 - <span class="pro-gradient-text">Activate</span> MxChat Pro
2640 - </h1>
2641 - <p class="pro-subtitle">
2642 - <?php esc_html_e('Enter your license key to unlock premium features, advanced AI capabilities, and priority support.', 'mxchat'); ?>
2643 - </p>
2644 - </div>
2645 -
2646 - <?php if ($license_status === 'inactive' && !empty($license_error)): ?>
2647 - <div class="error notice">
2648 - <p><?php echo esc_html($license_error); ?></p>
2649 - </div>
2650 - <?php endif; ?>
2651 -
2652 - <form id="mxchat-activation-form" class="mxchat-pro-form" style="<?php echo $license_status === 'active' ? 'display: none;' : ''; ?>">
2653 - <div class="mxchat-pro-form-container">
2654 - <table class="form-table">
2655 - <tr valign="top">
2656 - <th scope="row"><?php esc_html_e('Email Address', 'mxchat'); ?></th>
2657 - <td>
2658 - <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 />
2659 - </td>
2660 - </tr>
2661 - <tr valign="top">
2662 - <th scope="row"><?php esc_html_e('Activation Key', 'mxchat'); ?></th>
2663 - <td>
2664 - <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 />
2665 - </td>
2666 - </tr>
2667 - </table>
2668 - <?php if ($license_status !== 'active'): ?>
2669 - <div class="mxchat-pro-button-container">
2670 - <button type="submit" id="activate_license_button" class="button button-primary mxchat-pro-button"><?php esc_html_e('Activate License', 'mxchat'); ?></button>
2671 - <div id="mxchat-activation-spinner" class="mxchat-activation-spinner" style="display: none;"></div>
2672 - </div>
2673 - <?php endif; ?>
2674 - </div>
2675 - </form>
2676 - <!-- License Status Display -->
2677 - <div class="mxchat-pro-status">
2678 - <h3><?php esc_html_e('License Status:', 'mxchat'); ?>
2679 - <span id="mxchat-license-status" class="mxchat-status-badge <?php echo $license_status; ?>">
2680 - <?php echo $license_status === 'active' ? esc_html__('Active', 'mxchat') : esc_html__('Inactive', 'mxchat'); ?>
2681 - </span>
2682 - </h3>
2683 - </div>
2684 -
2685 - </div>
2686 - <?php
2687 -}
2688 -
2689 -public function mxchat_intents_page_html() {
2972 +public function mxchat_actions_page_html() {
2690 2973 if (!current_user_can('manage_options')) {
2691 2974 return;
2692 2975 }
2693 2976
2694 - // Keep existing data fetching logic
2695 2977 global $wpdb;
2696 2978 $table_name = $wpdb->prefix . 'mxchat_intents';
2697 - $page = isset($_GET['paged']) ? max(1, intval($_GET['paged'])) : 1;
2698 - $per_page = 20;
2699 - $offset = ($page - 1) * $per_page;
2700 2979
2701 - // Success message
2702 - if (isset($_GET['updated']) && $_GET['updated'] === 'true') {
2703 - echo '<div class="notice notice-success is-dismissible"><p>' .
2704 - esc_html__('Intent updated successfully.', 'mxchat') .
2705 - '</p></div>';
2706 - }
2980 + // Get stats for dashboard
2981 + $total_actions = $wpdb->get_var("SELECT COUNT(*) FROM $table_name");
2982 + $enabled_actions = $wpdb->get_var("SELECT COUNT(*) FROM $table_name WHERE enabled = 1");
2983 + $disabled_actions = $total_actions - $enabled_actions;
2707 2984
2708 - // Filtering logic
2709 - $where = '1=1';
2710 - $search_term = isset($_GET['s']) ? trim($_GET['s']) : '';
2711 - $callback_filter = isset($_GET['callback_filter']) ? sanitize_text_field($_GET['callback_filter']) : '';
2985 + // Get unique action types count
2986 + $action_types_count = $wpdb->get_var("SELECT COUNT(DISTINCT callback_function) FROM $table_name");
2712 2987
2713 - if ($search_term) {
2714 - $search_term_like = '%' . $wpdb->esc_like($search_term) . '%';
2715 - $where .= $wpdb->prepare(' AND (intent_label LIKE %s OR phrases LIKE %s)',
2716 - $search_term_like, $search_term_like);
2717 - }
2988 + // Get action type distribution
2989 + $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");
2990 + $available_callbacks = $this->mxchat_get_available_callbacks();
2991 + $callback_groups = $this->mxchat_get_available_callbacks(true, true);
2718 2992
2719 - if ($callback_filter) {
2720 - $where .= $wpdb->prepare(' AND callback_function = %s', $callback_filter);
2993 + $action_type_distribution = array();
2994 + foreach ($type_distribution_raw as $row) {
2995 + $label = isset($available_callbacks[$row->callback_function]['label'])
2996 + ? $available_callbacks[$row->callback_function]['label']
2997 + : $row->callback_function;
2998 + $action_type_distribution[$label] = $row->count;
2721 2999 }
2722 3000
2723 - // Pagination
2724 - $total_intents = $wpdb->get_var("SELECT COUNT(*) FROM $table_name WHERE $where");
2725 - $total_pages = ceil($total_intents / $per_page);
3001 + // Prepare page data
3002 + $page_data = array(
3003 + 'total_actions' => $total_actions,
3004 + 'enabled_actions' => $enabled_actions,
3005 + 'disabled_actions' => $disabled_actions,
3006 + 'action_types_count' => $action_types_count,
3007 + 'action_type_distribution' => $action_type_distribution,
3008 + 'available_callbacks' => $available_callbacks,
3009 + 'callback_groups' => $callback_groups,
3010 + );
2726 3011
2727 - // Get intents
2728 - $intents = $wpdb->get_results($wpdb->prepare(
2729 - "SELECT * FROM $table_name WHERE $where LIMIT %d OFFSET %d",
2730 - $per_page, $offset
2731 - ));
3012 + // Include and render the new template
3013 + require_once plugin_dir_path(__FILE__) . 'admin-actions-page.php';
3014 + mxchat_render_actions_page($this, $page_data);
3015 +}
2732 3016
2733 - // Get callbacks
2734 - $available_callbacks = $this->mxchat_get_available_callbacks();
2735 -
3017 +/**
3018 + * LEGACY HTML - Preserved below for reference, to be removed in future update
3019 + */
3020 +function mxchat_actions_page_legacy_html() {
3021 + // This function is deprecated and no longer used
3022 + // The new template is in includes/admin-actions-page.php
2736 3023 ?>
2737 3024 <div class="wrap mxchat-wrapper">
2738 3025 <!-- Hero Section -->
2739 3026 <div class="mxchat-hero">
2740 3027 <h1 class="mxchat-main-title">
2741 - <span class="mxchat-gradient-text">Intent</span> Manager
3028 + <span class="mxchat-gradient-text">Actions</span> Manager
2742 3029 </h1>
2743 3030 <p class="mxchat-hero-subtitle">
2744 - <?php esc_html_e('Create and manage custom intents to enhance your chatbots understanding and response capabilities.', 'mxchat'); ?>
3031 + <?php esc_html_e('Create and manage custom actions to enhance your chatbot\'s capabilities.', 'mxchat'); ?>
2745 3032 </p>
2746 3033 </div>
2747 3034
2748 - <div class="mxchat-content">
2749 - <!-- Add Intent Card -->
2750 - <div class="mxchat-card">
2751 - <div class="mxchat-card-header">
2752 - <h2><?php esc_html_e('Add New Intent', 'mxchat'); ?></h2>
2753 - </div>
2754 -
2755 - <div class="mxchat-intent-documentation">
2756 - <p>
2757 - <?php esc_html_e('We highly encourage users to quickly read our ', 'mxchat'); ?>
2758 - <a href="https://mxchat.ai/documentation/#intents" target="_blank" rel="noopener noreferrer">
2759 - <?php esc_html_e('documentation', 'mxchat'); ?>
2760 - </a>
2761 - <?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'); ?>
2762 - </p>
2763 - </div>
2764 -
2765 - <form id="mxchat-add-intent-form" method="post"
2766 - action="<?php echo esc_url(admin_url('admin-post.php')); ?>">
2767 - <input type="hidden" name="action" value="mxchat_add_intent">
2768 - <?php wp_nonce_field('mxchat_add_intent_nonce'); ?>
2769 -
2770 - <div class="mxchat-form-group">
2771 - <label for="intent_label">
2772 - <?php esc_html_e('Intent Label (For your reference only)', 'mxchat'); ?>
2773 - </label>
2774 - <input name="intent_label" type="text" id="intent_label" required
2775 - class="mxchat-intent-input"
2776 - placeholder="<?php esc_attr_e('Example Email Capture: Newsletter Signup', 'mxchat'); ?>">
3035 + <!-- Actions Header with Search and Filter -->
3036 + <div class="mxchat-actions-header">
3037 + <div class="mxchat-actions-filters">
3038 + <form method="get" class="mxchat-search-form">
3039 + <input type="hidden" name="page" value="mxchat-actions">
3040 + <div class="mxchat-search-group">
3041 + <span class="dashicons dashicons-search"></span>
3042 + <input type="text" name="s" class="mxchat-search-input"
3043 + placeholder="<?php esc_attr_e('Search Actions', 'mxchat'); ?>"
3044 + value="<?php echo esc_attr($search_term); ?>">
2777 3045 </div>
2778 -
2779 - <div class="mxchat-form-group">
2780 - <label for="phrases">
2781 - <?php esc_html_e('Phrases (comma-separated)', 'mxchat'); ?>
2782 - </label>
2783 - <textarea name="phrases" id="phrases" rows="5" required
2784 - class="mxchat-intent-textarea"
2785 - 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>
2786 - </div>
2787 -
2788 - <div class="mxchat-form-group">
2789 - <label for="callback_function">
2790 - <?php esc_html_e('Callback Function', 'mxchat'); ?>
2791 - </label>
2792 - <select name="callback_function" id="callback_function"
2793 - class="mxchat-intent-select" required>
2794 - <option value="">
2795 - <?php esc_html_e('Select a Callback', 'mxchat'); ?>
3046 + <select name="callback_filter" class="mxchat-action-filter">
3047 + <option value=""><?php esc_html_e('All Action Types', 'mxchat'); ?></option>
3048 + <?php foreach ($available_callbacks as $function => $callback_data) :
3049 + $label = $callback_data['label']; ?>
3050 + <option value="<?php echo esc_attr($function); ?>"
3051 + <?php selected($callback_filter, $function); ?>>
3052 + <?php echo esc_html($label); ?>
2796 3053 </option>
2797 - <?php
2798 - $groups = $this->mxchat_get_available_callbacks(true);
2799 - foreach ($groups as $group_label => $group_callbacks) :
2800 - echo '<optgroup label="' . esc_attr($group_label) . '">';
2801 - foreach ($group_callbacks as $function => $data) :
2802 - $label = $data['label'];
2803 - $pro_only = $data['pro_only'];
2804 - $disabled = (!$this->is_activated && $pro_only) ? 'disabled' : '';
2805 - $label_suffix = (!$this->is_activated && $pro_only) ? ' (Pro Only)' : '';
2806 - ?>
2807 - <option value="<?php echo esc_attr($function); ?>"
2808 - <?php echo $disabled; ?>>
2809 - <?php echo esc_html($label . $label_suffix); ?>
2810 - </option>
2811 - <?php
2812 - endforeach;
2813 - echo '</optgroup>';
2814 - endforeach;
2815 - ?>
2816 - </select>
2817 - </div>
2818 -
2819 - <button type="submit" class="mxchat-button-primary">
2820 - <?php esc_html_e('Add Intent', 'mxchat'); ?>
3054 + <?php endforeach; ?>
3055 + </select>
3056 + <button type="submit" class="mxchat-button-secondary">
3057 + <?php esc_html_e('Filter', 'mxchat'); ?>
2821 3058 </button>
2822 3059 </form>
2823 3060 </div>
3061 + <div class="mxchat-actions-controls">
3062 + <button type="button" id="mxchat-add-action-btn" class="mxchat-button-primary">
3063 + <span class="dashicons dashicons-plus-alt"></span>
3064 + <?php esc_html_e('Add New Action', 'mxchat'); ?>
3065 + </button>
3066 + </div>
3067 + </div>
2824 3068
2825 - <!-- Manage Intents Card -->
2826 - <div class="mxchat-card">
2827 - <div class="mxchat-card-header">
2828 - <h2><?php esc_html_e('Manage Intents', 'mxchat'); ?></h2>
2829 - <div class="mxchat-header-actions">
2830 - <form method="get" class="mxchat-search-form">
2831 - <input type="hidden" name="page" value="mxchat-intents">
2832 - <div class="mxchat-search-group">
2833 - <span class="dashicons dashicons-search"></span>
2834 - <input type="text" name="s"
2835 - placeholder="<?php esc_attr_e('Search Intents', 'mxchat'); ?>"
2836 - value="<?php echo esc_attr($search_term); ?>">
2837 - <select name="callback_filter" class="mxchat-intent-filter">
2838 - <option value="">
2839 - <?php esc_html_e('All Callbacks', 'mxchat'); ?>
2840 - </option>
2841 - <?php foreach ($available_callbacks as $function => $callback_data) :
2842 - $label = $callback_data['label']; ?>
2843 - <option value="<?php echo esc_attr($function); ?>"
2844 - <?php selected($callback_filter, $function); ?>>
2845 - <?php echo esc_html($label); ?>
2846 - </option>
2847 - <?php endforeach; ?>
2848 - </select>
2849 - <button type="submit" class="mxchat-button-secondary">
2850 - <?php esc_html_e('Filter', 'mxchat'); ?>
2851 - </button>
3069 + <!-- Actions Grid Layout - All actions in a single grid -->
3070 + <div class="mxchat-actions-grid">
3071 + <div class="mxchat-cards-container">
3072 + <?php if (!empty($actions)) : ?>
3073 + <?php foreach ($actions as $action) :
3074 + $callback_function = $action->callback_function;
3075 + $callback_label = isset($available_callbacks[$callback_function]['label'])
3076 + ? $available_callbacks[$callback_function]['label']
3077 + : $callback_function;
3078 + $threshold_value = isset($action->similarity_threshold)
3079 + ? round($action->similarity_threshold * 100)
3080 + : 85;
3081 +
3082 + // Check if this is a form action
3083 + $is_form_action = strpos($action->intent_label, 'Form ') === 0;
3084 +
3085 + // Get action status (enabled/disabled) - default to true if column doesn't exist
3086 + $is_enabled = isset($action->enabled) ? (bool)$action->enabled : true;
3087 +
3088 + // Get enabled bots for display
3089 + $enabled_bots = [];
3090 + if (isset($action->enabled_bots) && !empty($action->enabled_bots)) {
3091 + $enabled_bots = json_decode($action->enabled_bots, true);
3092 + if (!is_array($enabled_bots)) {
3093 + $enabled_bots = ['default'];
3094 + }
3095 + } else {
3096 + $enabled_bots = ['default']; // Backward compatibility
3097 + }
3098 + ?>
3099 + <div class="mxchat-action-card <?php echo $is_form_action ? 'mxchat-form-action' : ''; ?>">
3100 + <div class="mxchat-card-header">
3101 + <div class="mxchat-card-title"><?php echo esc_html($action->intent_label); ?></div>
3102 + <div class="mxchat-card-toggle">
3103 + <label class="mxchat-switch">
3104 + <input type="checkbox" class="mxchat-action-toggle"
3105 + data-action-id="<?php echo esc_attr($action->id); ?>"
3106 + <?php checked($is_enabled); ?>>
3107 + <span class="mxchat-slider round"></span>
3108 + </label>
3109 + </div>
2852 3110 </div>
2853 - </form>
2854 - </div>
2855 - </div>
2856 3111
2857 - <div class="mxchat-table-wrapper">
2858 - <table class="mxchat-records-table">
2859 - <thead>
2860 - <tr>
2861 - <th><?php esc_html_e('Intent Label', 'mxchat'); ?></th>
2862 - <th><?php esc_html_e('Phrases', 'mxchat'); ?></th>
2863 - <th><?php esc_html_e('Callback Function', 'mxchat'); ?></th>
2864 - <th><?php esc_html_e('Similarity Threshold', 'mxchat'); ?></th>
2865 - <th><?php esc_html_e('Actions', 'mxchat'); ?></th>
2866 - </tr>
2867 - </thead>
2868 - <tbody>
2869 - <?php if ($intents) :
2870 - foreach ($intents as $intent) :
2871 - $callback_function = $intent->callback_function;
2872 - $callback_label = isset($available_callbacks[$callback_function]['label'])
2873 - ? $available_callbacks[$callback_function]['label']
2874 - : $callback_function;
2875 - $threshold_value = isset($intent->similarity_threshold)
2876 - ? round($intent->similarity_threshold * 100)
2877 - : 85;
3112 + <div class="mxchat-card-body">
3113 + <div class="mxchat-card-description">
3114 + <strong><?php esc_html_e('Type:', 'mxchat'); ?></strong>
3115 + <?php echo esc_html($callback_label); ?>
3116 + </div>
2878 3117
2879 - // Check if this is a form intent (its intent_label starts with "Form ")
2880 - $is_form_intent = strpos($intent->intent_label, 'Form ') === 0;
3118 + <div class="mxchat-card-phrases">
3119 + <strong><?php esc_html_e('Trigger phrases:', 'mxchat'); ?></strong>
3120 + <div class="mxchat-phrases-preview">
3121 + <?php
3122 + // Check if the helper function exists, otherwise use a simple substring
3123 + if (method_exists($this, 'get_trimmed_phrases')) {
3124 + echo esc_html($this->get_trimmed_phrases($action->phrases));
3125 + } else {
3126 + echo esc_html(strlen($action->phrases) > 100 ?
3127 + substr($action->phrases, 0, 97) . '...' :
3128 + $action->phrases);
3129 + }
2881 3130 ?>
2882 - <tr<?php echo $is_form_intent ? ' class="mxchat-form-intent"' : ''; ?>>
2883 - <td>
2884 - <?php
2885 - if ($is_form_intent) {
2886 - // Attempt to extract the form ID from the intent_label.
2887 - preg_match('/Form (\d+)/', $intent->intent_label, $matches);
2888 - $form_id = isset($matches[1]) ? intval($matches[1]) : 0;
2889 - if ($form_id) {
2890 - global $wpdb;
2891 - $forms_table = $wpdb->prefix . 'mxchat_forms';
2892 - $form = $wpdb->get_row($wpdb->prepare("SELECT title FROM $forms_table WHERE id = %d", $form_id));
2893 - if ($form && !empty($form->title)) {
2894 - echo esc_html($form->title);
2895 - } else {
2896 - echo esc_html($intent->intent_label);
2897 - }
2898 - } else {
2899 - echo esc_html($intent->intent_label);
2900 - }
2901 - echo ' <span class="mxchat-badge">Form Intent</span>';
3131 + </div>
3132 + </div>
3133 +
3134 + <div class="mxchat-threshold-control">
3135 + <div class="mxchat-threshold-label">
3136 + <?php esc_html_e('Similarity Threshold:', 'mxchat'); ?>
3137 + <span class="mxchat-threshold-value"><?php echo esc_html($threshold_value); ?>%</span>
3138 + </div>
3139 + </div>
3140 +
3141 + <!-- Bot Availability Display (Read-only) -->
3142 + <div class="mxchat-card-bots">
3143 + <strong><?php esc_html_e('Assigned bots:', 'mxchat'); ?></strong>
3144 + <div class="mxchat-bot-badges">
3145 + <?php
3146 + foreach ($enabled_bots as $bot_id) {
3147 + if ($bot_id === 'default') {
3148 + echo '<span class="mxchat-bot-badge">' . esc_html__('Default Bot', 'mxchat') . '</span>';
3149 + } else {
3150 + // Try to get bot name from multi-bot manager
3151 + if (class_exists('MxChat_Multi_Bot_Core_Manager')) {
3152 + $multi_bot_manager = MxChat_Multi_Bot_Core_Manager::get_instance();
3153 + $available_bots = $multi_bot_manager->get_available_bots();
3154 + $bot_name = isset($available_bots[$bot_id]) ? $available_bots[$bot_id] : $bot_id;
3155 + echo '<span class="mxchat-bot-badge">' . esc_html($bot_name) . '</span>';
2902 3156 } else {
2903 - echo esc_html($intent->intent_label);
3157 + echo '<span class="mxchat-bot-badge">' . esc_html($bot_id) . '</span>';
2904 3158 }
2905 - ?>
2906 - </td>
2907 - <td class="mxchat-content-cell">
2908 - <?php echo esc_html($intent->phrases); ?>
2909 - </td>
2910 - <td><?php echo esc_html($callback_label); ?></td>
2911 - <td>
2912 - <!-- Similarity threshold adjustment (shown for all intents) -->
2913 - <form method="post"
2914 - action="<?php echo esc_url(admin_url('admin-post.php')); ?>"
2915 - class="mxchat-threshold-form">
2916 - <?php wp_nonce_field('mxchat_update_intent_threshold_nonce'); ?>
2917 - <input type="hidden" name="action"
2918 - value="mxchat_update_intent_threshold">
2919 - <input type="hidden" name="intent_id"
2920 - value="<?php echo esc_attr($intent->id); ?>">
2921 - <div class="mxchat-slider-group">
2922 - <input type="range"
2923 - name="intent_threshold"
2924 - id="intent_threshold_<?php echo esc_attr($intent->id); ?>"
2925 - min="70"
2926 - max="95"
2927 - value="<?php echo esc_attr($threshold_value); ?>"
2928 - class="mxchat-intent-slider"
2929 - oninput="document.getElementById('threshold_output_<?php echo esc_attr($intent->id); ?>').value = this.value + '%'">
2930 - <output id="threshold_output_<?php echo esc_attr($intent->id); ?>"
2931 - class="mxchat-intent-output">
2932 - <?php echo esc_html($threshold_value); ?>%
2933 - </output>
2934 - </div>
2935 - <button type="submit" class="mxchat-button-icon">
2936 - <span class="dashicons dashicons-saved"></span>
2937 - </button>
2938 - </form>
2939 - </td>
2940 - <td class="mxchat-actions-cell">
2941 - <?php if (!$is_form_intent) : ?>
2942 - <!-- Standard intents: allow edit and delete -->
2943 - <button type="button"
2944 - class="mxchat-button-icon mxchat-edit-button"
2945 - data-intent-id="<?php echo esc_attr($intent->id); ?>"
2946 - data-phrases="<?php echo esc_attr($intent->phrases); ?>">
2947 - <span class="dashicons dashicons-edit"></span>
2948 - </button>
2949 - <form method="post"
2950 - action="<?php echo esc_url(admin_url('admin-post.php')); ?>"
2951 - class="mxchat-delete-form"
2952 - onsubmit="return confirm('<?php esc_attr_e('Are you sure you want to delete this intent?', 'mxchat'); ?>');">
2953 - <?php wp_nonce_field('mxchat_delete_intent_nonce'); ?>
2954 - <input type="hidden" name="action" value="mxchat_delete_intent">
2955 - <input type="hidden" name="intent_id"
2956 - value="<?php echo esc_attr($intent->id); ?>">
2957 - <button type="submit" class="mxchat-button-icon">
2958 - <span class="dashicons dashicons-trash"></span>
2959 - </button>
2960 - </form>
2961 - <?php else : ?>
2962 - <!-- Form intents: show manage in forms button -->
2963 - <?php
2964 - preg_match('/Form (\d+)/', $intent->intent_label, $matches);
2965 - $form_id = isset($matches[1]) ? $matches[1] : '';
2966 - ?>
2967 - <a href="<?php echo esc_url(admin_url('admin.php?page=mxchat-forms&action=edit&form_id=' . $form_id)); ?>"
2968 - class="mxchat-button-secondary">
2969 - <?php esc_html_e('Manage in Forms', 'mxchat'); ?>
2970 - </a>
2971 - <?php endif; ?>
2972 - </td>
2973 - </tr>
2974 - <?php endforeach;
2975 - else : ?>
2976 - <tr>
2977 - <td colspan="5" class="mxchat-no-records">
2978 - <?php esc_html_e('No intents found.', 'mxchat'); ?>
2979 - </td>
2980 - </tr>
2981 - <?php endif; ?>
2982 - </tbody>
2983 - </table>
2984 - </div>
3159 + }
3160 + }
3161 + ?>
3162 + </div>
3163 + </div>
3164 + </div>
2985 3165
2986 - <?php if ($total_pages > 1) : ?>
2987 - <div class="mxchat-pagination">
2988 - <?php
2989 - echo paginate_links(array(
2990 - 'base' => add_query_arg('paged', '%#%'),
2991 - 'format' => '',
2992 - 'prev_text' => __('&laquo; Previous', 'mxchat'),
2993 - 'next_text' => __('Next &raquo;', 'mxchat'),
2994 - 'total' => $total_pages,
2995 - 'current' => $page
2996 - ));
2997 - ?>
3166 + <div class="mxchat-card-footer">
3167 + <?php
3168 + // Check if it's a form action
3169 + $is_form_action = preg_match('/Form (\d+)/', $action->intent_label, $form_matches);
3170 +
3171 + // Check if it's a recommendation flow action
3172 + $is_flow_action = preg_match('/Recommendation Flow (\d+)/', $action->intent_label, $flow_matches);
3173 +
3174 + if ($is_form_action) {
3175 + $form_id = isset($form_matches[1]) ? $form_matches[1] : '';
3176 + ?>
3177 + <a href="<?php echo esc_url(admin_url('admin.php?page=mxchat-forms&action=edit&form_id=' . $form_id)); ?>"
3178 + class="mxchat-button-primary">
3179 + <span class="dashicons dashicons-feedback"></span>
3180 + <?php esc_html_e('Edit Form', 'mxchat'); ?>
3181 + </a>
3182 + <?php } elseif ($is_flow_action) {
3183 + ?>
3184 + <a href="<?php echo esc_url(admin_url('admin.php?page=mxchat-smart-recommender')); ?>"
3185 + class="mxchat-button-primary">
3186 + <span class="dashicons dashicons-list-view"></span>
3187 + <?php esc_html_e('Manage Flows', 'mxchat'); ?>
3188 + </a>
3189 + <?php } else { ?>
3190 + <button type="button"
3191 + class="mxchat-button-secondary mxchat-edit-button"
3192 + data-action-id="<?php echo esc_attr($action->id); ?>"
3193 + data-phrases="<?php echo esc_attr($action->phrases); ?>"
3194 + data-label="<?php echo esc_attr($action->intent_label); ?>"
3195 + data-threshold="<?php echo esc_attr(round($action->similarity_threshold * 100)); ?>"
3196 + data-callback-function="<?php echo esc_attr($action->callback_function); ?>"
3197 + data-enabled-bots="<?php echo esc_attr(json_encode($enabled_bots)); ?>">
3198 + <span class="dashicons dashicons-edit"></span>
3199 + <?php esc_html_e('Edit', 'mxchat'); ?>
3200 + </button>
3201 + <form method="post"
3202 + action="<?php echo esc_url(admin_url('admin-post.php')); ?>"
3203 + class="mxchat-delete-form"
3204 + onsubmit="return confirm('<?php esc_attr_e('Are you sure you want to delete this action?', 'mxchat'); ?>');">
3205 + <?php wp_nonce_field('mxchat_delete_intent_nonce'); ?>
3206 + <input type="hidden" name="action" value="mxchat_delete_intent">
3207 + <input type="hidden" name="intent_id" value="<?php echo esc_attr($action->id); ?>">
3208 + <button type="submit" class="mxchat-button-text mxchat-delete-button">
3209 + <span class="dashicons dashicons-trash"></span>
3210 + <?php esc_html_e('Delete', 'mxchat'); ?>
3211 + </button>
3212 + </form>
3213 + <?php } ?>
3214 + </div>
3215 + </div>
3216 + <?php endforeach; ?>
3217 + <?php else : ?>
3218 + <!-- If no actions found -->
3219 + <div class="mxchat-no-actions">
3220 + <div class="mxchat-empty-state">
3221 + <span class="dashicons dashicons-format-chat"></span>
3222 + <h2><?php esc_html_e('No actions found', 'mxchat'); ?></h2>
3223 + <p><?php esc_html_e('Get started by creating your first action to enhance your chatbot.', 'mxchat'); ?></p>
3224 + <button type="button" id="mxchat-create-first-action" class="mxchat-button-primary">
3225 + <?php esc_html_e('Create Your First Action', 'mxchat'); ?>
3226 + </button>
3227 + </div>
2998 3228 </div>
2999 3229 <?php endif; ?>
3000 3230 </div>
3001 3231 </div>
3002 3232
3003 - <!-- Edit Modal -->
3004 -<div id="mxchat-edit-modal" class="mxchat-modal" style="display: none;">
3233 + <?php if ($total_pages > 1) : ?>
3234 + <div class="mxchat-pagination">
3235 + <?php
3236 + echo paginate_links(array(
3237 + 'base' => add_query_arg('paged', '%#%'),
3238 + 'format' => '',
3239 + 'prev_text' => __('&laquo; Previous', 'mxchat'),
3240 + 'next_text' => __('Next &raquo;', 'mxchat'),
3241 + 'total' => $total_pages,
3242 + 'current' => $page
3243 + ));
3244 + ?>
3245 + </div>
3246 + <?php endif; ?>
3247 +
3248 +<!-- Add/Edit Action Modal with Step-Based Approach -->
3249 +<!-- Complete Modal HTML with Defined Groups Variable -->
3250 +<div id="mxchat-action-modal" class="mxchat-modal" style="display: none;">
3005 3251 <div class="mxchat-modal-content">
3006 3252 <span class="mxchat-modal-close">&times;</span>
3007 - <div class="mxchat-card-header">
3008 - <h2><?php esc_html_e('Edit Intent Phrases', 'mxchat'); ?></h2>
3009 - </div>
3010 3253
3011 - <form id="mxchat-edit-intent-form" method="post"
3012 - action="<?php echo esc_url(admin_url('admin-post.php')); ?>">
3013 - <?php wp_nonce_field('mxchat_edit_intent_nonce'); ?>
3014 - <input type="hidden" name="action" value="mxchat_edit_intent">
3015 - <input type="hidden" name="intent_id" id="edit_intent_id">
3254 + <form id="mxchat-action-form" method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>">
3255 + <!-- Dynamic nonce field -->
3256 + <div id="action-nonce-container">
3257 + <?php wp_nonce_field('mxchat_add_intent_nonce', 'add_intent_nonce'); ?>
3258 + </div>
3259 + <input type="hidden" name="action" id="form_action_type" value="mxchat_add_intent">
3260 + <input type="hidden" name="intent_id" id="edit_action_id" value="">
3261 + <input type="hidden" name="callback_function" id="callback_function" value="">
3016 3262
3017 - <div class="mxchat-form-group">
3018 - <label for="edit_phrases">
3019 - <?php esc_html_e('Phrases (comma-separated)', 'mxchat'); ?>
3020 - </label>
3021 - <textarea name="phrases"
3022 - id="edit_phrases"
3023 - rows="5"
3024 - class="mxchat-intent-textarea"
3025 - required></textarea>
3263 + <!-- Step 1: Action Type Selection -->
3264 + <div id="mxchat-action-step-1" class="mxchat-action-step active">
3265 + <div class="mxchat-step-indicator">
3266 + <div class="mxchat-step-number">1</div>
3267 + <div class="mxchat-step-title"><?php esc_html_e('Select Action Type', 'mxchat'); ?></div>
3268 + </div>
3269 +
3270 + <div id="mxchat-action-type-selector" class="mxchat-action-type-selector">
3271 + <div class="mxchat-action-type-search">
3272 + <span class="dashicons dashicons-search"></span>
3273 + <input type="text" id="action-type-search" placeholder="<?php esc_attr_e('Search action types...', 'mxchat'); ?>" class="mxchat-action-type-search-input">
3274 + </div>
3275 +
3276 + <?php
3277 + // Get the callbacks - IMPORTANT: Define the $groups variable here
3278 + $groups = $this->mxchat_get_available_callbacks(true, true);
3279 + ?>
3280 +
3281 + <div class="mxchat-action-type-categories">
3282 + <button type="button" class="mxchat-category-button active" data-category="all"><?php esc_html_e('All', 'mxchat'); ?></button>
3283 + <?php
3284 + // Get unique categories from the defined groups
3285 + foreach ($groups as $group_label => $group_callbacks) :
3286 + $category_slug = sanitize_title($group_label);
3287 + ?>
3288 + <button type="button" class="mxchat-category-button" data-category="<?php echo esc_attr($category_slug); ?>"><?php echo esc_html($group_label); ?></button>
3289 + <?php endforeach; ?>
3290 + </div>
3291 +
3292 + <div class="mxchat-action-types-grid">
3293 + <?php
3294 + // Generate action cards from available callbacks
3295 + foreach ($groups as $group_label => $group_callbacks) :
3296 + $category_slug = sanitize_title($group_label);
3297 +
3298 + foreach ($group_callbacks as $function => $data) :
3299 + $label = $data['label'];
3300 + $pro_only = $data['pro_only'];
3301 + $icon = isset($data['icon']) ? $data['icon'] : 'admin-generic';
3302 + $description = isset($data['description']) ? $data['description'] : '';
3303 + $is_addon = isset($data['addon']) && $data['addon'] !== false;
3304 + $addon_name = isset($data['addon_name']) ? $data['addon_name'] : '';
3305 + $is_installed = isset($data['installed']) ? $data['installed'] : true;
3306 +
3307 + // Determine card status and styling
3308 + $card_class = 'mxchat-action-type-card';
3309 + $icon_class = 'mxchat-action-type-icon';
3310 + $status_badge = '';
3311 +
3312 + if ($pro_only && !$this->is_activated) {
3313 + // Pro feature but no Pro license
3314 + $icon_class .= ' pro-feature';
3315 + $status_badge = '<span class="mxchat-pro-badge">' . esc_html__('Pro', 'mxchat') . '</span>';
3316 + }
3317 +
3318 + if ($is_addon && !$is_installed) {
3319 + // Add-on not installed
3320 + $card_class .= ' not-installed';
3321 + $status_badge .= '<span class="mxchat-addon-badge">' . esc_html__('Add-on Required', 'mxchat') . '</span>';
3322 + }
3323 +
3324 + // Default description if none provided
3325 + if (empty($description)) {
3326 + $description = sprintf(
3327 + esc_html__('Use the %s action in your chatbot', 'mxchat'),
3328 + $label
3329 + );
3330 + }
3331 + ?>
3332 + <div class="<?php echo esc_attr($card_class); ?>"
3333 + data-category="<?php echo esc_attr($category_slug); ?>"
3334 + data-value="<?php echo esc_attr($function); ?>"
3335 + data-label="<?php echo esc_attr($label); ?>"
3336 + data-pro="<?php echo $pro_only ? 'true' : 'false'; ?>"
3337 + data-addon="<?php echo esc_attr($is_addon ? $data['addon'] : ''); ?>"
3338 + data-installed="<?php echo $is_installed ? 'true' : 'false'; ?>">
3339 + <div class="<?php echo esc_attr($icon_class); ?>">
3340 + <span class="dashicons dashicons-<?php echo esc_attr($icon); ?>"></span>
3341 + </div>
3342 + <div class="mxchat-action-type-info">
3343 + <h4><?php echo esc_html($label); ?></h4>
3344 + <p><?php echo esc_html($description); ?></p>
3345 + <?php if (!empty($status_badge)) : ?>
3346 + <?php echo $status_badge; ?>
3347 + <?php endif; ?>
3348 +
3349 + <?php if ($is_addon && !$is_installed) : ?>
3350 + <div class="mxchat-addon-info">
3351 + <?php echo esc_html(sprintf(
3352 + __('Requires %s', 'mxchat'),
3353 + $addon_name
3354 + )); ?>
3355 + </div>
3356 + <?php endif; ?>
3357 + </div>
3358 + </div>
3359 + <?php endforeach;
3360 + endforeach; ?>
3361 + </div>
3362 + </div>
3363 +
3364 + <div class="mxchat-modal-actions">
3365 + <button type="button" class="mxchat-button-secondary mxchat-modal-cancel">
3366 + <?php esc_html_e('Cancel', 'mxchat'); ?>
3367 + </button>
3368 + </div>
3026 3369 </div>
3027 3370
3028 - <div class="mxchat-modal-actions">
3029 - <button type="submit" class="mxchat-button-primary">
3030 - <?php esc_html_e('Update Phrases', 'mxchat'); ?>
3031 - </button>
3032 - <button type="button" class="mxchat-button-secondary mxchat-modal-cancel">
3033 - <?php esc_html_e('Cancel', 'mxchat'); ?>
3034 - </button>
3371 + <!-- Step 2: Action Configuration -->
3372 + <div id="mxchat-action-step-2" class="mxchat-action-step">
3373 + <div class="mxchat-step-indicator">
3374 + <div class="mxchat-step-number">2</div>
3375 + <div class="mxchat-step-title"><?php esc_html_e('Configure Action', 'mxchat'); ?></div>
3376 + </div>
3377 +
3378 + <div class="mxchat-selected-action">
3379 + <button type="button" class="mxchat-back-button" id="mxchat-back-to-step-1">
3380 + <span class="dashicons dashicons-arrow-left-alt"></span>
3381 + <?php esc_html_e('Back to Action Types', 'mxchat'); ?>
3382 + </button>
3383 + <div class="mxchat-selected-action-info">
3384 + <div id="selected-action-icon" class="mxchat-action-type-icon">
3385 + <span class="dashicons dashicons-admin-generic"></span>
3386 + </div>
3387 + <div class="mxchat-selected-action-details">
3388 + <h3 id="selected-action-title"><?php esc_html_e('Selected Action', 'mxchat'); ?></h3>
3389 + <p id="selected-action-description"><?php esc_html_e('Configure this action for your chatbot', 'mxchat'); ?></p>
3390 + </div>
3391 + </div>
3392 + </div>
3393 +
3394 + <div class="mxchat-form-group">
3395 + <label for="intent_label">
3396 + <?php esc_html_e('Action Label (For your reference only)', 'mxchat'); ?>
3397 + </label>
3398 + <input name="intent_label" type="text" id="intent_label" required
3399 + class="mxchat-intent-input"
3400 + placeholder="<?php esc_attr_e('Example: Newsletter Signup', 'mxchat'); ?>">
3401 + </div>
3402 +
3403 + <div class="mxchat-form-group">
3404 + <label for="phrases">
3405 + <?php esc_html_e('Trigger Phrases (comma-separated)', 'mxchat'); ?>
3406 + </label>
3407 + <textarea name="phrases" id="action_phrases" rows="5" required
3408 + class="mxchat-intent-textarea"
3409 + placeholder="<?php esc_attr_e('Example: sign me up, subscribe me, I want to join, add me to the newsletter', 'mxchat'); ?>"></textarea>
3410 + </div>
3411 +
3412 + <div class="mxchat-form-group">
3413 + <label for="similarity_threshold">
3414 + <?php esc_html_e('Similarity Threshold', 'mxchat'); ?>
3415 + <span class="mxchat-threshold-value-display">85%</span>
3416 + </label>
3417 + <div class="mxchat-slider-group modal-slider">
3418 + <input type="range"
3419 + name="similarity_threshold"
3420 + id="similarity_threshold"
3421 + min="10"
3422 + max="95"
3423 + value="85"
3424 + class="mxchat-intent-slider"
3425 + oninput="document.querySelector('.mxchat-threshold-value-display').textContent = this.value + '%'">
3426 + </div>
3427 + <div class="mxchat-threshold-hint">
3428 + <?php esc_html_e('Lower values (10-30) make the action trigger more easily. Higher values (70-95) require more exact matches.', 'mxchat'); ?>
3429 + </div>
3430 + </div>
3431 +
3432 + <!-- Bot Selection Section -->
3433 + <div class="mxchat-form-group">
3434 + <label for="enabled_bots">
3435 + <?php esc_html_e('Which bots should we enable this action for?', 'mxchat'); ?>
3436 + </label>
3437 + <div class="mxchat-bot-selector">
3438 + <div class="mxchat-bot-option">
3439 + <label class="mxchat-checkbox-label">
3440 + <input type="checkbox"
3441 + name="enabled_bots[]"
3442 + value="default"
3443 + id="bot_default"
3444 + checked="checked">
3445 + <span class="mxchat-checkmark"></span>
3446 + <?php esc_html_e('Default Bot', 'mxchat'); ?>
3447 + </label>
3448 + </div>
3449 +
3450 + <?php if (class_exists('MxChat_Multi_Bot_Core_Manager')) :
3451 + $multi_bot_manager = MxChat_Multi_Bot_Core_Manager::get_instance();
3452 + $available_bots = $multi_bot_manager->get_available_bots();
3453 +
3454 + foreach ($available_bots as $bot_id => $bot_name) :
3455 + if ($bot_id === 'default') continue; // Skip default, already shown above
3456 + ?>
3457 + <div class="mxchat-bot-option">
3458 + <label class="mxchat-checkbox-label">
3459 + <input type="checkbox"
3460 + name="enabled_bots[]"
3461 + value="<?php echo esc_attr($bot_id); ?>"
3462 + id="bot_<?php echo esc_attr($bot_id); ?>">
3463 + <span class="mxchat-checkmark"></span>
3464 + <?php echo esc_html($bot_name); ?>
3465 + </label>
3466 + </div>
3467 + <?php
3468 + endforeach;
3469 + endif; ?>
3470 + </div>
3471 + </div>
3472 +
3473 + <div class="mxchat-modal-actions">
3474 + <button type="button" class="mxchat-button-secondary mxchat-modal-cancel">
3475 + <?php esc_html_e('Cancel', 'mxchat'); ?>
3476 + </button>
3477 + <button type="submit" class="mxchat-button-primary" id="mxchat-save-action-btn">
3478 + <?php esc_html_e('Save Action', 'mxchat'); ?>
3479 + </button>
3480 + </div>
3035 3481 </div>
3036 3482 </form>
3037 3483 </div>
3038 3484 </div>
3039 3485
3040 - </div><!-- .mxchat-content -->
3486 +<div id="mxchat-action-loading" class="mxchat-action-loading" style="display: none;">
3487 + <div class="mxchat-action-loading-spinner"></div>
3488 + <div class="mxchat-action-loading-text">
3489 + <?php esc_html_e('Saving action, please wait...', 'mxchat'); ?>
3490 + </div>
3491 +</div>
3041 3492 </div><!-- .mxchat-wrapper -->
3493 + <?php
3494 +}
3495 +private function get_trimmed_phrases($phrases, $max_length = 100) {
3496 + if (strlen($phrases) <= $max_length) {
3497 + return $phrases;
3498 + }
3042 3499
3043 - <div id="mxchat-intent-loading" class="mxchat-intent-loading" style="display: none;">
3044 - <div class="mxchat-intent-loading-spinner"></div>
3045 - <div class="mxchat-intent-loading-text">
3046 - <?php esc_html_e('Saving intent, please wait...', 'mxchat'); ?>
3047 - </div>
3048 - </div>
3049 -<?php
3500 + $trimmed = substr($phrases, 0, $max_length);
3501 + $last_comma = strrpos($trimmed, ',');
3502 +
3503 + if ($last_comma !== false) {
3504 + $trimmed = substr($trimmed, 0, $last_comma);
3505 + }
3506 +
3507 + return $trimmed . '...';
3050 3508 }
3509 +public function mxchat_add_enabled_column_to_intents() {
3510 + global $wpdb;
3511 + $table_name = $wpdb->prefix . 'mxchat_intents';
3051 3512
3513 + // Check if the column already exists
3514 + $columns = $wpdb->get_results("SHOW COLUMNS FROM $table_name LIKE 'enabled'");
3052 3515
3516 + if (empty($columns)) {
3517 + // Add the column with default value of 1 (enabled)
3518 + $wpdb->query("ALTER TABLE $table_name ADD COLUMN enabled TINYINT(1) NOT NULL DEFAULT 1");
3519 + }
3520 +}
3053 3521
3054 -/**
3055 - * Handle editing of intent phrases
3056 - *
3057 - * @since 1.0.0
3058 - * @return void
3059 - */
3060 -public function handle_edit_intent() {
3061 - // Verify nonce and user capabilities
3062 - if (!isset($_POST['_wpnonce']) || !wp_verify_nonce($_POST['_wpnonce'], 'mxchat_edit_intent_nonce')) {
3063 - wp_die(esc_html__('Security check failed.', 'mxchat'));
3522 +public function mxchat_handle_delete_intent() {
3523 + if ( ! current_user_can( 'manage_options' ) ) {
3524 + wp_die( esc_html__('Unauthorized user', 'mxchat') );
3064 3525 }
3065 3526
3527 + check_admin_referer('mxchat_delete_intent_nonce');
3528 +
3529 + if (isset($_POST['intent_id'])) {
3530 + global $wpdb;
3531 + $table_name = $wpdb->prefix . 'mxchat_intents';
3532 + $intent_id = intval($_POST['intent_id']);
3533 +
3534 + $wpdb->delete($table_name, ['id' => $intent_id], ['%d']);
3535 + }
3536 +
3537 + wp_safe_redirect(admin_url('admin.php?page=mxchat-actions'));
3538 + exit;
3539 +}
3540 +public function mxchat_handle_edit_intent() {
3541 + // Security checks (nonce and permissions)
3066 3542 if (!current_user_can('manage_options')) {
3067 - wp_die(esc_html__('You do not have permission to perform this action.', 'mxchat'));
3543 + wp_die(esc_html__('Unauthorized user', 'mxchat'));
3068 3544 }
3545 + check_admin_referer('mxchat_edit_intent');
3069 3546
3070 - // Validate and sanitize input
3547 + // Get POST data
3071 3548 $intent_id = isset($_POST['intent_id']) ? absint($_POST['intent_id']) : 0;
3072 - if (!$intent_id) {
3073 - wp_die(esc_html__('Invalid intent ID.', 'mxchat'));
3549 + $intent_label = isset($_POST['intent_label']) ? sanitize_text_field($_POST['intent_label']) : '';
3550 + $phrases_input = isset($_POST['phrases']) ? sanitize_textarea_field($_POST['phrases']) : '';
3551 + $threshold_percentage = isset($_POST['similarity_threshold']) ? intval($_POST['similarity_threshold']) : 85;
3552 + $similarity_threshold = min(95, max(10, $threshold_percentage)) / 100; // Convert to 0.10–0.95
3553 +
3554 + // Handle enabled_bots
3555 + $enabled_bots = isset($_POST['enabled_bots']) ? $_POST['enabled_bots'] : array('default');
3556 + $enabled_bots = array_map('sanitize_text_field', $enabled_bots);
3557 +
3558 + // Ensure default is always included for backward compatibility
3559 + if (!in_array('default', $enabled_bots)) {
3560 + $enabled_bots[] = 'default';
3074 3561 }
3562 +
3563 + $enabled_bots_json = json_encode($enabled_bots);
3075 3564
3076 - $phrases_input = isset($_POST['phrases']) ? sanitize_textarea_field($_POST['phrases']) : '';
3077 - if (empty($phrases_input)) {
3078 - wp_die(esc_html__('Phrases cannot be empty.', 'mxchat'));
3565 + // Validate inputs
3566 + if (!$intent_id || empty($intent_label) || empty($phrases_input)) {
3567 + $this->handle_embedding_error(__('Invalid input. Please ensure all fields are filled out.', 'mxchat'));
3568 + return;
3079 3569 }
3080 3570
3081 - // Process phrases the same way as in intent creation
3082 3571 $phrases_array = array_map('sanitize_text_field', array_filter(array_map('trim', explode(',', $phrases_input))));
3083 -
3084 3572 if (empty($phrases_array)) {
3085 - wp_die(esc_html__('Please enter at least one valid phrase.', 'mxchat'));
3573 + $this->handle_embedding_error(__('Please enter at least one valid phrase.', 'mxchat'));
3574 + return;
3086 3575 }
3087 3576
3088 - // Generate embeddings and combine them
3577 + // Generate embeddings with improved error handling
3089 3578 $vectors = [];
3579 + $failed_phrases = [];
3580 +
3090 3581 foreach ($phrases_array as $phrase) {
3091 3582 $embedding_vector = $this->mxchat_generate_embedding($phrase, $this->options['api_key']);
3092 3583 if (is_array($embedding_vector)) {
3093 3584 $vectors[] = $embedding_vector;
3094 3585 } else {
3095 - wp_die(esc_html__('Error generating embedding for phrase: ', 'mxchat') . esc_html($phrase));
3586 + $failed_phrases[] = $phrase;
3096 3587 }
3097 3588 }
3098 3589
3590 + if (!empty($failed_phrases)) {
3591 + $this->handle_embedding_error(
3592 + sprintf(
3593 + __('Error generating embeddings for phrases: %s. Check your embedding API.', 'mxchat'),
3594 + implode(', ', $failed_phrases)
3595 + )
3596 + );
3597 + return;
3598 + }
3599 +
3099 3600 if (empty($vectors)) {
3100 - wp_die(esc_html__('No valid embeddings generated. Please check your phrases.', 'mxchat'));
3601 + $this->handle_embedding_error(__('No valid embeddings generated. Please check your phrases.', 'mxchat'));
3602 + return;
3101 3603 }
3102 3604
3103 - // Create combined vector just like in intent creation
3104 3605 $combined_vector = $this->mxchat_average_vectors($vectors);
3105 3606 $serialized_vector = maybe_serialize($combined_vector);
3106 3607
3608 + // Update the database
3107 3609 global $wpdb;
3108 3610 $table_name = $wpdb->prefix . 'mxchat_intents';
3109 3611
3110 - // Update the intent with both new phrases and the combined vector
3111 3612 $result = $wpdb->update(
3112 3613 $table_name,
3113 3614 array(
3615 + 'intent_label' => $intent_label,
3114 3616 'phrases' => implode(', ', $phrases_array),
3115 - 'embedding_vector' => $serialized_vector
3617 + 'embedding_vector' => $serialized_vector,
3618 + 'similarity_threshold' => $similarity_threshold,
3619 + 'enabled_bots' => $enabled_bots_json // Include enabled_bots in update
3116 3620 ),
3117 3621 array('id' => $intent_id),
3118 - array('%s', '%s'),
3119 - array('%d')
3622 + array('%s', '%s', '%s', '%f', '%s'), // Format: string, string, string, float, string
3623 + array('%d') // Where format: integer
3120 3624 );
3121 3625
3122 3626 if (false === $result) {
3123 - wp_die(esc_html__('Failed to update intent.', 'mxchat'));
3627 + $this->handle_embedding_error(__('Failed to update action in database.', 'mxchat'));
3628 + return;
3124 3629 }
3125 3630
3126 - // Redirect back to the intents page with a success message
3631 + // Set success message and redirect
3632 + set_transient('mxchat_admin_notice_success', __('Intent updated successfully!', 'mxchat'), 60);
3633 +
3127 3634 $redirect_url = add_query_arg(
3128 3635 array(
3129 - 'page' => 'mxchat-intents',
3130 - 'updated' => 'true'
3636 + 'page' => 'mxchat-actions'
3131 3637 ),
3132 3638 admin_url('admin.php')
3133 3639 );
3134 -
3135 3640 wp_safe_redirect($redirect_url);
3136 3641 exit;
3137 3642 }
3138 -public function mxchat_handle_update_intent_threshold() {
3139 - if ( ! current_user_can( 'manage_options' ) ) {
3140 - wp_die( esc_html__('Unauthorized user', 'mxchat') );
3643 +public function mxchat_handle_add_intent() {
3644 + if (!current_user_can('manage_options')) {
3645 + wp_die(esc_html__('Unauthorized user', 'mxchat'));
3141 3646 }
3647 + check_admin_referer('mxchat_add_intent_nonce');
3648 + global $wpdb;
3649 + $table_name = $wpdb->prefix . 'mxchat_intents';
3650 +
3651 + // Sanitize and get form data
3652 + $intent_label = isset($_POST['intent_label']) ? sanitize_text_field($_POST['intent_label']) : '';
3653 + $phrases_input = isset($_POST['phrases']) ? sanitize_textarea_field($_POST['phrases']) : '';
3654 + $callback_function = isset($_POST['callback_function']) ? sanitize_text_field($_POST['callback_function']) : '';
3655 +
3656 + // Get similarity threshold from form (convert percentage to decimal)
3657 + $similarity_threshold = isset($_POST['similarity_threshold']) ? floatval($_POST['similarity_threshold']) / 100 : 0.85;
3658 +
3659 + // Handle enabled_bots
3660 + $enabled_bots = isset($_POST['enabled_bots']) ? $_POST['enabled_bots'] : array('default');
3661 + $enabled_bots = array_map('sanitize_text_field', $enabled_bots);
3662 +
3663 + // Ensure default is always included for backward compatibility with existing actions
3664 + if (!in_array('default', $enabled_bots)) {
3665 + $enabled_bots[] = 'default';
3666 + }
3667 +
3668 + $enabled_bots_json = json_encode($enabled_bots);
3669 +
3670 + // Validate required fields
3671 + if (empty($intent_label) || empty($callback_function) || empty($phrases_input)) {
3672 + $this->handle_embedding_error(__('Invalid input. Please ensure all fields are filled out.', 'mxchat'));
3673 + return;
3674 + }
3675 +
3676 + // Validate callback function
3677 + $available_callbacks = $this->mxchat_get_available_callbacks();
3678 + if (!array_key_exists($callback_function, $available_callbacks)) {
3679 + $this->handle_embedding_error(__('Invalid callback function selected.', 'mxchat'));
3680 + return;
3681 + }
3682 +
3683 + // Check Pro requirements
3684 + $is_pro_only = $available_callbacks[$callback_function]['pro_only'];
3685 + if ($is_pro_only && !$this->is_activated) {
3686 + $this->handle_embedding_error(__('This callback function is available in the Pro version only.', 'mxchat'));
3687 + return;
3688 + }
3689 +
3690 + // Process phrases
3691 + $phrases_array = array_map('sanitize_text_field', array_filter(array_map('trim', explode(',', $phrases_input))));
3692 + if (empty($phrases_array)) {
3693 + $this->handle_embedding_error(__('Please enter at least one valid phrase.', 'mxchat'));
3694 + return;
3695 + }
3696 +
3697 + // Generate embeddings with improved error handling
3698 + $vectors = [];
3699 + $failed_phrases = [];
3700 + foreach ($phrases_array as $phrase) {
3701 + $embedding_vector = $this->mxchat_generate_embedding($phrase, $this->options['api_key']);
3702 + if (is_array($embedding_vector)) {
3703 + $vectors[] = $embedding_vector;
3704 + } else {
3705 + $failed_phrases[] = $phrase;
3706 + }
3707 + }
3708 +
3709 + // Check for embedding failures
3710 + if (!empty($failed_phrases)) {
3711 + $this->handle_embedding_error(
3712 + sprintf(
3713 + __('Error generating embeddings for phrases: %s. Check your embedding API.', 'mxchat'),
3714 + implode(', ', $failed_phrases)
3715 + )
3716 + );
3717 + return;
3718 + }
3719 +
3720 + if (empty($vectors)) {
3721 + $this->handle_embedding_error(__('No valid embeddings generated. Please check your phrases.', 'mxchat'));
3722 + return;
3723 + }
3724 +
3725 + // Create combined vector and insert into database
3726 + $combined_vector = $this->mxchat_average_vectors($vectors);
3727 + $serialized_vector = maybe_serialize($combined_vector);
3728 +
3729 + $result = $wpdb->insert($table_name, [
3730 + 'intent_label' => $intent_label,
3731 + 'phrases' => implode(', ', $phrases_array),
3732 + 'embedding_vector' => $serialized_vector,
3733 + 'callback_function' => $callback_function,
3734 + 'similarity_threshold' => $similarity_threshold,
3735 + 'enabled_bots' => $enabled_bots_json // NEW field
3736 + ]);
3737 +
3738 + if ($result === false) {
3739 + $this->handle_embedding_error(__('Database error: ', 'mxchat') . $wpdb->last_error);
3740 + return;
3741 + }
3742 +
3743 + // Set success message and redirect
3744 + set_transient('mxchat_admin_notice_success', __('New intent added successfully!', 'mxchat'), 60);
3745 + wp_safe_redirect(admin_url('admin.php?page=mxchat-actions'));
3746 + exit;
3747 +}
3142 3748
3143 - check_admin_referer('mxchat_update_intent_threshold_nonce');
3144 3749
3145 - if (isset($_POST['intent_id'], $_POST['intent_threshold'])) {
3146 - global $wpdb;
3147 - $table_name = $wpdb->prefix . 'mxchat_intents';
3148 - $intent_id = intval($_POST['intent_id']);
3149 - $threshold_percentage = max(70, min(95, intval($_POST['intent_threshold'])));
3150 - $similarity_threshold = $threshold_percentage / 100;
3151 3750
3152 - $wpdb->update(
3153 - $table_name,
3154 - ['similarity_threshold' => $similarity_threshold],
3155 - ['id' => $intent_id],
3156 - ['%f'],
3157 - ['%d']
3751 +
3752 +private function handle_embedding_error($message, $redirect = true) {
3753 + // Store the error message in the existing transient
3754 + set_transient('mxchat_admin_notice_error', $message, 60);
3755 +
3756 + if ($redirect) {
3757 + // Redirect back to the actions page
3758 + $redirect_url = add_query_arg(
3759 + array(
3760 + 'page' => 'mxchat-actions'
3761 + ),
3762 + admin_url('admin.php')
3158 3763 );
3764 + wp_safe_redirect($redirect_url);
3765 + exit;
3159 3766 }
3767 +}
3160 3768
3161 - wp_safe_redirect(admin_url('admin.php?page=mxchat-intents'));
3162 - exit;
3769 +/**
3770 + * AJAX handler to fetch actions list for the new split-panel UI
3771 + */
3772 +public function mxchat_fetch_actions_list() {
3773 + check_ajax_referer('mxchat_actions_nonce', 'security');
3774 +
3775 + if (!current_user_can('manage_options')) {
3776 + wp_send_json_error(__('Unauthorized', 'mxchat'));
3777 + }
3778 +
3779 + global $wpdb;
3780 + $table_name = $wpdb->prefix . 'mxchat_intents';
3781 +
3782 + $page = isset($_POST['page']) ? max(1, intval($_POST['page'])) : 1;
3783 + $per_page = isset($_POST['per_page']) ? min(100, max(1, intval($_POST['per_page']))) : 50;
3784 + $offset = ($page - 1) * $per_page;
3785 + $search = isset($_POST['search']) ? sanitize_text_field($_POST['search']) : '';
3786 + $callback_filter = isset($_POST['callback_filter']) ? sanitize_text_field($_POST['callback_filter']) : '';
3787 + $sort_order = isset($_POST['sort_order']) && $_POST['sort_order'] === 'asc' ? 'ASC' : 'DESC';
3788 +
3789 + // Build WHERE clause
3790 + $where = '1=1';
3791 + $params = array();
3792 +
3793 + if ($search) {
3794 + $search_like = '%' . $wpdb->esc_like($search) . '%';
3795 + $where .= ' AND (intent_label LIKE %s OR phrases LIKE %s)';
3796 + $params[] = $search_like;
3797 + $params[] = $search_like;
3798 + }
3799 +
3800 + if ($callback_filter) {
3801 + $where .= ' AND callback_function = %s';
3802 + $params[] = $callback_filter;
3803 + }
3804 +
3805 + // Get total count
3806 + $count_query = "SELECT COUNT(*) FROM $table_name WHERE $where";
3807 + if (!empty($params)) {
3808 + $count_query = $wpdb->prepare($count_query, $params);
3809 + }
3810 + $total_actions = $wpdb->get_var($count_query);
3811 +
3812 + // Get actions
3813 + $query = "SELECT * FROM $table_name WHERE $where ORDER BY id $sort_order LIMIT %d OFFSET %d";
3814 + $all_params = array_merge($params, array($per_page, $offset));
3815 + $actions = $wpdb->get_results($wpdb->prepare($query, $all_params));
3816 +
3817 + // Get available callbacks for labels/icons
3818 + $available_callbacks = $this->mxchat_get_available_callbacks();
3819 +
3820 + // Format actions for response
3821 + $formatted_actions = array();
3822 + foreach ($actions as $action) {
3823 + $callback_data = isset($available_callbacks[$action->callback_function])
3824 + ? $available_callbacks[$action->callback_function]
3825 + : array('label' => $action->callback_function, 'icon' => 'admin-generic');
3826 +
3827 + $enabled_bots = json_decode($action->enabled_bots, true);
3828 + if (!is_array($enabled_bots)) {
3829 + $enabled_bots = array('default');
3830 + }
3831 +
3832 + $formatted_actions[] = array(
3833 + 'id' => intval($action->id),
3834 + 'label' => $action->intent_label,
3835 + 'phrases' => $action->phrases,
3836 + 'callback_function' => $action->callback_function,
3837 + 'callback_label' => $callback_data['label'],
3838 + 'icon' => isset($callback_data['icon']) ? $callback_data['icon'] : 'admin-generic',
3839 + 'threshold' => round($action->similarity_threshold * 100),
3840 + 'enabled' => (bool) $action->enabled,
3841 + 'enabled_bots' => $enabled_bots,
3842 + );
3843 + }
3844 +
3845 + $total_pages = ceil($total_actions / $per_page);
3846 + $showing_start = $total_actions > 0 ? $offset + 1 : 0;
3847 + $showing_end = min($offset + $per_page, $total_actions);
3848 +
3849 + wp_send_json_success(array(
3850 + 'actions' => $formatted_actions,
3851 + 'page' => $page,
3852 + 'per_page' => $per_page,
3853 + 'total_actions' => intval($total_actions),
3854 + 'total_pages' => $total_pages,
3855 + 'showing_start' => $showing_start,
3856 + 'showing_end' => $showing_end,
3857 + ));
3163 3858 }
3164 3859
3165 -public function mxchat_handle_add_intent() {
3166 - if ( ! current_user_can( 'manage_options' ) ) {
3167 - wp_die( esc_html__('Unauthorized user', 'mxchat') );
3860 +/**
3861 + * AJAX handler to toggle action enabled status
3862 + */
3863 +public function mxchat_toggle_action_status() {
3864 + check_ajax_referer('mxchat_actions_nonce', 'security');
3865 +
3866 + if (!current_user_can('manage_options')) {
3867 + wp_send_json_error(__('Unauthorized', 'mxchat'));
3168 3868 }
3169 3869
3170 - check_admin_referer('mxchat_add_intent_nonce');
3870 + $action_id = isset($_POST['action_id']) ? intval($_POST['action_id']) : 0;
3871 + $enabled = isset($_POST['enabled']) ? intval($_POST['enabled']) : 0;
3171 3872
3873 + if (!$action_id) {
3874 + wp_send_json_error(__('Invalid action ID', 'mxchat'));
3875 + }
3876 +
3172 3877 global $wpdb;
3173 3878 $table_name = $wpdb->prefix . 'mxchat_intents';
3174 3879
3880 + $result = $wpdb->update(
3881 + $table_name,
3882 + array('enabled' => $enabled ? 1 : 0),
3883 + array('id' => $action_id),
3884 + array('%d'),
3885 + array('%d')
3886 + );
3887 +
3888 + if ($result === false) {
3889 + wp_send_json_error(__('Failed to update action status', 'mxchat'));
3890 + }
3891 +
3892 + wp_send_json_success(array('enabled' => (bool) $enabled));
3893 +}
3894 +
3895 +/**
3896 + * AJAX handler to bulk delete actions
3897 + */
3898 +public function mxchat_bulk_delete_actions() {
3899 + check_ajax_referer('mxchat_delete_intent_nonce', 'security');
3900 +
3901 + if (!current_user_can('manage_options')) {
3902 + wp_send_json_error(__('Unauthorized', 'mxchat'));
3903 + }
3904 +
3905 + $action_ids = isset($_POST['action_ids']) ? array_map('intval', (array) $_POST['action_ids']) : array();
3906 +
3907 + if (empty($action_ids)) {
3908 + wp_send_json_error(__('No actions selected', 'mxchat'));
3909 + }
3910 +
3911 + global $wpdb;
3912 + $table_name = $wpdb->prefix . 'mxchat_intents';
3913 +
3914 + $placeholders = implode(',', array_fill(0, count($action_ids), '%d'));
3915 + $query = $wpdb->prepare("DELETE FROM $table_name WHERE id IN ($placeholders)", $action_ids);
3916 + $result = $wpdb->query($query);
3917 +
3918 + if ($result === false) {
3919 + wp_send_json_error(__('Failed to delete actions', 'mxchat'));
3920 + }
3921 +
3922 + wp_send_json_success(array('deleted' => $result));
3923 +}
3924 +
3925 +/**
3926 + * AJAX handler to add a new intent/action
3927 + */
3928 +public function mxchat_add_intent_ajax() {
3929 + check_ajax_referer('mxchat_add_intent_nonce', 'security');
3930 +
3931 + if (!current_user_can('manage_options')) {
3932 + wp_send_json_error(__('Unauthorized', 'mxchat'));
3933 + }
3934 +
3935 + global $wpdb;
3936 + $table_name = $wpdb->prefix . 'mxchat_intents';
3937 +
3938 + // Sanitize input
3175 3939 $intent_label = isset($_POST['intent_label']) ? sanitize_text_field($_POST['intent_label']) : '';
3176 3940 $phrases_input = isset($_POST['phrases']) ? sanitize_textarea_field($_POST['phrases']) : '';
3177 3941 $callback_function = isset($_POST['callback_function']) ? sanitize_text_field($_POST['callback_function']) : '';
3178 - $default_threshold = 0.85;
3942 + $similarity_threshold = isset($_POST['similarity_threshold']) ? floatval($_POST['similarity_threshold']) / 100 : 0.85;
3943 + $enabled_bots = isset($_POST['enabled_bots']) ? array_map('sanitize_text_field', (array) $_POST['enabled_bots']) : array('default');
3179 3944
3180 - if (empty($intent_label) || empty($callback_function) || empty($phrases_input)) {
3181 - wp_die( esc_html__('Invalid input. Please ensure all fields are filled out.', 'mxchat') );
3945 + // Validate
3946 + if (empty($intent_label) || empty($phrases_input) || empty($callback_function)) {
3947 + wp_send_json_error(__('Please fill in all required fields.', 'mxchat'));
3182 3948 }
3183 3949
3184 - $available_callbacks = $this->mxchat_get_available_callbacks();
3950 + // Process phrases
3951 + $phrases_array = array_filter(array_map('trim', preg_split('/[\n,]+/', $phrases_input)));
3952 + if (empty($phrases_array)) {
3953 + wp_send_json_error(__('Please provide at least one trigger phrase.', 'mxchat'));
3954 + }
3185 3955
3186 - if (!array_key_exists($callback_function, $available_callbacks)) {
3187 - wp_die( esc_html__('Invalid callback function selected.', 'mxchat') );
3956 + // Generate embedding (combine phrases into single string for embedding)
3957 + $embedding_vector = $this->mxchat_generate_embedding(implode(' ', $phrases_array));
3958 + if (is_wp_error($embedding_vector)) {
3959 + // Fallback: store without embedding
3960 + $serialized_vector = null;
3961 + } else {
3962 + $serialized_vector = maybe_serialize($embedding_vector);
3188 3963 }
3189 3964
3190 - $is_pro_only = $available_callbacks[$callback_function]['pro_only'];
3191 - if ($is_pro_only && !$this->is_activated) {
3192 - wp_die( esc_html__('This callback function is available in the Pro version only.', 'mxchat') );
3965 + // Ensure default bot is included
3966 + if (!in_array('default', $enabled_bots)) {
3967 + $enabled_bots[] = 'default';
3193 3968 }
3969 + $enabled_bots_json = json_encode($enabled_bots);
3194 3970
3195 - $phrases_array = array_map('sanitize_text_field', array_filter(array_map('trim', explode(',', $phrases_input))));
3971 + // Insert
3972 + $result = $wpdb->insert(
3973 + $table_name,
3974 + array(
3975 + 'intent_label' => $intent_label,
3976 + 'phrases' => implode(', ', $phrases_array),
3977 + 'embedding_vector' => $serialized_vector,
3978 + 'similarity_threshold' => $similarity_threshold,
3979 + 'callback_function' => $callback_function,
3980 + 'enabled' => 1,
3981 + 'enabled_bots' => $enabled_bots_json,
3982 + )
3983 + );
3196 3984
3985 + if ($result === false) {
3986 + wp_send_json_error(__('Failed to add action to database.', 'mxchat'));
3987 + }
3988 +
3989 + wp_send_json_success(array('id' => $wpdb->insert_id));
3990 +}
3991 +
3992 +/**
3993 + * AJAX handler to edit an existing intent/action
3994 + */
3995 +public function mxchat_edit_intent_ajax() {
3996 + check_ajax_referer('mxchat_edit_intent', 'security');
3997 +
3998 + if (!current_user_can('manage_options')) {
3999 + wp_send_json_error(__('Unauthorized', 'mxchat'));
4000 + }
4001 +
4002 + global $wpdb;
4003 + $table_name = $wpdb->prefix . 'mxchat_intents';
4004 +
4005 + // Sanitize input
4006 + $intent_id = isset($_POST['intent_id']) ? intval($_POST['intent_id']) : 0;
4007 + $intent_label = isset($_POST['intent_label']) ? sanitize_text_field($_POST['intent_label']) : '';
4008 + $phrases_input = isset($_POST['phrases']) ? sanitize_textarea_field($_POST['phrases']) : '';
4009 + $callback_function = isset($_POST['callback_function']) ? sanitize_text_field($_POST['callback_function']) : '';
4010 + $similarity_threshold = isset($_POST['similarity_threshold']) ? floatval($_POST['similarity_threshold']) / 100 : 0.85;
4011 + $enabled_bots = isset($_POST['enabled_bots']) ? array_map('sanitize_text_field', (array) $_POST['enabled_bots']) : array('default');
4012 +
4013 + // Validate
4014 + if (!$intent_id || empty($intent_label) || empty($phrases_input)) {
4015 + wp_send_json_error(__('Please fill in all required fields.', 'mxchat'));
4016 + }
4017 +
4018 + // Process phrases
4019 + $phrases_array = array_filter(array_map('trim', preg_split('/[\n,]+/', $phrases_input)));
3197 4020 if (empty($phrases_array)) {
3198 - wp_die( esc_html__('Please enter at least one valid phrase.', 'mxchat') );
4021 + wp_send_json_error(__('Please provide at least one trigger phrase.', 'mxchat'));
3199 4022 }
3200 4023
3201 - $vectors = [];
3202 - foreach ($phrases_array as $phrase) {
3203 - $embedding_vector = $this->mxchat_generate_embedding($phrase, $this->options['api_key']);
3204 - if (is_array($embedding_vector)) {
3205 - $vectors[] = $embedding_vector;
3206 - } else {
3207 - wp_die( esc_html__('Error generating embedding for phrase: ', 'mxchat') . esc_html($phrase) );
3208 - }
4024 + // Generate new embedding (combine phrases into single string for embedding)
4025 + $embedding_vector = $this->mxchat_generate_embedding(implode(' ', $phrases_array));
4026 + if (is_wp_error($embedding_vector)) {
4027 + // Keep existing embedding
4028 + $serialized_vector = null;
4029 + $update_data = array(
4030 + 'intent_label' => $intent_label,
4031 + 'phrases' => implode(', ', $phrases_array),
4032 + 'similarity_threshold' => $similarity_threshold,
4033 + 'callback_function' => $callback_function,
4034 + 'enabled_bots' => json_encode($enabled_bots),
4035 + );
4036 + } else {
4037 + $serialized_vector = maybe_serialize($embedding_vector);
4038 + $update_data = array(
4039 + 'intent_label' => $intent_label,
4040 + 'phrases' => implode(', ', $phrases_array),
4041 + 'embedding_vector' => $serialized_vector,
4042 + 'similarity_threshold' => $similarity_threshold,
4043 + 'callback_function' => $callback_function,
4044 + 'enabled_bots' => json_encode($enabled_bots),
4045 + );
3209 4046 }
3210 4047
3211 - if (!empty($vectors)) {
3212 - $combined_vector = $this->mxchat_average_vectors($vectors);
3213 - $serialized_vector = maybe_serialize($combined_vector);
4048 + // Ensure default bot is included
4049 + if (!in_array('default', $enabled_bots)) {
4050 + $enabled_bots[] = 'default';
4051 + }
4052 + $update_data['enabled_bots'] = json_encode($enabled_bots);
3214 4053
3215 - $result = $wpdb->insert($table_name, [
3216 - 'intent_label' => $intent_label,
3217 - 'phrases' => implode(', ', $phrases_array),
3218 - 'embedding_vector' => $serialized_vector,
3219 - 'callback_function' => $callback_function,
3220 - 'similarity_threshold' => $default_threshold,
3221 - ]);
4054 + $result = $wpdb->update(
4055 + $table_name,
4056 + $update_data,
4057 + array('id' => $intent_id),
4058 + null,
4059 + array('%d')
4060 + );
3222 4061
3223 - if ($result === false) {
3224 - wp_die( esc_html__('Database error: ', 'mxchat') . esc_html($wpdb->last_error) );
3225 - }
3226 - } else {
3227 - wp_die( esc_html__('No valid embeddings generated. Please check your phrases.', 'mxchat') );
4062 + if ($result === false) {
4063 + wp_send_json_error(__('Failed to update action.', 'mxchat'));
3228 4064 }
3229 4065
3230 - wp_safe_redirect(admin_url('admin.php?page=mxchat-intents'));
3231 - exit;
4066 + wp_send_json_success(array('updated' => true));
3232 4067 }
3233 4068
4069 +/**
4070 + * AJAX handler to delete a single intent/action
4071 + */
4072 +public function mxchat_delete_intent_ajax() {
4073 + check_ajax_referer('mxchat_delete_intent_nonce', 'security');
3234 4074
4075 + if (!current_user_can('manage_options')) {
4076 + wp_send_json_error(__('Unauthorized', 'mxchat'));
4077 + }
3235 4078
4079 + $intent_id = isset($_POST['intent_id']) ? intval($_POST['intent_id']) : 0;
3236 4080
4081 + if (!$intent_id) {
4082 + wp_send_json_error(__('Invalid action ID', 'mxchat'));
4083 + }
3237 4084
4085 + global $wpdb;
4086 + $table_name = $wpdb->prefix . 'mxchat_intents';
3238 4087
3239 -private function mxchat_get_available_callbacks($grouped = false) {
3240 - $callbacks = [
3241 - 'mxchat_handle_email_capture' => [
3242 - 'label' => __('Email Capture', 'mxchat'),
3243 - 'pro_only' => false,
3244 - 'group' => __('Customer Engagement', 'mxchat'),
3245 - ],
3246 - 'mxchat_generate_image' => [
3247 - 'label' => __('Generate Image', 'mxchat'),
3248 - 'pro_only' => true,
3249 - 'group' => __('Other Features', 'mxchat'),
3250 - ],
3251 - 'mxchat_handle_search_request' => [
3252 - 'label' => __('Brave Web Search', 'mxchat'),
3253 - 'pro_only' => false,
3254 - 'group' => __('Search Features', 'mxchat'),
3255 - ],
3256 - 'mxchat_handle_image_search_request' => [
3257 - 'label' => __('Brave Image Search', 'mxchat'),
3258 - 'pro_only' => false,
3259 - 'group' => __('Search Features', 'mxchat'),
3260 - ],
3261 - 'mxchat_handle_pdf_discussion' => [
3262 - 'label' => __('Chat with PDF', 'mxchat'),
3263 - 'pro_only' => true,
3264 - 'group' => __('Other Features', 'mxchat'),
3265 - ],
3266 - 'mxchat_live_agent_handover' => [
3267 - 'label' => __('Live Agent', 'mxchat'),
3268 - 'pro_only' => true,
3269 - 'group' => __('Customer Engagement', 'mxchat'),
3270 - ],
3271 - 'mxchat_handle_switch_to_chatbot_intent' => [
3272 - 'label' => __('Back to Chatbot', 'mxchat'),
3273 - 'pro_only' => true,
3274 - 'group' => __('Customer Engagement', 'mxchat'),
3275 - ],
3276 - ];
4088 + $result = $wpdb->delete($table_name, array('id' => $intent_id), array('%d'));
3277 4089
3278 - // Allow other plugins to add their callbacks
3279 - $callbacks = apply_filters('mxchat_available_callbacks', $callbacks);
4090 + if ($result === false) {
4091 + wp_send_json_error(__('Failed to delete action', 'mxchat'));
4092 + }
3280 4093
3281 - // Return grouped structure if requested
3282 - if ($grouped) {
3283 - $grouped_callbacks = [];
3284 - foreach ($callbacks as $key => $data) {
3285 - $group_label = $data['group'] ?? __('Other Features', 'mxchat');
3286 - $grouped_callbacks[$group_label][$key] = [
3287 - 'label' => $data['label'],
3288 - 'pro_only' => $data['pro_only'],
3289 - ];
4094 + wp_send_json_success(array('deleted' => true));
4095 +}
4096 +
4097 +
4098 +/**
4099 + * Enhanced get_available_callbacks function with form action exclusion
4100 + *
4101 + * @param bool $grouped Whether to return callbacks grouped by category
4102 + * @param bool $include_all Whether to include all potential actions (even if add-on not installed)
4103 + * @return array Callbacks data with icons, descriptions and availability status
4104 + */
4105 +private function mxchat_get_available_callbacks($grouped = false, $include_all = true) {
4106 + // Load WordPress plugin functions if needed
4107 + if (!function_exists('get_plugins')) {
4108 + require_once ABSPATH . 'wp-admin/includes/plugin.php';
4109 + }
4110 +
4111 + // Get active plugins
4112 + $active_plugins = get_option('active_plugins', array());
4113 +
4114 + // Functions to exclude from the action selector only if Pro is activated
4115 + // If user doesn't have Pro, show these so they can see what they're missing
4116 + $excluded_when_pro_active_functions = array(
4117 + 'mxchat_handle_form_collection' // Forms add-on action
4118 + );
4119 +
4120 + // Always excluded functions (regardless of Pro status)
4121 + $always_excluded_functions = array();
4122 +
4123 + // Combine exclusion lists based on Pro activation status
4124 + $excluded_functions = $always_excluded_functions;
4125 + if ($this->is_activated) {
4126 + // Only exclude add-on managed functions if Pro is active
4127 + $excluded_functions = array_merge($excluded_functions, $excluded_when_pro_active_functions);
4128 + }
4129 +
4130 + // Define add-on plugin files and their corresponding action functions
4131 + $addon_plugins = array(
4132 + 'mxchat-woo/mxchat-woo.php' => array(
4133 + 'functions' => array(
4134 + 'mxchat_handle_product_recommendations',
4135 + 'mxchat_handle_order_history',
4136 + 'mxchat_show_product_card',
4137 + 'mxchat_add_to_cart',
4138 + 'mxchat_checkout_redirect'
4139 + ),
4140 + 'name' => __('WooCommerce Add-on', 'mxchat'),
4141 + 'pro_required' => true
4142 + ),
4143 + 'mxchat-perplexity/mxchat-perplexity.php' => array(
4144 + 'functions' => array('mxchat_perplexity_research'),
4145 + 'name' => __('Perplexity Add-on', 'mxchat'),
4146 + 'pro_required' => true
4147 + ),
4148 + 'mxchat-forms/mxchat-forms.php' => array(
4149 + 'functions' => array('mxchat_handle_form_collection'),
4150 + 'name' => __('Forms Add-on', 'mxchat'),
4151 + 'pro_required' => true
4152 + ),
4153 + // Add other add-ons and their functions here
4154 + );
4155 +
4156 + // Get the functions that are provided by active add-ons
4157 + $addon_provided_functions = array();
4158 + $addon_function_mapping = array(); // Maps functions to their add-on info
4159 +
4160 + // Check which add-ons are active
4161 + foreach ($addon_plugins as $plugin_file => $addon_info) {
4162 + $is_active = in_array($plugin_file, $active_plugins);
4163 +
4164 + // For each function in this addon
4165 + foreach ($addon_info['functions'] as $function) {
4166 + // Consider a function installed only if:
4167 + // 1. The add-on is active AND
4168 + // 2. Either it doesn't require Pro OR Pro is activated
4169 + $is_installed = $is_active && (!$addon_info['pro_required'] || $this->is_activated);
4170 +
4171 + // If the add-on is installed, mark this function as provided by an add-on
4172 + if ($is_installed) {
4173 + $addon_provided_functions[] = $function;
4174 + }
4175 +
4176 + // Store addon info for this function regardless of installation status
4177 + $addon_function_mapping[$function] = array(
4178 + 'addon' => basename(dirname($plugin_file)),
4179 + 'addon_name' => $addon_info['name'],
4180 + 'pro_required' => $addon_info['pro_required'],
4181 + 'is_active' => $is_active,
4182 + 'is_installed' => $is_installed
4183 + );
3290 4184 }
3291 - return $grouped_callbacks;
3292 4185 }
3293 - return $callbacks;
3294 -}
3295 4186
4187 + // Core callbacks - always available in the base plugin
4188 + $core_callbacks = array(
4189 + 'mxchat_handle_email_capture' => array(
4190 + 'label' => __('Loops Email Capture', 'mxchat'),
4191 + 'pro_only' => false,
4192 + 'group' => __('Customer Engagement', 'mxchat'),
4193 + 'icon' => 'email-alt',
4194 + 'description' => __('Collect visitor emails for your mailing list in Loops', 'mxchat'),
4195 + 'addon' => false, // Not from an add-on
4196 + 'installed' => true // Always installed with base plugin
4197 + ),
4198 + 'mxchat_handle_search_request' => array(
4199 + 'label' => __('Brave Web Search', 'mxchat'),
4200 + 'pro_only' => false,
4201 + 'group' => __('Search Features', 'mxchat'),
4202 + 'icon' => 'search',
4203 + 'description' => __('Let users search the web directly from the chat', 'mxchat'),
4204 + 'addon' => false,
4205 + 'installed' => true
4206 + ),
4207 + 'mxchat_handle_image_search_request' => array(
4208 + 'label' => __('Brave Image Search', 'mxchat'),
4209 + 'pro_only' => false,
4210 + 'group' => __('Search Features', 'mxchat'),
4211 + 'icon' => 'format-image',
4212 + 'description' => __('Search and display images in the chat conversation', 'mxchat'),
4213 + 'addon' => false,
4214 + 'installed' => true
4215 + ),
4216 + // Pro core features - check is_activated property
4217 + 'mxchat_generate_image' => array(
4218 + 'label' => __('Generate Image', 'mxchat'),
4219 + 'pro_only' => false,
4220 + 'group' => __('Other Features', 'mxchat'),
4221 + 'icon' => 'art',
4222 + 'description' => __('Create images with DALL-E 3 from OpenAI (requires OpenAI API key)', 'mxchat'),
4223 + 'addon' => false,
4224 + 'installed' => true
4225 + ),
4226 + 'mxchat_handle_pdf_discussion' => array(
4227 + 'label' => __('Chat with PDF', 'mxchat'),
4228 + 'pro_only' => false,
4229 + 'group' => __('Other Features', 'mxchat'),
4230 + 'icon' => 'media-document',
4231 + 'description' => __('Answer questions about uploaded PDF documents', 'mxchat'),
4232 + 'addon' => false,
4233 + 'installed' => true
4234 + ),
4235 + 'mxchat_live_agent_handover' => array(
4236 + 'label' => __('Slack Live Agent', 'mxchat'),
4237 + 'pro_only' => false,
4238 + 'group' => __('Customer Engagement', 'mxchat'),
4239 + 'icon' => 'admin-users',
4240 + 'description' => __('Transfer conversation to a human support agent on Slack', 'mxchat'),
4241 + 'addon' => false,
4242 + 'installed' => true
4243 + ),
4244 + 'mxchat_telegram_live_agent_handover' => array(
4245 + 'label' => __('Telegram Live Agent', 'mxchat'),
4246 + 'pro_only' => false,
4247 + 'group' => __('Customer Engagement', 'mxchat'),
4248 + 'icon' => 'format-chat',
4249 + 'description' => __('Transfer conversation to a human support agent on Telegram', 'mxchat'),
4250 + 'addon' => false,
4251 + 'installed' => true
4252 + ),
4253 + 'mxchat_handle_switch_to_chatbot_intent' => array(
4254 + 'label' => __('Back to Chatbot', 'mxchat'),
4255 + 'pro_only' => false,
4256 + 'group' => __('Customer Engagement', 'mxchat'),
4257 + 'icon' => 'backup',
4258 + 'description' => __('Return from live agent mode to AI chatbot', 'mxchat'),
4259 + 'addon' => false,
4260 + 'installed' => true
4261 + ),
4262 + );
3296 4263
3297 -private function mxchat_average_vectors($vectors) {
3298 - $vector_length = count($vectors[0]);
3299 - $sum_vector = array_fill(0, $vector_length, 0);
4264 + // Add-on callbacks with placeholders - only include if the add-on is NOT active
4265 + $addon_callbacks = array(
4266 + // WooCommerce Add-on
4267 + 'mxchat_handle_product_recommendations' => array(
4268 + 'label' => __('Product Recommendations', 'mxchat'),
4269 + 'pro_only' => true,
4270 + 'group' => __('WooCommerce Features', 'mxchat'),
4271 + 'icon' => 'cart',
4272 + 'description' => __('Suggest products based on customer preferences', 'mxchat'),
4273 + ),
4274 + 'mxchat_handle_order_history' => array(
4275 + 'label' => __('Order History', 'mxchat'),
4276 + 'pro_only' => true,
4277 + 'group' => __('WooCommerce Features', 'mxchat'),
4278 + 'icon' => 'clipboard',
4279 + 'description' => __('Allow customers to check their order status', 'mxchat'),
4280 + ),
4281 + 'mxchat_show_product_card' => array(
4282 + 'label' => __('Show Product Card', 'mxchat'),
4283 + 'pro_only' => true,
4284 + 'group' => __('WooCommerce Features', 'mxchat'),
4285 + 'icon' => 'products',
4286 + 'description' => __('Display product information in the chat', 'mxchat'),
4287 + ),
4288 + 'mxchat_add_to_cart' => array(
4289 + 'label' => __('Add to Cart', 'mxchat'),
4290 + 'pro_only' => true,
4291 + 'group' => __('WooCommerce Features', 'mxchat'),
4292 + 'icon' => 'plus-alt',
4293 + 'description' => __('Add products to cart directly from chat', 'mxchat'),
4294 + ),
4295 + 'mxchat_checkout_redirect' => array(
4296 + 'label' => __('Proceed to Checkout', 'mxchat'),
4297 + 'pro_only' => true,
4298 + 'group' => __('WooCommerce Features', 'mxchat'),
4299 + 'icon' => 'arrow-right-alt',
4300 + 'description' => __('Redirect customer to checkout page', 'mxchat'),
4301 + ),
3300 4302
3301 - foreach ($vectors as $vector) {
3302 - for ($i = 0; $i < $vector_length; $i++) {
3303 - $sum_vector[$i] += $vector[$i];
4303 + // Perplexity Add-on
4304 + 'mxchat_perplexity_research' => array(
4305 + 'label' => __('Perplexity Research', 'mxchat'),
4306 + 'pro_only' => true,
4307 + 'group' => __('Search Features', 'mxchat'),
4308 + 'icon' => 'book-alt',
4309 + 'description' => __('Allows the chatbot to search the web for accurate, up-to-date answers', 'mxchat'),
4310 + ),
4311 +
4312 + // Forms Add-on (only shown when Pro is not activated)
4313 + 'mxchat_handle_form_collection' => array(
4314 + 'label' => __('Form Collection', 'mxchat'),
4315 + 'pro_only' => true,
4316 + 'group' => __('Form Features', 'mxchat'),
4317 + 'icon' => 'feedback',
4318 + 'description' => __('Collect user information through custom forms in chat', 'mxchat'),
4319 + ),
4320 + );
4321 +
4322 + // Enhance add-on callbacks with installation status and addon info
4323 + foreach ($addon_callbacks as $function => $data) {
4324 + if (isset($addon_function_mapping[$function])) {
4325 + $addon_info = $addon_function_mapping[$function];
4326 +
4327 + $addon_callbacks[$function]['addon'] = $addon_info['addon'];
4328 + $addon_callbacks[$function]['addon_name'] = $addon_info['addon_name'];
4329 + $addon_callbacks[$function]['installed'] = $addon_info['is_installed'];
4330 +
4331 + // Set pro_only based on add-on configuration
4332 + $addon_callbacks[$function]['pro_only'] = $addon_info['pro_required'];
4333 + } else {
4334 + $addon_callbacks[$function]['addon'] = 'unknown';
4335 + $addon_callbacks[$function]['addon_name'] = __('Unknown Add-on', 'mxchat');
4336 + $addon_callbacks[$function]['installed'] = false;
3304 4337 }
3305 4338 }
3306 4339
3307 - // Divide each component by the number of vectors to get the average
3308 - $num_vectors = count($vectors);
3309 - for ($i = 0; $i < $vector_length; $i++) {
3310 - $sum_vector[$i] /= $num_vectors;
4340 + // Initialize callbacks with core features
4341 + $callbacks = $core_callbacks;
4342 +
4343 + // Get callbacks from active add-ons
4344 + $active_addon_callbacks = apply_filters('mxchat_available_callbacks', array());
4345 +
4346 + // Add placeholder callbacks only for add-ons that aren't active
4347 + if ($include_all) {
4348 + foreach ($addon_callbacks as $function => $data) {
4349 + // Skip placeholders for functions provided by active add-ons
4350 + if (in_array($function, $addon_provided_functions)) {
4351 + continue;
4352 + }
4353 +
4354 + // Skip excluded functions
4355 + if (in_array($function, $excluded_functions)) {
4356 + continue;
4357 + }
4358 +
4359 + // Add the placeholder
4360 + $callbacks[$function] = $data;
4361 + }
3311 4362 }
3312 4363
3313 - return $sum_vector;
3314 -}
4364 + // Add callbacks from active add-ons (will override placeholders)
4365 + foreach ($active_addon_callbacks as $function => $data) {
4366 + // Skip excluded functions
4367 + if (in_array($function, $excluded_functions)) {
4368 + continue;
4369 + }
3315 4370
3316 -public function mxchat_handle_delete_intent() {
3317 - if ( ! current_user_can( 'manage_options' ) ) {
3318 - wp_die( esc_html__('Unauthorized user', 'mxchat') );
4371 + // Always include callbacks from add-ons
4372 + $callbacks[$function] = $data;
4373 +
4374 + // Ensure they have the proper add-on info
4375 + if (isset($addon_function_mapping[$function])) {
4376 + $addon_info = $addon_function_mapping[$function];
4377 + $callbacks[$function]['addon'] = $addon_info['addon'];
4378 + $callbacks[$function]['addon_name'] = $addon_info['addon_name'];
4379 + $callbacks[$function]['installed'] = $addon_info['is_installed'];
4380 + $callbacks[$function]['pro_only'] = $addon_info['pro_required'];
4381 + }
3319 4382 }
3320 4383
3321 - check_admin_referer('mxchat_delete_intent_nonce');
4384 + // Just before returning callbacks, sort them to prioritize free features
4385 + if (!$grouped) {
4386 + // Create temporary arrays for sorting
4387 + $free_callbacks = array();
4388 + $pro_callbacks = array();
3322 4389
3323 - if (isset($_POST['intent_id'])) {
3324 - global $wpdb;
3325 - $table_name = $wpdb->prefix . 'mxchat_intents';
3326 - $intent_id = intval($_POST['intent_id']);
4390 + // Split callbacks into free and pro
4391 + foreach ($callbacks as $key => $data) {
4392 + if (isset($data['pro_only']) && $data['pro_only']) {
4393 + $pro_callbacks[$key] = $data;
4394 + } else {
4395 + $free_callbacks[$key] = $data;
4396 + }
4397 + }
3327 4398
3328 - $wpdb->delete($table_name, ['id' => $intent_id], ['%d']);
4399 + // Merge with free callbacks first
4400 + $callbacks = array_merge($free_callbacks, $pro_callbacks);
3329 4401 }
3330 4402
3331 - wp_safe_redirect(admin_url('admin.php?page=mxchat-intents'));
3332 - exit;
4403 + // Return grouped structure if requested
4404 + if ($grouped) {
4405 + $grouped_callbacks = array();
4406 + foreach ($callbacks as $key => $data) {
4407 + $group_label = isset($data['group']) ? $data['group'] : __('Other Features', 'mxchat');
4408 +
4409 + // Ensure we carry forward all the new fields in grouped mode
4410 + $callback_data = array(
4411 + 'label' => $data['label'],
4412 + 'pro_only' => isset($data['pro_only']) ? $data['pro_only'] : false,
4413 + 'icon' => isset($data['icon']) ? $data['icon'] : 'admin-generic',
4414 + 'description' => isset($data['description']) ? $data['description'] : __('Custom action for your chatbot', 'mxchat'),
4415 + 'addon' => isset($data['addon']) ? $data['addon'] : false,
4416 + 'addon_name' => isset($data['addon_name']) ? $data['addon_name'] : '',
4417 + 'installed' => isset($data['installed']) ? $data['installed'] : true
4418 + );
4419 +
4420 + $grouped_callbacks[$group_label][$key] = $callback_data;
4421 + }
4422 +
4423 + // Sort within each group to prioritize free features
4424 + foreach ($grouped_callbacks as $group => $items) {
4425 + $free_items = array();
4426 + $pro_items = array();
4427 +
4428 + foreach ($items as $key => $data) {
4429 + if (isset($data['pro_only']) && $data['pro_only']) {
4430 + $pro_items[$key] = $data;
4431 + } else {
4432 + $free_items[$key] = $data;
4433 + }
4434 + }
4435 +
4436 + $grouped_callbacks[$group] = array_merge($free_items, $pro_items);
4437 + }
4438 +
4439 + return $grouped_callbacks;
4440 + }
4441 +
4442 + return $callbacks;
3333 4443 }
3334 4444
3335 -
3336 4445 public function mxchat_page_init() {
3337 - // Register settings
3338 4446 register_setting(
3339 4447 'mxchat_option_group',
3340 4448 'mxchat_options',
3341 4449 array($this, 'mxchat_sanitize')
@@ -3346,10 +4454,10 @@
3346 4454 'mxchat_similarity_threshold',
3347 4455 array(
3348 4456 'type' => 'number',
3349 4457 'sanitize_callback' => function($value) {
3350 - $value = absint($value); // Ensure it's an integer
3351 - return min(max($value, 70), 85); // Enforce range
4458 + $value = absint($value);
4459 + return min(max($value, 20), 95);
3352 4460 },
3353 4461 'default' => 80,
3354 4462 )
3355 4463 );
@@ -3361,8 +4469,106 @@
3361 4469 null,
3362 4470 'mxchat-chatbot'
3363 4471 );
3364 4472
4473 + // API Keys Settings Section
4474 + add_settings_section(
4475 + 'mxchat_api_keys_section',
4476 + esc_html__('API Keys', 'mxchat'),
4477 + array($this, 'mxchat_api_keys_section_callback'),
4478 + 'mxchat-api-keys'
4479 + );
4480 +
4481 + // OpenAI API Key
4482 + add_settings_field(
4483 + 'api_key',
4484 + esc_html__('OpenAI API Key', 'mxchat'),
4485 + array($this, 'api_key_callback'),
4486 + 'mxchat-api-keys',
4487 + 'mxchat_api_keys_section'
4488 + );
4489 +
4490 + // X.AI API Key
4491 + add_settings_field(
4492 + 'xai_api_key',
4493 + esc_html__('X.AI API Key', 'mxchat'),
4494 + array($this, 'xai_api_key_callback'),
4495 + 'mxchat-api-keys',
4496 + 'mxchat_api_keys_section'
4497 + );
4498 +
4499 + // Claude API Key
4500 + add_settings_field(
4501 + 'claude_api_key',
4502 + esc_html__('Claude API Key', 'mxchat'),
4503 + array($this, 'claude_api_key_callback'),
4504 + 'mxchat-api-keys',
4505 + 'mxchat_api_keys_section'
4506 + );
4507 +
4508 + // DeepSeek API Key
4509 + add_settings_field(
4510 + 'deepseek_api_key',
4511 + esc_html__('DeepSeek API Key', 'mxchat'),
4512 + array($this, 'deepseek_api_key_callback'),
4513 + 'mxchat-api-keys',
4514 + 'mxchat_api_keys_section'
4515 + );
4516 +
4517 + // Google Gemini API Key
4518 + add_settings_field(
4519 + 'gemini_api_key',
4520 + esc_html__('Google Gemini API Key', 'mxchat'),
4521 + array($this, 'gemini_api_key_callback'),
4522 + 'mxchat-api-keys',
4523 + 'mxchat_api_keys_section'
4524 + );
4525 +
4526 + // Voyage AI API Key
4527 + add_settings_field(
4528 + 'voyage_api_key',
4529 + esc_html__('Voyage AI API Key', 'mxchat'),
4530 + array($this, 'voyage_api_key_callback'),
4531 + 'mxchat-api-keys',
4532 + 'mxchat_api_keys_section'
4533 + );
4534 +
4535 + // OpenRouter API Key
4536 + add_settings_field(
4537 + 'openrouter_api_key',
4538 + esc_html__('OpenRouter API Key', 'mxchat'),
4539 + array($this, 'openrouter_api_key_callback'),
4540 + 'mxchat-api-keys',
4541 + 'mxchat_api_keys_section'
4542 + );
4543 +
4544 + // Loops API Key
4545 + add_settings_field(
4546 + 'loops_api_key',
4547 + esc_html__('Loops API Key', 'mxchat'),
4548 + array($this, 'mxchat_loops_api_key_callback'),
4549 + 'mxchat-api-keys',
4550 + 'mxchat_api_keys_section'
4551 + );
4552 +
4553 + // Brave Search API Key
4554 + add_settings_field(
4555 + 'brave_api_key',
4556 + __('Brave API Key', 'mxchat'),
4557 + array($this, 'mxchat_brave_api_key_callback'),
4558 + 'mxchat-api-keys',
4559 + 'mxchat_api_keys_section'
4560 + );
4561 +
4562 + // Frontend Debugger Toggle
4563 + add_settings_field(
4564 + 'show_frontend_debugger',
4565 + esc_html__('Frontend Debugger', 'mxchat'),
4566 + array($this, 'mxchat_frontend_debugger_callback'),
4567 + 'mxchat-chatbot',
4568 + 'mxchat_chatbot_section'
4569 + );
4570 +
3365 4571 // Similarity Threshold Slider
3366 4572 add_settings_field(
3367 4573 'similarity_threshold', // Field ID
3368 4574 esc_html__('Similarity Threshold', 'mxchat'), // Field title
@@ -3370,9 +4576,18 @@
3370 4576 'mxchat-chatbot', // Page
3371 4577 'mxchat_chatbot_section' // Section
3372 4578 );
3373 4579
4580 + // RAG Sources Limit Slider
3374 4581 add_settings_field(
4582 + 'rag_sources_limit', // Field ID
4583 + esc_html__('RAG Sources Limit', 'mxchat'), // Field title
4584 + array($this, 'mxchat_rag_sources_limit_callback'), // Callback function
4585 + 'mxchat-chatbot', // Page
4586 + 'mxchat_chatbot_section' // Section
4587 + );
4588 +
4589 + add_settings_field(
3375 4590 'append_to_body',
3376 4591 esc_html__('Auto-Display Chatbot', 'mxchat'),
3377 4592 array($this, 'mxchat_append_to_body_callback'),
3378 4593 'mxchat-chatbot',
@@ -3378,51 +4593,37 @@
3378 4593 'mxchat-chatbot',
3379 4594 'mxchat_chatbot_section'
3380 4595 );
3381 4596
3382 -
3383 - // Existing fields...
3384 4597 add_settings_field(
3385 - 'api_key',
3386 - esc_html__('OpenAI API Key', 'mxchat'),
3387 - array($this, 'api_key_callback'),
4598 + 'contextual_awareness_toggle',
4599 + esc_html__('Contextual Awareness', 'mxchat'),
4600 + array($this, 'mxchat_contextual_awareness_callback'),
3388 4601 'mxchat-chatbot',
3389 4602 'mxchat_chatbot_section'
3390 4603 );
3391 4604
3392 4605 add_settings_field(
3393 - 'xai_api_key',
3394 - esc_html__('X.AI API Key', 'mxchat'),
3395 - array($this, 'xai_api_key_callback'),
4606 + 'citation_links_toggle',
4607 + esc_html__('Citation Links', 'mxchat'),
4608 + array($this, 'mxchat_citation_links_toggle_callback'),
3396 4609 'mxchat-chatbot',
3397 4610 'mxchat_chatbot_section'
3398 4611 );
3399 4612
3400 4613 add_settings_field(
3401 - 'claude_api_key',
3402 - esc_html__('Claude API Key', 'mxchat'),
3403 - array($this, 'claude_api_key_callback'),
4614 + 'enable_streaming_toggle',
4615 + esc_html__('Enable Streaming', 'mxchat'),
4616 + array($this, 'enable_streaming_toggle_callback'),
3404 4617 'mxchat-chatbot',
3405 - 'mxchat_chatbot_section'
4618 + 'mxchat_chatbot_section', // Same section as your working toggle
4619 + array(
4620 + 'class' => 'mxchat-setting-row streaming-setting',
4621 + 'style' => 'display: none;' // Hidden by default, shown when OpenAI/Claude selected
4622 + )
3406 4623 );
3407 4624
3408 - add_settings_field(
3409 - 'deepseek_api_key',
3410 - esc_html__('DeepSeek API Key', 'mxchat'),
3411 - array($this, 'deepseek_api_key_callback'),
3412 - 'mxchat-chatbot',
3413 - 'mxchat_chatbot_section'
3414 - );
3415 4625
3416 - add_settings_field(
3417 - 'voyage_api_key',
3418 - esc_html__('Voyage AI API Key', 'mxchat'),
3419 - array($this, 'voyage_api_key_callback'),
3420 - 'mxchat-chatbot',
3421 - 'mxchat_chatbot_section'
3422 - );
3423 -
3424 -
3425 4626 add_settings_field(
3426 4627 'model',
3427 4628 esc_html__('Chat Model', 'mxchat'),
3428 4629 array($this, 'mxchat_model_callback'),
@@ -3429,9 +4630,8 @@
3429 4630 'mxchat-chatbot',
3430 4631 'mxchat_chatbot_section'
3431 4632 );
3432 4633
3433 - // Add the settings field
3434 4634 add_settings_field(
3435 4635 'embedding_model',
3436 4636 esc_html__('Embedding Model', 'mxchat'),
3437 4637 array($this, 'embedding_model_callback'),
@@ -3456,8 +4656,16 @@
3456 4656 'mxchat_chatbot_section'
3457 4657 );
3458 4658
3459 4659 add_settings_field(
4660 + 'ai_agent_text',
4661 + esc_html__('AI Agent Text', 'mxchat'),
4662 + array($this, 'mxchat_ai_agent_text_callback'),
4663 + 'mxchat-chatbot',
4664 + 'mxchat_chatbot_section'
4665 + );
4666 +
4667 + add_settings_field(
3460 4668 'enable_email_block',
3461 4669 esc_html__('Require Email To Chat', 'mxchat'),
3462 4670 array($this, 'enable_email_block_callback'),
3463 4671 'mxchat-chatbot',
@@ -3480,48 +4688,40 @@
3480 4688 'mxchat_chatbot_section'
3481 4689 );
3482 4690
3483 4691 add_settings_field(
3484 - 'intro_message',
3485 - esc_html__('Introductory Message', 'mxchat'),
3486 - array($this, 'mxchat_intro_message_callback'),
4692 + 'enable_name_field',
4693 + esc_html__('Require Name Field', 'mxchat'),
4694 + array($this, 'enable_name_field_callback'),
3487 4695 'mxchat-chatbot',
3488 4696 'mxchat_chatbot_section'
3489 4697 );
3490 4698
3491 4699 add_settings_field(
3492 - 'input_copy',
3493 - esc_html__('Input Copy', 'mxchat'),
3494 - array($this, 'mxchat_input_copy_callback'),
4700 + 'name_field_placeholder',
4701 + esc_html__('Name Field Placeholder', 'mxchat'),
4702 + array($this, 'name_field_placeholder_callback'),
3495 4703 'mxchat-chatbot',
3496 4704 'mxchat_chatbot_section'
3497 4705 );
3498 4706
3499 4707 add_settings_field(
3500 - 'rate_limit_logged_out',
3501 - __('Rate Limit for Logged-out Users', 'mxchat'),
3502 - array($this, 'mxchat_rate_limit_logged_out_callback'),
4708 + 'intro_message',
4709 + esc_html__('Introductory Message', 'mxchat'),
4710 + array($this, 'mxchat_intro_message_callback'),
3503 4711 'mxchat-chatbot',
3504 4712 'mxchat_chatbot_section'
3505 4713 );
3506 4714
3507 4715 add_settings_field(
3508 - 'rate_limit_roles',
3509 - __('Rate Limits by User Role', 'mxchat'),
3510 - array($this, 'mxchat_rate_limit_roles_callback'),
4716 + 'input_copy',
4717 + esc_html__('Input Copy', 'mxchat'),
4718 + array($this, 'mxchat_input_copy_callback'),
3511 4719 'mxchat-chatbot',
3512 4720 'mxchat_chatbot_section'
3513 4721 );
3514 4722
3515 4723 add_settings_field(
3516 - 'rate_limit_message',
3517 - esc_html__('Rate Limit Message', 'mxchat'),
3518 - array($this, 'mxchat_rate_limit_message_callback'),
3519 - 'mxchat-chatbot',
3520 - 'mxchat_chatbot_section'
3521 - );
3522 -
3523 - add_settings_field(
3524 4724 'pre_chat_message',
3525 4725 esc_html__('Chat Teaser Pop-up', 'mxchat'),
3526 4726 array($this, 'mxchat_pre_chat_message_callback'),
3527 4727 'mxchat-chatbot',
@@ -3592,8 +4792,16 @@
3592 4792 'mxchat_chatbot_section'
3593 4793 );
3594 4794
3595 4795
4796 + add_settings_field(
4797 + 'rate_limits',
4798 + __('Rate Limits Settings', 'mxchat'),
4799 + array($this, 'mxchat_rate_limits_callback'),
4800 + 'mxchat-chatbot',
4801 + 'mxchat_chatbot_section'
4802 + );
4803 +
3596 4804 // Loops Settings Section
3597 4805 add_settings_section(
3598 4806 'mxchat_loops_section',
3599 4807 esc_html__('Loops Settings', 'mxchat'),
@@ -3600,18 +4808,10 @@
3600 4808 null,
3601 4809 'mxchat-embed'
3602 4810 );
3603 4811
3604 - // Loops Settings Fields
4812 + // Loops Settings Fields (API Key moved to API Keys tab)
3605 4813 add_settings_field(
3606 - 'loops_api_key',
3607 - esc_html__('Loops API Key', 'mxchat'),
3608 - array($this, 'mxchat_loops_api_key_callback'),
3609 - 'mxchat-embed',
3610 - 'mxchat_loops_section'
3611 - );
3612 -
3613 - add_settings_field(
3614 4814 'loops_mailing_list',
3615 4815 esc_html__('Loops Mailing List', 'mxchat'),
3616 4816 array($this, 'mxchat_loops_mailing_list_callback'),
3617 4817 'mxchat-embed',
@@ -3641,17 +4841,10 @@
3641 4841 array($this, 'mxchat_brave_section_callback'),
3642 4842 'mxchat-embed'
3643 4843 );
3644 4844
4845 + // Brave API Key moved to API Keys tab
3645 4846 add_settings_field(
3646 - 'brave_api_key',
3647 - __('Brave API Key', 'mxchat'),
3648 - array($this, 'mxchat_brave_api_key_callback'),
3649 - 'mxchat-embed',
3650 - 'mxchat_brave_section'
3651 - );
3652 -
3653 - add_settings_field(
3654 4847 'brave_image_count',
3655 4848 __('Number of Images to Return', 'mxchat'),
3656 4849 array($this, 'mxchat_brave_image_count_callback'),
3657 4850 'mxchat-embed',
@@ -3705,9 +4898,27 @@
3705 4898 'mxchat-embed',
3706 4899 'mxchat_pdf_intent_section'
3707 4900 );
3708 4901
4902 + // PDF Upload Button Toggle
3709 4903 add_settings_field(
4904 + 'show_pdf_upload_button',
4905 + __('Show PDF Upload Button', 'mxchat'),
4906 + array($this, 'mxchat_show_pdf_upload_button_callback'),
4907 + 'mxchat-embed',
4908 + 'mxchat_pdf_intent_section'
4909 + );
4910 +
4911 + // Word Upload Button Toggle
4912 + add_settings_field(
4913 + 'show_word_upload_button',
4914 + __('Show Word Upload Button', 'mxchat'),
4915 + array($this, 'mxchat_show_word_upload_button_callback'),
4916 + 'mxchat-embed',
4917 + 'mxchat_pdf_intent_section'
4918 + );
4919 +
4920 + add_settings_field(
3710 4921 'pdf_intent_trigger_text',
3711 4922 __('Intent Trigger Text', 'mxchat'),
3712 4923 array($this, 'mxchat_pdf_intent_trigger_text_callback'),
3713 4924 'mxchat-embed',
@@ -3772,8 +4983,16 @@
3772 4983 'mxchat_live_agent_section'
3773 4984 );
3774 4985
3775 4986 add_settings_field(
4987 + 'live_agent_user_ids',
4988 + __('Slack Agent User IDs', 'mxchat'),
4989 + array($this, 'mxchat_live_agent_user_ids_callback'),
4990 + 'mxchat-embed',
4991 + 'mxchat_live_agent_section'
4992 + );
4993 +
4994 + add_settings_field(
3776 4995 'live_agent_webhook_url',
3777 4996 __('Slack Webhook URL', 'mxchat'),
3778 4997 array($this, 'mxchat_live_agent_webhook_url_callback'),
3779 4998 'mxchat-embed',
@@ -3796,15 +5015,68 @@
3796 5015 'mxchat-embed',
3797 5016 'mxchat_live_agent_section'
3798 5017 );
3799 5018
5019 + // Telegram Integration Section
5020 + add_settings_section(
5021 + 'mxchat_telegram_section',
5022 + __('Telegram Settings', 'mxchat'),
5023 + array($this, 'mxchat_telegram_section_callback'),
5024 + 'mxchat-embed'
5025 + );
3800 5026
5027 + add_settings_field(
5028 + 'telegram_status',
5029 + __('Live Agent Status', 'mxchat'),
5030 + array($this, 'mxchat_telegram_status_callback'),
5031 + 'mxchat-embed',
5032 + 'mxchat_telegram_section'
5033 + );
3801 5034
5035 + add_settings_field(
5036 + 'telegram_notification_message',
5037 + __('Notification Message', 'mxchat'),
5038 + array($this, 'mxchat_telegram_notification_message_callback'),
5039 + 'mxchat-embed',
5040 + 'mxchat_telegram_section'
5041 + );
3802 5042
5043 + add_settings_field(
5044 + 'telegram_away_message',
5045 + __('Away Message', 'mxchat'),
5046 + array($this, 'mxchat_telegram_away_message_callback'),
5047 + 'mxchat-embed',
5048 + 'mxchat_telegram_section'
5049 + );
5050 +
5051 + add_settings_field(
5052 + 'telegram_bot_token',
5053 + __('Telegram Bot Token', 'mxchat'),
5054 + array($this, 'mxchat_telegram_bot_token_callback'),
5055 + 'mxchat-embed',
5056 + 'mxchat_telegram_section'
5057 + );
5058 +
5059 + add_settings_field(
5060 + 'telegram_group_id',
5061 + __('Telegram Group ID', 'mxchat'),
5062 + array($this, 'mxchat_telegram_group_id_callback'),
5063 + 'mxchat-embed',
5064 + 'mxchat_telegram_section'
5065 + );
5066 +
5067 + add_settings_field(
5068 + 'telegram_webhook_secret',
5069 + __('Webhook Secret Token', 'mxchat'),
5070 + array($this, 'mxchat_telegram_webhook_secret_callback'),
5071 + 'mxchat-embed',
5072 + 'mxchat_telegram_section'
5073 + );
5074 +
3803 5075 // General Settings Section
3804 5076 add_settings_section(
3805 5077 'mxchat_general_section',
3806 - esc_html__('Frequently Asked Questions (FAQ)', 'mxchat'),
5078 + esc_html__('YouTube Tutorials', 'mxchat'),
3807 5079 null,
3808 5080 'mxchat-general'
3809 5081 );
3810 5082 }
@@ -3809,9 +5081,8 @@
3809 5081 );
3810 5082 }
3811 5083
3812 5084 public function mxchat_prompts_page_init() {
3813 - // Register all settings as a single array
3814 5085 register_setting(
3815 5086 'mxchat_prompts_options',
3816 5087 'mxchat_prompts_options',
3817 5088 array(
@@ -3823,18 +5094,74 @@
3823 5094 'mxchat_use_pinecone' => 0,
3824 5095 'mxchat_pinecone_api_key' => '',
3825 5096 'mxchat_pinecone_environment' => '',
3826 5097 'mxchat_pinecone_index' => '',
3827 - 'mxchat_pinecone_host' => '', // Add this line
5098 + 'mxchat_pinecone_host' => '',
3828 5099 ),
3829 5100 'sanitize_callback' => array($this, 'sanitize_prompts_options'),
3830 5101 )
3831 5102 );
3832 5103
3833 - // Add settings saved message
3834 5104 add_action('admin_notices', array($this, 'sync_settings_notice'));
3835 5105 }
3836 5106
5107 +public function mxchat_transcripts_page_init() {
5108 + register_setting(
5109 + 'mxchat_transcripts_options',
5110 + 'mxchat_transcripts_options',
5111 + array(
5112 + 'type' => 'array',
5113 + 'description' => __('MXChat Transcripts Notification Settings', 'mxchat'),
5114 + 'default' => array(
5115 + 'mxchat_enable_notifications' => 0,
5116 + 'mxchat_notification_email' => get_option('admin_email'),
5117 + 'mxchat_auto_delete_transcripts' => 'never',
5118 + ),
5119 + 'sanitize_callback' => array($this, 'sanitize_transcripts_options'),
5120 + )
5121 + );
5122 +
5123 + add_settings_section(
5124 + 'mxchat_transcripts_notification_section',
5125 + esc_html__('Chat Notification Settings', 'mxchat'),
5126 + array($this, 'mxchat_transcripts_notification_section_callback'),
5127 + 'mxchat-transcripts'
5128 + );
5129 +
5130 + add_settings_field(
5131 + 'mxchat_enable_notifications',
5132 + esc_html__('Enable Chat Notifications', 'mxchat'),
5133 + array($this, 'mxchat_enable_notifications_callback'),
5134 + 'mxchat-transcripts',
5135 + 'mxchat_transcripts_notification_section'
5136 + );
5137 +
5138 + add_settings_field(
5139 + 'mxchat_notification_email',
5140 + esc_html__('Notification Email Address', 'mxchat'),
5141 + array($this, 'mxchat_notification_email_callback'),
5142 + 'mxchat-transcripts',
5143 + 'mxchat_transcripts_notification_section'
5144 + );
5145 +
5146 + add_settings_field(
5147 + 'mxchat_auto_delete_transcripts',
5148 + esc_html__('Auto-Delete Old Transcripts', 'mxchat'),
5149 + array($this, 'mxchat_auto_delete_transcripts_callback'),
5150 + 'mxchat-transcripts',
5151 + 'mxchat_transcripts_notification_section'
5152 + );
5153 +
5154 + add_settings_field(
5155 + 'mxchat_auto_email_transcript',
5156 + esc_html__('Auto-Email Full Transcript', 'mxchat'),
5157 + array($this, 'mxchat_auto_email_transcript_callback'),
5158 + 'mxchat-transcripts',
5159 + 'mxchat_transcripts_notification_section'
5160 + );
5161 +}
5162 +
5163 +
3837 5164 /**
3838 5165 * Sanitize all prompts options
3839 5166 *
3840 5167 * @param array $input The unsanitized options array
@@ -3893,9 +5220,8 @@
3893 5220
3894 5221 return $sanitized;
3895 5222 }
3896 5223
3897 -
3898 5224 public function sync_settings_notice() {
3899 5225 // Only show notice on our plugin page
3900 5226 if (!isset($_GET['page']) || $_GET['page'] !== 'mxchat-prompts') {
3901 5227 return;
@@ -3906,8 +5232,9 @@
3906 5232
3907 5233 ?>
3908 5234 <div class="notice notice-success is-dismissible">
3909 5235 <p><?php esc_html_e('Sync settings updated successfully.', 'mxchat'); ?></p>
5236 + <button type="button" class="notice-dismiss"><span class="screen-reader-text"><?php esc_html_e('Dismiss this notice.', 'mxchat'); ?></span></button>
3910 5237 </div>
3911 5238 <?php
3912 5239
3913 5240 }
@@ -3916,139 +5243,129 @@
3916 5243 public function sanitize_sync_setting($input) {
3917 5244 return (bool)$input ? __('1', 'mxchat') : __('', 'mxchat');
3918 5245 }
3919 5246
5247 +public function mxchat_rate_limits_callback() {
5248 + $all_options = get_option('mxchat_options', []);
3920 5249
5250 + // Define available rate limits
5251 + $rate_limits = array('1', '3', '5', '10', '15', '20', '50', '100', 'unlimited');
3921 5252
3922 -public function mxchat_handle_activate_license() {
3923 - // Check nonce
3924 - if (!check_ajax_referer('mxchat_activate_license_nonce', 'security', false)) {
3925 - wp_send_json_error(esc_html__('Invalid security token', 'mxchat'));
3926 - return;
3927 - }
5253 + // Define available timeframes
5254 + $timeframes = array(
5255 + 'hourly' => __('Per Hour', 'mxchat'),
5256 + 'daily' => __('Per Day', 'mxchat'),
5257 + 'weekly' => __('Per Week', 'mxchat'),
5258 + 'monthly' => __('Per Month', 'mxchat')
5259 + );
3928 5260
3929 - // Verify user capabilities
3930 - if (!current_user_can('manage_options')) {
3931 - wp_send_json_error(esc_html__('Unauthorized access', 'mxchat'));
3932 - return;
3933 - }
5261 + // Get all roles plus a "logged_out" pseudo-role
5262 + $roles = wp_roles()->get_names();
5263 + $roles['logged_out'] = __('Logged Out Users', 'mxchat');
3934 5264
3935 - $license_key = isset($_POST['mxchat_activation_key']) ? sanitize_text_field($_POST['mxchat_activation_key']) : '';
3936 - $customer_email = isset($_POST['mxchat_pro_email']) ? sanitize_email($_POST['mxchat_pro_email']) : '';
5265 + // Start the wrapper
5266 + echo '<div class="pro-feature-wrapper active">';
5267 + echo '<div class="mxchat-rate-limits-container">';
3937 5268
3938 - if (empty($license_key) || empty($customer_email)) {
3939 - wp_send_json_error(esc_html__('Email or License Key is missing', 'mxchat'));
3940 - return;
3941 - }
5269 + echo '<p class="description" style="margin-bottom: 20px;">' .
5270 + 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') .
5271 + '</p>';
3942 5272
3943 - $product_id = 'MxChatPRO';
3944 - $response = wp_remote_get(
3945 - add_query_arg(
3946 - array(
3947 - 'wc-api' => 'software-api',
3948 - 'request' => 'activation',
3949 - 'email' => $customer_email,
3950 - 'license_key' => $license_key,
3951 - 'product_id' => $product_id
3952 - ),
3953 - 'http://mxchat.ai/'
3954 - )
3955 - );
5273 + // Add markdown link documentation
5274 + echo '<div class="notice notice-info inline" style="margin-bottom: 20px; padding: 10px;">';
5275 + echo '<p><strong>' . esc_html__('Markdown Links Supported:', 'mxchat') . '</strong></p>';
5276 + echo '<p>' . esc_html__('You can include clickable links in your custom messages using markdown syntax:', 'mxchat') . '</p>';
5277 + echo '<ul style="margin-left: 20px;">';
5278 + echo '<li><code>[Link text](https://example.com)</code> - Creates a clickable link</li>';
5279 + echo '<li><code>[Visit our pricing](https://example.com/pricing)</code> - Link with custom text</li>';
5280 + echo '<li><code>Plain URLs like https://example.com will also become clickable</code></li>';
5281 + echo '</ul>';
5282 + echo '</div>';
3956 5283
3957 - if (is_wp_error($response)) {
3958 - wp_send_json_error(esc_html__('Activation failed due to a server error: ', 'mxchat') . $response->get_error_message());
3959 - return;
3960 - }
5284 + // Output the controls for each role
5285 + foreach ($roles as $role_id => $role_name) {
5286 + // Get saved options or defaults
5287 + $default_limit = ($role_id === 'logged_out') ? '10' : '100';
5288 + $default_timeframe = 'daily';
5289 + $default_message = __('Rate limit exceeded. Please try again later.', 'mxchat');
3961 5290
3962 - $body = wp_remote_retrieve_body($response);
3963 - $data = json_decode($body);
5291 + $selected_limit = isset($all_options['rate_limits'][$role_id]['limit'])
5292 + ? $all_options['rate_limits'][$role_id]['limit']
5293 + : $default_limit;
3964 5294
3965 - if ($data && isset($data->activated) && $data->activated) {
3966 - update_option('mxchat_license_status', 'active');
3967 - update_option('mxchat_pro_email', $customer_email);
3968 - update_option('mxchat_activation_key', $license_key);
3969 - wp_send_json_success(array('message' => esc_html__('License activated successfully', 'mxchat')));
3970 - } else {
3971 - $error_message = isset($data->error) ? $data->error : esc_html__('Activation failed', 'mxchat');
3972 - update_option('mxchat_license_status', 'inactive');
3973 - update_option('mxchat_license_error', $error_message);
3974 - wp_send_json_error($error_message);
3975 - }
3976 -}
5295 + $selected_timeframe = isset($all_options['rate_limits'][$role_id]['timeframe'])
5296 + ? $all_options['rate_limits'][$role_id]['timeframe']
5297 + : $default_timeframe;
3977 5298
5299 + $custom_message = isset($all_options['rate_limits'][$role_id]['message'])
5300 + ? $all_options['rate_limits'][$role_id]['message']
5301 + : $default_message;
3978 5302
3979 -public function mxchat_rate_limit_logged_out_callback() {
3980 - // Load the entire 'mxchat_options' array
3981 - $all_options = get_option('mxchat_options', []);
5303 + // Output the row
5304 + echo '<div class="mxchat-rate-limit-row mxchat-autosave-section">';
3982 5305
3983 - // Retrieve the saved rate limit or use the default value
3984 - $default_rate_limit = '10';
3985 - $selected_rate_limit = isset($all_options['rate_limit_logged_out']) ? $all_options['rate_limit_logged_out'] : $default_rate_limit;
5306 + // Role label
5307 + echo '<div class="mxchat-rate-limit-role">' . esc_html($role_name) . '</div>';
3986 5308
3987 - // Define available rate limits
3988 - $rate_limits = array('1', '3', '5', '10', '15', '20', '100', 'unlimited');
5309 + // Controls section
5310 + echo '<div class="mxchat-rate-limit-controls-wrapper">';
3989 5311
3990 - // Output the dropdown
3991 - echo '<div class="pro-feature-wrapper active">';
3992 - echo '<select id="rate_limit_logged_out" name="rate_limit_logged_out">';
3993 - foreach ($rate_limits as $limit) {
3994 - echo '<option value="' . esc_attr($limit) . '" ' . selected($selected_rate_limit, $limit, false) . '>' . esc_html($limit) . '</option>';
3995 - }
3996 - echo '</select>';
3997 - 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>';
3998 - echo '</div>';
3999 -}
5312 + // Rate limit and timeframe controls
5313 + echo '<div class="mxchat-rate-limit-controls">';
4000 5314
4001 -// Add this new callback function
4002 -public function mxchat_rate_limit_roles_callback() {
4003 - $all_options = get_option('mxchat_options', []);
4004 - $roles = wp_roles()->get_names();
4005 - $rate_limits = array('1', '3', '5', '10', '15', '20', '100', 'unlimited');
5315 + // Limit dropdown
5316 + echo '<div>';
5317 + echo '<label for="rate_limits_' . esc_attr($role_id) . '_limit">' . esc_html__('Limit:', 'mxchat') . '</label>';
5318 + echo '<select
5319 + id="rate_limits_' . esc_attr($role_id) . '_limit"
5320 + name="mxchat_options[rate_limits][' . esc_attr($role_id) . '][limit]"
5321 + class="mxchat-autosave-field">';
5322 + foreach ($rate_limits as $limit) {
5323 + echo '<option value="' . esc_attr($limit) . '" ' . selected($selected_limit, $limit, false) . '>' . esc_html($limit) . '</option>';
5324 + }
5325 + echo '</select>';
5326 + echo '</div>';
4006 5327
4007 - echo '<div class="pro-feature-wrapper active mxchat-autosave-section">';
4008 -
4009 - foreach ($roles as $role_id => $role_name) {
4010 - $default_rate_limit = '100';
4011 - $selected_rate_limit = isset($all_options['role_rate_limits'][$role_id])
4012 - ? $all_options['role_rate_limits'][$role_id]
4013 - : $default_rate_limit;
4014 -
4015 - echo '<div style="margin-bottom: 10px;">';
4016 - echo '<label style="display: inline-block; width: 150px;">' . esc_html($role_name) . ':</label>';
5328 + // Timeframe dropdown
5329 + echo '<div>';
5330 + echo '<label for="rate_limits_' . esc_attr($role_id) . '_timeframe">' . esc_html__('Timeframe:', 'mxchat') . '</label>';
4017 5331 echo '<select
4018 - id="role_rate_limits_' . esc_attr($role_id) . '"
4019 - name="mxchat_options[role_rate_limits][' . esc_attr($role_id) . ']"
5332 + id="rate_limits_' . esc_attr($role_id) . '_timeframe"
5333 + name="mxchat_options[rate_limits][' . esc_attr($role_id) . '][timeframe]"
4020 5334 class="mxchat-autosave-field">';
4021 - foreach ($rate_limits as $limit) {
4022 - echo '<option value="' . esc_attr($limit) . '" ' . selected($selected_rate_limit, $limit, false) . '>' . esc_html($limit) . '</option>';
5335 + foreach ($timeframes as $value => $label) {
5336 + echo '<option value="' . esc_attr($value) . '" ' . selected($selected_timeframe, $value, false) . '>' . esc_html($label) . '</option>';
4023 5337 }
4024 5338 echo '</select>';
4025 5339 echo '</div>';
4026 - }
4027 5340
4028 - 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>';
4029 - echo '</div>';
4030 -}
5341 + echo '</div>'; // End controls
4031 5342
4032 -public function mxchat_rate_limit_message_callback() {
4033 - // Load the entire 'mxchat_options' array
4034 - $all_options = get_option('mxchat_options', []);
5343 + // Custom message textarea
5344 + echo '<div class="mxchat-rate-limit-message">';
5345 + echo '<label for="rate_limits_' . esc_attr($role_id) . '_message">' . esc_html__('Custom Message:', 'mxchat') . '</label>';
5346 + echo '<textarea
5347 + id="rate_limits_' . esc_attr($role_id) . '_message"
5348 + name="mxchat_options[rate_limits][' . esc_attr($role_id) . '][message]"
5349 + class="mxchat-autosave-field"
5350 + placeholder="' . esc_attr__('Enter custom message when rate limit is exceeded', 'mxchat') . '">' .
5351 + esc_textarea($custom_message) .
5352 + '</textarea>';
5353 + echo '<p class="description">' .
5354 + esc_html__('Example: Rate limit reached! [Visit our pricing page](https://example.com/pricing) to upgrade.', 'mxchat') .
5355 + '</p>';
5356 + echo '</div>'; // End message
4035 5357
4036 - // Retrieve the saved message or use the default value
4037 - $default_message = esc_html__('Rate limit exceeded. Please try again later.', 'mxchat');
4038 - $rate_limit_message = isset($all_options['rate_limit_message']) ? $all_options['rate_limit_message'] : $default_message;
5358 + echo '</div>'; // End controls wrapper
4039 5359
4040 - // Output the textarea
4041 - echo '<div class="pro-feature-wrapper active">';
4042 - printf(
4043 - '<textarea id="rate_limit_message" name="rate_limit_message" rows="3" cols="50">%s</textarea>',
4044 - esc_textarea($rate_limit_message)
4045 - );
4046 - echo '<p class="description">' . esc_html__('This message will be displayed when a user exceeds the rate limit.', 'mxchat') . '</p>';
4047 - echo '</div>';
5360 + echo '</div>'; // End row
5361 + }
5362 +
5363 + echo '</div>'; // End container
5364 +
5365 + echo '</div>'; // End pro-feature-wrapper
4048 5366 }
4049 5367
4050 -
4051 5368 private function mxchat_add_option_field($id, $title, $callback = '') {
4052 5369 add_settings_field(
4053 5370 $id,
4054 5371 __($title, 'mxchat'),
@@ -4058,97 +5375,120 @@
4058 5375 $id === 'model' ? ['label_for' => 'model'] : []
4059 5376 );
4060 5377 }
4061 5378
5379 +// API Keys Section Callback
5380 +public function mxchat_api_keys_section_callback() {
5381 + 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>';
5382 +}
5383 +
5384 +// OpenAI API Key
4062 5385 public function api_key_callback() {
4063 - // Retrieve from your stored 'api_key' in the mxchat_options array
4064 5386 $apiKey = isset($this->options['api_key']) ? esc_attr($this->options['api_key']) : '';
5387 + $nonce = wp_create_nonce('mxchat_autosave_nonce');
4065 5388
4066 - // Notice we changed the name to "api_key" (no array notation).
4067 - echo '<input type="password" id="api_key" name="api_key" value="' . $apiKey . '" class="regular-text" />';
5389 + echo '<div class="api-key-wrapper">';
5390 + echo '<input type="password" id="api_key" name="api_key" value="' . $apiKey . '" class="regular-text mxchat-autosave-field" autocomplete="off" data-nonce="' . $nonce . '" />';
4068 5391 echo '<button type="button" id="toggleApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5392 + echo '<p class="description">' . esc_html__('Required for OpenAI GPT models and OpenAI embeddings. Get your API key from OpenAI Platform.', 'mxchat') . '</p>';
5393 + echo '</div>';
4069 5394 }
5395 +
5396 +// X.AI API Key
4070 5397 public function xai_api_key_callback() {
4071 - // Check if the feature is activated (paid feature)
4072 - $disabled = $this->is_activated ? '' : 'disabled'; // Disable input if not activated
4073 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive'; // CSS class to style the wrapper based on activation status
4074 -
4075 - // Retrieve the X.AI API key value
4076 5398 $xaiApiKey = isset($this->options['xai_api_key']) ? esc_attr($this->options['xai_api_key']) : '';
5399 + $nonce = wp_create_nonce('mxchat_autosave_nonce');
4077 5400
4078 - // Render the input field for the X.AI API key
4079 - echo '<div class="' . esc_attr($class) . '">';
4080 - printf(
4081 - '<input type="password" id="xai_api_key" name="xai_api_key" value="%s" class="regular-text" %s />',
4082 - $xaiApiKey,
4083 - $disabled
4084 - );
5401 + echo '<div class="api-key-wrapper">';
5402 + echo '<input type="password" id="xai_api_key" name="xai_api_key" value="' . $xaiApiKey . '" class="regular-text mxchat-autosave-field" autocomplete="off" data-nonce="' . $nonce . '" />';
4085 5403 echo '<button type="button" id="toggleXaiApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
4086 -
4087 - // If the feature is not activated, show the overlay with a "Pro Only" message
4088 - if (!$this->is_activated) {
4089 - echo '<div class="pro-feature-overlay">';
4090 - 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>';
4091 - echo '</div>';
4092 - }
4093 -
5404 + echo '<p class="description">' . esc_html__('Required for X.AI Grok models. Get your API key from X.AI Console.', 'mxchat') . '</p>';
4094 5405 echo '</div>';
4095 5406 }
5407 +// Claude API Key
4096 5408 public function claude_api_key_callback() {
4097 - // Check if the feature is activated (paid feature)
4098 - $disabled = $this->is_activated ? '' : 'disabled';
4099 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
4100 -
4101 - // Retrieve the Claude API key value directly like the others
4102 5409 $claudeApiKey = isset($this->options['claude_api_key']) ? esc_attr($this->options['claude_api_key']) : '';
5410 + $nonce = wp_create_nonce('mxchat_autosave_nonce');
4103 5411
4104 - // Use direct name field like the other working API keys
4105 - echo '<div class="' . esc_attr($class) . '">';
4106 - printf(
4107 - '<input type="password" id="claude_api_key" name="claude_api_key" value="%s" class="regular-text" %s />',
4108 - $claudeApiKey,
4109 - $disabled
4110 - );
5412 + echo '<div class="api-key-wrapper">';
5413 + echo '<input type="password" id="claude_api_key" name="claude_api_key" value="' . $claudeApiKey . '" class="regular-text mxchat-autosave-field" autocomplete="off" data-nonce="' . $nonce . '" />';
4111 5414 echo '<button type="button" id="toggleClaudeApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
4112 -
4113 - if (!$this->is_activated) {
4114 - echo '<div class="pro-feature-overlay">';
4115 - 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>';
4116 - echo '</div>';
4117 - }
5415 + echo '<p class="description">' . esc_html__('Required for Anthropic Claude models. Get your API key from Anthropic Console.', 'mxchat') . '</p>';
4118 5416 echo '</div>';
4119 5417 }
5418 +
5419 +// DeepSeek API Key
4120 5420 public function deepseek_api_key_callback() {
4121 - // Retrieve from your stored 'deepseek_api_key' in the mxchat_options array
4122 5421 $apiKey = isset($this->options['deepseek_api_key']) ? esc_attr($this->options['deepseek_api_key']) : '';
5422 + $nonce = wp_create_nonce('mxchat_autosave_nonce');
4123 5423
4124 - // Notice we changed the name to "deepseek_api_key" (no array notation).
4125 - echo '<input type="password" id="deepseek_api_key" name="deepseek_api_key" value="' . $apiKey . '" class="regular-text" />';
5424 + echo '<div class="api-key-wrapper">';
5425 + echo '<input type="password" id="deepseek_api_key" name="deepseek_api_key" value="' . $apiKey . '" class="regular-text mxchat-autosave-field" autocomplete="off" data-nonce="' . $nonce . '" />';
4126 5426 echo '<button type="button" id="toggleDeepSeekApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5427 + echo '<p class="description">' . esc_html__('Required for DeepSeek models. Get your API key from DeepSeek Platform.', 'mxchat') . '</p>';
5428 + echo '</div>';
4127 5429 }
4128 5430
5431 +// Gemini API Key
5432 +public function gemini_api_key_callback() {
5433 + $geminiApiKey = isset($this->options['gemini_api_key']) ? esc_attr($this->options['gemini_api_key']) : '';
5434 + $nonce = wp_create_nonce('mxchat_autosave_nonce');
4129 5435
5436 + echo '<div class="api-key-wrapper">';
5437 + echo '<input type="password" id="gemini_api_key" name="gemini_api_key" value="' . $geminiApiKey . '" class="regular-text mxchat-autosave-field" autocomplete="off" data-nonce="' . $nonce . '" />';
5438 + echo '<button type="button" id="toggleGeminiApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5439 + echo '<p class="description">' . esc_html__('Required for Google Gemini models and embeddings. Get your API key from Google AI Studio.', 'mxchat') . '</p>';
5440 + echo '</div>';
5441 +}
4130 5442
4131 5443
5444 +// OpenRouter API Key
5445 +public function openrouter_api_key_callback() {
5446 + $openrouterApiKey = isset($this->options['openrouter_api_key']) ? esc_attr($this->options['openrouter_api_key']) : '';
5447 + $nonce = wp_create_nonce('mxchat_autosave_nonce');
4132 5448
5449 + echo '<div class="api-key-wrapper">';
5450 + echo '<input type="password" id="openrouter_api_key" name="openrouter_api_key" value="' . $openrouterApiKey . '" class="regular-text mxchat-autosave-field" autocomplete="off" data-nonce="' . $nonce . '" />';
5451 + echo '<button type="button" id="toggleOpenRouterApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5452 + echo '<p class="description">' . esc_html__('Required for OpenRouter models. Get your API key from OpenRouter.ai', 'mxchat') . '</p>';
5453 + echo '</div>';
5454 +}
5455 +
5456 +// Voyage API Key
5457 +public function voyage_api_key_callback() {
5458 + $apiKey = isset($this->options['voyage_api_key']) ? esc_attr($this->options['voyage_api_key']) : '';
5459 + $nonce = wp_create_nonce('mxchat_autosave_nonce');
5460 +
5461 + echo '<div class="api-key-wrapper">';
5462 + echo '<input type="password" id="voyage_api_key" name="voyage_api_key" value="' . $apiKey . '" class="regular-text mxchat-autosave-field" autocomplete="off" data-nonce="' . $nonce . '" />';
5463 + echo '<button type="button" id="toggleVoyageAPIKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5464 + echo '<p class="description">' . esc_html__('Required for Voyage AI embedding models. Get your API key from Voyage AI.', 'mxchat') . '</p>';
5465 + echo '</div>';
5466 +}
5467 +
4133 5468 public function mxchat_loops_api_key_callback() {
4134 - // Support both old and new format
4135 5469 $loops_api_key = isset($this->options['loops_api_key']) ? esc_attr($this->options['loops_api_key']) : '';
5470 + $nonce = wp_create_nonce('mxchat_autosave_nonce');
4136 5471
5472 + // Hidden fields to "trap" autofill
5473 + echo '<input type="text" style="display:none" autocomplete="username" />';
5474 + echo '<input type="password" style="display:none" autocomplete="current-password" />';
5475 +
4137 5476 echo '<div class="api-key-wrapper">';
4138 5477 echo sprintf(
4139 - '<input type="password" id="loops_api_key" name="loops_api_key" value="%s" class="regular-text" />',
4140 - $loops_api_key
5478 + '<input type="password" id="loops_api_key" name="loops_api_key" value="%s" class="regular-text mxchat-autosave-field" autocomplete="new-password" data-nonce="%s" />',
5479 + $loops_api_key,
5480 + $nonce
4141 5481 );
4142 5482 echo '<button type="button" id="toggleLoopsApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
4143 5483 echo '</div>';
4144 - echo '<p class="description">' . esc_html__('Enter your Loops API Key here. (See FAQ for details)', 'mxchat') . '</p>';
5484 + echo '<p class="description">' . esc_html__('Required for Loops email integration. Get your API key from Loops.so', 'mxchat') . '</p>';
4145 5485 }
4146 -
4147 5486 public function mxchat_loops_mailing_list_callback() {
4148 5487 // Add error handling and type checking
4149 5488 $loops_api_key = '';
4150 5489 $selected_list = '';
5490 + $nonce = wp_create_nonce('mxchat_autosave_nonce');
4151 5491
4152 5492 // Safely get the API key
4153 5493 if (isset($this->options['loops_api_key']) && is_string($this->options['loops_api_key'])) {
4154 5494 $loops_api_key = $this->options['loops_api_key'];
@@ -4161,9 +5501,14 @@
4161 5501
4162 5502 if (!empty($loops_api_key)) {
4163 5503 $lists = $this->mxchat_fetch_loops_mailing_lists($loops_api_key);
4164 5504 if (is_array($lists) && !empty($lists)) {
4165 - echo '<select id="loops_mailing_list" name="loops_mailing_list">';
5505 + echo '<div class="mxchat-field-wrapper">';
5506 + echo '<select id="loops_mailing_list" name="loops_mailing_list" class="mxchat-autosave-field" data-nonce="' . $nonce . '">';
5507 +
5508 + // Add a default "Select a list" option
5509 + echo '<option value="" ' . selected($selected_list, '', false) . '>' . esc_html__('Select a list', 'mxchat') . '</option>';
5510 +
4166 5511 foreach ($lists as $list) {
4167 5512 if (is_array($list) && isset($list['id']) && isset($list['name'])) {
4168 5513 echo sprintf(
4169 5514 '<option value="%s" %s>%s</option>',
@@ -4173,8 +5518,10 @@
4173 5518 );
4174 5519 }
4175 5520 }
4176 5521 echo '</select>';
5522 + echo '</div>';
5523 + echo '<p class="description">' . esc_html__('Please select a mailing list to use with Loops.', 'mxchat') . '</p>';
4177 5524 } else {
4178 5525 echo '<p class="description">' . esc_html__('No lists found. Please verify your API Key.', 'mxchat') . '</p>';
4179 5526 }
4180 5527 } else {
@@ -4185,14 +5532,18 @@
4185 5532 $default_response = __('Would you like to join our mailing list? Please provide your email below.', 'mxchat');
4186 5533 $triggered_response = isset($this->options['triggered_phrase_response'])
4187 5534 ? $this->options['triggered_phrase_response']
4188 5535 : $default_response;
5536 + $nonce = wp_create_nonce('mxchat_autosave_nonce');
4189 5537
5538 + echo '<div class="mxchat-field-wrapper">';
4190 5539 echo sprintf(
4191 - '<textarea id="triggered_phrase_response" name="triggered_phrase_response" rows="3" cols="50">%s</textarea>',
5540 + '<textarea id="triggered_phrase_response" name="triggered_phrase_response" rows="3" cols="50" class="mxchat-autosave-field" data-nonce="%s">%s</textarea>',
5541 + $nonce,
4192 5542 esc_textarea($triggered_response)
4193 5543 );
4194 - 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>';
5544 + echo '</div>';
5545 + 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>';
4195 5546 }
4196 5547
4197 5548 public function mxchat_email_capture_response_callback() {
4198 5549 $default_response = __('Thank you for providing your email! You\'ve been added to our list.', 'mxchat');
@@ -4198,16 +5549,19 @@
4198 5549 $default_response = __('Thank you for providing your email! You\'ve been added to our list.', 'mxchat');
4199 5550 $email_capture_response = isset($this->options['email_capture_response'])
4200 5551 ? $this->options['email_capture_response']
4201 5552 : $default_response;
5553 + $nonce = wp_create_nonce('mxchat_autosave_nonce');
4202 5554
5555 + echo '<div class="mxchat-field-wrapper">';
4203 5556 echo sprintf(
4204 - '<textarea id="email_capture_response" name="email_capture_response" rows="3" cols="50">%s</textarea>',
5557 + '<textarea id="email_capture_response" name="email_capture_response" rows="3" cols="50" class="mxchat-autosave-field" data-nonce="%s">%s</textarea>',
5558 + $nonce,
4205 5559 esc_textarea($email_capture_response)
4206 5560 );
4207 - echo '<p class="description">' . esc_html__('Enter the message to send when a user provides their email.', 'mxchat') . '</p>';
5561 + echo '</div>';
5562 + 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>';
4208 5563 }
4209 -
4210 5564 public function mxchat_pre_chat_message_callback() {
4211 5565 // Load the entire 'mxchat_options' array
4212 5566 $all_options = get_option('mxchat_options', []);
4213 5567
@@ -4219,59 +5573,154 @@
4219 5573 printf(
4220 5574 '<textarea id="pre_chat_message" name="pre_chat_message" rows="5" cols="50">%s</textarea>',
4221 5575 esc_textarea($pre_chat_message)
4222 5576 );
4223 - 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>';
4224 5577 }
4225 5578
4226 -
4227 -
4228 5579 // Callback for AI Instructions textarea
4229 5580 public function system_prompt_instructions_callback() {
4230 5581 // Retrieve the current value of the system prompt instructions
4231 5582 $instructions = isset($this->options['system_prompt_instructions']) ? esc_textarea($this->options['system_prompt_instructions']) : '';
4232 -
4233 5583 // Render the textarea field
4234 5584 printf(
4235 5585 '<textarea id="system_prompt_instructions" name="system_prompt_instructions" rows="5" cols="50">%s</textarea>',
4236 5586 $instructions
4237 5587 );
5588 + // Personalization hint
5589 + echo '<p class="description" style="margin-top: 8px;">';
5590 + echo esc_html__('Use {visitor_name} to personalize AI responses when lead capture is enabled.', 'mxchat') . '<br>';
5591 + echo '<code style="font-size: 12px;">' . esc_html__('Example: The visitor\'s name is {visitor_name}. Address them by name.', 'mxchat') . '</code>';
5592 + echo '</p>';
5593 + // Sample instructions button
5594 + echo '<div class="mxchat-instructions-container">';
5595 + echo '<button type="button" class="mxchat-instructions-btn" id="mxchatViewSampleBtn">';
5596 + 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">';
5597 + echo '<path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/>';
5598 + echo '<path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/>';
5599 + echo '</svg>';
5600 + echo esc_html__('View Sample Instructions', 'mxchat');
5601 + echo '</button>';
5602 + echo '</div>';
4238 5603
4239 - // Provide a helpful description
4240 - 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>';
5604 + // Add modal to WordPress admin footer instead of inline
5605 + add_action('admin_footer', array($this, 'render_sample_instructions_modal'));
4241 5606 }
4242 5607
5608 +// New method to render modal in admin footer
5609 +public function render_sample_instructions_modal() {
5610 + static $modal_rendered = false;
5611 + if ($modal_rendered) return; // Prevent duplicate modals
5612 + $modal_rendered = true;
4243 5613
5614 + echo '<div class="mxchat-instructions-modal-overlay" id="mxchatSampleModal">';
5615 + echo '<div class="mxchat-instructions-modal-content">';
5616 + echo '<div class="mxchat-instructions-modal-header">';
5617 + echo '<h3 class="mxchat-instructions-modal-title">';
5618 + 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">';
5619 + echo '<path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/>';
5620 + echo '<path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/>';
5621 + echo '</svg>';
5622 + echo esc_html__('Sample AI Instructions', 'mxchat');
5623 + echo '</h3>';
5624 + echo '<button type="button" class="mxchat-instructions-modal-close" id="mxchatModalClose">';
5625 + 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">';
5626 + echo '<line x1="18" y1="6" x2="6" y2="18"/>';
5627 + echo '<line x1="6" y1="6" x2="18" y2="18"/>';
5628 + echo '</svg>';
5629 + echo '</button>';
5630 + echo '</div>';
5631 + echo '<div class="mxchat-instructions-modal-body">';
5632 + echo '<div class="mxchat-instructions-content">';
5633 + 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:
5634 +
5635 +# Response Style - CRITICALLY IMPORTANT
5636 +- MAXIMUM LENGTH: 1-3 short sentences per response
5637 +- Ultra-concise: Get straight to the answer with no filler
5638 +- No introductions like "Sure!" or "I\'d be happy to help"
5639 +- No phrases like "based on my knowledge" or "according to information"
5640 +- No explanatory text before giving the answer
5641 +- No summaries or repetition
5642 +- Hyperlink all URLs
5643 +- Respond in user\'s language
5644 +- Minor chit chat or conversation is okay, but try to keep it focused on [insert topic]
5645 +
5646 +# Knowledge Base Requirements - PREVENT HALLUCINATIONS
5647 +- ONLY answer using information explicitly provided in OFFICIAL KNOWLEDGE DATABASE CONTENT sections marked with ===== delimiters
5648 +- If required information is NOT in the knowledge database: "I don\'t have enough information in my knowledge base to answer that question accurately."
5649 +- NEVER invent or hallucinate URLs, links, product specs, procedures, dates, statistics, names, contacts, or company information
5650 +- When knowledge base information is unclear or contradictory, acknowledge the limitation rather than guessing
5651 +- Better to admit insufficient information than provide inaccurate answers');
5652 + echo '</div>';
5653 + echo '<button type="button" class="mxchat-instructions-copy-btn" id="mxchatCopyBtn">';
5654 + 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">';
5655 + echo '<rect x="9" y="9" width="13" height="13" rx="2" ry="2"/>';
5656 + echo '<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/>';
5657 + echo '</svg>';
5658 + echo esc_html__('Copy Instructions', 'mxchat');
5659 + echo '</button>';
5660 + echo '</div>';
5661 + echo '<div class="mxchat-instructions-modal-footer">';
5662 + echo '<button type="button" class="mxchat-instructions-btn-secondary" id="mxchatCloseBtn">' . esc_html__('Close', 'mxchat') . '</button>';
5663 + echo '</div>';
5664 + echo '</div>';
5665 + echo '</div>';
5666 +}
5667 +
5668 +
4244 5669 public function mxchat_model_callback() {
4245 5670 // Define available models grouped by provider
4246 5671 $models = array(
5672 + esc_html__('OpenRouter (100+ Models)', 'mxchat') => array(
5673 + 'openrouter' => esc_html__('OpenRouter - Select after entering API key', 'mxchat'),
5674 + ),
5675 + esc_html__('Google Gemini Models', 'mxchat') => array(
5676 + 'gemini-3-pro-preview' => esc_html__('Gemini 3 Pro (Most Intelligent, Multimodal)', 'mxchat'),
5677 + 'gemini-3-flash-preview' => esc_html__('Gemini 3 Flash (Balanced Speed & Scale)', 'mxchat'),
5678 + 'gemini-2.5-pro' => esc_html__('Gemini 2.5 Pro (Advanced Thinking)', 'mxchat'),
5679 + 'gemini-2.5-flash' => esc_html__('Gemini 2.5 Flash (Best Price-Performance)', 'mxchat'),
5680 + 'gemini-2.5-flash-lite' => esc_html__('Gemini 2.5 Flash-Lite (Ultra Fast)', 'mxchat'),
5681 + 'gemini-2.0-flash' => esc_html__('Gemini 2.0 Flash (Deprecated Mar 2026)', 'mxchat'),
5682 + 'gemini-2.0-flash-lite' => esc_html__('Gemini 2.0 Flash-Lite (Deprecated Mar 2026)', 'mxchat'),
5683 + 'gemini-1.5-pro' => esc_html__('Gemini 1.5 Pro (Deprecated Sep 2025)', 'mxchat'),
5684 + 'gemini-1.5-flash' => esc_html__('Gemini 1.5 Flash (Deprecated Sep 2025)', 'mxchat'),
5685 + ),
4247 5686 esc_html__('X.AI Models', 'mxchat') => array(
4248 - 'grok-beta' => esc_html__('grok-beta (Early Beta)', 'mxchat'),
4249 - 'grok-2' => esc_html__('Grok 2', 'mxchat')
5687 + 'grok-3-beta' => esc_html__('Grok-3 (Powerful)', 'mxchat'),
5688 + 'grok-3-fast-beta' => esc_html__('Grok-3 Fast (High Performance)', 'mxchat'),
5689 + 'grok-3-mini-beta' => esc_html__('Grok-3 Mini (Affordable)', 'mxchat'),
5690 + 'grok-3-mini-fast-beta' => esc_html__('Grok-3 Mini Fast (Quick Response)', 'mxchat'),
5691 + 'grok-2' => esc_html__('Grok 2', 'mxchat'),
5692 + 'grok-4-0709' => esc_html__('Grok 4 (Latest Flagship)', 'mxchat'),
5693 + 'grok-4-1-fast-reasoning' => esc_html__('Grok 4.1 Fast (Reasoning)', 'mxchat'),
5694 + 'grok-4-1-fast-non-reasoning' => esc_html__('Grok 4.1 Fast (Non-Reasoning)', 'mxchat'),
4250 5695 ),
4251 5696 esc_html__('DeepSeek Models', 'mxchat') => array(
4252 5697 'deepseek-chat' => esc_html__('DeepSeek-V3', 'mxchat'),
4253 5698 ),
4254 5699 esc_html__('Claude Models', 'mxchat') => array(
4255 - 'claude-3-5-sonnet-20241022' => esc_html__('Claude 3.5 Sonnet (Most Intelligent)', 'mxchat'),
4256 - 'claude-3-opus-20240229' => esc_html__('Claude 3 Opus (Highly Complex Tasks)', 'mxchat'),
4257 - 'claude-3-sonnet-20240229' => esc_html__('Claude 3 Sonnet (Balanced)', 'mxchat'),
4258 - 'claude-3-haiku-20240307' => esc_html__('Claude 3 Haiku (Fastest)', 'mxchat')
5700 + 'claude-opus-4-6' => esc_html__('Claude Opus 4.6 (Most Capable - Recommended)', 'mxchat'),
5701 + 'claude-opus-4-5' => esc_html__('Claude Opus 4.5 (Highly Capable)', 'mxchat'),
5702 + 'claude-sonnet-4-5-20250929' => esc_html__('Claude Sonnet 4.5 (Best for Agents & Coding)', 'mxchat'),
5703 + 'claude-opus-4-1-20250805' => esc_html__('Claude Opus 4.1 (Exceptional for Complex Tasks)', 'mxchat'),
5704 + 'claude-haiku-4-5-20251001' => esc_html__('Claude Haiku 4.5 (Fastest & Most Intelligent)', 'mxchat'),
5705 + 'claude-opus-4-20250514' => esc_html__('Claude 4 Opus (Complex Tasks)', 'mxchat'),
5706 + 'claude-sonnet-4-20250514' => esc_html__('Claude 4 Sonnet (High Performance)', 'mxchat'),
4259 5707 ),
4260 5708 esc_html__('OpenAI Models', 'mxchat') => array(
4261 - 'gpt-4o' => esc_html__('GPT-4o (Recommended)', 'mxchat'),
4262 - 'gpt-4o-mini' => esc_html__('GPT-4o Mini (Fast and Lightweight)', 'mxchat'),
4263 - 'gpt-4-turbo' => esc_html__('GPT-4 Turbo (High-Performance)', 'mxchat'),
4264 - 'gpt-4' => esc_html__('GPT-4 (High Intelligence)', 'mxchat'),
4265 - 'gpt-3.5-turbo' => esc_html__('GPT-3.5 Turbo (Affordable and Fast)', 'mxchat')
4266 - )
5709 + 'gpt-5.2' => esc_html__('GPT-5.2 (Best General-Purpose & Agentic Model)', 'mxchat'),
5710 + 'gpt-5.1-chat-latest' => esc_html__('GPT-5.1 Chat Latest (Recommended)', 'mxchat'),
5711 + 'gpt-5.1-2025-11-13' => esc_html__('GPT-5.1 (Flagship for Coding & Agentic Tasks)', 'mxchat'),
5712 + 'gpt-5' => esc_html__('GPT-5 (Flagship for Coding, Reasoning & Agents)', 'mxchat'),
5713 + 'gpt-5-mini' => esc_html__('GPT-5 Mini (Fast and Lightweight)', 'mxchat'),
5714 + 'gpt-5-nano' => esc_html__('GPT-5 Nano (Fastest & Cheapest for Summarization/Classification)', 'mxchat'),
5715 + ),
4267 5716 );
4268 5717
4269 5718 // Retrieve the currently selected model from saved options
4270 - $selected_model = isset($this->options['model']) ? esc_attr($this->options['model']) : 'gpt-4o';
5719 + $selected_model = isset($this->options['model']) ? esc_attr($this->options['model']) : 'gpt-5.1-chat-latest';
4271 5720
4272 5721 // Begin the select dropdown
4273 - echo '<select id="model" name="model">'; // No array notation in name attribute
5722 + echo '<select id="model" name="model">';
4274 5723
4275 5724 // Iterate over groups of models
4276 5725 foreach ($models as $group_label => $group_models) {
4277 5726 echo '<optgroup label="' . esc_attr($group_label) . '">';
@@ -4276,47 +5725,219 @@
4276 5725 foreach ($models as $group_label => $group_models) {
4277 5726 echo '<optgroup label="' . esc_attr($group_label) . '">';
4278 5727
4279 5728 foreach ($group_models as $model_value => $model_label) {
4280 - // Disable Pro-only models for non-activated users
4281 - $disabled = (!$this->is_activated && ($group_label === esc_html__('X.AI Models', 'mxchat') || $group_label === esc_html__('Claude Models', 'mxchat'))) ? 'disabled' : '';
4282 - $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') : '';
4283 -
4284 - // Output the option element
4285 - echo '<option value="' . esc_attr($model_value) . '" ' . selected($selected_model, $model_value, false) . ' ' . $disabled . '>' . esc_html($model_label . $label_suffix) . '</option>';
5729 + echo '<option value="' . esc_attr($model_value) . '" ' . selected($selected_model, $model_value, false) . '>' . esc_html($model_label) . '</option>';
4286 5730 }
4287 5731
4288 5732 echo '</optgroup>';
4289 5733 }
4290 5734
4291 - // Close the select dropdown
4292 5735 echo '</select>';
4293 5736
4294 - // Add a description below the dropdown
4295 - echo '<p class="description">' . esc_html__('Select the AI model your chatbot will use for chatting. Pro-only models are marked accordingly.', 'mxchat') . '</p>';
5737 + // Add a note for OpenRouter
5738 + echo '<p class="description" id="openrouter-model-note" style="display:none; color: #d63638; font-weight: 500;">';
5739 + echo '<span class="dashicons dashicons-info" style="font-size: 16px; vertical-align: middle;"></span> ';
5740 + echo esc_html__('After entering your OpenRouter API key above, click the button below to load available models.', 'mxchat');
5741 + echo '</p>';
5742 +
5743 + // API Key Status Messages (hidden by default, shown by JS based on selected model)
5744 + $has_openai_key = !empty($this->options['api_key']);
5745 + $has_claude_key = !empty($this->options['claude_api_key']);
5746 + $has_xai_key = !empty($this->options['xai_api_key']);
5747 + $has_deepseek_key = !empty($this->options['deepseek_api_key']);
5748 + $has_gemini_key = !empty($this->options['gemini_api_key']);
5749 + $has_openrouter_key = !empty($this->options['openrouter_api_key']);
5750 +
5751 + // OpenAI/GPT models
5752 + echo '<p class="mxchat-api-status" data-provider="openai" style="display:none;">';
5753 + if ($has_openai_key) {
5754 + echo '<span style="color: #00a32a;">✓ ' . esc_html__('API key for OpenAI detected', 'mxchat') . '</span>';
5755 + } else {
5756 + echo '<span style="color: #d63638;">⚠ ' . esc_html__('No API key for OpenAI detected. Please enter API key in API Keys tab.', 'mxchat') . '</span>';
5757 + }
5758 + echo '</p>';
5759 +
5760 + // Claude models
5761 + echo '<p class="mxchat-api-status" data-provider="claude" style="display:none;">';
5762 + if ($has_claude_key) {
5763 + echo '<span style="color: #00a32a;">✓ ' . esc_html__('API key for Anthropic (Claude) detected', 'mxchat') . '</span>';
5764 + } else {
5765 + echo '<span style="color: #d63638;">⚠ ' . esc_html__('No API key for Anthropic (Claude) detected. Please enter API key in API Keys tab.', 'mxchat') . '</span>';
5766 + }
5767 + echo '</p>';
5768 +
5769 + // X.AI models
5770 + echo '<p class="mxchat-api-status" data-provider="xai" style="display:none;">';
5771 + if ($has_xai_key) {
5772 + echo '<span style="color: #00a32a;">✓ ' . esc_html__('API key for X.AI (Grok) detected', 'mxchat') . '</span>';
5773 + } else {
5774 + 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>';
5775 + }
5776 + echo '</p>';
5777 +
5778 + // DeepSeek models
5779 + echo '<p class="mxchat-api-status" data-provider="deepseek" style="display:none;">';
5780 + if ($has_deepseek_key) {
5781 + echo '<span style="color: #00a32a;">✓ ' . esc_html__('API key for DeepSeek detected', 'mxchat') . '</span>';
5782 + } else {
5783 + echo '<span style="color: #d63638;">⚠ ' . esc_html__('No API key for DeepSeek detected. Please enter API key in API Keys tab.', 'mxchat') . '</span>';
5784 + }
5785 + echo '</p>';
5786 +
5787 + // Gemini models
5788 + echo '<p class="mxchat-api-status" data-provider="gemini" style="display:none;">';
5789 + if ($has_gemini_key) {
5790 + echo '<span style="color: #00a32a;">✓ ' . esc_html__('API key for Google Gemini detected', 'mxchat') . '</span>';
5791 + } else {
5792 + echo '<span style="color: #d63638;">⚠ ' . esc_html__('No API key for Google Gemini detected. Please enter API key in API Keys tab.', 'mxchat') . '</span>';
5793 + }
5794 + echo '</p>';
5795 +
5796 + // OpenRouter models
5797 + echo '<p class="mxchat-api-status" data-provider="openrouter" style="display:none;">';
5798 + if ($has_openrouter_key) {
5799 + echo '<span style="color: #00a32a;">✓ ' . esc_html__('API key for OpenRouter detected', 'mxchat') . '</span>';
5800 + } else {
5801 + echo '<span style="color: #d63638;">⚠ ' . esc_html__('No API key for OpenRouter detected. Please enter API key in API Keys tab.', 'mxchat') . '</span>';
5802 + }
5803 + echo '</p>';
5804 +
5805 + // ADD THESE HIDDEN FIELDS RIGHT HERE:
5806 + $openrouter_model = isset($this->options['openrouter_selected_model']) ? esc_attr($this->options['openrouter_selected_model']) : '';
5807 + $openrouter_model_name = isset($this->options['openrouter_selected_model_name']) ? esc_attr($this->options['openrouter_selected_model_name']) : '';
5808 +
5809 + echo '<input type="hidden" id="openrouter_selected_model" name="openrouter_selected_model" value="' . $openrouter_model . '" />';
5810 + echo '<input type="hidden" id="openrouter_selected_model_name" name="openrouter_selected_model_name" value="' . $openrouter_model_name . '" />';
4296 5811 }
4297 5812
4298 -public function voyage_api_key_callback() {
4299 - $apiKey = isset($this->options['voyage_api_key']) ? esc_attr($this->options['voyage_api_key']) : '';
4300 - echo '<input type="password" id="voyage_api_key" name="voyage_api_key" value="' . $apiKey . '" class="regular-text" />';
4301 - echo '<button type="button" id="toggleVoyageAPIKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5813 +// Update your existing callback method
5814 +public function enable_streaming_toggle_callback() {
5815 + // Get value from options array, default to 'on'
5816 + $enabled = isset($this->options['enable_streaming_toggle']) ? $this->options['enable_streaming_toggle'] : 'on';
5817 + $checked = ($enabled === 'on') ? 'checked' : '';
5818 +
5819 + echo '<label class="toggle-switch">';
5820 + echo sprintf(
5821 + '<input type="checkbox" id="enable_streaming_toggle" name="enable_streaming_toggle" value="on" %s />',
5822 + esc_attr($checked)
5823 + );
5824 + echo '<span class="slider"></span>';
5825 + echo '</label>';
5826 +
5827 + // Test button with better styling
5828 + echo '<div style="margin-top: 15px;">';
5829 + echo '<button type="button" id="mxchat-test-streaming-btn" class="button button-secondary">';
5830 + echo '<span class="dashicons dashicons-admin-tools" style="vertical-align: middle; margin-right: 5px;"></span>';
5831 + echo esc_html__('Test Streaming Compatibility', 'mxchat');
5832 + echo '</button>';
5833 + echo '<p id="mxchat-test-streaming-result" style="margin-top:10px; font-weight: bold;"></p>';
5834 + echo '</div>';
4302 5835 }
4303 5836
5837 +// Web Search toggle callback
5838 +public function enable_web_search_toggle_callback() {
5839 + // Get value from options array, default to 'off'
5840 + $enabled = isset($this->options['enable_web_search']) ? $this->options['enable_web_search'] : 'off';
5841 + $checked = ($enabled === 'on') ? 'checked' : '';
5842 +
5843 + // Get current model to determine if we should show/enable the toggle
5844 + $current_model = isset($this->options['model']) ? $this->options['model'] : 'gpt-5.1-chat-latest';
5845 +
5846 + // Models that DON'T support web search (per OpenAI docs)
5847 + // gpt-5 with minimal reasoning is handled at API level, gpt-4.1-nano doesn't support it
5848 + $unsupported_models = array('gpt-4.1-nano');
5849 +
5850 + // Check if current model is an OpenAI model that supports web search
5851 + $openai_models = array(
5852 + 'gpt-5.2', 'gpt-5.1-chat-latest', 'gpt-5.1-2025-11-13', 'gpt-5', 'gpt-5-mini', 'gpt-5-nano'
5853 + );
5854 +
5855 + $is_openai = in_array($current_model, $openai_models);
5856 + $is_supported = $is_openai && !in_array($current_model, $unsupported_models);
5857 +
5858 + // Wrapper div with data attributes for JS to show/hide
5859 + 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;"' : '') . '>';
5860 +
5861 + echo '<label class="toggle-switch">';
5862 + echo sprintf(
5863 + '<input type="checkbox" id="enable_web_search" name="enable_web_search" value="on" %s />',
5864 + esc_attr($checked)
5865 + );
5866 + echo '<span class="slider"></span>';
5867 + echo '</label>';
5868 +
5869 + echo '</div>';
5870 +
5871 + // Message shown when non-OpenAI model is selected
5872 + echo '<p id="web-search-unavailable-message" class="description" style="color: #666;' . ($is_supported ? ' display:none;' : '') . '">';
5873 + echo '<span class="dashicons dashicons-info" style="font-size: 16px; vertical-align: middle; margin-right: 4px;"></span>';
5874 + echo esc_html__('Web search is only available for OpenAI models.', 'mxchat');
5875 + echo '</p>';
5876 +}
5877 +
5878 +// AJAX handler to fetch OpenRouter models
5879 +public function fetch_openrouter_models() {
5880 + check_ajax_referer('mxchat_fetch_openrouter_models', 'nonce');
5881 +
5882 + $api_key = isset($_POST['api_key']) ? sanitize_text_field($_POST['api_key']) : '';
5883 +
5884 + if (empty($api_key)) {
5885 + wp_send_json_error(array('message' => 'API key is required'));
5886 + }
5887 +
5888 + $response = wp_remote_get('https://openrouter.ai/api/v1/models', array(
5889 + 'headers' => array(
5890 + 'Authorization' => 'Bearer ' . $api_key,
5891 + 'Content-Type' => 'application/json',
5892 + ),
5893 + 'timeout' => 15,
5894 + ));
5895 +
5896 + if (is_wp_error($response)) {
5897 + wp_send_json_error(array('message' => $response->get_error_message()));
5898 + }
5899 +
5900 + $body = wp_remote_retrieve_body($response);
5901 + $data = json_decode($body, true);
5902 +
5903 + if (isset($data['data']) && is_array($data['data'])) {
5904 + // Format the models for the frontend
5905 + $models = array_map(function($model) {
5906 + return array(
5907 + 'id' => $model['id'],
5908 + 'name' => $model['name'] ?? $model['id'],
5909 + 'description' => $model['description'] ?? '',
5910 + 'context_length' => $model['context_length'] ?? 0,
5911 + 'pricing' => array(
5912 + 'prompt' => $model['pricing']['prompt'] ?? 0,
5913 + 'completion' => $model['pricing']['completion'] ?? 0,
5914 + ),
5915 + );
5916 + }, $data['data']);
5917 +
5918 + wp_send_json_success(array('models' => $models));
5919 + } else {
5920 + wp_send_json_error(array('message' => 'Invalid response from OpenRouter'));
5921 + }
5922 +}
5923 +
4304 5924 // Callback function for embedding model selection
4305 5925 public function embedding_model_callback() {
4306 5926 $models = array(
4307 5927 esc_html__('OpenAI Embeddings', 'mxchat') => array(
4308 5928 'text-embedding-3-small' => esc_html__('TE3 Small (1536, Efficient)', 'mxchat'),
4309 - 'text-embedding-ada-002' => esc_html__('Ada 2 (1536, Original)', 'mxchat'),
5929 + 'text-embedding-ada-002' => esc_html__('Ada 2 (1536, Recommended)', 'mxchat'),
4310 5930 'text-embedding-3-large' => esc_html__('TE3 Large (3072, Powerful)', 'mxchat'),
4311 5931 ),
4312 5932 esc_html__('Voyage AI Embeddings', 'mxchat') => array(
4313 5933 'voyage-3-large' => esc_html__('Voyage-3 Large (2048, Most Capable)', 'mxchat'),
5934 + ),
5935 + esc_html__('Google Gemini Embeddings', 'mxchat') => array(
5936 + 'gemini-embedding-001' => esc_html__('Gemini Embedding (1536, Stable)', 'mxchat'),
4314 5937 )
4315 5938 );
4316 -
4317 5939 $selected_model = isset($this->options['embedding_model']) ? esc_attr($this->options['embedding_model']) : 'text-embedding-ada-002';
4318 -
4319 5940 echo '<select id="embedding_model" name="embedding_model">';
4320 5941 foreach ($models as $group_label => $group_models) {
4321 5942 echo '<optgroup label="' . esc_attr($group_label) . '">';
4322 5943 foreach ($group_models as $model_value => $model_label) {
@@ -4324,10 +5945,40 @@
4324 5945 }
4325 5946 echo '</optgroup>';
4326 5947 }
4327 5948 echo '</select>';
4328 -echo '<p class="description"><span class="red-warning">IMPORTANT:</span> Select the model for vector embeddings. Changing models is not recommended; if you do, you must delete all existing knowledge & intent data and reconfigure them.</p>';
4329 5949
5950 + // API Key Status Messages for Embedding Models
5951 + $has_openai_key = !empty($this->options['api_key']);
5952 + $has_voyage_key = !empty($this->options['voyage_api_key']);
5953 + $has_gemini_key = !empty($this->options['gemini_api_key']);
5954 +
5955 + // OpenAI Embeddings
5956 + echo '<p class="mxchat-embedding-api-status" data-provider="openai" style="display:none;">';
5957 + if ($has_openai_key) {
5958 + echo '<span style="color: #00a32a;">✓ ' . esc_html__('API key for OpenAI detected', 'mxchat') . '</span>';
5959 + } else {
5960 + echo '<span style="color: #d63638;">⚠ ' . esc_html__('No API key for OpenAI detected. Please enter API key in API Keys tab.', 'mxchat') . '</span>';
5961 + }
5962 + echo '</p>';
5963 +
5964 + // Voyage AI Embeddings
5965 + echo '<p class="mxchat-embedding-api-status" data-provider="voyage" style="display:none;">';
5966 + if ($has_voyage_key) {
5967 + echo '<span style="color: #00a32a;">✓ ' . esc_html__('API key for Voyage AI detected', 'mxchat') . '</span>';
5968 + } else {
5969 + echo '<span style="color: #d63638;">⚠ ' . esc_html__('No API key for Voyage AI detected. Please enter API key in API Keys tab.', 'mxchat') . '</span>';
5970 + }
5971 + echo '</p>';
5972 +
5973 + // Gemini Embeddings
5974 + echo '<p class="mxchat-embedding-api-status" data-provider="gemini" style="display:none;">';
5975 + if ($has_gemini_key) {
5976 + echo '<span style="color: #00a32a;">✓ ' . esc_html__('API key for Google Gemini detected', 'mxchat') . '</span>';
5977 + } else {
5978 + echo '<span style="color: #d63638;">⚠ ' . esc_html__('No API key for Google Gemini detected. Please enter API key in API Keys tab.', 'mxchat') . '</span>';
5979 + }
5980 + echo '</p>';
4330 5981 }
4331 5982
4332 5983
4333 5984 public function mxchat_top_bar_title_callback() {
@@ -4335,12 +5986,16 @@
4335 5986 $top_bar_title = isset($this->options['top_bar_title']) ? esc_attr($this->options['top_bar_title']) : '';
4336 5987
4337 5988 // Render the input field
4338 5989 echo '<input type="text" id="top_bar_title" name="top_bar_title" value="' . $top_bar_title . '" />';
5990 +}
5991 +public function mxchat_ai_agent_text_callback() {
5992 + // Retrieve the current value of the AI agent text from saved options
5993 + $ai_agent_text = isset($this->options['ai_agent_text']) ? esc_attr($this->options['ai_agent_text']) : '';
5994 + // Render the input field
5995 + echo '<input type="text" id="ai_agent_text" name="ai_agent_text" value="' . $ai_agent_text . '" />';
5996 +}
4339 5997
4340 - // Add a description
4341 - echo '<p class="description">' . esc_html__('Enter the title text that will appear on the top bar of the chatbot.', 'mxchat') . '</p>';
4342 -}
4343 5998
4344 5999 public function enable_email_block_callback() {
4345 6000 // Load full plugin options array
4346 6001 $all_options = get_option('mxchat_options', []);
@@ -4357,13 +6012,10 @@
4357 6012 esc_attr($checked)
4358 6013 );
4359 6014 echo '<span class="slider"></span>';
4360 6015 echo '</label>';
4361 - 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>';
4362 6016 }
4363 6017
4364 -
4365 -
4366 6018 public function email_blocker_header_content_callback() {
4367 6019 // Load the entire 'mxchat_options' array
4368 6020 $all_options = get_option('mxchat_options', []);
4369 6021
@@ -4371,22 +6023,18 @@
4371 6023 $content = isset($all_options['email_blocker_header_content'])
4372 6024 ? $all_options['email_blocker_header_content']
4373 6025 : '';
4374 6026
4375 - // Render the textarea
6027 + // Render the textarea - IMPORTANT: name should be just "email_blocker_header_content"
4376 6028 echo '<textarea
4377 6029 id="email_blocker_header_content"
4378 6030 name="email_blocker_header_content"
4379 6031 rows="5"
4380 6032 cols="70"
6033 + data-setting="email_blocker_header_content"
4381 6034 >' . esc_textarea($content) . '</textarea>';
4382 -
4383 - echo '<p class="description">';
4384 - 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');
4385 - echo '</p>';
4386 6035 }
4387 6036
4388 -
4389 6037 public function email_blocker_button_text_callback() {
4390 6038 // Load the entire 'mxchat_options' array
4391 6039 $all_options = get_option('mxchat_options', []);
4392 6040
@@ -4396,34 +6044,54 @@
4396 6044 : '';
4397 6045
4398 6046 // Use esc_attr to safely render the existing text
4399 6047 echo '<input type="text" id="email_blocker_button_text" name="email_blocker_button_text" value="' . esc_attr($button_text) . '" style="width: 300px;" />';
6048 +}
4400 6049
4401 - echo '<p class="description">';
4402 - echo esc_html__('Enter the text you want on the submit button, e.g. "Start Chat".', 'mxchat');
4403 - echo '</p>';
6050 +//Enable name field callback
6051 +public function enable_name_field_callback() {
6052 + // Load full plugin options array
6053 + $all_options = get_option('mxchat_options', []);
6054 + // Get the value, default to 'off'
6055 + $enable_name_field = isset($all_options['enable_name_field']) ? $all_options['enable_name_field'] : 'off';
6056 + // Check if it's 'on'
6057 + $checked = ($enable_name_field === 'on') ? 'checked' : '';
6058 + echo '<label class="toggle-switch">';
6059 + echo sprintf(
6060 + '<input type="checkbox" id="enable_name_field" name="enable_name_field" value="on" %s />',
6061 + esc_attr($checked)
6062 + );
6063 + echo '<span class="slider"></span>';
6064 + echo '</label>';
4404 6065 }
6066 +//Name field placeholder callback
6067 +public function name_field_placeholder_callback() {
6068 + $all_options = get_option('mxchat_options', []);
6069 + $placeholder = isset($all_options['name_field_placeholder'])
6070 + ? $all_options['name_field_placeholder']
6071 + : esc_html__('Enter your name', 'mxchat');
4405 6072
6073 + echo '<input type="text" id="name_field_placeholder" name="name_field_placeholder" value="' . esc_attr($placeholder) . '" style="width: 300px;" />';
6074 +}
4406 6075
4407 6076
6077 +
4408 6078 public function mxchat_intro_message_callback() {
4409 6079 // Load the entire 'mxchat_options' array
4410 6080 $all_options = get_option('mxchat_options', []);
4411 -
4412 6081 // Retrieve the saved intro message or use the default
4413 6082 $default_message = __('Hello! How can I assist you today?', 'mxchat');
4414 6083 $saved_message = isset($all_options['intro_message']) ? $all_options['intro_message'] : $default_message;
4415 -
4416 - // Output the textarea with the saved value
6084 + // Output the textarea with the saved value without escaping HTML
4417 6085 ?>
4418 - <textarea id="intro_message" name="intro_message" rows="5" cols="50"><?php echo esc_textarea($saved_message); ?></textarea>
4419 - <p class="description">
4420 - <?php esc_html_e('Enter your message. Line breaks will be preserved.', 'mxchat'); ?>
6086 + <textarea id="intro_message" name="intro_message" rows="5" cols="50"><?php echo $saved_message; ?></textarea>
6087 + <p class="description" style="margin-top: 8px;">
6088 + <?php esc_html_e('Use {visitor_name} to personalize greetings when lead capture is enabled.', 'mxchat'); ?><br>
6089 + <code style="font-size: 12px;"><?php esc_html_e('Example: Hello {visitor_name}! How can I help you today?', 'mxchat'); ?></code>
4421 6090 </p>
4422 6091 <?php
4423 6092 }
4424 6093
4425 -
4426 6094 public function mxchat_input_copy_callback() {
4427 6095 // Load the entire 'mxchat_options' array
4428 6096 $all_options = get_option('mxchat_options', []);
4429 6097
@@ -4436,11 +6104,8 @@
4436 6104 '<input type="text" id="input_copy" name="input_copy" value="%s" placeholder="%s" />',
4437 6105 esc_attr($input_copy),
4438 6106 esc_attr__('How can I assist?', 'mxchat')
4439 6107 );
4440 -
4441 - // Output the description
4442 - echo '<p class="description">' . esc_html__('This is the placeholder text for the chat input field.', 'mxchat') . '</p>';
4443 6108 }
4444 6109
4445 6110
4446 6111
@@ -4872,12 +6537,25 @@
4872 6537 echo '</div>';
4873 6538 }
4874 6539
4875 6540 public function mxchat_append_to_body_callback() {
6541 + // Fetch fresh options to ensure we have the latest saved values
6542 + $options = get_option('mxchat_options', array());
6543 +
4876 6544 // Get value from options array, default to 'off'
4877 - $append_to_body = isset($this->options['append_to_body']) ? $this->options['append_to_body'] : 'off';
6545 + $append_to_body = isset($options['append_to_body']) ? $options['append_to_body'] : 'off';
4878 6546 $checked = ($append_to_body === 'on') ? 'checked' : '';
4879 6547
6548 + // Get post type visibility settings
6549 + $visibility_mode = isset($options['post_type_visibility_mode']) ? $options['post_type_visibility_mode'] : 'all';
6550 + $visibility_list = isset($options['post_type_visibility_list']) ? $options['post_type_visibility_list'] : array();
6551 + if (!is_array($visibility_list)) {
6552 + $visibility_list = array();
6553 + }
6554 +
6555 + echo '<div class="mxchat-autosave-section">';
6556 +
6557 + // Main toggle
4880 6558 echo '<label class="toggle-switch">';
4881 6559 echo sprintf(
4882 6560 '<input type="checkbox" id="append_to_body" name="append_to_body" value="on" %s />',
4883 6561 esc_attr($checked)
@@ -4883,16 +6561,101 @@
4883 6561 esc_attr($checked)
4884 6562 );
4885 6563 echo '<span class="slider"></span>';
4886 6564 echo '</label>';
4887 - echo '<p class="description">' .
4888 - esc_html__('Show chatbot automatically on all pages. When disabled, you can place the chatbot manually using shortcode [mxchat_chatbot floating="yes"].', 'mxchat') .
4889 - '</p>';
4890 6565
6566 + // Post Type Visibility Options (only visible when auto-display is ON)
6567 + $display_style = ($append_to_body === 'on') ? '' : 'display: none;';
6568 + echo '<div id="post-type-visibility-options" class="mxchat-sub-options" style="' . esc_attr($display_style) . '">';
6569 +
6570 + echo '<div class="mxchat-post-type-visibility-header">';
6571 + echo '<h4>' . esc_html__('Post Type Visibility', 'mxchat') . '</h4>';
6572 + echo '</div>';
6573 +
6574 + // Mode selector (radio buttons)
6575 + echo '<div class="mxchat-visibility-mode">';
6576 +
6577 + echo '<label class="mxchat-radio-label">';
6578 + echo '<input type="radio" name="post_type_visibility_mode" value="all" ' . checked($visibility_mode, 'all', false) . ' />';
6579 + echo '<span>' . esc_html__('Show on all post types', 'mxchat') . '</span>';
6580 + echo '</label>';
6581 +
6582 + echo '<label class="mxchat-radio-label">';
6583 + echo '<input type="radio" name="post_type_visibility_mode" value="include" ' . checked($visibility_mode, 'include', false) . ' />';
6584 + echo '<span>' . esc_html__('Only show on selected post types', 'mxchat') . '</span>';
6585 + echo '</label>';
6586 +
6587 + echo '<label class="mxchat-radio-label">';
6588 + echo '<input type="radio" name="post_type_visibility_mode" value="exclude" ' . checked($visibility_mode, 'exclude', false) . ' />';
6589 + echo '<span>' . esc_html__('Hide on selected post types', 'mxchat') . '</span>';
6590 + echo '</label>';
6591 +
6592 + echo '</div>';
6593 +
6594 + // Post type checkboxes (only visible when mode is include or exclude)
6595 + $list_display = ($visibility_mode !== 'all') ? '' : 'display: none;';
6596 + echo '<div id="post-type-list" class="mxchat-post-type-list" style="' . esc_attr($list_display) . '">';
6597 +
6598 + // Get all public post types
6599 + $post_types = get_post_types(array('public' => true), 'objects');
6600 +
6601 + foreach ($post_types as $post_type) {
6602 + // Skip attachments
6603 + if ($post_type->name === 'attachment') {
6604 + continue;
6605 + }
6606 +
6607 + $is_checked = in_array($post_type->name, $visibility_list) ? 'checked' : '';
6608 +
6609 + echo '<label class="mxchat-checkbox-label">';
6610 + echo '<input type="checkbox" name="post_type_visibility_list[]" value="' . esc_attr($post_type->name) . '" ' . $is_checked . ' />';
6611 + echo '<span>' . esc_html($post_type->label) . '</span>';
6612 + echo '</label>';
6613 + }
6614 +
6615 + echo '</div>'; // End post-type-list
6616 + echo '</div>'; // End post-type-visibility-options
6617 + echo '</div>'; // End mxchat-autosave-section
4891 6618 }
4892 6619
6620 +public function mxchat_frontend_debugger_callback() {
6621 + $show_debugger = isset($this->options['show_frontend_debugger']) ? $this->options['show_frontend_debugger'] : 'on';
6622 + $checked = ($show_debugger === 'on') ? 'checked' : '';
6623 + echo '<label class="toggle-switch">';
6624 + echo sprintf(
6625 + '<input type="checkbox" id="show_frontend_debugger" name="show_frontend_debugger" value="on" %s />',
6626 + esc_attr($checked)
6627 + );
6628 + echo '<span class="slider"></span>';
6629 + echo '</label>';
6630 +}
4893 6631
6632 +public function mxchat_contextual_awareness_callback() {
6633 + // Get value from options array, default to 'off'
6634 + $contextual_awareness = isset($this->options['contextual_awareness_toggle']) ? $this->options['contextual_awareness_toggle'] : 'off';
6635 + $checked = ($contextual_awareness === 'on') ? 'checked' : '';
6636 + echo '<label class="toggle-switch">';
6637 + echo sprintf(
6638 + '<input type="checkbox" id="contextual_awareness_toggle" name="contextual_awareness_toggle" value="on" %s />',
6639 + esc_attr($checked)
6640 + );
6641 + echo '<span class="slider"></span>';
6642 + echo '</label>';
6643 +}
4894 6644
6645 +public function mxchat_citation_links_toggle_callback() {
6646 + // Get value from options array, default to 'on' (enabled by default)
6647 + $citation_links = isset($this->options['citation_links_toggle']) ? $this->options['citation_links_toggle'] : 'on';
6648 + $checked = ($citation_links === 'on') ? 'checked' : '';
6649 + echo '<label class="toggle-switch">';
6650 + echo sprintf(
6651 + '<input type="checkbox" id="citation_links_toggle" name="citation_links_toggle" value="on" %s />',
6652 + esc_attr($checked)
6653 + );
6654 + echo '<span class="slider"></span>';
6655 + echo '</label>';
6656 +}
6657 +
4895 6658 public function mxchat_privacy_toggle_callback() {
4896 6659 // Load from mxchat_options array
4897 6660 $options = get_option('mxchat_options', []);
4898 6661
@@ -4912,9 +6675,8 @@
4912 6675 esc_attr($checked)
4913 6676 );
4914 6677 echo '<span class="slider"></span>';
4915 6678 echo '</label>';
4916 - echo '<p class="description">' . esc_html__('Enable this option to display a privacy notice below the chat widget.', 'mxchat') . '</p>';
4917 6679
4918 6680 // Output the custom text input field
4919 6681 echo sprintf(
4920 6682 '<textarea id="privacy_text" name="privacy_text" rows="5" cols="50" class="regular-text">%s</textarea>',
@@ -4919,9 +6681,8 @@
4919 6681 echo sprintf(
4920 6682 '<textarea id="privacy_text" name="privacy_text" rows="5" cols="50" class="regular-text">%s</textarea>',
4921 6683 esc_textarea($privacy_text)
4922 6684 );
4923 - echo '<p class="description">' . esc_html__('Enter the privacy policy text. You can include HTML links.', 'mxchat') . '</p>';
4924 6685 }
4925 6686
4926 6687
4927 6688 public function mxchat_complianz_toggle_callback() {
@@ -4939,10 +6700,8 @@
4939 6700 esc_attr($checked)
4940 6701 );
4941 6702 echo '<span class="slider"></span>';
4942 6703 echo '</label>';
4943 -
4944 - echo '<p class="description">' . esc_html__('Enable this option to apply Complianz consent logic to the chatbot (must have Complianz Plugin).', 'mxchat') . '</p>';
4945 6704 }
4946 6705
4947 6706 public function mxchat_link_target_toggle_callback() {
4948 6707 // Load from mxchat_options array
@@ -4959,9 +6718,8 @@
4959 6718 esc_attr($checked)
4960 6719 );
4961 6720 echo '<span class="slider"></span>';
4962 6721 echo '</label>';
4963 - echo '<p class="description">' . esc_html__('Enable to open links in a new tab (default is to open in the same tab).', 'mxchat') . '</p>';
4964 6722 }
4965 6723
4966 6724 public function mxchat_chat_persistence_toggle_callback() {
4967 6725 // Load from mxchat_options array
@@ -4978,10 +6736,8 @@
4978 6736 esc_attr($checked)
4979 6737 );
4980 6738 echo '<span class="slider"></span>';
4981 6739 echo '</label>';
4982 -
4983 - echo '<p class="description">' . esc_html__('Enable to keep chat history when users navigate tabs or return to the site within 24 hours.', 'mxchat') . '</p>';
4984 6740 }
4985 6741
4986 6742 public function mxchat_popular_question_1_callback() {
4987 6743 // Load the full plugin options array
@@ -4995,11 +6751,8 @@
4995 6751 '<input type="text" id="popular_question_1" name="popular_question_1" value="%s" placeholder="%s" class="regular-text" />',
4996 6752 esc_attr($popular_question_1),
4997 6753 esc_attr__('Enter Quick Question 1', 'mxchat')
4998 6754 );
4999 -
5000 - // Add a description for the field
5001 - echo '<p class="description">' . esc_html__('This will be the first Quick Question in the chatbot, displayed above the input field.', 'mxchat') . '</p>';
5002 6755 }
5003 6756
5004 6757
5005 6758 public function mxchat_popular_question_2_callback() {
@@ -5014,11 +6767,8 @@
5014 6767 '<input type="text" id="popular_question_2" name="popular_question_2" value="%s" placeholder="%s" class="regular-text" />',
5015 6768 esc_attr($popular_question_2),
5016 6769 esc_attr__('Enter Quick Question 2', 'mxchat')
5017 6770 );
5018 -
5019 - // Add a description for the field
5020 - echo '<p class="description">' . esc_html__('This will be the second Quick Question in the chatbot.', 'mxchat') . '</p>';
5021 6771 }
5022 6772
5023 6773
5024 6774 public function mxchat_popular_question_3_callback() {
@@ -5033,11 +6783,8 @@
5033 6783 '<input type="text" id="popular_question_3" name="popular_question_3" value="%s" placeholder="%s" class="regular-text" />',
5034 6784 esc_attr($popular_question_3),
5035 6785 esc_attr(__('Enter Quick Question 3', 'mxchat'))
5036 6786 );
5037 -
5038 - // Add a description for the field
5039 - echo '<p class="description">' . esc_html__('This will be the third Quick Question in the chatbot.', 'mxchat') . '</p>';
5040 6787 }
5041 6788
5042 6789 public function mxchat_additional_popular_questions_callback() {
5043 6790 $options = get_option('mxchat_options', []);
@@ -5086,23 +6833,23 @@
5086 6833 '<button type="button" class="button mxchat-add-question" aria-label="%s">%s</button>',
5087 6834 esc_attr(__('Add question', 'mxchat')),
5088 6835 esc_html__('Add Question', 'mxchat')
5089 6836 );
5090 - echo '<p class="description">' . esc_html__('Add as many Quick Questions as you need.', 'mxchat') . '</p>';
5091 - echo '</div>';
5092 6837 }
5093 6838
5094 6839 public function mxchat_brave_api_key_callback() {
5095 6840 $brave_api_key = isset($this->options['brave_api_key']) ? esc_attr($this->options['brave_api_key']) : '';
6841 + $nonce = wp_create_nonce('mxchat_autosave_nonce');
5096 6842
5097 6843 echo '<div class="api-key-wrapper">';
5098 6844 echo sprintf(
5099 - '<input type="password" id="brave_api_key" name="brave_api_key" value="%s" class="regular-text" />',
5100 - $brave_api_key
6845 + '<input type="password" id="brave_api_key" name="brave_api_key" value="%s" class="regular-text mxchat-autosave-field" data-nonce="%s" />',
6846 + $brave_api_key,
6847 + $nonce
5101 6848 );
5102 6849 echo '<button type="button" id="toggleBraveApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5103 6850 echo '</div>';
5104 - echo '<p class="description">' . __('Enter your Brave Search API Key here. (See FAQ for details)', 'mxchat') . '</p>';
6851 + echo '<p class="description">' . __('Required for Brave Search integration. Get your API key from Brave Search API.', 'mxchat') . '</p>';
5105 6852 }
5106 6853
5107 6854 public function mxchat_brave_image_count_callback() {
5108 6855 $brave_image_count = isset($this->options['brave_image_count'])
@@ -5107,14 +6854,18 @@
5107 6854 public function mxchat_brave_image_count_callback() {
5108 6855 $brave_image_count = isset($this->options['brave_image_count'])
5109 6856 ? intval($this->options['brave_image_count'])
5110 6857 : 4;
6858 + $nonce = wp_create_nonce('mxchat_autosave_nonce');
5111 6859
6860 + echo '<div class="mxchat-field-wrapper">';
5112 6861 echo sprintf(
5113 6862 '<input type="number" id="brave_image_count" name="brave_image_count"
5114 - value="%d" min="1" max="6" class="small-text" />',
5115 - $brave_image_count
6863 + value="%d" min="1" max="6" class="small-text mxchat-autosave-field" data-nonce="%s" />',
6864 + $brave_image_count,
6865 + $nonce
5116 6866 );
6867 + echo '</div>';
5117 6868 echo '<p class="description">' . __('Select the number of images to return (1-6).', 'mxchat') . '</p>';
5118 6869 }
5119 6870
5120 6871 public function mxchat_brave_safe_search_callback() {
@@ -5120,10 +6871,12 @@
5120 6871 public function mxchat_brave_safe_search_callback() {
5121 6872 $brave_safe_search = isset($this->options['brave_safe_search'])
5122 6873 ? esc_attr($this->options['brave_safe_search'])
5123 6874 : 'strict';
6875 + $nonce = wp_create_nonce('mxchat_autosave_nonce');
5124 6876
5125 - echo '<select id="brave_safe_search" name="brave_safe_search">';
6877 + echo '<div class="mxchat-field-wrapper">';
6878 + echo '<select id="brave_safe_search" name="brave_safe_search" class="mxchat-autosave-field" data-nonce="' . $nonce . '">';
5126 6879 echo sprintf(
5127 6880 '<option value="strict" %s>%s</option>',
5128 6881 selected($brave_safe_search, 'strict', false),
5129 6882 __('Strict', 'mxchat')
@@ -5133,8 +6886,9 @@
5133 6886 selected($brave_safe_search, 'off', false),
5134 6887 __('Off', 'mxchat')
5135 6888 );
5136 6889 echo '</select>';
6890 + echo '</div>';
5137 6891 echo '<p class="description">' .
5138 6892 esc_html__('Set the Safe Search level for image searches. Brave Search only supports "Strict" and "Off" options.', 'mxchat') .
5139 6893 '</p>';
5140 6894 }
@@ -5142,14 +6896,18 @@
5142 6896 public function mxchat_brave_news_count_callback() {
5143 6897 $brave_news_count = isset($this->options['brave_news_count'])
5144 6898 ? intval($this->options['brave_news_count'])
5145 6899 : 3;
6900 + $nonce = wp_create_nonce('mxchat_autosave_nonce');
5146 6901
6902 + echo '<div class="mxchat-field-wrapper">';
5147 6903 echo sprintf(
5148 6904 '<input type="number" id="brave_news_count" name="brave_news_count"
5149 - value="%d" min="1" max="10" class="small-text" />',
5150 - $brave_news_count
6905 + value="%d" min="1" max="10" class="small-text mxchat-autosave-field" data-nonce="%s" />',
6906 + $brave_news_count,
6907 + $nonce
5151 6908 );
6909 + echo '</div>';
5152 6910 echo '<p class="description">' . esc_html__('Select the number of news articles to retrieve (1-10).', 'mxchat') . '</p>';
5153 6911 }
5154 6912
5155 6913 public function mxchat_brave_country_callback() {
@@ -5155,14 +6913,18 @@
5155 6913 public function mxchat_brave_country_callback() {
5156 6914 $brave_country = isset($this->options['brave_country'])
5157 6915 ? esc_attr($this->options['brave_country'])
5158 6916 : 'us';
6917 + $nonce = wp_create_nonce('mxchat_autosave_nonce');
5159 6918
6919 + echo '<div class="mxchat-field-wrapper">';
5160 6920 echo sprintf(
5161 6921 '<input type="text" id="brave_country" name="brave_country"
5162 - value="%s" maxlength="2" class="small-text" />',
5163 - $brave_country
6922 + value="%s" maxlength="2" class="small-text mxchat-autosave-field" data-nonce="%s" />',
6923 + $brave_country,
6924 + $nonce
5164 6925 );
6926 + echo '</div>';
5165 6927 echo '<p class="description">' . esc_html__('Enter the country code (e.g., "us" for United States).', 'mxchat') . '</p>';
5166 6928 }
5167 6929
5168 6930 public function mxchat_brave_language_callback() {
@@ -5168,14 +6930,18 @@
5168 6930 public function mxchat_brave_language_callback() {
5169 6931 $brave_language = isset($this->options['brave_language'])
5170 6932 ? esc_attr($this->options['brave_language'])
5171 6933 : 'en';
6934 + $nonce = wp_create_nonce('mxchat_autosave_nonce');
5172 6935
6936 + echo '<div class="mxchat-field-wrapper">';
5173 6937 echo sprintf(
5174 6938 '<input type="text" id="brave_language" name="brave_language"
5175 - value="%s" maxlength="2" class="small-text" />',
5176 - $brave_language
6939 + value="%s" maxlength="2" class="small-text mxchat-autosave-field" data-nonce="%s" />',
6940 + $brave_language,
6941 + $nonce
5177 6942 );
6943 + echo '</div>';
5178 6944 echo '<p class="description">' . esc_html__('Enter the language code (e.g., "en" for English).', 'mxchat') . '</p>';
5179 6945 }
5180 6946
5181 6947
@@ -5187,141 +6953,118 @@
5187 6953 echo '<p>' . esc_html__('Configure the intent settings for the Chat with PDF feature.', 'mxchat') . '</p>';
5188 6954 }
5189 6955
5190 6956 public function mxchat_chat_toolbar_toggle_callback() {
5191 - // Get chat toolbar toggle value with fallback
5192 - $chat_toolbar_toggle = isset($this->options['chat_toolbar_toggle']) ? $this->options['chat_toolbar_toggle'] : 'off';
5193 - $checked = ($chat_toolbar_toggle === 'on') ? 'checked' : '';
6957 + // Get chat toolbar toggle value with fallback
6958 + $chat_toolbar_toggle = isset($this->options['chat_toolbar_toggle']) ? $this->options['chat_toolbar_toggle'] : 'off';
6959 + $checked = ($chat_toolbar_toggle === 'on') ? 'checked' : '';
5194 6960
5195 - // Check if the plugin is activated (paid feature)
5196 - $disabled = $this->is_activated ? '' : 'disabled';
5197 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6961 + // Output the toggle switch
6962 + echo '<label class="toggle-switch">';
6963 + echo sprintf(
6964 + '<input type="checkbox" id="chat_toolbar_toggle" name="chat_toolbar_toggle" value="on" %s />',
6965 + esc_attr($checked)
6966 + );
6967 + echo '<span class="slider"></span>';
6968 + echo '</label>';
5198 6969
5199 - echo '<div class="' . esc_attr($class) . '">';
6970 + 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>';
6971 +}
5200 6972
5201 - // Output the toggle switch
5202 - echo '<label class="toggle-switch">';
5203 - echo sprintf(
5204 - '<input type="checkbox" id="chat_toolbar_toggle" name="chat_toolbar_toggle" value="on" %s %s />',
5205 - esc_attr($checked),
5206 - esc_attr($disabled)
5207 - );
5208 - echo '<span class="slider"></span>';
5209 - echo '</label>';
6973 +/**
6974 + * Callback for PDF upload button toggle setting
6975 + */
6976 +public function mxchat_show_pdf_upload_button_callback() {
6977 + // Get toggle value with fallback
6978 + $show_pdf_button = isset($this->options['show_pdf_upload_button']) ? $this->options['show_pdf_upload_button'] : 'on';
6979 + $checked = ($show_pdf_button === 'on') ? 'checked' : '';
5210 6980
5211 - // Pro feature overlay
5212 - if (!$this->is_activated) {
5213 - echo '<div class="pro-feature-overlay">';
5214 - echo '<a href="https://mxchat.ai/" target="_blank">';
5215 - echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
5216 - echo '</a>';
5217 - echo '</div>';
5218 - }
6981 + // Output the toggle switch
6982 + echo '<label class="toggle-switch">';
6983 + echo sprintf(
6984 + '<input type="checkbox" id="show_pdf_upload_button" name="show_pdf_upload_button" value="on" %s />',
6985 + esc_attr($checked)
6986 + );
6987 + echo '<span class="slider"></span>';
6988 + echo '</label>';
5219 6989
5220 - echo '</div>';
5221 - 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>';
6990 + echo '<p class="description">' . esc_html__('Enable to show the PDF upload button in the chatbot toolbar. Disable to hide it.', 'mxchat') . '</p>';
5222 6991 }
5223 6992
6993 +/**
6994 + * Callback for Word upload button toggle setting
6995 + */
6996 +public function mxchat_show_word_upload_button_callback() {
6997 + // Get toggle value with fallback
6998 + $show_word_button = isset($this->options['show_word_upload_button']) ? $this->options['show_word_upload_button'] : 'on';
6999 + $checked = ($show_word_button === 'on') ? 'checked' : '';
5224 7000
7001 + // Output the toggle switch
7002 + echo '<label class="toggle-switch">';
7003 + echo sprintf(
7004 + '<input type="checkbox" id="show_word_upload_button" name="show_word_upload_button" value="on" %s />',
7005 + esc_attr($checked)
7006 + );
7007 + echo '<span class="slider"></span>';
7008 + echo '</label>';
7009 +
7010 + echo '<p class="description">' . esc_html__('Enable to show the Word document upload button in the chatbot toolbar. Disable to hide it.', 'mxchat') . '</p>';
7011 +}
7012 +
5225 7013 public function mxchat_pdf_intent_trigger_text_callback() {
5226 - $disabled = $this->is_activated ? '' : 'disabled';
5227 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
5228 7014 $default_text = __("Please provide the URL to the PDF you'd like to discuss.", 'mxchat');
5229 7015
5230 - echo '<div class="' . esc_attr($class) . '">';
5231 7016 echo sprintf(
5232 7017 '<textarea id="pdf_intent_trigger_text"
5233 7018 name="pdf_intent_trigger_text"
5234 7019 rows="3"
5235 7020 cols="50"
5236 - placeholder="%s"
5237 - %s>%s</textarea>',
7021 + placeholder="%s">%s</textarea>',
5238 7022 esc_attr__('Enter trigger text', 'mxchat'),
5239 - esc_attr($disabled),
5240 7023 isset($this->options['pdf_intent_trigger_text'])
5241 7024 ? esc_textarea($this->options['pdf_intent_trigger_text'])
5242 7025 : esc_textarea($default_text)
5243 7026 );
5244 7027 echo '<p class="description">' . esc_html__('Text displayed when the intent is triggered.', 'mxchat') . '</p>';
5245 -
5246 - if (!$this->is_activated) {
5247 - echo '<div class="pro-feature-overlay">';
5248 - echo '<a href="https://mxchat.ai/" target="_blank">';
5249 - echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
5250 - echo '</a>';
5251 - echo '</div>';
5252 - }
5253 - echo '</div>';
5254 7028 }
5255 7029
5256 7030 public function mxchat_pdf_intent_success_text_callback() {
5257 - $disabled = $this->is_activated ? '' : 'disabled';
5258 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
5259 7031 $default_text = __("I've processed the PDF. What questions do you have about it?", 'mxchat');
5260 7032
5261 - echo '<div class="' . esc_attr($class) . '">';
5262 7033 echo sprintf(
5263 7034 '<textarea id="pdf_intent_success_text"
5264 7035 name="pdf_intent_success_text"
5265 7036 rows="3"
5266 7037 cols="50"
5267 - placeholder="%s"
5268 - %s>%s</textarea>',
7038 + placeholder="%s">%s</textarea>',
5269 7039 esc_attr__('Enter success text', 'mxchat'),
5270 - esc_attr($disabled),
5271 7040 isset($this->options['pdf_intent_success_text'])
5272 7041 ? esc_textarea($this->options['pdf_intent_success_text'])
5273 7042 : esc_textarea($default_text)
5274 7043 );
5275 7044 echo '<p class="description">' . esc_html__('Text displayed when the intent is successful.', 'mxchat') . '</p>';
5276 -
5277 - if (!$this->is_activated) {
5278 - echo '<div class="pro-feature-overlay">';
5279 - echo '<a href="https://mxchat.ai/" target="_blank">';
5280 - echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
5281 - echo '</a>';
5282 - echo '</div>';
5283 - }
5284 - echo '</div>';
5285 7045 }
5286 7046
5287 7047 public function mxchat_pdf_intent_error_text_callback() {
5288 - $disabled = $this->is_activated ? '' : 'disabled';
5289 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
5290 7048 $default_text = __("Sorry, I couldn't process the PDF. Please ensure it's a valid file.", 'mxchat');
5291 7049
5292 - echo '<div class="' . esc_attr($class) . '">';
5293 7050 echo sprintf(
5294 7051 '<textarea id="pdf_intent_error_text"
5295 7052 name="pdf_intent_error_text"
5296 7053 rows="3"
5297 7054 cols="50"
5298 - placeholder="%s"
5299 - %s>%s</textarea>',
7055 + placeholder="%s">%s</textarea>',
5300 7056 esc_attr__('Enter error text', 'mxchat'),
5301 - esc_attr($disabled),
5302 7057 isset($this->options['pdf_intent_error_text'])
5303 7058 ? esc_textarea($this->options['pdf_intent_error_text'])
5304 7059 : esc_textarea($default_text)
5305 7060 );
5306 7061 echo '<p class="description">' . esc_html__('Text displayed when an error occurs during the intent.', 'mxchat') . '</p>';
5307 -
5308 - if (!$this->is_activated) {
5309 - echo '<div class="pro-feature-overlay">';
5310 - echo '<a href="https://mxchat.ai/" target="_blank">';
5311 - echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
5312 - echo '</a>';
5313 - echo '</div>';
5314 - }
5315 - echo '</div>';
5316 7062 }
5317 7063
5318 7064 public function mxchat_pdf_max_pages_callback() {
5319 - $disabled = $this->is_activated ? '' : 'disabled';
5320 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
5321 7065 $max_pages = isset($this->options['pdf_max_pages']) ? intval($this->options['pdf_max_pages']) : 69;
5322 7066
5323 - echo '<div class="' . esc_attr($class) . '">';
5324 7067 echo sprintf(
5325 7068 '<input type="range"
5326 7069 id="pdf_max_pages"
5327 7070 name="pdf_max_pages"
@@ -5327,37 +7070,24 @@
5327 7070 name="pdf_max_pages"
5328 7071 min="1"
5329 7072 max="69"
5330 7073 value="%d"
5331 - %s
5332 7074 class="range-slider" />',
5333 - esc_attr($max_pages),
5334 - esc_attr($disabled)
7075 + esc_attr($max_pages)
5335 7076 );
5336 7077 echo '<span id="pdf_max_pages_output">' . esc_html($max_pages) . '</span>';
5337 7078 echo '<p class="description">' . esc_html__('Set the maximum number of document pages users can upload for processing. (1-69 pages)', 'mxchat') . '</p>';
5338 -
5339 - if (!$this->is_activated) {
5340 - echo '<div class="pro-feature-overlay">';
5341 - echo '<a href="https://mxchat.ai/" target="_blank">';
5342 - echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
5343 - echo '</a>';
5344 - echo '</div>';
5345 - }
5346 - echo '</div>';
5347 7079 }
5348 7080
5349 7081 public function mxchat_live_agent_status_callback() {
5350 - $disabled = $this->is_activated ? '' : 'disabled';
5351 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
5352 - $status = isset($this->options['live_agent_status']) ? $this->options['live_agent_status'] : 'off';
7082 + // Always get fresh options instead of using cached $this->options
7083 + $fresh_options = get_option('mxchat_options');
7084 + $status = isset($fresh_options['live_agent_status']) ? $fresh_options['live_agent_status'] : 'off';
5353 7085
5354 - echo '<div class="' . esc_attr($class) . '">';
5355 7086 echo '<label class="toggle-switch">';
5356 7087 echo sprintf(
5357 - '<input type="checkbox" id="live_agent_status" name="live_agent_status" value="on" %s %s />',
5358 - checked($status, 'on', false),
5359 - esc_attr($disabled)
7088 + '<input type="checkbox" id="live_agent_status" name="live_agent_status" value="on" %s />',
7089 + checked($status, 'on', false)
5360 7090 );
5361 7091 echo '<span class="slider"></span>';
5362 7092 echo '</label>';
5363 7093 echo '<label for="live_agent_status" class="mxchat-status-label">';
@@ -5362,98 +7092,167 @@
5362 7092 echo '</label>';
5363 7093 echo '<label for="live_agent_status" class="mxchat-status-label">';
5364 7094 echo '<span class="status-text">' . ($status === 'on' ? esc_html__('Online', 'mxchat') : esc_html__('Offline', 'mxchat')) . '</span>';
5365 7095 echo '</label>';
5366 -
5367 - if (!$this->is_activated) {
5368 - echo '<div class="pro-feature-overlay">';
5369 - 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>';
5370 - echo '</div>';
5371 - }
5372 - echo '</div>';
5373 7096 }
5374 7097
5375 -
5376 -// Away Message Callback
5377 7098 public function mxchat_live_agent_away_message_callback() {
5378 - $disabled = $this->is_activated ? '' : 'disabled';
5379 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
5380 7099 $message = isset($this->options['live_agent_away_message'])
5381 7100 ? $this->options['live_agent_away_message']
5382 7101 : __('Sorry, live agents are currently unavailable. I can continue helping you as an AI assistant.', 'mxchat');
5383 7102
5384 - echo '<div class="' . esc_attr($class) . '">';
5385 7103 printf(
5386 - '<textarea id="live_agent_away_message" name="live_agent_away_message" rows="3" cols="50" %s>%s</textarea>',
5387 - esc_attr($disabled),
7104 + '<textarea id="live_agent_away_message" name="live_agent_away_message" rows="3" cols="50">%s</textarea>',
5388 7105 esc_textarea($message)
5389 7106 );
5390 7107 echo '<p class="description">' . esc_html__('Message shown when live agents are offline.', 'mxchat') . '</p>';
5391 -
5392 - if (!$this->is_activated) {
5393 - echo '<div class="pro-feature-overlay">';
5394 - 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>';
5395 - echo '</div>';
5396 - }
5397 - echo '</div>';
5398 7108 }
5399 7109
5400 7110 public function mxchat_live_agent_notification_message_callback() {
5401 - $disabled = $this->is_activated ? '' : 'disabled';
5402 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
5403 7111 $message = isset($this->options['live_agent_notification_message'])
5404 7112 ? $this->options['live_agent_notification_message']
5405 7113 : __('Live agent has been notified.', 'mxchat');
5406 7114
5407 - echo '<div class="' . esc_attr($class) . '">';
5408 7115 printf(
5409 - '<textarea id="live_agent_notification_message" name="live_agent_notification_message" rows="3" cols="50" %s>%s</textarea>',
5410 - esc_attr($disabled),
7116 + '<textarea id="live_agent_notification_message" name="live_agent_notification_message" rows="3" cols="50">%s</textarea>',
5411 7117 esc_textarea($message)
5412 7118 );
5413 7119 echo '<p class="description">' . esc_html__('Message shown when live transfer activated.', 'mxchat') . '</p>';
5414 -
5415 - if (!$this->is_activated) {
5416 - echo '<div class="pro-feature-overlay">';
5417 - 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>';
5418 - echo '</div>';
5419 - }
5420 - echo '</div>';
5421 7120 }
5422 7121
5423 7122 public function mxchat_live_agent_webhook_url_callback() {
5424 - $disabled = $this->is_activated ? '' : 'disabled';
5425 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
5426 7123 $webhook_url = isset($this->options['live_agent_webhook_url'])
5427 7124 ? esc_url($this->options['live_agent_webhook_url'])
5428 7125 : esc_url(get_option('live_agent_webhook_url', ''));
5429 7126
5430 - echo '<div class="' . esc_attr($class) . '">';
5431 7127 printf(
5432 - '<input type="password" id="live_agent_webhook_url" name="live_agent_webhook_url" value="%s" class="regular-text" %s />',
5433 - $webhook_url,
5434 - esc_attr($disabled)
7128 + '<input type="password" id="live_agent_webhook_url" name="live_agent_webhook_url" value="%s" class="regular-text" />',
7129 + $webhook_url
5435 7130 );
5436 7131 echo '<button type="button" id="toggleWebhookUrlVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5437 7132 echo '<p class="description">' . esc_html__('Enter your Slack webhook URL for live agent notifications.', 'mxchat') . '</p>';
7133 +}
5438 7134
5439 - if (!$this->is_activated) {
5440 - echo '<div class="pro-feature-overlay">';
5441 - 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>';
5442 - echo '</div>';
7135 +public function mxchat_live_agent_secret_key_callback() {
7136 + printf(
7137 + '<input type="password" id="live_agent_secret_key" name="live_agent_secret_key" value="%s" class="regular-text" />',
7138 + isset($this->options['live_agent_secret_key']) ? esc_attr($this->options['live_agent_secret_key']) : ''
7139 + );
7140 + echo '<button type="button" id="toggleSecretKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
7141 + echo '<p class="description">' . esc_html__('Secret key for validating Slack requests. Keep this secure.', 'mxchat') . '</p>';
7142 +}
7143 +
7144 +public function mxchat_live_agent_bot_token_callback() {
7145 + printf(
7146 + '<input type="password" id="live_agent_bot_token" name="live_agent_bot_token" value="%s" class="regular-text" />',
7147 + isset($this->options['live_agent_bot_token']) ? esc_attr($this->options['live_agent_bot_token']) : ''
7148 + );
7149 + echo '<button type="button" id="toggleBotTokenVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
7150 + echo '<p class="description">' . esc_html__('Your Slack Bot OAuth Token (starts with xoxb-). Keep this secure.', 'mxchat') . '</p>';
7151 +}
7152 +
7153 +public function mxchat_live_agent_user_ids_callback() {
7154 + $user_ids = isset($this->options['live_agent_user_ids'])
7155 + ? esc_textarea($this->options['live_agent_user_ids'])
7156 + : '';
7157 +
7158 + printf(
7159 + '<textarea id="live_agent_user_ids" name="live_agent_user_ids" rows="4" class="large-text">%s</textarea>',
7160 + $user_ids
7161 + );
7162 + 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>';
7163 +}
7164 +
7165 +/**
7166 + * Telegram Integration Callbacks
7167 + */
7168 +public function mxchat_telegram_section_callback() {
7169 + echo '<p>' . esc_html__('Configure Telegram integration for live agent support.', 'mxchat') . '</p>';
7170 +}
7171 +
7172 +public function mxchat_telegram_status_callback() {
7173 + $fresh_options = get_option('mxchat_options');
7174 + $status = isset($fresh_options['telegram_status']) ? $fresh_options['telegram_status'] : 'off';
7175 +
7176 + echo '<label class="toggle-switch">';
7177 + echo sprintf(
7178 + '<input type="checkbox" id="telegram_status" name="telegram_status" value="on" %s />',
7179 + checked($status, 'on', false)
7180 + );
7181 + echo '<span class="slider"></span>';
7182 + echo '</label>';
7183 + echo '<label for="telegram_status" class="mxchat-status-label">';
7184 + echo '<span class="status-text">' . ($status === 'on' ? esc_html__('Online', 'mxchat') : esc_html__('Offline', 'mxchat')) . '</span>';
7185 + echo '</label>';
7186 +}
7187 +
7188 +public function mxchat_telegram_notification_message_callback() {
7189 + $message = isset($this->options['telegram_notification_message'])
7190 + ? $this->options['telegram_notification_message']
7191 + : __("I've notified a support agent. Please allow a moment for them to respond.", 'mxchat');
7192 +
7193 + printf(
7194 + '<textarea id="telegram_notification_message" name="telegram_notification_message" rows="3" cols="50">%s</textarea>',
7195 + esc_textarea($message)
7196 + );
7197 + echo '<p class="description">' . esc_html__('Message shown when live transfer activated.', 'mxchat') . '</p>';
7198 +}
7199 +
7200 +public function mxchat_telegram_away_message_callback() {
7201 + $message = isset($this->options['telegram_away_message'])
7202 + ? $this->options['telegram_away_message']
7203 + : __('Sorry, live agents are currently unavailable. I can continue helping you as an AI assistant.', 'mxchat');
7204 +
7205 + printf(
7206 + '<textarea id="telegram_away_message" name="telegram_away_message" rows="3" cols="50">%s</textarea>',
7207 + esc_textarea($message)
7208 + );
7209 + echo '<p class="description">' . esc_html__('Message shown when live agents are offline.', 'mxchat') . '</p>';
7210 +}
7211 +
7212 +public function mxchat_telegram_bot_token_callback() {
7213 + printf(
7214 + '<input type="password" id="telegram_bot_token" name="telegram_bot_token" value="%s" class="regular-text" />',
7215 + isset($this->options['telegram_bot_token']) ? esc_attr($this->options['telegram_bot_token']) : ''
7216 + );
7217 + 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>';
7218 + echo '<p class="description">' . esc_html__('Your Telegram Bot Token from @BotFather (e.g., 123456789:ABCdefGhIJKlmNoPQRsTUVwxyZ).', 'mxchat') . '</p>';
7219 +}
7220 +
7221 +public function mxchat_telegram_group_id_callback() {
7222 + printf(
7223 + '<input type="text" id="telegram_group_id" name="telegram_group_id" value="%s" class="regular-text" />',
7224 + isset($this->options['telegram_group_id']) ? esc_attr($this->options['telegram_group_id']) : ''
7225 + );
7226 + echo '<p class="description">' . esc_html__('Your Telegram supergroup ID (starts with -100). The group must have forum topics enabled.', 'mxchat') . '</p>';
7227 +}
7228 +
7229 +public function mxchat_telegram_webhook_secret_callback() {
7230 + $secret = isset($this->options['telegram_webhook_secret']) ? $this->options['telegram_webhook_secret'] : '';
7231 +
7232 + // Auto-generate secret if empty
7233 + if (empty($secret)) {
7234 + $secret = wp_generate_password(64, false, false);
7235 + $options = get_option('mxchat_options', []);
7236 + $options['telegram_webhook_secret'] = $secret;
7237 + update_option('mxchat_options', $options);
7238 + $this->options['telegram_webhook_secret'] = $secret;
5443 7239 }
5444 - echo '</div>';
7240 +
7241 + printf(
7242 + '<input type="password" id="telegram_webhook_secret" name="telegram_webhook_secret" value="%s" class="regular-text" />',
7243 + esc_attr($secret)
7244 + );
7245 + 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>';
7246 + 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>';
5445 7247 }
5446 7248
5447 -
5448 7249 public function mxchat_similarity_threshold_callback() {
5449 7250 // Load from mxchat_options array
5450 7251 $options = get_option('mxchat_options', []);
5451 7252
5452 - // Get value with backwards compatibility
5453 - $threshold = isset($options['similarity_threshold'])
5454 - ? $options['similarity_threshold']
5455 - : get_option('mxchat_similarity_threshold', 80);
7253 + // Get value from options array with default of 35
7254 + $threshold = isset($options['similarity_threshold']) ? $options['similarity_threshold'] : 35;
5456 7255
5457 7256 echo '<div class="slider-container">';
5458 7257 echo sprintf(
5459 7258 '<input type="range"
@@ -5458,9 +7257,9 @@
5458 7257 echo sprintf(
5459 7258 '<input type="range"
5460 7259 id="similarity_threshold"
5461 7260 name="similarity_threshold"
5462 - min="70"
7261 + min="20"
5463 7262 max="85"
5464 7263 step="1"
5465 7264 value="%s"
5466 7265 class="range-slider" />',
@@ -5470,200 +7269,282 @@
5470 7269 '<span id="threshold_value" class="range-value">%s</span>',
5471 7270 esc_html($threshold)
5472 7271 );
5473 7272 echo '</div>';
5474 -
5475 - echo '<p class="description">';
5476 - 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');
5477 - echo '</p>';
5478 7273 }
5479 7274
7275 +public function mxchat_rag_sources_limit_callback() {
7276 + // Load from mxchat_options array
7277 + $options = get_option('mxchat_options', []);
5480 7278
7279 + // Get value from options array with default of 6
7280 + $rag_sources_limit = isset($options['rag_sources_limit']) ? intval($options['rag_sources_limit']) : 6;
5481 7281
5482 -public function mxchat_live_agent_secret_key_callback() {
5483 - $disabled = $this->is_activated ? '' : 'disabled';
5484 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
5485 -
5486 - echo '<div class="' . esc_attr($class) . '">';
5487 - printf(
5488 - '<input type="password" id="live_agent_secret_key" name="live_agent_secret_key" value="%s" class="regular-text" %s />',
5489 - isset($this->options['live_agent_secret_key']) ? esc_attr($this->options['live_agent_secret_key']) : '',
5490 - esc_attr($disabled)
7282 + echo '<div class="slider-container">';
7283 + echo sprintf(
7284 + '<input type="range"
7285 + id="rag_sources_limit"
7286 + name="rag_sources_limit"
7287 + min="3"
7288 + max="10"
7289 + step="1"
7290 + value="%s"
7291 + class="range-slider" />',
7292 + esc_attr($rag_sources_limit)
5491 7293 );
5492 - echo '<button type="button" id="toggleSecretKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5493 - echo '<p class="description">' . esc_html__('Secret key for validating Slack requests. Keep this secure.', 'mxchat') . '</p>';
5494 -
5495 - if (!$this->is_activated) {
5496 - echo '<div class="pro-feature-overlay">';
5497 - 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>';
5498 - echo '</div>';
5499 - }
7294 + echo sprintf(
7295 + '<span id="rag_sources_limit_value" class="range-value">%s</span>',
7296 + esc_html($rag_sources_limit)
7297 + );
5500 7298 echo '</div>';
5501 7299 }
5502 7300
5503 -public function mxchat_live_agent_bot_token_callback() {
5504 - $disabled = $this->is_activated ? '' : 'disabled';
5505 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
7301 +public function mxchat_enqueue_admin_assets() {
7302 + // Get plugin version
7303 + $version = MXCHAT_VERSION;
5506 7304
5507 - echo '<div class="' . esc_attr($class) . '">';
5508 - printf(
5509 - '<input type="password" id="live_agent_bot_token" name="live_agent_bot_token" value="%s" class="regular-text" %s />',
5510 - isset($this->options['live_agent_bot_token']) ? esc_attr($this->options['live_agent_bot_token']) : '',
5511 - esc_attr($disabled)
5512 - );
5513 - echo '<button type="button" id="toggleBotTokenVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5514 - echo '<p class="description">' . esc_html__('Your Slack Bot OAuth Token (starts with xoxb-). Keep this secure.', 'mxchat') . '</p>';
5515 -
5516 - if (!$this->is_activated) {
5517 - echo '<div class="pro-feature-overlay">';
5518 - 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>';
5519 - echo '</div>';
7305 + // Use file modification time for development (remove in production)
7306 + if (defined('WP_DEBUG') && WP_DEBUG) {
7307 + $version = filemtime(plugin_dir_path(__FILE__) . '../mxchat-basic.php');
5520 7308 }
5521 - echo '</div>';
5522 -}
5523 7309
5524 -public function mxchat_enqueue_admin_assets() {
5525 - wp_enqueue_style('wp-color-picker');
7310 + $current_page = isset($_GET['page']) ? $_GET['page'] : '';
7311 + $plugin_url = plugin_dir_url(__FILE__) . '../';
5526 7312
5527 - // Get the plugin version or file modification time for cache busting
5528 - $plugin_version = '2.0.6'; // Replace this with your plugin's version
7313 + // Only load on MxChat pages
7314 + if (strpos($current_page, 'mxchat') === false) {
7315 + return;
7316 + }
5529 7317
5530 - // File paths
5531 - $color_picker_js_path = plugin_dir_path(__FILE__) . '../js/my-color-picker.js';
5532 - $embedding_check_js_path = plugin_dir_path(__FILE__) . '../js/embedding-check.js';
5533 - $admin_css_path = plugin_dir_path(__FILE__) . '../css/admin-style.css';
5534 - $knowledge_css_path = plugin_dir_path(__FILE__) . '../css/knowledge-style.css';
5535 - $intent_css_path = plugin_dir_path(__FILE__) . '../css/intent-style.css';
5536 - $transcripts_css_path = plugin_dir_path(__FILE__) . '../css/chat-transcripts.css';
5537 - $transcripts_js_path = plugin_dir_path(__FILE__) . '../js/mxchat_transcripts.js';
7318 + // Always load these on all MxChat pages
7319 + $this->enqueue_core_admin_assets($plugin_url, $version);
7320 + $this->enqueue_page_specific_assets($current_page, $plugin_url, $version);
7321 + $this->localize_admin_scripts($current_page);
7322 +}
7323 +private function enqueue_core_admin_assets($plugin_url, $version) {
7324 + // Core admin styles
7325 + wp_enqueue_style('mxchat-admin-css', $plugin_url . 'css/admin-style.css', array(), $version);
7326 + wp_enqueue_style('mxchat-knowledge-css', $plugin_url . 'css/knowledge-style.css', array(), $version);
5538 7327
7328 + // New sidebar navigation styles (for main settings page)
7329 + wp_enqueue_style('mxchat-admin-sidebar-css', $plugin_url . 'css/admin-sidebar.css', array(), $version);
5539 7330
5540 - // Check if files exist and get modification times
5541 - $color_picker_version = file_exists($color_picker_js_path) ? filemtime($color_picker_js_path) : $plugin_version;
5542 - $embedding_check_version = file_exists($embedding_check_js_path) ? filemtime($embedding_check_js_path) : $plugin_version;
5543 - $admin_css_version = file_exists($admin_css_path) ? filemtime($admin_css_path) : $plugin_version;
5544 - $knowledge_css_version = file_exists($knowledge_css_path) ? filemtime($knowledge_css_path) : $plugin_version;
5545 - $intent_css_version = file_exists($intent_css_path) ? filemtime($intent_css_path) : $plugin_version;
5546 - $transcripts_css_version = file_exists($transcripts_css_path) ? filemtime($transcripts_css_path) : $plugin_version;
5547 - $transcripts_js_version = file_exists($transcripts_js_path) ? filemtime($transcripts_js_path) : $plugin_version;
7331 + // Core admin scripts
7332 + wp_enqueue_script('mxchat-admin-js', $plugin_url . 'js/mxchat-admin.js', array('jquery'), $version, true);
7333 +}
7334 +private function enqueue_page_specific_assets($current_page, $plugin_url, $version) {
7335 + switch ($current_page) {
7336 + case 'mxchat-prompts':
7337 + // Knowledge processing page assets
7338 + wp_enqueue_style('mxchat-content-selector-css', $plugin_url . 'css/content-selector.css', array(), $version);
7339 + wp_enqueue_script('mxchat-content-selector-js', $plugin_url . 'js/content-selector.js', array('jquery'), $version, true);
7340 + // Add the knowledge processing script (common script needed for WordPress dismiss functionality)
7341 + wp_enqueue_script('mxchat-knowledge-processing', $plugin_url . 'js/knowledge-processing.js', array('jquery', 'common'), $version, true);
7342 + break;
5548 7343
7344 + case 'mxchat-transcripts':
7345 + // Load admin sidebar CSS first (shared styles for sidebar navigation)
7346 + wp_enqueue_style('mxchat-admin-sidebar-css', $plugin_url . 'css/admin-sidebar.css', array(), $version);
7347 + // Load transcripts-specific styles
7348 + wp_enqueue_style('mxchat-chat-transcripts-css', $plugin_url . 'css/chat-transcripts.css', array('mxchat-admin-sidebar-css'), $version);
7349 + wp_enqueue_script('mxchat-transcripts-js', $plugin_url . 'js/mxchat_transcripts.js', array('jquery'), $version, true);
7350 + break;
5549 7351
5550 - // Enqueue scripts and styles with corrected paths
5551 - wp_enqueue_script(
5552 - 'mxchat-color-picker',
5553 - plugin_dir_url(__FILE__) . '../js/my-color-picker.js',
5554 - array('wp-color-picker'),
5555 - $color_picker_version,
5556 - true
5557 - );
7352 + case 'mxchat-actions':
7353 + // Load admin sidebar CSS first (shared styles for sidebar navigation)
7354 + wp_enqueue_style('mxchat-admin-sidebar-css', $plugin_url . 'css/admin-sidebar.css', array(), $version);
7355 + // Load actions-specific styles
7356 + wp_enqueue_style('mxchat-actions-css', $plugin_url . 'css/actions.css', array('mxchat-admin-sidebar-css'), $version);
7357 + wp_enqueue_script('mxchat-actions-js', $plugin_url . 'js/mxchat_actions.js', array('jquery'), $version, true);
5558 7358
5559 - wp_enqueue_script(
5560 - 'mxchat-embedding-check',
5561 - plugin_dir_url(__FILE__) . '../js/embedding-check.js',
5562 - array(),
5563 - $embedding_check_version,
5564 - true
5565 - );
7359 + // Localize script data for actions page
7360 + $is_activated = get_option('mxchat_pro_license_status') === 'active';
7361 + wp_localize_script('mxchat-actions-js', 'mxchActionsData', array(
7362 + 'ajaxUrl' => admin_url('admin-ajax.php'),
7363 + 'nonce' => wp_create_nonce('mxchat_actions_nonce'),
7364 + 'addNonce' => wp_create_nonce('mxchat_add_intent_nonce'),
7365 + 'editNonce' => wp_create_nonce('mxchat_edit_intent'),
7366 + 'deleteNonce' => wp_create_nonce('mxchat_delete_intent_nonce'),
7367 + 'toggleNonce' => wp_create_nonce('mxchat_actions_nonce'),
7368 + 'isActivated' => $is_activated,
7369 + 'i18n' => array(
7370 + 'confirmDelete' => __('Are you sure you want to delete this action?', 'mxchat'),
7371 + 'confirmBulkDelete' => __('Are you sure you want to delete the selected actions?', 'mxchat'),
7372 + 'saving' => __('Saving...', 'mxchat'),
7373 + 'saved' => __('Saved successfully!', 'mxchat'),
7374 + 'error' => __('An error occurred. Please try again.', 'mxchat'),
7375 + 'proRequired' => __('This feature requires MxChat Pro.', 'mxchat'),
7376 + 'addonRequired' => __('This feature requires an add-on.', 'mxchat')
7377 + )
7378 + ));
7379 + break;
5566 7380
5567 - wp_enqueue_script(
5568 - 'mxchat-admin-js',
5569 - plugin_dir_url(__FILE__) . '../js/mxchat-admin.js',
5570 - array('jquery'),
5571 - $plugin_version,
5572 - true
5573 - );
7381 + case 'mxchat-activation':
7382 + // Load admin sidebar CSS first (shared styles for sidebar navigation)
7383 + wp_enqueue_style('mxchat-admin-sidebar-css', $plugin_url . 'css/admin-sidebar.css', array(), $version);
7384 + // Load pro page-specific styles
7385 + wp_enqueue_style('mxchat-pro-css', $plugin_url . 'css/admin-pro.css', array('mxchat-admin-sidebar-css'), $version);
7386 + // Load pro page JavaScript
7387 + wp_enqueue_script('mxchat-pro-js', $plugin_url . 'js/mxchat_pro.js', array('jquery'), $version, true);
7388 + // Load activation script for license activation/deactivation
7389 + wp_enqueue_script('mxchat-activation-js', $plugin_url . 'js/activation-script.js', array('jquery'), $version, true);
7390 + break;
7391 + default:
7392 + wp_enqueue_script(
7393 + 'mxchat-test-streaming-js',
7394 + $plugin_url . 'js/mxchat-test-streaming.js',
7395 + ['jquery'],
7396 + $version,
7397 + true
7398 + );
5574 7399
5575 - wp_localize_script('mxchat-admin-js', 'mxchatAdmin', array(
7400 + wp_localize_script('mxchat-test-streaming-js', 'mxchatTestStreamingAjax', [
7401 + 'ajax_url' => admin_url('admin-ajax.php'),
7402 + 'nonce' => wp_create_nonce('mxchat_test_streaming_nonce'),
7403 + 'settings_nonce' => wp_create_nonce('mxchat_save_setting_nonce')
7404 + ]);
7405 + break;
7406 + }
7407 +}
7408 +private function localize_admin_scripts($current_page) {
7409 + // Base localization data for main admin script
7410 + $base_data = array(
5576 7411 'ajax_url' => admin_url('admin-ajax.php'),
7412 + 'nonce' => wp_create_nonce('mxchat_admin_nonce'),
7413 + 'admin_url' => admin_url(),
5577 7414 'license_nonce' => wp_create_nonce('mxchat_activate_license_nonce'),
5578 7415 'inline_edit_nonce' => wp_create_nonce('mxchat_save_inline_nonce'),
5579 7416 'setting_nonce' => wp_create_nonce('mxchat_save_setting_nonce'),
5580 7417 'export_nonce' => wp_create_nonce('mxchat_export_transcripts'),
5581 - ));
7418 + 'actions_nonce' => wp_create_nonce('mxchat_actions_nonce'),
7419 + 'add_intent_nonce' => wp_create_nonce('mxchat_add_intent_nonce'),
7420 + 'edit_intent_nonce' => wp_create_nonce('mxchat_edit_intent'),
7421 + 'toggle_action_nonce' => wp_create_nonce('mxchat_actions_nonce'),
7422 + 'fetch_openrouter_models_nonce' => wp_create_nonce('mxchat_fetch_openrouter_models'),
7423 + 'is_activated' => $this->is_activated ? '1' : '0',
7424 + 'status_refresh_interval' => 5000,
7425 + 'prompts_setting_nonce' => wp_create_nonce('mxchat_prompts_setting_nonce'),
7426 + 'ajaxurl' => admin_url('admin-ajax.php')
7427 + );
5582 7428
5583 - // Enqueue the admin CSS
5584 - wp_enqueue_style(
5585 - 'mxchat-admin-css',
5586 - plugin_dir_url(__FILE__) . '../css/admin-style.css',
5587 - array(),
5588 - $admin_css_version
5589 - );
7429 + // Localize main admin script with base data
7430 + wp_localize_script('mxchat-admin-js', 'mxchatAdmin', $base_data);
5590 7431
5591 - if (isset($_GET['page']) && $_GET['page'] === 'mxchat-transcripts') {
5592 - wp_enqueue_style(
5593 - 'mxchat-chat-transcripts-css',
5594 - plugin_dir_url(__FILE__) . '../css/chat-transcripts.css',
5595 - array(),
5596 - $transcripts_css_version
5597 - );
7432 + // Page-specific localizations
7433 + $this->localize_page_specific_scripts($current_page);
7434 +}
7435 +private function localize_page_specific_scripts($current_page) {
7436 + switch ($current_page) {
7437 + case 'mxchat-prompts':
7438 + // Status updater localization
7439 + wp_localize_script('mxchat-status-updater', 'mxchat_status_data', array(
7440 + 'ajax_url' => admin_url('admin-ajax.php'),
7441 + 'nonce' => wp_create_nonce('mxchat_status_nonce')
7442 + ));
5598 7443
5599 - wp_enqueue_script(
5600 - 'mxchat-transcripts-js',
5601 - plugin_dir_url(__FILE__) . '../js/mxchat_transcripts.js',
5602 - array('jquery'),
5603 - $transcripts_js_version,
5604 - true
5605 - );
5606 - }
7444 + // Content selector localization
7445 + wp_localize_script('mxchat-content-selector-js', 'mxchatSelector', array(
7446 + 'ajaxurl' => admin_url('admin-ajax.php'),
7447 + 'nonce' => wp_create_nonce('mxchat_content_selector_nonce'),
7448 + 'i18n' => array(
7449 + 'searchPlaceholder' => __('Search posts and pages...', 'mxchat'),
7450 + 'selectAll' => __('Select All', 'mxchat'),
7451 + 'process' => __('Process Selected', 'mxchat'),
7452 + 'cancel' => __('Cancel', 'mxchat'),
7453 + 'noResults' => __('No content found.', 'mxchat')
7454 + )
7455 + ));
5607 7456
5608 - wp_enqueue_style(
5609 - 'mxchat-knowledge-css',
5610 - plugin_dir_url(__FILE__) . '../css/knowledge-style.css',
5611 - array(),
5612 - $knowledge_css_version
5613 - );
7457 + wp_localize_script('mxchat-knowledge-processing', 'mxchatAdmin', array(
7458 + 'ajax_url' => admin_url('admin-ajax.php'),
7459 + 'status_nonce' => wp_create_nonce('mxchat_status_nonce'),
7460 + 'queue_nonce' => wp_create_nonce('mxchat_queue_nonce'),
7461 + 'stop_nonce' => wp_create_nonce('mxchat_stop_processing_action'),
7462 + 'settings_nonce' => wp_create_nonce('mxchat_prompts_setting_nonce'),
7463 + 'setting_nonce' => wp_create_nonce('mxchat_save_setting_nonce'),
7464 + 'entries_nonce' => wp_create_nonce('mxchat_entries_nonce'),
7465 + 'admin_url' => admin_url(),
7466 + 'ajaxurl' => admin_url('admin-ajax.php'),
7467 + 'status_refresh_interval' => 2000,
7468 + 'bot_id' => isset($_GET['bot_id']) ? sanitize_text_field($_GET['bot_id']) : 'default'
7469 + ));
7470 + break;
5614 7471
7472 + case 'mxchat-activation':
7473 + wp_localize_script('mxchat-activation-js', 'mxchatAdmin', array(
7474 + 'ajax_url' => admin_url('admin-ajax.php'),
7475 + 'license_nonce' => wp_create_nonce('mxchat_activate_license_nonce')
7476 + ));
7477 + break;
5615 7478
5616 - wp_enqueue_style(
5617 - 'mxchat-intent-css',
5618 - plugin_dir_url(__FILE__) . '../css/intent-style.css',
5619 - array(),
5620 - $intent_css_version
5621 - );
7479 + case 'mxchat-transcripts':
7480 + // Get chart data for localization
7481 + $chart_data = $this->get_transcripts_chart_data();
5622 7482
5623 - // IMPORTANT: Use the same script handle as above for localizing mxchatPromptsAdmin
5624 - wp_localize_script( 'mxchat-admin-js', 'mxchatPromptsAdmin', array(
5625 - 'ajax_url' => admin_url( 'admin-ajax.php' ),
5626 - 'prompts_setting_nonce' => wp_create_nonce( 'mxchat_prompts_setting_nonce' ),
5627 - ) );
7483 + wp_localize_script('mxchat-transcripts-js', 'mxchatAdmin', array(
7484 + 'ajax_url' => admin_url('admin-ajax.php'),
7485 + 'export_nonce' => wp_create_nonce('mxchat_export_transcripts'),
7486 + 'delete_nonce' => wp_create_nonce('mxchat_delete_chat_history'),
7487 + 'setting_nonce' => wp_create_nonce('mxchat_save_setting_nonce'),
7488 + 'translate_nonce' => wp_create_nonce('mxchat_translate_messages')
7489 + ));
5628 7490
5629 - // Localize the script for color picker and settings
5630 - wp_localize_script('mxchat-color-picker', 'mxchatStyleSettings', array(
7491 + // Localize chart data separately - use array_values to ensure proper JSON array encoding
7492 + wp_localize_script('mxchat-transcripts-js', 'mxchatChartData', array(
7493 + 'labels' => array_values($chart_data['labels']),
7494 + 'chats' => array_values($chart_data['chats']),
7495 + 'messages' => array_values($chart_data['messages'])
7496 + ));
7497 + break;
7498 +
7499 + case 'mxchat-settings':
7500 + default:
7501 + // Color picker and settings localization
7502 + wp_localize_script('mxchat-color-picker', 'mxchatStyleSettings', array(
7503 + 'ajax_url' => admin_url('admin-ajax.php'),
7504 + 'link_target_toggle' => $this->options['link_target_toggle'] ?? 'off',
7505 + 'user_message_bg_color' => $this->options['user_message_bg_color'] ?? '#fff',
7506 + 'user_message_font_color' => $this->options['user_message_font_color'] ?? '#212121',
7507 + 'bot_message_bg_color' => $this->options['bot_message_bg_color'] ?? '#212121',
7508 + 'bot_message_font_color' => $this->options['bot_message_font_color'] ?? '#fff',
7509 + 'top_bar_bg_color' => $this->options['top_bar_bg_color'] ?? '#212121',
7510 + 'send_button_font_color' => $this->options['send_button_font_color'] ?? '#212121',
7511 + 'close_button_color' => $this->options['close_button_color'] ?? '#fff',
7512 + 'chatbot_background_color' => $this->options['chatbot_background_color'] ?? '#212121',
7513 + 'icon_color' => $this->options['icon_color'] ?? '#fff',
7514 + 'chat_input_font_color' => $this->options['chat_input_font_color'] ?? '#212121',
7515 + 'pre_chat_message' => $this->options['pre_chat_message'] ?? esc_html__('Hey there! Ask me anything!', 'mxchat'),
7516 + 'rate_limit_message' => $this->options['rate_limit_message'] ?? esc_html__('Rate limit exceeded. Please try again later.', 'mxchat'),
7517 + 'loops_api_key' => $this->options['loops_api_key'] ?? '',
7518 + 'loops_mailing_list' => $this->options['loops_mailing_list'] ?? '',
7519 + 'triggered_phrase_response' => $this->options['triggered_phrase_response'] ?? esc_html__('Would you like to join our mailing list? Please provide your email below.', 'mxchat'),
7520 + 'email_capture_response' => $this->options['email_capture_response'] ?? esc_html__('Thank you for providing your email! You\'ve been added to our list.', 'mxchat'),
7521 + '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'),
7522 + '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'),
7523 + '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'),
7524 + 'pdf_max_pages' => $this->options['pdf_max_pages'] ?? 69,
7525 + 'live_agent_webhook_url' => $this->options['live_agent_webhook_url'] ?? '',
7526 + 'live_agent_secret_key' => $this->options['live_agent_secret_key'] ?? '',
7527 + 'live_agent_bot_token' => $this->options['live_agent_bot_token'] ?? '',
7528 + 'live_agent_message_bg_color' => $this->options['live_agent_message_bg_color'] ?? '#ffffff',
7529 + 'live_agent_message_font_color' => $this->options['live_agent_message_font_color'] ?? '#333333',
7530 + 'chat_toolbar_toggle' => $this->options['chat_toolbar_toggle'] ?? 'off',
7531 + 'show_pdf_upload_button' => $this->options['show_pdf_upload_button'] ?? 'on',
7532 + 'show_word_upload_button' => $this->options['show_word_upload_button'] ?? 'on',
7533 + 'mode_indicator_bg_color' => $this->options['mode_indicator_bg_color'] ?? '#767676',
7534 + 'mode_indicator_font_color' => $this->options['mode_indicator_font_color'] ?? '#ffffff',
7535 + 'toolbar_icon_color' => $this->options['toolbar_icon_color'] ?? '#212121',
7536 + ));
7537 + break;
7538 + }
7539 +
7540 + // Additional localization that was in the original code
7541 + wp_localize_script('mxchat-admin-js', 'mxchatPromptsAdmin', array(
5631 7542 'ajax_url' => admin_url('admin-ajax.php'),
5632 - 'link_target_toggle' => $this->options['link_target_toggle'] ?? 'off',
5633 - 'user_message_bg_color' => $this->options['user_message_bg_color'] ?? '#fff',
5634 - 'user_message_font_color' => $this->options['user_message_font_color'] ?? '#212121',
5635 - 'bot_message_bg_color' => $this->options['bot_message_bg_color'] ?? '#212121',
5636 - 'bot_message_font_color' => $this->options['bot_message_font_color'] ?? '#fff',
5637 - 'top_bar_bg_color' => $this->options['top_bar_bg_color'] ?? '#212121',
5638 - 'send_button_font_color' => $this->options['send_button_font_color'] ?? '#212121',
5639 - 'close_button_color' => $this->options['close_button_color'] ?? '#fff',
5640 - 'chatbot_background_color' => $this->options['chatbot_background_color'] ?? '#212121',
5641 - 'icon_color' => $this->options['icon_color'] ?? '#fff',
5642 - 'chat_input_font_color' => $this->options['chat_input_font_color'] ?? '#212121',
5643 - 'pre_chat_message' => $this->options['pre_chat_message'] ?? esc_html__('Hey there! Ask me anything!', 'mxchat'),
5644 - 'rate_limit_message' => $this->options['rate_limit_message'] ?? esc_html__('Rate limit exceeded. Please try again later.', 'mxchat'),
5645 - 'loops_api_key' => $this->options['loops_api_key'] ?? '',
5646 - 'loops_mailing_list' => $this->options['loops_mailing_list'] ?? '',
5647 - 'triggered_phrase_response' => $this->options['triggered_phrase_response'] ?? esc_html__('Would you like to join our mailing list? Please provide your email below.', 'mxchat'),
5648 - 'email_capture_response' => $this->options['email_capture_response'] ?? esc_html__('Thank you for providing your email! You\'ve been added to our list.', 'mxchat'),
5649 - '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'),
5650 - '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'),
5651 - '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'),
5652 - 'pdf_max_pages' => $this->options['pdf_max_pages'] ?? 69,
5653 - 'live_agent_webhook_url' => $this->options['live_agent_webhook_url'] ?? '',
5654 - 'live_agent_secret_key' => $this->options['live_agent_secret_key'] ?? '',
5655 - 'live_agent_bot_token' => $this->options['live_agent_bot_token'] ?? '',
5656 - 'live_agent_message_bg_color' => $this->options['live_agent_message_bg_color'] ?? '#ffffff',
5657 - 'live_agent_message_font_color' => $this->options['live_agent_message_font_color'] ?? '#333333',
5658 - 'chat_toolbar_toggle' => $this->options['chat_toolbar_toggle'] ?? 'off',
5659 - 'mode_indicator_bg_color' => $this->options['mode_indicator_bg_color'] ?? '#767676',
5660 - 'mode_indicator_font_color' => $this->options['mode_indicator_font_color'] ?? '#ffffff',
5661 - 'toolbar_icon_color' => $this->options['toolbar_icon_color'] ?? '#212121',
7543 + 'prompts_setting_nonce' => wp_create_nonce('mxchat_prompts_setting_nonce'),
5662 7544 ));
5663 7545 }
5664 7546
5665 -
5666 7547 public function mxchat_sanitize($input) {
5667 7548 $new_input = array();
5668 7549
5669 7550 if (isset($input['api_key'])) {
@@ -5671,11 +7552,16 @@
5671 7552 }
5672 7553
5673 7554 if (isset($input['similarity_threshold'])) {
5674 7555 $new_input['similarity_threshold'] = absint($input['similarity_threshold']); // Ensure it's an integer
5675 - $new_input['similarity_threshold'] = min(max($new_input['similarity_threshold'], 70), 85); // Enforce range
7556 + $new_input['similarity_threshold'] = min(max($new_input['similarity_threshold'], 20), 85); // Enforce range
5676 7557 }
5677 7558
7559 + if (isset($input['rag_sources_limit'])) {
7560 + $new_input['rag_sources_limit'] = absint($input['rag_sources_limit']); // Ensure it's an integer
7561 + $new_input['rag_sources_limit'] = min(max($new_input['rag_sources_limit'], 3), 10); // Enforce range 3-10
7562 + }
7563 +
5678 7564 if (isset($input['xai_api_key'])) {
5679 7565 $new_input['xai_api_key'] = sanitize_text_field($input['xai_api_key']);
5680 7566 }
5681 7567
@@ -5682,12 +7568,30 @@
5682 7568 if (isset($input['claude_api_key'])) {
5683 7569 $new_input['claude_api_key'] = sanitize_text_field($input['claude_api_key']);
5684 7570 }
5685 7571
7572 + if (isset($input['enable_streaming_toggle'])) {
7573 + $new_input['enable_streaming_toggle'] = ($input['enable_streaming_toggle'] === 'on') ? 'on' : 'off';
7574 + } else {
7575 + // If checkbox not checked, it won't be in $input, so set to 'off'
7576 + $new_input['enable_streaming_toggle'] = 'off';
7577 + }
7578 +
7579 + if (isset($input['enable_web_search'])) {
7580 + $new_input['enable_web_search'] = ($input['enable_web_search'] === 'on') ? 'on' : 'off';
7581 + } else {
7582 + // If checkbox not checked, it won't be in $input, so set to 'off'
7583 + $new_input['enable_web_search'] = 'off';
7584 + }
7585 +
5686 7586 if (isset($input['deepseek_api_key'])) {
5687 7587 $new_input['deepseek_api_key'] = sanitize_text_field($input['deepseek_api_key']);
5688 7588 }
5689 7589
7590 + if (isset($input['gemini_api_key'])) {
7591 + $new_input['gemini_api_key'] = sanitize_text_field($input['gemini_api_key']);
7592 + }
7593 +
5690 7594 if (isset($input['enable_woocommerce_integration'])) {
5691 7595 $new_input['enable_woocommerce_integration'] = $input['enable_woocommerce_integration'] === 'on' ? 'on' : 'off';
5692 7596 }
5693 7597
@@ -5720,12 +7624,44 @@
5720 7624 if (isset($input['append_to_body'])) {
5721 7625 $new_input['append_to_body'] = $input['append_to_body'] === 'on' ? 'on' : 'off';
5722 7626 }
5723 7627
7628 + // Post type visibility settings
7629 + if (isset($input['post_type_visibility_mode'])) {
7630 + $allowed_modes = array('all', 'include', 'exclude');
7631 + $new_input['post_type_visibility_mode'] = in_array($input['post_type_visibility_mode'], $allowed_modes)
7632 + ? $input['post_type_visibility_mode']
7633 + : 'all';
7634 + }
7635 +
7636 + if (isset($input['post_type_visibility_list'])) {
7637 + if (is_array($input['post_type_visibility_list'])) {
7638 + $new_input['post_type_visibility_list'] = array_map('sanitize_key', $input['post_type_visibility_list']);
7639 + } else {
7640 + $new_input['post_type_visibility_list'] = array();
7641 + }
7642 + }
7643 +
7644 + if (isset($input['contextual_awareness_toggle'])) {
7645 + $new_input['contextual_awareness_toggle'] = $input['contextual_awareness_toggle'] === 'on' ? 'on' : 'off';
7646 +}
7647 +
7648 + if (isset($input['show_frontend_debugger'])) {
7649 + $new_input['show_frontend_debugger'] = $input['show_frontend_debugger'] === 'on' ? 'on' : 'off';
7650 +}
7651 +
7652 + if (isset($input['citation_links_toggle'])) {
7653 + $new_input['citation_links_toggle'] = $input['citation_links_toggle'] === 'on' ? 'on' : 'off';
7654 +}
7655 +
5724 7656 if (isset($input['top_bar_title'])) {
5725 7657 $new_input['top_bar_title'] = sanitize_text_field($input['top_bar_title']);
5726 7658 }
5727 7659
7660 + if (isset($input['ai_agent_text'])) {
7661 + $new_input['ai_agent_text'] = sanitize_text_field($input['ai_agent_text']);
7662 + }
7663 +
5728 7664 if (isset($input['enable_email_block'])) {
5729 7665 $new_input['enable_email_block'] = sanitize_text_field($input['enable_email_block']);
5730 7666 }
5731 7667
@@ -5735,11 +7671,20 @@
5735 7671 }
5736 7672 if (isset($input['email_blocker_button_text'])) {
5737 7673 $new_input['email_blocker_button_text'] = sanitize_text_field($input['email_blocker_button_text']);
5738 7674 }
5739 -
7675 + // Sanitize name field toggle
7676 + if (isset($input['enable_name_field'])) {
7677 + $new_input['enable_name_field'] = ($input['enable_name_field'] === 'on') ? 'on' : 'off';
7678 + } else {
7679 + $new_input['enable_name_field'] = 'off';
7680 + }
7681 + // Sanitize name field placeholder
7682 + if (isset($input['name_field_placeholder'])) {
7683 + $new_input['name_field_placeholder'] = sanitize_text_field($input['name_field_placeholder']);
7684 + }
5740 7685 if (isset($input['intro_message'])) {
5741 - $new_input['intro_message'] = sanitize_textarea_field($input['intro_message']); // Changed from sanitize_text_field
7686 + $new_input['intro_message'] = wp_kses_post($input['intro_message']); // Use wp_kses_post instead
5742 7687 }
5743 7688
5744 7689 if (isset($input['input_copy'])) {
5745 7690 $new_input['input_copy'] = sanitize_text_field($input['input_copy']);
@@ -5748,33 +7693,43 @@
5748 7693 if (isset($input['rate_limit_message'])) {
5749 7694 $new_input['rate_limit_message'] = sanitize_text_field($input['rate_limit_message']);
5750 7695 }
5751 7696
5752 - if (isset($input['rate_limit_logged_out'])) {
5753 - $allowed_limits = array('1', '3', '5', '10', '15', '20', '100', 'unlimited'); // Add 'unlimited' to allowed values
5754 - $rate_limit = sanitize_text_field($input['rate_limit_logged_out']);
7697 +// Handle the new rate limits format
7698 +if (isset($input['rate_limits']) && is_array($input['rate_limits'])) {
7699 + $new_input['rate_limits'] = array();
7700 + $allowed_limits = array('1', '3', '5', '10', '15', '20', '50', '100', 'unlimited');
7701 + $allowed_timeframes = array('hourly', 'daily', 'weekly', 'monthly');
5755 7702
5756 - if (in_array($rate_limit, $allowed_limits, true)) {
5757 - $new_input['rate_limit_logged_out'] = $rate_limit;
5758 - } else {
5759 - $new_input['rate_limit_logged_out'] = '10'; // Default for logged-out users
7703 + foreach ($input['rate_limits'] as $role_id => $settings) {
7704 + $new_input['rate_limits'][$role_id] = array();
7705 +
7706 + // Sanitize limit
7707 + if (isset($settings['limit'])) {
7708 + $limit = sanitize_text_field($settings['limit']);
7709 + if (in_array($limit, $allowed_limits, true)) {
7710 + $new_input['rate_limits'][$role_id]['limit'] = $limit;
7711 + } else {
7712 + $new_input['rate_limits'][$role_id]['limit'] = ($role_id === 'logged_out') ? '10' : '100'; // Default
7713 + }
5760 7714 }
5761 - }
5762 7715
5763 - // Handle role rate limits
5764 - if (isset($input['role_rate_limits']) && is_array($input['role_rate_limits'])) {
5765 - $allowed_limits = array('1', '3', '5', '10', '15', '20', '100', 'unlimited');
5766 - $new_input['role_rate_limits'] = array();
5767 -
5768 - foreach ($input['role_rate_limits'] as $role_id => $rate_limit) {
5769 - $rate_limit = sanitize_text_field($rate_limit);
5770 - if (in_array($rate_limit, $allowed_limits, true)) {
5771 - $new_input['role_rate_limits'][$role_id] = $rate_limit;
7716 + // Sanitize timeframe
7717 + if (isset($settings['timeframe'])) {
7718 + $timeframe = sanitize_text_field($settings['timeframe']);
7719 + if (in_array($timeframe, $allowed_timeframes, true)) {
7720 + $new_input['rate_limits'][$role_id]['timeframe'] = $timeframe;
5772 7721 } else {
5773 - $new_input['role_rate_limits'][$role_id] = '100'; // Default
7722 + $new_input['rate_limits'][$role_id]['timeframe'] = 'daily'; // Default
5774 7723 }
5775 7724 }
7725 +
7726 + // Sanitize message
7727 + if (isset($settings['message'])) {
7728 + $new_input['rate_limits'][$role_id]['message'] = sanitize_textarea_field($settings['message']);
7729 + }
5776 7730 }
7731 +}
5777 7732
5778 7733 if (isset($input['pre_chat_message'])) {
5779 7734 $new_input['pre_chat_message'] = sanitize_textarea_field($input['pre_chat_message']);
5780 7735 }
@@ -5788,9 +7743,10 @@
5788 7743 $allowed_models = array(
5789 7744 'text-embedding-ada-002',
5790 7745 'text-embedding-3-small',
5791 7746 'text-embedding-3-large',
5792 - 'voyage-3-large'
7747 + 'voyage-3-large',
7748 + 'gemini-embedding-001'
5793 7749 );
5794 7750 if (in_array($input['embedding_model'], $allowed_models)) {
5795 7751 $new_input['embedding_model'] = sanitize_text_field($input['embedding_model']);
5796 7752 }
@@ -5795,28 +7751,70 @@
5795 7751 $new_input['embedding_model'] = sanitize_text_field($input['embedding_model']);
5796 7752 }
5797 7753 }
5798 7754
5799 - if (isset($input['model'])) {
7755 +if (isset($input['model'])) {
7756 + if ($input['model'] === 'openrouter') {
7757 + $new_input['model'] = 'openrouter';
7758 + } else {
5800 7759 $allowed_models = array(
5801 - 'grok-beta',
7760 + 'gemini-3-pro-preview',
7761 + 'gemini-3-flash-preview',
7762 + 'gemini-2.5-pro',
7763 + 'gemini-2.5-flash',
7764 + 'gemini-2.5-flash-lite',
7765 + 'gemini-2.0-flash',
7766 + 'gemini-2.0-flash-lite',
7767 + 'gemini-1.5-pro',
7768 + 'gemini-1.5-flash',
7769 + 'grok-4-0709',
7770 + 'grok-4-1-fast-reasoning',
7771 + 'grok-4-1-fast-non-reasoning',
7772 + 'grok-3-beta',
7773 + 'grok-3-fast-beta',
7774 + 'grok-3-mini-beta',
7775 + 'grok-3-mini-fast-beta',
5802 7776 'grok-2',
5803 7777 'deepseek-chat',
5804 - 'claude-3-5-sonnet-20241022',
5805 - 'claude-3-opus-20240229',
5806 - 'claude-3-sonnet-20240229',
5807 - 'claude-3-haiku-20240307',
5808 - 'gpt-4o',
5809 - 'gpt-4o-mini',
5810 - 'gpt-4-turbo',
5811 - 'gpt-4',
5812 - 'gpt-3.5-turbo',
7778 + 'claude-opus-4-6',
7779 + 'claude-opus-4-5',
7780 + 'claude-sonnet-4-5-20250929',
7781 + 'claude-opus-4-1-20250805',
7782 + 'claude-haiku-4-5-20251001',
7783 + 'claude-opus-4-20250514',
7784 + 'claude-sonnet-4-20250514',
7785 + 'gpt-5.2',
7786 + 'gpt-5.1-chat-latest',
7787 + 'gpt-5.1-2025-11-13',
7788 + 'gpt-5',
7789 + 'gpt-5-mini',
7790 + 'gpt-5-nano',
5813 7791 );
7792 +
5814 7793 if (in_array($input['model'], $allowed_models)) {
5815 7794 $new_input['model'] = sanitize_text_field($input['model']);
7795 + } else {
7796 + // Fallback for any deprecated model
7797 + $new_input['model'] = 'gpt-5.1-chat-latest';
5816 7798 }
5817 7799 }
7800 +}
7801 +
7802 +if (isset($input['openrouter_selected_model'])) {
7803 + // Just sanitize it, don't validate against a whitelist
7804 + $new_input['openrouter_selected_model'] = sanitize_text_field($input['openrouter_selected_model']);
7805 +}
5818 7806
7807 +// ADD THIS:
7808 +if (isset($input['openrouter_selected_model_name'])) {
7809 + $new_input['openrouter_selected_model_name'] = sanitize_text_field($input['openrouter_selected_model_name']);
7810 +}
7811 +
7812 + if (isset($input['openrouter_api_key'])) {
7813 + $new_input['openrouter_api_key'] = sanitize_text_field($input['openrouter_api_key']);
7814 + }
7815 +
7816 +
5819 7817 if (isset($input['close_button_color'])) {
5820 7818 $new_input['close_button_color'] = sanitize_hex_color($input['close_button_color']);
5821 7819 }
5822 7820
@@ -5871,8 +7869,12 @@
5871 7869 if (isset($input['top_bar_bg_color'])) {
5872 7870 $new_input['top_bar_bg_color'] = sanitize_hex_color($input['top_bar_bg_color']);
5873 7871 }
5874 7872
7873 + if (isset($input['quick_questions_toggle_color'])) {
7874 + $new_input['quick_questions_toggle_color'] = sanitize_hex_color($input['quick_questions_toggle_color']);
7875 + }
7876 +
5875 7877 if (isset($input['send_button_font_color'])) {
5876 7878 $new_input['send_button_font_color'] = sanitize_hex_color($input['send_button_font_color']);
5877 7879 }
5878 7880
@@ -5930,15 +7932,14 @@
5930 7932 if (isset($input['loops_mailing_list'])) {
5931 7933 $new_input['loops_mailing_list'] = sanitize_text_field($input['loops_mailing_list']);
5932 7934 }
5933 7935
5934 - // Sanitize Triggered Phrase Response
7936 +// Sanitize Triggered Phrase Response
5935 7937 if (isset($input['triggered_phrase_response'])) {
5936 7938 $new_input['triggered_phrase_response'] = wp_kses_post($input['triggered_phrase_response']);
5937 7939 }
5938 -
5939 7940 if (isset($input['email_capture_response'])) {
5940 - $new_input['email_capture_response'] = sanitize_textarea_field($input['email_capture_response']);
7941 + $new_input['email_capture_response'] = wp_kses_post($input['email_capture_response']);
5941 7942 }
5942 7943
5943 7944 // Sanitize Brave Search Settings
5944 7945 if (isset($input['brave_api_key'])) {
@@ -5971,8 +7972,22 @@
5971 7972 if (isset($input['chat_toolbar_toggle'])) {
5972 7973 $new_input['chat_toolbar_toggle'] = $input['chat_toolbar_toggle'] === 'on' ? 'on' : 'off';
5973 7974 }
5974 7975
7976 + // Sanitize PDF upload button toggle
7977 + if (isset($input['show_pdf_upload_button'])) {
7978 + $new_input['show_pdf_upload_button'] = $input['show_pdf_upload_button'] === 'on' ? 'on' : 'off';
7979 + } else {
7980 + $new_input['show_pdf_upload_button'] = 'off'; // If checkbox is unchecked
7981 + }
7982 +
7983 + // Sanitize Word upload button toggle
7984 + if (isset($input['show_word_upload_button'])) {
7985 + $new_input['show_word_upload_button'] = $input['show_word_upload_button'] === 'on' ? 'on' : 'off';
7986 + } else {
7987 + $new_input['show_word_upload_button'] = 'off'; // If checkbox is unchecked
7988 + }
7989 +
5975 7990 if (isset($input['pdf_intent_trigger_text'])) {
5976 7991 $new_input['pdf_intent_trigger_text'] = sanitize_text_field($input['pdf_intent_trigger_text']);
5977 7992 }
5978 7993
@@ -6002,8 +8017,12 @@
6002 8017 if (isset($input['live_agent_bot_token'])) {
6003 8018 $new_input['live_agent_bot_token'] = sanitize_text_field($input['live_agent_bot_token']);
6004 8019 }
6005 8020
8021 + if (isset($input['live_agent_user_ids'])) {
8022 + $new_input['live_agent_user_ids'] = sanitize_textarea_field($input['live_agent_user_ids']);
8023 + }
8024 +
6006 8025 if (isset($input['live_agent_status'])) {
6007 8026 $new_input['live_agent_status'] = ($input['live_agent_status'] === 'on') ? 'on' : 'off';
6008 8027 }
6009 8028 if (isset($input['live_agent_away_message'])) {
@@ -6012,8 +8031,36 @@
6012 8031 if (isset($input['live_agent_notification_message'])) {
6013 8032 $new_input['live_agent_notification_message'] = sanitize_textarea_field($input['live_agent_notification_message']);
6014 8033 }
6015 8034
8035 + // Telegram Integration
8036 + if (isset($input['telegram_status'])) {
8037 + $new_input['telegram_status'] = ($input['telegram_status'] === 'on') ? 'on' : 'off';
8038 + }
8039 + if (isset($input['telegram_bot_token'])) {
8040 + $new_input['telegram_bot_token'] = sanitize_text_field($input['telegram_bot_token']);
8041 + }
8042 + if (isset($input['telegram_group_id'])) {
8043 + $new_input['telegram_group_id'] = sanitize_text_field($input['telegram_group_id']);
8044 + }
8045 + if (isset($input['telegram_webhook_secret'])) {
8046 + $new_input['telegram_webhook_secret'] = sanitize_text_field($input['telegram_webhook_secret']);
8047 + }
8048 + if (isset($input['telegram_notification_message'])) {
8049 + $new_input['telegram_notification_message'] = sanitize_textarea_field($input['telegram_notification_message']);
8050 + }
8051 + if (isset($input['telegram_away_message'])) {
8052 + $new_input['telegram_away_message'] = sanitize_textarea_field($input['telegram_away_message']);
8053 + }
8054 +
8055 + // Sanitize script loading strategy
8056 + if (isset($input['script_loading_strategy'])) {
8057 + $allowed_strategies = array('default', 'defer', 'delay_1s', 'delay_3s', 'delay_5s', 'on_interaction');
8058 + $new_input['script_loading_strategy'] = in_array($input['script_loading_strategy'], $allowed_strategies)
8059 + ? $input['script_loading_strategy']
8060 + : 'default';
8061 + }
8062 +
6016 8063 return $new_input;
6017 8064 }
6018 8065
6019 8066
@@ -6026,62 +8073,10 @@
6026 8073 }
6027 8074
6028 8075
6029 8076
6030 -private static function mxchat_extract_main_content($html) {
6031 - if (empty($html)) {
6032 - //error_log('mxchat_extract_main_content: Empty HTML content received');
6033 - return '';
6034 - }
6035 8077
6036 - try {
6037 - $dom = new DOMDocument;
6038 - libxml_use_internal_errors(true); // Suppress HTML parsing errors
6039 8078
6040 - // Simple load of HTML, with @ to suppress warnings
6041 - @$dom->loadHTML($html, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
6042 -
6043 - $xpath = new DOMXPath($dom);
6044 -
6045 - // Simplified selectors focusing on common content areas
6046 - $selectors = [
6047 - '//article',
6048 - '//*[@id="content"]',
6049 - '//*[@class="entry-content"]',
6050 - '//main'
6051 - ];
6052 -
6053 - foreach ($selectors as $selector) {
6054 - $nodes = $xpath->query($selector);
6055 - if ($nodes && $nodes->length > 0) {
6056 - $content = '';
6057 - foreach ($nodes as $node) {
6058 - $content .= $dom->saveHTML($node);
6059 - }
6060 - if (!empty($content)) {
6061 - return $content;
6062 - }
6063 - }
6064 - }
6065 -
6066 - // Fallback: Return the entire body content if no specific selector matches
6067 - $body = $dom->getElementsByTagName('body');
6068 - if ($body->length > 0) {
6069 - return $dom->saveHTML($body->item(0));
6070 - }
6071 -
6072 - // Last resort: return the original HTML
6073 - return $html;
6074 -
6075 - } catch (Exception $e) {
6076 - //error_log('mxchat_extract_main_content error: ' . $e->getMessage());
6077 - return $html; // Return original HTML if parsing fails
6078 - } finally {
6079 - libxml_clear_errors();
6080 - }
6081 -}
6082 -
6083 -
6084 8079 private function mxchat_fetch_loops_mailing_lists($api_key) {
6085 8080 $url = 'https://app.loops.so/api/v1/lists';
6086 8081 $response = wp_remote_get($url, array(
6087 8082 'headers' => array(
@@ -6099,12 +8094,8 @@
6099 8094
6100 8095 return isset($lists) && is_array($lists) ? $lists : array();
6101 8096 }
6102 8097
6103 -
6104 -
6105 -
6106 -
6107 8098 function mxchat_calculate_cosine_similarity($vec1, $vec2) {
6108 8099 if (empty($vec1) || empty($vec2)) {
6109 8100 return 0.0;
6110 8101 }
@@ -6125,8 +8116,441 @@
6125 8116 return $dot_product / (sqrt($norm_a) * sqrt($norm_b));
6126 8117 }
6127 8118 }
6128 8119
8120 +/**
8121 + * Validates nonce and deletes all chat prompts
8122 + */
8123 +public function mxchat_handle_delete_all_prompts() {
8124 + //error_log('=== DELETE ALL DEBUG START ===');
8125 +
8126 + // Verify nonce
8127 + if (!isset($_POST['mxchat_delete_all_prompts_nonce']) || !wp_verify_nonce($_POST['mxchat_delete_all_prompts_nonce'], 'mxchat_delete_all_prompts_action')) {
8128 + //error_log('DEBUG: Nonce verification failed');
8129 + wp_die(__('Nonce verification failed.', 'mxchat'));
8130 + }
8131 +
8132 + // Check permissions
8133 + if (!current_user_can('manage_options')) {
8134 + //error_log('DEBUG: Permission check failed');
8135 + wp_die(__('You do not have sufficient permissions to delete all prompts.', 'mxchat'));
8136 + }
8137 +
8138 + // Get bot_id from POST data
8139 + $bot_id = isset($_POST['bot_id']) ? sanitize_text_field($_POST['bot_id']) : 'default';
8140 + //error_log('DEBUG: Bot ID from POST: ' . $bot_id);
8141 +
8142 + $success = true;
8143 + $error_messages = array();
8144 +
8145 + // Get bot-specific Pinecone configuration
8146 + $pinecone_manager = MxChat_Pinecone_Manager::get_instance();
8147 + $pinecone_options = $pinecone_manager->mxchat_get_bot_pinecone_options($bot_id);
8148 +
8149 + //error_log('DEBUG: Pinecone options retrieved:');
8150 + //error_log(' - Use Pinecone: ' . ($pinecone_options['mxchat_use_pinecone'] ?? 'NOT SET'));
8151 + //error_log(' - API Key: ' . (empty($pinecone_options['mxchat_pinecone_api_key']) ? 'EMPTY' : 'SET'));
8152 + //error_log(' - Host: ' . ($pinecone_options['mxchat_pinecone_host'] ?? 'NOT SET'));
8153 +
8154 + $use_pinecone = ($pinecone_options['mxchat_use_pinecone'] ?? '0') === '1';
8155 +
8156 + if ($use_pinecone && !empty($pinecone_options['mxchat_pinecone_api_key'])) {
8157 + //error_log('[MXCHAT-DELETE] Pinecone is enabled for bot ' . $bot_id . ' - deleting all from Pinecone');
8158 +
8159 + // Delete from bot-specific Pinecone index
8160 + $result = $pinecone_manager->mxchat_delete_all_from_pinecone($pinecone_options);
8161 +
8162 + //error_log('DEBUG: Delete all result: ' . ($result['success'] ? 'SUCCESS' : 'FAILED'));
8163 + if (!$result['success']) {
8164 + //error_log('DEBUG: Delete all error: ' . $result['message']);
8165 + }
8166 +
8167 + if (!$result['success']) {
8168 + $success = false;
8169 + $error_messages[] = $result['message'];
8170 + }
8171 +
8172 + // No cache clearing needed since we removed caching
8173 +
8174 + } else {
8175 + //error_log('[MXCHAT-DELETE] Pinecone not enabled for bot ' . $bot_id . ' - deleting from WordPress database');
8176 +
8177 + // Delete from WordPress database
8178 + global $wpdb;
8179 + $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
8180 +
8181 + // If multi-bot is active and we have a specific bot, delete only that bot's content
8182 + if ($bot_id !== 'default' && class_exists('MxChat_Multi_Bot_Manager')) {
8183 + $result = $wpdb->delete(
8184 + $table_name,
8185 + array('bot_id' => $bot_id),
8186 + array('%s')
8187 + );
8188 + } else {
8189 + // Delete all content for default bot
8190 + $result = $wpdb->query("DELETE FROM {$table_name}");
8191 + }
8192 +
8193 + if ($result === false) {
8194 + $success = false;
8195 + $error_messages[] = 'Failed to delete from WordPress database';
8196 + }
8197 + }
8198 +
8199 + // No cache clearing needed since we removed caching
8200 +
8201 + //error_log('=== DELETE ALL DEBUG END ===');
8202 +
8203 + // Redirect back with a success message and bot_id
8204 + $redirect_url = add_query_arg(array(
8205 + 'page' => 'mxchat-prompts',
8206 + 'bot_id' => $bot_id,
8207 + 'all_deleted' => $success ? 'true' : 'false'
8208 + ), admin_url('admin.php'));
8209 +
8210 + wp_safe_redirect($redirect_url);
8211 + exit;
8212 +}
8213 +
8214 +
8215 +
8216 + /**
8217 + * Handles deletion prompt with nonce validation
8218 + */
8219 + public function mxchat_handle_delete_prompt() {
8220 + // Sanitize and validate nonce
8221 + $nonce = isset($_GET['_wpnonce']) ? sanitize_text_field(wp_unslash($_GET['_wpnonce'])) : '';
8222 + if (empty($nonce) || !wp_verify_nonce($nonce, 'mxchat_delete_prompt_nonce')) {
8223 + wp_die(esc_html__('Nonce verification failed.', 'mxchat'));
8224 + }
8225 +
8226 + // Check permissions
8227 + if (!current_user_can('manage_options')) {
8228 + wp_die(esc_html__('You do not have sufficient permissions to delete prompts.', 'mxchat'));
8229 + }
8230 +
8231 + // Get ID and source parameters
8232 + $id = isset($_GET['id']) ? sanitize_text_field($_GET['id']) : '';
8233 + $source = isset($_GET['source']) ? sanitize_text_field($_GET['source']) : '';
8234 +
8235 + if (empty($id)) {
8236 + wp_die(esc_html__('Invalid prompt ID.', 'mxchat'));
8237 + }
8238 +
8239 + $success = false;
8240 + $error_message = '';
8241 +
8242 + // Check if Pinecone is enabled and determine source automatically if not specified
8243 + $pinecone_options = get_option('mxchat_pinecone_addon_options', array());
8244 + $use_pinecone = ($pinecone_options['mxchat_use_pinecone'] ?? '0') === '1';
8245 +
8246 + // If source is not specified, determine based on Pinecone configuration
8247 + if (empty($source)) {
8248 + $source = $use_pinecone ? 'pinecone' : 'wordpress';
8249 + }
8250 +
8251 + if ($source === 'pinecone' || $use_pinecone) {
8252 + //error_log('[MXCHAT-DELETE] Deleting from Pinecone, ID: ' . $id);
8253 +
8254 + // Handle Pinecone deletion
8255 + if (empty($pinecone_options['mxchat_pinecone_host']) ||
8256 + empty($pinecone_options['mxchat_pinecone_api_key'])) {
8257 + wp_die(esc_html__('Pinecone configuration is missing.', 'mxchat'));
8258 + }
8259 +
8260 + // Delete from Pinecone using the vector ID directly
8261 + $pinecone_manager = MxChat_Pinecone_Manager::get_instance();
8262 + $result = $pinecone_manager->mxchat_delete_from_pinecone_by_vector_id(
8263 + $id,
8264 + $pinecone_options['mxchat_pinecone_api_key'],
8265 + $pinecone_options['mxchat_pinecone_host']
8266 + );
8267 + if ($result['success']) {
8268 + $success = true;
8269 +
8270 + // Remove from vector cache
8271 + $pinecone_manager->mxchat_remove_from_pinecone_vector_cache($id);
8272 + $pinecone_manager->mxchat_remove_from_processed_content_caches($id);
8273 +
8274 + set_transient('mxchat_admin_notice_success',
8275 + esc_html__('Vector deleted successfully from Pinecone.', 'mxchat'), 30);
8276 + } else {
8277 + $error_message = $result['message'];
8278 + set_transient('mxchat_admin_notice_error',
8279 + esc_html__('Failed to delete from Pinecone: ', 'mxchat') . esc_html($error_message), 30);
8280 + }
8281 + } else {
8282 + //error_log('[MXCHAT-DELETE] Deleting from WordPress database, ID: ' . $id);
8283 +
8284 + // Handle WordPress database deletion
8285 + global $wpdb;
8286 + $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
8287 +
8288 + // Clear cache and delete prompt
8289 + wp_cache_delete('prompt_' . $id, 'mxchat_prompts');
8290 +
8291 + $result = $wpdb->delete(
8292 + $table_name,
8293 + array('id' => intval($id)),
8294 + array('%d')
8295 + );
8296 +
8297 + if ($result !== false) {
8298 + $success = true;
8299 + set_transient('mxchat_admin_notice_success',
8300 + esc_html__('Entry deleted successfully.', 'mxchat'), 30);
8301 + } else {
8302 + set_transient('mxchat_admin_notice_error',
8303 + esc_html__('Failed to delete entry from database.', 'mxchat'), 30);
8304 + }
8305 + }
8306 +
8307 + // Redirect back to the prompts page
8308 + wp_safe_redirect(add_query_arg(
8309 + array(
8310 + 'page' => 'mxchat-prompts',
8311 + 'deleted' => $success ? 'true' : 'false'
8312 + ),
8313 + admin_url('admin.php')
8314 + ));
8315 + exit;
8316 + }
8317 +
8318 +
8319 +
8320 +
8321 + /**
8322 + * Averages multiple vectors into a single vector
8323 + */
8324 + private function mxchat_average_vectors($vectors) {
8325 + $vector_length = count($vectors[0]);
8326 + $sum_vector = array_fill(0, $vector_length, 0);
8327 +
8328 + foreach ($vectors as $vector) {
8329 + for ($i = 0; $i < $vector_length; $i++) {
8330 + $sum_vector[$i] += $vector[$i];
8331 + }
8332 + }
8333 +
8334 + // Divide each component by the number of vectors to get the average
8335 + $num_vectors = count($vectors);
8336 + for ($i = 0; $i < $vector_length; $i++) {
8337 + $sum_vector[$i] /= $num_vectors;
8338 + }
8339 +
8340 + return $sum_vector;
8341 + }
8342 +
8343 +
8344 +
8345 +
8346 + /**
8347 + * Generates embeddings from input text for MXChat
8348 + */
8349 + public function mxchat_generate_embedding($text) {
8350 + // Enable detailed logging for debugging
8351 + //error_log('[MXCHAT-EMBED] Starting embedding generation. Text length: ' . strlen($text) . ' bytes');
8352 + //error_log('[MXCHAT-EMBED] Text preview: ' . substr($text, 0, 100) . '...');
8353 +
8354 + $options = get_option('mxchat_options');
8355 + $selected_model = $options['embedding_model'] ?? 'text-embedding-ada-002';
8356 + //error_log('[MXCHAT-EMBED] Selected embedding model: ' . $selected_model);
8357 +
8358 + // Determine provider and endpoint
8359 + if (strpos($selected_model, 'voyage') === 0) {
8360 + $api_key = $options['voyage_api_key'] ?? '';
8361 + $endpoint = 'https://api.voyageai.com/v1/embeddings';
8362 + $provider_name = 'Voyage AI';
8363 + //error_log('[MXCHAT-EMBED] Using Voyage AI API');
8364 + } elseif (strpos($selected_model, 'gemini-embedding') === 0) {
8365 + $api_key = $options['gemini_api_key'] ?? '';
8366 + $endpoint = 'https://generativelanguage.googleapis.com/v1beta/models/' . $selected_model . ':embedContent';
8367 + $provider_name = 'Google Gemini';
8368 + //error_log('[MXCHAT-EMBED] Using Google Gemini API');
8369 + } else {
8370 + $api_key = $options['api_key'] ?? '';
8371 + $endpoint = 'https://api.openai.com/v1/embeddings';
8372 + $provider_name = 'OpenAI';
8373 + //error_log('[MXCHAT-EMBED] Using OpenAI API');
8374 + }
8375 +
8376 + //error_log('[MXCHAT-EMBED] Using endpoint: ' . $endpoint);
8377 +
8378 + if (empty($api_key)) {
8379 + $error_message = sprintf('Missing %s API key. Please configure your API key in the MxChat settings.', $provider_name);
8380 + //error_log('[MXCHAT-EMBED] Error: ' . $error_message);
8381 + return $error_message;
8382 + }
8383 +
8384 + // Check if text is too long (for OpenAI, roughly estimate tokens as words/0.75)
8385 + $estimated_tokens = ceil(str_word_count($text) / 0.75);
8386 + //error_log('[MXCHAT-EMBED] Estimated token count: ~' . $estimated_tokens);
8387 +
8388 + if ($estimated_tokens > 8000 && strpos($selected_model, 'voyage') === false && strpos($selected_model, 'gemini-embedding') === false) {
8389 + //error_log('[MXCHAT-EMBED] Warning: Text may exceed OpenAI token limits (8K for most models)');
8390 + // Consider truncating text here
8391 + }
8392 +
8393 + // Prepare request body based on provider
8394 + if (strpos($selected_model, 'gemini-embedding') === 0) {
8395 + // Gemini API format
8396 + $request_body = array(
8397 + 'model' => 'models/' . $selected_model,
8398 + 'content' => array(
8399 + 'parts' => array(
8400 + array('text' => $text)
8401 + )
8402 + )
8403 + );
8404 +
8405 + // Set output dimensionality to 1536 for consistency with other models
8406 + $request_body['outputDimensionality'] = 1536;
8407 + } else {
8408 + // OpenAI/Voyage API format
8409 + $request_body = array(
8410 + 'model' => $selected_model,
8411 + 'input' => $text
8412 + );
8413 +
8414 + // Add output_dimension for voyage-3-large model
8415 + if ($selected_model === 'voyage-3-large') {
8416 + $request_body['output_dimension'] = 2048;
8417 + }
8418 + }
8419 +
8420 + //error_log('[MXCHAT-EMBED] Request prepared with model: ' . $selected_model);
8421 +
8422 + // Prepare headers based on provider
8423 + if (strpos($selected_model, 'gemini-embedding') === 0) {
8424 + // Gemini uses API key as query parameter
8425 + $endpoint .= '?key=' . $api_key;
8426 + $headers = array(
8427 + 'Content-Type' => 'application/json'
8428 + );
8429 + } else {
8430 + // OpenAI/Voyage use Bearer token
8431 + $headers = array(
8432 + 'Authorization' => 'Bearer ' . $api_key,
8433 + 'Content-Type' => 'application/json'
8434 + );
8435 + }
8436 +
8437 + // Make API request
8438 + //error_log('[MXCHAT-EMBED] Sending API request to: ' . $endpoint);
8439 + $response = wp_remote_post($endpoint, array(
8440 + 'body' => wp_json_encode($request_body),
8441 + 'headers' => $headers,
8442 + 'timeout' => 60 // Increased timeout for large inputs
8443 + ));
8444 +
8445 + // Handle wp_remote_post errors
8446 + if (is_wp_error($response)) {
8447 + $error_message = $response->get_error_message();
8448 + //error_log('[MXCHAT-EMBED] WP Remote Post Error: ' . $error_message);
8449 + return 'Connection error: ' . $error_message;
8450 + }
8451 +
8452 + // Get and check HTTP response code
8453 + $http_code = wp_remote_retrieve_response_code($response);
8454 + //error_log('[MXCHAT-EMBED] API Response Code: ' . $http_code);
8455 +
8456 + if ($http_code !== 200) {
8457 + $error_body = wp_remote_retrieve_body($response);
8458 + //error_log('[MXCHAT-EMBED] API Error Response Body: ' . $error_body);
8459 +
8460 + // Try to parse error for more details
8461 + $error_json = json_decode($error_body, true);
8462 + if (json_last_error() === JSON_ERROR_NONE && isset($error_json['error'])) {
8463 + $error_type = $error_json['error']['type'] ?? 'unknown';
8464 + $error_message = $error_json['error']['message'] ?? 'No message';
8465 + //error_log('[MXCHAT-EMBED] API Error Type: ' . $error_type);
8466 + //error_log('[MXCHAT-EMBED] API Error Message: ' . $error_message);
8467 +
8468 + // Customize error message for common API errors
8469 + if ($error_type === 'invalid_request_error' && strpos($error_message, 'API key') !== false) {
8470 + $error_message = sprintf('Invalid %s API key. Please check your API key in the MxChat settings.', $provider_name);
8471 + } elseif ($error_type === 'authentication_error') {
8472 + $error_message = sprintf('%s authentication failed. Please verify your API key in the MxChat settings.', $provider_name);
8473 + }
8474 +
8475 + //error_log('[MXCHAT-EMBED] Returning error: ' . $error_message);
8476 + return $error_message;
8477 + }
8478 +
8479 + $error_message = sprintf("API Error (HTTP %d): Unable to generate embedding", $http_code);
8480 + //error_log('[MXCHAT-EMBED] Returning error: ' . $error_message);
8481 + return $error_message;
8482 + }
8483 +
8484 + // Parse response body
8485 + $response_body = wp_remote_retrieve_body($response);
8486 + //error_log('[MXCHAT-EMBED] Received response length: ' . strlen($response_body) . ' bytes');
8487 +
8488 + $response_data = json_decode($response_body, true);
8489 +
8490 + if (json_last_error() !== JSON_ERROR_NONE) {
8491 + $error = json_last_error_msg();
8492 + //error_log('[MXCHAT-EMBED] JSON Parse Error: ' . $error);
8493 + //error_log('[MXCHAT-EMBED] Response preview: ' . substr($response_body, 0, 200));
8494 + return "Failed to parse API response: $error";
8495 + }
8496 +
8497 + // Handle different response formats based on provider
8498 + if (strpos($selected_model, 'gemini-embedding') === 0) {
8499 + // Gemini API response format
8500 + if (isset($response_data['embedding']['values'])) {
8501 + $embedding_dimensions = count($response_data['embedding']['values']);
8502 + //error_log('[MXCHAT-EMBED] Successfully extracted Gemini embedding with ' . $embedding_dimensions . ' dimensions');
8503 +
8504 + // Check if embedding dimensions are as expected (should be 1536)
8505 + if ($embedding_dimensions !== 1536) {
8506 + //error_log('[MXCHAT-EMBED] Warning: Unexpected Gemini embedding dimensions: ' . $embedding_dimensions);
8507 + }
8508 +
8509 + return $response_data['embedding']['values'];
8510 + } else {
8511 + //error_log('[MXCHAT-EMBED] Error: No embedding found in Gemini response');
8512 + //error_log('[MXCHAT-EMBED] Response structure: ' . wp_json_encode(array_keys($response_data)));
8513 +
8514 + if (isset($response_data['error'])) {
8515 + $error_message = "Gemini API Error in response: " . wp_json_encode($response_data['error']);
8516 + //error_log('[MXCHAT-EMBED] ' . $error_message);
8517 + return $error_message;
8518 + }
8519 +
8520 + $error_message = "Invalid Gemini API response format: No embedding found";
8521 + //error_log('[MXCHAT-EMBED] ' . $error_message);
8522 + return $error_message;
8523 + }
8524 + } else {
8525 + // OpenAI/Voyage API response format
8526 + if (isset($response_data['data'][0]['embedding'])) {
8527 + $embedding_dimensions = count($response_data['data'][0]['embedding']);
8528 + //error_log('[MXCHAT-EMBED] Successfully extracted embedding with ' . $embedding_dimensions . ' dimensions');
8529 +
8530 + // Check if embedding dimensions are as expected
8531 + if (($selected_model === 'text-embedding-ada-002' && $embedding_dimensions !== 1536) ||
8532 + ($selected_model === 'voyage-3-large' && $embedding_dimensions !== 2048)) {
8533 + //error_log('[MXCHAT-EMBED] Warning: Unexpected embedding dimensions');
8534 + }
8535 +
8536 + return $response_data['data'][0]['embedding'];
8537 + } else {
8538 + //error_log('[MXCHAT-EMBED] Error: No embedding found in response');
8539 + //error_log('[MXCHAT-EMBED] Response structure: ' . wp_json_encode(array_keys($response_data)));
8540 +
8541 + if (isset($response_data['error'])) {
8542 + $error_message = "API Error in response: " . wp_json_encode($response_data['error']);
8543 + //error_log('[MXCHAT-EMBED] ' . $error_message);
8544 + return $error_message;
8545 + }
8546 +
8547 + $error_message = "Invalid API response format: No embedding found";
8548 + //error_log('[MXCHAT-EMBED] ' . $error_message);
8549 + return $error_message;
8550 + }
8551 + }
8552 + }
6129 8553
6130 8554
6131 8555
6132 8556 }