|
@@ -37,22 +37,11 @@ |
|
37
|
37
|
} |
|
38
|
38
|
|
|
39
|
39
|
// Add admin menu and initialize settings |
|
40
|
40
|
add_action('admin_menu', array($this, 'mxchat_add_plugin_page')); |
|
41
|
|
- // Pro & Extensions registers at priority 30 so it always lands last in |
|
42
|
|
- // the sidebar — below API Access (priority 20) and below any other |
|
43
|
|
- // submenu that hooks at default priority 10. |
|
44
|
|
- add_action('admin_menu', array($this, 'mxchat_add_pro_extensions_page'), 30); |
|
45
|
|
- // Onboarding visibility — runs LAST so it can remove the submenu after every |
|
46
|
|
- // other add_submenu_page() call. The page stays reachable by direct URL. |
|
47
|
|
- add_action('admin_menu', array($this, 'mxchat_apply_onboarding_visibility'), 999); |
|
48
|
41
|
add_action('admin_init', array($this, 'mxchat_page_init')); |
|
49
|
42
|
add_action('admin_init', array($this, 'mxchat_prompts_page_init')); |
|
50
|
43
|
add_action('admin_enqueue_scripts', array($this, 'mxchat_enqueue_admin_assets')); |
|
51
|
|
- // Add body class for the Onboarding wizard (plan-905439) so the |
|
52
|
|
- // chrome-surgery CSS in admin-onboarding-wizard.css can scope its |
|
53
|
|
- // WP-sidebar collapse to this page only. |
|
54
|
|
- add_filter('admin_body_class', array($this, 'mxchat_add_onboarding_body_class')); |
|
55
|
44
|
add_action('wp_ajax_mxchat_delete_chat_history', array($this, 'mxchat_delete_chat_history')); |
|
56
|
45
|
add_action('admin_post_mxchat_delete_prompt', array($this, 'mxchat_handle_delete_prompt')); |
|
57
|
46
|
add_action('wp_ajax_mxchat_fetch_chat_history', array($this, 'mxchat_fetch_chat_history')); |
|
58
|
47
|
add_action('wp_ajax_nopriv_mxchat_fetch_chat_history', array($this, 'mxchat_fetch_chat_history')); |
|
@@ -65,14 +54,8 @@ |
|
65
|
54
|
add_action('admin_post_mxchat_add_intent', array($this, 'mxchat_handle_add_intent')); |
|
66
|
55
|
add_action('admin_post_mxchat_delete_intent', array($this, 'mxchat_handle_delete_intent')); |
|
67
|
56
|
add_action('admin_post_mxchat_edit_intent', array($this, 'mxchat_handle_edit_intent')); |
|
68
|
57
|
add_action('wp_ajax_mxchat_export_transcripts', array($this, 'export_chat_transcripts')); |
|
69
|
|
- |
|
70
|
|
- // Leads tab (inside Transcripts) |
|
71
|
|
- add_action('wp_ajax_mxchat_fetch_leads', array($this, 'mxchat_fetch_leads')); |
|
72
|
|
- add_action('wp_ajax_mxchat_delete_leads', array($this, 'mxchat_delete_leads')); |
|
73
|
|
- add_action('wp_ajax_mxchat_export_leads', array($this, 'mxchat_export_leads')); |
|
74
|
|
- |
|
75
|
58
|
add_action('admin_init', array($this, 'mxchat_transcripts_page_init')); |
|
76
|
59
|
add_action('wp_ajax_dismiss_live_agent_notice', array($this, 'dismiss_live_agent_notice')); |
|
77
|
60
|
add_action('wp_ajax_dismiss_theme_migration_notice', array($this, 'dismiss_theme_migration_notice')); |
|
78
|
61
|
add_action('mxchat_cleanup_old_transcripts', array($this, 'cleanup_old_transcripts')); |
|
@@ -96,12 +79,8 @@ |
|
96
|
79
|
add_action('wp_ajax_mxchat_bulk_delete_actions', array($this, 'mxchat_bulk_delete_actions')); |
|
97
|
80
|
add_action('wp_ajax_mxchat_add_intent_ajax', array($this, 'mxchat_add_intent_ajax')); |
|
98
|
81
|
add_action('wp_ajax_mxchat_edit_intent_ajax', array($this, 'mxchat_edit_intent_ajax')); |
|
99
|
82
|
add_action('wp_ajax_mxchat_delete_intent_ajax', array($this, 'mxchat_delete_intent_ajax')); |
|
100
|
|
- add_action('wp_ajax_mxchat_add_phrase', array($this, 'mxchat_add_phrase_ajax')); |
|
101
|
|
- add_action('wp_ajax_mxchat_delete_phrase', array($this, 'mxchat_delete_phrase_ajax')); |
|
102
|
|
- add_action('wp_ajax_mxchat_get_phrases', array($this, 'mxchat_get_phrases_ajax')); |
|
103
|
|
- add_action('wp_ajax_mxchat_delete_legacy_phrases', array($this, 'mxchat_delete_legacy_phrases_ajax')); |
|
104
|
83
|
|
|
105
|
84
|
// Slack test connection |
|
106
|
85
|
add_action('wp_ajax_mxchat_test_slack_connection', array($this, 'mxchat_test_slack_connection')); |
|
107
|
86
|
|
|
@@ -107,149 +86,16 @@ |
|
107
|
86
|
|
|
108
|
87
|
// Translation handlers |
|
109
|
88
|
add_action('wp_ajax_mxchat_translate_messages', array($this, 'mxchat_translate_messages')); |
|
110
|
89
|
add_action('wp_ajax_mxchat_get_transcript_translation', array($this, 'mxchat_get_transcript_translation')); |
|
111
|
|
- |
|
112
|
|
- // 3.2.3: Embedding model switch protection |
|
113
|
|
- add_action('wp_ajax_mxchat_check_embedding_switch', array($this, 'mxchat_check_embedding_switch_ajax')); |
|
114
|
|
- add_action('wp_ajax_mxchat_dismiss_embedding_mismatch', array($this, 'mxchat_dismiss_embedding_mismatch_ajax')); |
|
115
|
|
- add_action('admin_notices', array($this, 'mxchat_embedding_mismatch_notice')); |
|
116
|
90
|
} |
|
117
|
91
|
|
|
118
|
|
- /** |
|
119
|
|
- * 3.2.3: Preflight check before allowing the embedding model dropdown to |
|
120
|
|
- * switch. Pure option comparison — no counts, no DB queries beyond the |
|
121
|
|
- * cached options. The dialog is shown whenever the user has previously |
|
122
|
|
- * embedded with a different model than the one they're switching to. |
|
123
|
|
- */ |
|
124
|
|
- public function mxchat_check_embedding_switch_ajax() { |
|
125
|
|
- check_ajax_referer('mxchat_admin_nonce', 'security'); |
|
126
|
|
- if (!current_user_can('manage_options')) { |
|
127
|
|
- wp_send_json_error(__('Unauthorized', 'mxchat')); |
|
128
|
|
- } |
|
129
|
|
- |
|
130
|
|
- $new_model = isset($_POST['new_model']) ? sanitize_text_field(wp_unslash($_POST['new_model'])) : ''; |
|
131
|
|
- $active_model = MxChat_Utils::get_active_embedding_model(); |
|
132
|
|
- |
|
133
|
|
- $is_mismatch = !empty($active_model) && !empty($new_model) && $active_model !== $new_model; |
|
134
|
|
- |
|
135
|
|
- $active_dims = MxChat_Utils::embedding_model_dimensions($active_model); |
|
136
|
|
- $new_dims = MxChat_Utils::embedding_model_dimensions($new_model); |
|
137
|
|
- |
|
138
|
|
- wp_send_json_success(array( |
|
139
|
|
- 'is_mismatch' => $is_mismatch, |
|
140
|
|
- 'active_model' => $active_model, |
|
141
|
|
- 'active_label' => MxChat_Utils::embedding_model_label($active_model), |
|
142
|
|
- 'new_model' => $new_model, |
|
143
|
|
- 'new_label' => MxChat_Utils::embedding_model_label($new_model), |
|
144
|
|
- 'dims_differ' => ($active_dims > 0 && $new_dims > 0 && $active_dims !== $new_dims), |
|
145
|
|
- 'active_dims' => $active_dims, |
|
146
|
|
- 'new_dims' => $new_dims, |
|
147
|
|
- )); |
|
148
|
|
- } |
|
149
|
|
- |
|
150
|
|
- /** |
|
151
|
|
- * 3.2.3: Dismiss the persistent mismatch banner. Tied to the active+selected |
|
152
|
|
- * pair so the banner reappears on the next switch event. |
|
153
|
|
- */ |
|
154
|
|
- public function mxchat_dismiss_embedding_mismatch_ajax() { |
|
155
|
|
- check_ajax_referer('mxchat_admin_nonce', 'security'); |
|
156
|
|
- if (!current_user_can('manage_options')) { |
|
157
|
|
- wp_send_json_error(__('Unauthorized', 'mxchat')); |
|
158
|
|
- } |
|
159
|
|
- |
|
160
|
|
- $options = get_option('mxchat_options', array()); |
|
161
|
|
- $selected = $options['embedding_model'] ?? ''; |
|
162
|
|
- $active = MxChat_Utils::get_active_embedding_model(); |
|
163
|
|
- update_option('mxchat_dismissed_embedding_mismatch', $active . '|' . $selected, false); |
|
164
|
|
- wp_send_json_success(); |
|
165
|
|
- } |
|
166
|
|
- |
|
167
|
|
- /** |
|
168
|
|
- * 3.2.3: Persistent admin banner shown whenever the active embedding model |
|
169
|
|
- * (last used to actually embed something) differs from the currently |
|
170
|
|
- * selected model. Pure option comparison — no DB queries on every page |
|
171
|
|
- * load. The banner auto-clears once both match again, i.e. after a delete |
|
172
|
|
- * + re-embed cycle. |
|
173
|
|
- */ |
|
174
|
|
- public function mxchat_embedding_mismatch_notice() { |
|
175
|
|
- if (!current_user_can('manage_options')) { |
|
176
|
|
- return; |
|
177
|
|
- } |
|
178
|
|
- |
|
179
|
|
- $options = get_option('mxchat_options', array()); |
|
180
|
|
- $selected = $options['embedding_model'] ?? ''; |
|
181
|
|
- $active = MxChat_Utils::get_active_embedding_model(); |
|
182
|
|
- |
|
183
|
|
- if (empty($active) || empty($selected) || $active === $selected) { |
|
184
|
|
- return; |
|
185
|
|
- } |
|
186
|
|
- |
|
187
|
|
- $dismissed = get_option('mxchat_dismissed_embedding_mismatch', ''); |
|
188
|
|
- if ($dismissed === $active . '|' . $selected) { |
|
189
|
|
- return; |
|
190
|
|
- } |
|
191
|
|
- |
|
192
|
|
- $active_label = MxChat_Utils::embedding_model_label($active); |
|
193
|
|
- $selected_label = MxChat_Utils::embedding_model_label($selected); |
|
194
|
|
- $active_dims = MxChat_Utils::embedding_model_dimensions($active); |
|
195
|
|
- $selected_dims = MxChat_Utils::embedding_model_dimensions($selected); |
|
196
|
|
- $dims_differ = ($active_dims > 0 && $selected_dims > 0 && $active_dims !== $selected_dims); |
|
197
|
|
- |
|
198
|
|
- $kb_url = admin_url('admin.php?page=mxchat-prompts'); |
|
199
|
|
- $actions_url = admin_url('admin.php?page=mxchat-actions'); |
|
200
|
|
- |
|
201
|
|
- ?> |
|
202
|
|
- <div class="notice notice-error is-dismissible mxchat-embedding-mismatch-notice" |
|
203
|
|
- data-active="<?php echo esc_attr($active); ?>" |
|
204
|
|
- data-selected="<?php echo esc_attr($selected); ?>"> |
|
205
|
|
- <p><strong><?php esc_html_e('MxChat: Embedding model mismatch detected', 'mxchat'); ?></strong></p> |
|
206
|
|
- <p> |
|
207
|
|
- <?php |
|
208
|
|
- printf( |
|
209
|
|
- /* translators: 1: previously-used model name, 2: currently-selected model name */ |
|
210
|
|
- esc_html__('Your knowledge base and actions were embedded with %1$s, but %2$s is now selected. Similarity matching will return inaccurate or empty results until you delete all existing embeddings and re-embed your content with the new model.', 'mxchat'), |
|
211
|
|
- '<code>' . esc_html($active_label) . '</code>', |
|
212
|
|
- '<code>' . esc_html($selected_label) . '</code>' |
|
213
|
|
- ); |
|
214
|
|
- ?> |
|
215
|
|
- </p> |
|
216
|
|
- <?php if ($dims_differ) : ?> |
|
217
|
|
- <p> |
|
218
|
|
- <strong><?php esc_html_e('Dimension mismatch:', 'mxchat'); ?></strong> |
|
219
|
|
- <?php |
|
220
|
|
- printf( |
|
221
|
|
- /* translators: 1: old dim count, 2: new dim count */ |
|
222
|
|
- esc_html__('Existing vectors are %1$d-dimensional but the new model produces %2$d-dimensional vectors. If you use Pinecone, your index will reject queries entirely until re-embedded.', 'mxchat'), |
|
223
|
|
- (int) $active_dims, |
|
224
|
|
- (int) $selected_dims |
|
225
|
|
- ); |
|
226
|
|
- ?> |
|
227
|
|
- </p> |
|
228
|
|
- <?php endif; ?> |
|
229
|
|
- <p> |
|
230
|
|
- <?php esc_html_e('To fix this:', 'mxchat'); ?> |
|
231
|
|
- <a href="<?php echo esc_url($kb_url); ?>"><?php esc_html_e('Delete all knowledge base entries', 'mxchat'); ?></a> · |
|
232
|
|
- <a href="<?php echo esc_url($actions_url); ?>"><?php esc_html_e('Delete all actions', 'mxchat'); ?></a> · |
|
233
|
|
- <?php esc_html_e('then re-import / re-add them with the new model selected.', 'mxchat'); ?> |
|
234
|
|
- </p> |
|
235
|
|
- </div> |
|
236
|
|
- <script> |
|
237
|
|
- (function($){ |
|
238
|
|
- $(document).on('click', '.mxchat-embedding-mismatch-notice .notice-dismiss', function(){ |
|
239
|
|
- $.post(ajaxurl, { |
|
240
|
|
- action: 'mxchat_dismiss_embedding_mismatch', |
|
241
|
|
- security: '<?php echo esc_js(wp_create_nonce('mxchat_admin_nonce')); ?>' |
|
242
|
|
- }); |
|
243
|
|
- }); |
|
244
|
|
- })(jQuery); |
|
245
|
|
- </script> |
|
246
|
|
- <?php |
|
247
|
|
- } |
|
248
|
|
- |
|
249
|
92
|
private function is_license_active() { |
|
93
|
+ // Get the raw value without translation |
|
250
|
94
|
$license_status = get_option('mxchat_license_status', 'inactive'); |
|
251
|
|
- return ($license_status === 'active'); |
|
95
|
+ |
|
96
|
+ // Check against multiple possible values, bypassing translation issues |
|
97
|
+ return ($license_status === 'active' || $license_status === esc_html__('active', 'mxchat')); |
|
252
|
98
|
} |
|
253
|
99
|
|
|
254
|
100
|
private function initialize_default_options() { |
|
255
|
101
|
$default_options = array( |
|
@@ -298,14 +144,9 @@ |
|
298
|
144
|
'post_type_visibility_mode' => 'all', // 'all', 'include', 'exclude' |
|
299
|
145
|
'post_type_visibility_list' => array(), // Array of post type slugs |
|
300
|
146
|
'contextual_awareness_toggle' => 'off', |
|
301
|
147
|
'citation_links_toggle' => 'on', |
|
302
|
|
- 'satisfaction_rating_enabled' => 'off', |
|
303
|
|
- 'satisfaction_rating_idle_seconds' => 60, |
|
304
|
|
- 'satisfaction_rating_question' => '', |
|
305
|
|
- 'satisfaction_rating_thanks' => '', |
|
306
|
|
- 'satisfaction_rating_placeholder' => '', |
|
307
|
|
- 'satisfaction_rating_saved' => '', |
|
148
|
+ 'show_frontend_debugger' => 'on', |
|
308
|
149
|
'close_button_color' => esc_html__('#fff', 'mxchat'), |
|
309
|
150
|
'chatbot_bg_color' => esc_html__('#fff', 'mxchat'), |
|
310
|
151
|
'user_message_bg_color' => esc_html__('#fff', 'mxchat'), |
|
311
|
152
|
'user_message_font_color' => esc_html__('#212121', 'mxchat'), |
|
@@ -384,55 +225,26 @@ |
|
384
|
225
|
$this->options = $merged_options; |
|
385
|
226
|
} |
|
386
|
227
|
|
|
387
|
228
|
public function mxchat_add_plugin_page() { |
|
388
|
|
- // Onboarding lifecycle helpers (admin_init redirect + ajax handlers live in the file). |
|
389
|
|
- require_once plugin_dir_path(__FILE__) . 'admin-onboarding-page.php'; |
|
390
|
|
- |
|
391
|
|
- // Main menu page — `mxchat-max` remains the parent slug for every MxChat submenu |
|
392
|
|
- // (Settings, Knowledge, Transcripts, …). Hitting `?page=mxchat-max` directly now |
|
393
|
|
- // dispatches to the Onboarding page (or Settings if the user has dismissed onboarding). |
|
229
|
+ // Main menu page |
|
394
|
230
|
add_menu_page( |
|
231
|
+ esc_html__('MxChat Settings', 'mxchat'), |
|
395
|
232
|
esc_html__('MxChat', 'mxchat'), |
|
396
|
|
- esc_html__('MxChat', 'mxchat'), |
|
397
|
233
|
'manage_options', |
|
398
|
234
|
'mxchat-max', |
|
399
|
|
- array($this, 'mxchat_create_dashboard_page'), |
|
235
|
+ array($this, 'mxchat_create_admin_page'), |
|
400
|
236
|
'dashicons-testimonial', |
|
401
|
237
|
6 |
|
402
|
238
|
); |
|
403
|
239
|
|
|
404
|
|
- // Onboarding submenu — first child under MxChat (plan-d14e89). |
|
405
|
|
- // First registration uses menu_slug === parent slug 'mxchat-max' → |
|
406
|
|
- // WP-canonical override of the auto-duplicate "MxChat" entry. Result: the |
|
407
|
|
- // first child shows as "Onboarding" instead of a redundant pair. |
|
240
|
+ // Rename the first submenu item from "MxChat" to "Settings" |
|
408
|
241
|
add_submenu_page( |
|
409
|
242
|
'mxchat-max', |
|
410
|
|
- esc_html__('MxChat Onboarding', 'mxchat'), |
|
411
|
|
- esc_html__('Onboarding', 'mxchat'), |
|
412
|
|
- 'manage_options', |
|
413
|
|
- 'mxchat-max', |
|
414
|
|
- array($this, 'mxchat_create_dashboard_page') |
|
415
|
|
- ); |
|
416
|
|
- // Hidden route for `?page=mxchat-onboarding` (Settings "Show again" link |
|
417
|
|
- // + legacy redirects still target this slug). Parent === null keeps it |
|
418
|
|
- // out of the menu while remaining accessible by URL. |
|
419
|
|
- add_submenu_page( |
|
420
|
|
- null, |
|
421
|
|
- esc_html__('MxChat Onboarding', 'mxchat'), |
|
422
|
|
- esc_html__('Onboarding', 'mxchat'), |
|
423
|
|
- 'manage_options', |
|
424
|
|
- 'mxchat-onboarding', |
|
425
|
|
- array($this, 'mxchat_create_dashboard_page') |
|
426
|
|
- ); |
|
427
|
|
- |
|
428
|
|
- // Settings submenu — same callback as before, just at a new slug. |
|
429
|
|
- add_submenu_page( |
|
430
|
|
- 'mxchat-max', |
|
431
|
243
|
esc_html__('MxChat Settings', 'mxchat'), |
|
432
|
244
|
esc_html__('Settings', 'mxchat'), |
|
433
|
245
|
'manage_options', |
|
434
|
|
- 'mxchat-settings', |
|
246
|
+ 'mxchat-max', |
|
435
|
247
|
array($this, 'mxchat_create_admin_page') |
|
436
|
248
|
); |
|
437
|
249
|
|
|
438
|
250
|
// Submenu page for Knowledge |
|
@@ -472,16 +284,9 @@ |
|
472
|
284
|
'mxchat-content', |
|
473
|
285
|
array($this, 'mxchat_create_content_page') |
|
474
|
286
|
); |
|
475
|
287
|
|
|
476
|
|
-} |
|
477
|
|
- |
|
478
|
|
-/** |
|
479
|
|
- * Register the Pro & Extensions submenu on a later admin_menu priority so it |
|
480
|
|
- * always renders as the bottom-most item in the MxChat sidebar — below |
|
481
|
|
- * configuration pages like API Access (priority 20). |
|
482
|
|
- */ |
|
483
|
|
-public function mxchat_add_pro_extensions_page() { |
|
288
|
+ // Consolidated Pro & Extensions page (replaces separate Add Ons and Pro Upgrade pages) |
|
484
|
289
|
add_submenu_page( |
|
485
|
290
|
'mxchat-max', |
|
486
|
291
|
esc_html__('Pro & Extensions', 'mxchat'), |
|
487
|
292
|
esc_html__('Pro & Extensions', 'mxchat'), |
|
@@ -1069,54 +874,8 @@ |
|
1069
|
874
|
</div> |
|
1070
|
875
|
<?php |
|
1071
|
876
|
} |
|
1072
|
877
|
} |
|
1073
|
|
-/** |
|
1074
|
|
- * Render the Onboarding page. Delegates to the procedural renderer in |
|
1075
|
|
- * includes/admin-onboarding-page.php. Wired to both `?page=mxchat-max` |
|
1076
|
|
- * (legacy top-level URL) and `?page=mxchat-onboarding` (the canonical |
|
1077
|
|
- * Onboarding submenu). |
|
1078
|
|
- * |
|
1079
|
|
- * When the user has dismissed onboarding and lands on `mxchat-max` (the |
|
1080
|
|
- * legacy URL, since the Onboarding submenu has been removed), redirect to |
|
1081
|
|
- * Settings instead — the page is "graduated" and we shouldn't dump them |
|
1082
|
|
- * back onto it. They can still navigate here directly via the unhide link. |
|
1083
|
|
- */ |
|
1084
|
|
-public function mxchat_create_dashboard_page() { |
|
1085
|
|
- require_once plugin_dir_path(__FILE__) . 'admin-onboarding-page.php'; |
|
1086
|
|
- |
|
1087
|
|
- $current = isset($_GET['page']) ? sanitize_key($_GET['page']) : ''; |
|
1088
|
|
- if ($current === 'mxchat-max' && function_exists('mxchat_onboarding_is_dismissed') && mxchat_onboarding_is_dismissed()) { |
|
1089
|
|
- wp_safe_redirect(admin_url('admin.php?page=mxchat-settings')); |
|
1090
|
|
- exit; |
|
1091
|
|
- } |
|
1092
|
|
- |
|
1093
|
|
- if (function_exists('mxchat_render_onboarding_page')) { |
|
1094
|
|
- mxchat_render_onboarding_page(); |
|
1095
|
|
- return; |
|
1096
|
|
- } |
|
1097
|
|
- // Defensive: if the include failed to load, fall back to the old Settings page |
|
1098
|
|
- // so the top-level menu never lands on an empty screen. |
|
1099
|
|
- $this->mxchat_create_admin_page(); |
|
1100
|
|
-} |
|
1101
|
|
- |
|
1102
|
|
-/** |
|
1103
|
|
- * Hide the Onboarding submenu when the user has dismissed it (either |
|
1104
|
|
- * manually or via auto-graduation). The page itself remains routable so |
|
1105
|
|
- * the Settings "Show MxChat Onboarding again" link can navigate back to it. |
|
1106
|
|
- */ |
|
1107
|
|
-public function mxchat_apply_onboarding_visibility() { |
|
1108
|
|
- if (!function_exists('mxchat_onboarding_is_dismissed')) { |
|
1109
|
|
- return; |
|
1110
|
|
- } |
|
1111
|
|
- if (mxchat_onboarding_is_dismissed()) { |
|
1112
|
|
- // The first MxChat child is the same-slug-as-parent registration |
|
1113
|
|
- // (slug 'mxchat-max', labelled "Onboarding") added in plan-d14e89. |
|
1114
|
|
- // Remove it so the menu opens straight to Settings after dismiss. |
|
1115
|
|
- remove_submenu_page('mxchat-max', 'mxchat-max'); |
|
1116
|
|
- } |
|
1117
|
|
-} |
|
1118
|
|
- |
|
1119
|
878
|
public function mxchat_create_admin_page() { |
|
1120
|
879
|
$this->add_live_agent_nonce(); |
|
1121
|
880
|
$this->add_theme_migration_nonce(); |
|
1122
|
881
|
|
|
@@ -1358,11 +1117,8 @@ |
|
1358
|
1117
|
$chart_messages[] = 0; |
|
1359
|
1118
|
} |
|
1360
|
1119
|
} |
|
1361
|
1120
|
|
|
1362
|
|
- // Satisfaction rating rollup — last 30 days, grouped by bot (plan-a5b006). |
|
1363
|
|
- $satisfaction_stats = $this->get_satisfaction_rating_stats(30); |
|
1364
|
|
- |
|
1365
|
1121
|
// Prepare page data for the template |
|
1366
|
1122
|
$page_data = array( |
|
1367
|
1123
|
'total_chats' => $total_chats, |
|
1368
|
1124
|
'total_messages' => $total_messages, |
|
@@ -1377,9 +1133,8 @@ |
|
1377
|
1133
|
'busiest_hour' => $busiest_hour, |
|
1378
|
1134
|
'chart_labels' => $chart_labels, |
|
1379
|
1135
|
'chart_chats' => $chart_chats, |
|
1380
|
1136
|
'chart_messages' => $chart_messages, |
|
1381
|
|
- 'satisfaction_stats' => $satisfaction_stats, |
|
1382
|
1137
|
); |
|
1383
|
1138
|
|
|
1384
|
1139
|
// Include and render the new template |
|
1385
|
1140
|
require_once plugin_dir_path(__FILE__) . 'admin-transcripts-page.php'; |
|
@@ -1386,50 +1141,8 @@ |
|
1386
|
1141
|
mxchat_render_transcripts_page($this, $page_data); |
|
1387
|
1142
|
} |
|
1388
|
1143
|
|
|
1389
|
1144
|
/** |
|
1390
|
|
- * Per-bot satisfaction rating rollup over the last $days days. Used by the |
|
1391
|
|
- * Satisfaction card on the Transcripts dashboard (plan-a5b006). |
|
1392
|
|
- * |
|
1393
|
|
- * @param int $days Window in days. |
|
1394
|
|
- * @return array Each entry: ['bot_id', 'total', 'positive', 'negative', 'positive_pct', 'negative_pct']. |
|
1395
|
|
- */ |
|
1396
|
|
-public function get_satisfaction_rating_stats($days = 30) { |
|
1397
|
|
- global $wpdb; |
|
1398
|
|
- $table = $wpdb->prefix . 'mxchat_session_ratings'; |
|
1399
|
|
- if ($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $table)) !== $table) { |
|
1400
|
|
- return array(); |
|
1401
|
|
- } |
|
1402
|
|
- $days = max(1, (int) $days); |
|
1403
|
|
- $rows = $wpdb->get_results($wpdb->prepare( |
|
1404
|
|
- "SELECT bot_id, |
|
1405
|
|
- COUNT(*) AS total, |
|
1406
|
|
- SUM(CASE WHEN rating_value = 1 THEN 1 ELSE 0 END) AS positive, |
|
1407
|
|
- SUM(CASE WHEN rating_value = -1 THEN 1 ELSE 0 END) AS negative |
|
1408
|
|
- FROM {$table} |
|
1409
|
|
- WHERE created_at >= DATE_SUB(NOW(), INTERVAL %d DAY) |
|
1410
|
|
- GROUP BY bot_id |
|
1411
|
|
- ORDER BY total DESC", |
|
1412
|
|
- $days |
|
1413
|
|
- )); |
|
1414
|
|
- $out = array(); |
|
1415
|
|
- foreach ((array) $rows as $row) { |
|
1416
|
|
- $total = (int) $row->total; |
|
1417
|
|
- $positive = (int) $row->positive; |
|
1418
|
|
- $negative = (int) $row->negative; |
|
1419
|
|
- $out[] = array( |
|
1420
|
|
- 'bot_id' => $row->bot_id ?: 'default', |
|
1421
|
|
- 'total' => $total, |
|
1422
|
|
- 'positive' => $positive, |
|
1423
|
|
- 'negative' => $negative, |
|
1424
|
|
- 'positive_pct' => $total > 0 ? (int) round(($positive / $total) * 100) : 0, |
|
1425
|
|
- 'negative_pct' => $total > 0 ? (int) round(($negative / $total) * 100) : 0, |
|
1426
|
|
- ); |
|
1427
|
|
- } |
|
1428
|
|
- return $out; |
|
1429
|
|
-} |
|
1430
|
|
- |
|
1431
|
|
-/** |
|
1432
|
1145
|
* Get chart data for transcripts page |
|
1433
|
1146
|
* Used by both page render and script localization |
|
1434
|
1147
|
* |
|
1435
|
1148
|
* @return array Chart data with labels, chats, and messages arrays |
|
@@ -1541,39 +1254,8 @@ |
|
1541
|
1254
|
</p> |
|
1542
|
1255
|
<?php |
|
1543
|
1256
|
} |
|
1544
|
1257
|
|
|
1545
|
|
-/** |
|
1546
|
|
- * Custom retention-days input. When > 0 it overrides the bucket dropdown above |
|
1547
|
|
- * and deletes transcripts older than the given number of days. Set to 0 to fall |
|
1548
|
|
- * back to the dropdown (or "Never" if the dropdown is also Never). |
|
1549
|
|
- * |
|
1550
|
|
- * Devs can override the final day count via the `mxchat_transcript_retention_days` |
|
1551
|
|
- * filter — runs in `cleanup_old_transcripts()` after this option is read. |
|
1552
|
|
- * |
|
1553
|
|
- * (plan-mxchat-20260509-9b80b1) |
|
1554
|
|
- */ |
|
1555
|
|
-public function mxchat_retention_days_callback() { |
|
1556
|
|
- $options = get_option('mxchat_transcripts_options', array()); |
|
1557
|
|
- $days = isset($options['mxchat_retention_days']) ? (int) $options['mxchat_retention_days'] : 0; |
|
1558
|
|
- ?> |
|
1559
|
|
- <input type="number" |
|
1560
|
|
- id="mxchat_retention_days" |
|
1561
|
|
- name="mxchat_transcripts_options[mxchat_retention_days]" |
|
1562
|
|
- value="<?php echo esc_attr($days); ?>" |
|
1563
|
|
- min="0" |
|
1564
|
|
- max="3650" |
|
1565
|
|
- step="1" |
|
1566
|
|
- style="width: 90px;" /> |
|
1567
|
|
- <p class="description"> |
|
1568
|
|
- <?php esc_html_e('Number of days to retain transcripts. When set to a value greater than 0, this overrides the dropdown above. Set to 0 to use the dropdown. Maximum 3650 (10 years). A daily wp-cron task removes anything older, cascading to translations and click-tracking rows.', 'mxchat'); ?> |
|
1569
|
|
- <br> |
|
1570
|
|
- <code>apply_filters( 'mxchat_transcript_retention_days', $days )</code> |
|
1571
|
|
- <?php esc_html_e('lets developers override the final day count programmatically.', 'mxchat'); ?> |
|
1572
|
|
- </p> |
|
1573
|
|
- <?php |
|
1574
|
|
-} |
|
1575
|
|
- |
|
1576
|
1258
|
public function mxchat_auto_email_transcript_callback() { |
|
1577
|
1259
|
$options = get_option('mxchat_transcripts_options', array()); |
|
1578
|
1260
|
$enabled = isset($options['mxchat_auto_email_transcript_enabled']) ? $options['mxchat_auto_email_transcript_enabled'] : 0; |
|
1579
|
1261
|
$delay = isset($options['mxchat_auto_email_transcript_delay']) ? $options['mxchat_auto_email_transcript_delay'] : '30'; |
|
@@ -1649,29 +1331,15 @@ |
|
1649
|
1331
|
} else { |
|
1650
|
1332
|
$sanitized['mxchat_auto_delete_transcripts'] = 'never'; |
|
1651
|
1333
|
} |
|
1652
|
1334
|
|
|
1653
|
|
- // Sanitize custom retention-days override (plan-9b80b1). |
|
1654
|
|
- if (isset($input['mxchat_retention_days'])) { |
|
1655
|
|
- $days = (int) $input['mxchat_retention_days']; |
|
1656
|
|
- $sanitized['mxchat_retention_days'] = max(0, min(3650, $days)); |
|
1657
|
|
- } else { |
|
1658
|
|
- $sanitized['mxchat_retention_days'] = 0; |
|
1659
|
|
- } |
|
1660
|
|
- |
|
1661
|
|
- // Get old values to check if auto-delete or retention-days changed. |
|
1335
|
+ // Get old value to check if auto-delete setting changed |
|
1662
|
1336
|
$old_options = get_option('mxchat_transcripts_options'); |
|
1663
|
1337
|
$old_interval = isset($old_options['mxchat_auto_delete_transcripts']) ? $old_options['mxchat_auto_delete_transcripts'] : 'never'; |
|
1664
|
|
- $old_retention = isset($old_options['mxchat_retention_days']) ? (int) $old_options['mxchat_retention_days'] : 0; |
|
1665
|
|
- |
|
1666
|
|
- // If either setting changed, reschedule the cron job. The schedule_transcript_cleanup |
|
1667
|
|
- // helper now treats "any active retention" (dropdown != never OR custom days > 0) as a |
|
1668
|
|
- // reason to keep the daily cron registered. |
|
1669
|
|
- $interval_changed = ($old_interval !== $sanitized['mxchat_auto_delete_transcripts']); |
|
1670
|
|
- $retention_changed = ($old_retention !== $sanitized['mxchat_retention_days']); |
|
1671
|
|
- if ($interval_changed || $retention_changed) { |
|
1672
|
|
- $any_active = ($sanitized['mxchat_auto_delete_transcripts'] !== 'never') || ($sanitized['mxchat_retention_days'] > 0); |
|
1673
|
|
- $this->schedule_transcript_cleanup($any_active ? 'active' : 'never'); |
|
1338
|
+ |
|
1339
|
+ // If the auto-delete setting changed, reschedule the cron job |
|
1340
|
+ if ($old_interval !== $sanitized['mxchat_auto_delete_transcripts']) { |
|
1341
|
+ $this->schedule_transcript_cleanup($sanitized['mxchat_auto_delete_transcripts']); |
|
1674
|
1342
|
} |
|
1675
|
1343
|
|
|
1676
|
1344
|
// Sanitize auto-email transcript settings |
|
1677
|
1345
|
$sanitized['mxchat_auto_email_transcript_enabled'] = isset($input['mxchat_auto_email_transcript_enabled']) ? 1 : 0; |
|
@@ -1699,13 +1367,10 @@ |
|
1699
|
1367
|
$timestamp = wp_next_scheduled('mxchat_cleanup_old_transcripts'); |
|
1700
|
1368
|
if ($timestamp) { |
|
1701
|
1369
|
wp_unschedule_event($timestamp, 'mxchat_cleanup_old_transcripts'); |
|
1702
|
1370
|
} |
|
1703
|
|
- |
|
1704
|
|
- // Schedule new event whenever retention is active. "active" is the canonical |
|
1705
|
|
- // value passed by sanitize_transcripts_options when either the dropdown != never |
|
1706
|
|
- // OR the custom retention-days > 0; "never" turns the cron off. Any other value |
|
1707
|
|
- // (the legacy "1week" / "2weeks" / "1month" strings) is also treated as active. |
|
1371
|
+ |
|
1372
|
+ // Schedule new event if not set to "never" |
|
1708
|
1373
|
if ($interval !== 'never') { |
|
1709
|
1374
|
// Schedule to run daily at 3 AM |
|
1710
|
1375
|
$next_run = strtotime('tomorrow 3:00 AM'); |
|
1711
|
1376
|
wp_schedule_event($next_run, 'daily', 'mxchat_cleanup_old_transcripts'); |
|
@@ -1715,74 +1380,58 @@ |
|
1715
|
1380
|
/** |
|
1716
|
1381
|
* Delete old transcripts based on the configured interval |
|
1717
|
1382
|
*/ |
|
1718
|
1383
|
public function cleanup_old_transcripts() { |
|
1719
|
|
- $options = get_option('mxchat_transcripts_options', array()); |
|
1720
|
|
- $interval = isset($options['mxchat_auto_delete_transcripts']) ? $options['mxchat_auto_delete_transcripts'] : 'never'; |
|
1721
|
|
- $custom_days = isset($options['mxchat_retention_days']) ? (int) $options['mxchat_retention_days'] : 0; |
|
1722
|
|
- |
|
1723
|
|
- // Custom retention-days (plan-9b80b1) takes precedence over the bucket dropdown. |
|
1384
|
+ $options = get_option('mxchat_transcripts_options', array()); |
|
1385
|
+ $interval = isset($options['mxchat_auto_delete_transcripts']) ? $options['mxchat_auto_delete_transcripts'] : 'never'; |
|
1386
|
+ |
|
1387
|
+ // If set to never, don't delete anything |
|
1388
|
+ if ($interval === 'never') { |
|
1389
|
+ return; |
|
1390
|
+ } |
|
1391
|
+ |
|
1392
|
+ // Calculate the cutoff date |
|
1724
|
1393
|
$days = 0; |
|
1725
|
|
- if ($custom_days > 0) { |
|
1726
|
|
- $days = $custom_days; |
|
1727
|
|
- } else { |
|
1728
|
|
- switch ($interval) { |
|
1729
|
|
- case '1week': $days = 7; break; |
|
1730
|
|
- case '2weeks': $days = 14; break; |
|
1731
|
|
- case '1month': $days = 30; break; |
|
1732
|
|
- case 'never': |
|
1733
|
|
- default: |
|
1734
|
|
- $days = 0; |
|
1735
|
|
- } |
|
1394
|
+ switch ($interval) { |
|
1395
|
+ case '1week': |
|
1396
|
+ $days = 7; |
|
1397
|
+ break; |
|
1398
|
+ case '2weeks': |
|
1399
|
+ $days = 14; |
|
1400
|
+ break; |
|
1401
|
+ case '1month': |
|
1402
|
+ $days = 30; |
|
1403
|
+ break; |
|
1404
|
+ default: |
|
1405
|
+ return; // Invalid interval, don't delete anything |
|
1736
|
1406
|
} |
|
1737
|
|
- |
|
1738
|
|
- // Devs can override the final day count programmatically. |
|
1739
|
|
- $days = (int) apply_filters('mxchat_transcript_retention_days', $days); |
|
1740
|
|
- |
|
1741
|
|
- if ($days <= 0) { |
|
1742
|
|
- return; // Retention disabled — bail. |
|
1743
|
|
- } |
|
1744
|
|
- |
|
1407
|
+ |
|
1745
|
1408
|
global $wpdb; |
|
1746
|
|
- $transcripts_table = $wpdb->prefix . 'mxchat_chat_transcripts'; |
|
1409
|
+ $table_name = $wpdb->prefix . 'mxchat_chat_transcripts'; |
|
1747
|
1410
|
$translations_table = $wpdb->prefix . 'mxchat_transcript_translations'; |
|
1748
|
|
- $url_clicks_table = $wpdb->prefix . 'mxchat_url_clicks'; |
|
1749
|
1411
|
|
|
1750
|
|
- // Defensive: if the main table doesn't exist (fresh-ish install), bail. |
|
1751
|
|
- if ($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $transcripts_table)) !== $transcripts_table) { |
|
1752
|
|
- return; |
|
1753
|
|
- } |
|
1412
|
+ // Calculate the cutoff timestamp |
|
1413
|
+ $cutoff_date = date('Y-m-d H:i:s', strtotime("-{$days} days")); |
|
1754
|
1414
|
|
|
1755
|
|
- $cutoff_date = gmdate('Y-m-d H:i:s', time() - ($days * DAY_IN_SECONDS)); |
|
1756
|
|
- |
|
1757
|
|
- // Cap at 5000 session_ids per run so a large unattended site doesn't OOM — |
|
1758
|
|
- // the cron will pick up where it left off on the next tick (within hours). |
|
1759
|
|
- $batch_cap = (int) apply_filters('mxchat_transcript_retention_batch_cap', 5000); |
|
1760
|
|
- |
|
1415
|
+ // First, get the session IDs that will be deleted (to clean up translations too) |
|
1761
|
1416
|
$sessions_to_delete = $wpdb->get_col( |
|
1762
|
1417
|
$wpdb->prepare( |
|
1763
|
|
- "SELECT DISTINCT session_id FROM {$transcripts_table} WHERE timestamp IS NOT NULL AND timestamp < %s LIMIT %d", |
|
1764
|
|
- $cutoff_date, |
|
1765
|
|
- $batch_cap |
|
1418
|
+ "SELECT DISTINCT session_id FROM {$table_name} WHERE timestamp < %s", |
|
1419
|
+ $cutoff_date |
|
1766
|
1420
|
) |
|
1767
|
1421
|
); |
|
1768
|
1422
|
|
|
1769
|
|
- if (empty($sessions_to_delete)) { |
|
1770
|
|
- return; |
|
1771
|
|
- } |
|
1772
|
|
- |
|
1773
|
|
- $placeholders = implode(',', array_fill(0, count($sessions_to_delete), '%s')); |
|
1774
|
|
- |
|
1775
|
|
- // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared |
|
1776
|
|
- // $placeholders is a server-built list of literal "%s" tokens. |
|
1777
|
|
- $deleted_transcripts = (int) $wpdb->query( |
|
1423
|
+ // Delete transcripts older than the cutoff date |
|
1424
|
+ $deleted = $wpdb->query( |
|
1778
|
1425
|
$wpdb->prepare( |
|
1779
|
|
- "DELETE FROM {$transcripts_table} WHERE session_id IN ($placeholders)", |
|
1780
|
|
- $sessions_to_delete |
|
1426
|
+ "DELETE FROM {$table_name} WHERE timestamp < %s", |
|
1427
|
+ $cutoff_date |
|
1781
|
1428
|
) |
|
1782
|
1429
|
); |
|
1783
|
1430
|
|
|
1784
|
|
- if ($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $translations_table)) === $translations_table) { |
|
1431
|
+ // Also delete any translations for the deleted sessions |
|
1432
|
+ if (!empty($sessions_to_delete) && $wpdb->get_var("SHOW TABLES LIKE '$translations_table'") === $translations_table) { |
|
1433
|
+ $placeholders = implode(',', array_fill(0, count($sessions_to_delete), '%s')); |
|
1785
|
1434
|
$wpdb->query( |
|
1786
|
1435
|
$wpdb->prepare( |
|
1787
|
1436
|
"DELETE FROM {$translations_table} WHERE session_id IN ($placeholders)", |
|
1788
|
1437
|
$sessions_to_delete |
|
@@ -1789,20 +1438,12 @@ |
|
1789
|
1438
|
) |
|
1790
|
1439
|
); |
|
1791
|
1440
|
} |
|
1792
|
1441
|
|
|
1793
|
|
- if ($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $url_clicks_table)) === $url_clicks_table) { |
|
1794
|
|
- $wpdb->query( |
|
1795
|
|
- $wpdb->prepare( |
|
1796
|
|
- "DELETE FROM {$url_clicks_table} WHERE session_id IN ($placeholders)", |
|
1797
|
|
- $sessions_to_delete |
|
1798
|
|
- ) |
|
1799
|
|
- ); |
|
1442
|
+ // Log the cleanup action |
|
1443
|
+ if ($deleted !== false && $deleted > 0) { |
|
1444
|
+ //error_log(sprintf('MXChat: Auto-deleted %d transcripts older than %d days', $deleted, $days)); |
|
1800
|
1445
|
} |
|
1801
|
|
- // phpcs:enable |
|
1802
|
|
- |
|
1803
|
|
- update_option('mxchat_retention_last_swept_at', time(), false); |
|
1804
|
|
- update_option('mxchat_retention_rows_last_deleted', $deleted_transcripts, false); |
|
1805
|
1446
|
} |
|
1806
|
1447
|
|
|
1807
|
1448
|
public function export_chat_transcripts() { |
|
1808
|
1449
|
if (!current_user_can('manage_options')) { |
|
@@ -1863,763 +1504,9 @@ |
|
1863
|
1504
|
fclose($output); |
|
1864
|
1505
|
wp_die(); |
|
1865
|
1506
|
} |
|
1866
|
1507
|
|
|
1867
|
|
-// ============================================================================ |
|
1868
|
|
-// Leads tab (inside Transcripts) |
|
1869
|
|
-// |
|
1870
|
|
-// Leads are derived from existing data — no dedicated table. Primary source: |
|
1871
|
|
-// wp_mxchat_chat_transcripts rows where user_email is populated. Secondary |
|
1872
|
|
-// source: wp_options entries `mxchat_email_{session_id}` / `mxchat_name_{sid}` |
|
1873
|
|
-// for "orphan" leads who submitted the pre-chat form but never chatted. |
|
1874
|
|
-// ============================================================================ |
|
1875
|
|
- |
|
1876
|
1508
|
/** |
|
1877
|
|
- * Fetch leads: dedup-by-email rows, stats strip, and top pages in one call. |
|
1878
|
|
- */ |
|
1879
|
|
-public function mxchat_fetch_leads() { |
|
1880
|
|
- if (!current_user_can('manage_options')) { |
|
1881
|
|
- wp_send_json_error(['message' => 'Insufficient permissions']); |
|
1882
|
|
- wp_die(); |
|
1883
|
|
- } |
|
1884
|
|
- |
|
1885
|
|
- global $wpdb; |
|
1886
|
|
- $table = $wpdb->prefix . 'mxchat_chat_transcripts'; |
|
1887
|
|
- |
|
1888
|
|
- $page = isset($_POST['page']) ? max(1, absint($_POST['page'])) : 1; |
|
1889
|
|
- $per_page = isset($_POST['per_page']) ? min(100, max(10, absint($_POST['per_page']))) : 25; |
|
1890
|
|
- $offset = ($page - 1) * $per_page; |
|
1891
|
|
- $search = isset($_POST['search']) ? sanitize_text_field(wp_unslash($_POST['search'])) : ''; |
|
1892
|
|
- $date_range = isset($_POST['date_range']) ? sanitize_key($_POST['date_range']) : 'all'; |
|
1893
|
|
- $status = isset($_POST['status']) ? sanitize_key($_POST['status']) : 'all'; |
|
1894
|
|
- $page_filter = isset($_POST['page_url']) ? esc_url_raw(wp_unslash($_POST['page_url'])) : ''; |
|
1895
|
|
- $sort = isset($_POST['sort']) ? sanitize_key($_POST['sort']) : 'last_seen'; |
|
1896
|
|
- $sort_dir = (isset($_POST['sort_dir']) && $_POST['sort_dir'] === 'asc') ? 'ASC' : 'DESC'; |
|
1897
|
|
- |
|
1898
|
|
- $date_cutoff = self::mxchat_leads_date_cutoff($date_range); |
|
1899
|
|
- $has_page_url_column = !empty($wpdb->get_results("SHOW COLUMNS FROM $table LIKE 'originating_page_url'")); |
|
1900
|
|
- |
|
1901
|
|
- // Base WHERE for transcripts leads. |
|
1902
|
|
- $where_clauses = ["user_email IS NOT NULL", "user_email != ''"]; |
|
1903
|
|
- $where_params = []; |
|
1904
|
|
- |
|
1905
|
|
- if ($date_cutoff) { |
|
1906
|
|
- $where_clauses[] = 'timestamp >= %s'; |
|
1907
|
|
- $where_params[] = $date_cutoff; |
|
1908
|
|
- } |
|
1909
|
|
- if ($page_filter && $has_page_url_column) { |
|
1910
|
|
- $where_clauses[] = 'originating_page_url = %s'; |
|
1911
|
|
- $where_params[] = $page_filter; |
|
1912
|
|
- } |
|
1913
|
|
- if ($search !== '') { |
|
1914
|
|
- $like = '%' . $wpdb->esc_like($search) . '%'; |
|
1915
|
|
- $where_clauses[] = '(user_email LIKE %s OR user_name LIKE %s)'; |
|
1916
|
|
- $where_params[] = $like; |
|
1917
|
|
- $where_params[] = $like; |
|
1918
|
|
- } |
|
1919
|
|
- $where_sql = 'WHERE ' . implode(' AND ', $where_clauses); |
|
1920
|
|
- |
|
1921
|
|
- // Aggregate query grouped by email. |
|
1922
|
|
- $select_sql = $has_page_url_column |
|
1923
|
|
- ? "SELECT user_email, MAX(timestamp) AS last_seen, MIN(timestamp) AS first_seen, |
|
1924
|
|
- COUNT(DISTINCT session_id) AS conversation_count" |
|
1925
|
|
- : "SELECT user_email, MAX(timestamp) AS last_seen, MIN(timestamp) AS first_seen, |
|
1926
|
|
- COUNT(DISTINCT session_id) AS conversation_count"; |
|
1927
|
|
- |
|
1928
|
|
- $order_column = in_array($sort, ['last_seen', 'conversation_count', 'first_seen'], true) ? $sort : 'last_seen'; |
|
1929
|
|
- $group_order_limit = " GROUP BY user_email ORDER BY {$order_column} {$sort_dir} LIMIT %d OFFSET %d"; |
|
1930
|
|
- |
|
1931
|
|
- $transcripts_sql = $wpdb->prepare( |
|
1932
|
|
- "{$select_sql} FROM {$table} {$where_sql}{$group_order_limit}", |
|
1933
|
|
- array_merge($where_params, [$per_page, $offset]) |
|
1934
|
|
- ); |
|
1935
|
|
- $transcript_rows = $wpdb->get_results($transcripts_sql); |
|
1936
|
|
- |
|
1937
|
|
- // Count of unique transcript-based leads under the same filters. |
|
1938
|
|
- $count_sql = $wpdb->prepare( |
|
1939
|
|
- "SELECT COUNT(DISTINCT user_email) FROM {$table} {$where_sql}", |
|
1940
|
|
- $where_params |
|
1941
|
|
- ); |
|
1942
|
|
- $transcripts_lead_count = (int) $wpdb->get_var($count_sql); |
|
1943
|
|
- |
|
1944
|
|
- // Hydrate each row: name, latest_session_id, top page. |
|
1945
|
|
- $leads = []; |
|
1946
|
|
- foreach ($transcript_rows as $row) { |
|
1947
|
|
- $detail = $has_page_url_column |
|
1948
|
|
- ? $wpdb->get_row($wpdb->prepare( |
|
1949
|
|
- "SELECT session_id, user_name, originating_page_url, originating_page_title |
|
1950
|
|
- FROM {$table} WHERE user_email = %s ORDER BY timestamp DESC LIMIT 1", |
|
1951
|
|
- $row->user_email |
|
1952
|
|
- )) |
|
1953
|
|
- : $wpdb->get_row($wpdb->prepare( |
|
1954
|
|
- "SELECT session_id, user_name FROM {$table} |
|
1955
|
|
- WHERE user_email = %s ORDER BY timestamp DESC LIMIT 1", |
|
1956
|
|
- $row->user_email |
|
1957
|
|
- )); |
|
1958
|
|
- |
|
1959
|
|
- $leads[] = [ |
|
1960
|
|
- 'email' => $row->user_email, |
|
1961
|
|
- 'name' => isset($detail->user_name) ? (string) $detail->user_name : '', |
|
1962
|
|
- 'conversation_count' => (int) $row->conversation_count, |
|
1963
|
|
- 'last_seen' => $row->last_seen, |
|
1964
|
|
- 'last_seen_display' => self::mxchat_leads_format_relative($row->last_seen), |
|
1965
|
|
- 'first_seen' => $row->first_seen, |
|
1966
|
|
- 'latest_session_id' => isset($detail->session_id) ? $detail->session_id : '', |
|
1967
|
|
- 'top_page_url' => isset($detail->originating_page_url) ? $detail->originating_page_url : '', |
|
1968
|
|
- 'top_page_title' => isset($detail->originating_page_title) ? $detail->originating_page_title : '', |
|
1969
|
|
- 'is_orphan' => false, |
|
1970
|
|
- 'status' => 'active', |
|
1971
|
|
- ]; |
|
1972
|
|
- } |
|
1973
|
|
- |
|
1974
|
|
- // Non-transcript lead sources. Built once here, then filtered/merged based on the |
|
1975
|
|
- // status filter below. Deduplication priority when the same email appears in multiple |
|
1976
|
|
- // sources: transcripts > chat_deleted > orphan. |
|
1977
|
|
- $transcripts_emails_seen = array_flip(array_map( |
|
1978
|
|
- function ($r) { return strtolower($r['email']); }, |
|
1979
|
|
- $leads |
|
1980
|
|
- )); |
|
1981
|
|
- |
|
1982
|
|
- // Chat-deleted leads: had a conversation that an admin removed. Preserved via |
|
1983
|
|
- // mxchat_lead_del_* options, with timestamps so they respect date filters. |
|
1984
|
|
- $chat_deleted_leads_all = []; |
|
1985
|
|
- if ($status === 'all' || $status === 'chat_deleted') { |
|
1986
|
|
- $chat_deleted_leads_all = self::mxchat_collect_chat_deleted_leads($search, $date_cutoff); |
|
1987
|
|
- // Dedup: drop any chat_deleted row whose email is already in the transcripts set. |
|
1988
|
|
- $chat_deleted_leads_all = array_values(array_filter( |
|
1989
|
|
- $chat_deleted_leads_all, |
|
1990
|
|
- function ($row) use ($transcripts_emails_seen) { |
|
1991
|
|
- return !isset($transcripts_emails_seen[strtolower($row['email'])]); |
|
1992
|
|
- } |
|
1993
|
|
- )); |
|
1994
|
|
- foreach ($chat_deleted_leads_all as $row) { |
|
1995
|
|
- $transcripts_emails_seen[strtolower($row['email'])] = true; |
|
1996
|
|
- } |
|
1997
|
|
- } |
|
1998
|
|
- |
|
1999
|
|
- // Orphans: pre-chat form captures with no conversation. No timestamp, so skipped |
|
2000
|
|
- // when a date filter is active. |
|
2001
|
|
- $orphan_leads_all = []; |
|
2002
|
|
- if (($status === 'all' || $status === 'orphan') && !$date_cutoff && !$page_filter) { |
|
2003
|
|
- $orphan_leads_all = self::mxchat_collect_orphan_leads($search); |
|
2004
|
|
- $orphan_leads_all = array_values(array_filter( |
|
2005
|
|
- $orphan_leads_all, |
|
2006
|
|
- function ($row) use ($transcripts_emails_seen) { |
|
2007
|
|
- return !isset($transcripts_emails_seen[strtolower($row['email'])]); |
|
2008
|
|
- } |
|
2009
|
|
- )); |
|
2010
|
|
- } |
|
2011
|
|
- |
|
2012
|
|
- // Apply status filter to the transcripts-derived list. |
|
2013
|
|
- if ($status === 'orphan' || $status === 'chat_deleted') { |
|
2014
|
|
- $leads = []; |
|
2015
|
|
- $transcripts_lead_count = 0; |
|
2016
|
|
- } |
|
2017
|
|
- |
|
2018
|
|
- // Stitch the current page from the three buckets in priority order. |
|
2019
|
|
- $total_count = $transcripts_lead_count + count($chat_deleted_leads_all) + count($orphan_leads_all); |
|
2020
|
|
- $remaining_slots = $per_page - count($leads); |
|
2021
|
|
- |
|
2022
|
|
- if ($remaining_slots > 0 && !empty($chat_deleted_leads_all)) { |
|
2023
|
|
- $start = max(0, ($page - 1) * $per_page - $transcripts_lead_count); |
|
2024
|
|
- if ($start < count($chat_deleted_leads_all)) { |
|
2025
|
|
- $leads = array_merge($leads, array_slice($chat_deleted_leads_all, $start, $remaining_slots)); |
|
2026
|
|
- $remaining_slots = $per_page - count($leads); |
|
2027
|
|
- } |
|
2028
|
|
- } |
|
2029
|
|
- |
|
2030
|
|
- if ($remaining_slots > 0 && !empty($orphan_leads_all)) { |
|
2031
|
|
- $before = $transcripts_lead_count + count($chat_deleted_leads_all); |
|
2032
|
|
- $start = max(0, ($page - 1) * $per_page - $before); |
|
2033
|
|
- if ($start < count($orphan_leads_all)) { |
|
2034
|
|
- $leads = array_merge($leads, array_slice($orphan_leads_all, $start, $remaining_slots)); |
|
2035
|
|
- } |
|
2036
|
|
- } |
|
2037
|
|
- |
|
2038
|
|
- $total_pages = $per_page > 0 ? (int) ceil($total_count / $per_page) : 1; |
|
2039
|
|
- |
|
2040
|
|
- // Stats strip: always computed over full dataset, unaffected by filters. |
|
2041
|
|
- $stats = self::mxchat_leads_stats($table, $has_page_url_column); |
|
2042
|
|
- |
|
2043
|
|
- // Top pages: top 5 by distinct emails captured. |
|
2044
|
|
- $top_pages = []; |
|
2045
|
|
- if ($has_page_url_column) { |
|
2046
|
|
- $top_pages_rows = $wpdb->get_results( |
|
2047
|
|
- "SELECT originating_page_url AS url, |
|
2048
|
|
- MAX(originating_page_title) AS title, |
|
2049
|
|
- COUNT(DISTINCT user_email) AS lead_count |
|
2050
|
|
- FROM {$table} |
|
2051
|
|
- WHERE user_email IS NOT NULL AND user_email != '' |
|
2052
|
|
- AND originating_page_url IS NOT NULL AND originating_page_url != '' |
|
2053
|
|
- GROUP BY originating_page_url |
|
2054
|
|
- ORDER BY lead_count DESC, url ASC |
|
2055
|
|
- LIMIT 5" |
|
2056
|
|
- ); |
|
2057
|
|
- foreach ($top_pages_rows as $p) { |
|
2058
|
|
- $top_pages[] = [ |
|
2059
|
|
- 'url' => $p->url, |
|
2060
|
|
- 'title' => $p->title ?: $p->url, |
|
2061
|
|
- 'lead_count' => (int) $p->lead_count, |
|
2062
|
|
- ]; |
|
2063
|
|
- } |
|
2064
|
|
- } |
|
2065
|
|
- |
|
2066
|
|
- wp_send_json([ |
|
2067
|
|
- 'success' => true, |
|
2068
|
|
- 'leads' => $leads, |
|
2069
|
|
- 'page' => $page, |
|
2070
|
|
- 'per_page' => $per_page, |
|
2071
|
|
- 'total_count' => $total_count, |
|
2072
|
|
- 'total_pages' => $total_pages, |
|
2073
|
|
- 'showing_start' => $total_count === 0 ? 0 : ($offset + 1), |
|
2074
|
|
- 'showing_end' => min($offset + $per_page, $total_count), |
|
2075
|
|
- 'stats' => $stats, |
|
2076
|
|
- 'top_pages' => $top_pages, |
|
2077
|
|
- ]); |
|
2078
|
|
- wp_die(); |
|
2079
|
|
-} |
|
2080
|
|
- |
|
2081
|
|
-/** |
|
2082
|
|
- * Delete one or more leads by email. Removes every transcripts row for that |
|
2083
|
|
- * email and cleans up related wp_options (mxchat_email_{sid}, mxchat_name_{sid}, |
|
2084
|
|
- * mxchat_history_{sid}) and any orphan option entries matching the email. |
|
2085
|
|
- */ |
|
2086
|
|
-public function mxchat_delete_leads() { |
|
2087
|
|
- if (!current_user_can('manage_options')) { |
|
2088
|
|
- wp_send_json_error(['message' => 'Insufficient permissions']); |
|
2089
|
|
- wp_die(); |
|
2090
|
|
- } |
|
2091
|
|
- check_ajax_referer('mxchat_delete_leads', 'security'); |
|
2092
|
|
- |
|
2093
|
|
- $emails_raw = isset($_POST['emails']) ? (array) wp_unslash($_POST['emails']) : []; |
|
2094
|
|
- $emails = []; |
|
2095
|
|
- foreach ($emails_raw as $e) { |
|
2096
|
|
- $clean = sanitize_email((string) $e); |
|
2097
|
|
- if ($clean) { |
|
2098
|
|
- $emails[] = $clean; |
|
2099
|
|
- } |
|
2100
|
|
- } |
|
2101
|
|
- if (empty($emails)) { |
|
2102
|
|
- wp_send_json_error(['message' => 'No emails provided']); |
|
2103
|
|
- wp_die(); |
|
2104
|
|
- } |
|
2105
|
|
- |
|
2106
|
|
- $summary = self::mxchat_wipe_leads_by_email($emails); |
|
2107
|
|
- |
|
2108
|
|
- wp_send_json([ |
|
2109
|
|
- 'success' => true, |
|
2110
|
|
- 'deleted_leads' => count($emails), |
|
2111
|
|
- 'deleted_sessions' => $summary['deleted_sessions'], |
|
2112
|
|
- 'deleted_rows' => $summary['deleted_rows'], |
|
2113
|
|
- ]); |
|
2114
|
|
- wp_die(); |
|
2115
|
|
-} |
|
2116
|
|
- |
|
2117
|
|
-/** |
|
2118
|
|
- * Fully wipe one or more leads by email: every transcripts row, every related wp_options |
|
2119
|
|
- * entry (history, pre-chat capture, chat_deleted preservation, agent name, translations). |
|
2120
|
|
- * |
|
2121
|
|
- * Shared between the Leads-tab Delete button and the transcript-delete opt-in checkbox. |
|
2122
|
|
- * Input emails must already be sanitized with sanitize_email(). |
|
2123
|
|
- */ |
|
2124
|
|
-private static function mxchat_wipe_leads_by_email(array $emails) { |
|
2125
|
|
- global $wpdb; |
|
2126
|
|
- $table = $wpdb->prefix . 'mxchat_chat_transcripts'; |
|
2127
|
|
- $translations_table = $wpdb->prefix . 'mxchat_transcript_translations'; |
|
2128
|
|
- $has_translations = $wpdb->get_var("SHOW TABLES LIKE '$translations_table'") === $translations_table; |
|
2129
|
|
- |
|
2130
|
|
- $deleted_sessions = 0; |
|
2131
|
|
- $deleted_rows = 0; |
|
2132
|
|
- $emails_lc = array_map('strtolower', $emails); |
|
2133
|
|
- |
|
2134
|
|
- foreach ($emails as $email) { |
|
2135
|
|
- $session_ids = $wpdb->get_col($wpdb->prepare( |
|
2136
|
|
- "SELECT DISTINCT session_id FROM {$table} WHERE user_email = %s", |
|
2137
|
|
- $email |
|
2138
|
|
- )); |
|
2139
|
|
- |
|
2140
|
|
- $rows_removed = $wpdb->delete($table, ['user_email' => $email], ['%s']); |
|
2141
|
|
- if ($rows_removed !== false) { |
|
2142
|
|
- $deleted_rows += (int) $rows_removed; |
|
2143
|
|
- } |
|
2144
|
|
- |
|
2145
|
|
- foreach ($session_ids as $sid) { |
|
2146
|
|
- $deleted_sessions++; |
|
2147
|
|
- wp_cache_delete('chat_session_' . $sid, 'mxchat_chat_sessions'); |
|
2148
|
|
- delete_option('mxchat_history_' . $sid); |
|
2149
|
|
- delete_option('mxchat_email_' . $sid); |
|
2150
|
|
- delete_option('mxchat_name_' . $sid); |
|
2151
|
|
- delete_option('mxchat_agent_name_' . $sid); |
|
2152
|
|
- delete_option('mxchat_lead_del_email_' . $sid); |
|
2153
|
|
- delete_option('mxchat_lead_del_name_' . $sid); |
|
2154
|
|
- delete_option('mxchat_lead_del_ts_' . $sid); |
|
2155
|
|
- if ($has_translations) { |
|
2156
|
|
- $wpdb->delete($translations_table, ['session_id' => $sid], ['%s']); |
|
2157
|
|
- } |
|
2158
|
|
- } |
|
2159
|
|
- } |
|
2160
|
|
- |
|
2161
|
|
- // Clean up any lingering option entries (orphan pre-chat captures + chat_deleted |
|
2162
|
|
- // preservations) whose stored value matches one of the emails being wiped. |
|
2163
|
|
- $lingering = $wpdb->get_results( |
|
2164
|
|
- "SELECT option_name, option_value FROM {$wpdb->options} |
|
2165
|
|
- WHERE option_name LIKE 'mxchat_email_%' OR option_name LIKE 'mxchat_lead_del_email_%'" |
|
2166
|
|
- ); |
|
2167
|
|
- foreach ($lingering as $opt) { |
|
2168
|
|
- if (!in_array(strtolower(trim($opt->option_value)), $emails_lc, true)) { |
|
2169
|
|
- continue; |
|
2170
|
|
- } |
|
2171
|
|
- if (strpos($opt->option_name, 'mxchat_lead_del_email_') === 0) { |
|
2172
|
|
- $sid = substr($opt->option_name, strlen('mxchat_lead_del_email_')); |
|
2173
|
|
- delete_option('mxchat_lead_del_email_' . $sid); |
|
2174
|
|
- delete_option('mxchat_lead_del_name_' . $sid); |
|
2175
|
|
- delete_option('mxchat_lead_del_ts_' . $sid); |
|
2176
|
|
- } else { |
|
2177
|
|
- $sid = substr($opt->option_name, strlen('mxchat_email_')); |
|
2178
|
|
- delete_option('mxchat_email_' . $sid); |
|
2179
|
|
- delete_option('mxchat_name_' . $sid); |
|
2180
|
|
- } |
|
2181
|
|
- } |
|
2182
|
|
- |
|
2183
|
|
- wp_cache_delete('all_chat_sessions', 'mxchat_chat_sessions'); |
|
2184
|
|
- |
|
2185
|
|
- return [ |
|
2186
|
|
- 'deleted_sessions' => $deleted_sessions, |
|
2187
|
|
- 'deleted_rows' => $deleted_rows, |
|
2188
|
|
- ]; |
|
2189
|
|
-} |
|
2190
|
|
- |
|
2191
|
|
-/** |
|
2192
|
|
- * Stream a leads CSV. scope=all exports every lead under current filters is not |
|
2193
|
|
- * supported to keep semantics simple; caller either exports all leads or a |
|
2194
|
|
- * specific set of selected emails. |
|
2195
|
|
- */ |
|
2196
|
|
-public function mxchat_export_leads() { |
|
2197
|
|
- if (!current_user_can('manage_options')) { |
|
2198
|
|
- wp_die(esc_html__('You do not have sufficient permissions to access this page.', 'mxchat')); |
|
2199
|
|
- } |
|
2200
|
|
- check_ajax_referer('mxchat_export_leads', 'security'); |
|
2201
|
|
- |
|
2202
|
|
- $scope = isset($_POST['scope']) ? sanitize_key($_POST['scope']) : 'all'; |
|
2203
|
|
- $fields_mode = isset($_POST['fields']) ? sanitize_key($_POST['fields']) : 'email_and_name'; |
|
2204
|
|
- $emails_in = isset($_POST['emails']) ? (array) wp_unslash($_POST['emails']) : []; |
|
2205
|
|
- |
|
2206
|
|
- $emails_in_clean = []; |
|
2207
|
|
- foreach ($emails_in as $e) { |
|
2208
|
|
- $clean = sanitize_email((string) $e); |
|
2209
|
|
- if ($clean) { |
|
2210
|
|
- $emails_in_clean[] = $clean; |
|
2211
|
|
- } |
|
2212
|
|
- } |
|
2213
|
|
- |
|
2214
|
|
- global $wpdb; |
|
2215
|
|
- $table = $wpdb->prefix . 'mxchat_chat_transcripts'; |
|
2216
|
|
- $has_page_url_column = !empty($wpdb->get_results("SHOW COLUMNS FROM $table LIKE 'originating_page_url'")); |
|
2217
|
|
- |
|
2218
|
|
- // Collect leads from transcripts. |
|
2219
|
|
- $transcripts_sql = "SELECT user_email AS email, |
|
2220
|
|
- MAX(timestamp) AS last_seen, |
|
2221
|
|
- COUNT(DISTINCT session_id) AS conversation_count |
|
2222
|
|
- FROM {$table} |
|
2223
|
|
- WHERE user_email IS NOT NULL AND user_email != ''"; |
|
2224
|
|
- $params = []; |
|
2225
|
|
- if ($scope === 'selected' && !empty($emails_in_clean)) { |
|
2226
|
|
- $placeholders = implode(',', array_fill(0, count($emails_in_clean), '%s')); |
|
2227
|
|
- $transcripts_sql .= " AND user_email IN ({$placeholders})"; |
|
2228
|
|
- $params = $emails_in_clean; |
|
2229
|
|
- } |
|
2230
|
|
- $transcripts_sql .= " GROUP BY user_email ORDER BY last_seen DESC"; |
|
2231
|
|
- |
|
2232
|
|
- $rows = !empty($params) |
|
2233
|
|
- ? $wpdb->get_results($wpdb->prepare($transcripts_sql, $params)) |
|
2234
|
|
- : $wpdb->get_results($transcripts_sql); |
|
2235
|
|
- |
|
2236
|
|
- // Hydrate each row with name + top page. |
|
2237
|
|
- $export_rows = []; |
|
2238
|
|
- foreach ($rows as $row) { |
|
2239
|
|
- $detail = $has_page_url_column |
|
2240
|
|
- ? $wpdb->get_row($wpdb->prepare( |
|
2241
|
|
- "SELECT user_name, originating_page_url FROM {$table} |
|
2242
|
|
- WHERE user_email = %s ORDER BY timestamp DESC LIMIT 1", |
|
2243
|
|
- $row->email |
|
2244
|
|
- )) |
|
2245
|
|
- : $wpdb->get_row($wpdb->prepare( |
|
2246
|
|
- "SELECT user_name FROM {$table} |
|
2247
|
|
- WHERE user_email = %s ORDER BY timestamp DESC LIMIT 1", |
|
2248
|
|
- $row->email |
|
2249
|
|
- )); |
|
2250
|
|
- $export_rows[] = [ |
|
2251
|
|
- 'email' => $row->email, |
|
2252
|
|
- 'name' => isset($detail->user_name) ? (string) $detail->user_name : '', |
|
2253
|
|
- 'conversation_count' => (int) $row->conversation_count, |
|
2254
|
|
- 'last_seen' => $row->last_seen, |
|
2255
|
|
- 'top_page_url' => isset($detail->originating_page_url) ? $detail->originating_page_url : '', |
|
2256
|
|
- ]; |
|
2257
|
|
- } |
|
2258
|
|
- |
|
2259
|
|
- // Include orphan + chat_deleted leads when exporting all. |
|
2260
|
|
- if ($scope === 'all') { |
|
2261
|
|
- $transcripts_emails_lc = array_flip(array_map( |
|
2262
|
|
- function ($r) { return strtolower($r['email']); }, |
|
2263
|
|
- $export_rows |
|
2264
|
|
- )); |
|
2265
|
|
- foreach (self::mxchat_collect_chat_deleted_leads('') as $cd) { |
|
2266
|
|
- if (isset($transcripts_emails_lc[strtolower($cd['email'])])) continue; |
|
2267
|
|
- $transcripts_emails_lc[strtolower($cd['email'])] = true; |
|
2268
|
|
- $export_rows[] = [ |
|
2269
|
|
- 'email' => $cd['email'], |
|
2270
|
|
- 'name' => $cd['name'], |
|
2271
|
|
- 'conversation_count' => 0, |
|
2272
|
|
- 'last_seen' => $cd['last_seen'], |
|
2273
|
|
- 'top_page_url' => '', |
|
2274
|
|
- ]; |
|
2275
|
|
- } |
|
2276
|
|
- foreach (self::mxchat_collect_orphan_leads('') as $orphan) { |
|
2277
|
|
- if (isset($transcripts_emails_lc[strtolower($orphan['email'])])) continue; |
|
2278
|
|
- $transcripts_emails_lc[strtolower($orphan['email'])] = true; |
|
2279
|
|
- $export_rows[] = [ |
|
2280
|
|
- 'email' => $orphan['email'], |
|
2281
|
|
- 'name' => $orphan['name'], |
|
2282
|
|
- 'conversation_count' => 0, |
|
2283
|
|
- 'last_seen' => '', |
|
2284
|
|
- 'top_page_url' => '', |
|
2285
|
|
- ]; |
|
2286
|
|
- } |
|
2287
|
|
- } |
|
2288
|
|
- |
|
2289
|
|
- if (empty($export_rows)) { |
|
2290
|
|
- wp_send_json_error(['message' => 'No leads to export.']); |
|
2291
|
|
- wp_die(); |
|
2292
|
|
- } |
|
2293
|
|
- |
|
2294
|
|
- $filename = 'mxchat-leads-' . date('Y-m-d') . '.csv'; |
|
2295
|
|
- header('Content-Type: text/csv'); |
|
2296
|
|
- header('Content-Disposition: attachment; filename="' . $filename . '"'); |
|
2297
|
|
- header('Pragma: no-cache'); |
|
2298
|
|
- header('Expires: 0'); |
|
2299
|
|
- |
|
2300
|
|
- $output = fopen('php://output', 'w'); |
|
2301
|
|
- fputs($output, "\xEF\xBB\xBF"); // UTF-8 BOM for Excel |
|
2302
|
|
- |
|
2303
|
|
- if ($fields_mode === 'email_only') { |
|
2304
|
|
- fputcsv($output, ['Email']); |
|
2305
|
|
- foreach ($export_rows as $r) { |
|
2306
|
|
- fputcsv($output, [$r['email']]); |
|
2307
|
|
- } |
|
2308
|
|
- } else { |
|
2309
|
|
- fputcsv($output, ['Email', 'Name', 'Conversations', 'Last seen', 'Top page']); |
|
2310
|
|
- foreach ($export_rows as $r) { |
|
2311
|
|
- fputcsv($output, [ |
|
2312
|
|
- $r['email'], |
|
2313
|
|
- $r['name'], |
|
2314
|
|
- $r['conversation_count'], |
|
2315
|
|
- $r['last_seen'], |
|
2316
|
|
- $r['top_page_url'], |
|
2317
|
|
- ]); |
|
2318
|
|
- } |
|
2319
|
|
- } |
|
2320
|
|
- |
|
2321
|
|
- fclose($output); |
|
2322
|
|
- wp_die(); |
|
2323
|
|
-} |
|
2324
|
|
- |
|
2325
|
|
-/** |
|
2326
|
|
- * Stats strip payload (independent of filters). |
|
2327
|
|
- */ |
|
2328
|
|
-private static function mxchat_leads_stats($table, $has_page_url_column) { |
|
2329
|
|
- global $wpdb; |
|
2330
|
|
- |
|
2331
|
|
- $total_transcripts_emails = (int) $wpdb->get_var( |
|
2332
|
|
- "SELECT COUNT(DISTINCT user_email) FROM {$table} |
|
2333
|
|
- WHERE user_email IS NOT NULL AND user_email != ''" |
|
2334
|
|
- ); |
|
2335
|
|
- |
|
2336
|
|
- $new_this_week = (int) $wpdb->get_var($wpdb->prepare( |
|
2337
|
|
- "SELECT COUNT(*) FROM ( |
|
2338
|
|
- SELECT user_email FROM {$table} |
|
2339
|
|
- WHERE user_email IS NOT NULL AND user_email != '' |
|
2340
|
|
- GROUP BY user_email |
|
2341
|
|
- HAVING MIN(timestamp) >= %s |
|
2342
|
|
- ) AS new_leads", |
|
2343
|
|
- gmdate('Y-m-d H:i:s', strtotime('-7 days')) |
|
2344
|
|
- )); |
|
2345
|
|
- |
|
2346
|
|
- $total_convos = (int) $wpdb->get_var( |
|
2347
|
|
- "SELECT COUNT(DISTINCT session_id) FROM {$table} |
|
2348
|
|
- WHERE user_email IS NOT NULL AND user_email != ''" |
|
2349
|
|
- ); |
|
2350
|
|
- |
|
2351
|
|
- $orphan_count = count(self::mxchat_collect_orphan_leads('')); |
|
2352
|
|
- $chat_deleted_count = self::mxchat_count_chat_deleted_leads(); |
|
2353
|
|
- |
|
2354
|
|
- // Total leads = unique emails across all three sources (dedup priority: transcripts > chat_deleted > orphan |
|
2355
|
|
- // is already enforced at collection time in mxchat_fetch_leads; stats re-apply it here). |
|
2356
|
|
- $total_leads = $total_transcripts_emails + $chat_deleted_count + $orphan_count; |
|
2357
|
|
- |
|
2358
|
|
- $avg = $total_transcripts_emails > 0 |
|
2359
|
|
- ? round($total_convos / $total_transcripts_emails, 1) |
|
2360
|
|
- : 0; |
|
2361
|
|
- |
|
2362
|
|
- // Orphan % reflects *true* orphans only (pre-chat dropoffs). Chat-deleted leads are |
|
2363
|
|
- // excluded so the metric stays meaningful — admins shouldn't see their cleanups |
|
2364
|
|
- // inflate this number. |
|
2365
|
|
- $orphan_pct = $total_leads > 0 |
|
2366
|
|
- ? (int) round(($orphan_count / $total_leads) * 100) |
|
2367
|
|
- : 0; |
|
2368
|
|
- |
|
2369
|
|
- return [ |
|
2370
|
|
- 'total_leads' => $total_leads, |
|
2371
|
|
- 'new_this_week' => $new_this_week, |
|
2372
|
|
- 'avg_convos' => $avg, |
|
2373
|
|
- 'orphan_pct' => $orphan_pct, |
|
2374
|
|
- 'orphan_count' => $orphan_count, |
|
2375
|
|
- 'chat_deleted_count' => $chat_deleted_count, |
|
2376
|
|
- ]; |
|
2377
|
|
-} |
|
2378
|
|
- |
|
2379
|
|
-/** |
|
2380
|
|
- * Collect leads who had a conversation that an admin later deleted (preserved via |
|
2381
|
|
- * mxchat_lead_del_* options). Returns rows tagged status='chat_deleted' with the |
|
2382
|
|
- * original last-seen timestamp so they still sort and filter sensibly. |
|
2383
|
|
- * |
|
2384
|
|
- * @param string $search Optional email/name substring filter. |
|
2385
|
|
- * @param string $date_cutoff Optional 'Y-m-d H:i:s' cutoff — only rows with last_ts >= cutoff. |
|
2386
|
|
- * @return array |
|
2387
|
|
- */ |
|
2388
|
|
-private static function mxchat_collect_chat_deleted_leads($search = '', $date_cutoff = '') { |
|
2389
|
|
- global $wpdb; |
|
2390
|
|
- $table = $wpdb->prefix . 'mxchat_chat_transcripts'; |
|
2391
|
|
- |
|
2392
|
|
- $rows = $wpdb->get_results( |
|
2393
|
|
- "SELECT option_name, option_value FROM {$wpdb->options} |
|
2394
|
|
- WHERE option_name LIKE 'mxchat_lead_del_email_%'" |
|
2395
|
|
- ); |
|
2396
|
|
- if (empty($rows)) { |
|
2397
|
|
- return []; |
|
2398
|
|
- } |
|
2399
|
|
- |
|
2400
|
|
- // Emails that currently have transcripts rows should not appear as chat_deleted — |
|
2401
|
|
- // they've come back and chatted, so they're active leads again. |
|
2402
|
|
- $emails_in_transcripts = array_map( |
|
2403
|
|
- 'strtolower', |
|
2404
|
|
- (array) $wpdb->get_col( |
|
2405
|
|
- "SELECT DISTINCT user_email FROM {$table} |
|
2406
|
|
- WHERE user_email IS NOT NULL AND user_email != ''" |
|
2407
|
|
- ) |
|
2408
|
|
- ); |
|
2409
|
|
- $emails_in_transcripts = array_flip($emails_in_transcripts); |
|
2410
|
|
- |
|
2411
|
|
- $needle = strtolower(trim((string) $search)); |
|
2412
|
|
- $by_email = []; |
|
2413
|
|
- |
|
2414
|
|
- foreach ($rows as $opt) { |
|
2415
|
|
- $email = sanitize_email(trim((string) $opt->option_value)); |
|
2416
|
|
- if (!$email) { |
|
2417
|
|
- continue; |
|
2418
|
|
- } |
|
2419
|
|
- if (isset($emails_in_transcripts[strtolower($email)])) { |
|
2420
|
|
- continue; |
|
2421
|
|
- } |
|
2422
|
|
- $sid = substr($opt->option_name, strlen('mxchat_lead_del_email_')); |
|
2423
|
|
- if (!$sid) { |
|
2424
|
|
- continue; |
|
2425
|
|
- } |
|
2426
|
|
- $name = (string) get_option('mxchat_lead_del_name_' . $sid, ''); |
|
2427
|
|
- $ts = (string) get_option('mxchat_lead_del_ts_' . $sid, ''); |
|
2428
|
|
- |
|
2429
|
|
- if ($date_cutoff !== '' && ($ts === '' || $ts < $date_cutoff)) { |
|
2430
|
|
- continue; |
|
2431
|
|
- } |
|
2432
|
|
- if ($needle !== '') { |
|
2433
|
|
- $hay = strtolower($email . ' ' . $name); |
|
2434
|
|
- if (strpos($hay, $needle) === false) { |
|
2435
|
|
- continue; |
|
2436
|
|
- } |
|
2437
|
|
- } |
|
2438
|
|
- |
|
2439
|
|
- $key = strtolower($email); |
|
2440
|
|
- if (!isset($by_email[$key]) || (isset($by_email[$key]['last_seen']) && $ts > $by_email[$key]['last_seen'])) { |
|
2441
|
|
- $by_email[$key] = [ |
|
2442
|
|
- 'email' => $email, |
|
2443
|
|
- 'name' => $name, |
|
2444
|
|
- 'conversation_count' => 0, |
|
2445
|
|
- 'last_seen' => $ts, |
|
2446
|
|
- 'last_seen_display' => $ts ? self::mxchat_leads_format_relative($ts) : __('Chat deleted', 'mxchat'), |
|
2447
|
|
- 'first_seen' => $ts, |
|
2448
|
|
- 'latest_session_id' => '', |
|
2449
|
|
- 'top_page_url' => '', |
|
2450
|
|
- 'top_page_title' => '', |
|
2451
|
|
- 'is_orphan' => false, |
|
2452
|
|
- 'status' => 'chat_deleted', |
|
2453
|
|
- ]; |
|
2454
|
|
- } |
|
2455
|
|
- } |
|
2456
|
|
- |
|
2457
|
|
- // Newest chat_deleted first. |
|
2458
|
|
- usort($by_email, function ($a, $b) { |
|
2459
|
|
- return strcmp((string) $b['last_seen'], (string) $a['last_seen']); |
|
2460
|
|
- }); |
|
2461
|
|
- return array_values($by_email); |
|
2462
|
|
-} |
|
2463
|
|
- |
|
2464
|
|
-/** |
|
2465
|
|
- * Count unique emails preserved as "chat deleted" (for the stats strip). |
|
2466
|
|
- */ |
|
2467
|
|
-private static function mxchat_count_chat_deleted_leads() { |
|
2468
|
|
- global $wpdb; |
|
2469
|
|
- $table = $wpdb->prefix . 'mxchat_chat_transcripts'; |
|
2470
|
|
- |
|
2471
|
|
- $emails = $wpdb->get_col( |
|
2472
|
|
- "SELECT DISTINCT option_value FROM {$wpdb->options} |
|
2473
|
|
- WHERE option_name LIKE 'mxchat_lead_del_email_%'" |
|
2474
|
|
- ); |
|
2475
|
|
- if (empty($emails)) { |
|
2476
|
|
- return 0; |
|
2477
|
|
- } |
|
2478
|
|
- |
|
2479
|
|
- $transcripts_emails = array_map( |
|
2480
|
|
- 'strtolower', |
|
2481
|
|
- (array) $wpdb->get_col( |
|
2482
|
|
- "SELECT DISTINCT user_email FROM {$table} |
|
2483
|
|
- WHERE user_email IS NOT NULL AND user_email != ''" |
|
2484
|
|
- ) |
|
2485
|
|
- ); |
|
2486
|
|
- $transcripts_emails = array_flip($transcripts_emails); |
|
2487
|
|
- |
|
2488
|
|
- $count = 0; |
|
2489
|
|
- $seen = []; |
|
2490
|
|
- foreach ($emails as $raw) { |
|
2491
|
|
- $email = strtolower(trim((string) $raw)); |
|
2492
|
|
- if (!$email || isset($seen[$email]) || isset($transcripts_emails[$email])) { |
|
2493
|
|
- continue; |
|
2494
|
|
- } |
|
2495
|
|
- $seen[$email] = true; |
|
2496
|
|
- $count++; |
|
2497
|
|
- } |
|
2498
|
|
- return $count; |
|
2499
|
|
-} |
|
2500
|
|
- |
|
2501
|
|
-/** |
|
2502
|
|
- * Find leads who submitted the pre-chat form but never produced a transcripts row. |
|
2503
|
|
- * Returned rows have no conversation_count, no timestamp. |
|
2504
|
|
- * |
|
2505
|
|
- * @param string $search Optional email/name substring filter. |
|
2506
|
|
- * @return array |
|
2507
|
|
- */ |
|
2508
|
|
-private static function mxchat_collect_orphan_leads($search = '') { |
|
2509
|
|
- global $wpdb; |
|
2510
|
|
- $table = $wpdb->prefix . 'mxchat_chat_transcripts'; |
|
2511
|
|
- |
|
2512
|
|
- $option_rows = $wpdb->get_results( |
|
2513
|
|
- "SELECT option_name, option_value FROM {$wpdb->options} |
|
2514
|
|
- WHERE option_name LIKE 'mxchat_email_%'" |
|
2515
|
|
- ); |
|
2516
|
|
- if (empty($option_rows)) { |
|
2517
|
|
- return []; |
|
2518
|
|
- } |
|
2519
|
|
- |
|
2520
|
|
- // Collect all session_ids that have real transcripts rows so we can exclude them. |
|
2521
|
|
- $session_ids_with_rows = $wpdb->get_col( |
|
2522
|
|
- "SELECT DISTINCT session_id FROM {$table} |
|
2523
|
|
- WHERE user_email IS NOT NULL AND user_email != ''" |
|
2524
|
|
- ); |
|
2525
|
|
- $session_ids_with_rows = array_flip($session_ids_with_rows); |
|
2526
|
|
- |
|
2527
|
|
- // Seen emails in transcripts (so orphans only include truly never-chatted leads). |
|
2528
|
|
- $emails_in_transcripts = array_map( |
|
2529
|
|
- 'strtolower', |
|
2530
|
|
- (array) $wpdb->get_col( |
|
2531
|
|
- "SELECT DISTINCT user_email FROM {$table} |
|
2532
|
|
- WHERE user_email IS NOT NULL AND user_email != ''" |
|
2533
|
|
- ) |
|
2534
|
|
- ); |
|
2535
|
|
- $emails_in_transcripts = array_flip($emails_in_transcripts); |
|
2536
|
|
- |
|
2537
|
|
- $orphans_by_email = []; |
|
2538
|
|
- $needle = strtolower(trim((string) $search)); |
|
2539
|
|
- |
|
2540
|
|
- foreach ($option_rows as $opt) { |
|
2541
|
|
- $email = sanitize_email(trim((string) $opt->option_value)); |
|
2542
|
|
- if (!$email) { |
|
2543
|
|
- continue; |
|
2544
|
|
- } |
|
2545
|
|
- $sid = substr($opt->option_name, strlen('mxchat_email_')); |
|
2546
|
|
- if (!$sid) { |
|
2547
|
|
- continue; |
|
2548
|
|
- } |
|
2549
|
|
- // Exclude leads who have any transcripts rows (they appear in the main list). |
|
2550
|
|
- if (isset($emails_in_transcripts[strtolower($email)])) { |
|
2551
|
|
- continue; |
|
2552
|
|
- } |
|
2553
|
|
- if (isset($session_ids_with_rows[$sid])) { |
|
2554
|
|
- continue; |
|
2555
|
|
- } |
|
2556
|
|
- |
|
2557
|
|
- $name_option = get_option('mxchat_name_' . $sid, ''); |
|
2558
|
|
- $name = is_string($name_option) ? trim($name_option) : ''; |
|
2559
|
|
- |
|
2560
|
|
- if ($needle !== '') { |
|
2561
|
|
- $hay = strtolower($email . ' ' . $name); |
|
2562
|
|
- if (strpos($hay, $needle) === false) { |
|
2563
|
|
- continue; |
|
2564
|
|
- } |
|
2565
|
|
- } |
|
2566
|
|
- |
|
2567
|
|
- $key = strtolower($email); |
|
2568
|
|
- if (!isset($orphans_by_email[$key])) { |
|
2569
|
|
- $orphans_by_email[$key] = [ |
|
2570
|
|
- 'email' => $email, |
|
2571
|
|
- 'name' => $name, |
|
2572
|
|
- 'conversation_count' => 0, |
|
2573
|
|
- 'last_seen' => '', |
|
2574
|
|
- 'last_seen_display' => __('No conversation yet', 'mxchat'), |
|
2575
|
|
- 'first_seen' => '', |
|
2576
|
|
- 'latest_session_id' => '', |
|
2577
|
|
- 'top_page_url' => '', |
|
2578
|
|
- 'top_page_title' => '', |
|
2579
|
|
- 'is_orphan' => true, |
|
2580
|
|
- 'status' => 'orphan', |
|
2581
|
|
- ]; |
|
2582
|
|
- } |
|
2583
|
|
- } |
|
2584
|
|
- |
|
2585
|
|
- return array_values($orphans_by_email); |
|
2586
|
|
-} |
|
2587
|
|
- |
|
2588
|
|
-/** |
|
2589
|
|
- * Map a date_range key to a SQL-comparable cutoff string, or '' for all-time. |
|
2590
|
|
- */ |
|
2591
|
|
-private static function mxchat_leads_date_cutoff($date_range) { |
|
2592
|
|
- switch ($date_range) { |
|
2593
|
|
- case 'today': return gmdate('Y-m-d H:i:s', strtotime('-24 hours')); |
|
2594
|
|
- case '7d': return gmdate('Y-m-d H:i:s', strtotime('-7 days')); |
|
2595
|
|
- case '30d': return gmdate('Y-m-d H:i:s', strtotime('-30 days')); |
|
2596
|
|
- case '90d': return gmdate('Y-m-d H:i:s', strtotime('-90 days')); |
|
2597
|
|
- case 'all': |
|
2598
|
|
- default: return ''; |
|
2599
|
|
- } |
|
2600
|
|
-} |
|
2601
|
|
- |
|
2602
|
|
-/** |
|
2603
|
|
- * Turn a UTC timestamp into a short relative display like "2h ago" or "Apr 12". |
|
2604
|
|
- */ |
|
2605
|
|
-private static function mxchat_leads_format_relative($timestamp) { |
|
2606
|
|
- if (!$timestamp) { |
|
2607
|
|
- return ''; |
|
2608
|
|
- } |
|
2609
|
|
- $ts = strtotime($timestamp . ' UTC'); |
|
2610
|
|
- if (!$ts) { |
|
2611
|
|
- return ''; |
|
2612
|
|
- } |
|
2613
|
|
- $diff = time() - $ts; |
|
2614
|
|
- if ($diff < 60) return __('just now', 'mxchat'); |
|
2615
|
|
- if ($diff < 3600) return floor($diff / 60) . __('m ago', 'mxchat'); |
|
2616
|
|
- if ($diff < 86400) return floor($diff / 3600) . __('h ago', 'mxchat'); |
|
2617
|
|
- if ($diff < 604800) return floor($diff / 86400) . __('d ago', 'mxchat'); |
|
2618
|
|
- return wp_date('M j', $ts); |
|
2619
|
|
-} |
|
2620
|
|
- |
|
2621
|
|
-/** |
|
2622
|
1509
|
* Handle translation of chat messages via AJAX |
|
2623
|
1510
|
*/ |
|
2624
|
1511
|
public function mxchat_translate_messages() { |
|
2625
|
1512
|
if (!current_user_can('manage_options')) { |
|
@@ -2902,12 +1789,8 @@ |
|
2902
|
1789
|
/** |
|
2903
|
1790
|
* Translate text using Claude API |
|
2904
|
1791
|
*/ |
|
2905
|
1792
|
private function translate_with_claude($api_key, $model, $system_prompt, $text) { |
|
2906
|
|
- // Anthropic retired claude-opus-4-20250514 / claude-sonnet-4-20250514 on 2026-06-15. |
|
2907
|
|
- // Read-time rescue: remap a saved dead ID to the current equivalent before the API call. |
|
2908
|
|
- if ($model === 'claude-opus-4-20250514') { $model = 'claude-opus-4-8'; } |
|
2909
|
|
- elseif ($model === 'claude-sonnet-4-20250514') { $model = 'claude-sonnet-4-6'; } |
|
2910
|
1793
|
$response = wp_remote_post('https://api.anthropic.com/v1/messages', [ |
|
2911
|
1794
|
'timeout' => 60, |
|
2912
|
1795
|
'headers' => [ |
|
2913
|
1796
|
'x-api-key' => $api_key, |
|
@@ -3018,11 +1901,8 @@ |
|
3018
|
1901
|
/** |
|
3019
|
1902
|
* Translate text using Google Gemini API |
|
3020
|
1903
|
*/ |
|
3021
|
1904
|
private function translate_with_gemini($api_key, $model, $system_prompt, $text) { |
|
3022
|
|
- if ($model === 'gemini-3-pro-preview') { |
|
3023
|
|
- $model = 'gemini-3.1-pro-preview'; |
|
3024
|
|
- } |
|
3025
|
1905
|
$url = 'https://generativelanguage.googleapis.com/v1beta/models/' . $model . ':generateContent?key=' . $api_key; |
|
3026
|
1906
|
|
|
3027
|
1907
|
$response = wp_remote_post($url, [ |
|
3028
|
1908
|
'timeout' => 60, |
|
@@ -3112,20 +1992,9 @@ |
|
3112
|
1992
|
$page = isset($_POST['page']) ? absint($_POST['page']) : 1; |
|
3113
|
1993
|
$per_page = isset($_POST['per_page']) ? absint($_POST['per_page']) : 50; |
|
3114
|
1994
|
$offset = ($page - 1) * $per_page; |
|
3115
|
1995
|
$search = isset($_POST['search']) ? sanitize_text_field($_POST['search']) : ''; |
|
3116
|
|
- $sort_raw = isset($_POST['sort_order']) ? sanitize_key($_POST['sort_order']) : 'desc'; |
|
3117
|
|
- $allowed_sorts = array('asc', 'desc', 'rating_positive', 'rating_negative'); |
|
3118
|
|
- if (!in_array($sort_raw, $allowed_sorts, true)) { $sort_raw = 'desc'; } |
|
3119
|
|
- $sort_order = ($sort_raw === 'asc') ? 'ASC' : 'DESC'; |
|
3120
|
|
- $ratings_table = $wpdb->prefix . 'mxchat_session_ratings'; |
|
3121
|
|
- $ratings_join = ''; |
|
3122
|
|
- $rating_table_exists = ($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $ratings_table)) === $ratings_table); |
|
3123
|
|
- if ($rating_table_exists && ($sort_raw === 'rating_positive' || $sort_raw === 'rating_negative')) { |
|
3124
|
|
- // We sort by rating then recency. Wrap rating_value so NULL sorts last. |
|
3125
|
|
- $rating_dir = ($sort_raw === 'rating_positive') ? 'DESC' : 'ASC'; |
|
3126
|
|
- $ratings_join = " LEFT JOIN {$ratings_table} r ON r.session_id = t.session_id "; |
|
3127
|
|
- } |
|
1996
|
+ $sort_order = isset($_POST['sort_order']) && $_POST['sort_order'] === 'asc' ? 'ASC' : 'DESC'; |
|
3128
|
1997
|
|
|
3129
|
1998
|
// Build search condition |
|
3130
|
1999
|
$search_condition = ''; |
|
3131
|
2000
|
$search_params = []; |
|
@@ -3145,24 +2014,18 @@ |
|
3145
|
2014
|
? $wpdb->prepare("SELECT COUNT(DISTINCT session_id) FROM {$table_name} {$search_condition}", $search_params) |
|
3146
|
2015
|
: "SELECT COUNT(DISTINCT session_id) FROM {$table_name}"; |
|
3147
|
2016
|
$total_sessions = (int) $wpdb->get_var($count_query); |
|
3148
|
2017
|
|
|
3149
|
|
- // Get session IDs for current page. Default sort is recency; rating sorts join the ratings table |
|
3150
|
|
- // and order by rating value (NULLs last) with recency as tiebreaker. |
|
3151
|
|
- if ($ratings_join) { |
|
3152
|
|
- $order_by = "ORDER BY (r.rating_value IS NULL), r.rating_value {$rating_dir}, MAX(t.timestamp) DESC"; |
|
3153
|
|
- } else { |
|
3154
|
|
- $order_by = "ORDER BY MAX(t.timestamp) {$sort_order}"; |
|
3155
|
|
- } |
|
2018
|
+ // Get session IDs for current page (sorted by newest or oldest) |
|
3156
|
2019
|
$session_query = !empty($search) |
|
3157
|
2020
|
? $wpdb->prepare( |
|
3158
|
|
- "SELECT DISTINCT t.session_id FROM {$table_name} t {$ratings_join} {$search_condition} |
|
3159
|
|
- GROUP BY t.session_id {$order_by} LIMIT %d OFFSET %d", |
|
2021
|
+ "SELECT DISTINCT t.session_id FROM {$table_name} t {$search_condition} |
|
2022
|
+ GROUP BY t.session_id ORDER BY MAX(t.timestamp) {$sort_order} LIMIT %d OFFSET %d", |
|
3160
|
2023
|
array_merge($search_params, [$per_page, $offset]) |
|
3161
|
2024
|
) |
|
3162
|
2025
|
: $wpdb->prepare( |
|
3163
|
|
- "SELECT DISTINCT t.session_id FROM {$table_name} t {$ratings_join} |
|
3164
|
|
- GROUP BY t.session_id {$order_by} LIMIT %d OFFSET %d", |
|
2026
|
+ "SELECT DISTINCT session_id FROM {$table_name} |
|
2027
|
+ GROUP BY session_id ORDER BY MAX(timestamp) {$sort_order} LIMIT %d OFFSET %d", |
|
3165
|
2028
|
$per_page, $offset |
|
3166
|
2029
|
); |
|
3167
|
2030
|
$session_ids = $wpdb->get_col($session_query); |
|
3168
|
2031
|
|
|
@@ -3184,24 +2047,8 @@ |
|
3184
|
2047
|
// Check for optional columns/tables |
|
3185
|
2048
|
$url_table_exists = $wpdb->get_var("SHOW TABLES LIKE '$url_clicks_table'") === $url_clicks_table; |
|
3186
|
2049
|
$originating_columns_exist = !empty($wpdb->get_results("SHOW COLUMNS FROM $table_name LIKE 'originating_page_url'")); |
|
3187
|
2050
|
|
|
3188
|
|
- // Batch-fetch session ratings for this page (plan-a5b006). |
|
3189
|
|
- $ratings_map = array(); |
|
3190
|
|
- if ($rating_table_exists && !empty($session_ids)) { |
|
3191
|
|
- $placeholders = implode(',', array_fill(0, count($session_ids), '%s')); |
|
3192
|
|
- $rating_rows = $wpdb->get_results($wpdb->prepare( |
|
3193
|
|
- "SELECT session_id, rating_value, rating_feedback FROM {$ratings_table} WHERE session_id IN ($placeholders)", |
|
3194
|
|
- $session_ids |
|
3195
|
|
- )); |
|
3196
|
|
- foreach ($rating_rows as $row) { |
|
3197
|
|
- $ratings_map[$row->session_id] = array( |
|
3198
|
|
- 'value' => (int) $row->rating_value, |
|
3199
|
|
- 'feedback' => (string) $row->rating_feedback, |
|
3200
|
|
- ); |
|
3201
|
|
- } |
|
3202
|
|
- } |
|
3203
|
|
- |
|
3204
|
2051
|
// Build session list data |
|
3205
|
2052
|
$sessions = []; |
|
3206
|
2053
|
foreach ($session_ids as $session_id) { |
|
3207
|
2054
|
// Get session metadata |
|
@@ -3258,12 +2105,8 @@ |
|
3258
|
2105
|
$parts = explode(' ', $display_name); |
|
3259
|
2106
|
$initials = strtoupper(substr($parts[0], 0, 1) . substr(end($parts), 0, 1)); |
|
3260
|
2107
|
} |
|
3261
|
2108
|
|
|
3262
|
|
- $rating_entry = isset($ratings_map[$session_id]) ? $ratings_map[$session_id] : null; |
|
3263
|
|
- $rating_value = $rating_entry ? $rating_entry['value'] : null; |
|
3264
|
|
- $rating_feedback = $rating_entry ? $rating_entry['feedback'] : ''; |
|
3265
|
|
- |
|
3266
|
2109
|
$sessions[] = [ |
|
3267
|
2110
|
'session_id' => $session_id, |
|
3268
|
2111
|
'display_name' => $display_name, |
|
3269
|
2112
|
'display_sub' => $display_sub, |
|
@@ -3270,11 +2113,9 @@ |
|
3270
|
2113
|
'initials' => $initials, |
|
3271
|
2114
|
'preview' => $preview, |
|
3272
|
2115
|
'message_count' => (int) $message_stats->count, |
|
3273
|
2116
|
'time_display' => $time_display, |
|
3274
|
|
- 'timestamp' => $message_stats->latest, |
|
3275
|
|
- 'rating_value' => $rating_value, |
|
3276
|
|
- 'rating_feedback' => $rating_feedback, |
|
2117
|
+ 'timestamp' => $message_stats->latest |
|
3277
|
2118
|
]; |
|
3278
|
2119
|
} |
|
3279
|
2120
|
|
|
3280
|
2121
|
wp_send_json([ |
|
@@ -3412,18 +2253,8 @@ |
|
3412
|
2253
|
|
|
3413
|
2254
|
// First message timestamp for "started" display |
|
3414
|
2255
|
$started = !empty($messages) ? wp_date('M j, Y g:i A', strtotime($messages[0]->timestamp . ' UTC')) : '-'; |
|
3415
|
2256
|
|
|
3416
|
|
- // Pull rating_feedback for this session (if any) so the details drawer can show it. |
|
3417
|
|
- $rating_feedback = ''; |
|
3418
|
|
- $ratings_table_det = $wpdb->prefix . 'mxchat_session_ratings'; |
|
3419
|
|
- if ($wpdb->get_var("SHOW TABLES LIKE '$ratings_table_det'") === $ratings_table_det) { |
|
3420
|
|
- $rating_feedback = (string) $wpdb->get_var($wpdb->prepare( |
|
3421
|
|
- "SELECT rating_feedback FROM {$ratings_table_det} WHERE session_id = %s LIMIT 1", |
|
3422
|
|
- $session_id |
|
3423
|
|
- )); |
|
3424
|
|
- } |
|
3425
|
|
- |
|
3426
|
2257
|
wp_send_json([ |
|
3427
|
2258
|
'success' => true, |
|
3428
|
2259
|
'session_id' => $session_id, |
|
3429
|
2260
|
'user' => [ |
|
@@ -3439,10 +2270,9 @@ |
|
3439
|
2270
|
], |
|
3440
|
2271
|
'clicked_urls' => $clicked_urls, |
|
3441
|
2272
|
'messages' => $formatted_messages, |
|
3442
|
2273
|
'message_count' => count($messages), |
|
3443
|
|
- 'started' => $started, |
|
3444
|
|
- 'rating_feedback' => $rating_feedback |
|
2274
|
+ 'started' => $started |
|
3445
|
2275
|
]); |
|
3446
|
2276
|
wp_die(); |
|
3447
|
2277
|
} |
|
3448
|
2278
|
|
|
@@ -3895,103 +2725,53 @@ |
|
3895
|
2725
|
echo wp_json_encode(['error' => esc_html__('You do not have sufficient permissions.', 'mxchat')]); |
|
3896
|
2726
|
wp_die(); |
|
3897
|
2727
|
} |
|
3898
|
2728
|
check_ajax_referer('mxchat_delete_chat_history', 'security'); |
|
3899
|
|
- |
|
3900
|
|
- if (!isset($_POST['delete_session_ids']) || !is_array($_POST['delete_session_ids'])) { |
|
3901
|
|
- echo wp_json_encode(['error' => esc_html__('No chat sessions selected for deletion.', 'mxchat')]); |
|
3902
|
|
- wp_die(); |
|
3903
|
|
- } |
|
3904
|
|
- |
|
3905
|
2729
|
global $wpdb; |
|
3906
|
2730
|
$table_name = $wpdb->prefix . 'mxchat_chat_transcripts'; |
|
3907
|
|
- $translations_table = $wpdb->prefix . 'mxchat_transcript_translations'; |
|
3908
|
|
- $has_translations = $wpdb->get_var("SHOW TABLES LIKE '$translations_table'") === $translations_table; |
|
3909
|
2731
|
|
|
3910
|
|
- // When true, any lead attached to these sessions is fully wiped (all their sessions, |
|
3911
|
|
- // across the whole table). Default false: the chat rows go away but the lead is |
|
3912
|
|
- // preserved as a separate "chat deleted" lead in the Leads tab. |
|
3913
|
|
- $also_delete_lead = !empty($_POST['also_delete_lead']) && $_POST['also_delete_lead'] !== 'false'; |
|
2732
|
+ if (isset($_POST['delete_session_ids']) && is_array($_POST['delete_session_ids'])) { |
|
2733
|
+ $deleted_count = 0; |
|
2734
|
+ $translations_table = $wpdb->prefix . 'mxchat_transcript_translations'; |
|
3914
|
2735
|
|
|
3915
|
|
- $deleted_count = 0; |
|
3916
|
|
- $preserved_as_deleted_leads = 0; |
|
3917
|
|
- $emails_to_fully_wipe = []; |
|
2736
|
+ foreach ($_POST['delete_session_ids'] as $session_id) { |
|
2737
|
+ $session_id_sanitized = sanitize_text_field($session_id); |
|
3918
|
2738
|
|
|
3919
|
|
- foreach ((array) $_POST['delete_session_ids'] as $session_id) { |
|
3920
|
|
- $session_id_sanitized = sanitize_text_field($session_id); |
|
3921
|
|
- if ($session_id_sanitized === '') { |
|
3922
|
|
- continue; |
|
3923
|
|
- } |
|
2739
|
+ // Clear relevant cache before deletion |
|
2740
|
+ $cache_key = 'chat_session_' . $session_id_sanitized; |
|
2741
|
+ wp_cache_delete($cache_key, 'mxchat_chat_sessions'); |
|
3924
|
2742
|
|
|
3925
|
|
- // Capture the lead info attached to this session *before* we delete the rows. |
|
3926
|
|
- $lead_row = $wpdb->get_row($wpdb->prepare( |
|
3927
|
|
- "SELECT user_email, user_name, MAX(timestamp) AS last_ts |
|
3928
|
|
- FROM {$table_name} |
|
3929
|
|
- WHERE session_id = %s AND user_email IS NOT NULL AND user_email != '' |
|
3930
|
|
- GROUP BY user_email, user_name |
|
3931
|
|
- ORDER BY last_ts DESC LIMIT 1", |
|
3932
|
|
- $session_id_sanitized |
|
3933
|
|
- )); |
|
2743
|
+ // Perform the deletion from the database table |
|
2744
|
+ $wpdb->delete($table_name, ['session_id' => $session_id_sanitized]); |
|
3934
|
2745
|
|
|
3935
|
|
- wp_cache_delete('chat_session_' . $session_id_sanitized, 'mxchat_chat_sessions'); |
|
3936
|
|
- $wpdb->delete($table_name, ['session_id' => $session_id_sanitized]); |
|
2746
|
+ // Delete any saved translations for this session |
|
2747
|
+ if ($wpdb->get_var("SHOW TABLES LIKE '$translations_table'") === $translations_table) { |
|
2748
|
+ $wpdb->delete($translations_table, ['session_id' => $session_id_sanitized]); |
|
2749
|
+ } |
|
3937
|
2750
|
|
|
3938
|
|
- if ($has_translations) { |
|
3939
|
|
- $wpdb->delete($translations_table, ['session_id' => $session_id_sanitized]); |
|
3940
|
|
- } |
|
2751
|
+ // Delete the corresponding option entry from wp_options table |
|
2752
|
+ delete_option("mxchat_history_" . $session_id_sanitized); |
|
3941
|
2753
|
|
|
3942
|
|
- delete_option('mxchat_history_' . $session_id_sanitized); |
|
3943
|
|
- delete_option('mxchat_agent_name_' . $session_id_sanitized); |
|
2754
|
+ // Delete any associated metadata options |
|
2755
|
+ delete_option("mxchat_email_" . $session_id_sanitized); |
|
2756
|
+ delete_option("mxchat_agent_name_" . $session_id_sanitized); |
|
3944
|
2757
|
|
|
3945
|
|
- if ($lead_row && !empty($lead_row->user_email)) { |
|
3946
|
|
- if ($also_delete_lead) { |
|
3947
|
|
- // Full-wipe requested — queue the email so that all their sessions and |
|
3948
|
|
- // related options get swept below. Also clear this session's pre-chat |
|
3949
|
|
- // capture options (they're no longer meaningful). |
|
3950
|
|
- $emails_to_fully_wipe[strtolower($lead_row->user_email)] = $lead_row->user_email; |
|
3951
|
|
- delete_option('mxchat_email_' . $session_id_sanitized); |
|
3952
|
|
- delete_option('mxchat_name_' . $session_id_sanitized); |
|
3953
|
|
- } else { |
|
3954
|
|
- // Preserve the lead in a "Chat deleted" state via distinct option keys so |
|
3955
|
|
- // they stay out of the orphan bucket (orphan = pre-chat form dropoff). |
|
3956
|
|
- update_option('mxchat_lead_del_email_' . $session_id_sanitized, $lead_row->user_email, false); |
|
3957
|
|
- if (!empty($lead_row->user_name)) { |
|
3958
|
|
- update_option('mxchat_lead_del_name_' . $session_id_sanitized, $lead_row->user_name, false); |
|
3959
|
|
- } |
|
3960
|
|
- if (!empty($lead_row->last_ts)) { |
|
3961
|
|
- update_option('mxchat_lead_del_ts_' . $session_id_sanitized, $lead_row->last_ts, false); |
|
3962
|
|
- } |
|
3963
|
|
- // Clean up pre-chat capture options for this session — chat_deleted supersedes. |
|
3964
|
|
- delete_option('mxchat_email_' . $session_id_sanitized); |
|
3965
|
|
- delete_option('mxchat_name_' . $session_id_sanitized); |
|
3966
|
|
- $preserved_as_deleted_leads++; |
|
3967
|
|
- } |
|
3968
|
|
- } else { |
|
3969
|
|
- // No lead attached — nothing to preserve. Clean up any orphan options anyway. |
|
3970
|
|
- delete_option('mxchat_email_' . $session_id_sanitized); |
|
3971
|
|
- delete_option('mxchat_name_' . $session_id_sanitized); |
|
2758
|
+ $deleted_count++; |
|
3972
|
2759
|
} |
|
3973
|
2760
|
|
|
3974
|
|
- $deleted_count++; |
|
3975
|
|
- } |
|
2761
|
+ // Optionally, clear a general cache if you have one |
|
2762
|
+ wp_cache_delete('all_chat_sessions', 'mxchat_chat_sessions'); |
|
3976
|
2763
|
|
|
3977
|
|
- // Opt-in full-lead wipe: sweep every remaining row + every option key (including |
|
3978
|
|
- // chat_deleted preservation) for each affected email. Reuses the same internal |
|
3979
|
|
- // helper as the Leads-tab Delete button for consistency. |
|
3980
|
|
- if (!empty($emails_to_fully_wipe)) { |
|
3981
|
|
- self::mxchat_wipe_leads_by_email(array_values($emails_to_fully_wipe)); |
|
2764
|
+ echo wp_json_encode([ |
|
2765
|
+ 'success' => sprintf( |
|
2766
|
+ esc_html__('%d chat session(s) have been deleted from all storage locations.', 'mxchat'), |
|
2767
|
+ $deleted_count |
|
2768
|
+ ) |
|
2769
|
+ ]); |
|
2770
|
+ } else { |
|
2771
|
+ echo wp_json_encode(['error' => esc_html__('No chat sessions selected for deletion.', 'mxchat')]); |
|
3982
|
2772
|
} |
|
3983
|
2773
|
|
|
3984
|
|
- wp_cache_delete('all_chat_sessions', 'mxchat_chat_sessions'); |
|
3985
|
|
- |
|
3986
|
|
- echo wp_json_encode([ |
|
3987
|
|
- 'success' => sprintf( |
|
3988
|
|
- esc_html__('%d chat session(s) have been deleted.', 'mxchat'), |
|
3989
|
|
- $deleted_count |
|
3990
|
|
- ), |
|
3991
|
|
- 'preserved_as_deleted_leads' => $preserved_as_deleted_leads, |
|
3992
|
|
- 'leads_fully_wiped' => count($emails_to_fully_wipe), |
|
3993
|
|
- ]); |
|
3994
|
2774
|
wp_die(); |
|
3995
|
2775
|
} |
|
3996
|
2776
|
|
|
3997
|
2777
|
/** |
|
@@ -4247,58 +3027,8 @@ |
|
4247
|
3027
|
: $row->callback_function; |
|
4248
|
3028
|
$action_type_distribution[$label] = $row->count; |
|
4249
|
3029
|
} |
|
4250
|
3030
|
|
|
4251
|
|
- // Native function-calling (AI Tools) data — plan-mxchat-20260617-a41dee. |
|
4252
|
|
- // The AI Tools checklist reads from MxChat_Tool_Registry, the SAME single |
|
4253
|
|
- // source the chat-time function-calling loop reads, so the two never drift. |
|
4254
|
|
- if (!class_exists('MxChat_Tool_Registry')) { |
|
4255
|
|
- require_once plugin_dir_path(__FILE__) . 'class-mxchat-tool-registry.php'; |
|
4256
|
|
- } |
|
4257
|
|
- if (!class_exists('MxChat_Model_Catalog')) { |
|
4258
|
|
- require_once plugin_dir_path(__FILE__) . 'class-mxchat-model-catalog.php'; |
|
4259
|
|
- } |
|
4260
|
|
- $fc_options = get_option('mxchat_options', array()); |
|
4261
|
|
- $fc_current_model = isset($fc_options['model']) ? $fc_options['model'] : 'gpt-5.1-chat-latest'; |
|
4262
|
|
- $fc_model_capable = class_exists('MxChat_Model_Catalog') |
|
4263
|
|
- ? MxChat_Model_Catalog::supports_tools($fc_current_model) : true; |
|
4264
|
|
- $active_tab = (isset($_GET['tab']) && $_GET['tab'] === 'ai-tools') ? 'ai-tools' : 'dashboard'; |
|
4265
|
|
- |
|
4266
|
|
- // Enrich each AI Tool with the dashicon its callback already uses on the |
|
4267
|
|
- // Trigger Phrases "Choose what it does" grid, so the AI Tools cards/modal |
|
4268
|
|
- // share the same iconography (plan 8bbf98 part 4). View-layer only — the |
|
4269
|
|
- // registry's model-facing data is untouched. Default admin-generic. |
|
4270
|
|
- $fc_tools = MxChat_Tool_Registry::available_tools(); |
|
4271
|
|
- foreach ($fc_tools as &$fc_tool_ref) { |
|
4272
|
|
- $fc_cb_ref = $fc_tool_ref['callback']; |
|
4273
|
|
- $fc_tool_ref['icon'] = isset($available_callbacks[$fc_cb_ref]['icon']) |
|
4274
|
|
- ? $available_callbacks[$fc_cb_ref]['icon'] |
|
4275
|
|
- : 'admin-generic'; |
|
4276
|
|
- } |
|
4277
|
|
- unset($fc_tool_ref); |
|
4278
|
|
- |
|
4279
|
|
- // Count of ACTIVE tools — drives the AI Tools sidebar nav badge, mirroring |
|
4280
|
|
- // $total_actions for Trigger Phrases. A tool in the list = active (plan |
|
4281
|
|
- // d450a7), so the badge shows the same number the list pane shows (plan 5f7409). |
|
4282
|
|
- $total_tools = count(array_filter($fc_tools, function ($t) { |
|
4283
|
|
- return !empty($t['enabled']); |
|
4284
|
|
- })); |
|
4285
|
|
- |
|
4286
|
|
- // Brave-key dependency check (plan 183856): Web Search + Image Search run on |
|
4287
|
|
- // the Brave Search API. If either is enabled as a tool but no brave_api_key is |
|
4288
|
|
- // configured, surface a graceful "key not set" notice so the admin isn't met |
|
4289
|
|
- // with a silently no-firing tool. |
|
4290
|
|
- $fc_brave_missing = false; |
|
4291
|
|
- $brave_key = isset($fc_options['brave_api_key']) ? trim($fc_options['brave_api_key']) : ''; |
|
4292
|
|
- if ($brave_key === '') { |
|
4293
|
|
- foreach ($fc_tools as $fc_t) { |
|
4294
|
|
- if (!empty($fc_t['enabled']) && isset($fc_t['requires_key']) && $fc_t['requires_key'] === 'brave_api_key') { |
|
4295
|
|
- $fc_brave_missing = true; |
|
4296
|
|
- break; |
|
4297
|
|
- } |
|
4298
|
|
- } |
|
4299
|
|
- } |
|
4300
|
|
- |
|
4301
|
3031
|
// Prepare page data |
|
4302
|
3032
|
$page_data = array( |
|
4303
|
3033
|
'total_actions' => $total_actions, |
|
4304
|
3034
|
'enabled_actions' => $enabled_actions, |
|
@@ -4306,17 +3036,8 @@ |
|
4306
|
3036
|
'action_types_count' => $action_types_count, |
|
4307
|
3037
|
'action_type_distribution' => $action_type_distribution, |
|
4308
|
3038
|
'available_callbacks' => $available_callbacks, |
|
4309
|
3039
|
'callback_groups' => $callback_groups, |
|
4310
|
|
- // AI Tools section |
|
4311
|
|
- 'active_tab' => $active_tab, |
|
4312
|
|
- 'fc_enabled' => MxChat_Tool_Registry::is_enabled(), |
|
4313
|
|
- 'total_tools' => $total_tools, |
|
4314
|
|
- 'fc_tools' => $fc_tools, |
|
4315
|
|
- 'fc_current_model' => $fc_current_model, |
|
4316
|
|
- 'fc_model_capable' => $fc_model_capable, |
|
4317
|
|
- 'fc_brave_missing' => $fc_brave_missing, |
|
4318
|
|
- 'fc_saved' => isset($_GET['mxchat_fc_saved']), |
|
4319
|
3040
|
); |
|
4320
|
3041
|
|
|
4321
|
3042
|
// Include and render the new template |
|
4322
|
3043
|
require_once plugin_dir_path(__FILE__) . 'admin-actions-page.php'; |
|
@@ -4926,9 +3647,9 @@ |
|
4926
|
3647
|
'intent_label' => $intent_label, |
|
4927
|
3648
|
'phrases' => implode(', ', $phrases_array), |
|
4928
|
3649
|
'embedding_vector' => $serialized_vector, |
|
4929
|
3650
|
'similarity_threshold' => $similarity_threshold, |
|
4930
|
|
- 'enabled_bots' => $enabled_bots_json, // Include enabled_bots in update |
|
3651
|
+ 'enabled_bots' => $enabled_bots_json // Include enabled_bots in update |
|
4931
|
3652
|
), |
|
4932
|
3653
|
array('id' => $intent_id), |
|
4933
|
3654
|
array('%s', '%s', '%s', '%f', '%s'), // Format: string, string, string, float, string |
|
4934
|
3655
|
array('%d') // Where format: integer |
|
@@ -5045,9 +3766,9 @@ |
|
5045
|
3766
|
'phrases' => implode(', ', $phrases_array), |
|
5046
|
3767
|
'embedding_vector' => $serialized_vector, |
|
5047
|
3768
|
'callback_function' => $callback_function, |
|
5048
|
3769
|
'similarity_threshold' => $similarity_threshold, |
|
5049
|
|
- 'enabled_bots' => $enabled_bots_json, // NEW field |
|
3770
|
+ 'enabled_bots' => $enabled_bots_json // NEW field |
|
5050
|
3771
|
]); |
|
5051
|
3772
|
|
|
5052
|
3773
|
if ($result === false) { |
|
5053
|
3774
|
$this->handle_embedding_error(__('Database error: ', 'mxchat') . $wpdb->last_error); |
|
@@ -5130,25 +3851,8 @@ |
|
5130
|
3851
|
|
|
5131
|
3852
|
// Get available callbacks for labels/icons |
|
5132
|
3853
|
$available_callbacks = $this->mxchat_get_available_callbacks(); |
|
5133
|
3854
|
|
|
5134
|
|
- // Prefetch individual phrase counts for all fetched actions |
|
5135
|
|
- $phrases_table = $wpdb->prefix . 'mxchat_intent_phrases'; |
|
5136
|
|
- $phrase_counts = array(); |
|
5137
|
|
- if ($wpdb->get_var("SHOW TABLES LIKE '$phrases_table'") === $phrases_table) { |
|
5138
|
|
- $action_ids = wp_list_pluck($actions, 'id'); |
|
5139
|
|
- if (!empty($action_ids)) { |
|
5140
|
|
- $id_placeholders = implode(',', array_fill(0, count($action_ids), '%d')); |
|
5141
|
|
- $count_results = $wpdb->get_results($wpdb->prepare( |
|
5142
|
|
- "SELECT intent_id, COUNT(*) as cnt FROM $phrases_table WHERE intent_id IN ($id_placeholders) GROUP BY intent_id", |
|
5143
|
|
- $action_ids |
|
5144
|
|
- )); |
|
5145
|
|
- foreach ($count_results as $row) { |
|
5146
|
|
- $phrase_counts[$row->intent_id] = intval($row->cnt); |
|
5147
|
|
- } |
|
5148
|
|
- } |
|
5149
|
|
- } |
|
5150
|
|
- |
|
5151
|
3855
|
// Format actions for response |
|
5152
|
3856
|
$formatted_actions = array(); |
|
5153
|
3857
|
foreach ($actions as $action) { |
|
5154
|
3858
|
$callback_data = isset($available_callbacks[$action->callback_function]) |
|
@@ -5169,10 +3873,8 @@ |
|
5169
|
3873
|
'icon' => isset($callback_data['icon']) ? $callback_data['icon'] : 'admin-generic', |
|
5170
|
3874
|
'threshold' => round($action->similarity_threshold * 100), |
|
5171
|
3875
|
'enabled' => (bool) $action->enabled, |
|
5172
|
3876
|
'enabled_bots' => $enabled_bots, |
|
5173
|
|
- 'has_legacy_vector' => !empty($action->embedding_vector), |
|
5174
|
|
- 'individual_phrase_count' => isset($phrase_counts[$action->id]) ? $phrase_counts[$action->id] : 0, |
|
5175
|
3877
|
); |
|
5176
|
3878
|
} |
|
5177
|
3879
|
|
|
5178
|
3880
|
$total_pages = ceil($total_actions / $per_page); |
|
@@ -5251,14 +3953,8 @@ |
|
5251
|
3953
|
if ($result === false) { |
|
5252
|
3954
|
wp_send_json_error(__('Failed to delete actions', 'mxchat')); |
|
5253
|
3955
|
} |
|
5254
|
3956
|
|
|
5255
|
|
- // Also delete individual phrases for these intents |
|
5256
|
|
- $phrases_table = $wpdb->prefix . 'mxchat_intent_phrases'; |
|
5257
|
|
- if ($wpdb->get_var("SHOW TABLES LIKE '$phrases_table'") === $phrases_table) { |
|
5258
|
|
- $wpdb->query($wpdb->prepare("DELETE FROM $phrases_table WHERE intent_id IN ($placeholders)", $action_ids)); |
|
5259
|
|
- } |
|
5260
|
|
- |
|
5261
|
3957
|
wp_send_json_success(array('deleted' => $result)); |
|
5262
|
3958
|
} |
|
5263
|
3959
|
|
|
5264
|
3960
|
/** |
|
@@ -5281,18 +3977,25 @@ |
|
5281
|
3977
|
$similarity_threshold = isset($_POST['similarity_threshold']) ? floatval($_POST['similarity_threshold']) / 100 : 0.85; |
|
5282
|
3978
|
$enabled_bots = isset($_POST['enabled_bots']) ? array_map('sanitize_text_field', (array) $_POST['enabled_bots']) : array('default'); |
|
5283
|
3979
|
|
|
5284
|
3980
|
// Validate |
|
5285
|
|
- // Check if using new individual phrases mode or legacy mode |
|
5286
|
|
- $individual_phrases = isset($_POST['individual_phrases']) ? array_filter(array_map('sanitize_text_field', (array) $_POST['individual_phrases'])) : array(); |
|
5287
|
|
- $use_individual = !empty($individual_phrases); |
|
3981
|
+ if (empty($intent_label) || empty($phrases_input) || empty($callback_function)) { |
|
3982
|
+ wp_send_json_error(__('Please fill in all required fields.', 'mxchat')); |
|
3983
|
+ } |
|
5288
|
3984
|
|
|
5289
|
|
- // Validate required fields (phrases not required when using individual mode) |
|
5290
|
|
- if (empty($intent_label) || empty($callback_function)) { |
|
5291
|
|
- wp_send_json_error(__('Please fill in all required fields.', 'mxchat')); |
|
3985
|
+ // Process phrases |
|
3986
|
+ $phrases_array = array_filter(array_map('trim', preg_split('/[\n,]+/', $phrases_input))); |
|
3987
|
+ if (empty($phrases_array)) { |
|
3988
|
+ wp_send_json_error(__('Please provide at least one trigger phrase.', 'mxchat')); |
|
5292
|
3989
|
} |
|
5293
|
|
- if (!$use_individual && empty($phrases_input)) { |
|
5294
|
|
- wp_send_json_error(__('Please fill in all required fields.', 'mxchat')); |
|
3990
|
+ |
|
3991
|
+ // Generate embedding (combine phrases into single string for embedding) |
|
3992
|
+ $embedding_vector = $this->mxchat_generate_embedding(implode(' ', $phrases_array)); |
|
3993
|
+ if (is_wp_error($embedding_vector)) { |
|
3994
|
+ // Fallback: store without embedding |
|
3995
|
+ $serialized_vector = null; |
|
3996
|
+ } else { |
|
3997
|
+ $serialized_vector = maybe_serialize($embedding_vector); |
|
5295
|
3998
|
} |
|
5296
|
3999
|
|
|
5297
|
4000
|
// Ensure default bot is included |
|
5298
|
4001
|
if (!in_array('default', $enabled_bots)) { |
|
@@ -5299,95 +4002,27 @@ |
|
5299
|
4002
|
$enabled_bots[] = 'default'; |
|
5300
|
4003
|
} |
|
5301
|
4004
|
$enabled_bots_json = json_encode($enabled_bots); |
|
5302
|
4005
|
|
|
5303
|
|
- if ($use_individual) { |
|
5304
|
|
- // New mode: individual phrases each get their own vector |
|
5305
|
|
- // Insert the intent row with empty legacy fields |
|
5306
|
|
- $result = $wpdb->insert( |
|
5307
|
|
- $table_name, |
|
5308
|
|
- array( |
|
5309
|
|
- 'intent_label' => $intent_label, |
|
5310
|
|
- 'phrases' => '', |
|
5311
|
|
- 'embedding_vector' => '', |
|
5312
|
|
- 'similarity_threshold' => $similarity_threshold, |
|
5313
|
|
- 'callback_function' => $callback_function, |
|
5314
|
|
- 'enabled' => 1, |
|
5315
|
|
- 'enabled_bots' => $enabled_bots_json, |
|
5316
|
|
- ) |
|
5317
|
|
- ); |
|
4006
|
+ // Insert |
|
4007
|
+ $result = $wpdb->insert( |
|
4008
|
+ $table_name, |
|
4009
|
+ array( |
|
4010
|
+ 'intent_label' => $intent_label, |
|
4011
|
+ 'phrases' => implode(', ', $phrases_array), |
|
4012
|
+ 'embedding_vector' => $serialized_vector, |
|
4013
|
+ 'similarity_threshold' => $similarity_threshold, |
|
4014
|
+ 'callback_function' => $callback_function, |
|
4015
|
+ 'enabled' => 1, |
|
4016
|
+ 'enabled_bots' => $enabled_bots_json, |
|
4017
|
+ ) |
|
4018
|
+ ); |
|
5318
|
4019
|
|
|
5319
|
|
- if ($result === false) { |
|
5320
|
|
- wp_send_json_error(__('Failed to add action to database.', 'mxchat')); |
|
5321
|
|
- } |
|
4020
|
+ if ($result === false) { |
|
4021
|
+ wp_send_json_error(__('Failed to add action to database.', 'mxchat')); |
|
4022
|
+ } |
|
5322
|
4023
|
|
|
5323
|
|
- $intent_id = $wpdb->insert_id; |
|
5324
|
|
- |
|
5325
|
|
- // Insert each phrase individually with its own embedding |
|
5326
|
|
- $phrases_table = $wpdb->prefix . 'mxchat_intent_phrases'; |
|
5327
|
|
- $failed_phrases = array(); |
|
5328
|
|
- foreach ($individual_phrases as $phrase) { |
|
5329
|
|
- $phrase = trim($phrase); |
|
5330
|
|
- if (empty($phrase)) continue; |
|
5331
|
|
- |
|
5332
|
|
- $embedding_vector = $this->mxchat_generate_embedding($phrase); |
|
5333
|
|
- if (is_wp_error($embedding_vector)) { |
|
5334
|
|
- $failed_phrases[] = $phrase; |
|
5335
|
|
- continue; |
|
5336
|
|
- } |
|
5337
|
|
- |
|
5338
|
|
- $wpdb->insert( |
|
5339
|
|
- $phrases_table, |
|
5340
|
|
- array( |
|
5341
|
|
- 'intent_id' => $intent_id, |
|
5342
|
|
- 'phrase' => $phrase, |
|
5343
|
|
- 'embedding_vector' => maybe_serialize($embedding_vector), |
|
5344
|
|
- ) |
|
5345
|
|
- ); |
|
5346
|
|
- } |
|
5347
|
|
- |
|
5348
|
|
- $response = array('id' => $intent_id); |
|
5349
|
|
- if (!empty($failed_phrases)) { |
|
5350
|
|
- $response['failed_phrases'] = $failed_phrases; |
|
5351
|
|
- } |
|
5352
|
|
- wp_send_json_success($response); |
|
5353
|
|
- |
|
5354
|
|
- } else { |
|
5355
|
|
- // Legacy mode: combine all phrases into one embedding (backwards compatible) |
|
5356
|
|
- $phrases_array = array_filter(array_map('trim', preg_split('/[\n,]+/', $phrases_input))); |
|
5357
|
|
- if (empty($phrases_array)) { |
|
5358
|
|
- wp_send_json_error(__('Please provide at least one trigger phrase.', 'mxchat')); |
|
5359
|
|
- } |
|
5360
|
|
- |
|
5361
|
|
- // Generate embedding (combine phrases into single string for embedding) |
|
5362
|
|
- $embedding_vector = $this->mxchat_generate_embedding(implode(' ', $phrases_array)); |
|
5363
|
|
- if (is_wp_error($embedding_vector)) { |
|
5364
|
|
- // Fallback: store without embedding |
|
5365
|
|
- $serialized_vector = null; |
|
5366
|
|
- } else { |
|
5367
|
|
- $serialized_vector = maybe_serialize($embedding_vector); |
|
5368
|
|
- } |
|
5369
|
|
- |
|
5370
|
|
- // Insert |
|
5371
|
|
- $result = $wpdb->insert( |
|
5372
|
|
- $table_name, |
|
5373
|
|
- array( |
|
5374
|
|
- 'intent_label' => $intent_label, |
|
5375
|
|
- 'phrases' => implode(', ', $phrases_array), |
|
5376
|
|
- 'embedding_vector' => $serialized_vector, |
|
5377
|
|
- 'similarity_threshold' => $similarity_threshold, |
|
5378
|
|
- 'callback_function' => $callback_function, |
|
5379
|
|
- 'enabled' => 1, |
|
5380
|
|
- 'enabled_bots' => $enabled_bots_json, |
|
5381
|
|
- ) |
|
5382
|
|
- ); |
|
5383
|
|
- |
|
5384
|
|
- if ($result === false) { |
|
5385
|
|
- wp_send_json_error(__('Failed to add action to database.', 'mxchat')); |
|
5386
|
|
- } |
|
5387
|
|
- |
|
5388
|
|
- wp_send_json_success(array('id' => $wpdb->insert_id)); |
|
5389
|
|
- } |
|
4024
|
+ wp_send_json_success(array('id' => $wpdb->insert_id)); |
|
5390
|
4025
|
} |
|
5391
|
4026
|
|
|
5392
|
4027
|
/** |
|
5393
|
4028
|
* AJAX handler to edit an existing intent/action |
|
@@ -5410,52 +4045,40 @@ |
|
5410
|
4045
|
$similarity_threshold = isset($_POST['similarity_threshold']) ? floatval($_POST['similarity_threshold']) / 100 : 0.85; |
|
5411
|
4046
|
$enabled_bots = isset($_POST['enabled_bots']) ? array_map('sanitize_text_field', (array) $_POST['enabled_bots']) : array('default'); |
|
5412
|
4047
|
|
|
5413
|
4048
|
// Validate |
|
5414
|
|
- if (!$intent_id || empty($intent_label)) { |
|
4049
|
+ if (!$intent_id || empty($intent_label) || empty($phrases_input)) { |
|
5415
|
4050
|
wp_send_json_error(__('Please fill in all required fields.', 'mxchat')); |
|
5416
|
4051
|
} |
|
5417
|
4052
|
|
|
5418
|
|
- // Check if phrases are managed individually (empty phrases_input means individual mode) |
|
5419
|
|
- $uses_individual_phrases = empty($phrases_input); |
|
4053
|
+ // Process phrases |
|
4054
|
+ $phrases_array = array_filter(array_map('trim', preg_split('/[\n,]+/', $phrases_input))); |
|
4055
|
+ if (empty($phrases_array)) { |
|
4056
|
+ wp_send_json_error(__('Please provide at least one trigger phrase.', 'mxchat')); |
|
4057
|
+ } |
|
5420
|
4058
|
|
|
5421
|
|
- if ($uses_individual_phrases) { |
|
5422
|
|
- // Individual phrase mode: only update non-phrase fields, skip embedding regeneration |
|
4059
|
+ // Generate new embedding (combine phrases into single string for embedding) |
|
4060
|
+ $embedding_vector = $this->mxchat_generate_embedding(implode(' ', $phrases_array)); |
|
4061
|
+ if (is_wp_error($embedding_vector)) { |
|
4062
|
+ // Keep existing embedding |
|
4063
|
+ $serialized_vector = null; |
|
5423
|
4064
|
$update_data = array( |
|
5424
|
4065
|
'intent_label' => $intent_label, |
|
4066
|
+ 'phrases' => implode(', ', $phrases_array), |
|
5425
|
4067
|
'similarity_threshold' => $similarity_threshold, |
|
5426
|
4068
|
'callback_function' => $callback_function, |
|
5427
|
4069
|
'enabled_bots' => json_encode($enabled_bots), |
|
5428
|
4070
|
); |
|
5429
|
4071
|
} else { |
|
5430
|
|
- // Legacy mode: process phrases and regenerate embedding (backwards compatible for add-ons) |
|
5431
|
|
- $phrases_array = array_filter(array_map('trim', preg_split('/[\n,]+/', $phrases_input))); |
|
5432
|
|
- if (empty($phrases_array)) { |
|
5433
|
|
- wp_send_json_error(__('Please provide at least one trigger phrase.', 'mxchat')); |
|
5434
|
|
- } |
|
5435
|
|
- |
|
5436
|
|
- // Generate new embedding (combine phrases into single string for embedding) |
|
5437
|
|
- $embedding_vector = $this->mxchat_generate_embedding(implode(' ', $phrases_array)); |
|
5438
|
|
- if (is_wp_error($embedding_vector)) { |
|
5439
|
|
- // Keep existing embedding |
|
5440
|
|
- $update_data = array( |
|
5441
|
|
- 'intent_label' => $intent_label, |
|
5442
|
|
- 'phrases' => implode(', ', $phrases_array), |
|
5443
|
|
- 'similarity_threshold' => $similarity_threshold, |
|
5444
|
|
- 'callback_function' => $callback_function, |
|
5445
|
|
- 'enabled_bots' => json_encode($enabled_bots), |
|
5446
|
|
- ); |
|
5447
|
|
- } else { |
|
5448
|
|
- $serialized_vector = maybe_serialize($embedding_vector); |
|
5449
|
|
- $update_data = array( |
|
5450
|
|
- 'intent_label' => $intent_label, |
|
5451
|
|
- 'phrases' => implode(', ', $phrases_array), |
|
5452
|
|
- 'embedding_vector' => $serialized_vector, |
|
5453
|
|
- 'similarity_threshold' => $similarity_threshold, |
|
5454
|
|
- 'callback_function' => $callback_function, |
|
5455
|
|
- 'enabled_bots' => json_encode($enabled_bots), |
|
5456
|
|
- ); |
|
5457
|
|
- } |
|
4072
|
+ $serialized_vector = maybe_serialize($embedding_vector); |
|
4073
|
+ $update_data = array( |
|
4074
|
+ 'intent_label' => $intent_label, |
|
4075
|
+ 'phrases' => implode(', ', $phrases_array), |
|
4076
|
+ 'embedding_vector' => $serialized_vector, |
|
4077
|
+ 'similarity_threshold' => $similarity_threshold, |
|
4078
|
+ 'callback_function' => $callback_function, |
|
4079
|
+ 'enabled_bots' => json_encode($enabled_bots), |
|
4080
|
+ ); |
|
5458
|
4081
|
} |
|
5459
|
4082
|
|
|
5460
|
4083
|
// Ensure default bot is included |
|
5461
|
4084
|
if (!in_array('default', $enabled_bots)) { |
|
@@ -5502,154 +4125,13 @@ |
|
5502
|
4125
|
if ($result === false) { |
|
5503
|
4126
|
wp_send_json_error(__('Failed to delete action', 'mxchat')); |
|
5504
|
4127
|
} |
|
5505
|
4128
|
|
|
5506
|
|
- // Also delete individual phrases for this intent |
|
5507
|
|
- $phrases_table = $wpdb->prefix . 'mxchat_intent_phrases'; |
|
5508
|
|
- if ($wpdb->get_var("SHOW TABLES LIKE '$phrases_table'") === $phrases_table) { |
|
5509
|
|
- $wpdb->delete($phrases_table, array('intent_id' => $intent_id), array('%d')); |
|
5510
|
|
- } |
|
5511
|
|
- |
|
5512
|
4129
|
wp_send_json_success(array('deleted' => true)); |
|
5513
|
4130
|
} |
|
5514
|
4131
|
|
|
5515
|
|
-/** |
|
5516
|
|
- * AJAX handler to add a single phrase with its own embedding to an intent |
|
5517
|
|
- */ |
|
5518
|
|
-public function mxchat_add_phrase_ajax() { |
|
5519
|
|
- check_ajax_referer('mxchat_add_phrase_nonce', 'security'); |
|
5520
|
4132
|
|
|
5521
|
|
- if (!current_user_can('manage_options')) { |
|
5522
|
|
- wp_send_json_error(__('Unauthorized', 'mxchat')); |
|
5523
|
|
- } |
|
5524
|
|
- |
|
5525
|
|
- $intent_id = isset($_POST['intent_id']) ? intval($_POST['intent_id']) : 0; |
|
5526
|
|
- $phrase = isset($_POST['phrase']) ? sanitize_text_field($_POST['phrase']) : ''; |
|
5527
|
|
- |
|
5528
|
|
- if (!$intent_id || empty($phrase)) { |
|
5529
|
|
- wp_send_json_error(__('Please provide an intent ID and phrase.', 'mxchat')); |
|
5530
|
|
- } |
|
5531
|
|
- |
|
5532
|
|
- // Verify the intent exists |
|
5533
|
|
- global $wpdb; |
|
5534
|
|
- $intents_table = $wpdb->prefix . 'mxchat_intents'; |
|
5535
|
|
- $intent = $wpdb->get_row($wpdb->prepare("SELECT id FROM $intents_table WHERE id = %d", $intent_id)); |
|
5536
|
|
- if (!$intent) { |
|
5537
|
|
- wp_send_json_error(__('Action not found.', 'mxchat')); |
|
5538
|
|
- } |
|
5539
|
|
- |
|
5540
|
|
- // Generate embedding for this single phrase |
|
5541
|
|
- $embedding_vector = $this->mxchat_generate_embedding($phrase); |
|
5542
|
|
- if (is_wp_error($embedding_vector)) { |
|
5543
|
|
- wp_send_json_error(__('Failed to generate embedding: ', 'mxchat') . $embedding_vector->get_error_message()); |
|
5544
|
|
- } |
|
5545
|
|
- |
|
5546
|
|
- $phrases_table = $wpdb->prefix . 'mxchat_intent_phrases'; |
|
5547
|
|
- $result = $wpdb->insert( |
|
5548
|
|
- $phrases_table, |
|
5549
|
|
- array( |
|
5550
|
|
- 'intent_id' => $intent_id, |
|
5551
|
|
- 'phrase' => $phrase, |
|
5552
|
|
- 'embedding_vector' => maybe_serialize($embedding_vector), |
|
5553
|
|
- ) |
|
5554
|
|
- ); |
|
5555
|
|
- |
|
5556
|
|
- if ($result === false) { |
|
5557
|
|
- wp_send_json_error(__('Failed to add phrase.', 'mxchat')); |
|
5558
|
|
- } |
|
5559
|
|
- |
|
5560
|
|
- wp_send_json_success(array('id' => $wpdb->insert_id, 'phrase' => $phrase)); |
|
5561
|
|
-} |
|
5562
|
|
- |
|
5563
|
4133
|
/** |
|
5564
|
|
- * AJAX handler to delete a single phrase from wp_mxchat_intent_phrases |
|
5565
|
|
- */ |
|
5566
|
|
-public function mxchat_delete_phrase_ajax() { |
|
5567
|
|
- check_ajax_referer('mxchat_delete_phrase_nonce', 'security'); |
|
5568
|
|
- |
|
5569
|
|
- if (!current_user_can('manage_options')) { |
|
5570
|
|
- wp_send_json_error(__('Unauthorized', 'mxchat')); |
|
5571
|
|
- } |
|
5572
|
|
- |
|
5573
|
|
- $phrase_id = isset($_POST['phrase_id']) ? intval($_POST['phrase_id']) : 0; |
|
5574
|
|
- if (!$phrase_id) { |
|
5575
|
|
- wp_send_json_error(__('Invalid phrase ID.', 'mxchat')); |
|
5576
|
|
- } |
|
5577
|
|
- |
|
5578
|
|
- global $wpdb; |
|
5579
|
|
- $phrases_table = $wpdb->prefix . 'mxchat_intent_phrases'; |
|
5580
|
|
- $result = $wpdb->delete($phrases_table, array('id' => $phrase_id), array('%d')); |
|
5581
|
|
- |
|
5582
|
|
- if ($result === false) { |
|
5583
|
|
- wp_send_json_error(__('Failed to delete phrase.', 'mxchat')); |
|
5584
|
|
- } |
|
5585
|
|
- |
|
5586
|
|
- wp_send_json_success(array('deleted' => true)); |
|
5587
|
|
-} |
|
5588
|
|
- |
|
5589
|
|
-/** |
|
5590
|
|
- * AJAX handler to fetch individual phrases for an intent |
|
5591
|
|
- */ |
|
5592
|
|
-public function mxchat_get_phrases_ajax() { |
|
5593
|
|
- check_ajax_referer('mxchat_get_phrases_nonce', 'security'); |
|
5594
|
|
- |
|
5595
|
|
- if (!current_user_can('manage_options')) { |
|
5596
|
|
- wp_send_json_error(__('Unauthorized', 'mxchat')); |
|
5597
|
|
- } |
|
5598
|
|
- |
|
5599
|
|
- $intent_id = isset($_POST['intent_id']) ? intval($_POST['intent_id']) : 0; |
|
5600
|
|
- if (!$intent_id) { |
|
5601
|
|
- wp_send_json_error(__('Invalid intent ID.', 'mxchat')); |
|
5602
|
|
- } |
|
5603
|
|
- |
|
5604
|
|
- global $wpdb; |
|
5605
|
|
- $phrases_table = $wpdb->prefix . 'mxchat_intent_phrases'; |
|
5606
|
|
- |
|
5607
|
|
- $phrases = array(); |
|
5608
|
|
- if ($wpdb->get_var("SHOW TABLES LIKE '$phrases_table'") === $phrases_table) { |
|
5609
|
|
- $phrases = $wpdb->get_results($wpdb->prepare( |
|
5610
|
|
- "SELECT id, phrase, created_at FROM $phrases_table WHERE intent_id = %d ORDER BY created_at ASC", |
|
5611
|
|
- $intent_id |
|
5612
|
|
- )); |
|
5613
|
|
- } |
|
5614
|
|
- |
|
5615
|
|
- wp_send_json_success(array('phrases' => $phrases)); |
|
5616
|
|
-} |
|
5617
|
|
- |
|
5618
|
|
-/** |
|
5619
|
|
- * AJAX handler to clear legacy phrases and embedding from the main intents table |
|
5620
|
|
- */ |
|
5621
|
|
-public function mxchat_delete_legacy_phrases_ajax() { |
|
5622
|
|
- check_ajax_referer('mxchat_delete_legacy_nonce', 'security'); |
|
5623
|
|
- |
|
5624
|
|
- if (!current_user_can('manage_options')) { |
|
5625
|
|
- wp_send_json_error(__('Unauthorized', 'mxchat')); |
|
5626
|
|
- } |
|
5627
|
|
- |
|
5628
|
|
- $intent_id = isset($_POST['intent_id']) ? intval($_POST['intent_id']) : 0; |
|
5629
|
|
- if (!$intent_id) { |
|
5630
|
|
- wp_send_json_error(__('Invalid intent ID.', 'mxchat')); |
|
5631
|
|
- } |
|
5632
|
|
- |
|
5633
|
|
- global $wpdb; |
|
5634
|
|
- $table_name = $wpdb->prefix . 'mxchat_intents'; |
|
5635
|
|
- |
|
5636
|
|
- $result = $wpdb->update( |
|
5637
|
|
- $table_name, |
|
5638
|
|
- array('phrases' => '', 'embedding_vector' => ''), |
|
5639
|
|
- array('id' => $intent_id), |
|
5640
|
|
- array('%s', '%s'), |
|
5641
|
|
- array('%d') |
|
5642
|
|
- ); |
|
5643
|
|
- |
|
5644
|
|
- if ($result === false) { |
|
5645
|
|
- wp_send_json_error(__('Failed to clear legacy phrases.', 'mxchat')); |
|
5646
|
|
- } |
|
5647
|
|
- |
|
5648
|
|
- wp_send_json_success(array('cleared' => true)); |
|
5649
|
|
-} |
|
5650
|
|
- |
|
5651
|
|
-/** |
|
5652
|
4134
|
* Enhanced get_available_callbacks function with form action exclusion |
|
5653
|
4135
|
* |
|
5654
|
4136
|
* @param bool $grouped Whether to return callbacks grouped by category |
|
5655
|
4137
|
* @param bool $include_all Whether to include all potential actions (even if add-on not installed) |
|
@@ -5687,10 +4169,9 @@ |
|
5687
|
4169
|
'mxchat_handle_product_recommendations', |
|
5688
|
4170
|
'mxchat_handle_order_history', |
|
5689
|
4171
|
'mxchat_show_product_card', |
|
5690
|
4172
|
'mxchat_add_to_cart', |
|
5691
|
|
- 'mxchat_checkout_redirect', |
|
5692
|
|
- 'mxchat_handle_featured_products' |
|
4173
|
+ 'mxchat_checkout_redirect' |
|
5693
|
4174
|
), |
|
5694
|
4175
|
'name' => __('WooCommerce Add-on', 'mxchat'), |
|
5695
|
4176
|
'pro_required' => true |
|
5696
|
4177
|
), |
|
@@ -5753,9 +4234,9 @@ |
|
5753
|
4234
|
'label' => __('Brave Web Search', 'mxchat'), |
|
5754
|
4235
|
'pro_only' => false, |
|
5755
|
4236
|
'group' => __('Search Features', 'mxchat'), |
|
5756
|
4237
|
'icon' => 'search', |
|
5757
|
|
- 'description' => __('Let users search the web directly from the chat (requires a Brave Search API key)', 'mxchat'), |
|
4238
|
+ 'description' => __('Let users search the web directly from the chat', 'mxchat'), |
|
5758
|
4239
|
'addon' => false, |
|
5759
|
4240
|
'installed' => true |
|
5760
|
4241
|
), |
|
5761
|
4242
|
'mxchat_handle_image_search_request' => array( |
|
@@ -5762,9 +4243,9 @@ |
|
5762
|
4243
|
'label' => __('Brave Image Search', 'mxchat'), |
|
5763
|
4244
|
'pro_only' => false, |
|
5764
|
4245
|
'group' => __('Search Features', 'mxchat'), |
|
5765
|
4246
|
'icon' => 'format-image', |
|
5766
|
|
- 'description' => __('Search and display images in the chat conversation (requires a Brave Search API key)', 'mxchat'), |
|
4247
|
+ 'description' => __('Search and display images in the chat conversation', 'mxchat'), |
|
5767
|
4248
|
'addon' => false, |
|
5768
|
4249
|
'installed' => true |
|
5769
|
4250
|
), |
|
5770
|
4251
|
// Pro core features - check is_activated property |
|
@@ -5867,16 +4348,8 @@ |
|
5867
|
4348
|
'group' => __('WooCommerce Features', 'mxchat'), |
|
5868
|
4349
|
'icon' => 'arrow-right-alt', |
|
5869
|
4350
|
'description' => __('Redirect customer to checkout page', 'mxchat'), |
|
5870
|
4351
|
), |
|
5871
|
|
- 'mxchat_handle_featured_products' => array( |
|
5872
|
|
- 'label' => __('Featured Products Showcase', 'mxchat'), |
|
5873
|
|
- 'pro_only' => false, |
|
5874
|
|
- 'addon_promo' => true, |
|
5875
|
|
- 'group' => __('WooCommerce Features', 'mxchat'), |
|
5876
|
|
- 'icon' => 'star-filled', |
|
5877
|
|
- 'description' => __('Display a curated selection of products with an AI-generated message', 'mxchat'), |
|
5878
|
|
- ), |
|
5879
|
4352
|
|
|
5880
|
4353
|
// Perplexity Add-on |
|
5881
|
4354
|
'mxchat_perplexity_research' => array( |
|
5882
|
4355
|
'label' => __('Perplexity Research', 'mxchat'), |
|
@@ -6119,17 +4592,8 @@ |
|
6119
|
4592
|
'mxchat-api-keys', |
|
6120
|
4593
|
'mxchat_api_keys_section' |
|
6121
|
4594
|
); |
|
6122
|
4595
|
|
|
6123
|
|
- // Custom (OpenAI-compatible) Provider — for Ollama, LM Studio, vLLM, llama.cpp, Azure OpenAI, etc. |
|
6124
|
|
- add_settings_field( |
|
6125
|
|
- 'custom_provider', |
|
6126
|
|
- esc_html__('Custom Provider (OpenAI-compatible)', 'mxchat'), |
|
6127
|
|
- array($this, 'custom_provider_callback'), |
|
6128
|
|
- 'mxchat-api-keys', |
|
6129
|
|
- 'mxchat_api_keys_section' |
|
6130
|
|
- ); |
|
6131
|
|
- |
|
6132
|
4596
|
// Loops API Key |
|
6133
|
4597
|
add_settings_field( |
|
6134
|
4598
|
'loops_api_key', |
|
6135
|
4599
|
esc_html__('Loops API Key', 'mxchat'), |
|
@@ -6146,8 +4610,17 @@ |
|
6146
|
4610
|
'mxchat-api-keys', |
|
6147
|
4611
|
'mxchat_api_keys_section' |
|
6148
|
4612
|
); |
|
6149
|
4613
|
|
|
4614
|
+ // Frontend Debugger Toggle |
|
4615
|
+ add_settings_field( |
|
4616
|
+ 'show_frontend_debugger', |
|
4617
|
+ esc_html__('Frontend Debugger', 'mxchat'), |
|
4618
|
+ array($this, 'mxchat_frontend_debugger_callback'), |
|
4619
|
+ 'mxchat-chatbot', |
|
4620
|
+ 'mxchat_chatbot_section' |
|
4621
|
+ ); |
|
4622
|
+ |
|
6150
|
4623
|
// Similarity Threshold Slider |
|
6151
|
4624
|
add_settings_field( |
|
6152
|
4625
|
'similarity_threshold', // Field ID |
|
6153
|
4626
|
esc_html__('Similarity Threshold', 'mxchat'), // Field title |
|
@@ -6164,18 +4637,9 @@ |
|
6164
|
4637
|
'mxchat-chatbot', // Page |
|
6165
|
4638
|
'mxchat_chatbot_section' // Section |
|
6166
|
4639
|
); |
|
6167
|
4640
|
|
|
6168
|
|
- // RAG Chunks Limit Slider |
|
6169
|
4641
|
add_settings_field( |
|
6170
|
|
- 'rag_chunks_limit', // Field ID |
|
6171
|
|
- esc_html__('RAG Chunks Limit', 'mxchat'), // Field title |
|
6172
|
|
- array($this, 'mxchat_rag_chunks_limit_callback'), // Callback function |
|
6173
|
|
- 'mxchat-chatbot', // Page |
|
6174
|
|
- 'mxchat_chatbot_section' // Section |
|
6175
|
|
- ); |
|
6176
|
|
- |
|
6177
|
|
- add_settings_field( |
|
6178
|
4642
|
'append_to_body', |
|
6179
|
4643
|
esc_html__('Auto-Display Chatbot', 'mxchat'), |
|
6180
|
4644
|
array($this, 'mxchat_append_to_body_callback'), |
|
6181
|
4645
|
'mxchat-chatbot', |
|
@@ -6197,22 +4661,9 @@ |
|
6197
|
4661
|
'mxchat-chatbot', |
|
6198
|
4662
|
'mxchat_chatbot_section' |
|
6199
|
4663
|
); |
|
6200
|
4664
|
|
|
6201
|
|
- // Satisfaction rating toggle (plan-a5b006). |
|
6202
|
4665
|
add_settings_field( |
|
6203
|
|
- 'satisfaction_rating_enabled', |
|
6204
|
|
- esc_html__('Satisfaction Rating Prompt', 'mxchat'), |
|
6205
|
|
- array($this, 'mxchat_satisfaction_rating_toggle_callback'), |
|
6206
|
|
- 'mxchat-chatbot', |
|
6207
|
|
- 'mxchat_chatbot_section' |
|
6208
|
|
- ); |
|
6209
|
|
- |
|
6210
|
|
- // Satisfaction rating customization (plan-141a12, plan-29caac): |
|
6211
|
|
- // the 5 customization fields are now inline-rendered inside |
|
6212
|
|
- // mxchat_satisfaction_rating_toggle_callback's sub-options wrapper. |
|
6213
|
|
- |
|
6214
|
|
- add_settings_field( |
|
6215
|
4666
|
'enable_streaming_toggle', |
|
6216
|
4667
|
esc_html__('Enable Streaming', 'mxchat'), |
|
6217
|
4668
|
array($this, 'enable_streaming_toggle_callback'), |
|
6218
|
4669
|
'mxchat-chatbot', |
|
@@ -6361,32 +4812,8 @@ |
|
6361
|
4812
|
'mxchat_chatbot_section' |
|
6362
|
4813
|
); |
|
6363
|
4814
|
|
|
6364
|
4815
|
add_settings_field( |
|
6365
|
|
- 'print_button_enabled', |
|
6366
|
|
- esc_html__('Show Download Transcript Button', 'mxchat'), |
|
6367
|
|
- array($this, 'mxchat_print_button_toggle_callback'), |
|
6368
|
|
- 'mxchat-chatbot', |
|
6369
|
|
- 'mxchat_chatbot_section' |
|
6370
|
|
- ); |
|
6371
|
|
- |
|
6372
|
|
- add_settings_field( |
|
6373
|
|
- 'reset_chat_enabled', |
|
6374
|
|
- esc_html__('Show Start-New-Chat Button', 'mxchat'), |
|
6375
|
|
- array($this, 'mxchat_reset_chat_toggle_callback'), |
|
6376
|
|
- 'mxchat-chatbot', |
|
6377
|
|
- 'mxchat_chatbot_section' |
|
6378
|
|
- ); |
|
6379
|
|
- |
|
6380
|
|
- add_settings_field( |
|
6381
|
|
- 'reset_chat_label', |
|
6382
|
|
- esc_html__('Start-New-Chat Button Label', 'mxchat'), |
|
6383
|
|
- array($this, 'mxchat_reset_chat_label_callback'), |
|
6384
|
|
- 'mxchat-chatbot', |
|
6385
|
|
- 'mxchat_chatbot_section' |
|
6386
|
|
- ); |
|
6387
|
|
- |
|
6388
|
|
- add_settings_field( |
|
6389
|
4816
|
'popular_question_1', |
|
6390
|
4817
|
esc_html__('Quick Question 1', 'mxchat'), |
|
6391
|
4818
|
array($this, 'mxchat_popular_question_1_callback'), |
|
6392
|
4819
|
'mxchat-chatbot', |
|
@@ -6776,16 +5203,8 @@ |
|
6776
|
5203
|
'mxchat_transcripts_notification_section' |
|
6777
|
5204
|
); |
|
6778
|
5205
|
|
|
6779
|
5206
|
add_settings_field( |
|
6780
|
|
- 'mxchat_retention_days', |
|
6781
|
|
- esc_html__('Custom Retention (Days)', 'mxchat'), |
|
6782
|
|
- array($this, 'mxchat_retention_days_callback'), |
|
6783
|
|
- 'mxchat-transcripts', |
|
6784
|
|
- 'mxchat_transcripts_notification_section' |
|
6785
|
|
- ); |
|
6786
|
|
- |
|
6787
|
|
- add_settings_field( |
|
6788
|
5207
|
'mxchat_auto_email_transcript', |
|
6789
|
5208
|
esc_html__('Auto-Email Full Transcript', 'mxchat'), |
|
6790
|
5209
|
array($this, 'mxchat_auto_email_transcript_callback'), |
|
6791
|
5210
|
'mxchat-transcripts', |
|
@@ -7022,9 +5441,8 @@ |
|
7022
|
5441
|
echo '<div class="api-key-wrapper">'; |
|
7023
|
5442
|
echo '<input type="text" id="api_key" name="api_key" value="' . $apiKey . '" class="regular-text mxchat-autosave-field mxchat-api-key-field" autocomplete="new-password" data-lpignore="true" data-form-type="other" data-nonce="' . $nonce . '" />'; |
|
7024
|
5443
|
echo '<button type="button" id="toggleApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>'; |
|
7025
|
5444
|
echo '<p class="description">' . esc_html__('Required for OpenAI GPT models and OpenAI embeddings. Get your API key from OpenAI Platform.', 'mxchat') . '</p>'; |
|
7026
|
|
- echo $this->mxchat_provider_key_test_button('openai', 'api_key'); |
|
7027
|
5445
|
echo '</div>'; |
|
7028
|
5446
|
} |
|
7029
|
5447
|
|
|
7030
|
5448
|
// X.AI API Key |
|
@@ -7035,9 +5453,8 @@ |
|
7035
|
5453
|
echo '<div class="api-key-wrapper">'; |
|
7036
|
5454
|
echo '<input type="text" id="xai_api_key" name="xai_api_key" value="' . $xaiApiKey . '" class="regular-text mxchat-autosave-field mxchat-api-key-field" autocomplete="new-password" data-lpignore="true" data-form-type="other" data-nonce="' . $nonce . '" />'; |
|
7037
|
5455
|
echo '<button type="button" id="toggleXaiApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>'; |
|
7038
|
5456
|
echo '<p class="description">' . esc_html__('Required for X.AI Grok models. Get your API key from X.AI Console.', 'mxchat') . '</p>'; |
|
7039
|
|
- echo $this->mxchat_provider_key_test_button('xai', 'xai_api_key'); |
|
7040
|
5457
|
echo '</div>'; |
|
7041
|
5458
|
} |
|
7042
|
5459
|
// Claude API Key |
|
7043
|
5460
|
public function claude_api_key_callback() { |
|
@@ -7047,9 +5464,8 @@ |
|
7047
|
5464
|
echo '<div class="api-key-wrapper">'; |
|
7048
|
5465
|
echo '<input type="text" id="claude_api_key" name="claude_api_key" value="' . $claudeApiKey . '" class="regular-text mxchat-autosave-field mxchat-api-key-field" autocomplete="new-password" data-lpignore="true" data-form-type="other" data-nonce="' . $nonce . '" />'; |
|
7049
|
5466
|
echo '<button type="button" id="toggleClaudeApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>'; |
|
7050
|
5467
|
echo '<p class="description">' . esc_html__('Required for Anthropic Claude models. Get your API key from Anthropic Console.', 'mxchat') . '</p>'; |
|
7051
|
|
- echo $this->mxchat_provider_key_test_button('claude', 'claude_api_key'); |
|
7052
|
5468
|
echo '</div>'; |
|
7053
|
5469
|
} |
|
7054
|
5470
|
|
|
7055
|
5471
|
// DeepSeek API Key |
|
@@ -7060,9 +5476,8 @@ |
|
7060
|
5476
|
echo '<div class="api-key-wrapper">'; |
|
7061
|
5477
|
echo '<input type="text" id="deepseek_api_key" name="deepseek_api_key" value="' . $apiKey . '" class="regular-text mxchat-autosave-field mxchat-api-key-field" autocomplete="new-password" data-lpignore="true" data-form-type="other" data-nonce="' . $nonce . '" />'; |
|
7062
|
5478
|
echo '<button type="button" id="toggleDeepSeekApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>'; |
|
7063
|
5479
|
echo '<p class="description">' . esc_html__('Required for DeepSeek models. Get your API key from DeepSeek Platform.', 'mxchat') . '</p>'; |
|
7064
|
|
- echo $this->mxchat_provider_key_test_button('deepseek', 'deepseek_api_key'); |
|
7065
|
5480
|
echo '</div>'; |
|
7066
|
5481
|
} |
|
7067
|
5482
|
|
|
7068
|
5483
|
// Gemini API Key |
|
@@ -7073,9 +5488,8 @@ |
|
7073
|
5488
|
echo '<div class="api-key-wrapper">'; |
|
7074
|
5489
|
echo '<input type="text" id="gemini_api_key" name="gemini_api_key" value="' . $geminiApiKey . '" class="regular-text mxchat-autosave-field mxchat-api-key-field" autocomplete="new-password" data-lpignore="true" data-form-type="other" data-nonce="' . $nonce . '" />'; |
|
7075
|
5490
|
echo '<button type="button" id="toggleGeminiApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>'; |
|
7076
|
5491
|
echo '<p class="description">' . esc_html__('Required for Google Gemini models and embeddings. Get your API key from Google AI Studio.', 'mxchat') . '</p>'; |
|
7077
|
|
- echo $this->mxchat_provider_key_test_button('gemini', 'gemini_api_key'); |
|
7078
|
5492
|
echo '</div>'; |
|
7079
|
5493
|
} |
|
7080
|
5494
|
|
|
7081
|
5495
|
|
|
@@ -7087,203 +5501,11 @@ |
|
7087
|
5501
|
echo '<div class="api-key-wrapper">'; |
|
7088
|
5502
|
echo '<input type="text" id="openrouter_api_key" name="openrouter_api_key" value="' . $openrouterApiKey . '" class="regular-text mxchat-autosave-field mxchat-api-key-field" autocomplete="new-password" data-lpignore="true" data-form-type="other" data-nonce="' . $nonce . '" />'; |
|
7089
|
5503
|
echo '<button type="button" id="toggleOpenRouterApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>'; |
|
7090
|
5504
|
echo '<p class="description">' . esc_html__('Required for OpenRouter models. Get your API key from OpenRouter.ai', 'mxchat') . '</p>'; |
|
7091
|
|
- echo $this->mxchat_provider_key_test_button('openrouter', 'openrouter_api_key'); |
|
7092
|
5505
|
echo '</div>'; |
|
7093
|
5506
|
} |
|
7094
|
5507
|
|
|
7095
|
|
-/** |
|
7096
|
|
- * Renders a "Test key" button + result target next to a built-in provider key |
|
7097
|
|
- * field, and emits the shared delegated click handler ONCE (static guard). The |
|
7098
|
|
- * button carries data-target = the key field id so the owner can test the value |
|
7099
|
|
- * they just typed (test-before-save); the AJAX handler falls back to the saved |
|
7100
|
|
- * key when the field is empty. Styled with the WP `button` class to match the |
|
7101
|
|
- * adjacent Custom-provider "Test Connection" button on this same page. |
|
7102
|
|
- * plan-mxchat-20260623-c41f74. |
|
7103
|
|
- */ |
|
7104
|
|
-public function mxchat_provider_key_test_button($provider, $target_id) { |
|
7105
|
|
- static $script_emitted = false; |
|
7106
|
|
- $nonce = wp_create_nonce('mxchat_test_provider_key'); |
|
7107
|
|
- $html = '<div class="mxchat-key-test" style="margin-top:8px;">' |
|
7108
|
|
- . '<button type="button" class="button mxchat-test-provider-key" data-provider="' . esc_attr($provider) . '" data-target="' . esc_attr($target_id) . '" data-nonce="' . esc_attr($nonce) . '">' . esc_html__('Test key', 'mxchat') . '</button>' |
|
7109
|
|
- . '<span class="mxchat-test-provider-key-result" style="margin-left:10px;font-size:13px;vertical-align:middle;"></span>' |
|
7110
|
|
- . '</div>'; |
|
7111
|
|
- if (!$script_emitted) { |
|
7112
|
|
- $script_emitted = true; |
|
7113
|
|
- $html .= $this->mxchat_provider_key_test_script(); |
|
7114
|
|
- } |
|
7115
|
|
- return $html; |
|
7116
|
|
-} |
|
7117
|
|
- |
|
7118
|
|
-/** |
|
7119
|
|
- * One-time delegated click handler shared by every .mxchat-test-provider-key |
|
7120
|
|
- * button. Posts the typed key value + provider to mxchat_test_provider_key and |
|
7121
|
|
- * renders the success/error message inline. Mirrors the Custom-provider test. |
|
7122
|
|
- */ |
|
7123
|
|
-private function mxchat_provider_key_test_script() { |
|
7124
|
|
- $t_testing = esc_js(__('Testing...', 'mxchat')); |
|
7125
|
|
- $t_valid = esc_js(__('Key is valid.', 'mxchat')); |
|
7126
|
|
- $t_failed = esc_js(__('Failed', 'mxchat')); |
|
7127
|
|
- $t_req = esc_js(__('Request failed', 'mxchat')); |
|
7128
|
|
- return '<script>(function(){' |
|
7129
|
|
- . 'if (window.__mxchatProviderKeyTestWired) { return; }' |
|
7130
|
|
- . 'window.__mxchatProviderKeyTestWired = true;' |
|
7131
|
|
- . 'document.addEventListener("click", function(e){' |
|
7132
|
|
- . 'var btn = e.target && e.target.closest ? e.target.closest(".mxchat-test-provider-key") : null;' |
|
7133
|
|
- . 'if (!btn) { return; }' |
|
7134
|
|
- . 'e.preventDefault();' |
|
7135
|
|
- . 'var field = btn.getAttribute("data-target") ? document.getElementById(btn.getAttribute("data-target")) : null;' |
|
7136
|
|
- . 'var out = btn.parentNode ? btn.parentNode.querySelector(".mxchat-test-provider-key-result") : null;' |
|
7137
|
|
- . 'if (out) { out.textContent = "' . $t_testing . '"; out.style.color = "#646970"; }' |
|
7138
|
|
- . 'btn.disabled = true;' |
|
7139
|
|
- . 'var fd = new FormData();' |
|
7140
|
|
- . 'fd.append("action", "mxchat_test_provider_key");' |
|
7141
|
|
- . 'fd.append("_wpnonce", btn.getAttribute("data-nonce"));' |
|
7142
|
|
- . 'fd.append("provider", btn.getAttribute("data-provider") || "");' |
|
7143
|
|
- . 'fd.append("key", field ? field.value : "");' |
|
7144
|
|
- . 'fetch(ajaxurl, { method:"POST", credentials:"same-origin", body: fd })' |
|
7145
|
|
- . '.then(function(r){ return r.json(); })' |
|
7146
|
|
- . '.then(function(j){' |
|
7147
|
|
- . 'if (out) {' |
|
7148
|
|
- . 'if (j && j.success) { out.textContent = "✓ " + ((j.data && j.data.message) ? j.data.message : "' . $t_valid . '"); out.style.color = "#00a32a"; }' |
|
7149
|
|
- . 'else { out.textContent = "⚠ " + ((j && j.data && j.data.message) ? j.data.message : "' . $t_failed . '"); out.style.color = "#d63638"; }' |
|
7150
|
|
- . '}' |
|
7151
|
|
- . 'btn.disabled = false;' |
|
7152
|
|
- . '})' |
|
7153
|
|
- . '.catch(function(){ if (out) { out.textContent = "⚠ ' . $t_req . '"; out.style.color = "#d63638"; } btn.disabled = false; });' |
|
7154
|
|
- . '});' |
|
7155
|
|
- . '})();</script>'; |
|
7156
|
|
-} |
|
7157
|
|
- |
|
7158
|
|
-// Custom (OpenAI-compatible) Provider — Ollama, LM Studio, vLLM, llama.cpp, Azure OpenAI, etc. |
|
7159
|
|
-public function custom_provider_callback() { |
|
7160
|
|
- $base_url = isset($this->options['custom_provider_base_url']) ? esc_attr($this->options['custom_provider_base_url']) : ''; |
|
7161
|
|
- $api_key = isset($this->options['custom_provider_api_key']) ? esc_attr($this->options['custom_provider_api_key']) : ''; |
|
7162
|
|
- $model_name = isset($this->options['custom_provider_model']) ? esc_attr($this->options['custom_provider_model']) : ''; |
|
7163
|
|
- $auth_scheme = isset($this->options['custom_provider_auth_scheme']) ? esc_attr($this->options['custom_provider_auth_scheme']) : 'bearer'; |
|
7164
|
|
- $api_version = isset($this->options['custom_provider_api_version']) ? esc_attr($this->options['custom_provider_api_version']) : ''; |
|
7165
|
|
- $use_embed = !empty($this->options['custom_provider_for_embeddings']) && $this->options['custom_provider_for_embeddings'] === 'on'; |
|
7166
|
|
- $use_images = !empty($this->options['custom_provider_for_images']) && $this->options['custom_provider_for_images'] === 'on'; |
|
7167
|
|
- $embed_model = isset($this->options['custom_provider_embedding_model']) ? esc_attr($this->options['custom_provider_embedding_model']) : ''; |
|
7168
|
|
- $nonce = wp_create_nonce('mxchat_autosave_nonce'); |
|
7169
|
|
- $test_nonce = wp_create_nonce('mxchat_test_custom_provider'); |
|
7170
|
|
- |
|
7171
|
|
- echo '<style> |
|
7172
|
|
- .mxchat-cp { max-width: 680px; } |
|
7173
|
|
- .mxchat-cp .mxchat-cp-intro { margin: 0 0 16px; color: #50575e; font-size: 13px; line-height: 1.5; } |
|
7174
|
|
- .mxchat-cp .mxchat-cp-row { display: block; margin: 0 0 18px; } |
|
7175
|
|
- .mxchat-cp .mxchat-cp-row > label { display: block; font-weight: 600; margin: 0 0 6px; color: #1d2327; font-size: 13px; } |
|
7176
|
|
- .mxchat-cp .mxchat-cp-row > input[type="text"], |
|
7177
|
|
- .mxchat-cp .mxchat-cp-row > input[type="password"], |
|
7178
|
|
- .mxchat-cp .mxchat-cp-row > select { display: block; width: 100%; max-width: 480px; margin: 0; } |
|
7179
|
|
- .mxchat-cp .mxchat-cp-row > .description { display: block; margin: 6px 0 0; color: #646970; font-size: 12px; line-height: 1.5; max-width: 480px; } |
|
7180
|
|
- .mxchat-cp .mxchat-cp-test { margin-top: 4px; padding-top: 14px; border-top: 1px solid #e5e7eb; } |
|
7181
|
|
- .mxchat-cp .mxchat-cp-test .mxchat-cp-test-status { display: inline-block; margin-left: 10px; vertical-align: middle; font-size: 13px; } |
|
7182
|
|
- .mxchat-cp .mxchat-cp-azure { margin: 0 0 18px; padding: 12px 14px; background: #f6f7ff; border: 1px solid #dfe1f5; border-left: 3px solid #7873f5; border-radius: 6px; max-width: 480px; } |
|
7183
|
|
- .mxchat-cp .mxchat-cp-azure > .mxchat-cp-azure-title { display: block; font-weight: 600; color: #1d2327; font-size: 12px; text-transform: uppercase; letter-spacing: 0.4px; margin: 0 0 8px; } |
|
7184
|
|
- .mxchat-cp .mxchat-cp-azure ol { margin: 0; padding: 0 0 0 18px; color: #50575e; font-size: 12px; line-height: 1.7; } |
|
7185
|
|
- .mxchat-cp .mxchat-cp-azure code { background: #eceefb; padding: 1px 5px; border-radius: 3px; font-size: 11px; } |
|
7186
|
|
- </style>'; |
|
7187
|
|
- |
|
7188
|
|
- echo '<div class="api-key-wrapper mxchat-cp">'; |
|
7189
|
|
- echo '<p class="mxchat-cp-intro">' . esc_html__('Point MxChat at any OpenAI-compatible /v1/chat/completions endpoint: Ollama, LM Studio, vLLM, llama.cpp, LocalAI, Azure OpenAI, etc. Then select "Custom (OpenAI-compatible)" in the model picker.', 'mxchat') . '</p>'; |
|
7190
|
|
- |
|
7191
|
|
- // Azure OpenAI quick start — consolidates the 4-field Azure recipe in one scannable callout |
|
7192
|
|
- // so admins can configure Azure without piecing it together from each field's hint. |
|
7193
|
|
- echo '<div class="mxchat-cp-azure">'; |
|
7194
|
|
- echo '<span class="mxchat-cp-azure-title">' . esc_html__('Azure OpenAI quick start', 'mxchat') . '</span>'; |
|
7195
|
|
- echo '<ol>'; |
|
7196
|
|
- echo '<li>' . wp_kses(__('<strong>Base URL</strong> → <code>https://<resource>.openai.azure.com/openai/deployments/<deployment></code>', 'mxchat'), array('strong' => array(), 'code' => array())) . '</li>'; |
|
7197
|
|
- echo '<li>' . wp_kses(__('<strong>API Key</strong> → your Azure OpenAI key (required)', 'mxchat'), array('strong' => array(), 'code' => array())) . '</li>'; |
|
7198
|
|
- echo '<li>' . wp_kses(__('<strong>Auth Scheme</strong> → <code>api-key header (Azure OpenAI)</code>', 'mxchat'), array('strong' => array(), 'code' => array())) . '</li>'; |
|
7199
|
|
- echo '<li>' . wp_kses(__('<strong>API Version</strong> → required for Azure, e.g. <code>2024-08-01-preview</code>', 'mxchat'), array('strong' => array(), 'code' => array())) . '</li>'; |
|
7200
|
|
- echo '</ol>'; |
|
7201
|
|
- echo '</div>'; |
|
7202
|
|
- |
|
7203
|
|
- echo '<div class="mxchat-cp-row">'; |
|
7204
|
|
- echo '<label for="custom_provider_base_url">' . esc_html__('Base URL', 'mxchat') . '</label>'; |
|
7205
|
|
- echo '<input type="text" id="custom_provider_base_url" name="custom_provider_base_url" value="' . $base_url . '" class="regular-text mxchat-autosave-field" placeholder="http://localhost:11434/v1" autocomplete="off" data-lpignore="true" data-form-type="other" data-nonce="' . $nonce . '" />'; |
|
7206
|
|
- echo '<p class="description">' . esc_html__('Examples: Ollama http://localhost:11434/v1 · LM Studio http://localhost:1234/v1 · vLLM http://gpu:8000/v1 · Azure https://<resource>.openai.azure.com/openai/deployments/<deployment>', 'mxchat') . '</p>'; |
|
7207
|
|
- echo '</div>'; |
|
7208
|
|
- |
|
7209
|
|
- echo '<div class="mxchat-cp-row">'; |
|
7210
|
|
- echo '<label for="custom_provider_api_key">' . esc_html__('API Key (optional)', 'mxchat') . '</label>'; |
|
7211
|
|
- echo '<input type="password" id="custom_provider_api_key" name="custom_provider_api_key" value="' . $api_key . '" class="regular-text mxchat-autosave-field mxchat-api-key-field" autocomplete="new-password" data-lpignore="true" data-form-type="other" data-nonce="' . $nonce . '" />'; |
|
7212
|
|
- echo '<p class="description">' . esc_html__('Leave empty for unauthenticated local servers. Required for Azure / vLLM / hosted endpoints.', 'mxchat') . '</p>'; |
|
7213
|
|
- echo '</div>'; |
|
7214
|
|
- |
|
7215
|
|
- echo '<div class="mxchat-cp-row">'; |
|
7216
|
|
- echo '<label for="custom_provider_model">' . esc_html__('Model Name', 'mxchat') . '</label>'; |
|
7217
|
|
- echo '<input type="text" id="custom_provider_model" name="custom_provider_model" value="' . $model_name . '" class="regular-text mxchat-autosave-field" placeholder="llama3.2" autocomplete="off" data-lpignore="true" data-nonce="' . $nonce . '" />'; |
|
7218
|
|
- echo '<p class="description">' . esc_html__('The model identifier the upstream server expects (e.g. llama3.2, mistral, gpt-oss). For Azure this is the deployment ID — leave empty if the Base URL already includes /deployments/<deployment>.', 'mxchat') . '</p>'; |
|
7219
|
|
- echo '</div>'; |
|
7220
|
|
- |
|
7221
|
|
- echo '<div class="mxchat-cp-row">'; |
|
7222
|
|
- echo '<label for="custom_provider_auth_scheme">' . esc_html__('Auth Scheme', 'mxchat') . '</label>'; |
|
7223
|
|
- echo '<select id="custom_provider_auth_scheme" name="custom_provider_auth_scheme" class="mxchat-autosave-field" data-nonce="' . $nonce . '">'; |
|
7224
|
|
- echo '<option value="bearer"' . selected($auth_scheme, 'bearer', false) . '>' . esc_html__('Authorization: Bearer (OpenAI / Ollama / vLLM / LM Studio)', 'mxchat') . '</option>'; |
|
7225
|
|
- echo '<option value="api-key"' . selected($auth_scheme, 'api-key', false) . '>' . esc_html__('api-key header (Azure OpenAI)', 'mxchat') . '</option>'; |
|
7226
|
|
- echo '</select>'; |
|
7227
|
|
- echo '<p class="description">' . esc_html__('Most OpenAI-compatible servers use Bearer. Azure OpenAI uses the api-key header.', 'mxchat') . '</p>'; |
|
7228
|
|
- echo '</div>'; |
|
7229
|
|
- |
|
7230
|
|
- echo '<div class="mxchat-cp-row">'; |
|
7231
|
|
- echo '<label for="custom_provider_api_version">' . esc_html__('API Version (Azure only)', 'mxchat') . '</label>'; |
|
7232
|
|
- echo '<input type="text" id="custom_provider_api_version" name="custom_provider_api_version" value="' . $api_version . '" class="regular-text mxchat-autosave-field" placeholder="2024-08-01-preview" autocomplete="off" data-lpignore="true" data-nonce="' . $nonce . '" />'; |
|
7233
|
|
- echo '<p class="description">' . esc_html__('Appended as ?api-version=... on the request URL. Required for Azure OpenAI; leave empty for non-Azure providers.', 'mxchat') . '</p>'; |
|
7234
|
|
- echo '</div>'; |
|
7235
|
|
- |
|
7236
|
|
- // Extended-use checkboxes — opt-in routing of other dispatcher paths through the custom provider. |
|
7237
|
|
- echo '<div class="mxchat-cp-row">'; |
|
7238
|
|
- echo '<label style="font-weight:600; display:block; margin:0 0 6px; color:#1d2327; font-size:13px;">' . esc_html__('Extended routing (opt-in)', 'mxchat') . '</label>'; |
|
7239
|
|
- echo '<label style="display:block; margin:0 0 6px; font-weight:400;"><input type="checkbox" id="custom_provider_for_embeddings" name="custom_provider_for_embeddings" value="on"' . checked($use_embed, true, false) . ' class="mxchat-autosave-field" data-nonce="' . $nonce . '" /> ' . esc_html__('Use custom provider for embeddings', 'mxchat') . '</label>'; |
|
7240
|
|
- echo '<label style="display:block; margin:0 0 0; font-weight:400;"><input type="checkbox" id="custom_provider_for_images" name="custom_provider_for_images" value="on"' . checked($use_images, true, false) . ' class="mxchat-autosave-field" data-nonce="' . $nonce . '" /> ' . esc_html__('Use custom provider for image generation', 'mxchat') . '</label>'; |
|
7241
|
|
- echo '<p class="description">' . esc_html__('When off, embeddings and image generation continue to use OpenAI (current behavior). Turn on only if your endpoint exposes OpenAI-compatible /embeddings or /images/generations routes (e.g. Ollama, vLLM, LocalAI).', 'mxchat') . '</p>'; |
|
7242
|
|
- echo '</div>'; |
|
7243
|
|
- |
|
7244
|
|
- echo '<div class="mxchat-cp-row">'; |
|
7245
|
|
- echo '<label for="custom_provider_embedding_model">' . esc_html__('Custom Embedding Model', 'mxchat') . '</label>'; |
|
7246
|
|
- echo '<input type="text" id="custom_provider_embedding_model" name="custom_provider_embedding_model" value="' . $embed_model . '" class="regular-text mxchat-autosave-field" placeholder="nomic-embed-text" autocomplete="off" data-lpignore="true" data-nonce="' . $nonce . '" />'; |
|
7247
|
|
- echo '<p class="description">' . esc_html__('Only used when "Use custom provider for embeddings" is on. The embedding model name is separate from the chat model name above (e.g. Ollama embedding models: nomic-embed-text, mxbai-embed-large). Leave blank to fall back to the chat model name.', 'mxchat') . '</p>'; |
|
7248
|
|
- echo '</div>'; |
|
7249
|
|
- |
|
7250
|
|
- echo '<div class="mxchat-cp-test">'; |
|
7251
|
|
- echo '<button type="button" class="button" id="mxchat-test-custom-provider" data-nonce="' . $test_nonce . '">' . esc_html__('Test Connection', 'mxchat') . '</button>'; |
|
7252
|
|
- echo '<span id="mxchat-test-custom-provider-result" class="mxchat-cp-test-status"></span>'; |
|
7253
|
|
- echo '</div>'; |
|
7254
|
|
- |
|
7255
|
|
- echo '<script>(function(){ |
|
7256
|
|
- var btn = document.getElementById("mxchat-test-custom-provider"); |
|
7257
|
|
- if (!btn || btn._wired) { return; } btn._wired = true; |
|
7258
|
|
- btn.addEventListener("click", function(){ |
|
7259
|
|
- var out = document.getElementById("mxchat-test-custom-provider-result"); |
|
7260
|
|
- out.textContent = "' . esc_js(__('Testing...', 'mxchat')) . '"; |
|
7261
|
|
- out.style.color = "#646970"; |
|
7262
|
|
- var fd = new FormData(); |
|
7263
|
|
- fd.append("action", "mxchat_test_custom_provider"); |
|
7264
|
|
- fd.append("_wpnonce", btn.getAttribute("data-nonce")); |
|
7265
|
|
- fetch(ajaxurl, { method:"POST", credentials:"same-origin", body: fd }) |
|
7266
|
|
- .then(function(r){ return r.json(); }) |
|
7267
|
|
- .then(function(j){ |
|
7268
|
|
- if (j && j.success) { |
|
7269
|
|
- out.textContent = "✓ " + (j.data && j.data.message ? j.data.message : "' . esc_js(__('OK', 'mxchat')) . '"); |
|
7270
|
|
- out.style.color = "#00a32a"; |
|
7271
|
|
- } else { |
|
7272
|
|
- out.textContent = "⚠ " + (j && j.data && j.data.message ? j.data.message : "' . esc_js(__('Failed', 'mxchat')) . '"); |
|
7273
|
|
- out.style.color = "#d63638"; |
|
7274
|
|
- } |
|
7275
|
|
- }) |
|
7276
|
|
- .catch(function(){ |
|
7277
|
|
- out.textContent = "⚠ ' . esc_js(__('Request failed', 'mxchat')) . '"; |
|
7278
|
|
- out.style.color = "#d63638"; |
|
7279
|
|
- }); |
|
7280
|
|
- }); |
|
7281
|
|
- })();</script>'; |
|
7282
|
|
- |
|
7283
|
|
- echo '</div>'; |
|
7284
|
|
-} |
|
7285
|
|
- |
|
7286
|
5508
|
// Voyage API Key |
|
7287
|
5509
|
public function voyage_api_key_callback() { |
|
7288
|
5510
|
$apiKey = isset($this->options['voyage_api_key']) ? esc_attr($this->options['voyage_api_key']) : ''; |
|
7289
|
5511
|
$nonce = wp_create_nonce('mxchat_autosave_nonce'); |
|
@@ -7492,15 +5714,56 @@ |
|
7492
|
5714
|
} |
|
7493
|
5715
|
|
|
7494
|
5716
|
|
|
7495
|
5717
|
public function mxchat_model_callback() { |
|
7496
|
|
- // Catalog refactor (plan-d14e89): single source of truth lives in |
|
7497
|
|
- // includes/class-mxchat-model-catalog.php. Dropdown groups are the |
|
7498
|
|
- // provider labels; each group maps model_id => "Label" strings. |
|
7499
|
|
- if (!class_exists('MxChat_Model_Catalog')) { |
|
7500
|
|
- require_once plugin_dir_path(__FILE__) . 'class-mxchat-model-catalog.php'; |
|
7501
|
|
- } |
|
7502
|
|
- $models = MxChat_Model_Catalog::settings_dropdown_groups(); |
|
5718
|
+ // Define available models grouped by provider |
|
5719
|
+ $models = array( |
|
5720
|
+ esc_html__('OpenRouter (100+ Models)', 'mxchat') => array( |
|
5721
|
+ 'openrouter' => esc_html__('OpenRouter - Select after entering API key', 'mxchat'), |
|
5722
|
+ ), |
|
5723
|
+ esc_html__('Google Gemini Models', 'mxchat') => array( |
|
5724
|
+ 'gemini-3-pro-preview' => esc_html__('Gemini 3 Pro (Most Intelligent, Multimodal)', 'mxchat'), |
|
5725
|
+ 'gemini-3-flash-preview' => esc_html__('Gemini 3 Flash (Balanced Speed & Scale)', 'mxchat'), |
|
5726
|
+ 'gemini-2.5-pro' => esc_html__('Gemini 2.5 Pro (Advanced Thinking)', 'mxchat'), |
|
5727
|
+ 'gemini-2.5-flash' => esc_html__('Gemini 2.5 Flash (Best Price-Performance)', 'mxchat'), |
|
5728
|
+ 'gemini-2.5-flash-lite' => esc_html__('Gemini 2.5 Flash-Lite (Ultra Fast)', 'mxchat'), |
|
5729
|
+ 'gemini-2.0-flash' => esc_html__('Gemini 2.0 Flash (Deprecated Mar 2026)', 'mxchat'), |
|
5730
|
+ 'gemini-2.0-flash-lite' => esc_html__('Gemini 2.0 Flash-Lite (Deprecated Mar 2026)', 'mxchat'), |
|
5731
|
+ 'gemini-1.5-pro' => esc_html__('Gemini 1.5 Pro (Deprecated Sep 2025)', 'mxchat'), |
|
5732
|
+ 'gemini-1.5-flash' => esc_html__('Gemini 1.5 Flash (Deprecated Sep 2025)', 'mxchat'), |
|
5733
|
+ ), |
|
5734
|
+ esc_html__('X.AI Models', 'mxchat') => array( |
|
5735
|
+ 'grok-3-beta' => esc_html__('Grok-3 (Powerful)', 'mxchat'), |
|
5736
|
+ 'grok-3-fast-beta' => esc_html__('Grok-3 Fast (High Performance)', 'mxchat'), |
|
5737
|
+ 'grok-3-mini-beta' => esc_html__('Grok-3 Mini (Affordable)', 'mxchat'), |
|
5738
|
+ 'grok-3-mini-fast-beta' => esc_html__('Grok-3 Mini Fast (Quick Response)', 'mxchat'), |
|
5739
|
+ 'grok-2' => esc_html__('Grok 2', 'mxchat'), |
|
5740
|
+ 'grok-4-0709' => esc_html__('Grok 4 (Latest Flagship)', 'mxchat'), |
|
5741
|
+ 'grok-4-1-fast-reasoning' => esc_html__('Grok 4.1 Fast (Reasoning)', 'mxchat'), |
|
5742
|
+ 'grok-4-1-fast-non-reasoning' => esc_html__('Grok 4.1 Fast (Non-Reasoning)', 'mxchat'), |
|
5743
|
+ ), |
|
5744
|
+ esc_html__('DeepSeek Models', 'mxchat') => array( |
|
5745
|
+ 'deepseek-chat' => esc_html__('DeepSeek-V3', 'mxchat'), |
|
5746
|
+ ), |
|
5747
|
+ esc_html__('Claude Models', 'mxchat') => array( |
|
5748
|
+ 'claude-opus-4-6' => esc_html__('Claude Opus 4.6 (Most Capable - Recommended)', 'mxchat'), |
|
5749
|
+ 'claude-sonnet-4-6' => esc_html__('Claude Sonnet 4.6 (Latest Sonnet - Fast & Capable)', 'mxchat'), |
|
5750
|
+ 'claude-opus-4-5' => esc_html__('Claude Opus 4.5 (Highly Capable)', 'mxchat'), |
|
5751
|
+ 'claude-sonnet-4-5-20250929' => esc_html__('Claude Sonnet 4.5 (Best for Agents & Coding)', 'mxchat'), |
|
5752
|
+ 'claude-opus-4-1-20250805' => esc_html__('Claude Opus 4.1 (Exceptional for Complex Tasks)', 'mxchat'), |
|
5753
|
+ 'claude-haiku-4-5-20251001' => esc_html__('Claude Haiku 4.5 (Fastest & Most Intelligent)', 'mxchat'), |
|
5754
|
+ 'claude-opus-4-20250514' => esc_html__('Claude 4 Opus (Complex Tasks)', 'mxchat'), |
|
5755
|
+ 'claude-sonnet-4-20250514' => esc_html__('Claude 4 Sonnet (High Performance)', 'mxchat'), |
|
5756
|
+ ), |
|
5757
|
+ esc_html__('OpenAI Models', 'mxchat') => array( |
|
5758
|
+ 'gpt-5.2' => esc_html__('GPT-5.2 (Best General-Purpose & Agentic Model)', 'mxchat'), |
|
5759
|
+ 'gpt-5.1-chat-latest' => esc_html__('GPT-5.1 Chat Latest (Recommended)', 'mxchat'), |
|
5760
|
+ 'gpt-5.1-2025-11-13' => esc_html__('GPT-5.1 (Flagship for Coding & Agentic Tasks)', 'mxchat'), |
|
5761
|
+ 'gpt-5' => esc_html__('GPT-5 (Flagship for Coding, Reasoning & Agents)', 'mxchat'), |
|
5762
|
+ 'gpt-5-mini' => esc_html__('GPT-5 Mini (Fast and Lightweight)', 'mxchat'), |
|
5763
|
+ 'gpt-5-nano' => esc_html__('GPT-5 Nano (Fastest & Cheapest for Summarization/Classification)', 'mxchat'), |
|
5764
|
+ ), |
|
5765
|
+ ); |
|
7503
|
5766
|
|
|
7504
|
5767
|
// Retrieve the currently selected model from saved options |
|
7505
|
5768
|
$selected_model = isset($this->options['model']) ? esc_attr($this->options['model']) : 'gpt-5.1-chat-latest'; |
|
7506
|
5769
|
|
|
@@ -7609,15 +5872,15 @@ |
|
7609
|
5872
|
); |
|
7610
|
5873
|
echo '<span class="slider"></span>'; |
|
7611
|
5874
|
echo '</label>'; |
|
7612
|
5875
|
|
|
7613
|
|
- // Test button — branded .mxch-btn with inline SVG (IDs preserved for AJAX binding) |
|
7614
|
|
- echo '<div class="mxch-streaming-test-row">'; |
|
7615
|
|
- echo '<button type="button" id="mxchat-test-streaming-btn" class="mxch-btn mxch-btn-secondary">'; |
|
7616
|
|
- echo '<svg class="mxch-streaming-test-icon" 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" aria-hidden="true"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/></svg>'; |
|
5876
|
+ // Test button with better styling |
|
5877
|
+ echo '<div style="margin-top: 15px;">'; |
|
5878
|
+ echo '<button type="button" id="mxchat-test-streaming-btn" class="button button-secondary">'; |
|
5879
|
+ echo '<span class="dashicons dashicons-admin-tools" style="vertical-align: middle; margin-right: 5px;"></span>'; |
|
7617
|
5880
|
echo esc_html__('Test Streaming Compatibility', 'mxchat'); |
|
7618
|
5881
|
echo '</button>'; |
|
7619
|
|
- echo '<p id="mxchat-test-streaming-result" class="mxch-streaming-test-result"></p>'; |
|
5882
|
+ echo '<p id="mxchat-test-streaming-result" style="margin-top:10px; font-weight: bold;"></p>'; |
|
7620
|
5883
|
echo '</div>'; |
|
7621
|
5884
|
} |
|
7622
|
5885
|
|
|
7623
|
5886
|
// Web Search toggle callback |
|
@@ -7628,37 +5891,22 @@ |
|
7628
|
5891
|
|
|
7629
|
5892
|
// Get current model to determine if we should show/enable the toggle |
|
7630
|
5893
|
$current_model = isset($this->options['model']) ? $this->options['model'] : 'gpt-5.1-chat-latest'; |
|
7631
|
5894
|
|
|
7632
|
|
- // Models that DON'T support web search — OpenAI-docs-driven exception list. |
|
7633
|
|
- // Keep hardcoded; the catalog can't infer "supports web search" per-model, so any |
|
7634
|
|
- // future OpenAI model that lacks Responses-API web_search support is added here. |
|
5895
|
+ // Models that DON'T support web search (per OpenAI docs) |
|
5896
|
+ // gpt-5 with minimal reasoning is handled at API level, gpt-4.1-nano doesn't support it |
|
7635
|
5897
|
$unsupported_models = array('gpt-4.1-nano'); |
|
7636
|
5898
|
|
|
7637
|
|
- // Web-search-capable chat-model allowlists, derived from the central model catalog |
|
7638
|
|
- // (class-mxchat-model-catalog.php). When a new OpenAI/Gemini chat model is added there, |
|
7639
|
|
- // the Web Search toggle picks it up automatically — no edit here. |
|
7640
|
|
- // OpenAI grounds via the Responses-API web_search tool; Gemini grounds natively via the |
|
7641
|
|
- // Google Search tool (plan 46b9ea wired the Gemini dispatch — every shipped Gemini chat |
|
7642
|
|
- // model is 2.x/3.x and grounds, matching that path's empty opt-out list, so all catalog |
|
7643
|
|
- // Gemini models are supported here). |
|
7644
|
|
- if (!class_exists('MxChat_Model_Catalog')) { |
|
7645
|
|
- require_once plugin_dir_path(__FILE__) . 'class-mxchat-model-catalog.php'; |
|
7646
|
|
- } |
|
7647
|
|
- $chat_catalog = MxChat_Model_Catalog::chat_models(); |
|
7648
|
|
- $openai_models = (isset($chat_catalog['openai']['models']) && is_array($chat_catalog['openai']['models'])) |
|
7649
|
|
- ? array_keys($chat_catalog['openai']['models']) |
|
7650
|
|
- : array(); |
|
7651
|
|
- $gemini_models = (isset($chat_catalog['gemini']['models']) && is_array($chat_catalog['gemini']['models'])) |
|
7652
|
|
- ? array_keys($chat_catalog['gemini']['models']) |
|
7653
|
|
- : array(); |
|
5899
|
+ // Check if current model is an OpenAI model that supports web search |
|
5900
|
+ $openai_models = array( |
|
5901
|
+ 'gpt-5.2', 'gpt-5.1-chat-latest', 'gpt-5.1-2025-11-13', 'gpt-5', 'gpt-5-mini', 'gpt-5-nano' |
|
5902
|
+ ); |
|
7654
|
5903
|
|
|
7655
|
|
- $is_capable = in_array($current_model, $openai_models) || in_array($current_model, $gemini_models); |
|
7656
|
|
- $is_supported = $is_capable && !in_array($current_model, $unsupported_models); |
|
5904
|
+ $is_openai = in_array($current_model, $openai_models); |
|
5905
|
+ $is_supported = $is_openai && !in_array($current_model, $unsupported_models); |
|
7657
|
5906
|
|
|
7658
|
|
- // Wrapper div with data attributes for JS to show/hide. data-openai-models is kept for |
|
7659
|
|
- // back-compat; data-gemini-models is the added second provider the JS now also honors. |
|
7660
|
|
- echo '<div id="web-search-toggle-wrapper" data-openai-models="' . esc_attr(implode(',', $openai_models)) . '" data-gemini-models="' . esc_attr(implode(',', $gemini_models)) . '" data-unsupported-models="' . esc_attr(implode(',', $unsupported_models)) . '"' . (!$is_supported ? ' style="display:none;"' : '') . '>'; |
|
5907
|
+ // Wrapper div with data attributes for JS to show/hide |
|
5908
|
+ 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;"' : '') . '>'; |
|
7661
|
5909
|
|
|
7662
|
5910
|
echo '<label class="toggle-switch">'; |
|
7663
|
5911
|
echo sprintf( |
|
7664
|
5912
|
'<input type="checkbox" id="enable_web_search" name="enable_web_search" value="on" %s />', |
|
@@ -7668,12 +5916,12 @@ |
|
7668
|
5916
|
echo '</label>'; |
|
7669
|
5917
|
|
|
7670
|
5918
|
echo '</div>'; |
|
7671
|
5919
|
|
|
7672
|
|
- // Message shown when a model that can't ground (Claude, Grok, DeepSeek, OpenRouter, etc.) is selected |
|
5920
|
+ // Message shown when non-OpenAI model is selected |
|
7673
|
5921
|
echo '<p id="web-search-unavailable-message" class="description" style="color: #666;' . ($is_supported ? ' display:none;' : '') . '">'; |
|
7674
|
5922
|
echo '<span class="dashicons dashicons-info" style="font-size: 16px; vertical-align: middle; margin-right: 4px;"></span>'; |
|
7675
|
|
- echo esc_html__('Web search is only available for OpenAI and Gemini models.', 'mxchat'); |
|
5923
|
+ echo esc_html__('Web search is only available for OpenAI models.', 'mxchat'); |
|
7676
|
5924
|
echo '</p>'; |
|
7677
|
5925
|
} |
|
7678
|
5926
|
|
|
7679
|
5927
|
// AJAX handler to fetch OpenRouter models |
|
@@ -7778,9 +6026,8 @@ |
|
7778
|
6026
|
} else { |
|
7779
|
6027
|
echo '<span style="color: #d63638;">⚠ ' . esc_html__('No API key for Google Gemini detected. Please enter API key in API Keys tab.', 'mxchat') . '</span>'; |
|
7780
|
6028
|
} |
|
7781
|
6029
|
echo '</p>'; |
|
7782
|
|
- |
|
7783
|
6030
|
} |
|
7784
|
6031
|
|
|
7785
|
6032
|
|
|
7786
|
6033
|
public function mxchat_top_bar_title_callback() { |
|
@@ -7882,12 +6129,11 @@ |
|
7882
|
6129
|
$all_options = get_option('mxchat_options', []); |
|
7883
|
6130
|
// Retrieve the saved intro message or use the default |
|
7884
|
6131
|
$default_message = __('Hello! How can I assist you today?', 'mxchat'); |
|
7885
|
6132
|
$saved_message = isset($all_options['intro_message']) ? $all_options['intro_message'] : $default_message; |
|
7886
|
|
- // Escape on output (esc_textarea) — neutralizes any payload already stored before the |
|
7887
|
|
- // Wordfence Stored-XSS fix (CWE-79, plan-3f8158) and prevents </textarea> context-breakout. |
|
6133
|
+ // Output the textarea with the saved value without escaping HTML |
|
7888
|
6134
|
?> |
|
7889
|
|
- <textarea id="intro_message" name="intro_message" rows="5" cols="50"><?php echo esc_textarea( $saved_message ); ?></textarea> |
|
6135
|
+ <textarea id="intro_message" name="intro_message" rows="5" cols="50"><?php echo $saved_message; ?></textarea> |
|
7890
|
6136
|
<p class="description" style="margin-top: 8px;"> |
|
7891
|
6137
|
<?php esc_html_e('Use {visitor_name} to personalize greetings when lead capture is enabled.', 'mxchat'); ?><br> |
|
7892
|
6138
|
<code style="font-size: 12px;"><?php esc_html_e('Example: Hello {visitor_name}! How can I help you today?', 'mxchat'); ?></code> |
|
7893
|
6139
|
</p> |
|
@@ -7990,8 +6236,20 @@ |
|
7990
|
6236
|
echo '</div>'; // End post-type-visibility-options |
|
7991
|
6237
|
echo '</div>'; // End mxchat-autosave-section |
|
7992
|
6238
|
} |
|
7993
|
6239
|
|
|
6240
|
+public function mxchat_frontend_debugger_callback() { |
|
6241
|
+ $show_debugger = isset($this->options['show_frontend_debugger']) ? $this->options['show_frontend_debugger'] : 'on'; |
|
6242
|
+ $checked = ($show_debugger === 'on') ? 'checked' : ''; |
|
6243
|
+ echo '<label class="toggle-switch">'; |
|
6244
|
+ echo sprintf( |
|
6245
|
+ '<input type="checkbox" id="show_frontend_debugger" name="show_frontend_debugger" value="on" %s />', |
|
6246
|
+ esc_attr($checked) |
|
6247
|
+ ); |
|
6248
|
+ echo '<span class="slider"></span>'; |
|
6249
|
+ echo '</label>'; |
|
6250
|
+} |
|
6251
|
+ |
|
7994
|
6252
|
public function mxchat_contextual_awareness_callback() { |
|
7995
|
6253
|
// Get value from options array, default to 'off' |
|
7996
|
6254
|
$contextual_awareness = isset($this->options['contextual_awareness_toggle']) ? $this->options['contextual_awareness_toggle'] : 'off'; |
|
7997
|
6255
|
$checked = ($contextual_awareness === 'on') ? 'checked' : ''; |
|
@@ -8016,125 +6274,8 @@ |
|
8016
|
6274
|
echo '<span class="slider"></span>'; |
|
8017
|
6275
|
echo '</label>'; |
|
8018
|
6276
|
} |
|
8019
|
6277
|
|
|
8020
|
|
-/** |
|
8021
|
|
- * Toggle for the end-of-session satisfaction rating prompt (plan-a5b006). |
|
8022
|
|
- * Default ON. The widget reads this through the localized object; the |
|
8023
|
|
- * mxchat_satisfaction_rating_enabled filter still lets developers force |
|
8024
|
|
- * the value site-wide. |
|
8025
|
|
- * |
|
8026
|
|
- * The 5 customization fields (idle/question/thanks/placeholder/saved) are |
|
8027
|
|
- * rendered inline here inside a single wrapper div whose initial display |
|
8028
|
|
- * is set server-side from the toggle value (plan-29caac). Mirrors the |
|
8029
|
|
- * auto-display chatbot pattern at mxchat_append_to_body_callback — no |
|
8030
|
|
- * DOMContentLoaded race because rows exist as direct children of this |
|
8031
|
|
- * callback's output, and the wrapper's display: none is inline at render |
|
8032
|
|
- * time so refresh shows the correct state with no flash. |
|
8033
|
|
- */ |
|
8034
|
|
-public function mxchat_satisfaction_rating_toggle_callback() { |
|
8035
|
|
- $options = $this->options; |
|
8036
|
|
- $value = isset($options['satisfaction_rating_enabled']) ? $options['satisfaction_rating_enabled'] : 'off'; |
|
8037
|
|
- $checked = ($value === 'on') ? 'checked' : ''; |
|
8038
|
|
- |
|
8039
|
|
- $idle = isset($options['satisfaction_rating_idle_seconds']) ? intval($options['satisfaction_rating_idle_seconds']) : 60; |
|
8040
|
|
- $idle = max(5, min(600, $idle)); |
|
8041
|
|
- $question = isset($options['satisfaction_rating_question']) ? $options['satisfaction_rating_question'] : ''; |
|
8042
|
|
- $thanks = isset($options['satisfaction_rating_thanks']) ? $options['satisfaction_rating_thanks'] : ''; |
|
8043
|
|
- $placeholder = isset($options['satisfaction_rating_placeholder']) ? $options['satisfaction_rating_placeholder'] : ''; |
|
8044
|
|
- $saved = isset($options['satisfaction_rating_saved']) ? $options['satisfaction_rating_saved'] : ''; |
|
8045
|
|
- |
|
8046
|
|
- echo '<div class="mxchat-autosave-section">'; |
|
8047
|
|
- |
|
8048
|
|
- echo '<label class="toggle-switch">'; |
|
8049
|
|
- echo sprintf( |
|
8050
|
|
- '<input type="checkbox" id="satisfaction_rating_enabled" name="satisfaction_rating_enabled" value="on" %s />', |
|
8051
|
|
- esc_attr($checked) |
|
8052
|
|
- ); |
|
8053
|
|
- echo '<span class="slider"></span>'; |
|
8054
|
|
- echo '</label>'; |
|
8055
|
|
- |
|
8056
|
|
- ?> |
|
8057
|
|
- <style> |
|
8058
|
|
- .mxchat-sub-options-field { margin: 12px 0; } |
|
8059
|
|
- .mxchat-sub-options-field label { display: inline-block; margin-bottom: 4px; } |
|
8060
|
|
- #satisfaction-rating-sub-options h4 { margin: 16px 0 8px; } |
|
8061
|
|
- </style> |
|
8062
|
|
- <?php |
|
8063
|
|
- |
|
8064
|
|
- $display_style = ($value === 'on') ? '' : 'display: none;'; |
|
8065
|
|
- echo '<div id="satisfaction-rating-sub-options" class="mxchat-sub-options" style="' . esc_attr($display_style) . '">'; |
|
8066
|
|
- |
|
8067
|
|
- echo '<h4>' . esc_html__('Customize the prompt (optional)', 'mxchat') . '</h4>'; |
|
8068
|
|
- |
|
8069
|
|
- echo '<div class="mxchat-sub-options-field">'; |
|
8070
|
|
- echo '<label for="satisfaction_rating_idle_seconds"><strong>' . esc_html__('Idle Timeout', 'mxchat') . '</strong></label><br />'; |
|
8071
|
|
- printf( |
|
8072
|
|
- '<input type="number" id="satisfaction_rating_idle_seconds" name="satisfaction_rating_idle_seconds" value="%d" min="5" max="600" step="1" class="small-text" /> <span class="description">%s</span>', |
|
8073
|
|
- (int) $idle, |
|
8074
|
|
- esc_html__('seconds of user inactivity before the prompt appears (5-600)', 'mxchat') |
|
8075
|
|
- ); |
|
8076
|
|
- echo '</div>'; |
|
8077
|
|
- |
|
8078
|
|
- echo '<div class="mxchat-sub-options-field">'; |
|
8079
|
|
- echo '<label for="satisfaction_rating_question"><strong>' . esc_html__('Prompt Question', 'mxchat') . '</strong></label><br />'; |
|
8080
|
|
- printf( |
|
8081
|
|
- '<input type="text" id="satisfaction_rating_question" name="satisfaction_rating_question" value="%s" maxlength="200" class="regular-text" placeholder="%s" />', |
|
8082
|
|
- esc_attr($question), |
|
8083
|
|
- esc_attr__('Was this helpful?', 'mxchat') |
|
8084
|
|
- ); |
|
8085
|
|
- echo '<p class="description">' . esc_html__('Leave blank for the default. Shown above the thumbs up/down.', 'mxchat') . '</p>'; |
|
8086
|
|
- echo '</div>'; |
|
8087
|
|
- |
|
8088
|
|
- echo '<div class="mxchat-sub-options-field">'; |
|
8089
|
|
- echo '<label for="satisfaction_rating_thanks"><strong>' . esc_html__('Thank-You Message', 'mxchat') . '</strong></label><br />'; |
|
8090
|
|
- printf( |
|
8091
|
|
- '<input type="text" id="satisfaction_rating_thanks" name="satisfaction_rating_thanks" value="%s" maxlength="300" class="regular-text" placeholder="%s" />', |
|
8092
|
|
- esc_attr($thanks), |
|
8093
|
|
- esc_attr__('Thanks! Anything we should improve? (optional)', 'mxchat') |
|
8094
|
|
- ); |
|
8095
|
|
- echo '<p class="description">' . esc_html__('Leave blank for the default. Shown after the user clicks a thumb.', 'mxchat') . '</p>'; |
|
8096
|
|
- echo '</div>'; |
|
8097
|
|
- |
|
8098
|
|
- echo '<div class="mxchat-sub-options-field">'; |
|
8099
|
|
- echo '<label for="satisfaction_rating_placeholder"><strong>' . esc_html__('Feedback Placeholder', 'mxchat') . '</strong></label><br />'; |
|
8100
|
|
- printf( |
|
8101
|
|
- '<input type="text" id="satisfaction_rating_placeholder" name="satisfaction_rating_placeholder" value="%s" maxlength="200" class="regular-text" placeholder="%s" />', |
|
8102
|
|
- esc_attr($placeholder), |
|
8103
|
|
- esc_attr__('Tell us what could be better…', 'mxchat') |
|
8104
|
|
- ); |
|
8105
|
|
- echo '<p class="description">' . esc_html__('Leave blank for the default. Placeholder text inside the feedback textarea.', 'mxchat') . '</p>'; |
|
8106
|
|
- echo '</div>'; |
|
8107
|
|
- |
|
8108
|
|
- echo '<div class="mxchat-sub-options-field">'; |
|
8109
|
|
- echo '<label for="satisfaction_rating_saved"><strong>' . esc_html__('Saved Confirmation', 'mxchat') . '</strong></label><br />'; |
|
8110
|
|
- printf( |
|
8111
|
|
- '<input type="text" id="satisfaction_rating_saved" name="satisfaction_rating_saved" value="%s" maxlength="200" class="regular-text" placeholder="%s" />', |
|
8112
|
|
- esc_attr($saved), |
|
8113
|
|
- esc_attr__('Thanks for the feedback.', 'mxchat') |
|
8114
|
|
- ); |
|
8115
|
|
- echo '<p class="description">' . esc_html__('Leave blank for the default. Shown after the feedback is sent.', 'mxchat') . '</p>'; |
|
8116
|
|
- echo '</div>'; |
|
8117
|
|
- |
|
8118
|
|
- echo '</div>'; // #satisfaction-rating-sub-options |
|
8119
|
|
- echo '</div>'; // .mxchat-autosave-section |
|
8120
|
|
- |
|
8121
|
|
- ?> |
|
8122
|
|
- <script> |
|
8123
|
|
- (function() { |
|
8124
|
|
- document.addEventListener('DOMContentLoaded', function() { |
|
8125
|
|
- var toggle = document.getElementById('satisfaction_rating_enabled'); |
|
8126
|
|
- var subOptions = document.getElementById('satisfaction-rating-sub-options'); |
|
8127
|
|
- if (!toggle || !subOptions) return; |
|
8128
|
|
- toggle.addEventListener('change', function() { |
|
8129
|
|
- subOptions.style.display = toggle.checked ? '' : 'none'; |
|
8130
|
|
- }); |
|
8131
|
|
- }); |
|
8132
|
|
- })(); |
|
8133
|
|
- </script> |
|
8134
|
|
- <?php |
|
8135
|
|
-} |
|
8136
|
|
- |
|
8137
|
6278
|
public function mxchat_privacy_toggle_callback() { |
|
8138
|
6279
|
// Load from mxchat_options array |
|
8139
|
6280
|
$options = get_option('mxchat_options', []); |
|
8140
|
6281
|
|
|
@@ -8217,53 +6358,8 @@ |
|
8217
|
6358
|
echo '<span class="slider"></span>'; |
|
8218
|
6359
|
echo '</label>'; |
|
8219
|
6360
|
} |
|
8220
|
6361
|
|
|
8221
|
|
-public function mxchat_print_button_toggle_callback() { |
|
8222
|
|
- // Load from mxchat_options array |
|
8223
|
|
- $options = get_option('mxchat_options', []); |
|
8224
|
|
- |
|
8225
|
|
- // Default ON — the option was previously unexposed and the button always showed. |
|
8226
|
|
- $print_button_enabled = isset($options['print_button_enabled']) ? $options['print_button_enabled'] : 'on'; |
|
8227
|
|
- $checked = ($print_button_enabled === 'on') ? 'checked' : ''; |
|
8228
|
|
- |
|
8229
|
|
- // Output the toggle switch |
|
8230
|
|
- echo '<label class="toggle-switch">'; |
|
8231
|
|
- echo sprintf( |
|
8232
|
|
- '<input type="checkbox" id="print_button_enabled" name="print_button_enabled" value="on" %s />', |
|
8233
|
|
- esc_attr($checked) |
|
8234
|
|
- ); |
|
8235
|
|
- echo '<span class="slider"></span>'; |
|
8236
|
|
- echo '</label>'; |
|
8237
|
|
-} |
|
8238
|
|
- |
|
8239
|
|
-public function mxchat_reset_chat_toggle_callback() { |
|
8240
|
|
- // Load from mxchat_options array. plan ac2e81 — default OFF (new, opt-in). |
|
8241
|
|
- $options = get_option('mxchat_options', []); |
|
8242
|
|
- $reset_chat_enabled = isset($options['reset_chat_enabled']) ? $options['reset_chat_enabled'] : 'off'; |
|
8243
|
|
- $checked = ($reset_chat_enabled === 'on') ? 'checked' : ''; |
|
8244
|
|
- |
|
8245
|
|
- echo '<label class="toggle-switch">'; |
|
8246
|
|
- echo sprintf( |
|
8247
|
|
- '<input type="checkbox" id="reset_chat_enabled" name="reset_chat_enabled" value="on" %s />', |
|
8248
|
|
- esc_attr($checked) |
|
8249
|
|
- ); |
|
8250
|
|
- echo '<span class="slider"></span>'; |
|
8251
|
|
- echo '</label>'; |
|
8252
|
|
-} |
|
8253
|
|
- |
|
8254
|
|
-public function mxchat_reset_chat_label_callback() { |
|
8255
|
|
- // Editable label for the "Start new chat" menu item. plan ac2e81. |
|
8256
|
|
- $options = get_option('mxchat_options', []); |
|
8257
|
|
- $reset_chat_label = isset($options['reset_chat_label']) ? $options['reset_chat_label'] : ''; |
|
8258
|
|
- |
|
8259
|
|
- printf( |
|
8260
|
|
- '<input type="text" id="reset_chat_label" name="reset_chat_label" value="%s" placeholder="%s" class="regular-text" />', |
|
8261
|
|
- esc_attr($reset_chat_label), |
|
8262
|
|
- esc_attr__('Start new chat', 'mxchat') |
|
8263
|
|
- ); |
|
8264
|
|
-} |
|
8265
|
|
- |
|
8266
|
6362
|
public function mxchat_popular_question_1_callback() { |
|
8267
|
6363
|
// Load the full plugin options array |
|
8268
|
6364
|
$all_options = get_option('mxchat_options', []); |
|
8269
|
6365
|
|
|
@@ -8795,35 +6891,14 @@ |
|
8795
|
6891
|
); |
|
8796
|
6892
|
echo '</div>'; |
|
8797
|
6893
|
} |
|
8798
|
6894
|
|
|
8799
|
|
-public function mxchat_max_input_length_callback() { |
|
8800
|
|
- // Load from mxchat_options array (plan a3fae2 part C). |
|
8801
|
|
- $options = get_option('mxchat_options', []); |
|
8802
|
|
- |
|
8803
|
|
- // 0 = unlimited (default — preserves current behavior, no cap). |
|
8804
|
|
- $max_input_length = isset($options['max_input_length']) ? intval($options['max_input_length']) : 0; |
|
8805
|
|
- |
|
8806
|
|
- echo sprintf( |
|
8807
|
|
- '<input type="number" |
|
8808
|
|
- id="max_input_length" |
|
8809
|
|
- name="max_input_length" |
|
8810
|
|
- min="0" |
|
8811
|
|
- max="100000" |
|
8812
|
|
- step="1" |
|
8813
|
|
- value="%s" |
|
8814
|
|
- placeholder="0" |
|
8815
|
|
- class="mxch-input mxch-input-sm" />', |
|
8816
|
|
- esc_attr($max_input_length) |
|
8817
|
|
- ); |
|
8818
|
|
-} |
|
8819
|
|
- |
|
8820
|
6895
|
public function mxchat_rag_sources_limit_callback() { |
|
8821
|
6896
|
// Load from mxchat_options array |
|
8822
|
6897
|
$options = get_option('mxchat_options', []); |
|
8823
|
6898
|
|
|
8824
|
|
- // Get value from options array with default of 3 |
|
8825
|
|
- $rag_sources_limit = isset($options['rag_sources_limit']) ? intval($options['rag_sources_limit']) : 3; |
|
6899
|
+ // Get value from options array with default of 6 |
|
6900
|
+ $rag_sources_limit = isset($options['rag_sources_limit']) ? intval($options['rag_sources_limit']) : 6; |
|
8826
|
6901
|
|
|
8827
|
6902
|
echo '<div class="slider-container">'; |
|
8828
|
6903
|
echo sprintf( |
|
8829
|
6904
|
'<input type="range" |
|
@@ -8842,46 +6917,8 @@ |
|
8842
|
6917
|
); |
|
8843
|
6918
|
echo '</div>'; |
|
8844
|
6919
|
} |
|
8845
|
6920
|
|
|
8846
|
|
-public function mxchat_rag_chunks_limit_callback() { |
|
8847
|
|
- // Load from mxchat_options array |
|
8848
|
|
- $options = get_option('mxchat_options', []); |
|
8849
|
|
- |
|
8850
|
|
- // Get value from options array with default of 15 |
|
8851
|
|
- $rag_chunks_limit = isset($options['rag_chunks_limit']) ? intval($options['rag_chunks_limit']) : 15; |
|
8852
|
|
- |
|
8853
|
|
- echo '<div class="slider-container">'; |
|
8854
|
|
- echo sprintf( |
|
8855
|
|
- '<input type="range" |
|
8856
|
|
- id="rag_chunks_limit" |
|
8857
|
|
- name="rag_chunks_limit" |
|
8858
|
|
- min="8" |
|
8859
|
|
- max="20" |
|
8860
|
|
- step="1" |
|
8861
|
|
- value="%s" |
|
8862
|
|
- class="range-slider" />', |
|
8863
|
|
- esc_attr($rag_chunks_limit) |
|
8864
|
|
- ); |
|
8865
|
|
- echo sprintf( |
|
8866
|
|
- '<span id="rag_chunks_limit_value" class="range-value">%s</span>', |
|
8867
|
|
- esc_html($rag_chunks_limit) |
|
8868
|
|
- ); |
|
8869
|
|
- echo '</div>'; |
|
8870
|
|
-} |
|
8871
|
|
- |
|
8872
|
|
-/** |
|
8873
|
|
- * Add body class on the Onboarding wizard page so CSS-only chrome surgery |
|
8874
|
|
- * (collapsing the WP admin sidebar) only applies on this page. |
|
8875
|
|
- * Plan: plan-mxchat-20260527-905439. |
|
8876
|
|
- */ |
|
8877
|
|
-public function mxchat_add_onboarding_body_class($classes) { |
|
8878
|
|
- if (isset($_GET['page']) && $_GET['page'] === 'mxchat-onboarding') { |
|
8879
|
|
- $classes .= ' mxchat-onboarding-focused'; |
|
8880
|
|
- } |
|
8881
|
|
- return $classes; |
|
8882
|
|
-} |
|
8883
|
|
- |
|
8884
|
6921
|
public function mxchat_enqueue_admin_assets() { |
|
8885
|
6922
|
// Get plugin version |
|
8886
|
6923
|
$version = MXCHAT_VERSION; |
|
8887
|
6924
|
|
|
@@ -8921,13 +6958,8 @@ |
|
8921
|
6958
|
wp_enqueue_style('mxchat-content-selector-css', $plugin_url . 'css/content-selector.css', array(), $version); |
|
8922
|
6959
|
wp_enqueue_script('mxchat-content-selector-js', $plugin_url . 'js/content-selector.js', array('jquery'), $version, true); |
|
8923
|
6960
|
// Add the knowledge processing script (common script needed for WordPress dismiss functionality) |
|
8924
|
6961
|
wp_enqueue_script('mxchat-knowledge-processing', $plugin_url . 'js/knowledge-processing.js', array('jquery', 'common'), $version, true); |
|
8925
|
|
- // Per-entry "View indexed content" inspector (plan-d8cb4b) reuses the |
|
8926
|
|
- // Testing tab's match-card / chunk-detail components, which are scoped |
|
8927
|
|
- // under .mxch-testing-results. Load that stylesheet here so the inspector |
|
8928
|
|
- // modal renders identically to the Testing tab. |
|
8929
|
|
- wp_enqueue_style('mxchat-admin-testing-css', $plugin_url . 'css/admin-testing-tab.css', array('mxchat-admin-sidebar-css'), $version); |
|
8930
|
6962
|
break; |
|
8931
|
6963
|
|
|
8932
|
6964
|
case 'mxchat-transcripts': |
|
8933
|
6965
|
// Load admin sidebar CSS first (shared styles for sidebar navigation) |
|
@@ -8952,16 +6984,12 @@ |
|
8952
|
6984
|
'addNonce' => wp_create_nonce('mxchat_add_intent_nonce'), |
|
8953
|
6985
|
'editNonce' => wp_create_nonce('mxchat_edit_intent'), |
|
8954
|
6986
|
'deleteNonce' => wp_create_nonce('mxchat_delete_intent_nonce'), |
|
8955
|
6987
|
'toggleNonce' => wp_create_nonce('mxchat_actions_nonce'), |
|
8956
|
|
- 'addPhraseNonce' => wp_create_nonce('mxchat_add_phrase_nonce'), |
|
8957
|
|
- 'deletePhraseNonce' => wp_create_nonce('mxchat_delete_phrase_nonce'), |
|
8958
|
|
- 'getPhrasesNonce' => wp_create_nonce('mxchat_get_phrases_nonce'), |
|
8959
|
|
- 'deleteLegacyNonce' => wp_create_nonce('mxchat_delete_legacy_nonce'), |
|
8960
|
6988
|
'isActivated' => $is_activated, |
|
8961
|
6989
|
'i18n' => array( |
|
8962
|
|
- 'confirmDelete' => __('Are you sure you want to delete this trigger phrase?', 'mxchat'), |
|
8963
|
|
- 'confirmBulkDelete' => __('Are you sure you want to delete the selected trigger phrases?', 'mxchat'), |
|
6990
|
+ 'confirmDelete' => __('Are you sure you want to delete this action?', 'mxchat'), |
|
6991
|
+ 'confirmBulkDelete' => __('Are you sure you want to delete the selected actions?', 'mxchat'), |
|
8964
|
6992
|
'saving' => __('Saving...', 'mxchat'), |
|
8965
|
6993
|
'saved' => __('Saved successfully!', 'mxchat'), |
|
8966
|
6994
|
'error' => __('An error occurred. Please try again.', 'mxchat'), |
|
8967
|
6995
|
'proRequired' => __('This feature requires MxChat Pro.', 'mxchat'), |
|
@@ -8988,38 +7016,8 @@ |
|
8988
|
7016
|
wp_enqueue_style('mxchat-content-css', $plugin_url . 'css/admin-content.css', array('mxchat-admin-sidebar-css'), $version); |
|
8989
|
7017
|
// Load content page JavaScript |
|
8990
|
7018
|
wp_enqueue_script('mxchat-content-js', $plugin_url . 'js/mxchat-content.js', array('jquery'), $version, true); |
|
8991
|
7019
|
break; |
|
8992
|
|
- |
|
8993
|
|
- case 'mxchat-api-access': |
|
8994
|
|
- // Shared sidebar shell (CSS + JS for tab switching / mobile menu / copy buttons). |
|
8995
|
|
- wp_enqueue_style('mxchat-admin-sidebar-css', $plugin_url . 'css/admin-sidebar.css', array(), $version); |
|
8996
|
|
- wp_enqueue_script('mxchat-admin-sidebar-js', $plugin_url . 'js/admin-sidebar.js', array(), $version, true); |
|
8997
|
|
- wp_localize_script('mxchat-admin-sidebar-js', 'MxChatAdminSidebarI18n', array( |
|
8998
|
|
- 'copied' => __('Copied', 'mxchat'), |
|
8999
|
|
- )); |
|
9000
|
|
- break; |
|
9001
|
|
- |
|
9002
|
|
- case 'mxchat-max': |
|
9003
|
|
- case 'mxchat-onboarding': |
|
9004
|
|
- // Onboarding page — uses the shared admin shell PLUS the wizard |
|
9005
|
|
- // overlay (plan-905439). admin-onboarding-wizard.css scopes the |
|
9006
|
|
- // WP-chrome surgery to body.mxchat-onboarding-focused so it only |
|
9007
|
|
- // applies on THIS page. The body class is added below via the |
|
9008
|
|
- // admin_body_class filter. |
|
9009
|
|
- wp_enqueue_style('mxchat-admin-sidebar-css', $plugin_url . 'css/admin-sidebar.css', array(), $version); |
|
9010
|
|
- wp_enqueue_script('mxchat-admin-sidebar-js', $plugin_url . 'js/admin-sidebar.js', array(), $version, true); |
|
9011
|
|
- wp_localize_script('mxchat-admin-sidebar-js', 'MxChatAdminSidebarI18n', array( |
|
9012
|
|
- 'copied' => __('Copied', 'mxchat'), |
|
9013
|
|
- )); |
|
9014
|
|
- // admin-style.css provides the .mxchat-instructions-modal-* classes |
|
9015
|
|
- // the new Behavior step's "View Sample Instructions" modal needs. |
|
9016
|
|
- // Enqueued AFTER admin-sidebar.css but BEFORE the wizard overlay |
|
9017
|
|
- // so the wizard's own rules win where they collide. plan-a2e4d6. |
|
9018
|
|
- wp_enqueue_style('mxchat-admin-style-css', $plugin_url . 'css/admin-style.css', array('mxchat-admin-sidebar-css'), $version); |
|
9019
|
|
- wp_enqueue_style('mxchat-admin-onboarding-wizard-css', $plugin_url . 'css/admin-onboarding-wizard.css', array('mxchat-admin-sidebar-css', 'mxchat-admin-style-css'), $version); |
|
9020
|
|
- wp_enqueue_script('mxchat-admin-onboarding-wizard-js', $plugin_url . 'js/admin-onboarding-wizard.js', array(), $version, true); |
|
9021
|
|
- break; |
|
9022
|
7020
|
default: |
|
9023
|
7021
|
wp_enqueue_script( |
|
9024
|
7022
|
'mxchat-test-streaming-js', |
|
9025
|
7023
|
$plugin_url . 'js/mxchat-test-streaming.js', |
|
@@ -9032,77 +7030,8 @@ |
|
9032
|
7030
|
'ajax_url' => admin_url('admin-ajax.php'), |
|
9033
|
7031
|
'nonce' => wp_create_nonce('mxchat_test_streaming_nonce'), |
|
9034
|
7032
|
'settings_nonce' => wp_create_nonce('mxchat_save_setting_nonce') |
|
9035
|
7033
|
]); |
|
9036
|
|
- |
|
9037
|
|
- // Testing Tab: Load chatbot assets for the embedded testing chatbot |
|
9038
|
|
- wp_enqueue_style('mxchat-chat-css', $plugin_url . 'css/chat-style.css', array(), $version); |
|
9039
|
|
- wp_enqueue_style('mxchat-admin-testing-css', $plugin_url . 'css/admin-testing-tab.css', array('mxchat-admin-sidebar-css', 'mxchat-chat-css'), $version); |
|
9040
|
|
- |
|
9041
|
|
- wp_enqueue_script('mxchat-chat-js', $plugin_url . 'js/chat-script.js', array('jquery'), $version, true); |
|
9042
|
|
- wp_enqueue_script('mxchat-admin-testing-js', $plugin_url . 'js/admin-testing-tab.js', array('jquery', 'mxchat-chat-js'), $version, true); |
|
9043
|
|
- |
|
9044
|
|
- // Allow add-ons to enqueue their public CSS/JS for the testing chatbot |
|
9045
|
|
- do_action('mxchat_enqueue_testing_tab_assets'); |
|
9046
|
|
- |
|
9047
|
|
- // Localize mxchatChat for the chatbot JS (same settings as frontend) |
|
9048
|
|
- $options = get_option('mxchat_options', array()); |
|
9049
|
|
- $prompts_options = get_option('mxchat_prompts_options', array()); |
|
9050
|
|
- $theme_options = get_option('mxchat_theme_options', array()); |
|
9051
|
|
- $ai_theme_active = !empty($theme_options['active_ai_theme_css']); |
|
9052
|
|
- $has_bot_theme_assignments = !empty($theme_options['bot_theme_assignments']); |
|
9053
|
|
- $skip_inline_colors = $ai_theme_active || $has_bot_theme_assignments; |
|
9054
|
|
- |
|
9055
|
|
- wp_localize_script('mxchat-chat-js', 'mxchatChat', array( |
|
9056
|
|
- 'ajax_url' => admin_url('admin-ajax.php'), |
|
9057
|
|
- 'nonce' => wp_create_nonce('mxchat_chat_nonce'), |
|
9058
|
|
- 'model' => isset($options['model']) ? $options['model'] : 'gpt-5.1-chat-latest', |
|
9059
|
|
- 'enable_streaming_toggle' => isset($options['enable_streaming_toggle']) ? $options['enable_streaming_toggle'] : 'on', |
|
9060
|
|
- 'contextual_awareness_toggle' => isset($options['contextual_awareness_toggle']) ? $options['contextual_awareness_toggle'] : 'off', |
|
9061
|
|
- 'link_target_toggle' => $options['link_target_toggle'] ?? 'off', |
|
9062
|
|
- 'rate_limit_message' => $options['rate_limit_message'] ?? 'Rate limit exceeded. Please try again later.', |
|
9063
|
|
- 'complianz_toggle' => isset($options['complianz_toggle']) && $options['complianz_toggle'] === 'on', |
|
9064
|
|
- 'user_message_bg_color' => $options['user_message_bg_color'] ?? '#fff', |
|
9065
|
|
- 'user_message_font_color' => $options['user_message_font_color'] ?? '#212121', |
|
9066
|
|
- 'bot_message_bg_color' => $options['bot_message_bg_color'] ?? '#212121', |
|
9067
|
|
- 'bot_message_font_color' => $options['bot_message_font_color'] ?? '#fff', |
|
9068
|
|
- 'top_bar_bg_color' => $options['top_bar_bg_color'] ?? '#212121', |
|
9069
|
|
- 'send_button_font_color' => $options['send_button_font_color'] ?? '#212121', |
|
9070
|
|
- 'close_button_color' => $options['close_button_color'] ?? '#fff', |
|
9071
|
|
- 'chatbot_background_color' => $options['chatbot_background_color'] ?? '#212121', |
|
9072
|
|
- 'chatbot_bg_color' => $options['chatbot_bg_color'] ?? '#fff', |
|
9073
|
|
- 'icon_color' => $options['icon_color'] ?? '#fff', |
|
9074
|
|
- 'chat_input_font_color' => $options['chat_input_font_color'] ?? '#212121', |
|
9075
|
|
- 'chat_persistence_toggle' => 'off', // Always off for testing chatbot |
|
9076
|
|
- 'appendWidgetToBody' => 'off', |
|
9077
|
|
- 'live_agent_message_bg_color' => $options['live_agent_message_bg_color'] ?? '#ffffff', |
|
9078
|
|
- 'live_agent_message_font_color' => $options['live_agent_message_font_color'] ?? '#333333', |
|
9079
|
|
- 'chat_toolbar_toggle' => $options['chat_toolbar_toggle'] ?? 'off', |
|
9080
|
|
- 'mode_indicator_bg_color' => $options['mode_indicator_bg_color'] ?? '#767676', |
|
9081
|
|
- 'mode_indicator_font_color' => $options['mode_indicator_font_color'] ?? '#ffffff', |
|
9082
|
|
- 'toolbar_icon_color' => $options['toolbar_icon_color'] ?? '#212121', |
|
9083
|
|
- 'use_pinecone' => $prompts_options['mxchat_use_pinecone'] ?? '0', |
|
9084
|
|
- 'email_collection_enabled' => 'off', // No email collection in testing |
|
9085
|
|
- 'initial_email_state' => null, |
|
9086
|
|
- 'skip_email_check' => true, |
|
9087
|
|
- 'pinecone_enabled' => isset($prompts_options['mxchat_use_pinecone']) && $prompts_options['mxchat_use_pinecone'] === '1', |
|
9088
|
|
- 'skip_inline_colors' => $skip_inline_colors, |
|
9089
|
|
- 'bot_theme_assignments' => $theme_options['bot_theme_assignments'] ?? array() |
|
9090
|
|
- )); |
|
9091
|
|
- |
|
9092
|
|
- // Localize testing tab script data |
|
9093
|
|
- wp_localize_script('mxchat-admin-testing-js', 'mxchatAdminTestData', array( |
|
9094
|
|
- 'ajaxUrl' => admin_url('admin-ajax.php'), |
|
9095
|
|
- 'nonce' => wp_create_nonce('mxchat_test_nonce'), |
|
9096
|
|
- 'isAdmin' => true, |
|
9097
|
|
- 'testingEnabled' => true |
|
9098
|
|
- )); |
|
9099
|
|
- |
|
9100
|
|
- // Add testing enabled flag for the chatbot to return debug data |
|
9101
|
|
- add_action('admin_footer', function() { |
|
9102
|
|
- echo '<script>window.mxchatTestingEnabled = true;</script>'; |
|
9103
|
|
- }); |
|
9104
|
|
- |
|
9105
|
7034
|
break; |
|
9106
|
7035
|
} |
|
9107
|
7036
|
} |
|
9108
|
7037
|
private function localize_admin_scripts($current_page) { |
|
@@ -9117,12 +7046,8 @@ |
|
9117
|
7046
|
'export_nonce' => wp_create_nonce('mxchat_export_transcripts'), |
|
9118
|
7047
|
'actions_nonce' => wp_create_nonce('mxchat_actions_nonce'), |
|
9119
|
7048
|
'add_intent_nonce' => wp_create_nonce('mxchat_add_intent_nonce'), |
|
9120
|
7049
|
'edit_intent_nonce' => wp_create_nonce('mxchat_edit_intent'), |
|
9121
|
|
- 'add_phrase_nonce' => wp_create_nonce('mxchat_add_phrase_nonce'), |
|
9122
|
|
- 'delete_phrase_nonce' => wp_create_nonce('mxchat_delete_phrase_nonce'), |
|
9123
|
|
- 'get_phrases_nonce' => wp_create_nonce('mxchat_get_phrases_nonce'), |
|
9124
|
|
- 'delete_legacy_nonce' => wp_create_nonce('mxchat_delete_legacy_nonce'), |
|
9125
|
7050
|
'toggle_action_nonce' => wp_create_nonce('mxchat_actions_nonce'), |
|
9126
|
7051
|
'fetch_openrouter_models_nonce' => wp_create_nonce('mxchat_fetch_openrouter_models'), |
|
9127
|
7052
|
'is_activated' => $this->is_activated ? '1' : '0', |
|
9128
|
7053
|
'status_refresh_interval' => 5000, |
|
@@ -9132,16 +7057,8 @@ |
|
9132
|
7057
|
|
|
9133
|
7058
|
// Localize main admin script with base data |
|
9134
|
7059
|
wp_localize_script('mxchat-admin-js', 'mxchatAdmin', $base_data); |
|
9135
|
7060
|
|
|
9136
|
|
- // Canonical chat-model catalog for the modal picker grid |
|
9137
|
|
- // (plan-d14e89). Adding a model in class-mxchat-model-catalog.php |
|
9138
|
|
- // automatically appears here. |
|
9139
|
|
- if (!class_exists('MxChat_Model_Catalog')) { |
|
9140
|
|
- require_once plugin_dir_path(__FILE__) . 'class-mxchat-model-catalog.php'; |
|
9141
|
|
- } |
|
9142
|
|
- wp_localize_script('mxchat-admin-js', 'mxchatChatModelCatalog', MxChat_Model_Catalog::js_picker_shape()); |
|
9143
|
|
- |
|
9144
|
7061
|
// Page-specific localizations |
|
9145
|
7062
|
$this->localize_page_specific_scripts($current_page); |
|
9146
|
7063
|
} |
|
9147
|
7064
|
private function localize_page_specific_scripts($current_page) { |
|
@@ -9153,22 +7070,17 @@ |
|
9153
|
7070
|
'nonce' => wp_create_nonce('mxchat_status_nonce') |
|
9154
|
7071
|
)); |
|
9155
|
7072
|
|
|
9156
|
7073
|
// Content selector localization |
|
9157
|
|
- $mxchat_options_for_selector = get_option('mxchat_options', array()); |
|
9158
|
|
- $acf_pdf_extract_default = !empty($mxchat_options_for_selector['acf_pdf_extract_default']); |
|
9159
|
7074
|
wp_localize_script('mxchat-content-selector-js', 'mxchatSelector', array( |
|
9160
|
7075
|
'ajaxurl' => admin_url('admin-ajax.php'), |
|
9161
|
7076
|
'nonce' => wp_create_nonce('mxchat_content_selector_nonce'), |
|
9162
|
|
- 'acfPdfExtractDefault' => $acf_pdf_extract_default ? 1 : 0, |
|
9163
|
7077
|
'i18n' => array( |
|
9164
|
7078
|
'searchPlaceholder' => __('Search posts and pages...', 'mxchat'), |
|
9165
|
7079
|
'selectAll' => __('Select All', 'mxchat'), |
|
9166
|
7080
|
'process' => __('Process Selected', 'mxchat'), |
|
9167
|
7081
|
'cancel' => __('Cancel', 'mxchat'), |
|
9168
|
|
- 'noResults' => __('No content found.', 'mxchat'), |
|
9169
|
|
- 'extractingPdfs' => __('extracting %d PDF(s)...', 'mxchat'), |
|
9170
|
|
- 'pdfExtractedSuffix' => __(' (%d PDF(s) extracted)', 'mxchat') |
|
7082
|
+ 'noResults' => __('No content found.', 'mxchat') |
|
9171
|
7083
|
) |
|
9172
|
7084
|
)); |
|
9173
|
7085
|
|
|
9174
|
7086
|
wp_localize_script('mxchat-knowledge-processing', 'mxchatAdmin', array( |
|
@@ -9309,13 +7221,8 @@ |
|
9309
|
7221
|
$new_input['rag_sources_limit'] = absint($input['rag_sources_limit']); // Ensure it's an integer |
|
9310
|
7222
|
$new_input['rag_sources_limit'] = min(max($new_input['rag_sources_limit'], 3), 10); // Enforce range 3-10 |
|
9311
|
7223
|
} |
|
9312
|
7224
|
|
|
9313
|
|
- if (isset($input['rag_chunks_limit'])) { |
|
9314
|
|
- $new_input['rag_chunks_limit'] = absint($input['rag_chunks_limit']); // Ensure it's an integer |
|
9315
|
|
- $new_input['rag_chunks_limit'] = min(max($new_input['rag_chunks_limit'], 8), 20); // Enforce range 8-20 |
|
9316
|
|
- } |
|
9317
|
|
- |
|
9318
|
7225
|
if (isset($input['xai_api_key'])) { |
|
9319
|
7226
|
$new_input['xai_api_key'] = sanitize_text_field($input['xai_api_key']); |
|
9320
|
7227
|
} |
|
9321
|
7228
|
|
|
@@ -9398,48 +7305,16 @@ |
|
9398
|
7305
|
if (isset($input['contextual_awareness_toggle'])) { |
|
9399
|
7306
|
$new_input['contextual_awareness_toggle'] = $input['contextual_awareness_toggle'] === 'on' ? 'on' : 'off'; |
|
9400
|
7307
|
} |
|
9401
|
7308
|
|
|
7309
|
+ if (isset($input['show_frontend_debugger'])) { |
|
7310
|
+ $new_input['show_frontend_debugger'] = $input['show_frontend_debugger'] === 'on' ? 'on' : 'off'; |
|
7311
|
+} |
|
7312
|
+ |
|
9402
|
7313
|
if (isset($input['citation_links_toggle'])) { |
|
9403
|
7314
|
$new_input['citation_links_toggle'] = $input['citation_links_toggle'] === 'on' ? 'on' : 'off'; |
|
9404
|
7315
|
} |
|
9405
|
7316
|
|
|
9406
|
|
- // Satisfaction rating prompt — defaults ON when unchecked (so first save |
|
9407
|
|
- // doesn't accidentally disable it). The form posts 'on' when checked; |
|
9408
|
|
- // unchecked checkboxes don't post a value at all, so we infer 'off' only |
|
9409
|
|
- // when the autosave/PHP request explicitly clears it via empty string. |
|
9410
|
|
- if (array_key_exists('satisfaction_rating_enabled', $input)) { |
|
9411
|
|
- $new_input['satisfaction_rating_enabled'] = $input['satisfaction_rating_enabled'] === 'on' ? 'on' : 'off'; |
|
9412
|
|
- } |
|
9413
|
|
- |
|
9414
|
|
- // Satisfaction rating customization (plan-141a12). Idle is clamped 5-600; |
|
9415
|
|
- // the 4 text strings are sanitized + capped server-side. Blank values are |
|
9416
|
|
- // preserved so the integrator falls back to translated defaults. |
|
9417
|
|
- if (array_key_exists('satisfaction_rating_idle_seconds', $input)) { |
|
9418
|
|
- $new_input['satisfaction_rating_idle_seconds'] = max(5, min(600, intval($input['satisfaction_rating_idle_seconds']))); |
|
9419
|
|
- } |
|
9420
|
|
- |
|
9421
|
|
- // Max input length in characters (plan a3fae2 part C). 0 = unlimited (default, |
|
9422
|
|
- // preserves current behavior). Clamp to a sane ceiling so a typo can't lock out |
|
9423
|
|
- // all input. REQUIRED here — mxchat_sanitize() rebuilds the array from a whitelist, |
|
9424
|
|
- // so without this isset-handler the key is stripped on the next save of ANY field |
|
9425
|
|
- // (the 2c02ea global-rate-limit footgun). |
|
9426
|
|
- if (array_key_exists('max_input_length', $input)) { |
|
9427
|
|
- $new_input['max_input_length'] = max(0, min(100000, intval($input['max_input_length']))); |
|
9428
|
|
- } |
|
9429
|
|
- if (array_key_exists('satisfaction_rating_question', $input)) { |
|
9430
|
|
- $new_input['satisfaction_rating_question'] = mb_substr(sanitize_text_field($input['satisfaction_rating_question']), 0, 200); |
|
9431
|
|
- } |
|
9432
|
|
- if (array_key_exists('satisfaction_rating_thanks', $input)) { |
|
9433
|
|
- $new_input['satisfaction_rating_thanks'] = mb_substr(sanitize_text_field($input['satisfaction_rating_thanks']), 0, 300); |
|
9434
|
|
- } |
|
9435
|
|
- if (array_key_exists('satisfaction_rating_placeholder', $input)) { |
|
9436
|
|
- $new_input['satisfaction_rating_placeholder'] = mb_substr(sanitize_text_field($input['satisfaction_rating_placeholder']), 0, 200); |
|
9437
|
|
- } |
|
9438
|
|
- if (array_key_exists('satisfaction_rating_saved', $input)) { |
|
9439
|
|
- $new_input['satisfaction_rating_saved'] = mb_substr(sanitize_text_field($input['satisfaction_rating_saved']), 0, 200); |
|
9440
|
|
- } |
|
9441
|
|
- |
|
9442
|
7317
|
if (isset($input['top_bar_title'])) { |
|
9443
|
7318
|
$new_input['top_bar_title'] = sanitize_text_field($input['top_bar_title']); |
|
9444
|
7319
|
} |
|
9445
|
7320
|
|
|
@@ -9491,13 +7366,9 @@ |
|
9491
|
7366
|
|
|
9492
|
7367
|
// Sanitize limit |
|
9493
|
7368
|
if (isset($settings['limit'])) { |
|
9494
|
7369
|
$limit = sanitize_text_field($settings['limit']); |
|
9495
|
|
- // Accept presets, 'unlimited', the '__custom__' sentinel, OR any positive |
|
9496
|
|
- // integer (custom value) — mirrors the global branch (plan-2c02ea). Without |
|
9497
|
|
- // the custom path the per-role custom input was dropped and reset to the role |
|
9498
|
|
- // default on every save (plan-7e23e7). |
|
9499
|
|
- if (in_array($limit, $allowed_limits, true) || $limit === '__custom__' || (ctype_digit($limit) && (int) $limit >= 1)) { |
|
7370
|
+ if (in_array($limit, $allowed_limits, true)) { |
|
9500
|
7371
|
$new_input['rate_limits'][$role_id]['limit'] = $limit; |
|
9501
|
7372
|
} else { |
|
9502
|
7373
|
$new_input['rate_limits'][$role_id]['limit'] = ($role_id === 'logged_out') ? '10' : '100'; // Default |
|
9503
|
7374
|
} |
|
@@ -9502,13 +7373,8 @@ |
|
9502
|
7373
|
$new_input['rate_limits'][$role_id]['limit'] = ($role_id === 'logged_out') ? '10' : '100'; // Default |
|
9503
|
7374
|
} |
|
9504
|
7375
|
} |
|
9505
|
7376
|
|
|
9506
|
|
- // Preserve the per-role custom value (mirrors the global branch's limit_custom). |
|
9507
|
|
- if (isset($settings['limit_custom'])) { |
|
9508
|
|
- $new_input['rate_limits'][$role_id]['limit_custom'] = preg_replace('/[^0-9]/', '', (string) $settings['limit_custom']); |
|
9509
|
|
- } |
|
9510
|
|
- |
|
9511
|
7377
|
// Sanitize timeframe |
|
9512
|
7378
|
if (isset($settings['timeframe'])) { |
|
9513
|
7379
|
$timeframe = sanitize_text_field($settings['timeframe']); |
|
9514
|
7380
|
if (in_array($timeframe, $allowed_timeframes, true)) { |
|
@@ -9524,46 +7390,8 @@ |
|
9524
|
7390
|
} |
|
9525
|
7391
|
} |
|
9526
|
7392
|
} |
|
9527
|
7393
|
|
|
9528
|
|
-// Handle the whole-chatbot global rate limit (plan-mxchat-20260603-2c02ea). |
|
9529
|
|
-// Mirrors the per-role block above, adapted to the single global shape. Without |
|
9530
|
|
-// this branch the whitelist-rebuild dropped rate_limits_global entirely on every |
|
9531
|
|
-// save, so the global cap silently fell back to its 'unlimited' default. |
|
9532
|
|
-// MUST accept arbitrary positive integers so the custom-value path (d55f65) is not regressed. |
|
9533
|
|
-if (isset($input['rate_limits_global']) && is_array($input['rate_limits_global'])) { |
|
9534
|
|
- $g = $input['rate_limits_global']; |
|
9535
|
|
- $allowed_limits = array('1', '3', '5', '10', '15', '20', '50', '100', 'unlimited'); |
|
9536
|
|
- $allowed_timeframes = array('hourly', 'daily', 'weekly', 'monthly'); |
|
9537
|
|
- $global_out = array(); |
|
9538
|
|
- |
|
9539
|
|
- if (isset($g['limit'])) { |
|
9540
|
|
- $limit = sanitize_text_field($g['limit']); |
|
9541
|
|
- // Accept presets, 'unlimited', the '__custom__' sentinel (resolved by the |
|
9542
|
|
- // autosave handler / renderer), OR any positive integer (custom value). |
|
9543
|
|
- if (in_array($limit, $allowed_limits, true) || $limit === '__custom__' || (ctype_digit($limit) && (int) $limit >= 1)) { |
|
9544
|
|
- $global_out['limit'] = $limit; |
|
9545
|
|
- } else { |
|
9546
|
|
- $global_out['limit'] = 'unlimited'; |
|
9547
|
|
- } |
|
9548
|
|
- } |
|
9549
|
|
- |
|
9550
|
|
- if (isset($g['limit_custom'])) { |
|
9551
|
|
- $global_out['limit_custom'] = preg_replace('/[^0-9]/', '', (string) $g['limit_custom']); |
|
9552
|
|
- } |
|
9553
|
|
- |
|
9554
|
|
- if (isset($g['timeframe'])) { |
|
9555
|
|
- $timeframe = sanitize_text_field($g['timeframe']); |
|
9556
|
|
- $global_out['timeframe'] = in_array($timeframe, $allowed_timeframes, true) ? $timeframe : 'daily'; |
|
9557
|
|
- } |
|
9558
|
|
- |
|
9559
|
|
- if (isset($g['message'])) { |
|
9560
|
|
- $global_out['message'] = sanitize_textarea_field($g['message']); |
|
9561
|
|
- } |
|
9562
|
|
- |
|
9563
|
|
- $new_input['rate_limits_global'] = $global_out; |
|
9564
|
|
-} |
|
9565
|
|
- |
|
9566
|
7394
|
if (isset($input['pre_chat_message'])) { |
|
9567
|
7395
|
$new_input['pre_chat_message'] = sanitize_textarea_field($input['pre_chat_message']); |
|
9568
|
7396
|
} |
|
9569
|
7397
|
|
|
@@ -9572,14 +7400,15 @@ |
|
9572
|
7400
|
} |
|
9573
|
7401
|
|
|
9574
|
7402
|
// Add to your sanitize function |
|
9575
|
7403
|
if (isset($input['embedding_model'])) { |
|
9576
|
|
- // Catalog refactor (plan-d14e89): allowlist derived from the canonical |
|
9577
|
|
- // catalog in includes/class-mxchat-model-catalog.php. |
|
9578
|
|
- if (!class_exists('MxChat_Model_Catalog')) { |
|
9579
|
|
- require_once plugin_dir_path(__FILE__) . 'class-mxchat-model-catalog.php'; |
|
9580
|
|
- } |
|
9581
|
|
- $allowed_models = MxChat_Model_Catalog::embedding_model_ids(); |
|
7404
|
+ $allowed_models = array( |
|
7405
|
+ 'text-embedding-ada-002', |
|
7406
|
+ 'text-embedding-3-small', |
|
7407
|
+ 'text-embedding-3-large', |
|
7408
|
+ 'voyage-3-large', |
|
7409
|
+ 'gemini-embedding-001' |
|
7410
|
+ ); |
|
9582
|
7411
|
if (in_array($input['embedding_model'], $allowed_models)) { |
|
9583
|
7412
|
$new_input['embedding_model'] = sanitize_text_field($input['embedding_model']); |
|
9584
|
7413
|
} |
|
9585
|
7414
|
} |
|
@@ -9587,12 +7416,42 @@ |
|
9587
|
7416
|
if (isset($input['model'])) { |
|
9588
|
7417
|
if ($input['model'] === 'openrouter') { |
|
9589
|
7418
|
$new_input['model'] = 'openrouter'; |
|
9590
|
7419
|
} else { |
|
9591
|
|
- if (!class_exists('MxChat_Model_Catalog')) { |
|
9592
|
|
- require_once plugin_dir_path(__FILE__) . 'class-mxchat-model-catalog.php'; |
|
9593
|
|
- } |
|
9594
|
|
- $allowed_models = MxChat_Model_Catalog::chat_model_ids(); |
|
7420
|
+ $allowed_models = array( |
|
7421
|
+ 'gemini-3-pro-preview', |
|
7422
|
+ 'gemini-3-flash-preview', |
|
7423
|
+ 'gemini-2.5-pro', |
|
7424
|
+ 'gemini-2.5-flash', |
|
7425
|
+ 'gemini-2.5-flash-lite', |
|
7426
|
+ 'gemini-2.0-flash', |
|
7427
|
+ 'gemini-2.0-flash-lite', |
|
7428
|
+ 'gemini-1.5-pro', |
|
7429
|
+ 'gemini-1.5-flash', |
|
7430
|
+ 'grok-4-0709', |
|
7431
|
+ 'grok-4-1-fast-reasoning', |
|
7432
|
+ 'grok-4-1-fast-non-reasoning', |
|
7433
|
+ 'grok-3-beta', |
|
7434
|
+ 'grok-3-fast-beta', |
|
7435
|
+ 'grok-3-mini-beta', |
|
7436
|
+ 'grok-3-mini-fast-beta', |
|
7437
|
+ 'grok-2', |
|
7438
|
+ 'deepseek-chat', |
|
7439
|
+ 'claude-opus-4-6', |
|
7440
|
+ 'claude-sonnet-4-6', |
|
7441
|
+ 'claude-opus-4-5', |
|
7442
|
+ 'claude-sonnet-4-5-20250929', |
|
7443
|
+ 'claude-opus-4-1-20250805', |
|
7444
|
+ 'claude-haiku-4-5-20251001', |
|
7445
|
+ 'claude-opus-4-20250514', |
|
7446
|
+ 'claude-sonnet-4-20250514', |
|
7447
|
+ 'gpt-5.2', |
|
7448
|
+ 'gpt-5.1-chat-latest', |
|
7449
|
+ 'gpt-5.1-2025-11-13', |
|
7450
|
+ 'gpt-5', |
|
7451
|
+ 'gpt-5-mini', |
|
7452
|
+ 'gpt-5-nano', |
|
7453
|
+ ); |
|
9595
|
7454
|
|
|
9596
|
7455
|
if (in_array($input['model'], $allowed_models)) { |
|
9597
|
7456
|
$new_input['model'] = sanitize_text_field($input['model']); |
|
9598
|
7457
|
} else { |
|
@@ -9615,37 +7474,10 @@ |
|
9615
|
7474
|
if (isset($input['openrouter_api_key'])) { |
|
9616
|
7475
|
$new_input['openrouter_api_key'] = sanitize_text_field($input['openrouter_api_key']); |
|
9617
|
7476
|
} |
|
9618
|
7477
|
|
|
9619
|
|
- // Custom (OpenAI-compatible) Provider — Ollama, LM Studio, vLLM, Azure OpenAI, etc. |
|
9620
|
|
- if (isset($input['custom_provider_base_url'])) { |
|
9621
|
|
- $new_input['custom_provider_base_url'] = esc_url_raw(rtrim(trim((string) $input['custom_provider_base_url']), '/')); |
|
9622
|
|
- } |
|
9623
|
|
- if (isset($input['custom_provider_api_key'])) { |
|
9624
|
|
- $new_input['custom_provider_api_key'] = sanitize_text_field($input['custom_provider_api_key']); |
|
9625
|
|
- } |
|
9626
|
|
- if (isset($input['custom_provider_model'])) { |
|
9627
|
|
- $new_input['custom_provider_model'] = sanitize_text_field($input['custom_provider_model']); |
|
9628
|
|
- } |
|
9629
|
|
- if (isset($input['custom_provider_auth_scheme'])) { |
|
9630
|
|
- $scheme = sanitize_text_field($input['custom_provider_auth_scheme']); |
|
9631
|
|
- $new_input['custom_provider_auth_scheme'] = in_array($scheme, array('bearer', 'api-key'), true) ? $scheme : 'bearer'; |
|
9632
|
|
- } |
|
9633
|
|
- if (isset($input['custom_provider_for_embeddings'])) { |
|
9634
|
|
- $new_input['custom_provider_for_embeddings'] = ($input['custom_provider_for_embeddings'] === 'on') ? 'on' : 'off'; |
|
9635
|
|
- } |
|
9636
|
|
- if (isset($input['custom_provider_for_images'])) { |
|
9637
|
|
- $new_input['custom_provider_for_images'] = ($input['custom_provider_for_images'] === 'on') ? 'on' : 'off'; |
|
9638
|
|
- } |
|
9639
|
|
- if (isset($input['custom_provider_embedding_model'])) { |
|
9640
|
|
- $new_input['custom_provider_embedding_model'] = sanitize_text_field($input['custom_provider_embedding_model']); |
|
9641
|
|
- } |
|
9642
|
|
- if (isset($input['custom_provider_api_version'])) { |
|
9643
|
|
- $new_input['custom_provider_api_version'] = sanitize_text_field($input['custom_provider_api_version']); |
|
9644
|
|
- } |
|
9645
|
7478
|
|
|
9646
|
7479
|
|
|
9647
|
|
- |
|
9648
|
7480
|
if (isset($input['woocommerce_consumer_key'])) { |
|
9649
|
7481
|
$new_input['woocommerce_consumer_key'] = sanitize_text_field($input['woocommerce_consumer_key']); |
|
9650
|
7482
|
} |
|
9651
|
7483
|
|
|
@@ -9667,25 +7499,8 @@ |
|
9667
|
7499
|
if (isset($input['chat_persistence_toggle'])) { |
|
9668
|
7500
|
$new_input['chat_persistence_toggle'] = $input['chat_persistence_toggle'] === 'on' ? 'on' : 'off'; |
|
9669
|
7501
|
} |
|
9670
|
7502
|
|
|
9671
|
|
- // No else clause: an absent key stays absent, so the front-end default ('on') applies |
|
9672
|
|
- // and the rebuild never strips a saved 'off' (autosave passes the full options array back through here). |
|
9673
|
|
- if (isset($input['print_button_enabled'])) { |
|
9674
|
|
- $new_input['print_button_enabled'] = $input['print_button_enabled'] === 'on' ? 'on' : 'off'; |
|
9675
|
|
- } |
|
9676
|
|
- |
|
9677
|
|
- // plan ac2e81 — "Start new chat" toggle (default OFF) + editable label. |
|
9678
|
|
- // No else on the toggle: an absent key stays absent so the front-end '?? off' |
|
9679
|
|
- // default applies, and the full-array autosave rebuild preserves a saved value. |
|
9680
|
|
- if (isset($input['reset_chat_enabled'])) { |
|
9681
|
|
- $new_input['reset_chat_enabled'] = $input['reset_chat_enabled'] === 'on' ? 'on' : 'off'; |
|
9682
|
|
- } |
|
9683
|
|
- |
|
9684
|
|
- if (isset($input['reset_chat_label'])) { |
|
9685
|
|
- $new_input['reset_chat_label'] = sanitize_text_field($input['reset_chat_label']); |
|
9686
|
|
- } |
|
9687
|
|
- |
|
9688
|
7503
|
if (isset($input['popular_question_1'])) { |
|
9689
|
7504
|
$new_input['popular_question_1'] = sanitize_text_field($input['popular_question_1']); |
|
9690
|
7505
|
} |
|
9691
|
7506
|
|
|
@@ -9844,12 +7659,8 @@ |
|
9844
|
7659
|
} |
|
9845
|
7660
|
if (isset($input['content_image_model'])) { |
|
9846
|
7661
|
$new_input['content_image_model'] = sanitize_text_field($input['content_image_model']); |
|
9847
|
7662
|
} |
|
9848
|
|
- if (isset($input['content_image_quality'])) { |
|
9849
|
|
- $q = sanitize_text_field($input['content_image_quality']); |
|
9850
|
|
- $new_input['content_image_quality'] = in_array($q, array('auto', 'low', 'medium', 'high'), true) ? $q : 'auto'; |
|
9851
|
|
- } |
|
9852
|
7663
|
if (isset($input['content_enable_images'])) { |
|
9853
|
7664
|
$new_input['content_enable_images'] = ($input['content_enable_images'] === 'on') ? 'on' : 'off'; |
|
9854
|
7665
|
} |
|
9855
|
7666
|
if (isset($input['content_use_placeholders'])) { |
|
@@ -9860,11 +7671,8 @@ |
|
9860
|
7671
|
} |
|
9861
|
7672
|
if (isset($input['content_tool_use'])) { |
|
9862
|
7673
|
$new_input['content_tool_use'] = ($input['content_tool_use'] === 'on') ? 'on' : 'off'; |
|
9863
|
7674
|
} |
|
9864
|
|
- if (isset($input['content_image_count'])) { |
|
9865
|
|
- $new_input['content_image_count'] = (string) max(1, min(5, (int) $input['content_image_count'])); |
|
9866
|
|
- } |
|
9867
|
7675
|
|
|
9868
|
7676
|
// SEO Optimize toggle fields |
|
9869
|
7677
|
foreach (array('seo_optimize_meta_desc', 'seo_optimize_seo_title', 'seo_optimize_slug', 'seo_optimize_readability', 'seo_optimize_internal_links', 'seo_optimize_img_alt', 'seo_optimize_featured_img') as $seo_key) { |
|
9870
|
7678
|
if (isset($input[$seo_key])) { |
|
@@ -9874,9 +7682,9 @@ |
|
9874
|
7682
|
|
|
9875
|
7683
|
// Preserve content generator settings when saving from main settings page |
|
9876
|
7684
|
// (where content fields are not in the form submission) |
|
9877
|
7685
|
$existing = get_option('mxchat_options', array()); |
|
9878
|
|
- foreach (array('content_model', 'content_image_model', 'content_image_quality', 'content_image_count', 'content_enable_images', 'content_use_placeholders', 'content_internal_linking', 'content_tool_use', 'seo_optimize_meta_desc', 'seo_optimize_seo_title', 'seo_optimize_slug', 'seo_optimize_readability', 'seo_optimize_internal_links', 'seo_optimize_img_alt', 'seo_optimize_featured_img') as $key) { |
|
7686
|
+ foreach (array('content_model', 'content_image_model', 'content_enable_images', 'content_use_placeholders', 'content_internal_linking', 'content_tool_use', 'seo_optimize_meta_desc', 'seo_optimize_seo_title', 'seo_optimize_slug', 'seo_optimize_readability', 'seo_optimize_internal_links', 'seo_optimize_img_alt', 'seo_optimize_featured_img') as $key) { |
|
9879
|
7687
|
if (!isset($new_input[$key]) && isset($existing[$key])) { |
|
9880
|
7688
|
$new_input[$key] = $existing[$key]; |
|
9881
|
7689
|
} |
|
9882
|
7690
|
} |
|
@@ -10100,65 +7908,73 @@ |
|
10100
|
7908
|
//error_log('DEBUG: Permission check failed'); |
|
10101
|
7909
|
wp_die(__('You do not have sufficient permissions to delete all prompts.', 'mxchat')); |
|
10102
|
7910
|
} |
|
10103
|
7911
|
|
|
10104
|
|
- // Get bot_id and content type filter from POST data |
|
7912
|
+ // Get bot_id from POST data |
|
10105
|
7913
|
$bot_id = isset($_POST['bot_id']) ? sanitize_text_field($_POST['bot_id']) : 'default'; |
|
10106
|
|
- $content_type_filter = isset($_POST['content_type_filter']) ? sanitize_text_field($_POST['content_type_filter']) : ''; |
|
10107
|
|
- |
|
7914
|
+ //error_log('DEBUG: Bot ID from POST: ' . $bot_id); |
|
7915
|
+ |
|
10108
|
7916
|
$success = true; |
|
10109
|
7917
|
$error_messages = array(); |
|
10110
|
|
- |
|
7918
|
+ |
|
10111
|
7919
|
// Get bot-specific Pinecone configuration |
|
10112
|
7920
|
$pinecone_manager = MxChat_Pinecone_Manager::get_instance(); |
|
10113
|
7921
|
$pinecone_options = $pinecone_manager->mxchat_get_bot_pinecone_options($bot_id); |
|
10114
|
|
- |
|
7922
|
+ |
|
7923
|
+ //error_log('DEBUG: Pinecone options retrieved:'); |
|
7924
|
+ //error_log(' - Use Pinecone: ' . ($pinecone_options['mxchat_use_pinecone'] ?? 'NOT SET')); |
|
7925
|
+ //error_log(' - API Key: ' . (empty($pinecone_options['mxchat_pinecone_api_key']) ? 'EMPTY' : 'SET')); |
|
7926
|
+ //error_log(' - Host: ' . ($pinecone_options['mxchat_pinecone_host'] ?? 'NOT SET')); |
|
7927
|
+ |
|
10115
|
7928
|
$use_pinecone = ($pinecone_options['mxchat_use_pinecone'] ?? '0') === '1'; |
|
10116
|
|
- |
|
7929
|
+ |
|
10117
|
7930
|
if ($use_pinecone && !empty($pinecone_options['mxchat_pinecone_api_key'])) { |
|
10118
|
|
- // Delete from Pinecone (with optional content type filter) |
|
10119
|
|
- $result = $pinecone_manager->mxchat_delete_all_from_pinecone($pinecone_options, $content_type_filter); |
|
10120
|
|
- |
|
7931
|
+ //error_log('[MXCHAT-DELETE] Pinecone is enabled for bot ' . $bot_id . ' - deleting all from Pinecone'); |
|
7932
|
+ |
|
7933
|
+ // Delete from bot-specific Pinecone index |
|
7934
|
+ $result = $pinecone_manager->mxchat_delete_all_from_pinecone($pinecone_options); |
|
7935
|
+ |
|
7936
|
+ //error_log('DEBUG: Delete all result: ' . ($result['success'] ? 'SUCCESS' : 'FAILED')); |
|
10121
|
7937
|
if (!$result['success']) { |
|
7938
|
+ //error_log('DEBUG: Delete all error: ' . $result['message']); |
|
7939
|
+ } |
|
7940
|
+ |
|
7941
|
+ if (!$result['success']) { |
|
10122
|
7942
|
$success = false; |
|
10123
|
7943
|
$error_messages[] = $result['message']; |
|
10124
|
7944
|
} |
|
10125
|
|
- |
|
7945
|
+ |
|
7946
|
+ // No cache clearing needed since we removed caching |
|
7947
|
+ |
|
10126
|
7948
|
} else { |
|
7949
|
+ //error_log('[MXCHAT-DELETE] Pinecone not enabled for bot ' . $bot_id . ' - deleting from WordPress database'); |
|
7950
|
+ |
|
10127
|
7951
|
// Delete from WordPress database |
|
10128
|
7952
|
global $wpdb; |
|
10129
|
7953
|
$table_name = $wpdb->prefix . 'mxchat_system_prompt_content'; |
|
10130
|
|
- |
|
10131
|
|
- // Build WHERE conditions |
|
10132
|
|
- $where_clauses = array(); |
|
10133
|
|
- $where_values = array(); |
|
10134
|
|
- |
|
10135
|
|
- // Bot filter |
|
7954
|
+ |
|
7955
|
+ // If multi-bot is active and we have a specific bot, delete only that bot's content |
|
10136
|
7956
|
if ($bot_id !== 'default' && class_exists('MxChat_Multi_Bot_Manager')) { |
|
10137
|
|
- $where_clauses[] = 'bot_id = %s'; |
|
10138
|
|
- $where_values[] = $bot_id; |
|
10139
|
|
- } |
|
10140
|
|
- |
|
10141
|
|
- // Content type filter |
|
10142
|
|
- if (!empty($content_type_filter)) { |
|
10143
|
|
- $where_clauses[] = 'content_type = %s'; |
|
10144
|
|
- $where_values[] = $content_type_filter; |
|
10145
|
|
- } |
|
10146
|
|
- |
|
10147
|
|
- if (!empty($where_clauses)) { |
|
10148
|
|
- $where_sql = implode(' AND ', $where_clauses); |
|
10149
|
|
- $result = $wpdb->query($wpdb->prepare("DELETE FROM {$table_name} WHERE {$where_sql}", $where_values)); |
|
7957
|
+ $result = $wpdb->delete( |
|
7958
|
+ $table_name, |
|
7959
|
+ array('bot_id' => $bot_id), |
|
7960
|
+ array('%s') |
|
7961
|
+ ); |
|
10150
|
7962
|
} else { |
|
10151
|
|
- // No filters — delete all |
|
7963
|
+ // Delete all content for default bot |
|
10152
|
7964
|
$result = $wpdb->query("DELETE FROM {$table_name}"); |
|
10153
|
7965
|
} |
|
10154
|
|
- |
|
7966
|
+ |
|
10155
|
7967
|
if ($result === false) { |
|
10156
|
7968
|
$success = false; |
|
10157
|
7969
|
$error_messages[] = 'Failed to delete from WordPress database'; |
|
10158
|
7970
|
} |
|
10159
|
7971
|
} |
|
7972
|
+ |
|
7973
|
+ // No cache clearing needed since we removed caching |
|
10160
|
7974
|
|
|
7975
|
+ //error_log('=== DELETE ALL DEBUG END ==='); |
|
7976
|
+ |
|
10161
|
7977
|
// Redirect back with a success message and bot_id |
|
10162
|
7978
|
$redirect_url = add_query_arg(array( |
|
10163
|
7979
|
'page' => 'mxchat-prompts', |
|
10164
|
7980
|
'bot_id' => $bot_id, |
|
@@ -10463,9 +8279,8 @@ |
|
10463
|
8279
|
if ($embedding_dimensions !== 1536) { |
|
10464
|
8280
|
//error_log('[MXCHAT-EMBED] Warning: Unexpected Gemini embedding dimensions: ' . $embedding_dimensions); |
|
10465
|
8281
|
} |
|
10466
|
8282
|
|
|
10467
|
|
- MxChat_Utils::stamp_active_embedding_model($selected_model); |
|
10468
|
8283
|
return $response_data['embedding']['values']; |
|
10469
|
8284
|
} else { |
|
10470
|
8285
|
//error_log('[MXCHAT-EMBED] Error: No embedding found in Gemini response'); |
|
10471
|
8286
|
//error_log('[MXCHAT-EMBED] Response structure: ' . wp_json_encode(array_keys($response_data))); |
|
@@ -10491,9 +8306,8 @@ |
|
10491
|
8306
|
($selected_model === 'voyage-3-large' && $embedding_dimensions !== 2048)) { |
|
10492
|
8307
|
//error_log('[MXCHAT-EMBED] Warning: Unexpected embedding dimensions'); |
|
10493
|
8308
|
} |
|
10494
|
8309
|
|
|
10495
|
|
- MxChat_Utils::stamp_active_embedding_model($selected_model); |
|
10496
|
8310
|
return $response_data['data'][0]['embedding']; |
|
10497
|
8311
|
} else { |
|
10498
|
8312
|
//error_log('[MXCHAT-EMBED] Error: No embedding found in response'); |
|
10499
|
8313
|
//error_log('[MXCHAT-EMBED] Response structure: ' . wp_json_encode(array_keys($response_data))); |