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 +5809 -3096 2.0.43.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,49 +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'));
49 - add_action('wp_ajax_mxchat_save_prompts_setting', array($this, 'mxchat_save_prompts_setting_callback'));
50 - if (isset($this->options['enable_woocommerce_integration']) &&
51 - ($this->options['enable_woocommerce_integration'] === '1' ||
52 - $this->options['enable_woocommerce_integration'] === 'on')) {
56 + add_action('admin_post_mxchat_edit_intent', array($this, 'mxchat_handle_edit_intent'));
57 + add_action('wp_ajax_mxchat_export_transcripts', array($this, 'export_chat_transcripts'));
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'));
53 62
54 - //error_log('MxChat Admin: WooCommerce integration is enabled, adding hooks');
55 - add_action('wp_insert_post', array($this, 'mxchat_handle_product_change'), 10, 3);
56 - add_action('wp_trash_post', array($this, 'mxchat_handle_product_delete'));
57 - add_action('before_delete_post', array($this, 'mxchat_handle_product_delete'));
58 - }
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'));
59 90 }
60 91
61 - // Method to check if the license is active
62 92 private function is_license_active() {
63 - $license_status = get_option('mxchat_license_status', esc_html__('inactive', 'mxchat'));
64 - return $license_status === esc_html__('active', 'mxchat');
65 - }
93 + // Get the raw value without translation
94 + $license_status = get_option('mxchat_license_status', 'inactive');
66 95
67 - // 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 +
68 100 private function initialize_default_options() {
69 101 $default_options = array(
70 102 'api_key' => '',
71 103 'xai_api_key' => '',
@@ -70,25 +102,51 @@
70 102 'api_key' => '',
71 103 'xai_api_key' => '',
72 104 'claude_api_key' => '',
73 105 'deepseek_api_key' => '',
74 - 'system_prompt_instructions' => esc_html__('[EXAMPLE INSTRUCTIONS] You are an AI Chatbot assistant for this website. The primary subject you should focus on is [insert proper subject here]. Your main goal is to assist visitors with questions related to this specific topic. Here are some key things to keep in mind:
75 - - Your name is [Chatbot Name].
76 - - Stay focused on topics related to [insert proper subject here]. If a visitor asks about an unrelated topic, politely redirect the conversation to how you can assist them with this subject.
77 - - Do not make up links to pages. Only use specific links you see in your knowledgebase from [insert your domain]. Do not make assumptions or make up URLs.
78 - - Keep your responses short, concise, and to the point. Provide clear and direct answers suitable for a chatbot interaction.
79 - - Provide answers based on the knowledge available to you. If you do not have an answer to a specific question, let the visitor know that you don’t have the information.', 'mxchat'),
80 - 'model' => esc_html__('gpt-4o', 'mxchat'),
106 + 'voyage_api_key' => '',
107 + 'gemini_api_key' => '',
108 + 'enable_streaming_toggle' => 'on',
109 + 'enable_web_search' => 'off',
110 + 'embedding_model' => 'text-embedding-ada-002',
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'),
81 131 'rate_limit_logged_out' => esc_html__('100', 'mxchat'),
82 132 'role_rate_limits' => array(),
83 133 'rate_limit_message' => esc_html__('Rate limit exceeded. Please try again later.', 'mxchat'),
84 134 'enable_email_block' => '',
85 - 'email_blocker_header_content' => esc_html__("<h2>Welcome to Our Chat!</h2>\n<p>Let's get started. Enter your email to begin chatting with us.</p>", 'mxchat'),
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'),
86 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
87 139 'top_bar_title' => esc_html__('MxChat', 'mxchat'),
88 - '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'),
89 142 'input_copy' => esc_html__('How can I assist?', 'mxchat'),
90 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',
91 149 'close_button_color' => esc_html__('#fff', 'mxchat'),
92 150 'chatbot_bg_color' => esc_html__('#fff', 'mxchat'),
93 151 'user_message_bg_color' => esc_html__('#fff', 'mxchat'),
94 152 'user_message_font_color' => esc_html__('#212121', 'mxchat'),
@@ -105,28 +163,42 @@
105 163
106 164 // New fields for Loops Integration
107 165 'loops_api_key' => '',
108 166 'loops_mailing_list' => '',
109 - 'triggered_phrase_response' => esc_html__('Would you like to join our mailing list? Please provide your email below.', 'mxchat'),
110 - 'email_capture_response' => esc_html__('Thank you for providing your email! You\'ve been added to our list.', 'mxchat'),
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'),
111 169 'popular_question_1' => '',
112 170 'popular_question_2' => '',
113 171 'popular_question_3' => '',
114 - 'pdf_intent_trigger_text' => esc_html__("Please provide the URL to the PDF you'd like to discuss.", 'mxchat'),
115 - 'pdf_intent_success_text' => esc_html__("I've processed the PDF. What questions do you have about it?", 'mxchat'),
116 - 'pdf_intent_error_text' => esc_html__("Sorry, I couldn't process the PDF. Please ensure it's a valid file.", 'mxchat'),
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'),
117 175 'pdf_max_pages' => 69,
176 + 'show_pdf_upload_button' => 'on',
177 + 'show_word_upload_button' => 'on',
118 178
119 - // Live Agent Integration
179 + // Live Agent Integration (Slack)
120 180 'live_agent_webhook_url' => '',
121 181 'live_agent_secret_key' => '',
122 182 'live_agent_bot_token' => '',
123 183 'live_agent_message_bg_color' => esc_html__('#ffffff', 'mxchat'),
124 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 +
125 194 'chat_toolbar_toggle' => esc_html__('off', 'mxchat'),
126 195 'mode_indicator_bg_color' => esc_html__('#767676', 'mxchat'),
127 196 'mode_indicator_font_color' => esc_html__('#ffffff', 'mxchat'),
128 197 'toolbar_icon_color' => esc_html__('#212121', 'mxchat'),
198 +
199 + // Optimization settings
200 + 'script_loading_strategy' => 'default',
129 201 );
130 202
131 203
132 204 // Merge existing options with defaults
@@ -149,10 +221,8 @@
149 221 // Update the $this->options property
150 222 $this->options = $merged_options;
151 223 }
152 224
153 -
154 -
155 225 public function mxchat_add_plugin_page() {
156 226 // Main menu page
157 227 add_menu_page(
158 228 esc_html__('MxChat Settings', 'mxchat'),
@@ -163,8 +233,18 @@
163 233 'dashicons-testimonial',
164 234 6
165 235 );
166 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 +
167 247 // Submenu page for Knowledge
168 248 add_submenu_page(
169 249 'mxchat-max',
170 250 esc_html__('Prompts', 'mxchat'),
@@ -173,42 +253,31 @@
173 253 'mxchat-prompts',
174 254 array($this, 'mxchat_create_prompts_page')
175 255 );
176 256
177 - // Submenu page for Chat Transcripts
178 257 add_submenu_page(
179 - 'mxchat-max', // Corrected parent slug to match the main menu
258 + 'mxchat-max',
180 259 esc_html__('Chat Transcripts', 'mxchat'),
181 260 esc_html__('Transcripts', 'mxchat'),
182 261 'manage_options',
183 262 'mxchat-transcripts',
184 - array($this, 'mxchat_create_transcripts_page') // Prefixed function name with mxchat_
263 + array($this, 'mxchat_create_transcripts_page')
185 264 );
186 265
187 - // Submenu page for Intents
188 266 add_submenu_page(
189 - 'mxchat-max', // Parent slug
190 - esc_html__('MxChat Intents', 'mxchat'), // Page title
191 - esc_html__('Intents', 'mxchat'), // Menu title
192 - 'manage_options', // Capability
193 - 'mxchat-intents', // Menu slug
194 - array($this, 'mxchat_intents_page_html') // Callback function
267 + 'mxchat-max',
268 + esc_html__('MxChat Actions', 'mxchat'),
269 + esc_html__('Actions', 'mxchat'),
270 + 'manage_options',
271 + 'mxchat-actions',
272 + array($this, 'mxchat_actions_page_html')
195 273 );
196 274
197 - add_submenu_page(
198 - 'mxchat-max',
199 - esc_html__('Add Ons', 'mxchat'),
200 - esc_html__('Add Ons', 'mxchat'),
201 - 'manage_options',
202 - 'mxchat-addons',
203 - array($this, 'mxchat_create_addons_page')
204 - );
205 -
206 - // Submenu page for Activation Key
275 + // Consolidated Pro & Extensions page (replaces separate Add Ons and Pro Upgrade pages)
207 276 add_submenu_page(
208 277 'mxchat-max',
209 - esc_html__('Pro Upgrade', 'mxchat'),
210 - esc_html__('Pro Upgrade', 'mxchat'),
278 + esc_html__('Pro & Extensions', 'mxchat'),
279 + esc_html__('Pro & Extensions', 'mxchat'),
211 280 'manage_options',
212 281 'mxchat-activation',
213 282 array($this, 'mxchat_create_activation_page')
214 283 );
@@ -219,154 +288,525 @@
219 288 $addons_page = new MxChat_Addons();
220 289 $addons_page->render_page();
221 290 }
222 291
223 -public function mxchat_save_setting_callback() {
224 - check_ajax_referer('mxchat_save_setting_nonce');
225 - if (!current_user_can('manage_options')) {
226 - wp_send_json_error(['message' => esc_html__('Unauthorized', 'mxchat')]);
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;
227 302 }
228 - $name = isset($_POST['name']) ? $_POST['name'] : '';
229 - // Strip slashes from the value before saving
230 - $value = isset($_POST['value']) ? stripslashes($_POST['value']) : '';
231 - if (empty($name)) {
232 - wp_send_json_error(['message' => esc_html__('Invalid field name', 'mxchat')]);
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;
233 308 }
234 - // Load the full options array
309 +
310 + // Get user's selected model and API key
235 311 $options = get_option('mxchat_options', []);
236 - // Handle special cases
237 - switch ($name) {
238 - case 'additional_popular_questions':
239 - $questions = json_decode($value, true); // No need for stripslashes here
240 - if (is_array($questions)) {
241 - $options[$name] = $questions;
242 - // Also update old option for backwards compatibility
243 - update_option('additional_popular_questions', $questions);
244 - }
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'] ?? '';
245 324 break;
246 - case 'user_message_bg_color':
247 - case 'user_message_font_color':
248 - case 'bot_message_bg_color':
249 - case 'bot_message_font_color':
250 - case 'top_bar_bg_color':
251 - case 'send_button_font_color':
252 - case 'chatbot_background_color':
253 - case 'icon_color':
254 - case 'chat_input_font_color':
255 - case 'live_agent_message_bg_color':
256 - case 'live_agent_message_font_color':
257 - case 'mode_indicator_bg_color':
258 - case 'mode_indicator_font_color':
259 - case 'toolbar_icon_color':
260 - // Store color values directly
261 - $options[$name] = $value;
325 + case 'claude':
326 + $api_key = $options['claude_api_key'] ?? '';
262 327 break;
263 - case 'live_agent_status':
264 - // Set the new value
265 - $options[$name] = ($value === 'on') ? 'on' : 'off';
328 + case 'grok':
329 + $api_key = $options['xai_api_key'] ?? '';
266 330 break;
267 - case 'enable_woocommerce_integration':
268 - // Handle values that used to be 1/0
269 - $options[$name] = ($value === 'on' || $value === '1') ? 'on' : 'off';
331 + case 'deepseek':
332 + $api_key = $options['deepseek_api_key'] ?? '';
270 333 break;
334 + case 'gemini':
335 + $api_key = $options['gemini_api_key'] ?? '';
336 + break;
271 337 default:
272 - // Handle role rate limits
273 - if (strpos($name, 'mxchat_options[role_rate_limits]') !== false) {
274 - // Extract role ID from the name
275 - preg_match('/\[role_rate_limits\]\[(.*?)\]/', $name, $matches);
276 - if (isset($matches[1])) {
277 - $role_id = $matches[1];
278 - // Initialize role_rate_limits if it doesn't exist
279 - if (!isset($options['role_rate_limits'])) {
280 - $options['role_rate_limits'] = [];
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();
281 510 }
282 - // Update the specific role's rate limit
283 - $options['role_rate_limits'][$role_id] = sanitize_text_field($value);
284 - break;
285 511 }
286 512 }
287 - // Handle toggles
288 - if (strpos($name, 'toggle') !== false || in_array($name, [
289 - 'chat_persistence_toggle',
290 - 'privacy_toggle',
291 - 'complianz_toggle',
292 - 'chat_toolbar_toggle' // Removed live_agent_status from here
293 - ])) {
294 - $options[$name] = ($value === 'on') ? 'on' : 'off';
295 - } else {
296 - // Store all other values directly
297 - $options[$name] = $value;
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 + }
298 532 }
299 - break;
533 + }
300 534 }
301 - // Save all updates
302 - $updated = update_option('mxchat_options', $options);
303 - // Handle backwards compatibility for certain fields
304 - $legacy_fields = ['brave_api_key', 'brave_image_count', 'brave_safe_search', 'brave_news_count',
305 - 'brave_country', 'brave_language', 'similarity_threshold'];
306 - if (in_array($name, $legacy_fields)) {
307 - // Also update the individual option for backwards compatibility
308 - update_option($name, $value);
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;
309 609 }
310 - if ($updated) {
311 - 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')));
312 682 } else {
313 - 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));
314 685 }
315 686 }
316 687
317 688 /**
318 - * Handles AJAX auto-save for prompts and auto-sync settings.
689 + * AJAX handler to test Slack connection and validate scopes
319 690 */
320 -public function mxchat_save_prompts_setting_callback() {
321 - // Check the correct nonce for this action.
322 - check_ajax_referer( 'mxchat_prompts_setting_nonce', '_ajax_nonce' );
691 +public function mxchat_test_slack_connection() {
692 + check_ajax_referer('mxchat_admin_nonce', 'nonce');
323 693
324 - if ( ! current_user_can( 'manage_options' ) ) {
325 - 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;
326 697 }
327 698
328 - $name = isset( $_POST['name'] ) ? sanitize_text_field( wp_unslash( $_POST['name'] ) ) : '';
329 - $value = isset( $_POST['value'] ) ? sanitize_text_field( wp_unslash( $_POST['value'] ) ) : '';
699 + $bot_token = $this->options['live_agent_bot_token'] ?? '';
330 700
331 - if ( empty( $name ) ) {
332 - 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;
333 704 }
334 705
335 - // Handle standalone auto-sync settings.
336 - if ( in_array( $name, [ 'mxchat_auto_sync_posts', 'mxchat_auto_sync_pages' ], true ) ) {
337 - // Convert checkbox value to "1" if checked, "0" if not.
338 - $value = ( $value === 'on' ) ? '1' : '0';
339 - update_option( $name, $value );
340 - wp_send_json_success( [ 'message' => esc_html__( 'Setting saved', 'mxchat' ) ] );
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;
341 718 }
342 719
343 - // Handle fields stored in the 'mxchat_prompts_options' array.
344 -// Handle fields stored in the 'mxchat_prompts_options' array.
345 -if ( false !== strpos( $name, 'mxchat_prompts_options[' ) ) {
346 - // Extract the key name using regex.
347 - if ( preg_match( '/mxchat_prompts_options\[(.*?)\]/', $name, $matches ) && ! empty( $matches[1] ) ) {
348 - $key = sanitize_text_field( $matches[1] );
349 - $options = get_option( 'mxchat_prompts_options', [] );
720 + $auth_body = json_decode(wp_remote_retrieve_body($auth_response), true);
350 721
351 - // Remove Pinecone-specific settings from the main plugin handling.
352 - if ( 'mxchat_use_pinecone' === $key ) {
353 - // Optionally, you could simply ignore this field or set a default value.
354 - // For example, ensure it's always 0 or remove it entirely:
355 - // $options[ $key ] = '0';
356 - // Or, if you prefer, just return an error so that the main plugin doesn't handle it:
357 - wp_send_json_error( [ 'message' => esc_html__( 'Pinecone settings are now handled in the add-on.', 'mxchat' ) ] );
358 - } else {
359 - // For all other fields, use the existing logic.
360 - $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;
361 783 }
362 - update_option( 'mxchat_prompts_options', $options );
363 - wp_send_json_success( [ 'message' => esc_html__( 'Setting saved', 'mxchat' ) ] );
364 784 }
365 -}
366 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');
367 793
368 - 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 + }
369 809 }
370 810
371 811 public function mxchat_display_admin_notice() {
372 812 // Success notice
@@ -391,2744 +831,3619 @@
391 831 delete_transient('mxchat_admin_notice_error'); // Clear the transient after displaying
392 832 }
393 833 }
394 834
835 +public function show_live_agent_disabled_banner() {
836 + $show_disabled_notice = get_option('mxchat_show_live_agent_disabled_notice', false);
395 837
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>
852 + </div>
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();
396 860
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 +}
397 865
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));
398 870
399 -public function mxchat_create_admin_page() {
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 + }
400 876
401 - ?>
402 - <div class="wrap mxchat-admin">
403 - <?php if (!$this->is_activated): ?>
404 - <div class="mxchat-pro-banner">
405 - <p>
406 - <?php echo esc_html__('For a limited time, get lifetime access and save $20 on MxChat Pro!', 'mxchat'); ?>
407 - <a href="https://mxchat.ai/" target="_blank"><?php echo esc_html__('Upgrade to Pro today', 'mxchat'); ?></a>
408 - </p>
409 - </div>
410 - <?php endif; ?>
877 + // Remove the notice flag
878 + $deleted = delete_option('mxchat_show_live_agent_disabled_notice');
879 + //error_log('Option deleted: ' . ($deleted ? 'yes' : 'no'));
411 880
412 - <div class="mxchat-agents-banner">
413 - <p>
414 - <strong><?php echo esc_html__('The MxChat Add-Ons Platform', 'mxchat'); ?></strong><?php echo esc_html__(' is here! Pro users get unlimited access to our growing collection of powerful add-ons. ', 'mxchat'); ?><a href="<?php echo admin_url('admin.php?page=mxchat-addons'); ?>"><?php echo esc_html__('Explore all add-ons', 'mxchat'); ?></a><?php echo esc_html__(' including Forms Builder, Theme Customizer, Content Moderation, and more – all included with your lifetime Pro license!', 'mxchat'); ?>
415 - </p>
416 - </div>
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 +}
417 892
418 - <h2 class="mxchat-nav-tab-wrapper">
419 - <a href="#chatbot" class="mxchat-nav-tab mxchat-nav-tab-active" data-tab="chatbot"><?php echo esc_html__('Chatbot', 'mxchat'); ?></a>
420 - <a href="#embed" class="mxchat-nav-tab" data-tab="embed"><?php echo esc_html__('Integrations', 'mxchat'); ?></a>
421 - <a href="#general" class="mxchat-nav-tab" data-tab="general"><?php echo esc_html__('FAQ', 'mxchat'); ?></a>
422 - </h2>
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 + }
423 902
424 - <div id="chatbot" class="mxchat-tab-content active">
425 - <div class="mxchat-autosave-section">
426 - <?php do_settings_sections('mxchat-chatbot'); ?>
903 + // Check if notice should be shown
904 + $show_notice = get_option('mxchat_show_theme_migration_notice', false);
905 +
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>
919 + </div>
427 920 </div>
428 921 </div>
922 + <?php
923 + }
924 +}
429 925
430 - <div id="embed" class="mxchat-tab-content">
431 - <div class="mxchat-autosave-section">
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 + }
432 934
433 - <div class="mxchat-settings-section">
434 - <h2><?php echo esc_html__('Loops Settings', 'mxchat'); ?></h2>
435 - <table class="form-table">
436 - <?php do_settings_fields('mxchat-embed', 'mxchat_loops_section'); ?>
437 - </table>
438 - </div>
935 + // Remove the notice flag
936 + delete_option('mxchat_show_theme_migration_notice');
439 937
440 - <div class="section-divider"></div>
938 + wp_send_json_success();
939 +}
441 940
442 - <div class="mxchat-settings-section">
443 - <h2><?php echo esc_html__('Brave Search Settings', 'mxchat'); ?></h2>
444 - <table class="form-table">
445 - <?php do_settings_fields('mxchat-embed', 'mxchat_brave_section'); ?>
446 - </table>
447 - </div>
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 + }
951 +}
448 952
449 - <div class="section-divider"></div>
953 +public function mxchat_create_transcripts_page() {
954 + global $wpdb;
955 + $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
450 956
451 - <div class="mxchat-settings-section">
452 - <h2><?php echo esc_html__('Toolbar Settings & Intents', 'mxchat'); ?></h2>
453 - <table class="form-table">
454 - <?php do_settings_fields('mxchat-embed', 'mxchat_pdf_intent_section'); ?>
455 - </table>
456 - </div>
957 + // Get basic stats
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;
457 960
458 - <div class="section-divider"></div>
961 + // Count unique users with detailed breakdown
962 + $total_users = $wpdb->get_var("
963 + SELECT COUNT(DISTINCT
964 + CASE
965 + WHEN user_email != '' AND user_email IS NOT NULL THEN user_email
966 + WHEN user_id != 0 THEN CONCAT('user_', user_id)
967 + WHEN user_identifier NOT LIKE 'Tech-Savvy User'
968 + AND user_identifier NOT LIKE 'Detail-Oriented User'
969 + AND user_identifier NOT LIKE 'Language Learner'
970 + AND user_identifier NOT LIKE 'Casual Browser'
971 + AND user_identifier NOT LIKE 'Policy Enforcer'
972 + AND user_identifier NOT LIKE 'Researcher'
973 + AND user_identifier NOT LIKE 'Loyalty Member'
974 + AND user_identifier NOT LIKE 'Gift Buyer'
975 + AND user_identifier NOT LIKE 'Parent or Caregiver'
976 + THEN user_identifier
977 + ELSE session_id
978 + END
979 + )
980 + FROM $table_name
981 + WHERE role != 'assistant'
982 + ");
459 983
460 - <!-- Live Agent Settings Section -->
461 - <div class="mxchat-settings-section">
462 - <h2><?php echo esc_html__('Live Agent Settings', 'mxchat'); ?></h2>
463 - <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>
464 - <table class="form-table">
465 - <?php do_settings_fields('mxchat-embed', 'mxchat_live_agent_section'); ?>
466 - </table>
467 - </div>
468 - </div>
469 - </div>
984 + // Get user type breakdown
985 + $registered_users = $wpdb->get_var("
986 + SELECT COUNT(DISTINCT user_email)
987 + FROM $table_name
988 + WHERE user_email != '' AND user_email IS NOT NULL
989 + ");
470 990
991 + $guest_users = $wpdb->get_var("
992 + SELECT COUNT(DISTINCT user_identifier)
993 + FROM $table_name
994 + WHERE (user_email = '' OR user_email IS NULL)
995 + AND role != 'assistant'
996 + AND user_identifier NOT LIKE 'Tech-Savvy User'
997 + AND user_identifier NOT LIKE 'Detail-Oriented User'
998 + AND user_identifier NOT LIKE 'Language Learner'
999 + AND user_identifier NOT LIKE 'Casual Browser'
1000 + AND user_identifier NOT LIKE 'Policy Enforcer'
1001 + AND user_identifier NOT LIKE 'Researcher'
1002 + AND user_identifier NOT LIKE 'Loyalty Member'
1003 + AND user_identifier NOT LIKE 'Gift Buyer'
1004 + AND user_identifier NOT LIKE 'Parent or Caregiver'
1005 + ");
471 1006
472 - <div id="general" class="mxchat-tab-content">
473 - <?php do_settings_sections('mxchat-general'); ?>
474 - <p>
475 - <?php echo esc_html__('If you’re having trouble with setup or getting the responses you need, we encourage you to review our', 'mxchat'); ?>
476 - <a href="https://mxchat.ai/documentation/" target="_blank" rel="noopener noreferrer"><?php echo esc_html__('documentation', 'mxchat'); ?></a> <?php echo esc_html__('or', 'mxchat'); ?>
477 - <a href="https://wordpress.org/support/plugin/mxchat-basic/" target="_blank" rel="noopener noreferrer"><?php echo esc_html__('create a support ticket', 'mxchat'); ?></a>.
478 - </p>
479 - <p>
480 - <?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>.
481 - </p>
1007 + // Get agent test messages count
1008 + $agent_tests = $wpdb->get_var("
1009 + SELECT COUNT(DISTINCT session_id)
1010 + FROM $table_name
1011 + WHERE user_identifier IN (
1012 + 'Tech-Savvy User',
1013 + 'Detail-Oriented User',
1014 + 'Language Learner',
1015 + 'Casual Browser',
1016 + 'Policy Enforcer',
1017 + 'Researcher',
1018 + 'Loyalty Member',
1019 + 'Gift Buyer',
1020 + 'Parent or Caregiver'
1021 + )
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 + }
482 1099
483 - <div class="faq-item">
484 - <h3><?php echo esc_html__('How does the Claude API integration work?', 'mxchat'); ?></h3>
485 - <p>
486 - <?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'); ?>
487 - </p>
488 - <p>
489 - <?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'); ?>
490 - </p>
491 - <p>
492 - <?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>.
493 - </p>
494 - </div>
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 + );
495 1117
496 - <div class="faq-item">
497 - <h3><?php echo esc_html__('How does the X.AI API integration work?', 'mxchat'); ?></h3>
498 - <p>
499 - <?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'); ?>
500 - </p>
501 - <p>
502 - <?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>.
503 - </p>
504 - </div>
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 +}
505 1122
506 - <div class="faq-item">
507 - <h3><?php echo esc_html__('Do I need an OpenAI API key to use the chatbot?', 'mxchat'); ?></h3>
508 - <p>
509 - <?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'); ?>
510 - </p>
511 - <p>
512 - <?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'); ?>
513 - </p>
514 - </div>
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';
515 1132
516 - <div class="faq-item">
517 - <h3><?php echo esc_html__('How do I add the chatbot to my site?', 'mxchat'); ?></h3>
518 - <p>
519 - <?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'); ?>
520 - </p>
521 - </div>
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 + ");
522 1144
523 - <div class="faq-item">
524 - <h3><?php echo esc_html__('How does the chatbot use my content to generate responses?', 'mxchat'); ?></h3>
525 - <p>
526 - <?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'); ?>
527 - </p>
528 - </div>
1145 + // Prepare chart data
1146 + $chart_labels = array();
1147 + $chart_chats = array();
1148 + $chart_messages = array();
529 1149
530 - <div class="faq-item">
531 - <h3><?php echo esc_html__('Why does the chatbot sometimes make up links or information?', 'mxchat'); ?></h3>
532 - <p>
533 - <?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'); ?>
534 - </p>
535 - </div>
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;
536 1155
537 - <div class="faq-item">
538 - <h3><?php echo esc_html__('How do intents work in MxChat?', 'mxchat'); ?></h3>
539 - <p>
540 - <?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>.
541 - </p>
542 - </div>
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 + }
543 1172
544 - <div class="faq-item">
545 - <h3><?php echo esc_html__('How does the Complianz integration work?', 'mxchat'); ?></h3>
546 - <p>
547 - <?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'); ?>
548 - </p>
549 - </div>
1173 + return array(
1174 + 'labels' => $chart_labels,
1175 + 'chats' => $chart_chats,
1176 + 'messages' => $chart_messages
1177 + );
1178 +}
550 1179
551 - <div class="faq-item">
552 - <h3><?php echo esc_html__('How does the WooCommerce integration work?', 'mxchat'); ?></h3>
553 - <p>
554 - <?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'); ?>
555 - </p>
556 - </div>
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;
1186 + ?>
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>
1196 + <?php
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 +}
557 1211
558 - <div class="faq-item">
559 - <h3><?php echo esc_html__('Why isn\'t my chatbot responding as expected?', 'mxchat'); ?></h3>
560 - <p>
561 - <?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'); ?>
562 - </p>
563 - <p>
564 - <?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'); ?>
565 - </p>
566 - </div>
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 +}
567 1236
568 - <div class="faq-item">
569 - <h3><?php echo esc_html__('What is Loops, and how do I get an API key?', 'mxchat'); ?></h3>
570 - <p>
571 - <?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'); ?>
572 - </p>
573 - </div>
574 -
575 - </div>
576 - </div>
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>
577 1281 <?php
578 1282 }
579 1283
580 1284
581 1285
582 -public function mxchat_create_transcripts_page() {
583 - global $wpdb;
584 - $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
1286 +public function sanitize_transcripts_options($input) {
1287 + $sanitized = array();
585 1288
586 - // 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");
589 - $total_users = $wpdb->get_var("SELECT COUNT(DISTINCT user_email) FROM $table_name WHERE user_email != ''");
590 - ?>
591 - <div class="wrap mxchat-admin">
592 - <!-- Stats Cards -->
593 - <div class="mxchat-stats-grid">
594 - <div class="mxchat-stat-card">
595 - <div class="stat-icon">💬</div>
596 - <div class="stat-content">
597 - <span class="stat-value"><?php echo esc_html($total_chats); ?></span>
598 - <span class="stat-label"><?php esc_html_e('Total Chats', 'mxchat'); ?></span>
599 - </div>
600 - </div>
601 - <div class="mxchat-stat-card">
602 - <div class="stat-icon">📝</div>
603 - <div class="stat-content">
604 - <span class="stat-value"><?php echo esc_html($total_messages); ?></span>
605 - <span class="stat-label"><?php esc_html_e('Total Messages', 'mxchat'); ?></span>
606 - </div>
607 - </div>
608 - <div class="mxchat-stat-card">
609 - <div class="stat-icon">👥</div>
610 - <div class="stat-content">
611 - <span class="stat-value"><?php echo esc_html($total_users); ?></span>
612 - <span class="stat-label"><?php esc_html_e('Unique Users', 'mxchat'); ?></span>
613 - </div>
614 - </div>
615 - </div>
1289 + $sanitized['mxchat_enable_notifications'] = isset($input['mxchat_enable_notifications']) ? 1 : 0;
616 1290
617 - <!-- Search and Filter Controls -->
618 - <div class="mxchat-controls-wrapper">
619 - <div class="mxchat-search-box">
620 - <input type="text" id="mxchat-search-transcripts"
621 - placeholder="<?php esc_attr_e('Search transcripts...', 'mxchat'); ?>"
622 - class="regular-text">
623 - </div>
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 + }
624 1303
625 - <form id="mxchat-delete-form" method="post">
626 - <?php wp_nonce_field('mxchat_delete_chat_history', 'mxchat_delete_chat_nonce'); ?>
627 - <div class="mxchat-controls">
628 - <button type="button" id="mxchat-select-all-transcripts" class="mxchat-select-button">
629 - <span class="dashicons dashicons-yes-alt"></span>
630 - <span class="button-text"><?php esc_html_e('Select All', 'mxchat'); ?></span>
631 - </button>
632 - <button type="submit" class="button delete-chats-button">
633 - <span class="dashicons dashicons-trash"></span>
634 - <?php esc_html_e('Delete Selected', 'mxchat'); ?>
635 - </button>
636 - </div>
637 - </form>
638 - </div>
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 + }
639 1313
640 - <!-- Transcripts Container -->
641 - <div id="mxchat-transcripts"></div>
642 - </div>
643 - <?php
644 -}
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 + }
645 1322
646 -public function mxchat_create_prompts_page() {
647 - global $wpdb;
648 - $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
1323 + // Sanitize auto-email transcript settings
1324 + $sanitized['mxchat_auto_email_transcript_enabled'] = isset($input['mxchat_auto_email_transcript_enabled']) ? 1 : 0;
649 1325
650 - // Display success message if all prompts were deleted
651 - if (isset($_GET['all_deleted']) && $_GET['all_deleted'] === 'true') {
652 - echo '<div class="notice notice-success is-dismissible"><p>' . esc_html__('All knowledge has been deleted successfully.', 'mxchat') . '</p></div>';
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';
653 1333 }
654 1334
655 - // Set up pagination and search query
656 - $nonce = isset($_GET['_wpnonce']) ? sanitize_text_field($_GET['_wpnonce']) : '';
657 - $search_query = (!empty($nonce) && wp_verify_nonce($nonce, 'mxchat_prompts_search_nonce') && isset($_GET['search'])) ? sanitize_text_field($_GET['search']) : '';
658 - $current_page = isset($_GET['paged']) ? absint($_GET['paged']) : 1;
659 - $per_page = 10;
660 - $offset = ($current_page - 1) * $per_page;
1335 + // Sanitize require contact info setting
1336 + $sanitized['mxchat_auto_email_transcript_require_contact'] = isset($input['mxchat_auto_email_transcript_require_contact']) ? 1 : 0;
661 1337
662 - // Modify query to handle search input
663 - $sql_search = "";
664 - if ($search_query) {
665 - $sql_search = $wpdb->prepare("WHERE article_content LIKE %s", '%' . $wpdb->esc_like($search_query) . '%');
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');
666 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 +}
667 1358
668 - // Retrieve total number of prompts, considering search filter
669 - $total_prompts = $wpdb->get_var("SELECT COUNT(*) FROM {$table_name} {$sql_search}");
670 - $total_pages = ceil($total_prompts / $per_page);
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';
671 1390
672 - // Retrieve prompts from the database
673 - $prompts = $wpdb->get_results(
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(
674 1396 $wpdb->prepare(
675 - "SELECT * FROM {$table_name} {$sql_search} ORDER BY timestamp DESC LIMIT %d OFFSET %d",
676 - $per_page,
677 - $offset
1397 + "SELECT DISTINCT session_id FROM {$table_name} WHERE timestamp < %s",
1398 + $cutoff_date
678 1399 )
679 1400 );
680 1401
681 - // Add the pagination links generation here
682 - $page_links = paginate_links(array(
683 - 'base' => add_query_arg(array(
684 - 'paged' => '%#%',
685 - 'search' => urlencode($search_query),
686 - '_wpnonce' => wp_create_nonce('mxchat_prompts_search_nonce')
687 - ), admin_url('admin.php?page=mxchat-prompts')),
688 - 'format' => '',
689 - 'prev_text' => __('&laquo; Previous', 'mxchat'),
690 - 'next_text' => __('Next &raquo;', 'mxchat'),
691 - 'total' => $total_pages,
692 - 'current' => $current_page,
693 - ));
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 + );
694 1409
695 - // Retrieve processing statuses
696 - $pdf_url = get_transient('mxchat_last_pdf_url');
697 - $sitemap_url = get_transient('mxchat_last_sitemap_url');
698 - $pdf_status = $pdf_url ? $this->get_pdf_processing_status($pdf_url) : false;
699 - $sitemap_status = $sitemap_url ? $this->get_sitemap_processing_status($sitemap_url) : false;
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 + }
700 1420
701 - if ($pdf_status && $pdf_status['status'] === 'complete') {
702 - delete_transient('mxchat_last_pdf_url');
703 - $pdf_status = false;
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));
704 1424 }
705 - if ($sitemap_status && $sitemap_status['status'] === 'complete') {
706 - delete_transient('mxchat_last_sitemap_url');
707 - $sitemap_status = false;
1425 +}
1426 +
1427 +public function export_chat_transcripts() {
1428 + if (!current_user_can('manage_options')) {
1429 + wp_die(esc_html__('You do not have sufficient permissions to access this page.', 'mxchat'));
708 1430 }
709 1431
710 - $is_processing = ($pdf_status && ($pdf_status['status'] === 'processing' || $pdf_status['status'] === 'error'))
711 - || ($sitemap_status && ($sitemap_status['status'] === 'processing' || $sitemap_status['status'] === 'error'));
712 - ?>
713 - <div class="wrap mxchat-wrapper">
714 - <!-- Hero Section -->
715 - <div class="mxchat-hero">
716 - <h1 class="mxchat-main-title">
717 - <span class="mxchat-gradient-text">Knowledge Base</span> Manager
718 - </h1>
719 - <p class="mxchat-hero-subtitle">
720 - <?php esc_html_e('Enhance your AI chatbot with custom knowledge. Import, manage, and organize your content to keep responses accurate and relevant.', 'mxchat'); ?>
721 - </p>
722 - </div>
1432 + check_ajax_referer('mxchat_export_transcripts', 'security');
723 1433
724 - <div class="mxchat-content">
725 - <!-- Import Settings Card -->
726 - <div class="mxchat-card">
727 - <h2><?php esc_html_e('Knowledge Import Settings', 'mxchat'); ?></h2>
728 - <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>
1434 + global $wpdb;
1435 + $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
729 1436
730 - <!-- Tab Contents -->
731 - <div class="mxchat-tab-contents">
732 - <!-- Default Database Tab -->
733 - <div id="default-db" class="mxchat-tab-content active">
734 - <!-- Auto-Sync Settings -->
735 - <div class="mxchat-settings-section">
736 - <h3><?php esc_html_e('Auto-Sync Settings', 'mxchat'); ?></h3>
737 - <p class="mxchat-description">
738 - <?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'); ?>
739 - </p>
740 - <div class="mxchat-autosave-section"> <!-- Add this wrapper -->
741 - <div class="mxchat-toggle-group">
742 - <div class="mxchat-toggle-container">
743 - <label class="mxchat-toggle-switch">
744 - <input type="checkbox"
745 - name="mxchat_auto_sync_posts"
746 - class="mxchat-autosave-field"
747 - value="1"
748 - data-nonce="<?php echo wp_create_nonce('mxchat_prompts_setting_nonce'); ?>"
749 - <?php checked(get_option('mxchat_auto_sync_posts', '0'), '1'); ?>>
750 - <span class="mxchat-toggle-slider"></span>
751 - </label>
752 - <span class="mxchat-toggle-label">
753 - <?php esc_html_e('Auto-sync Posts', 'mxchat'); ?>
754 - </span>
755 - </div>
756 - <div class="mxchat-toggle-container">
757 - <label class="mxchat-toggle-switch">
758 - <input type="checkbox"
759 - name="mxchat_auto_sync_pages"
760 - class="mxchat-autosave-field"
761 - value="1"
762 - data-nonce="<?php echo wp_create_nonce('mxchat_prompts_setting_nonce'); ?>"
763 - <?php checked(get_option('mxchat_auto_sync_pages', '0'), '1'); ?>>
764 - <span class="mxchat-toggle-slider"></span>
765 - </label>
766 - <span class="mxchat-toggle-label">
767 - <?php esc_html_e('Auto-sync Pages', 'mxchat'); ?>
768 - </span>
769 - </div>
770 - </div>
771 - </div>
772 - </div>
1437 + // Get all transcripts ordered by session and timestamp
1438 + $results = $wpdb->get_results(
1439 + "SELECT session_id, user_email, user_identifier, role, message, timestamp
1440 + FROM {$table_name}
1441 + ORDER BY session_id, timestamp ASC"
1442 + );
773 1443
774 - <!-- Import Methods -->
775 - <div class="mxchat-import-methods">
776 - <div class="mxchat-method-card">
777 - <h4><?php esc_html_e('Sitemap Import', 'mxchat'); ?></h4>
778 - <p><?php esc_html_e('Import all content from your sitemap automatically.', 'mxchat'); ?></p>
779 - </div>
780 - <div class="mxchat-method-card">
781 - <h4><?php esc_html_e('PDF Import', 'mxchat'); ?></h4>
782 - <p><?php esc_html_e('Import knowledge directly from PDF documents.', 'mxchat'); ?></p>
783 - </div>
784 - <div class="mxchat-method-card">
785 - <h4><?php esc_html_e('Direct URL', 'mxchat'); ?></h4>
786 - <p><?php esc_html_e('Import content from any specific webpage.', 'mxchat'); ?></p>
787 - </div>
788 - <div class="mxchat-method-card">
789 - <h4><?php esc_html_e('Direct Content', 'mxchat'); ?></h4>
790 - <p><?php esc_html_e('Directly submit content to be vectorized.', 'mxchat'); ?></p>
791 - </div>
792 - </div>
1444 + if (empty($results)) {
1445 + wp_send_json_error(array('message' => 'No transcripts found.'));
1446 + wp_die();
1447 + }
793 1448
794 - <!-- Import Form -->
795 - <?php if (!$is_processing) : ?>
796 - <div class="mxchat-import-form">
797 - <form id="mxchat-sitemap-form" method="post"
798 - action="<?php echo esc_url(admin_url('admin-post.php?action=mxchat_submit_sitemap')); ?>">
799 - <?php wp_nonce_field('mxchat_submit_sitemap_action', 'mxchat_submit_sitemap_nonce'); ?>
800 - <div class="mxchat-url-input-group">
801 - <input type="url"
802 - name="sitemap_url"
803 - id="sitemap_url"
804 - placeholder="<?php esc_attr_e('Enter Sitemap, PDF, or Webpage URL', 'mxchat'); ?>"
805 - required />
806 - <button type="submit"
807 - name="submit_sitemap"
808 - class="mxchat-button-primary">
809 - <?php esc_html_e('Import', 'mxchat'); ?>
810 - </button>
811 - </div>
812 - </form>
813 - </div>
814 - <?php endif; ?>
1449 + // Set headers for CSV download
1450 + header('Content-Type: text/csv');
1451 + header('Content-Disposition: attachment; filename="chat-transcripts-' . date('Y-m-d') . '.csv"');
1452 + header('Pragma: no-cache');
1453 + header('Expires: 0');
815 1454
816 - <!-- Processing Status -->
817 - <?php if ($pdf_status && $pdf_status['status'] !== 'complete') : ?>
818 - <div class="mxchat-status-card">
819 - <div class="mxchat-status-header">
820 - <h4><?php esc_html_e('PDF Processing Status', 'mxchat'); ?></h4>
821 - <?php if ($is_processing) : ?>
822 - <form method="post" class="mxchat-stop-form"
823 - action="<?php echo esc_url(admin_url('admin-post.php?action=mxchat_stop_processing')); ?>">
824 - <?php wp_nonce_field('mxchat_stop_processing_action', 'mxchat_stop_processing_nonce'); ?>
825 - <button type="submit" name="stop_processing" class="mxchat-button-secondary">
826 - <?php esc_html_e('Stop Processing', 'mxchat'); ?>
827 - </button>
828 - </form>
829 - <?php endif; ?>
830 - </div>
831 - <div class="mxchat-progress-bar">
832 - <div class="mxchat-progress-fill" style="width: <?php echo esc_attr($pdf_status['percentage']); ?>%"></div>
833 - </div>
834 - <div class="mxchat-status-details">
835 - <p><?php printf(
836 - esc_html__('Progress: %1$d of %2$d pages (%3$d%%)', 'mxchat'),
837 - absint($pdf_status['processed_pages']),
838 - absint($pdf_status['total_pages']),
839 - absint($pdf_status['percentage'])
840 - ); ?></p>
841 - <p><?php printf(
842 - esc_html__('Status: %s', 'mxchat'),
843 - esc_html(ucfirst($pdf_status['status']))
844 - ); ?></p>
845 - <p><?php printf(
846 - esc_html__('Last update: %s', 'mxchat'),
847 - esc_html($pdf_status['last_update'])
848 - ); ?></p>
849 - </div>
850 - </div>
851 - <?php endif; ?>
1455 + // Create output stream
1456 + $output = fopen('php://output', 'w');
852 1457
853 - <?php if ($sitemap_status && $sitemap_status['status'] !== 'complete') : ?>
854 - <div class="mxchat-status-card">
855 - <div class="mxchat-status-header">
856 - <h4><?php esc_html_e('Sitemap Processing Status (Refresh for update)', 'mxchat'); ?></h4>
857 - <?php if ($is_processing) : ?>
858 - <form method="post" class="mxchat-stop-form"
859 - action="<?php echo esc_url(admin_url('admin-post.php?action=mxchat_stop_processing')); ?>">
860 - <?php wp_nonce_field('mxchat_stop_processing_action', 'mxchat_stop_processing_nonce'); ?>
861 - <button type="submit" name="stop_processing" class="mxchat-button-secondary">
862 - <?php esc_html_e('Stop Processing', 'mxchat'); ?>
863 - </button>
864 - </form>
865 - <?php endif; ?>
866 - </div>
867 - <div class="mxchat-progress-bar">
868 - <div class="mxchat-progress-fill" style="width: <?php echo esc_attr($sitemap_status['percentage']); ?>%"></div>
869 - </div>
870 - <div class="mxchat-status-details">
871 - <p><?php printf(
872 - esc_html__('Progress: %1$d of %2$d URLs (%3$d%%)', 'mxchat'),
873 - absint($sitemap_status['processed_urls']),
874 - absint($sitemap_status['total_urls']),
875 - absint($sitemap_status['percentage'])
876 - ); ?></p>
877 - <?php if (!empty($sitemap_status['error']) || !empty($sitemap_status['last_error'])) : ?>
878 - <div class="mxchat-error-notice">
879 - <?php if (!empty($sitemap_status['error'])) : ?>
880 - <p class="error"><?php echo esc_html($sitemap_status['error']); ?></p>
881 - <?php endif; ?>
882 - <?php if (!empty($sitemap_status['last_error'])) : ?>
883 - <p class="last-error"><?php echo esc_html__('Last error:', 'mxchat') . ' ' . esc_html($sitemap_status['last_error']); ?></p>
884 - <?php endif; ?>
885 - </div>
886 - <?php endif; ?>
887 - </div>
888 - </div>
889 - <?php endif; ?>
890 - </div>
1458 + // Add UTF-8 BOM for proper Excel encoding
1459 + fputs($output, "\xEF\xBB\xBF");
891 1460
1461 + // Add CSV headers
1462 + fputcsv($output, array(
1463 + 'Session ID',
1464 + 'Email',
1465 + 'User Identifier',
1466 + 'Role',
1467 + 'Message',
1468 + 'Timestamp'
1469 + ));
892 1470
893 - </div>
894 - </div>
1471 + // Add data rows
1472 + foreach ($results as $row) {
1473 + fputcsv($output, array(
1474 + $row->session_id,
1475 + $row->user_email,
1476 + $row->user_identifier,
1477 + $row->role,
1478 + $row->message,
1479 + $row->timestamp
1480 + ));
1481 + }
895 1482
896 - <!-- Direct Content Submission Card -->
897 -<div class="mxchat-card">
898 - <div class="mxchat-card-header">
899 - <h2><?php esc_html_e('Submit Direct Content', 'mxchat'); ?></h2>
900 - </div>
901 - <form id="mxchat-content-form" method="post"
902 - action="<?php echo esc_url(admin_url('admin-post.php?action=mxchat_submit_content')); ?>">
903 - <?php wp_nonce_field('mxchat_submit_content_action', 'mxchat_submit_content_nonce'); ?>
904 - <div class="mxchat-form-group">
905 - <label for="article_content"><?php esc_html_e('Content:', 'mxchat'); ?></label>
906 - <textarea name="article_content" id="article_content" required
907 - placeholder="<?php esc_attr_e('Enter your content here...', 'mxchat'); ?>"></textarea>
908 - </div>
909 - <div class="mxchat-form-group">
910 - <label for="article_url"><?php esc_html_e('Source URL (Optional):', 'mxchat'); ?></label>
911 - <input type="url" name="article_url" id="article_url"
912 - placeholder="<?php esc_attr_e('Enter source URL', 'mxchat'); ?>">
913 - </div>
914 - <button type="submit" name="submit_content" class="mxchat-button-primary">
915 - <?php esc_html_e('Submit Content', 'mxchat'); ?>
916 - </button>
917 - </form>
918 -</div>
919 -
920 -<!-- Knowledge Base Table Card -->
921 -<div class="mxchat-card">
922 - <div class="mxchat-card-header">
923 - <h2><?php esc_html_e('Knowledge Base', 'mxchat'); ?></h2>
924 - <div class="mxchat-header-actions">
925 - <!-- Search -->
926 - <form method="get" id="knowledge-search" class="mxchat-search-form">
927 - <?php wp_nonce_field('mxchat_prompts_search_nonce'); ?>
928 - <input type="hidden" name="page" value="mxchat-prompts" />
929 - <div class="mxchat-search-group">
930 - <span class="dashicons dashicons-search"></span>
931 - <input type="text"
932 - name="search"
933 - placeholder="<?php esc_attr_e('Search Knowledge', 'mxchat'); ?>"
934 - value="<?php echo esc_attr($search_query); ?>" />
935 - </div>
936 - </form>
937 -
938 - <!-- Delete All -->
939 - <form method="post"
940 - action="<?php echo esc_url(admin_url('admin-post.php?action=mxchat_delete_all_prompts')); ?>"
941 - class="mxchat-delete-form"
942 - onsubmit="return confirm('<?php esc_attr_e('Are you sure you want to delete all knowledge? This action cannot be undone.', 'mxchat'); ?>');">
943 - <?php wp_nonce_field('mxchat_delete_all_prompts_action', 'mxchat_delete_all_prompts_nonce'); ?>
944 - <button type="submit" name="delete_all_prompts" class="mxchat-button-danger">
945 - <span class="dashicons dashicons-trash"></span>
946 - <?php esc_html_e('Delete All', 'mxchat'); ?>
947 - </button>
948 - </form>
949 - </div>
950 - </div>
951 -
952 - <!-- Table -->
953 - <div class="mxchat-table-wrapper">
954 - <table class="mxchat-records-table">
955 - <thead>
956 - <tr>
957 - <th><?php esc_html_e('ID', 'mxchat'); ?></th>
958 - <th><?php esc_html_e('Content', 'mxchat'); ?></th>
959 - <th><?php esc_html_e('Source', 'mxchat'); ?></th>
960 - <th><?php esc_html_e('Actions', 'mxchat'); ?></th>
961 - </tr>
962 - </thead>
963 - <tbody>
964 - <?php if ($prompts) : ?>
965 - <?php foreach ($prompts as $prompt) : ?>
966 - <tr id="prompt-<?php echo esc_attr($prompt->id); ?>">
967 - <td><?php echo esc_html($prompt->id); ?></td>
968 - <td class="mxchat-content-cell">
969 - <div class="content-view">
970 - <?php echo wp_kses_post(wpautop(esc_textarea($prompt->article_content))); ?>
971 - </div>
972 - <textarea class="content-edit" style="display:none;">
973 - <?php echo esc_textarea($prompt->article_content); ?>
974 - </textarea>
975 - </td>
976 - <td class="mxchat-url-cell">
977 - <?php if (!empty($prompt->source_url)) : ?>
978 - <a href="<?php echo esc_url($prompt->source_url); ?>" target="_blank">
979 - <span class="dashicons dashicons-external"></span>
980 - <?php esc_html_e('View Source', 'mxchat'); ?>
981 - </a>
982 - <?php else : ?>
983 - <span class="mxchat-na"><?php esc_html_e('N/A', 'mxchat'); ?></span>
984 - <?php endif; ?>
985 - </td>
986 - <td class="mxchat-actions-cell">
987 - <button class="mxchat-button-icon edit-button"
988 - data-id="<?php echo esc_attr($prompt->id); ?>">
989 - <span class="dashicons dashicons-edit"></span>
990 - </button>
991 - <button class="mxchat-button-icon save-button"
992 - data-id="<?php echo esc_attr($prompt->id); ?>"
993 - style="display:none;"
994 - data-nonce="<?php echo wp_create_nonce('mxchat_save_inline_nonce'); ?>">
995 - <span class="dashicons dashicons-saved"></span>
996 - </button>
997 - <a href="<?php echo esc_url(admin_url(
998 - 'admin-post.php?action=mxchat_delete_prompt&id=' . esc_attr($prompt->id)
999 - . '&_wpnonce=' . wp_create_nonce('mxchat_delete_prompt_nonce')
1000 - )); ?>"
1001 - class="mxchat-button-icon delete-button"
1002 - onclick="return confirm('<?php esc_attr_e('Are you sure you want to delete this entry?', 'mxchat'); ?>');">
1003 - <span class="dashicons dashicons-trash"></span>
1004 - </a>
1005 - </td>
1006 - </tr>
1007 - <?php endforeach; ?>
1008 - <?php else : ?>
1009 - <tr>
1010 - <td colspan="4" class="mxchat-no-records">
1011 - <?php esc_html_e('No knowledge base entries found.', 'mxchat'); ?>
1012 - </td>
1013 - </tr>
1014 - <?php endif; ?>
1015 - </tbody>
1016 - </table>
1017 - </div>
1018 -
1019 - <?php if ($page_links) : ?>
1020 - <div class="mxchat-pagination">
1021 - <?php echo wp_kses_post($page_links); ?>
1022 - </div>
1023 - <?php endif; ?>
1024 -</div>
1025 -</div>
1026 -</div>
1027 -<?php
1483 + fclose($output);
1484 + wp_die();
1028 1485 }
1029 1486
1030 -
1031 -public function mxchat_handle_delete_all_prompts() {
1032 - // Verify nonce
1033 - if (!isset($_POST['mxchat_delete_all_prompts_nonce']) || !wp_verify_nonce($_POST['mxchat_delete_all_prompts_nonce'], 'mxchat_delete_all_prompts_action')) {
1034 - wp_die(__('Nonce verification failed.', 'mxchat'));
1035 - }
1036 -
1037 - // Check permissions
1487 +/**
1488 + * Handle translation of chat messages via AJAX
1489 + */
1490 +public function mxchat_translate_messages() {
1038 1491 if (!current_user_can('manage_options')) {
1039 - wp_die(__('You do not have sufficient permissions to delete all prompts.', 'mxchat'));
1492 + wp_send_json_error(['error' => 'Insufficient permissions']);
1493 + wp_die();
1040 1494 }
1041 1495
1042 - global $wpdb;
1043 - $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
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);
1044 1500
1045 - // Delete all prompts from the table
1046 - $wpdb->query("DELETE FROM {$table_name}");
1501 + if (empty($session_id)) {
1502 + wp_send_json_error(['error' => 'No session ID provided']);
1503 + wp_die();
1504 + }
1047 1505
1048 - // Clear relevant cache
1049 - wp_cache_delete('all_prompts', 'mxchat_prompts');
1506 + if (empty($messages) || !is_array($messages)) {
1507 + wp_send_json_error(['error' => 'No messages to translate']);
1508 + wp_die();
1509 + }
1050 1510
1051 - // Redirect back with a success message
1052 - $redirect_url = add_query_arg(array(
1053 - 'page' => 'mxchat-prompts',
1054 - 'all_deleted' => 'true'
1055 - ), admin_url('admin.php'));
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 + ];
1056 1534
1057 - wp_safe_redirect($redirect_url);
1058 - exit;
1059 -}
1060 -public function mxchat_handle_delete_prompt() {
1061 - // Sanitize and validate nonce
1062 - $nonce = isset($_GET['_wpnonce']) ? sanitize_text_field(wp_unslash($_GET['_wpnonce'])) : '';
1063 - if (empty($nonce) || !wp_verify_nonce($nonce, 'mxchat_delete_prompt_nonce')) {
1064 - wp_die(esc_html__('Nonce verification failed.', 'mxchat'));
1065 - }
1535 + $target_lang_name = isset($languages[$target_lang]) ? $languages[$target_lang] : 'English';
1066 1536
1067 - // Check permissions
1068 - if (!current_user_can('manage_options')) {
1069 - wp_die(esc_html__('You do not have sufficient permissions to delete prompts.', 'mxchat'));
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 . "]";
1543 + }
1070 1544 }
1071 1545
1072 - // Validate and sanitize ID parameter
1073 - $id = isset($_GET['id']) ? intval($_GET['id']) : 0;
1074 - if ($id <= 0) {
1075 - wp_die(esc_html__('Invalid prompt ID.', 'mxchat'));
1546 + if (empty($numbered_messages)) {
1547 + wp_send_json_error(['error' => 'No valid messages to translate']);
1548 + wp_die();
1076 1549 }
1077 1550
1078 - global $wpdb;
1079 - $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
1551 + $combined_text = implode("\n\n", $numbered_messages);
1080 1552
1081 - // Clear cache and delete prompt
1082 - wp_cache_delete('prompt_' . $id, 'mxchat_prompts');
1083 - $wpdb->delete($table_name, array('id' => $id), array('%d'));
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.";
1084 1555
1085 - wp_safe_redirect(add_query_arg(array('page' => 'mxchat-prompts', 'deleted' => 'true'), admin_url('admin.php')));
1086 - exit;
1087 -}
1088 -public function mxchat_generate_embedding($text) {
1089 - $options = get_option('mxchat_options');
1090 - $api_key = $options['api_key'] ?? esc_html__('default_api_key', 'mxchat');
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';
1091 1559
1092 - $response = wp_remote_post('https://api.openai.com/v1/embeddings', array(
1093 - 'body' => wp_json_encode(array(
1094 - 'model' => esc_html__('text-embedding-ada-002', 'mxchat'),
1095 - 'input' => $text
1096 - )),
1097 - 'headers' => array(
1098 - 'Authorization' => 'Bearer ' . $api_key,
1099 - 'Content-Type' => esc_html__('application/json', 'mxchat')
1100 - ),
1101 - ));
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'] ?? '';
1102 1565
1103 - if (is_wp_error($response)) {
1104 - return null;
1566 + if (empty($selected_model)) {
1567 + wp_send_json_error(['error' => 'No OpenRouter model selected']);
1568 + wp_die();
1105 1569 }
1570 + } else {
1571 + // Determine provider from model name
1572 + $model_parts = explode('-', $selected_model);
1573 + $provider = strtolower($model_parts[0]);
1106 1574
1107 - $response_data = json_decode(wp_remote_retrieve_body($response), true);
1108 - return $response_data['data'][0]['embedding'] ?? null;
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 + }
1109 1600 }
1110 -public function mxchat_delete_chat_history() {
1111 - if (!current_user_can('manage_options')) {
1112 - echo wp_json_encode(['error' => esc_html__('You do not have sufficient permissions.', 'mxchat')]);
1113 - wp_die();
1114 - }
1115 1601
1116 - check_ajax_referer('mxchat_delete_chat_history', 'security');
1602 + if (empty($api_key)) {
1603 + wp_send_json_error(['error' => 'No API key configured for ' . $provider]);
1604 + wp_die();
1605 + }
1117 1606
1118 - global $wpdb;
1119 - $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
1607 + // Make API request based on provider
1608 + $response = $this->translate_with_provider($provider, $selected_model, $api_key, $system_prompt, $combined_text);
1120 1609
1121 - if (isset($_POST['delete_session_ids']) && is_array($_POST['delete_session_ids'])) {
1122 - foreach ($_POST['delete_session_ids'] as $session_id) {
1123 - $session_id_sanitized = sanitize_text_field($session_id);
1610 + if (is_wp_error($response)) {
1611 + wp_send_json_error(['error' => $response->get_error_message()]);
1612 + wp_die();
1613 + }
1124 1614
1125 - // Clear relevant cache before deletion
1126 - $cache_key = 'chat_session_' . $session_id_sanitized;
1127 - wp_cache_delete($cache_key, 'mxchat_chat_sessions');
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 + ];
1625 + }
1626 + }
1128 1627
1129 - // Perform the deletion
1130 - $wpdb->delete($table_name, ['session_id' => $session_id_sanitized]);
1131 - }
1628 + // Save translations to database
1629 + if (!empty($translations)) {
1630 + $this->save_transcript_translation($session_id, $target_lang, $translations);
1631 + }
1132 1632
1133 - // Optionally, clear a general cache if you have one
1134 - wp_cache_delete('all_chat_sessions', 'mxchat_chat_sessions');
1633 + wp_send_json(['success' => true, 'translations' => $translations, 'language' => $target_lang]);
1634 + wp_die();
1635 +}
1135 1636
1136 - echo wp_json_encode(['success' => esc_html__('Selected chat sessions have been deleted.', 'mxchat')]);
1137 - } else {
1138 - echo wp_json_encode(['error' => esc_html__('No chat sessions selected for deletion.', 'mxchat')]);
1139 - }
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';
1140 1643
1141 - wp_die();
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();
1142 1647 }
1143 1648
1649 + $now = current_time('mysql');
1650 + $translations_json = wp_json_encode($translations);
1144 1651
1145 -public function mxchat_save_inline_prompt() {
1146 - // Check for nonce security
1147 - check_ajax_referer('mxchat_save_inline_nonce');
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 +}
1148 1663
1149 - // Verify permissions
1664 +/**
1665 + * Get saved translation for a session
1666 + */
1667 +public function mxchat_get_transcript_translation() {
1150 1668 if (!current_user_can('manage_options')) {
1151 - wp_send_json_error(esc_html__('Permission denied.', 'mxchat'));
1152 - return;
1669 + wp_send_json_error(['error' => 'Insufficient permissions']);
1670 + wp_die();
1153 1671 }
1154 1672
1155 - global $wpdb;
1156 - $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
1673 + $session_id = isset($_POST['session_id']) ? sanitize_text_field($_POST['session_id']) : '';
1157 1674
1158 - // Validate and sanitize input data
1159 - $prompt_id = isset($_POST['id']) ? absint($_POST['id']) : 0;
1160 - $article_content = isset($_POST['article_content']) ? sanitize_textarea_field($_POST['article_content']) : '';
1161 - $article_url = isset($_POST['article_url']) ? esc_url_raw($_POST['article_url']) : '';
1675 + if (empty($session_id)) {
1676 + wp_send_json_error(['error' => 'No session ID provided']);
1677 + wp_die();
1678 + }
1162 1679
1163 - if ($prompt_id > 0 && !empty($article_content)) {
1164 - // Re-generate the embedding vector for the updated content
1165 - $embedding_vector = $this->mxchat_generate_embedding($article_content);
1680 + global $wpdb;
1681 + $table_name = $wpdb->prefix . 'mxchat_transcript_translations';
1166 1682
1167 - if (is_array($embedding_vector)) {
1168 - // Serialize the embedding vector before storing it
1169 - $embedding_vector_serialized = serialize($embedding_vector);
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 + }
1170 1688
1171 - // Update the prompt in the database
1172 - $updated = $wpdb->update(
1173 - $table_name,
1174 - array(
1175 - 'article_content' => $article_content,
1176 - 'embedding_vector' => $embedding_vector_serialized,
1177 - 'source_url' => $article_url,
1178 - ),
1179 - array('id' => $prompt_id),
1180 - array('%s', '%s', '%s'),
1181 - array('%d')
1182 - );
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
1693 + ));
1183 1694
1184 - if ($updated !== false) {
1185 - wp_send_json_success();
1186 - } else {
1187 - wp_send_json_error(esc_html__('Database update failed.', 'mxchat'));
1188 - }
1189 - } else {
1190 - wp_send_json_error(esc_html__('Embedding generation failed.', 'mxchat'));
1191 - }
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 + ]);
1192 1703 } else {
1193 - wp_send_json_error(esc_html__('Invalid data.', 'mxchat'));
1704 + wp_send_json(['success' => true, 'has_translation' => false]);
1194 1705 }
1706 + wp_die();
1195 1707 }
1196 1708
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);
1728 + }
1729 +}
1197 1730
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 + ]);
1198 1750
1199 -// Add this method to handle post updates
1200 -public function handle_post_update($post_id, $post, $update) {
1201 - // Basic validation checks
1202 - if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE || wp_is_post_revision($post_id)) {
1203 - return;
1751 + if (is_wp_error($response)) {
1752 + return $response;
1204 1753 }
1205 1754
1206 - // Only process published content
1207 - if (!in_array($post->post_status, array('publish'))) {
1208 - return;
1755 + $body = json_decode(wp_remote_retrieve_body($response), true);
1756 +
1757 + if (isset($body['error'])) {
1758 + return new WP_Error('api_error', $body['error']['message']);
1209 1759 }
1210 1760
1211 - // Check if sync is enabled for this post type
1212 - $post_type = $post->post_type;
1213 - if (!in_array($post_type, array('post', 'page'))) {
1214 - return;
1761 + if (isset($body['choices'][0]['message']['content'])) {
1762 + return $body['choices'][0]['message']['content'];
1215 1763 }
1216 1764
1217 - $sync_option = ($post_type === 'post') ? 'mxchat_auto_sync_posts' : 'mxchat_auto_sync_pages';
1218 - if (get_option($sync_option) != '1') {
1219 - return;
1220 - }
1765 + return new WP_Error('api_error', 'Invalid API response');
1766 +}
1221 1767
1222 - // Prepare content and URL
1223 - $content = wp_strip_all_tags($post->post_content);
1224 - $url = get_permalink($post_id);
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 + ]);
1225 1788
1226 - // Generate embedding vector
1227 - $embedding_vector = $this->mxchat_generate_embedding($content);
1228 - if (!$embedding_vector) {
1229 - return;
1789 + if (is_wp_error($response)) {
1790 + return $response;
1230 1791 }
1231 1792
1232 - // Check for Pinecone addon and its settings
1233 - $pinecone_settings = get_option('mxchat_pinecone_addon_options');
1234 - $use_pinecone = false;
1793 + $body = json_decode(wp_remote_retrieve_body($response), true);
1235 1794
1236 - if ($pinecone_settings && is_array($pinecone_settings)) {
1237 - // Check if Pinecone is enabled and all required settings are present
1238 - $use_pinecone = (
1239 - isset($pinecone_settings['mxchat_use_pinecone']) &&
1240 - $pinecone_settings['mxchat_use_pinecone'] === '1' &&
1241 - !empty($pinecone_settings['mxchat_pinecone_api_key']) &&
1242 - !empty($pinecone_settings['mxchat_pinecone_host']) &&
1243 - !empty($pinecone_settings['mxchat_pinecone_index']) &&
1244 - !empty($pinecone_settings['mxchat_pinecone_environment'])
1245 - );
1795 + if (isset($body['error'])) {
1796 + return new WP_Error('api_error', $body['error']['message']);
1246 1797 }
1247 1798
1248 - if ($use_pinecone) {
1249 - // Use Pinecone
1250 - $pinecone_result = $this->store_in_pinecone_main(
1251 - $embedding_vector,
1252 - $content,
1253 - $url,
1254 - $pinecone_settings['mxchat_pinecone_api_key'],
1255 - $pinecone_settings['mxchat_pinecone_environment'],
1256 - $pinecone_settings['mxchat_pinecone_index']
1257 - );
1799 + if (isset($body['content'][0]['text'])) {
1800 + return $body['content'][0]['text'];
1801 + }
1258 1802
1259 - if (!$pinecone_result['success']) {
1260 - //error_log('MXChat: Pinecone sync failed - falling back to WordPress DB');
1261 - $this->store_in_wordpress_db($content, $url, $embedding_vector);
1262 - }
1263 - } else {
1264 - // Fallback to WordPress DB storage
1265 - $this->store_in_wordpress_db($content, $url, $embedding_vector);
1266 - }
1803 + return new WP_Error('api_error', 'Invalid API response');
1267 1804 }
1268 -// Modified storage function to add type field
1269 -private function store_in_pinecone_main($embedding_vector, $content, $url, $api_key, $environment, $index_name, $vector_id = null) {
1270 - $vector_id = $vector_id ?: md5($url);
1271 - $options = get_option('mxchat_pinecone_addon_options');
1272 - $host = $options['mxchat_pinecone_host'] ?? '';
1273 1805
1274 - if (empty($host)) {
1275 - return array(
1276 - 'success' => false,
1277 - 'message' => 'Pinecone host is not configured. Please set the host in your settings.'
1278 - );
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 + ]);
1825 +
1826 + if (is_wp_error($response)) {
1827 + return $response;
1279 1828 }
1280 1829
1281 - // Determine if this is a product URL
1282 - $is_product = (strpos($url, '/product/') !== false || strpos($url, '/shop/') !== false);
1830 + $body = json_decode(wp_remote_retrieve_body($response), true);
1283 1831
1284 - $api_endpoint = "https://{$host}/vectors/upsert";
1285 - $request_body = array(
1286 - 'vectors' => array(
1287 - array(
1288 - 'id' => $vector_id,
1289 - 'values' => $embedding_vector,
1290 - 'metadata' => array(
1291 - 'text' => $content,
1292 - 'source_url' => $url,
1293 - 'type' => $is_product ? 'product' : 'content',
1294 - 'last_updated' => time()
1295 - )
1296 - )
1297 - )
1298 - );
1299 -
1300 - $response = wp_remote_post($api_endpoint, array(
1301 - 'headers' => array(
1302 - 'Api-Key' => $api_key,
1303 - 'accept' => 'application/json',
1304 - 'content-type' => 'application/json'
1305 - ),
1306 - 'body' => wp_json_encode($request_body),
1307 - 'timeout' => 30,
1308 - 'data_format' => 'body'
1309 - ));
1310 -
1311 - if (is_wp_error($response)) {
1312 - return array(
1313 - 'success' => false,
1314 - 'message' => $response->get_error_message()
1315 - );
1832 + if (isset($body['error'])) {
1833 + return new WP_Error('api_error', $body['error']['message']);
1316 1834 }
1317 1835
1318 - $response_code = wp_remote_retrieve_response_code($response);
1319 - if ($response_code !== 200) {
1320 - $body = wp_remote_retrieve_body($response);
1321 - return array(
1322 - 'success' => false,
1323 - 'message' => sprintf(
1324 - 'Pinecone API error (HTTP %d): %s',
1325 - $response_code,
1326 - $body
1327 - )
1328 - );
1836 + if (isset($body['choices'][0]['message']['content'])) {
1837 + return $body['choices'][0]['message']['content'];
1329 1838 }
1330 1839
1331 - return array(
1332 - 'success' => true,
1333 - 'message' => 'Successfully stored in Pinecone'
1334 - );
1840 + return new WP_Error('api_error', 'Invalid API response');
1335 1841 }
1336 -// Helper method for WordPress DB storage
1337 -private function store_in_wordpress_db($content, $url, $embedding_vector) {
1338 - global $wpdb;
1339 - $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
1340 1842
1341 - $existing_entry = $wpdb->get_row(
1342 - $wpdb->prepare("SELECT id FROM $table_name WHERE source_url = %s", $url)
1343 - );
1344 -
1345 - if ($existing_entry) {
1346 - $wpdb->update(
1347 - $table_name,
1348 - array(
1349 - 'article_content' => $content,
1350 - 'embedding_vector' => serialize($embedding_vector),
1351 - 'timestamp' => current_time('mysql')
1352 - ),
1353 - array('id' => $existing_entry->id),
1354 - array('%s', '%s', '%s')
1355 - );
1356 - } else {
1357 - $wpdb->insert(
1358 - $table_name,
1359 - array(
1360 - 'article_content' => $content,
1361 - 'source_url' => $url,
1362 - 'embedding_vector' => serialize($embedding_vector),
1363 - 'timestamp' => current_time('mysql')
1364 - ),
1365 - array('%s', '%s', '%s', '%s')
1366 - );
1367 - }
1368 -}
1369 1843 /**
1370 - * Handle deletion of posts and pages from both Pinecone and WordPress DB
1371 - *
1372 - * @param int $post_id The ID of the post being deleted
1373 - * @return void
1844 + * Translate text using DeepSeek API
1374 1845 */
1375 -public function mxchat_handle_post_delete($post_id) {
1376 - // Get post data before it's deleted
1377 - $post = get_post($post_id);
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 + ]);
1378 1862
1379 - // Basic validation
1380 - if (!$post || wp_is_post_revision($post_id)) {
1381 - return;
1863 + if (is_wp_error($response)) {
1864 + return $response;
1382 1865 }
1383 1866
1384 - // Check post type
1385 - $post_type = $post->post_type;
1386 - if (!in_array($post_type, array('post', 'page'))) {
1387 - return;
1388 - }
1867 + $body = json_decode(wp_remote_retrieve_body($response), true);
1389 1868
1390 - // Check if sync is enabled for this post type
1391 - $sync_option = ($post_type === 'post') ? 'mxchat_auto_sync_posts' : 'mxchat_auto_sync_pages';
1392 - if (get_option($sync_option) != '1') {
1393 - return;
1869 + if (isset($body['error'])) {
1870 + return new WP_Error('api_error', $body['error']['message']);
1394 1871 }
1395 1872
1396 - // Get the URL before post is deleted
1397 - $source_url = get_permalink($post_id);
1398 - if (!$source_url) {
1399 - //error_log('MXChat: Failed to get permalink for post ' . $post_id);
1400 - return;
1873 + if (isset($body['choices'][0]['message']['content'])) {
1874 + return $body['choices'][0]['message']['content'];
1401 1875 }
1402 1876
1403 - // Check for Pinecone addon and its settings
1404 - $pinecone_settings = get_option('mxchat_pinecone_addon_options');
1405 - $use_pinecone = false;
1406 - if ($pinecone_settings && is_array($pinecone_settings)) {
1407 - $use_pinecone = (
1408 - isset($pinecone_settings['mxchat_use_pinecone']) &&
1409 - $pinecone_settings['mxchat_use_pinecone'] === '1' &&
1410 - !empty($pinecone_settings['mxchat_pinecone_api_key']) &&
1411 - !empty($pinecone_settings['mxchat_pinecone_host']) &&
1412 - !empty($pinecone_settings['mxchat_pinecone_index']) &&
1413 - !empty($pinecone_settings['mxchat_pinecone_environment'])
1414 - );
1415 - }
1877 + return new WP_Error('api_error', 'Invalid API response');
1878 +}
1416 1879
1417 - $deletion_successful = false;
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;
1418 1885
1419 - if ($use_pinecone) {
1420 - try {
1421 - $pinecone_result = $this->delete_from_pinecone(
1422 - array($source_url),
1423 - $pinecone_settings['mxchat_pinecone_api_key'],
1424 - $pinecone_settings['mxchat_pinecone_environment'],
1425 - $pinecone_settings['mxchat_pinecone_index']
1426 - );
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 + ]);
1427 1904
1428 - if (!$pinecone_result['success']) {
1429 - //error_log('MXChat: Pinecone deletion failed for URL: ' . $source_url . ' - ' . $pinecone_result['message']);
1430 - } else {
1431 - $deletion_successful = true;
1432 - }
1433 - } catch (Exception $e) {
1434 - //error_log('MXChat: Exception during Pinecone deletion - ' . $e->getMessage());
1435 - }
1905 + if (is_wp_error($response)) {
1906 + return $response;
1436 1907 }
1437 1908
1438 - // Always attempt WordPress DB deletion, regardless of Pinecone status
1439 - try {
1440 - global $wpdb;
1441 - $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
1909 + $body = json_decode(wp_remote_retrieve_body($response), true);
1442 1910
1443 - $result = $wpdb->delete(
1444 - $table_name,
1445 - array('source_url' => $source_url),
1446 - array('%s')
1447 - );
1911 + if (isset($body['error'])) {
1912 + return new WP_Error('api_error', $body['error']['message']);
1913 + }
1448 1914
1449 - if ($result === false) {
1450 - //error_log('MXChat: WordPress DB deletion failed for URL: ' . $source_url);
1451 - } else {
1452 - $deletion_successful = true;
1453 - }
1454 - } catch (Exception $e) {
1455 - //error_log('MXChat: Exception during WordPress DB deletion - ' . $e->getMessage());
1915 + if (isset($body['candidates'][0]['content']['parts'][0]['text'])) {
1916 + return $body['candidates'][0]['content']['parts'][0]['text'];
1456 1917 }
1457 1918
1458 - if (!$deletion_successful) {
1459 - //error_log('MXChat: Complete deletion failure for post ID: ' . $post_id . ' URL: ' . $source_url);
1460 - }
1919 + return new WP_Error('api_error', 'Invalid API response');
1461 1920 }
1462 1921
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' => [
1929 + 'Authorization' => 'Bearer ' . $api_key,
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 + ]);
1463 1943
1464 -public function mxchat_handle_content_submission() {
1465 - // Check if the form was submitted and the user has permission.
1466 - if (!isset($_POST['submit_content']) || !current_user_can('manage_options')) {
1467 - return;
1944 + if (is_wp_error($response)) {
1945 + return $response;
1468 1946 }
1469 1947
1470 - // Verify the nonce.
1471 - $nonce = isset($_POST['mxchat_submit_content_nonce']) ? sanitize_text_field(wp_unslash($_POST['mxchat_submit_content_nonce'])) : '';
1472 - if (!wp_verify_nonce($nonce, 'mxchat_submit_content_action')) {
1473 - wp_die(esc_html__('Nonce verification failed.', 'mxchat'));
1948 + $body = json_decode(wp_remote_retrieve_body($response), true);
1949 +
1950 + if (isset($body['error'])) {
1951 + return new WP_Error('api_error', $body['error']['message']);
1474 1952 }
1475 1953
1476 - // Sanitize the inputs.
1477 - $article_content = sanitize_textarea_field($_POST['article_content']);
1478 - $article_url = isset($_POST['article_url']) ? esc_url_raw($_POST['article_url']) : '';
1954 + if (isset($body['choices'][0]['message']['content'])) {
1955 + return $body['choices'][0]['message']['content'];
1956 + }
1479 1957
1480 - // Generate the embedding vector.
1481 - $embedding_vector = $this->mxchat_generate_embedding($article_content);
1482 - if (!is_array($embedding_vector)) {
1483 - set_transient('mxchat_admin_notice_error',
1484 - esc_html__('Embedding generation failed. Please ensure your API key is correct and try again.', 'mxchat'),
1485 - 30
1486 - );
1487 - wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
1488 - exit;
1489 - }
1958 + return new WP_Error('api_error', 'Invalid API response');
1959 +}
1490 1960
1491 - // Store in the WordPress database.
1961 +public function mxchat_fetch_chat_history() {
1492 1962 global $wpdb;
1493 - $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
1963 + $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
1964 + $url_clicks_table = $wpdb->prefix . 'mxchat_url_clicks';
1494 1965
1495 - // Ensure the source_url column exists.
1496 - if ($wpdb->get_var($wpdb->prepare("SHOW COLUMNS FROM {$table_name} LIKE %s", 'source_url')) != 'source_url') {
1497 - $wpdb->query($wpdb->prepare("ALTER TABLE {$table_name} ADD source_url VARCHAR(255) DEFAULT ''"));
1966 + if (!current_user_can('manage_options')) {
1967 + wp_send_json_error(['message' => 'Insufficient permissions']);
1968 + wp_die();
1498 1969 }
1499 1970
1500 - // Serialize and store the embedding vector.
1501 - $embedding_vector_serialized = serialize($embedding_vector);
1502 - $inserted = $wpdb->insert(
1503 - $table_name,
1504 - array(
1505 - 'article_content' => $article_content,
1506 - 'embedding_vector' => $embedding_vector_serialized,
1507 - 'source_url' => $article_url,
1508 - ),
1509 - array('%s', '%s', '%s')
1510 - );
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';
1511 1976
1512 - if ($inserted === false) {
1513 - set_transient('mxchat_admin_notice_error',
1514 - esc_html__('Error inserting content into the database. Please try again.', 'mxchat'),
1515 - 30
1516 - );
1517 - } else {
1518 - set_transient('mxchat_admin_notice_success',
1519 - esc_html__('Content successfully submitted!', 'mxchat'),
1520 - 30
1521 - );
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) . '%');
1522 1989 }
1523 1990
1524 - wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
1525 - exit;
1526 -}
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);
1527 1996
1528 -private function is_pdf_url($url, $response) {
1529 - $content_type = wp_remote_retrieve_header($response, 'content-type');
1530 - $file_extension = strtolower(pathinfo($url, PATHINFO_EXTENSION));
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);
1531 2010
1532 - return strpos($content_type, 'pdf') !== false || $file_extension === 'pdf';
1533 -}
1534 -private function handle_pdf_for_knowledge_base($pdf_url, $response) {
1535 - if (!current_user_can('manage_options')) {
1536 - //error_log(esc_html__('Unauthorized PDF processing attempt', 'mxchat'));
1537 - return false;
2011 + $total_pages = ceil($total_sessions / $per_page);
2012 +
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 + ]);
2023 + wp_die();
1538 2024 }
1539 2025
1540 - $pdf_url = esc_url_raw($pdf_url);
1541 - $upload_dir = wp_upload_dir();
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'"));
1542 2029
1543 - if (isset($upload_dir['error']) && $upload_dir['error'] !== false) {
1544 - //error_log(sprintf(esc_html__('Upload directory error: %s', 'mxchat'), esc_html($upload_dir['error'])));
1545 - return false;
1546 - }
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 + ));
1547 2045
1548 - $pdf_filename = sanitize_file_name('mxchat_kb_' . time() . '.pdf');
1549 - $pdf_path = trailingslashit($upload_dir['path']) . $pdf_filename;
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 + ));
1550 2051
1551 - $response_body = wp_remote_retrieve_body($response);
1552 - if (empty($response_body)) {
1553 - //error_log(esc_html__('Empty PDF response body', 'mxchat'));
1554 - return false;
1555 - }
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';
1556 2058
1557 - if (!wp_mkdir_p(dirname($pdf_path))) {
1558 - //error_log(sprintf(esc_html__('Failed to create directory for PDF: %s', 'mxchat'), esc_html($pdf_path)));
1559 - return false;
1560 - }
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';
1561 2063
1562 - try {
1563 - file_put_contents($pdf_path, $response_body);
2064 + $display_name = $user_name ?: ($user_email ? explode('@', $user_email)[0] : $user_identifier);
2065 + $display_sub = $user_email ?: ('ID: ' . $user_identifier);
1564 2066
1565 - if (!file_exists($pdf_path)) {
1566 - throw new Exception(__('Failed to save PDF file', 'mxchat'));
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);
1567 2079 }
1568 2080
1569 - $parser = new \Smalot\PdfParser\Parser();
1570 - $pdf = $parser->parseFile($pdf_path);
1571 - $total_pages = absint(count($pdf->getPages()));
1572 -
1573 - if ($total_pages < 1) {
1574 - throw new Exception(__('Invalid PDF: no pages found', 'mxchat'));
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));
1575 2086 }
1576 2087
1577 - wp_schedule_single_event(time(), 'mxchat_process_pdf_pages', array(
1578 - 'pdf_path' => $pdf_path,
1579 - 'pdf_url' => $pdf_url,
1580 - 'total_pages' => $total_pages,
1581 - 'batch_size' => absint(15),
1582 - 'batch_pause' => absint(10)
1583 - ));
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 + ];
2098 + }
1584 2099
1585 - $status_data = array(
1586 - 'total_pages' => $total_pages,
1587 - 'processed_pages' => 0,
1588 - 'status' => 'processing',
1589 - 'last_update' => time()
1590 - );
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();
2110 +}
1591 2111
1592 - set_transient(
1593 - sanitize_key('mxchat_pdf_status_' . md5($pdf_url)),
1594 - array_map('sanitize_text_field', $status_data),
1595 - DAY_IN_SECONDS
1596 - );
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';
1597 2119
1598 - return __('scheduled', 'mxchat');
2120 + if (!current_user_can('manage_options')) {
2121 + wp_send_json_error(['message' => 'Insufficient permissions']);
2122 + wp_die();
2123 + }
1599 2124
1600 - } catch (Exception $e) {
1601 - //error_log(sprintf(esc_html__('Error preparing PDF for processing: %s', 'mxchat'), esc_html($e->getMessage())));
1602 - if (file_exists($pdf_path)) {
1603 - wp_delete_file($pdf_path);
1604 - }
1605 - return false;
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();
1606 2129 }
1607 -}
1608 -public static function process_pdf_pages_cron($pdf_path, $pdf_url, $total_pages, $batch_size, $batch_pause) {
1609 - // Validate inputs
1610 - $pdf_path = sanitize_text_field($pdf_path);
1611 - $pdf_url = esc_url_raw($pdf_url);
1612 - $total_pages = absint($total_pages);
1613 - $batch_size = absint($batch_size);
1614 - $batch_pause = absint($batch_pause);
1615 2130
1616 - try {
1617 - if (!file_exists($pdf_path)) {
1618 - throw new Exception(sprintf('PDF file not found at path: %s', esc_html($pdf_path)));
1619 - }
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'"));
1620 2134
1621 - $parser = new \Smalot\PdfParser\Parser();
1622 - $pdf = $parser->parseFile($pdf_path);
1623 - $pages = $pdf->getPages();
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 + ));
1624 2147
1625 - // Get current progress
1626 - $status_key = sanitize_key('mxchat_pdf_status_' . md5($pdf_url));
1627 - $status = get_transient($status_key);
2148 + if (!$session_data) {
2149 + wp_send_json_error(['message' => 'Session not found']);
2150 + wp_die();
2151 + }
1628 2152
1629 - if (!$status || !is_array($status)) {
1630 - throw new Exception('Invalid status data retrieved from transient');
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;
1631 2163 }
2164 + }
1632 2165
1633 - $start_page = absint($status['processed_pages']);
1634 - $end_page = min($start_page + $batch_size, $total_pages);
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 + ));
1635 2171
1636 - $instance = new self(); // Create an instance of the class
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';
1637 2176
1638 - for ($i = $start_page; $i < $end_page; $i++) {
1639 - $text = $pages[$i]->getText();
1640 - $sanitized_content = $instance->mxchat_sanitize_content_for_api($text); // Call via instance
2177 + $display_name = $user_name ?: ($user_email ? explode('@', $user_email)[0] : $user_identifier);
2178 + $display_sub = $user_email ?: ('ID: ' . $user_identifier);
1641 2179
1642 - if (!empty($sanitized_content)) {
1643 - $embedding_vector = $instance->mxchat_generate_embedding($sanitized_content); // Call via instance
1644 - if (is_array($embedding_vector)) {
1645 - $metadata = array(
1646 - 'document_type' => 'pdf',
1647 - 'total_pages' => $total_pages,
1648 - 'current_page' => $i + 1,
1649 - 'prev_page' => $i > 0 ? $i : null,
1650 - 'next_page' => $i < ($total_pages - 1) ? $i + 2 : null,
1651 - 'source_url' => $pdf_url
1652 - );
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));
2185 + }
1653 2186
1654 - $content_with_metadata = wp_json_encode($metadata) . "\n---\n" . $sanitized_content;
1655 - $page_url = esc_url($pdf_url . "#page=" . ($i + 1));
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);
2192 + }
1656 2193
1657 - $options = get_option('mxchat_options');
1658 - MxChat_Utils::submit_content_to_db($content_with_metadata, $page_url, $options['api_key']);
1659 - }
1660 - }
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');
1661 2199
1662 - // Update progress with sanitized data
1663 - $status['processed_pages'] = absint($i + 1);
1664 - $status['last_update'] = time();
1665 - set_transient($status_key, array_map('sanitize_text_field', $status), DAY_IN_SECONDS);
1666 - }
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);
1667 2209
1668 - // Schedule next batch if needed
1669 - if ($end_page < $total_pages) {
1670 - wp_schedule_single_event(time() + $batch_pause, 'mxchat_process_pdf_pages', array(
1671 - 'pdf_path' => $pdf_path,
1672 - 'pdf_url' => $pdf_url,
1673 - 'total_pages' => $total_pages,
1674 - 'batch_size' => $batch_size,
1675 - 'batch_pause' => $batch_pause
1676 - ));
1677 - } else {
1678 - // Processing complete
1679 - $status['status'] = 'complete';
1680 - set_transient($status_key, array_map('sanitize_text_field', $status), DAY_IN_SECONDS);
1681 - if (file_exists($pdf_path)) {
1682 - wp_delete_file($pdf_path);
1683 - }
1684 - }
2210 + $has_rag = $is_bot && !empty($msg->rag_context);
1685 2211
1686 - } catch (\Exception $e) {
1687 - $status['status'] = 'error';
1688 - $status['error'] = sanitize_text_field($e->getMessage());
1689 - set_transient($status_key, array_map('sanitize_text_field', $status), DAY_IN_SECONDS);
1690 - if (file_exists($pdf_path)) {
1691 - wp_delete_file($pdf_path);
1692 - }
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 + ];
1693 2222 }
1694 -}
1695 -public function get_pdf_processing_status($pdf_url) {
1696 - $pdf_url = esc_url_raw($pdf_url);
1697 - $status = get_transient(sanitize_key('mxchat_pdf_status_' . md5($pdf_url)));
1698 2223
1699 - if (!$status || !is_array($status)) {
1700 - return false;
1701 - }
2224 + // First message timestamp for "started" display
2225 + $started = !empty($messages) ? wp_date('M j, Y g:i A', strtotime($messages[0]->timestamp . ' UTC')) : '-';
1702 2226
1703 - return array(
1704 - 'total_pages' => absint($status['total_pages']),
1705 - 'processed_pages' => absint($status['processed_pages']),
1706 - 'percentage' => ($status['total_pages'] > 0)
1707 - ? round((absint($status['processed_pages']) / absint($status['total_pages'])) * 100)
1708 - : 0,
1709 - 'status' => sanitize_text_field($status['status']),
1710 - 'last_update' => human_time_diff(absint($status['last_update']), time()) . ' ' . esc_html__('ago', 'mxchat')
1711 - );
2227 + wp_send_json([
2228 + 'success' => true,
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();
1712 2247 }
1713 -public function mxchat_handle_sitemap_submission() {
1714 - // Check if the form was submitted and verify permissions
1715 - if (!isset($_POST['submit_sitemap']) || !current_user_can('manage_options')) {
1716 - wp_die(esc_html__('Unauthorized access', 'mxchat'));
1717 - }
1718 2248
1719 - // Verify nonce
1720 - check_admin_referer('mxchat_submit_sitemap_action', 'mxchat_submit_sitemap_nonce');
1721 2249
1722 - // Validate URL
1723 - if (!isset($_POST['sitemap_url']) || empty($_POST['sitemap_url'])) {
1724 - set_transient('mxchat_admin_notice_error',
1725 - esc_html__('Please provide a valid URL.', 'mxchat'),
1726 - 30
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 + ]
1727 2262 );
1728 - wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
1729 - exit;
1730 2263 }
1731 2264
1732 - $submitted_url = esc_url_raw($_POST['sitemap_url']);
1733 - $response = wp_remote_get($submitted_url, array('timeout' => 30));
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 + );
1734 2274
1735 - if (is_wp_error($response) || wp_remote_retrieve_response_code($response) !== 200) {
1736 - $error_message = is_wp_error($response) ? $response->get_error_message() : __('Failed to fetch URL', 'mxchat');
1737 - set_transient('mxchat_admin_notice_error',
1738 - sprintf(
1739 - esc_html__('Failed to fetch the URL: %s', 'mxchat'),
1740 - esc_html($error_message)
1741 - ),
1742 - 30
1743 - );
1744 - wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
1745 - exit;
1746 - }
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 + ];
1747 2286
1748 - $content_type = wp_remote_retrieve_header($response, 'content-type');
1749 - $body_content = wp_remote_retrieve_body($response);
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];
1750 2294
1751 - if (empty($body_content)) {
1752 - set_transient('mxchat_admin_notice_error',
1753 - esc_html__('Empty response received from URL.', 'mxchat'),
1754 - 30
1755 - );
1756 - wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
1757 - exit;
1758 - }
2295 + // Check if it already has the class
2296 + if (strpos($full_match, 'mxchat-clicked-link') !== false) {
2297 + return $full_match;
2298 + }
1759 2299
1760 - // Handle PDF URL
1761 - if ($this->is_pdf_url($submitted_url, $response)) {
1762 - $result = $this->handle_pdf_for_knowledge_base($submitted_url, $response);
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 + }
1763 2312
1764 - if ($result === 'scheduled') {
1765 - set_transient(
1766 - 'mxchat_last_pdf_url',
1767 - sanitize_text_field($submitted_url),
1768 - DAY_IN_SECONDS
1769 - );
1770 - set_transient('mxchat_admin_notice_info',
1771 - esc_html__('PDF processing has started in the background. You can check the progress in the Knowledge Base section.', 'mxchat'),
1772 - 30
1773 - );
1774 - } else {
1775 - set_transient('mxchat_admin_notice_error',
1776 - esc_html__('Failed to start PDF processing. Please try again.', 'mxchat'),
1777 - 30
1778 - );
2313 + // Add title if not present
2314 + if (strpos($new_attributes, 'title=') === false) {
2315 + $new_attributes .= ' title="User clicked this link"';
2316 + }
2317 +
2318 + return '<a' . $new_attributes . '>';
2319 + },
2320 + $message_content
2321 + );
2322 +
2323 + // If we found and replaced, break out of the patterns loop
2324 + break;
2325 + }
1779 2326 }
1780 -
1781 - wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
1782 - exit;
1783 2327 }
1784 2328
1785 - // Handle Sitemap XML
1786 - if (strpos($content_type, 'xml') !== false || strpos($body_content, '<urlset') !== false) {
1787 - libxml_use_internal_errors(true);
1788 - $xml = simplexml_load_string($body_content);
1789 - $xml_errors = libxml_get_errors();
1790 - libxml_clear_errors();
2329 + return $message_content;
2330 +}
1791 2331
1792 - if ($xml === false || !empty($xml_errors)) {
1793 - set_transient('mxchat_admin_notice_error',
1794 - esc_html__('Invalid sitemap XML. Please provide a valid sitemap.', 'mxchat'),
1795 - 30
1796 - );
1797 - wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
1798 - exit;
1799 - }
2332 +public function mxchat_create_prompts_page() {
2333 + //error_log('=== DEBUG: mxchat_create_prompts_page started ===');
1800 2334
1801 - $result = $this->handle_sitemap_for_knowledge_base($xml, $submitted_url);
2335 + global $wpdb;
2336 + $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
1802 2337
1803 - if ($result === 'scheduled') {
1804 - set_transient(
1805 - 'mxchat_last_sitemap_url',
1806 - sanitize_text_field($submitted_url),
1807 - DAY_IN_SECONDS
1808 - );
1809 - set_transient('mxchat_admin_notice_info',
1810 - esc_html__('Sitemap processing has started in the background. You can check the progress in the Knowledge Base section.', 'mxchat'),
1811 - 30
1812 - );
1813 - } else {
1814 - set_transient('mxchat_admin_notice_error',
1815 - esc_html__('Failed to start sitemap processing. Please try again.', 'mxchat'),
1816 - 30
1817 - );
1818 - }
2338 + $knowledge_manager = MxChat_Knowledge_Manager::get_instance();
2339 + $pinecone_manager = MxChat_Pinecone_Manager::get_instance();
1819 2340
1820 - wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
1821 - exit;
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>';
1822 2344 }
1823 2345
1824 - // Handle Regular URL
1825 - $page_content = $this->mxchat_extract_main_content($body_content);
1826 - $sanitized_content = $this->mxchat_sanitize_content_for_api($page_content);
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;
1827 2352
1828 - if (empty($sanitized_content)) {
1829 - set_transient('mxchat_admin_notice_error',
1830 - esc_html__('No valid content found on the provided URL.', 'mxchat'),
1831 - 30
1832 - );
1833 - wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
1834 - exit;
1835 - }
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);
1836 2357
1837 - $embedding_vector = $this->mxchat_generate_embedding($sanitized_content);
1838 - if (is_array($embedding_vector)) {
1839 - MxChat_Utils::submit_content_to_db(
1840 - $sanitized_content,
1841 - $submitted_url,
1842 - $this->options['api_key']
1843 - );
1844 - set_transient('mxchat_admin_notice_success',
1845 - esc_html__('URL content successfully submitted!', 'mxchat'),
1846 - 30
1847 - );
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;
1848 2377 } else {
1849 - set_transient('mxchat_admin_notice_error',
1850 - esc_html__('Failed to generate embedding for the URL content. Please check your API key and try again.', 'mxchat'),
1851 - 30
1852 - );
2378 + $current_bot_id = 'default';
2379 + $multibot_active = false;
1853 2380 }
1854 2381
1855 - wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
1856 - exit;
1857 -}
1858 -private function handle_sitemap_for_knowledge_base($xml, $sitemap_url) {
1859 - if (!current_user_can('manage_options')) {
1860 - //error_log(esc_html__('Unauthorized sitemap processing attempt', 'mxchat'));
1861 - return false;
1862 - }
2382 + // ================================
2383 + // DATA SOURCE CONFIGURATION
2384 + // ================================
1863 2385
1864 - try {
1865 - $sitemap_url = esc_url_raw($sitemap_url);
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'] ?? '';
1866 2390
1867 - if (!$xml || !is_object($xml)) {
1868 - throw new Exception(__('Invalid XML object provided', 'mxchat'));
1869 - }
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';
1870 2394
1871 - $urls = [];
1872 - foreach ($xml->url as $url_element) {
1873 - $url = esc_url_raw((string)$url_element->loc);
1874 - if ($url) {
1875 - $urls[] = $url;
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'));
2399 +
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);
2408 +
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));
2415 +
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) . '...');
1876 2421 }
1877 2422 }
2423 + //error_log('=== END DEBUG ===');
1878 2424
1879 - $total_urls = absint(count($urls));
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;
1880 2429
1881 - if ($total_urls < 1) {
1882 - throw new Exception(__('No valid URLs found in sitemap', 'mxchat'));
1883 - }
2430 + $total_pages = ceil($total_records / $per_page);
1884 2431
1885 - wp_schedule_single_event(time(), 'mxchat_process_sitemap_urls', array(
1886 - 'urls' => $urls,
1887 - 'sitemap_url' => $sitemap_url,
1888 - 'total_urls' => $total_urls,
1889 - 'batch_size' => absint(10),
1890 - 'batch_pause' => absint(5)
1891 - ));
2432 + } else {
2433 + //error_log('DEBUG: Using WORDPRESS DB data source');
2434 + // WORDPRESS DB DATA SOURCE (your existing logic)
2435 + $data_source = 'wordpress';
1892 2436
1893 - $status_data = array(
1894 - 'total_urls' => $total_urls,
1895 - 'processed_urls' => 0,
1896 - 'status' => 'processing',
1897 - 'last_update' => time()
1898 - );
2437 + // Initialize these variables for WordPress DB
2438 + $total_in_database = 0;
2439 + $showing_recent_only = false;
1899 2440
1900 - set_transient(
1901 - sanitize_key('mxchat_sitemap_status_' . md5($sitemap_url)),
1902 - array_map('sanitize_text_field', $status_data),
1903 - DAY_IN_SECONDS
1904 - );
2441 + $offset = ($current_page - 1) * $per_page;
1905 2442
1906 - 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();
1907 2446
1908 - } catch (\Exception $e) {
1909 - //error_log(sprintf(esc_html__('Error preparing sitemap for processing: %s', 'mxchat'), esc_html($e->getMessage())));
1910 - return false;
1911 - }
1912 -}
1913 -public static function process_sitemap_urls_cron($urls, $sitemap_url, $total_urls, $batch_size, $batch_pause) {
1914 - // Validate inputs
1915 - $sitemap_url = esc_url_raw($sitemap_url);
1916 - $total_urls = absint($total_urls);
1917 - $batch_size = absint($batch_size);
1918 - $batch_pause = absint($batch_pause);
2447 + if ($search_query) {
2448 + $where_clauses[] = "article_content LIKE %s";
2449 + $where_values[] = '%' . $wpdb->esc_like($search_query) . '%';
2450 + }
1919 2451
1920 - if (!is_array($urls) || empty($urls)) {
1921 - return;
1922 - }
2452 + if ($content_type_filter) {
2453 + $where_clauses[] = "content_type = %s";
2454 + $where_values[] = $content_type_filter;
2455 + }
1923 2456
1924 - try {
1925 - $status_key = sanitize_key('mxchat_sitemap_status_' . md5($sitemap_url));
1926 - $status = get_transient($status_key);
2457 + $sql_where = "";
2458 + if (!empty($where_clauses)) {
2459 + $sql_where = "WHERE " . implode(" AND ", $where_clauses);
2460 + }
1927 2461
1928 - if (!$status || !is_array($status)) {
1929 - throw new Exception('Invalid status data retrieved from transient');
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)";
1930 2475 }
2476 + $total_records = $wpdb->get_var($count_query);
2477 + $total_pages = ceil($total_records / $per_page);
1931 2478
1932 - $start_url = absint($status['processed_urls']);
1933 - $end_url = min($start_url + $batch_size, $total_urls);
1934 - $instance = new self();
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 + );
2493 + }
2494 + $page_urls = $wpdb->get_results($urls_query);
1935 2495
1936 - // Track failures in batch
1937 - $batch_stats = [
1938 - 'processed' => 0,
1939 - 'failed' => 0,
1940 - 'last_error' => ''
1941 - ];
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 + }
1942 2513
1943 - for ($i = $start_url; $i < $end_url; $i++) {
1944 - $page_url = esc_url_raw($urls[$i]);
1945 - $page_response = wp_remote_get($page_url);
2514 + // Build query to fetch all rows for these URLs
2515 + $url_conditions = array();
2516 + $url_values = array();
1946 2517
1947 - if (is_wp_error($page_response) || wp_remote_retrieve_response_code($page_response) !== 200) {
1948 - $batch_stats['failed']++;
1949 - continue;
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);
1950 2522 }
1951 2523
1952 - $page_html = wp_remote_retrieve_body($page_response);
1953 - $page_content = $instance->mxchat_extract_main_content($page_html);
1954 - $sanitized_content = $instance->mxchat_sanitize_content_for_api($page_content);
2524 + if ($has_empty_url) {
2525 + $url_conditions[] = "(source_url = '' OR source_url IS NULL)";
2526 + }
1955 2527
1956 - if (!empty($sanitized_content)) {
1957 - $embedding_vector = $instance->mxchat_generate_embedding($sanitized_content);
2528 + if (!empty($url_conditions)) {
2529 + $url_where = "WHERE (" . implode(" OR ", $url_conditions) . ")";
1958 2530
1959 - if (is_array($embedding_vector)) {
1960 - $options = get_option('mxchat_options');
1961 - $submission_result = MxChat_Utils::submit_content_to_db($sanitized_content, $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);
2535 + }
1962 2536
1963 - if (is_wp_error($submission_result)) {
1964 - $batch_stats['failed']++;
1965 - $batch_stats['last_error'] = $submission_result->get_error_message();
1966 - continue;
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 + }
2545 +
2546 + $prompts = $wpdb->get_results($prompts_query);
2547 +
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;
2554 +
2555 + // First sort by URL order (newest URLs first)
2556 + if ($order_a !== $order_b) {
2557 + return $order_a - $order_b;
1967 2558 }
1968 2559
1969 - $batch_stats['processed']++;
1970 - } else {
1971 - $batch_stats['failed']++;
1972 - $batch_stats['last_error'] = 'Failed to generate embedding. Please check OpenAI API key.';
1973 - }
2560 + // Within same URL, sort by timestamp DESC (newest chunks first)
2561 + return strtotime($b->timestamp) - strtotime($a->timestamp);
2562 + });
1974 2563 }
2564 + }
2565 + }
1975 2566
1976 - $status['processed_urls'] = absint($i + 1);
1977 - $status['last_update'] = time();
1978 - $status['failed_urls'] = absint($status['failed_urls'] ?? 0) + $batch_stats['failed'];
1979 - $status['last_error'] = $batch_stats['last_error'];
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');
1980 2575
1981 - set_transient($status_key, array_map('sanitize_text_field', $status), DAY_IN_SECONDS);
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;
1982 2579 }
1983 2580
1984 - // If all URLs in this batch failed, stop processing
1985 - if ($batch_stats['processed'] === 0 && $batch_stats['failed'] > 0) {
1986 - $status['status'] = 'error';
1987 - $status['error'] = sprintf(
1988 - 'Processing stopped: %d consecutive failures. Last error: %s',
1989 - $batch_stats['failed'],
1990 - $batch_stats['last_error']
1991 - );
1992 - set_transient($status_key, array_map('sanitize_text_field', $status), DAY_IN_SECONDS);
1993 - return;
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 + }
1994 2587 }
1995 2588
1996 - if ($end_url < $total_urls) {
1997 - wp_schedule_single_event(time() + $batch_pause, 'mxchat_process_sitemap_urls', array(
1998 - 'urls' => array_map('esc_url_raw', $urls),
1999 - 'sitemap_url' => $sitemap_url,
2000 - 'total_urls' => $total_urls,
2001 - 'batch_size' => $batch_size,
2002 - 'batch_pause' => $batch_pause,
2003 - ));
2004 - } else {
2005 - $status['status'] = 'complete';
2006 - set_transient($status_key, array_map('sanitize_text_field', $status), DAY_IN_SECONDS);
2589 + // Preserve content type filter if present
2590 + if ($content_type_filter) {
2591 + $pagination_args['content_type'] = $content_type_filter;
2007 2592 }
2008 - } catch (\Exception $e) {
2009 - $status['status'] = 'error';
2010 - $status['error'] = sanitize_text_field($e->getMessage());
2011 - set_transient($status_key, array_map('sanitize_text_field', $status), DAY_IN_SECONDS);
2593 +
2594 + // Build clean base URL
2595 + $base_url = add_query_arg($pagination_args, admin_url('admin.php'));
2596 +
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 = '';
2012 2608 }
2013 -}
2014 2609
2015 -public function get_sitemap_processing_status($sitemap_url) {
2016 - $sitemap_url = esc_url_raw($sitemap_url);
2017 - $status = get_transient(sanitize_key('mxchat_sitemap_status_' . md5($sitemap_url)));
2610 + // ================================
2611 + // PROCESSING STATUS RETRIEVAL
2612 + // ================================
2018 2613
2019 - if (!$status || !is_array($status)) {
2020 - return false;
2021 - }
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'];
2022 2619
2023 - return array(
2024 - 'total_urls' => absint($status['total_urls']),
2025 - 'processed_urls' => absint($status['processed_urls']),
2026 - 'failed_urls' => absint($status['failed_urls'] ?? 0),
2027 - 'percentage' => ($status['total_urls'] > 0)
2028 - ? round((absint($status['processed_urls']) / absint($status['total_urls'])) * 100)
2029 - : 0,
2030 - 'status' => sanitize_text_field($status['status']),
2031 - 'last_update' => human_time_diff(absint($status['last_update']), time()) . ' ' . esc_html__('ago', 'mxchat'),
2032 - 'error' => isset($status['error']) ? sanitize_text_field($status['error']) : '',
2033 - 'last_error' => isset($status['last_error']) ? sanitize_text_field($status['last_error']) : ''
2620 + //error_log('=== DEBUG: mxchat_create_prompts_page data preparation completed ===');
2621 +
2622 + // ================================
2623 + // RENDER PAGE WITH NEW SIDEBAR LAYOUT
2624 + // ================================
2625 +
2626 + // Include the new knowledge page template
2627 + require_once plugin_dir_path(__FILE__) . 'admin-knowledge-page.php';
2628 +
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,
2034 2649 );
2650 +
2651 + // Render the new sidebar-based page
2652 + mxchat_render_knowledge_page($this, $knowledge_manager, $page_data);
2035 2653 }
2036 2654
2037 -public function mxchat_stop_processing() {
2038 - // Verify permissions
2039 - if (!current_user_can('manage_options')) {
2040 - wp_die(esc_html__('Unauthorized access', 'mxchat'));
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'] ?? ''
2671 + );
2672 + //error_log("MXCHAT DEBUG: Default config - use_pinecone: " . ($config['use_pinecone'] ? 'true' : 'false'));
2673 + return $config;
2041 2674 }
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'));
2686 + } else {
2687 + //error_log("MXCHAT DEBUG: Filter returned empty config!");
2688 + }
2689 +
2690 + return is_array($bot_pinecone_config) ? $bot_pinecone_config : array();
2691 +}
2042 2692
2043 - // Verify nonce
2044 - check_admin_referer('mxchat_stop_processing_action', 'mxchat_stop_processing_nonce');
2045 -
2046 - // Get the last sitemap URL and clear its transient
2047 - $sitemap_url = get_transient('mxchat_last_sitemap_url');
2048 - if ($sitemap_url) {
2049 - delete_transient('mxchat_sitemap_status_' . md5($sitemap_url));
2050 - delete_transient('mxchat_last_sitemap_url');
2693 +public function mxchat_delete_chat_history() {
2694 + if (!current_user_can('manage_options')) {
2695 + echo wp_json_encode(['error' => esc_html__('You do not have sufficient permissions.', 'mxchat')]);
2696 + wp_die();
2051 2697 }
2698 + check_ajax_referer('mxchat_delete_chat_history', 'security');
2699 + global $wpdb;
2700 + $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
2052 2701
2053 - // Get the last PDF URL and clear its transient
2054 - $pdf_url = get_transient('mxchat_last_pdf_url');
2055 - if ($pdf_url) {
2056 - delete_transient('mxchat_pdf_status_' . md5($pdf_url));
2057 - delete_transient('mxchat_last_pdf_url');
2058 - }
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';
2059 2705
2060 - // Unschedule any pending sitemap events
2061 - $timestamp = wp_next_scheduled('mxchat_process_sitemap_urls');
2062 - if ($timestamp) {
2063 - wp_unschedule_event($timestamp, 'mxchat_process_sitemap_urls');
2064 - }
2706 + foreach ($_POST['delete_session_ids'] as $session_id) {
2707 + $session_id_sanitized = sanitize_text_field($session_id);
2065 2708
2066 - // Redirect back with a success message
2067 - set_transient('mxchat_admin_notice_success',
2068 - esc_html__('Processing has been stopped successfully.', 'mxchat'),
2069 - 30
2070 - );
2071 - wp_safe_redirect(admin_url('admin.php?page=mxchat-prompts'));
2072 - exit;
2073 -}
2074 -private function mxchat_sanitize_content_for_api($content) {
2075 - // Remove script, style tags, and HTML comments
2076 - $content = preg_replace('/<script\b[^>]*>(.*?)<\/script>/is', '', $content);
2077 - $content = preg_replace('/<style\b[^>]*>(.*?)<\/style>/is', '', $content);
2078 - $content = preg_replace('/<!--(.|\s)*?-->/', '', $content);
2709 + // Clear relevant cache before deletion
2710 + $cache_key = 'chat_session_' . $session_id_sanitized;
2711 + wp_cache_delete($cache_key, 'mxchat_chat_sessions');
2079 2712
2080 - // Remove all HTML tags and decode HTML entities
2081 - $content = wp_strip_all_tags($content);
2082 - $content = html_entity_decode($content, ENT_QUOTES | ENT_HTML5);
2713 + // Perform the deletion from the database table
2714 + $wpdb->delete($table_name, ['session_id' => $session_id_sanitized]);
2083 2715
2084 - // Trim and normalize whitespace
2085 - $content = trim(preg_replace('/\s+/', ' ', $content));
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 + }
2086 2720
2087 - return $content;
2088 -}
2721 + // Delete the corresponding option entry from wp_options table
2722 + delete_option("mxchat_history_" . $session_id_sanitized);
2089 2723
2724 + // Delete any associated metadata options
2725 + delete_option("mxchat_email_" . $session_id_sanitized);
2726 + delete_option("mxchat_agent_name_" . $session_id_sanitized);
2090 2727
2728 + $deleted_count++;
2729 + }
2091 2730
2731 + // Optionally, clear a general cache if you have one
2732 + wp_cache_delete('all_chat_sessions', 'mxchat_chat_sessions');
2092 2733
2093 -public function mxchat_handle_product_change($post_id, $post, $update) {
2094 - if ($post->post_type !== 'product') {
2095 - return;
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')]);
2096 2742 }
2097 2743
2098 - if ($post->post_status === 'publish') {
2099 - add_action('shutdown', function() use ($post_id) {
2100 - $product = wc_get_product($post_id);
2101 - if ($product) {
2102 - $this->mxchat_store_product_embedding($product);
2103 - }
2104 - });
2105 - }
2744 + wp_die();
2106 2745 }
2107 2746
2108 -private function mxchat_store_product_embedding($product) {
2109 - if (!isset($this->options['enable_woocommerce_integration']) ||
2110 - !in_array($this->options['enable_woocommerce_integration'], ['1', 'on'])) {
2111 - 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 '';
2112 2754 }
2113 2755
2114 - $source_url = get_permalink($product->get_id());
2756 + // Normalize line endings and clean up excessive whitespace
2757 + $text = str_replace("\r\n", "\n", $text);
2758 + $text = str_replace("\r", "\n", $text);
2115 2759
2116 - // Build description
2117 - $regular_price = $product->get_regular_price();
2118 - $sale_price = $product->get_sale_price();
2119 - $description = $product->get_description() . "\n\n" .
2120 - esc_html__('Short Description:', 'mxchat') . " " . $product->get_short_description() . "\n" .
2121 - esc_html__('Price:', 'mxchat') . " " . $regular_price . "\n" .
2122 - esc_html__('Sale Price:', 'mxchat') . " " . ($sale_price ?: esc_html__('N/A', 'mxchat')) . "\n" .
2123 - esc_html__('SKU:', 'mxchat') . " " . $product->get_sku();
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);
2124 2763
2125 - // Generate embedding vector
2126 - $embedding_vector = $this->mxchat_generate_embedding($description);
2127 - if (!is_array($embedding_vector)) {
2128 - return;
2129 - }
2764 + // Collapse 3+ consecutive newlines to just 2 (paragraph break)
2765 + $text = preg_replace('/\n{3,}/', "\n\n", $text);
2130 2766
2131 - // Check for Pinecone addon and its settings
2132 - $pinecone_settings = get_option('mxchat_pinecone_addon_options');
2133 - $use_pinecone = false;
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);
2134 2773
2135 - if ($pinecone_settings && is_array($pinecone_settings)) {
2136 - $use_pinecone = (
2137 - isset($pinecone_settings['mxchat_use_pinecone']) &&
2138 - $pinecone_settings['mxchat_use_pinecone'] === '1' &&
2139 - !empty($pinecone_settings['mxchat_pinecone_api_key']) &&
2140 - !empty($pinecone_settings['mxchat_pinecone_host']) &&
2141 - !empty($pinecone_settings['mxchat_pinecone_index']) &&
2142 - !empty($pinecone_settings['mxchat_pinecone_environment'])
2143 - );
2144 - }
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);
2145 2780
2146 - if ($use_pinecone) {
2147 - $result = $this->store_in_pinecone_main(
2148 - $embedding_vector,
2149 - $description,
2150 - $source_url,
2151 - $pinecone_settings['mxchat_pinecone_api_key'],
2152 - $pinecone_settings['mxchat_pinecone_environment'],
2153 - $pinecone_settings['mxchat_pinecone_index']
2154 - );
2781 + // Process inline code with single backticks
2782 + $text = preg_replace('/`([^`]+)`/', '<code>$1</code>', $text);
2155 2783
2156 - if (!$result['success']) {
2157 - // Fallback to WordPress DB if Pinecone storage fails
2158 - $this->store_in_wordpress_db($description, $source_url, $embedding_vector);
2159 - }
2160 - } else {
2161 - // Use WordPress DB storage
2162 - $this->store_in_wordpress_db($description, $source_url, $embedding_vector);
2163 - }
2164 -}
2784 + // Process bold text **text** or __text__
2785 + $text = preg_replace('/\*\*(.+?)\*\*/', '<strong>$1</strong>', $text);
2786 + $text = preg_replace('/__(.+?)__/', '<strong>$1</strong>', $text);
2165 2787
2166 -public function mxchat_handle_product_delete($post_id) {
2167 - if (get_post_type($post_id) !== 'product') {
2168 - return;
2169 - }
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);
2170 2791
2171 - $source_url = get_permalink($post_id);
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);
2172 2798
2173 - // Check for Pinecone addon and its settings
2174 - $pinecone_settings = get_option('mxchat_pinecone_addon_options');
2175 - $use_pinecone = false;
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);
2176 2804
2177 - if ($pinecone_settings && is_array($pinecone_settings)) {
2178 - $use_pinecone = (
2179 - isset($pinecone_settings['mxchat_use_pinecone']) &&
2180 - $pinecone_settings['mxchat_use_pinecone'] === '1' &&
2181 - !empty($pinecone_settings['mxchat_pinecone_api_key']) &&
2182 - !empty($pinecone_settings['mxchat_pinecone_host']) &&
2183 - !empty($pinecone_settings['mxchat_pinecone_index']) &&
2184 - !empty($pinecone_settings['mxchat_pinecone_environment'])
2185 - );
2186 - }
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 + );
2187 2816
2188 - if ($use_pinecone) {
2189 - $this->delete_from_pinecone(
2190 - array($source_url),
2191 - $pinecone_settings['mxchat_pinecone_api_key'],
2192 - $pinecone_settings['mxchat_pinecone_environment'],
2193 - $pinecone_settings['mxchat_pinecone_index']
2194 - );
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);
2823 +
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);
2827 +
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 + }));
2832 +
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>';
2195 2842 } else {
2196 - // Fallback to WordPress DB deletion
2197 - global $wpdb;
2198 - $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
2199 - $wpdb->delete($table_name, array('source_url' => $source_url), array('%s'));
2843 + // Single paragraph - just convert newlines to <br>
2844 + $text = nl2br($paragraphs[0]);
2200 2845 }
2846 +
2847 + return $text;
2201 2848 }
2202 2849
2203 -
2204 2850 /**
2205 - * Delete vectors from Pinecone by source URL
2206 - *
2207 - * @param array $urls Array of source URLs to delete
2208 - * @param string $api_key Pinecone API key
2209 - * @param string $environment Pinecone environment
2210 - * @param string $index_name Pinecone index name
2211 - * @return array Associative array with 'success' boolean and 'message' string
2851 + * AJAX handler to fetch RAG context for a specific message
2852 + * Used by the transcript viewer to show retrieved documents
2212 2853 */
2213 -private function delete_from_pinecone($urls, $api_key, $environment, $index_name) {
2214 - // Get the Pinecone host from options (matching your store_in_pinecone_main pattern)
2215 - $options = get_option('mxchat_pinecone_addon_options');
2216 - $host = $options['mxchat_pinecone_host'] ?? '';
2854 +public function mxchat_get_rag_context() {
2855 + // Check permissions
2856 + if (!current_user_can('manage_options')) {
2857 + wp_send_json_error(['message' => esc_html__('You do not have sufficient permissions.', 'mxchat')]);
2858 + wp_die();
2859 + }
2217 2860
2218 - if (empty($host)) {
2219 - return array(
2220 - 'success' => false,
2221 - 'message' => 'Pinecone host is not configured. Please set the host in your settings.'
2222 - );
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();
2223 2865 }
2224 2866
2225 - // Build API endpoint using the configured host
2226 - $api_endpoint = "https://{$host}/vectors/delete";
2867 + $message_id = absint($_POST['message_id']);
2227 2868
2228 - // Create vector IDs from URLs (matching your store method's ID generation)
2229 - $vector_ids = array_map('md5', $urls);
2869 + global $wpdb;
2870 + $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
2230 2871
2231 - // Prepare the delete request body
2232 - $request_body = array(
2233 - 'ids' => $vector_ids,
2234 - 'filter' => array(
2235 - 'source_url' => array(
2236 - '$in' => $urls
2237 - )
2238 - )
2239 - );
2240 -
2241 - // Make the deletion request
2242 - $response = wp_remote_post($api_endpoint, array(
2243 - 'headers' => array(
2244 - 'Api-Key' => $api_key,
2245 - 'accept' => 'application/json',
2246 - 'content-type' => 'application/json'
2247 - ),
2248 - 'body' => wp_json_encode($request_body),
2249 - 'timeout' => 30,
2250 - 'data_format' => 'body'
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
2251 2876 ));
2252 2877
2253 - // Handle WordPress HTTP API errors
2254 - if (is_wp_error($response)) {
2255 - return array(
2256 - 'success' => false,
2257 - 'message' => $response->get_error_message()
2258 - );
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();
2259 2881 }
2260 2882
2261 - // Check response status
2262 - $response_code = wp_remote_retrieve_response_code($response);
2263 - if ($response_code !== 200) {
2264 - $body = wp_remote_retrieve_body($response);
2265 - return array(
2266 - 'success' => false,
2267 - 'message' => sprintf(
2268 - 'Pinecone API error (HTTP %d): %s',
2269 - $response_code,
2270 - $body
2271 - )
2272 - );
2273 - }
2883 + // Decode the JSON data
2884 + $rag_context = json_decode($result->rag_context, true);
2274 2885
2275 - // Parse response body
2276 - $body = wp_remote_retrieve_body($response);
2277 - $response_data = json_decode($body, true);
2278 -
2279 - // Final validation of the response
2280 2886 if (json_last_error() !== JSON_ERROR_NONE) {
2281 - return array(
2282 - 'success' => false,
2283 - 'message' => 'Failed to parse Pinecone response: ' . json_last_error_msg()
2284 - );
2887 + wp_send_json_error(['message' => esc_html__('Invalid RAG context data.', 'mxchat')]);
2888 + wp_die();
2285 2889 }
2286 2890
2287 - return array(
2288 - 'success' => true,
2289 - 'message' => sprintf('Successfully deleted %d vectors from Pinecone', count($vector_ids))
2290 - );
2891 + wp_send_json_success($rag_context);
2892 + wp_die();
2291 2893 }
2292 2894
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) {
2899 + return;
2900 + }
2293 2901
2294 -public function mxchat_fetch_chat_history() {
2295 - global $wpdb;
2296 - $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
2902 + $dismiss_button = '<button type="button" class="notice-dismiss"><span class="screen-reader-text">' . esc_html__('Dismiss this notice.', 'mxchat') . '</span></button>';
2297 2903
2298 - if (!current_user_can('manage_options')) {
2299 - wp_die(esc_html__('You do not have sufficient permissions to view this page.', 'mxchat'));
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');
2300 2909 }
2301 2910
2302 - // First get unique session IDs ordered by most recent message in each session
2303 - $session_ids = $wpdb->get_col(
2304 - $wpdb->prepare(
2305 - "SELECT DISTINCT session_id
2306 - FROM {$table_name}
2307 - GROUP BY session_id
2308 - ORDER BY MAX(timestamp) DESC"
2309 - )
2310 - );
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');
2916 + }
2311 2917
2312 - if (empty($session_ids)) {
2313 - wp_die(esc_html__('No chat history available.', 'mxchat'));
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');
2314 2923 }
2924 +}
2315 2925
2316 - ob_start();
2317 - echo '<div class="mxchat-transcript">';
2318 2926
2319 - // Iterate through sessions from newest to oldest
2320 - foreach ($session_ids as $session_id) {
2321 - // Get the email associated with this session (if available)
2322 - $email = $wpdb->get_var(
2323 - $wpdb->prepare(
2324 - "SELECT user_email
2325 - FROM {$table_name}
2326 - WHERE session_id = %s AND user_email != ''
2327 - ORDER BY timestamp ASC
2328 - LIMIT 1",
2329 - $session_id
2330 - )
2331 - );
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';
2332 2930
2333 - // Debug: Check email retrieval
2334 - if (empty($email)) {
2335 - //error_log("No email found for session {$session_id}");
2336 - } else {
2337 - //error_log("Email for session {$session_id}: " . $email);
2338 - }
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();
2339 2935
2340 - // Get messages for this session ordered by timestamp
2341 - $messages = $wpdb->get_results(
2342 - $wpdb->prepare(
2343 - "SELECT * FROM {$table_name}
2344 - WHERE session_id = %s
2345 - ORDER BY timestamp ASC",
2346 - $session_id
2347 - )
2348 - );
2936 + // Render the consolidated Pro & Extensions page
2937 + mxchat_render_pro_page($this, $addons_config);
2938 +}
2349 2939
2350 - // Start session block
2351 - echo '<div class="mxchat-session">';
2352 - echo '<div class="mxchat-session-header">';
2353 - // Wrap checkbox and session ID in one block
2354 - echo '<div class="mxchat-session-id">';
2355 - echo '<input type="checkbox" name="delete_session_ids[]" value="' . esc_attr($session_id) . '"> ';
2356 - echo '<strong>' . esc_html__('Session ID:', 'mxchat') . '</strong> ' . esc_html($session_id);
2357 - echo '</div>';
2940 +/**
2941 + * Check if current activation is linked to a domain
2942 + * This checks YOUR website's database, not the user's local database
2943 + */
2944 +public function is_current_activation_linked($domain) {
2945 + $license_key = get_option('mxchat_activation_key');
2946 + $email = get_option('mxchat_pro_email');
2358 2947
2359 - // Place email directly below the session ID block
2360 - if (!empty($email)) {
2361 - echo '<div class="mxchat-session-email">';
2362 - echo '<strong>' . esc_html__('Email:', 'mxchat') . '</strong> ' . esc_html($email);
2363 - echo '</div>';
2364 - }
2365 - echo '</div>';
2948 + if (empty($license_key) || empty($email)) {
2949 + return false;
2950 + }
2366 2951
2367 - echo '<div class="mxchat-messages">';
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
2958 + ),
2959 + 'timeout' => 10,
2960 + 'sslverify' => false
2961 + ));
2368 2962
2369 - // Display messages for this session
2370 - foreach ($messages as $transcript) {
2371 - $formatted_timestamp = date_i18n('F j, Y g:i a', strtotime($transcript->timestamp));
2372 -
2373 - // Determine message styling
2374 - switch ($transcript->role) {
2375 - case 'assistant':
2376 - case 'bot':
2377 - $message_class = 'bot-message';
2378 - $display_role = esc_html__('Chatbot', 'mxchat');
2379 - break;
2380 - case 'user':
2381 - $message_class = 'user-message';
2382 - $display_role = !empty($transcript->user_identifier)
2383 - ? sanitize_text_field($transcript->user_identifier)
2384 - : esc_html__('User', 'mxchat');
2385 - break;
2386 - case 'agent':
2387 - $message_class = 'agent-message';
2388 - $display_role = esc_html__('Agent', 'mxchat');
2389 - break;
2390 - default:
2391 - $message_class = 'unknown-message';
2392 - $display_role = esc_html__('Unknown', 'mxchat');
2393 - }
2394 -
2395 - // Process message content
2396 - $message_content = wp_kses(
2397 - stripslashes($transcript->message),
2398 - [
2399 - 'b' => [], 'strong' => [], 'i' => [], 'em' => [], 'u' => [],
2400 - 'br' => [], 'p' => [], 'ul' => [], 'ol' => [], 'li' => [],
2401 - 'a' => ['href' => [], 'title' => []]
2402 - ]
2403 - );
2404 - $message_content = nl2br($message_content);
2405 -
2406 - // Render message
2407 - echo '<div class="mxchat-message ' . esc_attr($message_class) . '">';
2408 - echo '<div class="mxchat-message-header">' . esc_html($display_role) . '</div>';
2409 - echo '<div class="mxchat-message-content">' . $message_content . '</div>';
2410 - echo '<div class="mxchat-timestamp">' . esc_html($formatted_timestamp) . '</div>';
2411 - echo '</div>';
2412 - }
2413 -
2414 - // Close session block
2415 - echo '</div></div>';
2963 + if (is_wp_error($response)) {
2964 + return false;
2416 2965 }
2417 2966
2418 - echo '</div>';
2419 - $output = ob_get_clean();
2420 - echo $output;
2421 - wp_die();
2967 + $body = json_decode(wp_remote_retrieve_body($response), true);
2968 + return isset($body['success']) && $body['success'] && isset($body['data']['linked']) && $body['data']['linked'];
2422 2969 }
2423 2970
2424 2971
2425 -public function mxchat_create_activation_page() {
2426 - $license_status = get_option('mxchat_license_status', 'inactive');
2427 - $license_error = get_option('mxchat_license_error', '');
2428 - ?>
2429 - <div class="wrap mxchat-admin-activation">
2430 - <div class="mxchat-pro-hero">
2431 - <h1 class="pro-title">
2432 - <span class="pro-gradient-text">Activate</span> MxChat Pro
2433 - </h1>
2434 - <p class="pro-subtitle">
2435 - <?php esc_html_e('Enter your license key to unlock premium features, advanced AI capabilities, and priority support.', 'mxchat'); ?>
2436 - </p>
2437 - </div>
2438 -
2439 - <?php if ($license_status === 'inactive' && !empty($license_error)): ?>
2440 - <div class="error notice">
2441 - <p><?php echo esc_html($license_error); ?></p>
2442 - </div>
2443 - <?php endif; ?>
2444 -
2445 - <form id="mxchat-activation-form" class="mxchat-pro-form" style="<?php echo $license_status === 'active' ? 'display: none;' : ''; ?>">
2446 - <div class="mxchat-pro-form-container">
2447 - <table class="form-table">
2448 - <tr valign="top">
2449 - <th scope="row"><?php esc_html_e('Email Address', 'mxchat'); ?></th>
2450 - <td>
2451 - <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 />
2452 - </td>
2453 - </tr>
2454 - <tr valign="top">
2455 - <th scope="row"><?php esc_html_e('Activation Key', 'mxchat'); ?></th>
2456 - <td>
2457 - <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 />
2458 - </td>
2459 - </tr>
2460 - </table>
2461 - <?php if ($license_status !== 'active'): ?>
2462 - <div class="mxchat-pro-button-container">
2463 - <button type="submit" id="activate_license_button" class="button button-primary mxchat-pro-button"><?php esc_html_e('Activate License', 'mxchat'); ?></button>
2464 - <div id="mxchat-activation-spinner" class="mxchat-activation-spinner" style="display: none;"></div>
2465 - </div>
2466 - <?php endif; ?>
2467 - </div>
2468 - </form>
2469 - <!-- License Status Display -->
2470 - <div class="mxchat-pro-status">
2471 - <h3><?php esc_html_e('License Status:', 'mxchat'); ?>
2472 - <span id="mxchat-license-status" class="mxchat-status-badge <?php echo $license_status; ?>">
2473 - <?php echo $license_status === 'active' ? esc_html__('Active', 'mxchat') : esc_html__('Inactive', 'mxchat'); ?>
2474 - </span>
2475 - </h3>
2476 - </div>
2477 -
2478 - </div>
2479 - <?php
2480 -}
2481 -
2482 -public function mxchat_intents_page_html() {
2972 +public function mxchat_actions_page_html() {
2483 2973 if (!current_user_can('manage_options')) {
2484 2974 return;
2485 2975 }
2486 2976
2487 - // Keep existing data fetching logic
2488 2977 global $wpdb;
2489 2978 $table_name = $wpdb->prefix . 'mxchat_intents';
2490 - $page = isset($_GET['paged']) ? max(1, intval($_GET['paged'])) : 1;
2491 - $per_page = 20;
2492 - $offset = ($page - 1) * $per_page;
2493 2979
2494 - // Success message
2495 - if (isset($_GET['updated']) && $_GET['updated'] === 'true') {
2496 - echo '<div class="notice notice-success is-dismissible"><p>' .
2497 - esc_html__('Intent updated successfully.', 'mxchat') .
2498 - '</p></div>';
2499 - }
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;
2500 2984
2501 - // Filtering logic
2502 - $where = '1=1';
2503 - $search_term = isset($_GET['s']) ? trim($_GET['s']) : '';
2504 - $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");
2505 2987
2506 - if ($search_term) {
2507 - $search_term_like = '%' . $wpdb->esc_like($search_term) . '%';
2508 - $where .= $wpdb->prepare(' AND (intent_label LIKE %s OR phrases LIKE %s)',
2509 - $search_term_like, $search_term_like);
2510 - }
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);
2511 2992
2512 - if ($callback_filter) {
2513 - $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;
2514 2999 }
2515 3000
2516 - // Pagination
2517 - $total_intents = $wpdb->get_var("SELECT COUNT(*) FROM $table_name WHERE $where");
2518 - $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 + );
2519 3011
2520 - // Get intents
2521 - $intents = $wpdb->get_results($wpdb->prepare(
2522 - "SELECT * FROM $table_name WHERE $where LIMIT %d OFFSET %d",
2523 - $per_page, $offset
2524 - ));
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 +}
2525 3016
2526 - // Get callbacks
2527 - $available_callbacks = $this->mxchat_get_available_callbacks();
2528 -
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
2529 3023 ?>
2530 3024 <div class="wrap mxchat-wrapper">
2531 3025 <!-- Hero Section -->
2532 3026 <div class="mxchat-hero">
2533 3027 <h1 class="mxchat-main-title">
2534 - <span class="mxchat-gradient-text">Intent</span> Manager
3028 + <span class="mxchat-gradient-text">Actions</span> Manager
2535 3029 </h1>
2536 3030 <p class="mxchat-hero-subtitle">
2537 - <?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'); ?>
2538 3032 </p>
2539 3033 </div>
2540 3034
2541 - <div class="mxchat-content">
2542 - <!-- Add Intent Card -->
2543 - <div class="mxchat-card">
2544 - <div class="mxchat-card-header">
2545 - <h2><?php esc_html_e('Add New Intent', 'mxchat'); ?></h2>
2546 - </div>
2547 -
2548 - <div class="mxchat-intent-documentation">
2549 - <p>
2550 - <?php esc_html_e('We highly encourage users to quickly read our ', 'mxchat'); ?>
2551 - <a href="https://mxchat.ai/documentation/#intents" target="_blank" rel="noopener noreferrer">
2552 - <?php esc_html_e('documentation', 'mxchat'); ?>
2553 - </a>
2554 - <?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'); ?>
2555 - </p>
2556 - </div>
2557 -
2558 - <form id="mxchat-add-intent-form" method="post"
2559 - action="<?php echo esc_url(admin_url('admin-post.php')); ?>">
2560 - <input type="hidden" name="action" value="mxchat_add_intent">
2561 - <?php wp_nonce_field('mxchat_add_intent_nonce'); ?>
2562 -
2563 - <div class="mxchat-form-group">
2564 - <label for="intent_label">
2565 - <?php esc_html_e('Intent Label (For your reference only)', 'mxchat'); ?>
2566 - </label>
2567 - <input name="intent_label" type="text" id="intent_label" required
2568 - class="mxchat-intent-input"
2569 - 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); ?>">
2570 3045 </div>
2571 -
2572 - <div class="mxchat-form-group">
2573 - <label for="phrases">
2574 - <?php esc_html_e('Phrases (comma-separated)', 'mxchat'); ?>
2575 - </label>
2576 - <textarea name="phrases" id="phrases" rows="5" required
2577 - class="mxchat-intent-textarea"
2578 - 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>
2579 - </div>
2580 -
2581 - <div class="mxchat-form-group">
2582 - <label for="callback_function">
2583 - <?php esc_html_e('Callback Function', 'mxchat'); ?>
2584 - </label>
2585 - <select name="callback_function" id="callback_function"
2586 - class="mxchat-intent-select" required>
2587 - <option value="">
2588 - <?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); ?>
2589 3053 </option>
2590 - <?php
2591 - $groups = $this->mxchat_get_available_callbacks(true);
2592 - foreach ($groups as $group_label => $group_callbacks) :
2593 - echo '<optgroup label="' . esc_attr($group_label) . '">';
2594 - foreach ($group_callbacks as $function => $data) :
2595 - $label = $data['label'];
2596 - $pro_only = $data['pro_only'];
2597 - $disabled = (!$this->is_activated && $pro_only) ? 'disabled' : '';
2598 - $label_suffix = (!$this->is_activated && $pro_only) ? ' (Pro Only)' : '';
2599 - ?>
2600 - <option value="<?php echo esc_attr($function); ?>"
2601 - <?php echo $disabled; ?>>
2602 - <?php echo esc_html($label . $label_suffix); ?>
2603 - </option>
2604 - <?php
2605 - endforeach;
2606 - echo '</optgroup>';
2607 - endforeach;
2608 - ?>
2609 - </select>
2610 - </div>
2611 -
2612 - <button type="submit" class="mxchat-button-primary">
2613 - <?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'); ?>
2614 3058 </button>
2615 3059 </form>
2616 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>
2617 3068
2618 - <!-- Manage Intents Card -->
2619 - <div class="mxchat-card">
2620 - <div class="mxchat-card-header">
2621 - <h2><?php esc_html_e('Manage Intents', 'mxchat'); ?></h2>
2622 - <div class="mxchat-header-actions">
2623 - <form method="get" class="mxchat-search-form">
2624 - <input type="hidden" name="page" value="mxchat-intents">
2625 - <div class="mxchat-search-group">
2626 - <span class="dashicons dashicons-search"></span>
2627 - <input type="text" name="s"
2628 - placeholder="<?php esc_attr_e('Search Intents', 'mxchat'); ?>"
2629 - value="<?php echo esc_attr($search_term); ?>">
2630 - <select name="callback_filter" class="mxchat-intent-filter">
2631 - <option value="">
2632 - <?php esc_html_e('All Callbacks', 'mxchat'); ?>
2633 - </option>
2634 - <?php foreach ($available_callbacks as $function => $callback_data) :
2635 - $label = $callback_data['label']; ?>
2636 - <option value="<?php echo esc_attr($function); ?>"
2637 - <?php selected($callback_filter, $function); ?>>
2638 - <?php echo esc_html($label); ?>
2639 - </option>
2640 - <?php endforeach; ?>
2641 - </select>
2642 - <button type="submit" class="mxchat-button-secondary">
2643 - <?php esc_html_e('Filter', 'mxchat'); ?>
2644 - </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>
2645 3110 </div>
2646 - </form>
2647 - </div>
2648 - </div>
2649 3111
2650 - <div class="mxchat-table-wrapper">
2651 - <table class="mxchat-records-table">
2652 - <thead>
2653 - <tr>
2654 - <th><?php esc_html_e('Intent Label', 'mxchat'); ?></th>
2655 - <th><?php esc_html_e('Phrases', 'mxchat'); ?></th>
2656 - <th><?php esc_html_e('Callback Function', 'mxchat'); ?></th>
2657 - <th><?php esc_html_e('Similarity Threshold', 'mxchat'); ?></th>
2658 - <th><?php esc_html_e('Actions', 'mxchat'); ?></th>
2659 - </tr>
2660 - </thead>
2661 - <tbody>
2662 - <?php if ($intents) :
2663 - foreach ($intents as $intent) :
2664 - $callback_function = $intent->callback_function;
2665 - $callback_label = isset($available_callbacks[$callback_function]['label'])
2666 - ? $available_callbacks[$callback_function]['label']
2667 - : $callback_function;
2668 - $threshold_value = isset($intent->similarity_threshold)
2669 - ? round($intent->similarity_threshold * 100)
2670 - : 85;
2671 -
2672 - // Check if this is a form intent (its intent_label starts with "Form ")
2673 - $is_form_intent = strpos($intent->intent_label, 'Form ') === 0;
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>
3117 +
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 + }
2674 3130 ?>
2675 - <tr<?php echo $is_form_intent ? ' class="mxchat-form-intent"' : ''; ?>>
2676 - <td>
2677 - <?php
2678 - if ($is_form_intent) {
2679 - // Attempt to extract the form ID from the intent_label.
2680 - preg_match('/Form (\d+)/', $intent->intent_label, $matches);
2681 - $form_id = isset($matches[1]) ? intval($matches[1]) : 0;
2682 - if ($form_id) {
2683 - global $wpdb;
2684 - $forms_table = $wpdb->prefix . 'mxchat_forms';
2685 - $form = $wpdb->get_row($wpdb->prepare("SELECT title FROM $forms_table WHERE id = %d", $form_id));
2686 - if ($form && !empty($form->title)) {
2687 - echo esc_html($form->title);
2688 - } else {
2689 - echo esc_html($intent->intent_label);
2690 - }
2691 - } else {
2692 - echo esc_html($intent->intent_label);
2693 - }
2694 - 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>';
2695 3156 } else {
2696 - echo esc_html($intent->intent_label);
3157 + echo '<span class="mxchat-bot-badge">' . esc_html($bot_id) . '</span>';
2697 3158 }
2698 - ?>
2699 - </td>
2700 - <td class="mxchat-content-cell">
2701 - <?php echo esc_html($intent->phrases); ?>
2702 - </td>
2703 - <td><?php echo esc_html($callback_label); ?></td>
2704 - <td>
2705 - <!-- Similarity threshold adjustment (shown for all intents) -->
2706 - <form method="post"
2707 - action="<?php echo esc_url(admin_url('admin-post.php')); ?>"
2708 - class="mxchat-threshold-form">
2709 - <?php wp_nonce_field('mxchat_update_intent_threshold_nonce'); ?>
2710 - <input type="hidden" name="action"
2711 - value="mxchat_update_intent_threshold">
2712 - <input type="hidden" name="intent_id"
2713 - value="<?php echo esc_attr($intent->id); ?>">
2714 - <div class="mxchat-slider-group">
2715 - <input type="range"
2716 - name="intent_threshold"
2717 - id="intent_threshold_<?php echo esc_attr($intent->id); ?>"
2718 - min="70"
2719 - max="95"
2720 - value="<?php echo esc_attr($threshold_value); ?>"
2721 - class="mxchat-intent-slider"
2722 - oninput="document.getElementById('threshold_output_<?php echo esc_attr($intent->id); ?>').value = this.value + '%'">
2723 - <output id="threshold_output_<?php echo esc_attr($intent->id); ?>"
2724 - class="mxchat-intent-output">
2725 - <?php echo esc_html($threshold_value); ?>%
2726 - </output>
2727 - </div>
2728 - <button type="submit" class="mxchat-button-icon">
2729 - <span class="dashicons dashicons-saved"></span>
2730 - </button>
2731 - </form>
2732 - </td>
2733 - <td class="mxchat-actions-cell">
2734 - <?php if (!$is_form_intent) : ?>
2735 - <!-- Standard intents: allow edit and delete -->
2736 - <button type="button"
2737 - class="mxchat-button-icon mxchat-edit-button"
2738 - data-intent-id="<?php echo esc_attr($intent->id); ?>"
2739 - data-phrases="<?php echo esc_attr($intent->phrases); ?>">
2740 - <span class="dashicons dashicons-edit"></span>
2741 - </button>
2742 - <form method="post"
2743 - action="<?php echo esc_url(admin_url('admin-post.php')); ?>"
2744 - class="mxchat-delete-form"
2745 - onsubmit="return confirm('<?php esc_attr_e('Are you sure you want to delete this intent?', 'mxchat'); ?>');">
2746 - <?php wp_nonce_field('mxchat_delete_intent_nonce'); ?>
2747 - <input type="hidden" name="action" value="mxchat_delete_intent">
2748 - <input type="hidden" name="intent_id"
2749 - value="<?php echo esc_attr($intent->id); ?>">
2750 - <button type="submit" class="mxchat-button-icon">
2751 - <span class="dashicons dashicons-trash"></span>
2752 - </button>
2753 - </form>
2754 - <?php else : ?>
2755 - <!-- Form intents: show manage in forms button -->
2756 - <?php
2757 - preg_match('/Form (\d+)/', $intent->intent_label, $matches);
2758 - $form_id = isset($matches[1]) ? $matches[1] : '';
2759 - ?>
2760 - <a href="<?php echo esc_url(admin_url('admin.php?page=mxchat-forms&action=edit&form_id=' . $form_id)); ?>"
2761 - class="mxchat-button-secondary">
2762 - <?php esc_html_e('Manage in Forms', 'mxchat'); ?>
2763 - </a>
2764 - <?php endif; ?>
2765 - </td>
2766 - </tr>
2767 - <?php endforeach;
2768 - else : ?>
2769 - <tr>
2770 - <td colspan="5" class="mxchat-no-records">
2771 - <?php esc_html_e('No intents found.', 'mxchat'); ?>
2772 - </td>
2773 - </tr>
2774 - <?php endif; ?>
2775 - </tbody>
2776 - </table>
2777 - </div>
3159 + }
3160 + }
3161 + ?>
3162 + </div>
3163 + </div>
3164 + </div>
2778 3165
2779 - <?php if ($total_pages > 1) : ?>
2780 - <div class="mxchat-pagination">
2781 - <?php
2782 - echo paginate_links(array(
2783 - 'base' => add_query_arg('paged', '%#%'),
2784 - 'format' => '',
2785 - 'prev_text' => __('&laquo; Previous', 'mxchat'),
2786 - 'next_text' => __('Next &raquo;', 'mxchat'),
2787 - 'total' => $total_pages,
2788 - 'current' => $page
2789 - ));
2790 - ?>
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>
2791 3228 </div>
2792 3229 <?php endif; ?>
2793 3230 </div>
2794 3231 </div>
2795 3232
2796 - <!-- Edit Modal -->
2797 -<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;">
2798 3251 <div class="mxchat-modal-content">
2799 3252 <span class="mxchat-modal-close">&times;</span>
2800 - <div class="mxchat-card-header">
2801 - <h2><?php esc_html_e('Edit Intent Phrases', 'mxchat'); ?></h2>
2802 - </div>
2803 3253
2804 - <form id="mxchat-edit-intent-form" method="post"
2805 - action="<?php echo esc_url(admin_url('admin-post.php')); ?>">
2806 - <?php wp_nonce_field('mxchat_edit_intent_nonce'); ?>
2807 - <input type="hidden" name="action" value="mxchat_edit_intent">
2808 - <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="">
2809 3262
2810 - <div class="mxchat-form-group">
2811 - <label for="edit_phrases">
2812 - <?php esc_html_e('Phrases (comma-separated)', 'mxchat'); ?>
2813 - </label>
2814 - <textarea name="phrases"
2815 - id="edit_phrases"
2816 - rows="5"
2817 - class="mxchat-intent-textarea"
2818 - 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>
2819 3369 </div>
2820 3370
2821 - <div class="mxchat-modal-actions">
2822 - <button type="submit" class="mxchat-button-primary">
2823 - <?php esc_html_e('Update Phrases', 'mxchat'); ?>
2824 - </button>
2825 - <button type="button" class="mxchat-button-secondary mxchat-modal-cancel">
2826 - <?php esc_html_e('Cancel', 'mxchat'); ?>
2827 - </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>
2828 3481 </div>
2829 3482 </form>
2830 3483 </div>
2831 3484 </div>
2832 3485
2833 - </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>
2834 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 + }
2835 3499
2836 - <div id="mxchat-intent-loading" class="mxchat-intent-loading" style="display: none;">
2837 - <div class="mxchat-intent-loading-spinner"></div>
2838 - <div class="mxchat-intent-loading-text">
2839 - <?php esc_html_e('Saving intent, please wait...', 'mxchat'); ?>
2840 - </div>
2841 - </div>
2842 -<?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 . '...';
2843 3508 }
3509 +public function mxchat_add_enabled_column_to_intents() {
3510 + global $wpdb;
3511 + $table_name = $wpdb->prefix . 'mxchat_intents';
2844 3512
3513 + // Check if the column already exists
3514 + $columns = $wpdb->get_results("SHOW COLUMNS FROM $table_name LIKE 'enabled'");
2845 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 +}
2846 3521
2847 -/**
2848 - * Handle editing of intent phrases
2849 - *
2850 - * @since 1.0.0
2851 - * @return void
2852 - */
2853 -public function handle_edit_intent() {
2854 - // Verify nonce and user capabilities
2855 - if (!isset($_POST['_wpnonce']) || !wp_verify_nonce($_POST['_wpnonce'], 'mxchat_edit_intent_nonce')) {
2856 - 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') );
2857 3525 }
2858 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)
2859 3542 if (!current_user_can('manage_options')) {
2860 - wp_die(esc_html__('You do not have permission to perform this action.', 'mxchat'));
3543 + wp_die(esc_html__('Unauthorized user', 'mxchat'));
2861 3544 }
3545 + check_admin_referer('mxchat_edit_intent');
2862 3546
2863 - // Validate and sanitize input
3547 + // Get POST data
2864 3548 $intent_id = isset($_POST['intent_id']) ? absint($_POST['intent_id']) : 0;
2865 - if (!$intent_id) {
2866 - 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';
2867 3561 }
3562 +
3563 + $enabled_bots_json = json_encode($enabled_bots);
2868 3564
2869 - $phrases_input = isset($_POST['phrases']) ? sanitize_textarea_field($_POST['phrases']) : '';
2870 - if (empty($phrases_input)) {
2871 - 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;
2872 3569 }
2873 3570
2874 - // Process phrases the same way as in intent creation
2875 3571 $phrases_array = array_map('sanitize_text_field', array_filter(array_map('trim', explode(',', $phrases_input))));
2876 -
2877 3572 if (empty($phrases_array)) {
2878 - 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;
2879 3575 }
2880 3576
2881 - // Generate embeddings and combine them
3577 + // Generate embeddings with improved error handling
2882 3578 $vectors = [];
3579 + $failed_phrases = [];
3580 +
2883 3581 foreach ($phrases_array as $phrase) {
2884 3582 $embedding_vector = $this->mxchat_generate_embedding($phrase, $this->options['api_key']);
2885 3583 if (is_array($embedding_vector)) {
2886 3584 $vectors[] = $embedding_vector;
2887 3585 } else {
2888 - wp_die(esc_html__('Error generating embedding for phrase: ', 'mxchat') . esc_html($phrase));
3586 + $failed_phrases[] = $phrase;
2889 3587 }
2890 3588 }
2891 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 +
2892 3600 if (empty($vectors)) {
2893 - 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;
2894 3603 }
2895 3604
2896 - // Create combined vector just like in intent creation
2897 3605 $combined_vector = $this->mxchat_average_vectors($vectors);
2898 3606 $serialized_vector = maybe_serialize($combined_vector);
2899 3607
3608 + // Update the database
2900 3609 global $wpdb;
2901 3610 $table_name = $wpdb->prefix . 'mxchat_intents';
2902 3611
2903 - // Update the intent with both new phrases and the combined vector
2904 3612 $result = $wpdb->update(
2905 3613 $table_name,
2906 3614 array(
3615 + 'intent_label' => $intent_label,
2907 3616 'phrases' => implode(', ', $phrases_array),
2908 - '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
2909 3620 ),
2910 3621 array('id' => $intent_id),
2911 - array('%s', '%s'),
2912 - array('%d')
3622 + array('%s', '%s', '%s', '%f', '%s'), // Format: string, string, string, float, string
3623 + array('%d') // Where format: integer
2913 3624 );
2914 3625
2915 3626 if (false === $result) {
2916 - wp_die(esc_html__('Failed to update intent.', 'mxchat'));
3627 + $this->handle_embedding_error(__('Failed to update action in database.', 'mxchat'));
3628 + return;
2917 3629 }
2918 3630
2919 - // 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 +
2920 3634 $redirect_url = add_query_arg(
2921 3635 array(
2922 - 'page' => 'mxchat-intents',
2923 - 'updated' => 'true'
3636 + 'page' => 'mxchat-actions'
2924 3637 ),
2925 3638 admin_url('admin.php')
2926 3639 );
2927 -
2928 3640 wp_safe_redirect($redirect_url);
2929 3641 exit;
2930 3642 }
2931 -public function mxchat_handle_update_intent_threshold() {
2932 - if ( ! current_user_can( 'manage_options' ) ) {
2933 - 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'));
2934 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 +}
2935 3748
2936 - check_admin_referer('mxchat_update_intent_threshold_nonce');
2937 3749
2938 - if (isset($_POST['intent_id'], $_POST['intent_threshold'])) {
2939 - global $wpdb;
2940 - $table_name = $wpdb->prefix . 'mxchat_intents';
2941 - $intent_id = intval($_POST['intent_id']);
2942 - $threshold_percentage = max(70, min(95, intval($_POST['intent_threshold'])));
2943 - $similarity_threshold = $threshold_percentage / 100;
2944 3750
2945 - $wpdb->update(
2946 - $table_name,
2947 - ['similarity_threshold' => $similarity_threshold],
2948 - ['id' => $intent_id],
2949 - ['%f'],
2950 - ['%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')
2951 3763 );
3764 + wp_safe_redirect($redirect_url);
3765 + exit;
2952 3766 }
3767 +}
2953 3768
2954 - wp_safe_redirect(admin_url('admin.php?page=mxchat-intents'));
2955 - 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 + ));
2956 3858 }
2957 3859
2958 -public function mxchat_handle_add_intent() {
2959 - if ( ! current_user_can( 'manage_options' ) ) {
2960 - 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'));
2961 3868 }
2962 3869
2963 - 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;
2964 3872
3873 + if (!$action_id) {
3874 + wp_send_json_error(__('Invalid action ID', 'mxchat'));
3875 + }
3876 +
2965 3877 global $wpdb;
2966 3878 $table_name = $wpdb->prefix . 'mxchat_intents';
2967 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
2968 3939 $intent_label = isset($_POST['intent_label']) ? sanitize_text_field($_POST['intent_label']) : '';
2969 3940 $phrases_input = isset($_POST['phrases']) ? sanitize_textarea_field($_POST['phrases']) : '';
2970 3941 $callback_function = isset($_POST['callback_function']) ? sanitize_text_field($_POST['callback_function']) : '';
2971 - $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');
2972 3944
2973 - if (empty($intent_label) || empty($callback_function) || empty($phrases_input)) {
2974 - 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'));
2975 3948 }
2976 3949
2977 - $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 + }
2978 3955
2979 - if (!array_key_exists($callback_function, $available_callbacks)) {
2980 - 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);
2981 3963 }
2982 3964
2983 - $is_pro_only = $available_callbacks[$callback_function]['pro_only'];
2984 - if ($is_pro_only && !$this->is_activated) {
2985 - 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';
2986 3968 }
3969 + $enabled_bots_json = json_encode($enabled_bots);
2987 3970
2988 - $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 + );
2989 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)));
2990 4020 if (empty($phrases_array)) {
2991 - 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'));
2992 4022 }
2993 4023
2994 - $vectors = [];
2995 - foreach ($phrases_array as $phrase) {
2996 - $embedding_vector = $this->mxchat_generate_embedding($phrase, $this->options['api_key']);
2997 - if (is_array($embedding_vector)) {
2998 - $vectors[] = $embedding_vector;
2999 - } else {
3000 - wp_die( esc_html__('Error generating embedding for phrase: ', 'mxchat') . esc_html($phrase) );
3001 - }
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 + );
3002 4046 }
3003 4047
3004 - if (!empty($vectors)) {
3005 - $combined_vector = $this->mxchat_average_vectors($vectors);
3006 - $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);
3007 4053
3008 - $result = $wpdb->insert($table_name, [
3009 - 'intent_label' => $intent_label,
3010 - 'phrases' => implode(', ', $phrases_array),
3011 - 'embedding_vector' => $serialized_vector,
3012 - 'callback_function' => $callback_function,
3013 - 'similarity_threshold' => $default_threshold,
3014 - ]);
4054 + $result = $wpdb->update(
4055 + $table_name,
4056 + $update_data,
4057 + array('id' => $intent_id),
4058 + null,
4059 + array('%d')
4060 + );
3015 4061
3016 - if ($result === false) {
3017 - wp_die( esc_html__('Database error: ', 'mxchat') . esc_html($wpdb->last_error) );
3018 - }
3019 - } else {
3020 - 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'));
3021 4064 }
3022 4065
3023 - wp_safe_redirect(admin_url('admin.php?page=mxchat-intents'));
3024 - exit;
4066 + wp_send_json_success(array('updated' => true));
3025 4067 }
3026 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');
3027 4074
4075 + if (!current_user_can('manage_options')) {
4076 + wp_send_json_error(__('Unauthorized', 'mxchat'));
4077 + }
3028 4078
4079 + $intent_id = isset($_POST['intent_id']) ? intval($_POST['intent_id']) : 0;
3029 4080
4081 + if (!$intent_id) {
4082 + wp_send_json_error(__('Invalid action ID', 'mxchat'));
4083 + }
3030 4084
4085 + global $wpdb;
4086 + $table_name = $wpdb->prefix . 'mxchat_intents';
3031 4087
3032 -private function mxchat_get_available_callbacks($grouped = false) {
3033 - $callbacks = [
3034 - 'mxchat_handle_email_capture' => [
3035 - 'label' => __('Email Capture', 'mxchat'),
3036 - 'pro_only' => false,
3037 - 'group' => __('Customer Engagement', 'mxchat'),
3038 - ],
3039 - 'mxchat_generate_image' => [
3040 - 'label' => __('Generate Image', 'mxchat'),
3041 - 'pro_only' => true,
3042 - 'group' => __('Other Features', 'mxchat'),
3043 - ],
3044 - 'mxchat_handle_search_request' => [
3045 - 'label' => __('Brave Web Search', 'mxchat'),
3046 - 'pro_only' => false,
3047 - 'group' => __('Search Features', 'mxchat'),
3048 - ],
3049 - 'mxchat_handle_image_search_request' => [
3050 - 'label' => __('Brave Image Search', 'mxchat'),
3051 - 'pro_only' => false,
3052 - 'group' => __('Search Features', 'mxchat'),
3053 - ],
3054 - 'mxchat_handle_pdf_discussion' => [
3055 - 'label' => __('Chat with PDF', 'mxchat'),
3056 - 'pro_only' => true,
3057 - 'group' => __('Other Features', 'mxchat'),
3058 - ],
3059 - 'mxchat_live_agent_handover' => [
3060 - 'label' => __('Live Agent', 'mxchat'),
3061 - 'pro_only' => true,
3062 - 'group' => __('Customer Engagement', 'mxchat'),
3063 - ],
3064 - 'mxchat_handle_switch_to_chatbot_intent' => [
3065 - 'label' => __('Back to Chatbot', 'mxchat'),
3066 - 'pro_only' => true,
3067 - 'group' => __('Customer Engagement', 'mxchat'),
3068 - ],
3069 - ];
4088 + $result = $wpdb->delete($table_name, array('id' => $intent_id), array('%d'));
3070 4089
3071 - // Allow other plugins to add their callbacks
3072 - $callbacks = apply_filters('mxchat_available_callbacks', $callbacks);
4090 + if ($result === false) {
4091 + wp_send_json_error(__('Failed to delete action', 'mxchat'));
4092 + }
3073 4093
3074 - // Return grouped structure if requested
3075 - if ($grouped) {
3076 - $grouped_callbacks = [];
3077 - foreach ($callbacks as $key => $data) {
3078 - $group_label = $data['group'] ?? __('Other Features', 'mxchat');
3079 - $grouped_callbacks[$group_label][$key] = [
3080 - 'label' => $data['label'],
3081 - 'pro_only' => $data['pro_only'],
3082 - ];
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 + );
3083 4184 }
3084 - return $grouped_callbacks;
3085 4185 }
3086 - return $callbacks;
3087 -}
3088 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 + );
3089 4263
3090 -private function mxchat_average_vectors($vectors) {
3091 - $vector_length = count($vectors[0]);
3092 - $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 + ),
3093 4302
3094 - foreach ($vectors as $vector) {
3095 - for ($i = 0; $i < $vector_length; $i++) {
3096 - $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;
3097 4337 }
3098 4338 }
3099 4339
3100 - // Divide each component by the number of vectors to get the average
3101 - $num_vectors = count($vectors);
3102 - for ($i = 0; $i < $vector_length; $i++) {
3103 - $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 + }
3104 4362 }
3105 4363
3106 - return $sum_vector;
3107 -}
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 + }
3108 4370
3109 -public function mxchat_handle_delete_intent() {
3110 - if ( ! current_user_can( 'manage_options' ) ) {
3111 - 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 + }
3112 4382 }
3113 4383
3114 - 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();
3115 4389
3116 - if (isset($_POST['intent_id'])) {
3117 - global $wpdb;
3118 - $table_name = $wpdb->prefix . 'mxchat_intents';
3119 - $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 + }
3120 4398
3121 - $wpdb->delete($table_name, ['id' => $intent_id], ['%d']);
4399 + // Merge with free callbacks first
4400 + $callbacks = array_merge($free_callbacks, $pro_callbacks);
3122 4401 }
3123 4402
3124 - wp_safe_redirect(admin_url('admin.php?page=mxchat-intents'));
3125 - 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;
3126 4443 }
3127 4444
3128 -
3129 4445 public function mxchat_page_init() {
3130 - // Register settings
3131 4446 register_setting(
3132 4447 'mxchat_option_group',
3133 4448 'mxchat_options',
3134 4449 array($this, 'mxchat_sanitize')
@@ -3139,10 +4454,10 @@
3139 4454 'mxchat_similarity_threshold',
3140 4455 array(
3141 4456 'type' => 'number',
3142 4457 'sanitize_callback' => function($value) {
3143 - $value = absint($value); // Ensure it's an integer
3144 - return min(max($value, 70), 85); // Enforce range
4458 + $value = absint($value);
4459 + return min(max($value, 20), 95);
3145 4460 },
3146 4461 'default' => 80,
3147 4462 )
3148 4463 );
@@ -3154,8 +4469,106 @@
3154 4469 null,
3155 4470 'mxchat-chatbot'
3156 4471 );
3157 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 +
3158 4571 // Similarity Threshold Slider
3159 4572 add_settings_field(
3160 4573 'similarity_threshold', // Field ID
3161 4574 esc_html__('Similarity Threshold', 'mxchat'), // Field title
@@ -3163,9 +4576,18 @@
3163 4576 'mxchat-chatbot', // Page
3164 4577 'mxchat_chatbot_section' // Section
3165 4578 );
3166 4579
4580 + // RAG Sources Limit Slider
3167 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(
3168 4590 'append_to_body',
3169 4591 esc_html__('Auto-Display Chatbot', 'mxchat'),
3170 4592 array($this, 'mxchat_append_to_body_callback'),
3171 4593 'mxchat-chatbot',
@@ -3171,38 +4593,49 @@
3171 4593 'mxchat-chatbot',
3172 4594 'mxchat_chatbot_section'
3173 4595 );
3174 4596
4597 + add_settings_field(
4598 + 'contextual_awareness_toggle',
4599 + esc_html__('Contextual Awareness', 'mxchat'),
4600 + array($this, 'mxchat_contextual_awareness_callback'),
4601 + 'mxchat-chatbot',
4602 + 'mxchat_chatbot_section'
4603 + );
3175 4604
3176 - // Existing fields...
3177 4605 add_settings_field(
3178 - 'api_key',
3179 - esc_html__('OpenAI API Key', 'mxchat'),
3180 - array($this, 'api_key_callback'),
4606 + 'citation_links_toggle',
4607 + esc_html__('Citation Links', 'mxchat'),
4608 + array($this, 'mxchat_citation_links_toggle_callback'),
3181 4609 'mxchat-chatbot',
3182 4610 'mxchat_chatbot_section'
3183 4611 );
3184 4612
3185 4613 add_settings_field(
3186 - 'xai_api_key',
3187 - esc_html__('X.AI API Key', 'mxchat'),
3188 - array($this, 'xai_api_key_callback'),
4614 + 'enable_streaming_toggle',
4615 + esc_html__('Enable Streaming', 'mxchat'),
4616 + array($this, 'enable_streaming_toggle_callback'),
3189 4617 'mxchat-chatbot',
3190 - '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 + )
3191 4623 );
3192 4624
4625 +
3193 4626 add_settings_field(
3194 - 'claude_api_key',
3195 - esc_html__('Claude API Key', 'mxchat'),
3196 - array($this, 'claude_api_key_callback'),
4627 + 'model',
4628 + esc_html__('Chat Model', 'mxchat'),
4629 + array($this, 'mxchat_model_callback'),
3197 4630 'mxchat-chatbot',
3198 4631 'mxchat_chatbot_section'
3199 4632 );
3200 4633
3201 4634 add_settings_field(
3202 - 'deepseek_api_key',
3203 - esc_html__('DeepSeek API Key', 'mxchat'),
3204 - array($this, 'deepseek_api_key_callback'),
4635 + 'embedding_model',
4636 + esc_html__('Embedding Model', 'mxchat'),
4637 + array($this, 'embedding_model_callback'),
3205 4638 'mxchat-chatbot',
3206 4639 'mxchat_chatbot_section'
3207 4640 );
3208 4641
@@ -3213,20 +4646,21 @@
3213 4646 'mxchat-chatbot',
3214 4647 'mxchat_chatbot_section'
3215 4648 );
3216 4649
4650 +
3217 4651 add_settings_field(
3218 - 'model',
3219 - esc_html__('Model', 'mxchat'),
3220 - array($this, 'mxchat_model_callback'),
4652 + 'top_bar_title',
4653 + esc_html__('Top Bar Title', 'mxchat'),
4654 + array($this, 'mxchat_top_bar_title_callback'),
3221 4655 'mxchat-chatbot',
3222 4656 'mxchat_chatbot_section'
3223 4657 );
3224 4658
3225 4659 add_settings_field(
3226 - 'top_bar_title',
3227 - esc_html__('Top Bar Title', 'mxchat'),
3228 - array($this, 'mxchat_top_bar_title_callback'),
4660 + 'ai_agent_text',
4661 + esc_html__('AI Agent Text', 'mxchat'),
4662 + array($this, 'mxchat_ai_agent_text_callback'),
3229 4663 'mxchat-chatbot',
3230 4664 'mxchat_chatbot_section'
3231 4665 );
3232 4666
@@ -3254,48 +4688,40 @@
3254 4688 'mxchat_chatbot_section'
3255 4689 );
3256 4690
3257 4691 add_settings_field(
3258 - 'intro_message',
3259 - esc_html__('Introductory Message', 'mxchat'),
3260 - array($this, 'mxchat_intro_message_callback'),
4692 + 'enable_name_field',
4693 + esc_html__('Require Name Field', 'mxchat'),
4694 + array($this, 'enable_name_field_callback'),
3261 4695 'mxchat-chatbot',
3262 4696 'mxchat_chatbot_section'
3263 4697 );
3264 4698
3265 4699 add_settings_field(
3266 - 'input_copy',
3267 - esc_html__('Input Copy', 'mxchat'),
3268 - array($this, 'mxchat_input_copy_callback'),
4700 + 'name_field_placeholder',
4701 + esc_html__('Name Field Placeholder', 'mxchat'),
4702 + array($this, 'name_field_placeholder_callback'),
3269 4703 'mxchat-chatbot',
3270 4704 'mxchat_chatbot_section'
3271 4705 );
3272 4706
3273 4707 add_settings_field(
3274 - 'rate_limit_logged_out',
3275 - __('Rate Limit for Logged-out Users', 'mxchat'),
3276 - array($this, 'mxchat_rate_limit_logged_out_callback'),
4708 + 'intro_message',
4709 + esc_html__('Introductory Message', 'mxchat'),
4710 + array($this, 'mxchat_intro_message_callback'),
3277 4711 'mxchat-chatbot',
3278 4712 'mxchat_chatbot_section'
3279 4713 );
3280 4714
3281 4715 add_settings_field(
3282 - 'rate_limit_roles',
3283 - __('Rate Limits by User Role', 'mxchat'),
3284 - array($this, 'mxchat_rate_limit_roles_callback'),
4716 + 'input_copy',
4717 + esc_html__('Input Copy', 'mxchat'),
4718 + array($this, 'mxchat_input_copy_callback'),
3285 4719 'mxchat-chatbot',
3286 4720 'mxchat_chatbot_section'
3287 4721 );
3288 4722
3289 4723 add_settings_field(
3290 - 'rate_limit_message',
3291 - esc_html__('Rate Limit Message', 'mxchat'),
3292 - array($this, 'mxchat_rate_limit_message_callback'),
3293 - 'mxchat-chatbot',
3294 - 'mxchat_chatbot_section'
3295 - );
3296 -
3297 - add_settings_field(
3298 4724 'pre_chat_message',
3299 4725 esc_html__('Chat Teaser Pop-up', 'mxchat'),
3300 4726 array($this, 'mxchat_pre_chat_message_callback'),
3301 4727 'mxchat-chatbot',
@@ -3335,9 +4761,9 @@
3335 4761 );
3336 4762
3337 4763 add_settings_field(
3338 4764 'popular_question_1',
3339 - esc_html__('Popular Question 1', 'mxchat'),
4765 + esc_html__('Quick Question 1', 'mxchat'),
3340 4766 array($this, 'mxchat_popular_question_1_callback'),
3341 4767 'mxchat-chatbot',
3342 4768 'mxchat_chatbot_section'
3343 4769 );
@@ -3343,9 +4769,9 @@
3343 4769 );
3344 4770
3345 4771 add_settings_field(
3346 4772 'popular_question_2',
3347 - esc_html__('Popular Question 2', 'mxchat'),
4773 + esc_html__('Quick Question 2', 'mxchat'),
3348 4774 array($this, 'mxchat_popular_question_2_callback'),
3349 4775 'mxchat-chatbot',
3350 4776 'mxchat_chatbot_section'
3351 4777 );
@@ -3351,9 +4777,9 @@
3351 4777 );
3352 4778
3353 4779 add_settings_field(
3354 4780 'popular_question_3',
3355 - esc_html__('Popular Question 3', 'mxchat'),
4781 + esc_html__('Quick Question 3', 'mxchat'),
3356 4782 array($this, 'mxchat_popular_question_3_callback'),
3357 4783 'mxchat-chatbot',
3358 4784 'mxchat_chatbot_section'
3359 4785 );
@@ -3359,9 +4785,9 @@
3359 4785 );
3360 4786
3361 4787 add_settings_field(
3362 4788 'additional_popular_questions',
3363 - esc_html__('Additional Popular Questions', 'mxchat'),
4789 + esc_html__('Additional Quick Questions', 'mxchat'),
3364 4790 array($this, 'mxchat_additional_popular_questions_callback'),
3365 4791 'mxchat-chatbot',
3366 4792 'mxchat_chatbot_section'
3367 4793 );
@@ -3366,8 +4792,16 @@
3366 4792 'mxchat_chatbot_section'
3367 4793 );
3368 4794
3369 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 +
3370 4804 // Loops Settings Section
3371 4805 add_settings_section(
3372 4806 'mxchat_loops_section',
3373 4807 esc_html__('Loops Settings', 'mxchat'),
@@ -3374,18 +4808,10 @@
3374 4808 null,
3375 4809 'mxchat-embed'
3376 4810 );
3377 4811
3378 - // Loops Settings Fields
4812 + // Loops Settings Fields (API Key moved to API Keys tab)
3379 4813 add_settings_field(
3380 - 'loops_api_key',
3381 - esc_html__('Loops API Key', 'mxchat'),
3382 - array($this, 'mxchat_loops_api_key_callback'),
3383 - 'mxchat-embed',
3384 - 'mxchat_loops_section'
3385 - );
3386 -
3387 - add_settings_field(
3388 4814 'loops_mailing_list',
3389 4815 esc_html__('Loops Mailing List', 'mxchat'),
3390 4816 array($this, 'mxchat_loops_mailing_list_callback'),
3391 4817 'mxchat-embed',
@@ -3415,17 +4841,10 @@
3415 4841 array($this, 'mxchat_brave_section_callback'),
3416 4842 'mxchat-embed'
3417 4843 );
3418 4844
4845 + // Brave API Key moved to API Keys tab
3419 4846 add_settings_field(
3420 - 'brave_api_key',
3421 - __('Brave API Key', 'mxchat'),
3422 - array($this, 'mxchat_brave_api_key_callback'),
3423 - 'mxchat-embed',
3424 - 'mxchat_brave_section'
3425 - );
3426 -
3427 - add_settings_field(
3428 4847 'brave_image_count',
3429 4848 __('Number of Images to Return', 'mxchat'),
3430 4849 array($this, 'mxchat_brave_image_count_callback'),
3431 4850 'mxchat-embed',
@@ -3479,9 +4898,27 @@
3479 4898 'mxchat-embed',
3480 4899 'mxchat_pdf_intent_section'
3481 4900 );
3482 4901
4902 + // PDF Upload Button Toggle
3483 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(
3484 4921 'pdf_intent_trigger_text',
3485 4922 __('Intent Trigger Text', 'mxchat'),
3486 4923 array($this, 'mxchat_pdf_intent_trigger_text_callback'),
3487 4924 'mxchat-embed',
@@ -3546,8 +4983,16 @@
3546 4983 'mxchat_live_agent_section'
3547 4984 );
3548 4985
3549 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(
3550 4995 'live_agent_webhook_url',
3551 4996 __('Slack Webhook URL', 'mxchat'),
3552 4997 array($this, 'mxchat_live_agent_webhook_url_callback'),
3553 4998 'mxchat-embed',
@@ -3570,15 +5015,68 @@
3570 5015 'mxchat-embed',
3571 5016 'mxchat_live_agent_section'
3572 5017 );
3573 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 + );
3574 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 + );
3575 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 + );
3576 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 +
3577 5075 // General Settings Section
3578 5076 add_settings_section(
3579 5077 'mxchat_general_section',
3580 - esc_html__('Frequently Asked Questions (FAQ)', 'mxchat'),
5078 + esc_html__('YouTube Tutorials', 'mxchat'),
3581 5079 null,
3582 5080 'mxchat-general'
3583 5081 );
3584 5082 }
@@ -3583,9 +5081,8 @@
3583 5081 );
3584 5082 }
3585 5083
3586 5084 public function mxchat_prompts_page_init() {
3587 - // Register all settings as a single array
3588 5085 register_setting(
3589 5086 'mxchat_prompts_options',
3590 5087 'mxchat_prompts_options',
3591 5088 array(
@@ -3597,18 +5094,74 @@
3597 5094 'mxchat_use_pinecone' => 0,
3598 5095 'mxchat_pinecone_api_key' => '',
3599 5096 'mxchat_pinecone_environment' => '',
3600 5097 'mxchat_pinecone_index' => '',
3601 - 'mxchat_pinecone_host' => '', // Add this line
5098 + 'mxchat_pinecone_host' => '',
3602 5099 ),
3603 5100 'sanitize_callback' => array($this, 'sanitize_prompts_options'),
3604 5101 )
3605 5102 );
3606 5103
3607 - // Add settings saved message
3608 5104 add_action('admin_notices', array($this, 'sync_settings_notice'));
3609 5105 }
3610 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 +
3611 5164 /**
3612 5165 * Sanitize all prompts options
3613 5166 *
3614 5167 * @param array $input The unsanitized options array
@@ -3667,9 +5220,8 @@
3667 5220
3668 5221 return $sanitized;
3669 5222 }
3670 5223
3671 -
3672 5224 public function sync_settings_notice() {
3673 5225 // Only show notice on our plugin page
3674 5226 if (!isset($_GET['page']) || $_GET['page'] !== 'mxchat-prompts') {
3675 5227 return;
@@ -3680,8 +5232,9 @@
3680 5232
3681 5233 ?>
3682 5234 <div class="notice notice-success is-dismissible">
3683 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>
3684 5237 </div>
3685 5238 <?php
3686 5239
3687 5240 }
@@ -3690,139 +5243,129 @@
3690 5243 public function sanitize_sync_setting($input) {
3691 5244 return (bool)$input ? __('1', 'mxchat') : __('', 'mxchat');
3692 5245 }
3693 5246
5247 +public function mxchat_rate_limits_callback() {
5248 + $all_options = get_option('mxchat_options', []);
3694 5249
5250 + // Define available rate limits
5251 + $rate_limits = array('1', '3', '5', '10', '15', '20', '50', '100', 'unlimited');
3695 5252
3696 -public function mxchat_handle_activate_license() {
3697 - // Check nonce
3698 - if (!check_ajax_referer('mxchat_activate_license_nonce', 'security', false)) {
3699 - wp_send_json_error(esc_html__('Invalid security token', 'mxchat'));
3700 - return;
3701 - }
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 + );
3702 5260
3703 - // Verify user capabilities
3704 - if (!current_user_can('manage_options')) {
3705 - wp_send_json_error(esc_html__('Unauthorized access', 'mxchat'));
3706 - return;
3707 - }
5261 + // Get all roles plus a "logged_out" pseudo-role
5262 + $roles = wp_roles()->get_names();
5263 + $roles['logged_out'] = __('Logged Out Users', 'mxchat');
3708 5264
3709 - $license_key = isset($_POST['mxchat_activation_key']) ? sanitize_text_field($_POST['mxchat_activation_key']) : '';
3710 - $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">';
3711 5268
3712 - if (empty($license_key) || empty($customer_email)) {
3713 - wp_send_json_error(esc_html__('Email or License Key is missing', 'mxchat'));
3714 - return;
3715 - }
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>';
3716 5272
3717 - $product_id = 'MxChatPRO';
3718 - $response = wp_remote_get(
3719 - add_query_arg(
3720 - array(
3721 - 'wc-api' => 'software-api',
3722 - 'request' => 'activation',
3723 - 'email' => $customer_email,
3724 - 'license_key' => $license_key,
3725 - 'product_id' => $product_id
3726 - ),
3727 - 'http://mxchat.ai/'
3728 - )
3729 - );
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>';
3730 5283
3731 - if (is_wp_error($response)) {
3732 - wp_send_json_error(esc_html__('Activation failed due to a server error: ', 'mxchat') . $response->get_error_message());
3733 - return;
3734 - }
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');
3735 5290
3736 - $body = wp_remote_retrieve_body($response);
3737 - $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;
3738 5294
3739 - if ($data && isset($data->activated) && $data->activated) {
3740 - update_option('mxchat_license_status', 'active');
3741 - update_option('mxchat_pro_email', $customer_email);
3742 - update_option('mxchat_activation_key', $license_key);
3743 - wp_send_json_success(array('message' => esc_html__('License activated successfully', 'mxchat')));
3744 - } else {
3745 - $error_message = isset($data->error) ? $data->error : esc_html__('Activation failed', 'mxchat');
3746 - update_option('mxchat_license_status', 'inactive');
3747 - update_option('mxchat_license_error', $error_message);
3748 - wp_send_json_error($error_message);
3749 - }
3750 -}
5295 + $selected_timeframe = isset($all_options['rate_limits'][$role_id]['timeframe'])
5296 + ? $all_options['rate_limits'][$role_id]['timeframe']
5297 + : $default_timeframe;
3751 5298
5299 + $custom_message = isset($all_options['rate_limits'][$role_id]['message'])
5300 + ? $all_options['rate_limits'][$role_id]['message']
5301 + : $default_message;
3752 5302
3753 -public function mxchat_rate_limit_logged_out_callback() {
3754 - // Load the entire 'mxchat_options' array
3755 - $all_options = get_option('mxchat_options', []);
5303 + // Output the row
5304 + echo '<div class="mxchat-rate-limit-row mxchat-autosave-section">';
3756 5305
3757 - // Retrieve the saved rate limit or use the default value
3758 - $default_rate_limit = '10';
3759 - $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>';
3760 5308
3761 - // Define available rate limits
3762 - $rate_limits = array('1', '3', '5', '10', '15', '20', '100', 'unlimited');
5309 + // Controls section
5310 + echo '<div class="mxchat-rate-limit-controls-wrapper">';
3763 5311
3764 - // Output the dropdown
3765 - echo '<div class="pro-feature-wrapper active">';
3766 - echo '<select id="rate_limit_logged_out" name="rate_limit_logged_out">';
3767 - foreach ($rate_limits as $limit) {
3768 - echo '<option value="' . esc_attr($limit) . '" ' . selected($selected_rate_limit, $limit, false) . '>' . esc_html($limit) . '</option>';
3769 - }
3770 - echo '</select>';
3771 - 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>';
3772 - echo '</div>';
3773 -}
5312 + // Rate limit and timeframe controls
5313 + echo '<div class="mxchat-rate-limit-controls">';
3774 5314
3775 -// Add this new callback function
3776 -public function mxchat_rate_limit_roles_callback() {
3777 - $all_options = get_option('mxchat_options', []);
3778 - $roles = wp_roles()->get_names();
3779 - $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>';
3780 5327
3781 - echo '<div class="pro-feature-wrapper active mxchat-autosave-section">';
3782 -
3783 - foreach ($roles as $role_id => $role_name) {
3784 - $default_rate_limit = '100';
3785 - $selected_rate_limit = isset($all_options['role_rate_limits'][$role_id])
3786 - ? $all_options['role_rate_limits'][$role_id]
3787 - : $default_rate_limit;
3788 -
3789 - echo '<div style="margin-bottom: 10px;">';
3790 - 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>';
3791 5331 echo '<select
3792 - id="role_rate_limits_' . esc_attr($role_id) . '"
3793 - 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]"
3794 5334 class="mxchat-autosave-field">';
3795 - foreach ($rate_limits as $limit) {
3796 - 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>';
3797 5337 }
3798 5338 echo '</select>';
3799 5339 echo '</div>';
3800 - }
3801 5340
3802 - 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>';
3803 - echo '</div>';
3804 -}
5341 + echo '</div>'; // End controls
3805 5342
3806 -public function mxchat_rate_limit_message_callback() {
3807 - // Load the entire 'mxchat_options' array
3808 - $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
3809 5357
3810 - // Retrieve the saved message or use the default value
3811 - $default_message = esc_html__('Rate limit exceeded. Please try again later.', 'mxchat');
3812 - $rate_limit_message = isset($all_options['rate_limit_message']) ? $all_options['rate_limit_message'] : $default_message;
5358 + echo '</div>'; // End controls wrapper
3813 5359
3814 - // Output the textarea
3815 - echo '<div class="pro-feature-wrapper active">';
3816 - printf(
3817 - '<textarea id="rate_limit_message" name="rate_limit_message" rows="3" cols="50">%s</textarea>',
3818 - esc_textarea($rate_limit_message)
3819 - );
3820 - echo '<p class="description">' . esc_html__('This message will be displayed when a user exceeds the rate limit.', 'mxchat') . '</p>';
3821 - echo '</div>';
5360 + echo '</div>'; // End row
5361 + }
5362 +
5363 + echo '</div>'; // End container
5364 +
5365 + echo '</div>'; // End pro-feature-wrapper
3822 5366 }
3823 5367
3824 -
3825 5368 private function mxchat_add_option_field($id, $title, $callback = '') {
3826 5369 add_settings_field(
3827 5370 $id,
3828 5371 __($title, 'mxchat'),
@@ -3832,98 +5375,120 @@
3832 5375 $id === 'model' ? ['label_for' => 'model'] : []
3833 5376 );
3834 5377 }
3835 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
3836 5385 public function api_key_callback() {
3837 - // Retrieve from your stored 'api_key' in the mxchat_options array
3838 5386 $apiKey = isset($this->options['api_key']) ? esc_attr($this->options['api_key']) : '';
5387 + $nonce = wp_create_nonce('mxchat_autosave_nonce');
3839 5388
3840 - // Notice we changed the name to "api_key" (no array notation).
3841 - 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 . '" />';
3842 5391 echo '<button type="button" id="toggleApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
3843 - echo '<p class="description">' . esc_html__('The OpenAI API key is required even when using other models as it is used for vector embedding functionality. You must add credits to your OpenAI billing account before use.', 'mxchat') . '</p>';
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>';
3844 5394 }
5395 +
5396 +// X.AI API Key
3845 5397 public function xai_api_key_callback() {
3846 - // Check if the feature is activated (paid feature)
3847 - $disabled = $this->is_activated ? '' : 'disabled'; // Disable input if not activated
3848 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive'; // CSS class to style the wrapper based on activation status
3849 -
3850 - // Retrieve the X.AI API key value
3851 5398 $xaiApiKey = isset($this->options['xai_api_key']) ? esc_attr($this->options['xai_api_key']) : '';
5399 + $nonce = wp_create_nonce('mxchat_autosave_nonce');
3852 5400
3853 - // Render the input field for the X.AI API key
3854 - echo '<div class="' . esc_attr($class) . '">';
3855 - printf(
3856 - '<input type="password" id="xai_api_key" name="xai_api_key" value="%s" class="regular-text" %s />',
3857 - $xaiApiKey,
3858 - $disabled
3859 - );
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 . '" />';
3860 5403 echo '<button type="button" id="toggleXaiApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
3861 -
3862 - // If the feature is not activated, show the overlay with a "Pro Only" message
3863 - if (!$this->is_activated) {
3864 - echo '<div class="pro-feature-overlay">';
3865 - 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>';
3866 - echo '</div>';
3867 - }
3868 -
5404 + echo '<p class="description">' . esc_html__('Required for X.AI Grok models. Get your API key from X.AI Console.', 'mxchat') . '</p>';
3869 5405 echo '</div>';
3870 5406 }
5407 +// Claude API Key
3871 5408 public function claude_api_key_callback() {
3872 - // Check if the feature is activated (paid feature)
3873 - $disabled = $this->is_activated ? '' : 'disabled';
3874 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
3875 -
3876 - // Retrieve the Claude API key value directly like the others
3877 5409 $claudeApiKey = isset($this->options['claude_api_key']) ? esc_attr($this->options['claude_api_key']) : '';
3878 -
3879 - // Use direct name field like the other working API keys
3880 - echo '<div class="' . esc_attr($class) . '">';
3881 - printf(
3882 - '<input type="password" id="claude_api_key" name="claude_api_key" value="%s" class="regular-text" %s />',
3883 - $claudeApiKey,
3884 - $disabled
3885 - );
5410 + $nonce = wp_create_nonce('mxchat_autosave_nonce');
5411 +
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 . '" />';
3886 5414 echo '<button type="button" id="toggleClaudeApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
3887 -
3888 - if (!$this->is_activated) {
3889 - echo '<div class="pro-feature-overlay">';
3890 - 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>';
3891 - echo '</div>';
3892 - }
5415 + echo '<p class="description">' . esc_html__('Required for Anthropic Claude models. Get your API key from Anthropic Console.', 'mxchat') . '</p>';
3893 5416 echo '</div>';
3894 5417 }
5418 +
5419 +// DeepSeek API Key
3895 5420 public function deepseek_api_key_callback() {
3896 - // Retrieve from your stored 'deepseek_api_key' in the mxchat_options array
3897 5421 $apiKey = isset($this->options['deepseek_api_key']) ? esc_attr($this->options['deepseek_api_key']) : '';
5422 + $nonce = wp_create_nonce('mxchat_autosave_nonce');
3898 5423
3899 - // Notice we changed the name to "deepseek_api_key" (no array notation).
3900 - 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 . '" />';
3901 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>';
3902 5429 }
3903 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');
3904 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 +}
3905 5442
3906 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');
3907 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 +
3908 5468 public function mxchat_loops_api_key_callback() {
3909 - // Support both old and new format
3910 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');
3911 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 +
3912 5476 echo '<div class="api-key-wrapper">';
3913 5477 echo sprintf(
3914 - '<input type="password" id="loops_api_key" name="loops_api_key" value="%s" class="regular-text" />',
3915 - $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
3916 5481 );
3917 5482 echo '<button type="button" id="toggleLoopsApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
3918 5483 echo '</div>';
3919 - 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>';
3920 5485 }
3921 -
3922 5486 public function mxchat_loops_mailing_list_callback() {
3923 5487 // Add error handling and type checking
3924 5488 $loops_api_key = '';
3925 5489 $selected_list = '';
5490 + $nonce = wp_create_nonce('mxchat_autosave_nonce');
3926 5491
3927 5492 // Safely get the API key
3928 5493 if (isset($this->options['loops_api_key']) && is_string($this->options['loops_api_key'])) {
3929 5494 $loops_api_key = $this->options['loops_api_key'];
@@ -3936,9 +5501,14 @@
3936 5501
3937 5502 if (!empty($loops_api_key)) {
3938 5503 $lists = $this->mxchat_fetch_loops_mailing_lists($loops_api_key);
3939 5504 if (is_array($lists) && !empty($lists)) {
3940 - 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 +
3941 5511 foreach ($lists as $list) {
3942 5512 if (is_array($list) && isset($list['id']) && isset($list['name'])) {
3943 5513 echo sprintf(
3944 5514 '<option value="%s" %s>%s</option>',
@@ -3948,8 +5518,10 @@
3948 5518 );
3949 5519 }
3950 5520 }
3951 5521 echo '</select>';
5522 + echo '</div>';
5523 + echo '<p class="description">' . esc_html__('Please select a mailing list to use with Loops.', 'mxchat') . '</p>';
3952 5524 } else {
3953 5525 echo '<p class="description">' . esc_html__('No lists found. Please verify your API Key.', 'mxchat') . '</p>';
3954 5526 }
3955 5527 } else {
@@ -3960,14 +5532,18 @@
3960 5532 $default_response = __('Would you like to join our mailing list? Please provide your email below.', 'mxchat');
3961 5533 $triggered_response = isset($this->options['triggered_phrase_response'])
3962 5534 ? $this->options['triggered_phrase_response']
3963 5535 : $default_response;
5536 + $nonce = wp_create_nonce('mxchat_autosave_nonce');
3964 5537
5538 + echo '<div class="mxchat-field-wrapper">';
3965 5539 echo sprintf(
3966 - '<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,
3967 5542 esc_textarea($triggered_response)
3968 5543 );
3969 - 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>';
3970 5546 }
3971 5547
3972 5548 public function mxchat_email_capture_response_callback() {
3973 5549 $default_response = __('Thank you for providing your email! You\'ve been added to our list.', 'mxchat');
@@ -3973,16 +5549,19 @@
3973 5549 $default_response = __('Thank you for providing your email! You\'ve been added to our list.', 'mxchat');
3974 5550 $email_capture_response = isset($this->options['email_capture_response'])
3975 5551 ? $this->options['email_capture_response']
3976 5552 : $default_response;
5553 + $nonce = wp_create_nonce('mxchat_autosave_nonce');
3977 5554
5555 + echo '<div class="mxchat-field-wrapper">';
3978 5556 echo sprintf(
3979 - '<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,
3980 5559 esc_textarea($email_capture_response)
3981 5560 );
3982 - 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>';
3983 5563 }
3984 -
3985 5564 public function mxchat_pre_chat_message_callback() {
3986 5565 // Load the entire 'mxchat_options' array
3987 5566 $all_options = get_option('mxchat_options', []);
3988 5567
@@ -3994,59 +5573,154 @@
3994 5573 printf(
3995 5574 '<textarea id="pre_chat_message" name="pre_chat_message" rows="5" cols="50">%s</textarea>',
3996 5575 esc_textarea($pre_chat_message)
3997 5576 );
3998 - 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>';
3999 5577 }
4000 5578
4001 -
4002 -
4003 5579 // Callback for AI Instructions textarea
4004 5580 public function system_prompt_instructions_callback() {
4005 5581 // Retrieve the current value of the system prompt instructions
4006 5582 $instructions = isset($this->options['system_prompt_instructions']) ? esc_textarea($this->options['system_prompt_instructions']) : '';
4007 -
4008 5583 // Render the textarea field
4009 5584 printf(
4010 5585 '<textarea id="system_prompt_instructions" name="system_prompt_instructions" rows="5" cols="50">%s</textarea>',
4011 5586 $instructions
4012 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>';
4013 5603
4014 - // Provide a helpful description
4015 - 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'));
4016 5606 }
4017 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;
4018 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 +
4019 5669 public function mxchat_model_callback() {
4020 5670 // Define available models grouped by provider
4021 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 + ),
4022 5686 esc_html__('X.AI Models', 'mxchat') => array(
4023 - 'grok-beta' => esc_html__('grok-beta (Early Beta)', 'mxchat'),
4024 - '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'),
4025 5695 ),
4026 5696 esc_html__('DeepSeek Models', 'mxchat') => array(
4027 5697 'deepseek-chat' => esc_html__('DeepSeek-V3', 'mxchat'),
4028 5698 ),
4029 5699 esc_html__('Claude Models', 'mxchat') => array(
4030 - 'claude-3-5-sonnet-20241022' => esc_html__('Claude 3.5 Sonnet (Most Intelligent)', 'mxchat'),
4031 - 'claude-3-opus-20240229' => esc_html__('Claude 3 Opus (Highly Complex Tasks)', 'mxchat'),
4032 - 'claude-3-sonnet-20240229' => esc_html__('Claude 3 Sonnet (Balanced)', 'mxchat'),
4033 - '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'),
4034 5707 ),
4035 5708 esc_html__('OpenAI Models', 'mxchat') => array(
4036 - 'gpt-4o' => esc_html__('GPT-4o (Recommended)', 'mxchat'),
4037 - 'gpt-4o-mini' => esc_html__('GPT-4o Mini (Fast and Lightweight)', 'mxchat'),
4038 - 'gpt-4-turbo' => esc_html__('GPT-4 Turbo (High-Performance)', 'mxchat'),
4039 - 'gpt-4' => esc_html__('GPT-4 (High Intelligence)', 'mxchat'),
4040 - 'gpt-3.5-turbo' => esc_html__('GPT-3.5 Turbo (Affordable and Fast)', 'mxchat')
4041 - )
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 + ),
4042 5716 );
4043 5717
4044 5718 // Retrieve the currently selected model from saved options
4045 - $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';
4046 5720
4047 5721 // Begin the select dropdown
4048 - echo '<select id="model" name="model">'; // No array notation in name attribute
5722 + echo '<select id="model" name="model">';
4049 5723
4050 5724 // Iterate over groups of models
4051 5725 foreach ($models as $group_label => $group_models) {
4052 5726 echo '<optgroup label="' . esc_attr($group_label) . '">';
@@ -4051,28 +5725,263 @@
4051 5725 foreach ($models as $group_label => $group_models) {
4052 5726 echo '<optgroup label="' . esc_attr($group_label) . '">';
4053 5727
4054 5728 foreach ($group_models as $model_value => $model_label) {
4055 - // Disable Pro-only models for non-activated users
4056 - $disabled = (!$this->is_activated && ($group_label === esc_html__('X.AI Models', 'mxchat') || $group_label === esc_html__('Claude Models', 'mxchat'))) ? 'disabled' : '';
4057 - $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') : '';
4058 -
4059 - // Output the option element
4060 - 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>';
4061 5730 }
4062 5731
4063 5732 echo '</optgroup>';
4064 5733 }
4065 5734
4066 - // Close the select dropdown
4067 5735 echo '</select>';
4068 5736
4069 - // Add a description below the dropdown
4070 - echo '<p class="description">' . esc_html__('Select the AI model to use for your chatbot. 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 . '" />';
4071 5811 }
4072 5812
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' : '';
4073 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>';
4074 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>';
5835 +}
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 +
5924 +// Callback function for embedding model selection
5925 +public function embedding_model_callback() {
5926 + $models = array(
5927 + esc_html__('OpenAI Embeddings', 'mxchat') => array(
5928 + 'text-embedding-3-small' => esc_html__('TE3 Small (1536, Efficient)', 'mxchat'),
5929 + 'text-embedding-ada-002' => esc_html__('Ada 2 (1536, Recommended)', 'mxchat'),
5930 + 'text-embedding-3-large' => esc_html__('TE3 Large (3072, Powerful)', 'mxchat'),
5931 + ),
5932 + esc_html__('Voyage AI Embeddings', 'mxchat') => array(
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'),
5937 + )
5938 + );
5939 + $selected_model = isset($this->options['embedding_model']) ? esc_attr($this->options['embedding_model']) : 'text-embedding-ada-002';
5940 + echo '<select id="embedding_model" name="embedding_model">';
5941 + foreach ($models as $group_label => $group_models) {
5942 + echo '<optgroup label="' . esc_attr($group_label) . '">';
5943 + foreach ($group_models as $model_value => $model_label) {
5944 + echo '<option value="' . esc_attr($model_value) . '" ' . selected($selected_model, $model_value, false) . '>' . esc_html($model_label) . '</option>';
5945 + }
5946 + echo '</optgroup>';
5947 + }
5948 + echo '</select>';
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>';
5981 +}
5982 +
5983 +
4075 5984 public function mxchat_top_bar_title_callback() {
4076 5985 // Retrieve the current value of the top bar title from saved options
4077 5986 $top_bar_title = isset($this->options['top_bar_title']) ? esc_attr($this->options['top_bar_title']) : '';
4078 5987
@@ -4077,12 +5986,16 @@
4077 5986 $top_bar_title = isset($this->options['top_bar_title']) ? esc_attr($this->options['top_bar_title']) : '';
4078 5987
4079 5988 // Render the input field
4080 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 +}
4081 5997
4082 - // Add a description
4083 - echo '<p class="description">' . esc_html__('Enter the title text that will appear on the top bar of the chatbot.', 'mxchat') . '</p>';
4084 -}
4085 5998
4086 5999 public function enable_email_block_callback() {
4087 6000 // Load full plugin options array
4088 6001 $all_options = get_option('mxchat_options', []);
@@ -4099,13 +6012,10 @@
4099 6012 esc_attr($checked)
4100 6013 );
4101 6014 echo '<span class="slider"></span>';
4102 6015 echo '</label>';
4103 - 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>';
4104 6016 }
4105 6017
4106 -
4107 -
4108 6018 public function email_blocker_header_content_callback() {
4109 6019 // Load the entire 'mxchat_options' array
4110 6020 $all_options = get_option('mxchat_options', []);
4111 6021
@@ -4113,22 +6023,18 @@
4113 6023 $content = isset($all_options['email_blocker_header_content'])
4114 6024 ? $all_options['email_blocker_header_content']
4115 6025 : '';
4116 6026
4117 - // Render the textarea
6027 + // Render the textarea - IMPORTANT: name should be just "email_blocker_header_content"
4118 6028 echo '<textarea
4119 6029 id="email_blocker_header_content"
4120 6030 name="email_blocker_header_content"
4121 6031 rows="5"
4122 6032 cols="70"
6033 + data-setting="email_blocker_header_content"
4123 6034 >' . esc_textarea($content) . '</textarea>';
4124 -
4125 - echo '<p class="description">';
4126 - 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');
4127 - echo '</p>';
4128 6035 }
4129 6036
4130 -
4131 6037 public function email_blocker_button_text_callback() {
4132 6038 // Load the entire 'mxchat_options' array
4133 6039 $all_options = get_option('mxchat_options', []);
4134 6040
@@ -4138,34 +6044,54 @@
4138 6044 : '';
4139 6045
4140 6046 // Use esc_attr to safely render the existing text
4141 6047 echo '<input type="text" id="email_blocker_button_text" name="email_blocker_button_text" value="' . esc_attr($button_text) . '" style="width: 300px;" />';
6048 +}
4142 6049
4143 - echo '<p class="description">';
4144 - echo esc_html__('Enter the text you want on the submit button, e.g. "Start Chat".', 'mxchat');
4145 - 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>';
4146 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');
4147 6072
6073 + echo '<input type="text" id="name_field_placeholder" name="name_field_placeholder" value="' . esc_attr($placeholder) . '" style="width: 300px;" />';
6074 +}
4148 6075
4149 6076
6077 +
4150 6078 public function mxchat_intro_message_callback() {
4151 6079 // Load the entire 'mxchat_options' array
4152 6080 $all_options = get_option('mxchat_options', []);
4153 -
4154 6081 // Retrieve the saved intro message or use the default
4155 6082 $default_message = __('Hello! How can I assist you today?', 'mxchat');
4156 6083 $saved_message = isset($all_options['intro_message']) ? $all_options['intro_message'] : $default_message;
4157 -
4158 - // Output the textarea with the saved value
6084 + // Output the textarea with the saved value without escaping HTML
4159 6085 ?>
4160 - <textarea id="intro_message" name="intro_message" rows="5" cols="50"><?php echo esc_textarea($saved_message); ?></textarea>
4161 - <p class="description">
4162 - <?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>
4163 6090 </p>
4164 6091 <?php
4165 6092 }
4166 6093
4167 -
4168 6094 public function mxchat_input_copy_callback() {
4169 6095 // Load the entire 'mxchat_options' array
4170 6096 $all_options = get_option('mxchat_options', []);
4171 6097
@@ -4178,11 +6104,8 @@
4178 6104 '<input type="text" id="input_copy" name="input_copy" value="%s" placeholder="%s" />',
4179 6105 esc_attr($input_copy),
4180 6106 esc_attr__('How can I assist?', 'mxchat')
4181 6107 );
4182 -
4183 - // Output the description
4184 - echo '<p class="description">' . esc_html__('This is the placeholder text for the chat input field.', 'mxchat') . '</p>';
4185 6108 }
4186 6109
4187 6110
4188 6111
@@ -4614,12 +6537,25 @@
4614 6537 echo '</div>';
4615 6538 }
4616 6539
4617 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 +
4618 6544 // Get value from options array, default to 'off'
4619 - $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';
4620 6546 $checked = ($append_to_body === 'on') ? 'checked' : '';
4621 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
4622 6558 echo '<label class="toggle-switch">';
4623 6559 echo sprintf(
4624 6560 '<input type="checkbox" id="append_to_body" name="append_to_body" value="on" %s />',
4625 6561 esc_attr($checked)
@@ -4625,16 +6561,101 @@
4625 6561 esc_attr($checked)
4626 6562 );
4627 6563 echo '<span class="slider"></span>';
4628 6564 echo '</label>';
4629 - echo '<p class="description">' .
4630 - esc_html__('Show chatbot automatically on all pages. When disabled, you can place the chatbot manually using shortcode [mxchat_chatbot floating="yes"].', 'mxchat') .
4631 - '</p>';
4632 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
4633 6618 }
4634 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 +}
4635 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 +}
4636 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 +
4637 6658 public function mxchat_privacy_toggle_callback() {
4638 6659 // Load from mxchat_options array
4639 6660 $options = get_option('mxchat_options', []);
4640 6661
@@ -4654,9 +6675,8 @@
4654 6675 esc_attr($checked)
4655 6676 );
4656 6677 echo '<span class="slider"></span>';
4657 6678 echo '</label>';
4658 - echo '<p class="description">' . esc_html__('Enable this option to display a privacy notice below the chat widget.', 'mxchat') . '</p>';
4659 6679
4660 6680 // Output the custom text input field
4661 6681 echo sprintf(
4662 6682 '<textarea id="privacy_text" name="privacy_text" rows="5" cols="50" class="regular-text">%s</textarea>',
@@ -4661,9 +6681,8 @@
4661 6681 echo sprintf(
4662 6682 '<textarea id="privacy_text" name="privacy_text" rows="5" cols="50" class="regular-text">%s</textarea>',
4663 6683 esc_textarea($privacy_text)
4664 6684 );
4665 - echo '<p class="description">' . esc_html__('Enter the privacy policy text. You can include HTML links.', 'mxchat') . '</p>';
4666 6685 }
4667 6686
4668 6687
4669 6688 public function mxchat_complianz_toggle_callback() {
@@ -4681,10 +6700,8 @@
4681 6700 esc_attr($checked)
4682 6701 );
4683 6702 echo '<span class="slider"></span>';
4684 6703 echo '</label>';
4685 -
4686 - echo '<p class="description">' . esc_html__('Enable this option to apply Complianz consent logic to the chatbot (must have Complianz Plugin).', 'mxchat') . '</p>';
4687 6704 }
4688 6705
4689 6706 public function mxchat_link_target_toggle_callback() {
4690 6707 // Load from mxchat_options array
@@ -4701,9 +6718,8 @@
4701 6718 esc_attr($checked)
4702 6719 );
4703 6720 echo '<span class="slider"></span>';
4704 6721 echo '</label>';
4705 - echo '<p class="description">' . esc_html__('Enable to open links in a new tab (default is to open in the same tab).', 'mxchat') . '</p>';
4706 6722 }
4707 6723
4708 6724 public function mxchat_chat_persistence_toggle_callback() {
4709 6725 // Load from mxchat_options array
@@ -4720,10 +6736,8 @@
4720 6736 esc_attr($checked)
4721 6737 );
4722 6738 echo '<span class="slider"></span>';
4723 6739 echo '</label>';
4724 -
4725 - 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>';
4726 6740 }
4727 6741
4728 6742 public function mxchat_popular_question_1_callback() {
4729 6743 // Load the full plugin options array
@@ -4735,13 +6749,10 @@
4735 6749 // Render the input field
4736 6750 printf(
4737 6751 '<input type="text" id="popular_question_1" name="popular_question_1" value="%s" placeholder="%s" class="regular-text" />',
4738 6752 esc_attr($popular_question_1),
4739 - esc_attr__('Enter Popular Question 1', 'mxchat')
6753 + esc_attr__('Enter Quick Question 1', 'mxchat')
4740 6754 );
4741 -
4742 - // Add a description for the field
4743 - echo '<p class="description">' . esc_html__('This will be the first popular question in the chatbot.', 'mxchat') . '</p>';
4744 6755 }
4745 6756
4746 6757
4747 6758 public function mxchat_popular_question_2_callback() {
@@ -4754,13 +6765,10 @@
4754 6765 // Render the input field
4755 6766 printf(
4756 6767 '<input type="text" id="popular_question_2" name="popular_question_2" value="%s" placeholder="%s" class="regular-text" />',
4757 6768 esc_attr($popular_question_2),
4758 - esc_attr__('Enter Popular Question 2', 'mxchat')
6769 + esc_attr__('Enter Quick Question 2', 'mxchat')
4759 6770 );
4760 -
4761 - // Add a description for the field
4762 - echo '<p class="description">' . esc_html__('This will be the second popular question in the chatbot.', 'mxchat') . '</p>';
4763 6771 }
4764 6772
4765 6773
4766 6774 public function mxchat_popular_question_3_callback() {
@@ -4773,13 +6781,10 @@
4773 6781 // Render the input field
4774 6782 printf(
4775 6783 '<input type="text" id="popular_question_3" name="popular_question_3" value="%s" placeholder="%s" class="regular-text" />',
4776 6784 esc_attr($popular_question_3),
4777 - esc_attr(__('Enter Popular Question 3', 'mxchat'))
6785 + esc_attr(__('Enter Quick Question 3', 'mxchat'))
4778 6786 );
4779 -
4780 - // Add a description for the field
4781 - echo '<p class="description">' . esc_html__('This will be the third popular question in the chatbot.', 'mxchat') . '</p>';
4782 6787 }
4783 6788
4784 6789 public function mxchat_additional_popular_questions_callback() {
4785 6790 $options = get_option('mxchat_options', []);
@@ -4800,9 +6805,9 @@
4800 6805 <button type="button" class="button mxchat-remove-question"
4801 6806 aria-label="%s">%s</button>
4802 6807 </div>',
4803 6808 esc_attr($question),
4804 - esc_attr(sprintf(__('Enter Additional Popular Question %d', 'mxchat'), $index + 4)),
6809 + esc_attr(sprintf(__('Enter Additional Quick Question %d', 'mxchat'), $index + 4)),
4805 6810 $index,
4806 6811 esc_attr(__('Remove question', 'mxchat')),
4807 6812 esc_html__('Remove', 'mxchat')
4808 6813 );
@@ -4817,9 +6822,9 @@
4817 6822 data-question-index="0" />
4818 6823 <button type="button" class="button mxchat-remove-question"
4819 6824 aria-label="%s">%s</button>
4820 6825 </div>',
4821 - esc_attr(__('Enter Additional Popular Question 4', 'mxchat')),
6826 + esc_attr(__('Enter Additional Quick Question 4', 'mxchat')),
4822 6827 esc_attr(__('Remove question', 'mxchat')),
4823 6828 esc_html__('Remove', 'mxchat')
4824 6829 );
4825 6830 }
@@ -4828,23 +6833,23 @@
4828 6833 '<button type="button" class="button mxchat-add-question" aria-label="%s">%s</button>',
4829 6834 esc_attr(__('Add question', 'mxchat')),
4830 6835 esc_html__('Add Question', 'mxchat')
4831 6836 );
4832 - echo '<p class="description">' . esc_html__('Add as many popular questions as you need.', 'mxchat') . '</p>';
4833 - echo '</div>';
4834 6837 }
4835 6838
4836 6839 public function mxchat_brave_api_key_callback() {
4837 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');
4838 6842
4839 6843 echo '<div class="api-key-wrapper">';
4840 6844 echo sprintf(
4841 - '<input type="password" id="brave_api_key" name="brave_api_key" value="%s" class="regular-text" />',
4842 - $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
4843 6848 );
4844 6849 echo '<button type="button" id="toggleBraveApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
4845 6850 echo '</div>';
4846 - 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>';
4847 6852 }
4848 6853
4849 6854 public function mxchat_brave_image_count_callback() {
4850 6855 $brave_image_count = isset($this->options['brave_image_count'])
@@ -4849,14 +6854,18 @@
4849 6854 public function mxchat_brave_image_count_callback() {
4850 6855 $brave_image_count = isset($this->options['brave_image_count'])
4851 6856 ? intval($this->options['brave_image_count'])
4852 6857 : 4;
6858 + $nonce = wp_create_nonce('mxchat_autosave_nonce');
4853 6859
6860 + echo '<div class="mxchat-field-wrapper">';
4854 6861 echo sprintf(
4855 6862 '<input type="number" id="brave_image_count" name="brave_image_count"
4856 - value="%d" min="1" max="6" class="small-text" />',
4857 - $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
4858 6866 );
6867 + echo '</div>';
4859 6868 echo '<p class="description">' . __('Select the number of images to return (1-6).', 'mxchat') . '</p>';
4860 6869 }
4861 6870
4862 6871 public function mxchat_brave_safe_search_callback() {
@@ -4862,10 +6871,12 @@
4862 6871 public function mxchat_brave_safe_search_callback() {
4863 6872 $brave_safe_search = isset($this->options['brave_safe_search'])
4864 6873 ? esc_attr($this->options['brave_safe_search'])
4865 6874 : 'strict';
6875 + $nonce = wp_create_nonce('mxchat_autosave_nonce');
4866 6876
4867 - 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 . '">';
4868 6879 echo sprintf(
4869 6880 '<option value="strict" %s>%s</option>',
4870 6881 selected($brave_safe_search, 'strict', false),
4871 6882 __('Strict', 'mxchat')
@@ -4875,8 +6886,9 @@
4875 6886 selected($brave_safe_search, 'off', false),
4876 6887 __('Off', 'mxchat')
4877 6888 );
4878 6889 echo '</select>';
6890 + echo '</div>';
4879 6891 echo '<p class="description">' .
4880 6892 esc_html__('Set the Safe Search level for image searches. Brave Search only supports "Strict" and "Off" options.', 'mxchat') .
4881 6893 '</p>';
4882 6894 }
@@ -4884,14 +6896,18 @@
4884 6896 public function mxchat_brave_news_count_callback() {
4885 6897 $brave_news_count = isset($this->options['brave_news_count'])
4886 6898 ? intval($this->options['brave_news_count'])
4887 6899 : 3;
6900 + $nonce = wp_create_nonce('mxchat_autosave_nonce');
4888 6901
6902 + echo '<div class="mxchat-field-wrapper">';
4889 6903 echo sprintf(
4890 6904 '<input type="number" id="brave_news_count" name="brave_news_count"
4891 - value="%d" min="1" max="10" class="small-text" />',
4892 - $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
4893 6908 );
6909 + echo '</div>';
4894 6910 echo '<p class="description">' . esc_html__('Select the number of news articles to retrieve (1-10).', 'mxchat') . '</p>';
4895 6911 }
4896 6912
4897 6913 public function mxchat_brave_country_callback() {
@@ -4897,14 +6913,18 @@
4897 6913 public function mxchat_brave_country_callback() {
4898 6914 $brave_country = isset($this->options['brave_country'])
4899 6915 ? esc_attr($this->options['brave_country'])
4900 6916 : 'us';
6917 + $nonce = wp_create_nonce('mxchat_autosave_nonce');
4901 6918
6919 + echo '<div class="mxchat-field-wrapper">';
4902 6920 echo sprintf(
4903 6921 '<input type="text" id="brave_country" name="brave_country"
4904 - value="%s" maxlength="2" class="small-text" />',
4905 - $brave_country
6922 + value="%s" maxlength="2" class="small-text mxchat-autosave-field" data-nonce="%s" />',
6923 + $brave_country,
6924 + $nonce
4906 6925 );
6926 + echo '</div>';
4907 6927 echo '<p class="description">' . esc_html__('Enter the country code (e.g., "us" for United States).', 'mxchat') . '</p>';
4908 6928 }
4909 6929
4910 6930 public function mxchat_brave_language_callback() {
@@ -4910,14 +6930,18 @@
4910 6930 public function mxchat_brave_language_callback() {
4911 6931 $brave_language = isset($this->options['brave_language'])
4912 6932 ? esc_attr($this->options['brave_language'])
4913 6933 : 'en';
6934 + $nonce = wp_create_nonce('mxchat_autosave_nonce');
4914 6935
6936 + echo '<div class="mxchat-field-wrapper">';
4915 6937 echo sprintf(
4916 6938 '<input type="text" id="brave_language" name="brave_language"
4917 - value="%s" maxlength="2" class="small-text" />',
4918 - $brave_language
6939 + value="%s" maxlength="2" class="small-text mxchat-autosave-field" data-nonce="%s" />',
6940 + $brave_language,
6941 + $nonce
4919 6942 );
6943 + echo '</div>';
4920 6944 echo '<p class="description">' . esc_html__('Enter the language code (e.g., "en" for English).', 'mxchat') . '</p>';
4921 6945 }
4922 6946
4923 6947
@@ -4929,141 +6953,118 @@
4929 6953 echo '<p>' . esc_html__('Configure the intent settings for the Chat with PDF feature.', 'mxchat') . '</p>';
4930 6954 }
4931 6955
4932 6956 public function mxchat_chat_toolbar_toggle_callback() {
4933 - // Get chat toolbar toggle value with fallback
4934 - $chat_toolbar_toggle = isset($this->options['chat_toolbar_toggle']) ? $this->options['chat_toolbar_toggle'] : 'off';
4935 - $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' : '';
4936 6960
4937 - // Check if the plugin is activated (paid feature)
4938 - $disabled = $this->is_activated ? '' : 'disabled';
4939 - $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>';
4940 6969
4941 - 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 +}
4942 6972
4943 - // Output the toggle switch
4944 - echo '<label class="toggle-switch">';
4945 - echo sprintf(
4946 - '<input type="checkbox" id="chat_toolbar_toggle" name="chat_toolbar_toggle" value="on" %s %s />',
4947 - esc_attr($checked),
4948 - esc_attr($disabled)
4949 - );
4950 - echo '<span class="slider"></span>';
4951 - 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' : '';
4952 6980
4953 - // Pro feature overlay
4954 - if (!$this->is_activated) {
4955 - echo '<div class="pro-feature-overlay">';
4956 - echo '<a href="https://mxchat.ai/" target="_blank">';
4957 - echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
4958 - echo '</a>';
4959 - echo '</div>';
4960 - }
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>';
4961 6989
4962 - echo '</div>';
4963 - 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>';
4964 6991 }
4965 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' : '';
4966 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 +
4967 7013 public function mxchat_pdf_intent_trigger_text_callback() {
4968 - $disabled = $this->is_activated ? '' : 'disabled';
4969 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
4970 7014 $default_text = __("Please provide the URL to the PDF you'd like to discuss.", 'mxchat');
4971 7015
4972 - echo '<div class="' . esc_attr($class) . '">';
4973 7016 echo sprintf(
4974 7017 '<textarea id="pdf_intent_trigger_text"
4975 7018 name="pdf_intent_trigger_text"
4976 7019 rows="3"
4977 7020 cols="50"
4978 - placeholder="%s"
4979 - %s>%s</textarea>',
7021 + placeholder="%s">%s</textarea>',
4980 7022 esc_attr__('Enter trigger text', 'mxchat'),
4981 - esc_attr($disabled),
4982 7023 isset($this->options['pdf_intent_trigger_text'])
4983 7024 ? esc_textarea($this->options['pdf_intent_trigger_text'])
4984 7025 : esc_textarea($default_text)
4985 7026 );
4986 7027 echo '<p class="description">' . esc_html__('Text displayed when the intent is triggered.', 'mxchat') . '</p>';
4987 -
4988 - if (!$this->is_activated) {
4989 - echo '<div class="pro-feature-overlay">';
4990 - echo '<a href="https://mxchat.ai/" target="_blank">';
4991 - echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
4992 - echo '</a>';
4993 - echo '</div>';
4994 - }
4995 - echo '</div>';
4996 7028 }
4997 7029
4998 7030 public function mxchat_pdf_intent_success_text_callback() {
4999 - $disabled = $this->is_activated ? '' : 'disabled';
5000 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
5001 7031 $default_text = __("I've processed the PDF. What questions do you have about it?", 'mxchat');
5002 7032
5003 - echo '<div class="' . esc_attr($class) . '">';
5004 7033 echo sprintf(
5005 7034 '<textarea id="pdf_intent_success_text"
5006 7035 name="pdf_intent_success_text"
5007 7036 rows="3"
5008 7037 cols="50"
5009 - placeholder="%s"
5010 - %s>%s</textarea>',
7038 + placeholder="%s">%s</textarea>',
5011 7039 esc_attr__('Enter success text', 'mxchat'),
5012 - esc_attr($disabled),
5013 7040 isset($this->options['pdf_intent_success_text'])
5014 7041 ? esc_textarea($this->options['pdf_intent_success_text'])
5015 7042 : esc_textarea($default_text)
5016 7043 );
5017 7044 echo '<p class="description">' . esc_html__('Text displayed when the intent is successful.', 'mxchat') . '</p>';
5018 -
5019 - if (!$this->is_activated) {
5020 - echo '<div class="pro-feature-overlay">';
5021 - echo '<a href="https://mxchat.ai/" target="_blank">';
5022 - echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
5023 - echo '</a>';
5024 - echo '</div>';
5025 - }
5026 - echo '</div>';
5027 7045 }
5028 7046
5029 7047 public function mxchat_pdf_intent_error_text_callback() {
5030 - $disabled = $this->is_activated ? '' : 'disabled';
5031 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
5032 7048 $default_text = __("Sorry, I couldn't process the PDF. Please ensure it's a valid file.", 'mxchat');
5033 7049
5034 - echo '<div class="' . esc_attr($class) . '">';
5035 7050 echo sprintf(
5036 7051 '<textarea id="pdf_intent_error_text"
5037 7052 name="pdf_intent_error_text"
5038 7053 rows="3"
5039 7054 cols="50"
5040 - placeholder="%s"
5041 - %s>%s</textarea>',
7055 + placeholder="%s">%s</textarea>',
5042 7056 esc_attr__('Enter error text', 'mxchat'),
5043 - esc_attr($disabled),
5044 7057 isset($this->options['pdf_intent_error_text'])
5045 7058 ? esc_textarea($this->options['pdf_intent_error_text'])
5046 7059 : esc_textarea($default_text)
5047 7060 );
5048 7061 echo '<p class="description">' . esc_html__('Text displayed when an error occurs during the intent.', 'mxchat') . '</p>';
5049 -
5050 - if (!$this->is_activated) {
5051 - echo '<div class="pro-feature-overlay">';
5052 - echo '<a href="https://mxchat.ai/" target="_blank">';
5053 - echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
5054 - echo '</a>';
5055 - echo '</div>';
5056 - }
5057 - echo '</div>';
5058 7062 }
5059 7063
5060 7064 public function mxchat_pdf_max_pages_callback() {
5061 - $disabled = $this->is_activated ? '' : 'disabled';
5062 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
5063 7065 $max_pages = isset($this->options['pdf_max_pages']) ? intval($this->options['pdf_max_pages']) : 69;
5064 7066
5065 - echo '<div class="' . esc_attr($class) . '">';
5066 7067 echo sprintf(
5067 7068 '<input type="range"
5068 7069 id="pdf_max_pages"
5069 7070 name="pdf_max_pages"
@@ -5069,37 +7070,24 @@
5069 7070 name="pdf_max_pages"
5070 7071 min="1"
5071 7072 max="69"
5072 7073 value="%d"
5073 - %s
5074 7074 class="range-slider" />',
5075 - esc_attr($max_pages),
5076 - esc_attr($disabled)
7075 + esc_attr($max_pages)
5077 7076 );
5078 7077 echo '<span id="pdf_max_pages_output">' . esc_html($max_pages) . '</span>';
5079 7078 echo '<p class="description">' . esc_html__('Set the maximum number of document pages users can upload for processing. (1-69 pages)', 'mxchat') . '</p>';
5080 -
5081 - if (!$this->is_activated) {
5082 - echo '<div class="pro-feature-overlay">';
5083 - echo '<a href="https://mxchat.ai/" target="_blank">';
5084 - echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
5085 - echo '</a>';
5086 - echo '</div>';
5087 - }
5088 - echo '</div>';
5089 7079 }
5090 7080
5091 7081 public function mxchat_live_agent_status_callback() {
5092 - $disabled = $this->is_activated ? '' : 'disabled';
5093 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
5094 - $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';
5095 7085
5096 - echo '<div class="' . esc_attr($class) . '">';
5097 7086 echo '<label class="toggle-switch">';
5098 7087 echo sprintf(
5099 - '<input type="checkbox" id="live_agent_status" name="live_agent_status" value="on" %s %s />',
5100 - checked($status, 'on', false),
5101 - esc_attr($disabled)
7088 + '<input type="checkbox" id="live_agent_status" name="live_agent_status" value="on" %s />',
7089 + checked($status, 'on', false)
5102 7090 );
5103 7091 echo '<span class="slider"></span>';
5104 7092 echo '</label>';
5105 7093 echo '<label for="live_agent_status" class="mxchat-status-label">';
@@ -5104,98 +7092,167 @@
5104 7092 echo '</label>';
5105 7093 echo '<label for="live_agent_status" class="mxchat-status-label">';
5106 7094 echo '<span class="status-text">' . ($status === 'on' ? esc_html__('Online', 'mxchat') : esc_html__('Offline', 'mxchat')) . '</span>';
5107 7095 echo '</label>';
5108 -
5109 - if (!$this->is_activated) {
5110 - echo '<div class="pro-feature-overlay">';
5111 - 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>';
5112 - echo '</div>';
5113 - }
5114 - echo '</div>';
5115 7096 }
5116 7097
5117 -
5118 -// Away Message Callback
5119 7098 public function mxchat_live_agent_away_message_callback() {
5120 - $disabled = $this->is_activated ? '' : 'disabled';
5121 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
5122 7099 $message = isset($this->options['live_agent_away_message'])
5123 7100 ? $this->options['live_agent_away_message']
5124 7101 : __('Sorry, live agents are currently unavailable. I can continue helping you as an AI assistant.', 'mxchat');
5125 7102
5126 - echo '<div class="' . esc_attr($class) . '">';
5127 7103 printf(
5128 - '<textarea id="live_agent_away_message" name="live_agent_away_message" rows="3" cols="50" %s>%s</textarea>',
5129 - esc_attr($disabled),
7104 + '<textarea id="live_agent_away_message" name="live_agent_away_message" rows="3" cols="50">%s</textarea>',
5130 7105 esc_textarea($message)
5131 7106 );
5132 7107 echo '<p class="description">' . esc_html__('Message shown when live agents are offline.', 'mxchat') . '</p>';
5133 -
5134 - if (!$this->is_activated) {
5135 - echo '<div class="pro-feature-overlay">';
5136 - 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>';
5137 - echo '</div>';
5138 - }
5139 - echo '</div>';
5140 7108 }
5141 7109
5142 7110 public function mxchat_live_agent_notification_message_callback() {
5143 - $disabled = $this->is_activated ? '' : 'disabled';
5144 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
5145 7111 $message = isset($this->options['live_agent_notification_message'])
5146 7112 ? $this->options['live_agent_notification_message']
5147 7113 : __('Live agent has been notified.', 'mxchat');
5148 7114
5149 - echo '<div class="' . esc_attr($class) . '">';
5150 7115 printf(
5151 - '<textarea id="live_agent_notification_message" name="live_agent_notification_message" rows="3" cols="50" %s>%s</textarea>',
5152 - esc_attr($disabled),
7116 + '<textarea id="live_agent_notification_message" name="live_agent_notification_message" rows="3" cols="50">%s</textarea>',
5153 7117 esc_textarea($message)
5154 7118 );
5155 7119 echo '<p class="description">' . esc_html__('Message shown when live transfer activated.', 'mxchat') . '</p>';
5156 -
5157 - if (!$this->is_activated) {
5158 - echo '<div class="pro-feature-overlay">';
5159 - 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>';
5160 - echo '</div>';
5161 - }
5162 - echo '</div>';
5163 7120 }
5164 7121
5165 7122 public function mxchat_live_agent_webhook_url_callback() {
5166 - $disabled = $this->is_activated ? '' : 'disabled';
5167 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
5168 7123 $webhook_url = isset($this->options['live_agent_webhook_url'])
5169 7124 ? esc_url($this->options['live_agent_webhook_url'])
5170 7125 : esc_url(get_option('live_agent_webhook_url', ''));
5171 7126
5172 - echo '<div class="' . esc_attr($class) . '">';
5173 7127 printf(
5174 - '<input type="password" id="live_agent_webhook_url" name="live_agent_webhook_url" value="%s" class="regular-text" %s />',
5175 - $webhook_url,
5176 - 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
5177 7130 );
5178 7131 echo '<button type="button" id="toggleWebhookUrlVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5179 7132 echo '<p class="description">' . esc_html__('Enter your Slack webhook URL for live agent notifications.', 'mxchat') . '</p>';
7133 +}
5180 7134
5181 - if (!$this->is_activated) {
5182 - echo '<div class="pro-feature-overlay">';
5183 - 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>';
5184 - 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;
5185 7239 }
5186 - 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>';
5187 7247 }
5188 7248
5189 -
5190 7249 public function mxchat_similarity_threshold_callback() {
5191 7250 // Load from mxchat_options array
5192 7251 $options = get_option('mxchat_options', []);
5193 7252
5194 - // Get value with backwards compatibility
5195 - $threshold = isset($options['similarity_threshold'])
5196 - ? $options['similarity_threshold']
5197 - : 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;
5198 7255
5199 7256 echo '<div class="slider-container">';
5200 7257 echo sprintf(
5201 7258 '<input type="range"
@@ -5200,9 +7257,9 @@
5200 7257 echo sprintf(
5201 7258 '<input type="range"
5202 7259 id="similarity_threshold"
5203 7260 name="similarity_threshold"
5204 - min="70"
7261 + min="20"
5205 7262 max="85"
5206 7263 step="1"
5207 7264 value="%s"
5208 7265 class="range-slider" />',
@@ -5212,186 +7269,282 @@
5212 7269 '<span id="threshold_value" class="range-value">%s</span>',
5213 7270 esc_html($threshold)
5214 7271 );
5215 7272 echo '</div>';
5216 -
5217 - echo '<p class="description">';
5218 - 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');
5219 - echo '</p>';
5220 7273 }
5221 7274
7275 +public function mxchat_rag_sources_limit_callback() {
7276 + // Load from mxchat_options array
7277 + $options = get_option('mxchat_options', []);
5222 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;
5223 7281
5224 -public function mxchat_live_agent_secret_key_callback() {
5225 - $disabled = $this->is_activated ? '' : 'disabled';
5226 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
5227 -
5228 - echo '<div class="' . esc_attr($class) . '">';
5229 - printf(
5230 - '<input type="password" id="live_agent_secret_key" name="live_agent_secret_key" value="%s" class="regular-text" %s />',
5231 - isset($this->options['live_agent_secret_key']) ? esc_attr($this->options['live_agent_secret_key']) : '',
5232 - 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)
5233 7293 );
5234 - echo '<button type="button" id="toggleSecretKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5235 - echo '<p class="description">' . esc_html__('Secret key for validating Slack requests. Keep this secure.', 'mxchat') . '</p>';
5236 -
5237 - if (!$this->is_activated) {
5238 - echo '<div class="pro-feature-overlay">';
5239 - 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>';
5240 - echo '</div>';
5241 - }
7294 + echo sprintf(
7295 + '<span id="rag_sources_limit_value" class="range-value">%s</span>',
7296 + esc_html($rag_sources_limit)
7297 + );
5242 7298 echo '</div>';
5243 7299 }
5244 7300
5245 -public function mxchat_live_agent_bot_token_callback() {
5246 - $disabled = $this->is_activated ? '' : 'disabled';
5247 - $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;
5248 7304
5249 - echo '<div class="' . esc_attr($class) . '">';
5250 - printf(
5251 - '<input type="password" id="live_agent_bot_token" name="live_agent_bot_token" value="%s" class="regular-text" %s />',
5252 - isset($this->options['live_agent_bot_token']) ? esc_attr($this->options['live_agent_bot_token']) : '',
5253 - esc_attr($disabled)
5254 - );
5255 - echo '<button type="button" id="toggleBotTokenVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5256 - echo '<p class="description">' . esc_html__('Your Slack Bot OAuth Token (starts with xoxb-). Keep this secure.', 'mxchat') . '</p>';
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');
7308 + }
5257 7309
5258 - if (!$this->is_activated) {
5259 - echo '<div class="pro-feature-overlay">';
5260 - 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>';
5261 - echo '</div>';
7310 + $current_page = isset($_GET['page']) ? $_GET['page'] : '';
7311 + $plugin_url = plugin_dir_url(__FILE__) . '../';
7312 +
7313 + // Only load on MxChat pages
7314 + if (strpos($current_page, 'mxchat') === false) {
7315 + return;
5262 7316 }
5263 - echo '</div>';
7317 +
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);
5264 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);
5265 7327
5266 -public function mxchat_enqueue_admin_assets() {
5267 - wp_enqueue_style('wp-color-picker');
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);
5268 7330
5269 - // Get the plugin version or file modification time for cache busting
5270 - $plugin_version = '2.0.4'; // Replace this with your plugin's 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;
5271 7343
5272 - // File paths
5273 - $color_picker_js_path = plugin_dir_path(__FILE__) . '../js/my-color-picker.js';
5274 - $embedding_check_js_path = plugin_dir_path(__FILE__) . '../js/embedding-check.js';
5275 - $admin_css_path = plugin_dir_path(__FILE__) . '../css/admin-style.css';
5276 - $knowledge_css_path = plugin_dir_path(__FILE__) . '../css/knowledge-style.css';
5277 - $intent_css_path = plugin_dir_path(__FILE__) . '../css/intent-style.css';
5278 - $transcripts_js_path = plugin_dir_path(__FILE__) . '../js/mxchat_transcripts.js';
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;
5279 7351
5280 - // Check if files exist and get modification times
5281 - $color_picker_version = file_exists($color_picker_js_path) ? filemtime($color_picker_js_path) : $plugin_version;
5282 - $embedding_check_version = file_exists($embedding_check_js_path) ? filemtime($embedding_check_js_path) : $plugin_version;
5283 - $admin_css_version = file_exists($admin_css_path) ? filemtime($admin_css_path) : $plugin_version;
5284 - $knowledge_css_version = file_exists($knowledge_css_path) ? filemtime($knowledge_css_path) : $plugin_version;
5285 - $intent_css_version = file_exists($intent_css_path) ? filemtime($intent_css_path) : $plugin_version;
5286 - $transcripts_js_version = file_exists($transcripts_js_path) ? filemtime($transcripts_js_path) : $plugin_version;
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);
5287 7358
5288 - // Enqueue scripts and styles with corrected paths
5289 - wp_enqueue_script(
5290 - 'mxchat-color-picker',
5291 - plugin_dir_url(__FILE__) . '../js/my-color-picker.js',
5292 - array('wp-color-picker'),
5293 - $color_picker_version,
5294 - true
5295 - );
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;
5296 7380
5297 - wp_enqueue_script(
5298 - 'mxchat-embedding-check',
5299 - plugin_dir_url(__FILE__) . '../js/embedding-check.js',
5300 - array(),
5301 - $embedding_check_version,
5302 - true
5303 - );
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 + );
5304 7399
5305 - wp_enqueue_script(
5306 - 'mxchat-transcripts-js',
5307 - plugin_dir_url(__FILE__) . '../js/mxchat_transcripts.js',
5308 - array('jquery'),
5309 - $transcripts_js_version,
5310 - true
5311 - );
5312 -
5313 - wp_enqueue_script(
5314 - 'mxchat-admin-js',
5315 - plugin_dir_url(__FILE__) . '../js/mxchat-admin.js',
5316 - array('jquery'),
5317 - $plugin_version,
5318 - true
5319 - );
5320 -
5321 - 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(
5322 7411 'ajax_url' => admin_url('admin-ajax.php'),
7412 + 'nonce' => wp_create_nonce('mxchat_admin_nonce'),
7413 + 'admin_url' => admin_url(),
5323 7414 'license_nonce' => wp_create_nonce('mxchat_activate_license_nonce'),
5324 7415 'inline_edit_nonce' => wp_create_nonce('mxchat_save_inline_nonce'),
5325 7416 'setting_nonce' => wp_create_nonce('mxchat_save_setting_nonce'),
5326 - ));
7417 + 'export_nonce' => wp_create_nonce('mxchat_export_transcripts'),
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 + );
5327 7428
5328 - // Enqueue the admin CSS
5329 - wp_enqueue_style(
5330 - 'mxchat-admin-css',
5331 - plugin_dir_url(__FILE__) . '../css/admin-style.css',
5332 - array(),
5333 - $admin_css_version
5334 - );
7429 + // Localize main admin script with base data
7430 + wp_localize_script('mxchat-admin-js', 'mxchatAdmin', $base_data);
5335 7431
5336 - wp_enqueue_style(
5337 - 'mxchat-knowledge-css',
5338 - plugin_dir_url(__FILE__) . '../css/knowledge-style.css',
5339 - array(),
5340 - $knowledge_css_version
5341 - );
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 + ));
5342 7443
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 + ));
5343 7456
5344 - wp_enqueue_style(
5345 - 'mxchat-intent-css',
5346 - plugin_dir_url(__FILE__) . '../css/intent-style.css',
5347 - array(),
5348 - $intent_css_version
5349 - );
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;
5350 7471
5351 - // IMPORTANT: Use the same script handle as above for localizing mxchatPromptsAdmin
5352 - wp_localize_script( 'mxchat-admin-js', 'mxchatPromptsAdmin', array(
5353 - 'ajax_url' => admin_url( 'admin-ajax.php' ),
5354 - 'prompts_setting_nonce' => wp_create_nonce( 'mxchat_prompts_setting_nonce' ),
5355 - ) );
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;
5356 7478
5357 - // Localize the script for color picker and settings
5358 - wp_localize_script('mxchat-color-picker', 'mxchatStyleSettings', array(
7479 + case 'mxchat-transcripts':
7480 + // Get chart data for localization
7481 + $chart_data = $this->get_transcripts_chart_data();
7482 +
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 + ));
7490 +
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(
5359 7542 'ajax_url' => admin_url('admin-ajax.php'),
5360 - 'link_target_toggle' => $this->options['link_target_toggle'] ?? 'off',
5361 - 'user_message_bg_color' => $this->options['user_message_bg_color'] ?? '#fff',
5362 - 'user_message_font_color' => $this->options['user_message_font_color'] ?? '#212121',
5363 - 'bot_message_bg_color' => $this->options['bot_message_bg_color'] ?? '#212121',
5364 - 'bot_message_font_color' => $this->options['bot_message_font_color'] ?? '#fff',
5365 - 'top_bar_bg_color' => $this->options['top_bar_bg_color'] ?? '#212121',
5366 - 'send_button_font_color' => $this->options['send_button_font_color'] ?? '#212121',
5367 - 'close_button_color' => $this->options['close_button_color'] ?? '#fff',
5368 - 'chatbot_background_color' => $this->options['chatbot_background_color'] ?? '#212121',
5369 - 'icon_color' => $this->options['icon_color'] ?? '#fff',
5370 - 'chat_input_font_color' => $this->options['chat_input_font_color'] ?? '#212121',
5371 - 'pre_chat_message' => $this->options['pre_chat_message'] ?? esc_html__('Hey there! Ask me anything!', 'mxchat'),
5372 - 'rate_limit_message' => $this->options['rate_limit_message'] ?? esc_html__('Rate limit exceeded. Please try again later.', 'mxchat'),
5373 - 'loops_api_key' => $this->options['loops_api_key'] ?? '',
5374 - 'loops_mailing_list' => $this->options['loops_mailing_list'] ?? '',
5375 - 'triggered_phrase_response' => $this->options['triggered_phrase_response'] ?? esc_html__('Would you like to join our mailing list? Please provide your email below.', 'mxchat'),
5376 - 'email_capture_response' => $this->options['email_capture_response'] ?? esc_html__('Thank you for providing your email! You\'ve been added to our list.', 'mxchat'),
5377 - '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'),
5378 - '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'),
5379 - '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'),
5380 - 'pdf_max_pages' => $this->options['pdf_max_pages'] ?? 69,
5381 - 'live_agent_webhook_url' => $this->options['live_agent_webhook_url'] ?? '',
5382 - 'live_agent_secret_key' => $this->options['live_agent_secret_key'] ?? '',
5383 - 'live_agent_bot_token' => $this->options['live_agent_bot_token'] ?? '',
5384 - 'live_agent_message_bg_color' => $this->options['live_agent_message_bg_color'] ?? '#ffffff',
5385 - 'live_agent_message_font_color' => $this->options['live_agent_message_font_color'] ?? '#333333',
5386 - 'chat_toolbar_toggle' => $this->options['chat_toolbar_toggle'] ?? 'off',
5387 - 'mode_indicator_bg_color' => $this->options['mode_indicator_bg_color'] ?? '#767676',
5388 - 'mode_indicator_font_color' => $this->options['mode_indicator_font_color'] ?? '#ffffff',
5389 - 'toolbar_icon_color' => $this->options['toolbar_icon_color'] ?? '#212121',
7543 + 'prompts_setting_nonce' => wp_create_nonce('mxchat_prompts_setting_nonce'),
5390 7544 ));
5391 7545 }
5392 7546
5393 -
5394 7547 public function mxchat_sanitize($input) {
5395 7548 $new_input = array();
5396 7549
5397 7550 if (isset($input['api_key'])) {
@@ -5399,11 +7552,16 @@
5399 7552 }
5400 7553
5401 7554 if (isset($input['similarity_threshold'])) {
5402 7555 $new_input['similarity_threshold'] = absint($input['similarity_threshold']); // Ensure it's an integer
5403 - $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
5404 7557 }
5405 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 +
5406 7564 if (isset($input['xai_api_key'])) {
5407 7565 $new_input['xai_api_key'] = sanitize_text_field($input['xai_api_key']);
5408 7566 }
5409 7567
@@ -5410,12 +7568,30 @@
5410 7568 if (isset($input['claude_api_key'])) {
5411 7569 $new_input['claude_api_key'] = sanitize_text_field($input['claude_api_key']);
5412 7570 }
5413 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 +
5414 7586 if (isset($input['deepseek_api_key'])) {
5415 7587 $new_input['deepseek_api_key'] = sanitize_text_field($input['deepseek_api_key']);
5416 7588 }
5417 7589
7590 + if (isset($input['gemini_api_key'])) {
7591 + $new_input['gemini_api_key'] = sanitize_text_field($input['gemini_api_key']);
7592 + }
7593 +
5418 7594 if (isset($input['enable_woocommerce_integration'])) {
5419 7595 $new_input['enable_woocommerce_integration'] = $input['enable_woocommerce_integration'] === 'on' ? 'on' : 'off';
5420 7596 }
5421 7597
@@ -5448,12 +7624,44 @@
5448 7624 if (isset($input['append_to_body'])) {
5449 7625 $new_input['append_to_body'] = $input['append_to_body'] === 'on' ? 'on' : 'off';
5450 7626 }
5451 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 +
5452 7656 if (isset($input['top_bar_title'])) {
5453 7657 $new_input['top_bar_title'] = sanitize_text_field($input['top_bar_title']);
5454 7658 }
5455 7659
7660 + if (isset($input['ai_agent_text'])) {
7661 + $new_input['ai_agent_text'] = sanitize_text_field($input['ai_agent_text']);
7662 + }
7663 +
5456 7664 if (isset($input['enable_email_block'])) {
5457 7665 $new_input['enable_email_block'] = sanitize_text_field($input['enable_email_block']);
5458 7666 }
5459 7667
@@ -5463,11 +7671,20 @@
5463 7671 }
5464 7672 if (isset($input['email_blocker_button_text'])) {
5465 7673 $new_input['email_blocker_button_text'] = sanitize_text_field($input['email_blocker_button_text']);
5466 7674 }
5467 -
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 + }
5468 7685 if (isset($input['intro_message'])) {
5469 - $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
5470 7687 }
5471 7688
5472 7689 if (isset($input['input_copy'])) {
5473 7690 $new_input['input_copy'] = sanitize_text_field($input['input_copy']);
@@ -5476,58 +7693,128 @@
5476 7693 if (isset($input['rate_limit_message'])) {
5477 7694 $new_input['rate_limit_message'] = sanitize_text_field($input['rate_limit_message']);
5478 7695 }
5479 7696
5480 - if (isset($input['rate_limit_logged_out'])) {
5481 - $allowed_limits = array('1', '3', '5', '10', '15', '20', '100', 'unlimited'); // Add 'unlimited' to allowed values
5482 - $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');
5483 7702
5484 - if (in_array($rate_limit, $allowed_limits, true)) {
5485 - $new_input['rate_limit_logged_out'] = $rate_limit;
5486 - } else {
5487 - $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 + }
5488 7714 }
5489 - }
5490 7715
5491 - // Handle role rate limits
5492 - if (isset($input['role_rate_limits']) && is_array($input['role_rate_limits'])) {
5493 - $allowed_limits = array('1', '3', '5', '10', '15', '20', '100', 'unlimited');
5494 - $new_input['role_rate_limits'] = array();
5495 -
5496 - foreach ($input['role_rate_limits'] as $role_id => $rate_limit) {
5497 - $rate_limit = sanitize_text_field($rate_limit);
5498 - if (in_array($rate_limit, $allowed_limits, true)) {
5499 - $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;
5500 7721 } else {
5501 - $new_input['role_rate_limits'][$role_id] = '100'; // Default
7722 + $new_input['rate_limits'][$role_id]['timeframe'] = 'daily'; // Default
5502 7723 }
5503 7724 }
7725 +
7726 + // Sanitize message
7727 + if (isset($settings['message'])) {
7728 + $new_input['rate_limits'][$role_id]['message'] = sanitize_textarea_field($settings['message']);
7729 + }
5504 7730 }
7731 +}
5505 7732
5506 7733 if (isset($input['pre_chat_message'])) {
5507 7734 $new_input['pre_chat_message'] = sanitize_textarea_field($input['pre_chat_message']);
5508 7735 }
5509 7736
5510 - if (isset($input['model'])) {
7737 + if (isset($input['voyage_api_key'])) {
7738 + $new_input['voyage_api_key'] = sanitize_text_field($input['voyage_api_key']);
7739 + }
7740 +
7741 + // Add to your sanitize function
7742 + if (isset($input['embedding_model'])) {
5511 7743 $allowed_models = array(
5512 - 'grok-beta',
7744 + 'text-embedding-ada-002',
7745 + 'text-embedding-3-small',
7746 + 'text-embedding-3-large',
7747 + 'voyage-3-large',
7748 + 'gemini-embedding-001'
7749 + );
7750 + if (in_array($input['embedding_model'], $allowed_models)) {
7751 + $new_input['embedding_model'] = sanitize_text_field($input['embedding_model']);
7752 + }
7753 + }
7754 +
7755 +if (isset($input['model'])) {
7756 + if ($input['model'] === 'openrouter') {
7757 + $new_input['model'] = 'openrouter';
7758 + } else {
7759 + $allowed_models = array(
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',
5513 7776 'grok-2',
5514 7777 'deepseek-chat',
5515 - 'claude-3-5-sonnet-20241022',
5516 - 'claude-3-opus-20240229',
5517 - 'claude-3-sonnet-20240229',
5518 - 'claude-3-haiku-20240307',
5519 - 'gpt-4o',
5520 - 'gpt-4o-mini',
5521 - 'gpt-4-turbo',
5522 - 'gpt-4',
5523 - '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',
5524 7791 );
7792 +
5525 7793 if (in_array($input['model'], $allowed_models)) {
5526 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';
5527 7798 }
5528 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 +}
5529 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 +
5530 7817 if (isset($input['close_button_color'])) {
5531 7818 $new_input['close_button_color'] = sanitize_hex_color($input['close_button_color']);
5532 7819 }
5533 7820
@@ -5582,8 +7869,12 @@
5582 7869 if (isset($input['top_bar_bg_color'])) {
5583 7870 $new_input['top_bar_bg_color'] = sanitize_hex_color($input['top_bar_bg_color']);
5584 7871 }
5585 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 +
5586 7877 if (isset($input['send_button_font_color'])) {
5587 7878 $new_input['send_button_font_color'] = sanitize_hex_color($input['send_button_font_color']);
5588 7879 }
5589 7880
@@ -5641,15 +7932,14 @@
5641 7932 if (isset($input['loops_mailing_list'])) {
5642 7933 $new_input['loops_mailing_list'] = sanitize_text_field($input['loops_mailing_list']);
5643 7934 }
5644 7935
5645 - // Sanitize Triggered Phrase Response
7936 +// Sanitize Triggered Phrase Response
5646 7937 if (isset($input['triggered_phrase_response'])) {
5647 7938 $new_input['triggered_phrase_response'] = wp_kses_post($input['triggered_phrase_response']);
5648 7939 }
5649 -
5650 7940 if (isset($input['email_capture_response'])) {
5651 - $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']);
5652 7942 }
5653 7943
5654 7944 // Sanitize Brave Search Settings
5655 7945 if (isset($input['brave_api_key'])) {
@@ -5682,8 +7972,22 @@
5682 7972 if (isset($input['chat_toolbar_toggle'])) {
5683 7973 $new_input['chat_toolbar_toggle'] = $input['chat_toolbar_toggle'] === 'on' ? 'on' : 'off';
5684 7974 }
5685 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 +
5686 7990 if (isset($input['pdf_intent_trigger_text'])) {
5687 7991 $new_input['pdf_intent_trigger_text'] = sanitize_text_field($input['pdf_intent_trigger_text']);
5688 7992 }
5689 7993
@@ -5713,8 +8017,12 @@
5713 8017 if (isset($input['live_agent_bot_token'])) {
5714 8018 $new_input['live_agent_bot_token'] = sanitize_text_field($input['live_agent_bot_token']);
5715 8019 }
5716 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 +
5717 8025 if (isset($input['live_agent_status'])) {
5718 8026 $new_input['live_agent_status'] = ($input['live_agent_status'] === 'on') ? 'on' : 'off';
5719 8027 }
5720 8028 if (isset($input['live_agent_away_message'])) {
@@ -5723,8 +8031,36 @@
5723 8031 if (isset($input['live_agent_notification_message'])) {
5724 8032 $new_input['live_agent_notification_message'] = sanitize_textarea_field($input['live_agent_notification_message']);
5725 8033 }
5726 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 +
5727 8063 return $new_input;
5728 8064 }
5729 8065
5730 8066
@@ -5737,62 +8073,10 @@
5737 8073 }
5738 8074
5739 8075
5740 8076
5741 -private static function mxchat_extract_main_content($html) {
5742 - if (empty($html)) {
5743 - //error_log('mxchat_extract_main_content: Empty HTML content received');
5744 - return '';
5745 - }
5746 8077
5747 - try {
5748 - $dom = new DOMDocument;
5749 - libxml_use_internal_errors(true); // Suppress HTML parsing errors
5750 8078
5751 - // Simple load of HTML, with @ to suppress warnings
5752 - @$dom->loadHTML($html, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
5753 -
5754 - $xpath = new DOMXPath($dom);
5755 -
5756 - // Simplified selectors focusing on common content areas
5757 - $selectors = [
5758 - '//article',
5759 - '//*[@id="content"]',
5760 - '//*[@class="entry-content"]',
5761 - '//main'
5762 - ];
5763 -
5764 - foreach ($selectors as $selector) {
5765 - $nodes = $xpath->query($selector);
5766 - if ($nodes && $nodes->length > 0) {
5767 - $content = '';
5768 - foreach ($nodes as $node) {
5769 - $content .= $dom->saveHTML($node);
5770 - }
5771 - if (!empty($content)) {
5772 - return $content;
5773 - }
5774 - }
5775 - }
5776 -
5777 - // Fallback: Return the entire body content if no specific selector matches
5778 - $body = $dom->getElementsByTagName('body');
5779 - if ($body->length > 0) {
5780 - return $dom->saveHTML($body->item(0));
5781 - }
5782 -
5783 - // Last resort: return the original HTML
5784 - return $html;
5785 -
5786 - } catch (Exception $e) {
5787 - //error_log('mxchat_extract_main_content error: ' . $e->getMessage());
5788 - return $html; // Return original HTML if parsing fails
5789 - } finally {
5790 - libxml_clear_errors();
5791 - }
5792 -}
5793 -
5794 -
5795 8079 private function mxchat_fetch_loops_mailing_lists($api_key) {
5796 8080 $url = 'https://app.loops.so/api/v1/lists';
5797 8081 $response = wp_remote_get($url, array(
5798 8082 'headers' => array(
@@ -5810,12 +8094,8 @@
5810 8094
5811 8095 return isset($lists) && is_array($lists) ? $lists : array();
5812 8096 }
5813 8097
5814 -
5815 -
5816 -
5817 -
5818 8098 function mxchat_calculate_cosine_similarity($vec1, $vec2) {
5819 8099 if (empty($vec1) || empty($vec2)) {
5820 8100 return 0.0;
5821 8101 }
@@ -5836,8 +8116,441 @@
5836 8116 return $dot_product / (sqrt($norm_a) * sqrt($norm_b));
5837 8117 }
5838 8118 }
5839 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 + }
5840 8553
5841 8554
5842 8555
5843 8556 }