PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 3.2.13
MxChat – AI Chatbot & Content Generation for WordPress v3.2.13
3.2.21 3.2.20 3.2.19 3.2.18 3.2.17 3.2.16 3.2.15 3.2.14 3.2.12 3.2.13 3.2.11 3.2.10 3.2.9 3.2.8 3.2.7 3.2.6 3.2.5 3.2.4 3.2.3 3.2.2 3.2.1 2.0.3 2.0.4 2.0.5 2.0.6 All 152 releases
mxchat-basic / includes / admin-knowledge-page.php

admin-knowledge-page.php in MxChat – AI Chatbot & Content Generation for WordPress 3.2.13, at includes/admin-knowledge-page.php

2,456 lines 166.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * MxChat Knowledge Base Page - Redesigned with Sidebar Navigation
4 *
5 * @package MxChat
6 * @since 2.7.0
7 */
8
9 if (!defined('ABSPATH')) {
10 exit;
11 }
12
13 /**
14 * Render the new sidebar-based Knowledge Base page
15 */
16 function mxchat_render_knowledge_page($admin_instance, $knowledge_manager, $page_data) {
17 $is_activated = $admin_instance->is_activated();
18 $plugin_url = plugin_dir_url(dirname(__FILE__));
19
20 // Check if integrations are active
21 $pinecone_options = get_option('mxchat_pinecone_addon_options', array());
22 $is_pinecone_active = ($pinecone_options['mxchat_use_pinecone'] ?? '0') === '1';
23
24 $vectorstore_options = get_option('mxchat_openai_vectorstore_options', array());
25 $is_vectorstore_active = ($vectorstore_options['mxchat_use_openai_vectorstore'] ?? '0') === '1';
26
27 // Extract page data
28 extract($page_data);
29 ?>
30 <div class="mxch-admin-wrapper">
31 <!-- Mobile Header -->
32 <header class="mxch-mobile-header">
33 <a href="#" class="mxch-mobile-logo">
34 <div class="mxch-mobile-logo-icon">
35 <img src="<?php echo esc_url($plugin_url . 'images/icon-128x128.png'); ?>" alt="MxChat">
36 </div>
37 <span class="mxch-mobile-logo-text">MxChat</span>
38 </a>
39 <button type="button" class="mxch-mobile-menu-btn" aria-label="<?php esc_attr_e('Open menu', 'mxchat'); ?>">
40 <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="1"/><circle cx="12" cy="5" r="1"/><circle cx="12" cy="19" r="1"/></svg>
41 </button>
42 </header>
43
44 <!-- Mobile Menu Overlay -->
45 <div class="mxch-mobile-overlay"></div>
46
47 <!-- Mobile Menu Modal -->
48 <div class="mxch-mobile-menu">
49 <div class="mxch-mobile-menu-header">
50 <span class="mxch-mobile-menu-title"><?php esc_html_e('Knowledge Base', 'mxchat'); ?></span>
51 <button type="button" class="mxch-mobile-menu-close" aria-label="<?php esc_attr_e('Close menu', 'mxchat'); ?>">
52 <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
53 </button>
54 </div>
55 <nav class="mxch-mobile-menu-nav">
56 <!-- Import Section -->
57 <div class="mxch-mobile-nav-section">
58 <div class="mxch-mobile-nav-section-title"><?php esc_html_e('Import', 'mxchat'); ?></div>
59 <button class="mxch-mobile-nav-link active" data-target="import-options">
60 <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>
61 <span><?php esc_html_e('Import Options', 'mxchat'); ?></span>
62 </button>
63 <button class="mxch-mobile-nav-link" data-target="knowledge-base">
64 <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"/><path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"/></svg>
65 <span><?php esc_html_e('Knowledge Base', 'mxchat'); ?></span>
66 </button>
67 </div>
68 <!-- Settings Section -->
69 <div class="mxch-mobile-nav-section">
70 <div class="mxch-mobile-nav-section-title"><?php esc_html_e('Settings', 'mxchat'); ?></div>
71 <button class="mxch-mobile-nav-link" data-target="auto-sync">
72 <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/><path d="M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16"/><path d="M16 16h5v5"/></svg>
73 <span><?php esc_html_e('Auto-Sync', 'mxchat'); ?></span>
74 </button>
75 <button class="mxch-mobile-nav-link" data-target="chunking">
76 <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/></svg>
77 <span><?php esc_html_e('Content Chunking', 'mxchat'); ?></span>
78 </button>
79 <button class="mxch-mobile-nav-link" data-target="role-restrictions">
80 <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>
81 <span><?php esc_html_e('Role Restrictions', 'mxchat'); ?></span>
82 </button>
83 <button class="mxch-mobile-nav-link" data-target="acf-fields">
84 <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/><line x1="10" y1="9" x2="8" y2="9"/></svg>
85 <span><?php esc_html_e('ACF Fields', 'mxchat'); ?></span>
86 </button>
87 <button class="mxch-mobile-nav-link" data-target="custom-meta">
88 <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 7V4h16v3"/><path d="M9 20h6"/><path d="M12 4v16"/></svg>
89 <span><?php esc_html_e('Custom Meta', 'mxchat'); ?></span>
90 </button>
91 </div>
92 <!-- Integrations Section -->
93 <div class="mxch-mobile-nav-section">
94 <div class="mxch-mobile-nav-section-title"><?php esc_html_e('Integrations', 'mxchat'); ?></div>
95 <button class="mxch-mobile-nav-link" data-target="pinecone">
96 <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17.5 19H9a7 7 0 1 1 6.71-9h1.79a4.5 4.5 0 1 1 0 9Z"/></svg>
97 <span><?php esc_html_e('Pinecone', 'mxchat'); ?></span>
98 <?php if ($is_pinecone_active): ?>
99 <span class="mxch-nav-link-badge mxch-active-badge"><?php esc_html_e('Active', 'mxchat'); ?></span>
100 <?php endif; ?>
101 </button>
102 <button class="mxch-mobile-nav-link" data-target="openai-vectorstore">
103 <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/><path d="M11 8v6"/><path d="M8 11h6"/></svg>
104 <span><?php esc_html_e('OpenAI Vector Store', 'mxchat'); ?></span>
105 <?php if ($is_vectorstore_active): ?>
106 <span class="mxch-nav-link-badge mxch-active-badge"><?php esc_html_e('Active', 'mxchat'); ?></span>
107 <?php endif; ?>
108 </button>
109 </div>
110 <?php if (!$is_activated): ?>
111 <div class="mxch-mobile-menu-footer">
112 <a href="https://mxchat.ai/" target="_blank" class="mxch-mobile-upgrade-btn">
113 <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 9H4.5a2.5 2.5 0 0 1 0-5H6"/><path d="M18 9h1.5a2.5 2.5 0 0 0 0-5H18"/><path d="M4 22h16"/><path d="M10 14.66V17c0 .55-.47.98-.97 1.21C7.85 18.75 7 20.24 7 22"/><path d="M14 14.66V17c0 .55.47.98.97 1.21C16.15 18.75 17 20.24 17 22"/><path d="M18 2H6v7a6 6 0 0 0 12 0V2Z"/></svg>
114 <?php esc_html_e('Upgrade to Pro', 'mxchat'); ?>
115 </a>
116 </div>
117 <?php endif; ?>
118 </nav>
119 </div>
120
121 <!-- Sidebar Navigation -->
122 <aside class="mxch-sidebar">
123 <div class="mxch-sidebar-header">
124 <a href="#" class="mxch-sidebar-logo">
125 <div class="mxch-sidebar-logo-icon">
126 <img src="<?php echo esc_url($plugin_url . 'images/icon-128x128.png'); ?>" alt="MxChat">
127 </div>
128 <span class="mxch-sidebar-logo-text">MxChat</span>
129 <span class="mxch-sidebar-version">v<?php echo esc_html(MXCHAT_VERSION ?? '2.7.0'); ?></span>
130 </a>
131 </div>
132
133 <nav class="mxch-sidebar-nav">
134 <!-- Import Section -->
135 <div class="mxch-nav-section">
136 <div class="mxch-nav-section-title"><?php esc_html_e('Import', 'mxchat'); ?></div>
137
138 <div class="mxch-nav-item" data-section="import-options">
139 <button class="mxch-nav-link active" data-target="import-options">
140 <span class="mxch-nav-link-icon">
141 <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>
142 </span>
143 <span class="mxch-nav-link-text"><?php esc_html_e('Import Options', 'mxchat'); ?></span>
144 </button>
145 </div>
146
147 <div class="mxch-nav-item" data-section="knowledge-base">
148 <button class="mxch-nav-link" data-target="knowledge-base">
149 <span class="mxch-nav-link-icon">
150 <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"/><path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"/></svg>
151 </span>
152 <span class="mxch-nav-link-text"><?php esc_html_e('Knowledge Base', 'mxchat'); ?></span>
153 <span id="mxchat-sidebar-count" class="mxch-nav-link-badge"><?php echo esc_html($total_records); ?></span>
154 </button>
155 </div>
156 </div>
157
158 <!-- Settings Section -->
159 <div class="mxch-nav-section">
160 <div class="mxch-nav-section-title"><?php esc_html_e('Settings', 'mxchat'); ?></div>
161
162 <div class="mxch-nav-item" data-section="auto-sync">
163 <button class="mxch-nav-link" data-target="auto-sync">
164 <span class="mxch-nav-link-icon">
165 <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/><path d="M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16"/><path d="M16 16h5v5"/></svg>
166 </span>
167 <span class="mxch-nav-link-text"><?php esc_html_e('Auto-Sync', 'mxchat'); ?></span>
168 </button>
169 </div>
170
171 <div class="mxch-nav-item" data-section="chunking">
172 <button class="mxch-nav-link" data-target="chunking">
173 <span class="mxch-nav-link-icon">
174 <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/></svg>
175 </span>
176 <span class="mxch-nav-link-text"><?php esc_html_e('Content Chunking', 'mxchat'); ?></span>
177 </button>
178 </div>
179
180 <div class="mxch-nav-item" data-section="role-restrictions">
181 <button class="mxch-nav-link" data-target="role-restrictions">
182 <span class="mxch-nav-link-icon">
183 <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>
184 </span>
185 <span class="mxch-nav-link-text"><?php esc_html_e('Role Restrictions', 'mxchat'); ?></span>
186 </button>
187 </div>
188
189 <div class="mxch-nav-item" data-section="acf-fields">
190 <button class="mxch-nav-link" data-target="acf-fields">
191 <span class="mxch-nav-link-icon">
192 <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/><line x1="10" y1="9" x2="8" y2="9"/></svg>
193 </span>
194 <span class="mxch-nav-link-text"><?php esc_html_e('ACF Fields', 'mxchat'); ?></span>
195 </button>
196 </div>
197
198 <div class="mxch-nav-item" data-section="custom-meta">
199 <button class="mxch-nav-link" data-target="custom-meta">
200 <span class="mxch-nav-link-icon">
201 <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 7V4h16v3"/><path d="M9 20h6"/><path d="M12 4v16"/></svg>
202 </span>
203 <span class="mxch-nav-link-text"><?php esc_html_e('Custom Meta', 'mxchat'); ?></span>
204 </button>
205 </div>
206 </div>
207
208 <!-- Integrations Section -->
209 <div class="mxch-nav-section">
210 <div class="mxch-nav-section-title"><?php esc_html_e('Integrations', 'mxchat'); ?></div>
211
212 <div class="mxch-nav-item" data-section="pinecone">
213 <button class="mxch-nav-link" data-target="pinecone">
214 <span class="mxch-nav-link-icon">
215 <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17.5 19H9a7 7 0 1 1 6.71-9h1.79a4.5 4.5 0 1 1 0 9Z"/></svg>
216 </span>
217 <span class="mxch-nav-link-text"><?php esc_html_e('Pinecone', 'mxchat'); ?></span>
218 <?php if ($is_pinecone_active): ?>
219 <span class="mxch-nav-link-badge mxch-active-badge"><?php esc_html_e('Active', 'mxchat'); ?></span>
220 <?php endif; ?>
221 </button>
222 </div>
223
224 <div class="mxch-nav-item" data-section="openai-vectorstore">
225 <button class="mxch-nav-link" data-target="openai-vectorstore">
226 <span class="mxch-nav-link-icon">
227 <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/><path d="M11 8v6"/><path d="M8 11h6"/></svg>
228 </span>
229 <span class="mxch-nav-link-text"><?php esc_html_e('OpenAI Vector Store', 'mxchat'); ?></span>
230 <?php if ($is_vectorstore_active): ?>
231 <span class="mxch-nav-link-badge mxch-active-badge"><?php esc_html_e('Active', 'mxchat'); ?></span>
232 <?php endif; ?>
233 </button>
234 </div>
235 </div>
236 </nav>
237
238 <?php if (!$is_activated): ?>
239 <div class="mxch-sidebar-footer">
240 <a href="https://mxchat.ai/" target="_blank" class="mxch-sidebar-upgrade-v2">
241 <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg>
242 <?php esc_html_e('Pro Upgrade', 'mxchat'); ?>
243 </a>
244 </div>
245 <?php endif; ?>
246 </aside>
247
248 <!-- Main Content Area -->
249 <main class="mxch-content">
250 <?php
251 // Multi-Bot Selector
252 mxchat_render_knowledge_bot_selector($multibot_active, $current_bot_id);
253
254 // Global Processing Status - Always visible when processing
255 mxchat_render_global_processing_status($page_data, $knowledge_manager);
256
257 // Render content sections
258 mxchat_render_import_options_section($admin_instance, $knowledge_manager, $page_data);
259 mxchat_render_knowledge_base_section($admin_instance, $knowledge_manager, $page_data);
260 mxchat_render_auto_sync_section($knowledge_manager);
261 mxchat_render_chunking_section();
262 mxchat_render_role_restrictions_section($knowledge_manager);
263 mxchat_render_acf_fields_section($knowledge_manager);
264 mxchat_render_custom_meta_section();
265 mxchat_render_pinecone_section();
266 mxchat_render_openai_vectorstore_section();
267 ?>
268
269 </main>
270 </div>
271
272 <?php
273 // Render the Content Selector Modal
274 mxchat_render_content_selector_modal();
275
276 // Render the Edit Entry Modal
277 mxchat_render_edit_entry_modal();
278
279 // Render the read-only Knowledge Inspector modal (plan-d8cb4b)
280 mxchat_render_inspect_entry_modal();
281
282 // Render the navigation JavaScript
283 mxchat_render_knowledge_page_scripts();
284 }
285
286 /**
287 * Render Multi-Bot Selector
288 */
289 function mxchat_render_knowledge_bot_selector($multibot_active, $current_bot_id) {
290 if (!$multibot_active || !class_exists('MxChat_Multi_Bot_Manager')) {
291 return;
292 }
293
294 $multi_bot_manager = MxChat_Multi_Bot_Core_Manager::get_instance();
295 $available_bots = $multi_bot_manager->get_available_bots();
296 ?>
297 <div class="mxch-card" style="margin-bottom: 24px;">
298 <div class="mxch-card-body">
299 <div style="display: flex; align-items: center; gap: 15px; flex-wrap: wrap;">
300 <label for="mxchat-bot-selector" style="font-weight: 600; white-space: nowrap;">
301 <?php esc_html_e('Select Bot Database:', 'mxchat'); ?>
302 </label>
303 <select id="mxchat-bot-selector" class="mxch-select" style="min-width: 200px; max-width: 300px;">
304 <?php foreach ($available_bots as $bot_id => $bot_name) : ?>
305 <option value="<?php echo esc_attr($bot_id); ?>" <?php selected($current_bot_id, $bot_id); ?>>
306 <?php echo esc_html($bot_name); ?>
307 </option>
308 <?php endforeach; ?>
309 </select>
310 <span style="color: var(--mxch-text-secondary); font-size: 13px;">
311 <?php esc_html_e('Content will be added to the selected bot\'s knowledge base', 'mxchat'); ?>
312 </span>
313 <span id="mxchat-bot-save-status" style="display: none; color: var(--mxch-success); font-size: 13px;">
314 <?php esc_html_e('Saved', 'mxchat'); ?>
315 </span>
316 </div>
317 </div>
318 </div>
319 <?php
320 }
321
322 /**
323 * Render Global Processing Status - Always visible when processing is active
324 */
325 function mxchat_render_global_processing_status($page_data, $knowledge_manager) {
326 extract($page_data);
327
328 // Only show if there's active processing
329 if (!$is_processing) {
330 return;
331 }
332 ?>
333 <div class="mxch-processing-status-global mxchat-import-section" style="margin-bottom: 24px;">
334 <?php
335 // PDF Processing Status - Using mxchat-status-card class for JavaScript compatibility
336 if ($pdf_status && $pdf_status['status'] === 'processing') : ?>
337 <div class="mxchat-status-card mxch-card" data-card-type="pdf" data-queue-id="<?php echo esc_attr(get_transient('mxchat_active_queue_pdf')); ?>" style="border-left: 4px solid var(--mxch-primary); margin-bottom: 16px;">
338 <div class="mxchat-status-header mxch-card-header" style="background: var(--mxch-primary-light);">
339 <h4 style="margin: 0; display: flex; align-items: center; gap: 8px; font-size: 15px; font-weight: 600;">
340 <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"/><polyline points="14 2 14 8 20 8"/></svg>
341 <?php esc_html_e('PDF Processing Status', 'mxchat'); ?>
342 </h4>
343 <div class="mxchat-status-warning" style="background: #fff3cd; color: #856404; padding: 8px 12px; border-radius: 4px; font-size: 13px; margin: 10px 0;">
344 <?php esc_html_e('Keep this tab open - Processing runs in your browser', 'mxchat'); ?>
345 </div>
346 <form method="post" class="mxchat-stop-form" action="<?php echo esc_url(admin_url('admin-post.php?action=mxchat_stop_processing')); ?>">
347 <?php wp_nonce_field('mxchat_stop_processing_action', 'mxchat_stop_processing_nonce'); ?>
348 <button type="submit" name="stop_processing" class="mxchat-button-secondary mxch-btn mxch-btn-secondary mxch-btn-sm">
349 <?php esc_html_e('Stop Processing', 'mxchat'); ?>
350 </button>
351 </form>
352 </div>
353 <div class="mxchat-progress-bar" style="height: 8px; background: #e2e8f0; border-radius: 4px; overflow: hidden; margin: 0;">
354 <div class="mxchat-progress-fill" style="height: 100%; background: var(--mxch-primary); width: <?php echo esc_attr($pdf_status['percentage']); ?>%; transition: width 0.3s;"></div>
355 </div>
356 <div class="mxchat-status-details mxch-card-body">
357 <p><?php printf(esc_html__('Progress: %1$d of %2$d pages (%3$d%%)', 'mxchat'), absint($pdf_status['processed_pages']), absint($pdf_status['total_pages']), absint($pdf_status['percentage'])); ?></p>
358 <?php if (!empty($pdf_status['failed_pages']) && $pdf_status['failed_pages'] > 0) : ?>
359 <p class="error-count" style="color: var(--mxch-error);"><strong><?php esc_html_e('Failed pages:', 'mxchat'); ?></strong> <?php echo esc_html($pdf_status['failed_pages']); ?></p>
360 <?php endif; ?>
361 <p><strong><?php esc_html_e('Status:', 'mxchat'); ?></strong> <?php esc_html_e('Processing', 'mxchat'); ?></p>
362 </div>
363 </div>
364 <?php endif;
365
366 // Sitemap Processing Status - Using mxchat-status-card class for JavaScript compatibility
367 if ($sitemap_status && $sitemap_status['status'] === 'processing') : ?>
368 <div class="mxchat-status-card mxch-card" data-card-type="sitemap" data-queue-id="<?php echo esc_attr(get_transient('mxchat_active_queue_sitemap')); ?>" style="border-left: 4px solid var(--mxch-primary); margin-bottom: 16px;">
369 <div class="mxchat-status-header mxch-card-header" style="background: var(--mxch-primary-light);">
370 <h4 style="margin: 0; display: flex; align-items: center; gap: 8px; font-size: 15px; font-weight: 600;">
371 <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg>
372 <?php esc_html_e('Sitemap Processing Status', 'mxchat'); ?>
373 </h4>
374 <div class="mxchat-status-warning" style="background: #fff3cd; color: #856404; padding: 8px 12px; border-radius: 4px; font-size: 13px; margin: 10px 0;">
375 <?php esc_html_e('Keep this tab open - Processing runs in your browser', 'mxchat'); ?>
376 </div>
377 <form method="post" class="mxchat-stop-form" action="<?php echo esc_url(admin_url('admin-post.php?action=mxchat_stop_processing')); ?>">
378 <?php wp_nonce_field('mxchat_stop_processing_action', 'mxchat_stop_processing_nonce'); ?>
379 <button type="submit" name="stop_processing" class="mxchat-button-secondary mxch-btn mxch-btn-secondary mxch-btn-sm">
380 <?php esc_html_e('Stop Processing', 'mxchat'); ?>
381 </button>
382 </form>
383 </div>
384 <div class="mxchat-progress-bar" style="height: 8px; background: #e2e8f0; border-radius: 4px; overflow: hidden; margin: 0;">
385 <div class="mxchat-progress-fill" style="height: 100%; background: var(--mxch-primary); width: <?php echo esc_attr($sitemap_status['percentage']); ?>%; transition: width 0.3s;"></div>
386 </div>
387 <div class="mxchat-status-details mxch-card-body">
388 <p><?php printf(esc_html__('Progress: %1$d of %2$d URLs (%3$d%%)', 'mxchat'), absint($sitemap_status['processed_urls']), absint($sitemap_status['total_urls']), absint($sitemap_status['percentage'])); ?></p>
389 <?php if (!empty($sitemap_status['failed_urls']) && $sitemap_status['failed_urls'] > 0) : ?>
390 <p class="error-count" style="color: var(--mxch-error);"><strong><?php esc_html_e('Failed URLs:', 'mxchat'); ?></strong> <?php echo esc_html($sitemap_status['failed_urls']); ?></p>
391 <?php endif; ?>
392 <p><strong><?php esc_html_e('Status:', 'mxchat'); ?></strong> <?php esc_html_e('Processing', 'mxchat'); ?></p>
393 </div>
394 </div>
395 <?php endif;
396
397 // Completed status cards
398 echo $knowledge_manager->mxchat_render_completed_status_cards();
399 ?>
400 </div>
401 <?php
402 }
403
404 /**
405 * Render Import Options Section
406 */
407 function mxchat_render_import_options_section($admin_instance, $knowledge_manager, $page_data) {
408 extract($page_data);
409 $options = $admin_instance->options ?? get_option('mxchat_options', array());
410
411 // Check if user is using WordPress database (not Pinecone or OpenAI Vector Store)
412 $pinecone_options = get_option('mxchat_pinecone_addon_options', array());
413 $is_pinecone_active = ($pinecone_options['mxchat_use_pinecone'] ?? '0') === '1';
414 $vectorstore_options = get_option('mxchat_openai_vectorstore_options', array());
415 $is_vectorstore_active = ($vectorstore_options['mxchat_use_openai_vectorstore'] ?? '0') === '1';
416 $is_using_wordpress_db = !$is_pinecone_active && !$is_vectorstore_active;
417
418 // Check embedding API key
419 $embedding_model = isset($options['embedding_model']) ? esc_attr($options['embedding_model']) : 'text-embedding-ada-002';
420 $has_openai_key = !empty($options['api_key']);
421 $has_voyage_key = !empty($options['voyage_api_key']);
422 $has_gemini_key = !empty($options['gemini_api_key']);
423
424 $has_required_key = false;
425 $required_key_type = '';
426
427 if (strpos($embedding_model, 'text-embedding-') !== false && $has_openai_key) {
428 $has_required_key = true;
429 $required_key_type = 'OpenAI';
430 } elseif (strpos($embedding_model, 'voyage-') !== false && $has_voyage_key) {
431 $has_required_key = true;
432 $required_key_type = 'Voyage AI';
433 } elseif (strpos($embedding_model, 'gemini-embedding-') !== false && $has_gemini_key) {
434 $has_required_key = true;
435 $required_key_type = 'Google Gemini';
436 } elseif (strpos($embedding_model, 'text-embedding-') !== false) {
437 $required_key_type = 'OpenAI';
438 } elseif (strpos($embedding_model, 'voyage-') !== false) {
439 $required_key_type = 'Voyage AI';
440 } elseif (strpos($embedding_model, 'gemini-embedding-') !== false) {
441 $required_key_type = 'Google Gemini';
442 }
443 ?>
444 <div id="import-options" class="mxch-section active">
445 <div class="mxch-content-header">
446 <h1 class="mxch-content-title"><?php esc_html_e('Import Options', 'mxchat'); ?></h1>
447 <p class="mxch-content-subtitle"><?php esc_html_e('Import content to your knowledge base from various sources.', 'mxchat'); ?></p>
448 </div>
449
450 <!-- API Key Status -->
451 <div class="mxch-notice <?php echo $has_required_key ? 'mxch-notice-success' : 'mxch-notice-warning'; ?>" style="margin-bottom: 24px;">
452 <svg class="mxch-notice-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
453 <?php if ($has_required_key): ?>
454 <path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/>
455 <?php else: ?>
456 <path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/>
457 <?php endif; ?>
458 </svg>
459 <div>
460 <?php if ($has_required_key): ?>
461 <?php echo wp_kses_post(sprintf(__('We detected your %s API key. <strong>Remember to add credits to your %s account</strong> before using the knowledgebase.', 'mxchat'), $required_key_type, $required_key_type)); ?>
462 <?php else: ?>
463 <strong><?php esc_html_e('Important:', 'mxchat'); ?></strong>
464 <?php echo sprintf(esc_html__('Before importing knowledge, you must add a %s API key with sufficient credits in the Chatbot settings.', 'mxchat'), $required_key_type); ?>
465 <a href="<?php echo admin_url('admin.php?page=mxchat-max#api-keys'); ?>"><?php esc_html_e('Go to API Key Settings', 'mxchat'); ?></a>
466 <?php endif; ?>
467 </div>
468 </div>
469
470 <?php if ($is_using_wordpress_db): ?>
471 <!-- WordPress Database Caution Notice -->
472 <div class="mxch-notice mxch-notice-warning" style="margin-bottom: 24px;">
473 <svg class="mxch-notice-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
474 <path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/>
475 </svg>
476 <div>
477 <strong><?php esc_html_e('Using WordPress Database:', 'mxchat'); ?></strong>
478 <?php esc_html_e('The WordPress database is not optimized for vector search with large datasets. If you plan to have more than 500 knowledge entries, we highly recommend using Pinecone for better performance and scalability.', 'mxchat'); ?>
479 </div>
480 </div>
481 <?php endif; ?>
482
483 <div class="mxch-card">
484 <div class="mxch-card-header">
485 <h3 class="mxch-card-title">
486 <svg class="mxch-card-title-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>
487 <?php esc_html_e('Choose Import Method', 'mxchat'); ?>
488 </h3>
489 </div>
490 <div class="mxch-card-body">
491 <!-- Import Options Grid -->
492 <div class="mxchat-import-options">
493 <!-- WordPress Import Option -->
494 <button type="button" id="mxchat-open-content-selector" class="mxchat-import-box mxchat-import-wordpress" data-option="wordpress">
495 <div class="mxchat-import-icon">
496 <span class="dashicons dashicons-wordpress"></span>
497 </div>
498 <div class="mxchat-import-content">
499 <h4><?php esc_html_e('WordPress Content', 'mxchat'); ?></h4>
500 <p><?php esc_html_e('Import specific posts and pages to your knowledge base.', 'mxchat'); ?></p>
501 </div>
502 <div class="mxchat-recommended-tag"><?php esc_html_e('Recommended', 'mxchat'); ?></div>
503 </button>
504
505 <!-- Sitemap Import Option -->
506 <button type="button" class="mxchat-import-box" data-option="sitemap" data-placeholder="<?php esc_attr_e('Enter sitemap URL here', 'mxchat'); ?>" data-type="sitemap">
507 <div class="mxchat-import-icon">
508 <span class="dashicons dashicons-admin-site-alt"></span>
509 </div>
510 <div class="mxchat-import-content">
511 <h4><?php esc_html_e('Sitemap Import', 'mxchat'); ?></h4>
512 <p><?php esc_html_e('Use a content-specific sub-sitemap, not the sitemap index.', 'mxchat'); ?></p>
513 </div>
514 </button>
515
516 <!-- Direct URL Import Option -->
517 <button type="button" class="mxchat-import-box" data-option="url" data-placeholder="<?php esc_attr_e('Enter webpage URL here', 'mxchat'); ?>" data-type="url">
518 <div class="mxchat-import-icon">
519 <span class="dashicons dashicons-admin-links"></span>
520 </div>
521 <div class="mxchat-import-content">
522 <h4><?php esc_html_e('Direct URL', 'mxchat'); ?></h4>
523 <p><?php esc_html_e('Import content from any webpage.', 'mxchat'); ?></p>
524 </div>
525 </button>
526
527 <!-- Direct Content Import Option -->
528 <button type="button" class="mxchat-import-box" data-option="content" data-type="content">
529 <div class="mxchat-import-icon">
530 <span class="dashicons dashicons-editor-paste-text"></span>
531 </div>
532 <div class="mxchat-import-content">
533 <h4><?php esc_html_e('Direct Content', 'mxchat'); ?></h4>
534 <p><?php esc_html_e('Submit content to be vectorized.', 'mxchat'); ?></p>
535 </div>
536 </button>
537
538 <!-- PDF Import Option (URL) -->
539 <button type="button" class="mxchat-import-box" data-option="pdf-url" data-placeholder="<?php esc_attr_e('Enter PDF URL here', 'mxchat'); ?>" data-type="pdf">
540 <div class="mxchat-import-icon">
541 <span class="dashicons dashicons-media-document"></span>
542 </div>
543 <div class="mxchat-import-content">
544 <h4><?php esc_html_e('PDF Import', 'mxchat'); ?></h4>
545 <p><?php esc_html_e('Import knowledge from PDF URL.', 'mxchat'); ?></p>
546 </div>
547 </button>
548
549 <!-- PDF File Upload Option -->
550 <button type="button" class="mxchat-import-box" data-option="pdf-upload" data-type="pdf-upload">
551 <div class="mxchat-import-icon">
552 <span class="dashicons dashicons-upload"></span>
553 </div>
554 <div class="mxchat-import-content">
555 <h4><?php esc_html_e('PDF Upload', 'mxchat'); ?></h4>
556 <p><?php esc_html_e('Upload a PDF file from your computer.', 'mxchat'); ?></p>
557 </div>
558 </button>
559 </div>
560
561 <!-- Detected Sitemaps Section (hidden by default, shown when Sitemap Import is clicked) -->
562 <div id="mxchat-detected-sitemaps" class="mxchat-detected-sitemaps" style="display: none;">
563 <div class="mxchat-sitemaps-header">
564 <h4>
565 <span class="dashicons dashicons-admin-site-alt"></span>
566 <?php esc_html_e('Detected Sitemaps', 'mxchat'); ?>
567 </h4>
568 <button type="button" id="mxchat-refresh-sitemaps" class="mxch-btn mxch-btn-ghost mxch-btn-sm" title="<?php esc_attr_e('Refresh', 'mxchat'); ?>">
569 <span class="dashicons dashicons-update"></span>
570 </button>
571 </div>
572 <div id="mxchat-sitemaps-list" class="mxchat-sitemaps-list">
573 <!-- Sitemaps will be loaded here via AJAX -->
574 </div>
575 <input type="hidden" id="mxchat-detect-sitemaps-nonce" value="<?php echo wp_create_nonce('mxchat_detect_sitemaps_nonce'); ?>">
576 <?php if ($multibot_active && $current_bot_id !== 'default') : ?>
577 <input type="hidden" id="mxchat-sitemap-bot-id" value="<?php echo esc_attr($current_bot_id); ?>">
578 <?php endif; ?>
579 </div>
580
581 <!-- No Sitemaps Found Message (hidden by default) -->
582 <div id="mxchat-no-sitemaps" class="mxchat-no-sitemaps" style="display: none;">
583 <span class="dashicons dashicons-info"></span>
584 <p>
585 <?php esc_html_e('No sitemaps detected. You can manually enter a sitemap URL using the input field above.', 'mxchat'); ?>
586 </p>
587 </div>
588
589 <!-- Sitemaps Loading State (hidden by default) -->
590 <div id="mxchat-sitemaps-loading" class="mxchat-sitemaps-loading" style="display: none;">
591 <span class="dashicons dashicons-update spin"></span>
592 <p><?php esc_html_e('Detecting sitemaps...', 'mxchat'); ?></p>
593 </div>
594
595 <!-- Input Areas for URL and Content -->
596 <?php if (!$is_processing) : ?>
597 <div class="mxchat-import-input-area" id="mxchat-url-input-area" style="display: none; margin-top: 20px;">
598 <form id="mxchat-url-form" method="post" action="<?php echo esc_url(admin_url('admin-post.php?action=mxchat_submit_sitemap')); ?>">
599 <?php wp_nonce_field('mxchat_submit_sitemap_action', 'mxchat_submit_sitemap_nonce'); ?>
600 <input type="hidden" name="import_type" id="import_type" value="url">
601 <?php if ($multibot_active && $current_bot_id !== 'default') : ?>
602 <input type="hidden" name="bot_id" value="<?php echo esc_attr($current_bot_id); ?>">
603 <?php endif; ?>
604 <div style="display: flex; gap: 10px;">
605 <input type="url" name="sitemap_url" id="sitemap_url" class="mxch-input" placeholder="<?php esc_attr_e('Enter URL here', 'mxchat'); ?>" required style="flex: 1;" />
606 <button type="submit" name="submit_sitemap" class="mxch-btn mxch-btn-primary">
607 <?php esc_html_e('Import', 'mxchat'); ?>
608 </button>
609 </div>
610 <p class="mxch-field-description" id="url-description-text"></p>
611 </form>
612 </div>
613
614 <div class="mxchat-import-input-area" id="mxchat-content-input-area" style="display: none; margin-top: 20px;">
615 <form id="mxchat-content-form" method="post" action="<?php echo esc_url(admin_url('admin-post.php?action=mxchat_submit_content')); ?>">
616 <?php wp_nonce_field('mxchat_submit_content_action', 'mxchat_submit_content_nonce'); ?>
617 <?php if ($multibot_active && $current_bot_id !== 'default') : ?>
618 <input type="hidden" name="bot_id" value="<?php echo esc_attr($current_bot_id); ?>">
619 <?php endif; ?>
620 <div class="mxch-field">
621 <textarea name="article_content" id="article_content" class="mxch-textarea" placeholder="<?php esc_attr_e('Enter your content here...', 'mxchat'); ?>" required rows="6"></textarea>
622 </div>
623 <div class="mxch-field">
624 <input type="url" name="article_url" id="article_url" class="mxch-input" placeholder="<?php esc_attr_e('Enter source URL (Optional)', 'mxchat'); ?>">
625 <div style="margin-top: 8px; display: flex; align-items: center; gap: 8px;">
626 <label style="display: flex; align-items: center; gap: 6px; font-size: 13px; color: var(--mxch-text-secondary); cursor: pointer;">
627 <input type="checkbox" id="mxchat-unique-url-toggle" style="margin: 0;">
628 <?php esc_html_e('Generate unique URL for duplicate content', 'mxchat'); ?>
629 </label>
630 <button type="button" id="mxchat-generate-unique-url" class="mxch-btn mxch-btn-secondary mxch-btn-sm" style="display: none;">
631 <span class="dashicons dashicons-randomize" style="font-size: 14px; margin-top: 2px;"></span>
632 <?php esc_html_e('Generate Unique', 'mxchat'); ?>
633 </button>
634 </div>
635 <p class="mxch-field-description">
636 <?php esc_html_e('Enable this option to submit multiple entries with the same base URL. A unique reference will be appended.', 'mxchat'); ?>
637 </p>
638 </div>
639 <button type="submit" name="submit_content" class="mxch-btn mxch-btn-primary">
640 <?php esc_html_e('Import Content', 'mxchat'); ?>
641 </button>
642 </form>
643 </div>
644
645 <div class="mxchat-import-input-area" id="mxchat-pdf-upload-area" style="display: none; margin-top: 20px;">
646 <form id="mxchat-pdf-upload-form" method="post" action="<?php echo esc_url(admin_url('admin-post.php?action=mxchat_submit_pdf_file')); ?>" enctype="multipart/form-data">
647 <?php wp_nonce_field('mxchat_submit_pdf_file_action', 'mxchat_submit_pdf_file_nonce'); ?>
648 <?php if ($multibot_active && $current_bot_id !== 'default') : ?>
649 <input type="hidden" name="bot_id" value="<?php echo esc_attr($current_bot_id); ?>">
650 <?php endif; ?>
651 <div class="mxch-field">
652 <input type="file" name="pdf_file" id="mxchat-pdf-file-input" accept=".pdf" required>
653 </div>
654 <p class="mxch-field-description">
655 <?php esc_html_e('Select a PDF file from your computer to import into the knowledge base. Maximum file size depends on your server settings.', 'mxchat'); ?>
656 </p>
657 <button type="submit" name="submit_pdf_file" class="mxch-btn mxch-btn-primary">
658 <?php esc_html_e('Import PDF', 'mxchat'); ?>
659 </button>
660 </form>
661 </div>
662 <?php endif; ?>
663 </div>
664 </div>
665 </div>
666 <?php
667 }
668
669 /**
670 * Render Processing Status Cards (legacy - kept for reference)
671 * Note: Processing status is now rendered globally via mxchat_render_global_processing_status()
672 */
673 function mxchat_render_processing_status($page_data, $knowledge_manager) {
674 extract($page_data);
675
676 // PDF Processing Status
677 if ($pdf_status && $pdf_status['status'] === 'processing') : ?>
678 <div class="mxch-card" style="margin-top: 20px;">
679 <div class="mxch-card-header" style="background: var(--mxch-primary-light);">
680 <h3 class="mxch-card-title"><?php esc_html_e('PDF Processing Status', 'mxchat'); ?></h3>
681 <div style="display: flex; gap: 10px;">
682 <form method="post" action="<?php echo esc_url(admin_url('admin-post.php?action=mxchat_stop_processing')); ?>">
683 <?php wp_nonce_field('mxchat_stop_processing_action', 'mxchat_stop_processing_nonce'); ?>
684 <button type="submit" name="stop_processing" class="mxch-btn mxch-btn-secondary mxch-btn-sm">
685 <?php esc_html_e('Stop Processing', 'mxchat'); ?>
686 </button>
687 </form>
688 <button type="button" class="mxch-btn mxch-btn-primary mxch-btn-sm mxchat-manual-batch-btn" data-process-type="pdf" data-url="<?php echo esc_attr(get_transient('mxchat_last_pdf_url')); ?>">
689 <?php esc_html_e('Process Batch', 'mxchat'); ?>
690 </button>
691 </div>
692 </div>
693 <div class="mxch-card-body">
694 <div class="mxchat-progress-bar" style="height: 8px; background: #e2e8f0; border-radius: 4px; overflow: hidden; margin-bottom: 15px;">
695 <div class="mxchat-progress-fill" style="height: 100%; background: var(--mxch-primary); width: <?php echo esc_attr($pdf_status['percentage']); ?>%; transition: width 0.3s;"></div>
696 </div>
697 <p><?php printf(esc_html__('Progress: %1$d of %2$d pages (%3$d%%)', 'mxchat'), absint($pdf_status['processed_pages']), absint($pdf_status['total_pages']), absint($pdf_status['percentage'])); ?></p>
698 <?php if (!empty($pdf_status['failed_pages']) && $pdf_status['failed_pages'] > 0) : ?>
699 <p><strong><?php esc_html_e('Failed pages:', 'mxchat'); ?></strong> <?php echo esc_html($pdf_status['failed_pages']); ?></p>
700 <?php endif; ?>
701 <p><strong><?php esc_html_e('Last update:', 'mxchat'); ?></strong> <?php echo esc_html($pdf_status['last_update']); ?></p>
702 </div>
703 </div>
704 <?php endif;
705
706 // Sitemap Processing Status
707 if ($sitemap_status && $sitemap_status['status'] === 'processing') : ?>
708 <div class="mxch-card" style="margin-top: 20px;">
709 <div class="mxch-card-header" style="background: var(--mxch-primary-light);">
710 <h3 class="mxch-card-title"><?php esc_html_e('Sitemap Processing Status', 'mxchat'); ?></h3>
711 <div style="display: flex; gap: 10px;">
712 <form method="post" action="<?php echo esc_url(admin_url('admin-post.php?action=mxchat_stop_processing')); ?>">
713 <?php wp_nonce_field('mxchat_stop_processing_action', 'mxchat_stop_processing_nonce'); ?>
714 <button type="submit" name="stop_processing" class="mxch-btn mxch-btn-secondary mxch-btn-sm">
715 <?php esc_html_e('Stop Processing', 'mxchat'); ?>
716 </button>
717 </form>
718 <button type="button" class="mxch-btn mxch-btn-primary mxch-btn-sm mxchat-manual-batch-btn" data-process-type="sitemap" data-url="<?php echo esc_attr(get_transient('mxchat_last_sitemap_url')); ?>">
719 <?php esc_html_e('Process Batch', 'mxchat'); ?>
720 </button>
721 </div>
722 </div>
723 <div class="mxch-card-body">
724 <div class="mxchat-progress-bar" style="height: 8px; background: #e2e8f0; border-radius: 4px; overflow: hidden; margin-bottom: 15px;">
725 <div class="mxchat-progress-fill" style="height: 100%; background: var(--mxch-primary); width: <?php echo esc_attr($sitemap_status['percentage']); ?>%; transition: width 0.3s;"></div>
726 </div>
727 <p><?php printf(esc_html__('Progress: %1$d of %2$d URLs (%3$d%%)', 'mxchat'), absint($sitemap_status['processed_urls']), absint($sitemap_status['total_urls']), absint($sitemap_status['percentage'])); ?></p>
728 <?php if (!empty($sitemap_status['failed_urls']) && $sitemap_status['failed_urls'] > 0) : ?>
729 <p><strong><?php esc_html_e('Failed URLs:', 'mxchat'); ?></strong> <?php echo esc_html($sitemap_status['failed_urls']); ?></p>
730 <?php endif; ?>
731 <p><strong><?php esc_html_e('Last update:', 'mxchat'); ?></strong> <?php echo esc_html($sitemap_status['last_update']); ?></p>
732 </div>
733 </div>
734 <?php endif;
735
736 // Completed status cards
737 echo $knowledge_manager->mxchat_render_completed_status_cards();
738 }
739
740 /**
741 * Render Knowledge Base Section (table)
742 */
743 function mxchat_render_knowledge_base_section($admin_instance, $knowledge_manager, $page_data) {
744 extract($page_data);
745
746 // Group prompts by source_url for chunked content display
747 $grouped_prompts = array();
748
749 if ($prompts) {
750 foreach ($prompts as $prompt) {
751 $source_url = $prompt->source_url ?? '';
752
753 // Check if chunk metadata exists on the record itself (Pinecone)
754 if (isset($prompt->chunk_index) && $prompt->chunk_index !== null) {
755 // Pinecone: metadata is already on the record object
756 $prompt->chunk_metadata = array(
757 'chunk_index' => intval($prompt->chunk_index),
758 'total_chunks' => isset($prompt->total_chunks) ? intval($prompt->total_chunks) : null,
759 'is_chunked' => isset($prompt->is_chunked) ? (bool) $prompt->is_chunked : true
760 );
761 $prompt->display_content = $prompt->article_content; // Pinecone content is already clean
762 } else {
763 // WordPress: Parse chunk metadata from JSON prefix in article_content
764 if (class_exists('MxChat_Chunker')) {
765 $chunk_meta = MxChat_Chunker::parse_stored_chunk($prompt->article_content);
766 $prompt->chunk_metadata = $chunk_meta['metadata'];
767 $prompt->display_content = $chunk_meta['text']; // Store clean content without JSON prefix
768 } else {
769 $prompt->chunk_metadata = array();
770 $prompt->display_content = $prompt->article_content;
771 }
772 }
773
774 if (!empty($source_url)) {
775 if (!isset($grouped_prompts[$source_url])) {
776 $grouped_prompts[$source_url] = array();
777 }
778 $grouped_prompts[$source_url][] = $prompt;
779 } else {
780 // Use a unique key for ungrouped prompts
781 $grouped_prompts['_ungrouped_' . $prompt->id] = array($prompt);
782 }
783 }
784
785 // Sort each group by chunk_index from metadata
786 foreach ($grouped_prompts as $source_url => &$group) {
787 usort($group, function($a, $b) {
788 $index_a = isset($a->chunk_metadata['chunk_index']) ? intval($a->chunk_metadata['chunk_index']) : 0;
789 $index_b = isset($b->chunk_metadata['chunk_index']) ? intval($b->chunk_metadata['chunk_index']) : 0;
790 return $index_a - $index_b;
791 });
792 }
793 unset($group); // Break reference
794 }
795 $display_index = 0;
796 ?>
797 <div id="knowledge-base" class="mxch-section">
798 <div class="mxch-content-header">
799 <h1 class="mxch-content-title"><?php esc_html_e('Knowledge Base', 'mxchat'); ?></h1>
800 <p class="mxch-content-subtitle"><?php esc_html_e('View and manage your imported knowledge entries.', 'mxchat'); ?></p>
801 </div>
802
803 <?php if (!empty($use_vectorstore)) : ?>
804 <div class="mxch-notice mxch-notice-info" style="margin-bottom: 20px;">
805 <svg class="mxch-notice-icon" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></svg>
806 <span><strong><?php esc_html_e('OpenAI Vector Store is Active', 'mxchat'); ?></strong> - <?php esc_html_e('Your chatbot is using OpenAI\'s hosted Vector Store for knowledge retrieval. The entries below are stored locally and are not being searched. To use this local database, disable Vector Store in the Integrations settings.', 'mxchat'); ?></span>
807 </div>
808 <?php endif; ?>
809
810 <div class="mxch-card">
811 <div class="mxch-card-header">
812 <h3 class="mxch-card-title">
813 <?php esc_html_e('Knowledge Entries', 'mxchat'); ?>
814 <span id="mxchat-entry-count" style="font-weight: normal; color: var(--mxch-text-secondary);">(<?php echo esc_html($total_records); ?>)</span>
815 <?php if (!empty($use_vectorstore)) : ?>
816 <span style="display: inline-flex; align-items: center; gap: 4px; padding: 4px 8px; border-radius: 12px; font-size: 12px; font-weight: 500; margin-left: 8px; background: #fff3e0; color: #e65100;">
817 <span class="dashicons dashicons-database" style="font-size: 14px;"></span>
818 <?php esc_html_e('Not in use', 'mxchat'); ?>
819 </span>
820 <?php elseif ($use_pinecone) : ?>
821 <span style="display: inline-flex; align-items: center; gap: 4px; padding: 4px 8px; border-radius: 12px; font-size: 12px; font-weight: 500; margin-left: 8px; background: #e3f2fd; color: #1976d2;">
822 <span class="dashicons dashicons-cloud" style="font-size: 14px;"></span>
823 <?php esc_html_e('Pinecone', 'mxchat'); ?>
824 </span>
825 <?php else : ?>
826 <span style="display: inline-flex; align-items: center; gap: 4px; padding: 4px 8px; border-radius: 12px; font-size: 12px; font-weight: 500; margin-left: 8px; background: #f3e5f5; color: #7b1fa2;">
827 <span class="dashicons dashicons-database-view" style="font-size: 14px;"></span>
828 <?php esc_html_e('WordPress DB', 'mxchat'); ?>
829 </span>
830 <?php endif; ?>
831 </h3>
832 <div class="mxch-kb-header-controls">
833 <form method="get" id="knowledge-search" class="mxch-kb-search-form">
834 <?php wp_nonce_field('mxchat_prompts_search_nonce'); ?>
835 <input type="hidden" name="page" value="mxchat-prompts" />
836 <?php if ($multibot_active && !empty($current_bot_id) && $current_bot_id !== 'default') : ?>
837 <input type="hidden" name="bot_id" value="<?php echo esc_attr($current_bot_id); ?>" />
838 <?php endif; ?>
839 <input type="text" name="search" class="mxch-input mxch-input-sm mxch-kb-search-input" placeholder="<?php esc_attr_e('Search...', 'mxchat'); ?>" value="<?php echo esc_attr($search_query); ?>" />
840 <select name="content_type" class="mxch-select mxch-kb-type-filter" onchange="this.form.submit()">
841 <option value=""><?php esc_html_e('All Types', 'mxchat'); ?></option>
842 <?php
843 $post_types = get_post_types(array('public' => true), 'objects');
844 foreach ($post_types as $post_type) {
845 echo '<option value="' . esc_attr($post_type->name) . '" ' . selected($content_type_filter, $post_type->name, false) . '>' . esc_html($post_type->label) . '</option>';
846 }
847 ?>
848 <option value="pdf" <?php selected($content_type_filter, 'pdf'); ?>><?php esc_html_e('PDFs', 'mxchat'); ?></option>
849 <option value="url" <?php selected($content_type_filter, 'url'); ?>><?php esc_html_e('URLs', 'mxchat'); ?></option>
850 </select>
851 </form>
852 <!-- Delete Button Container - transforms between Delete All and Delete Selected -->
853 <div class="mxchat-delete-container" id="mxchat-delete-container">
854 <!-- Delete All Form (shown when nothing selected) -->
855 <?php
856 // Build confirm message and button text based on active filter
857 $delete_all_label = __('Delete All', 'mxchat');
858 $delete_all_confirm = __('Are you sure you want to delete all knowledge?', 'mxchat');
859 if (!empty($content_type_filter)) {
860 $filter_display = $content_type_filter;
861 // Try to get a human-readable label
862 $pt_obj = get_post_type_object($content_type_filter);
863 if ($pt_obj) {
864 $filter_display = $pt_obj->label;
865 } elseif ($content_type_filter === 'pdf') {
866 $filter_display = 'PDFs';
867 } elseif ($content_type_filter === 'url') {
868 $filter_display = 'URLs';
869 }
870 /* translators: %s: content type label (e.g. "Pages", "PDFs") */
871 $delete_all_label = sprintf(__('Delete All %s', 'mxchat'), $filter_display);
872 /* translators: %s: content type label */
873 $delete_all_confirm = sprintf(__('Are you sure you want to delete all %s from the knowledge base?', 'mxchat'), $filter_display);
874 }
875 ?>
876 <form method="post" action="<?php echo esc_url(admin_url('admin-post.php?action=mxchat_delete_all_prompts')); ?>" id="mxchat-delete-all-form" onsubmit="return confirm('<?php echo esc_attr($delete_all_confirm); ?>');">
877 <?php wp_nonce_field('mxchat_delete_all_prompts_action', 'mxchat_delete_all_prompts_nonce'); ?>
878 <input type="hidden" name="data_source" value="<?php echo esc_attr($data_source); ?>" />
879 <input type="hidden" name="bot_id" value="<?php echo esc_attr($current_bot_id); ?>" />
880 <?php if (!empty($content_type_filter)) : ?>
881 <input type="hidden" name="content_type_filter" value="<?php echo esc_attr($content_type_filter); ?>" />
882 <?php endif; ?>
883 <button type="submit" class="mxch-btn mxch-btn-secondary mxch-btn-sm" style="color: var(--mxch-error);">
884 <span class="dashicons dashicons-trash" style="font-size: 14px;"></span>
885 <?php echo esc_html($delete_all_label); ?>
886 </button>
887 </form>
888 <!-- Delete Selected Button (shown when items selected) -->
889 <button type="button"
890 class="mxch-btn mxch-btn-secondary mxch-btn-sm mxchat-bulk-delete"
891 id="mxchat-delete-selected-entries"
892 style="display: none; color: var(--mxch-error);"
893 data-nonce="<?php echo wp_create_nonce('mxchat_bulk_delete_knowledge_nonce'); ?>"
894 data-bot-id="<?php echo esc_attr($current_bot_id); ?>"
895 data-data-source="<?php echo esc_attr($data_source); ?>">
896 <span class="dashicons dashicons-trash" style="font-size: 14px;"></span>
897 <span class="mxchat-bulk-delete-text"><?php esc_html_e('Delete Selected', 'mxchat'); ?></span>
898 <span class="mxchat-selected-count" id="mxchat-selected-entry-count"></span>
899 </button>
900 </div>
901 </div>
902 </div>
903 <div class="mxch-card-body" style="padding: 0;">
904 <?php if ($use_pinecone) : ?>
905 <div class="mxch-notice mxch-notice-info mxch-pinecone-notice" style="margin: 16px; border-radius: var(--mxch-radius-md); display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 10px;">
906 <div style="display: flex; align-items: center; gap: 8px;">
907 <svg class="mxch-notice-icon" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></svg>
908 <span><?php esc_html_e('Changes may take up to 60 seconds to process in Pinecone. Click "Refresh Entries" to see updates.', 'mxchat'); ?></span>
909 </div>
910 <button type="button" id="mxchat-refresh-pinecone-entries" class="mxch-btn mxch-btn-secondary mxch-btn-sm">
911 <span class="dashicons dashicons-update"></span>
912 <?php esc_html_e('Refresh Entries', 'mxchat'); ?>
913 </button>
914 </div>
915 <?php endif; ?>
916
917 <div class="mxchat-table-wrapper" style="overflow-x: auto;">
918 <table class="mxchat-records-table" id="mxchat-records-table" style="width: 100%; border-collapse: collapse;">
919 <thead>
920 <tr style="background: #f8fafc; border-bottom: 1px solid var(--mxch-card-border);">
921 <th style="padding: 12px 16px; width: 40px; text-align: center;">
922 <input type="checkbox" id="mxchat-select-all-entries" class="mxchat-entry-checkbox-all" title="<?php esc_attr_e('Select All', 'mxchat'); ?>">
923 </th>
924 <th style="padding: 12px 16px; text-align: left; font-weight: 600; color: var(--mxch-text-secondary); font-size: 12px; text-transform: uppercase;"><?php esc_html_e('ID', 'mxchat'); ?></th>
925 <th style="padding: 12px 16px; text-align: left; font-weight: 600; color: var(--mxch-text-secondary); font-size: 12px; text-transform: uppercase;"><?php esc_html_e('Content', 'mxchat'); ?></th>
926 <th style="padding: 12px 16px; text-align: left; font-weight: 600; color: var(--mxch-text-secondary); font-size: 12px; text-transform: uppercase;"><?php esc_html_e('Source', 'mxchat'); ?></th>
927 <th style="padding: 12px 16px; text-align: left; font-weight: 600; color: var(--mxch-text-secondary); font-size: 12px; text-transform: uppercase;"><?php esc_html_e('Actions', 'mxchat'); ?></th>
928 </tr>
929 </thead>
930 <tbody id="mxchat-entries-tbody">
931 <?php if (empty($grouped_prompts)) : ?>
932 <tr>
933 <td colspan="5" style="padding: 40px; text-align: center; color: var(--mxch-text-muted);">
934 <?php esc_html_e('No knowledge entries found. Use the Import Options to add content.', 'mxchat'); ?>
935 </td>
936 </tr>
937 <?php else : ?>
938 <?php
939 // Display grouped prompts
940 foreach ($grouped_prompts as $source_url => $group) :
941 $chunk_count = count($group);
942 $first_prompt = $group[0];
943 $display_index++;
944
945 if ($chunk_count > 1) :
946 // Multiple chunks - show grouped row with expand button
947 $group_id = 'group-' . md5($source_url);
948 ?>
949 <tr id="prompt-<?php echo esc_attr($first_prompt->id); ?>"
950 class="mxchat-chunk-group-header"
951 data-source="<?php echo esc_attr($data_source); ?>"
952 data-group-id="<?php echo esc_attr($group_id); ?>"
953 style="border-bottom: 1px solid var(--mxch-card-border); <?php if ($data_source === 'pinecone') echo 'background: rgba(33, 150, 243, 0.02);'; ?>">
954 <td style="padding: 12px 16px; text-align: center;">
955 <input type="checkbox"
956 class="mxchat-entry-checkbox"
957 data-entry-id="<?php echo esc_attr($first_prompt->id); ?>"
958 data-source="<?php echo esc_attr($data_source); ?>"
959 data-source-url="<?php echo esc_attr($source_url); ?>"
960 data-is-group="true"
961 data-chunk-count="<?php echo esc_attr($chunk_count); ?>">
962 </td>
963 <td style="padding: 12px 16px; font-size: 13px;">
964 <?php if ($data_source === 'pinecone') : ?>
965 <?php echo esc_html($display_index + (($current_page - 1) * $per_page)); ?>
966 <?php else : ?>
967 <?php echo esc_html($first_prompt->id); ?>
968 <?php endif; ?>
969 </td>
970 <td class="mxchat-content-cell" style="padding: 12px 16px; font-size: 13px;">
971 <div class="mxchat-chunk-group-info">
972 <button type="button" class="mxchat-chunk-toggle" data-group-id="<?php echo esc_attr($group_id); ?>">
973 <span class="dashicons dashicons-arrow-right-alt2"></span>
974 </button>
975 <span class="mxchat-chunk-badge"><?php echo esc_html($chunk_count); ?> <?php esc_html_e('chunks', 'mxchat'); ?></span>
976 <span class="mxchat-chunk-preview">
977 <?php
978 $parent_content = isset($first_prompt->display_content) ? $first_prompt->display_content : $first_prompt->article_content;
979 $content_preview = mb_substr($parent_content, 0, 100);
980 echo esc_html($content_preview . '...');
981 ?>
982 </span>
983 </div>
984 </td>
985 <td class="mxchat-url-cell" style="padding: 12px 16px; font-size: 13px;">
986 <?php if (!empty($source_url) && strpos($source_url, 'mxchat://') !== 0 && strpos($source_url, '_ungrouped_') !== 0) : ?>
987 <a href="<?php echo esc_url($source_url); ?>" target="_blank" style="color: var(--mxch-primary); text-decoration: none;">
988 <span class="dashicons dashicons-external" style="font-size: 14px;"></span>
989 <?php esc_html_e('View Source', 'mxchat'); ?>
990 </a>
991 <?php else : ?>
992 <span style="color: var(--mxch-text-muted);"><?php esc_html_e('Manual Content', 'mxchat'); ?></span>
993 <?php endif; ?>
994 </td>
995 <td class="mxchat-actions-cell" style="padding: 12px 16px; white-space: nowrap;">
996 <button type="button"
997 class="mxch-btn mxch-btn-ghost mxch-btn-sm mxchat-inspect-entry-btn"
998 data-source-url="<?php echo esc_attr($source_url); ?>"
999 data-entry-id="<?php echo esc_attr($first_prompt->id); ?>"
1000 data-data-source="<?php echo esc_attr($data_source); ?>"
1001 data-bot-id="<?php echo esc_attr($current_bot_id); ?>"
1002 data-nonce="<?php echo wp_create_nonce('mxchat_inspect_entry_nonce'); ?>"
1003 title="<?php esc_attr_e('View indexed content', 'mxchat'); ?>">
1004 <span class="dashicons dashicons-visibility" style="font-size: 14px;"></span>
1005 </button>
1006 <?php if ($data_source !== 'pinecone') : ?>
1007 <button type="button"
1008 class="mxch-btn mxch-btn-ghost mxch-btn-sm mxchat-edit-entry-btn"
1009 data-source-url="<?php echo esc_attr($source_url); ?>"
1010 data-entry-id="<?php echo esc_attr($first_prompt->id); ?>"
1011 data-data-source="<?php echo esc_attr($data_source); ?>"
1012 data-bot-id="<?php echo esc_attr($current_bot_id); ?>"
1013 data-nonce="<?php echo wp_create_nonce('mxchat_edit_entry_nonce'); ?>"
1014 title="<?php esc_attr_e('Edit content', 'mxchat'); ?>">
1015 <span class="dashicons dashicons-edit" style="font-size: 14px;"></span>
1016 </button>
1017 <?php endif; ?>
1018 <button type="button"
1019 class="mxch-btn mxch-btn-ghost mxch-btn-sm delete-button-group"
1020 data-source-url="<?php echo esc_attr($source_url); ?>"
1021 data-chunk-count="<?php echo esc_attr($chunk_count); ?>"
1022 data-data-source="<?php echo esc_attr($data_source); ?>"
1023 data-bot-id="<?php echo esc_attr($current_bot_id); ?>"
1024 data-nonce="<?php echo wp_create_nonce('mxchat_delete_chunks_nonce'); ?>"
1025 style="color: var(--mxch-error);"
1026 title="<?php esc_attr_e('Delete all chunks', 'mxchat'); ?>">
1027 <span class="dashicons dashicons-trash" style="font-size: 14px;"></span>
1028 </button>
1029 </td>
1030 </tr>
1031 <?php
1032 // Render hidden chunk rows
1033 foreach ($group as $chunk_index => $prompt) :
1034 $meta_chunk_index = isset($prompt->chunk_metadata['chunk_index']) ? intval($prompt->chunk_metadata['chunk_index']) : $chunk_index;
1035 $meta_total_chunks = isset($prompt->chunk_metadata['total_chunks']) ? intval($prompt->chunk_metadata['total_chunks']) : $chunk_count;
1036 $content = isset($prompt->display_content) ? $prompt->display_content : $prompt->article_content;
1037 $preview_length = 150;
1038 $content_preview = mb_strlen($content) > $preview_length
1039 ? mb_substr($content, 0, $preview_length) . '...'
1040 : $content;
1041 ?>
1042 <tr id="prompt-<?php echo esc_attr($prompt->id); ?>"
1043 class="mxchat-chunk-row <?php echo esc_attr($group_id); ?>"
1044 data-source="<?php echo esc_attr($data_source); ?>"
1045 style="display: none; background: #f8f9fa; border-bottom: 1px solid var(--mxch-card-border);">
1046 <td style="padding: 12px 16px; text-align: center;">
1047 <!-- Checkbox placeholder for chunk rows -->
1048 </td>
1049 <td style="padding: 12px 16px 12px 30px; font-size: 13px;">
1050 <!-- Hidden ID column for chunks -->
1051 </td>
1052 <td class="mxchat-content-cell" style="padding: 12px 16px; font-size: 13px;">
1053 <div class="mxchat-accordion-wrapper">
1054 <div class="mxchat-content-preview">
1055 <span class="mxchat-chunk-indicator" style="margin-right: 10px; color: var(--mxch-text-secondary); font-size: 12px;">
1056 <?php printf(esc_html__('Chunk %d of %d', 'mxchat'), $meta_chunk_index + 1, $meta_total_chunks); ?>
1057 </span>
1058 <span class="preview-text"><?php echo esc_html($content_preview); ?></span>
1059 <?php if (mb_strlen($content) > $preview_length) : ?>
1060 <button class="mxchat-expand-toggle" type="button">
1061 <span class="dashicons dashicons-arrow-down-alt2"></span>
1062 </button>
1063 <?php endif; ?>
1064 </div>
1065 <div class="mxchat-content-full" style="display: none;">
1066 <div class="content-view">
1067 <?php
1068 if (preg_match('/[\x{0590}-\x{05FF}]/u', $content)) {
1069 echo '<div dir="rtl" lang="he" class="rtl-content">';
1070 echo wp_kses_post(wpautop($content));
1071 echo '</div>';
1072 } else {
1073 echo wp_kses_post(wpautop($content));
1074 }
1075 ?>
1076 </div>
1077 </div>
1078 </div>
1079 </td>
1080 <td class="mxchat-url-cell" style="padding: 12px 16px; font-size: 13px;">
1081 <span class="mxchat-chunk-label" style="color: var(--mxch-text-muted);"><?php esc_html_e('Same as parent', 'mxchat'); ?></span>
1082 </td>
1083 <td class="mxchat-actions-cell" style="padding: 12px 16px;">
1084 <span class="mxchat-chunk-label" style="color: var(--mxch-text-muted);"><?php esc_html_e('Managed by group', 'mxchat'); ?></span>
1085 </td>
1086 </tr>
1087 <?php endforeach; ?>
1088 <?php else :
1089 // Single entry - display normally with accordion
1090 $prompt = $first_prompt;
1091 $content = isset($prompt->display_content) ? $prompt->display_content : $prompt->article_content;
1092 $preview_length = 150;
1093 $content_preview = mb_strlen($content) > $preview_length
1094 ? mb_substr($content, 0, $preview_length) . '...'
1095 : $content;
1096 ?>
1097 <tr id="prompt-<?php echo esc_attr($prompt->id); ?>"
1098 data-source="<?php echo esc_attr($data_source); ?>"
1099 style="border-bottom: 1px solid var(--mxch-card-border); <?php if ($data_source === 'pinecone') echo 'background: rgba(33, 150, 243, 0.02);'; ?>">
1100 <td style="padding: 12px 16px; text-align: center;">
1101 <input type="checkbox"
1102 class="mxchat-entry-checkbox"
1103 data-entry-id="<?php echo esc_attr($prompt->id); ?>"
1104 data-source="<?php echo esc_attr($data_source); ?>"
1105 data-source-url="<?php echo esc_attr($source_url); ?>"
1106 data-is-group="false">
1107 </td>
1108 <td style="padding: 12px 16px; font-size: 13px;">
1109 <?php if ($data_source === 'pinecone') : ?>
1110 <?php echo esc_html($display_index + (($current_page - 1) * $per_page)); ?>
1111 <?php else : ?>
1112 <?php echo esc_html($prompt->id); ?>
1113 <?php endif; ?>
1114 </td>
1115 <td class="mxchat-content-cell" style="padding: 12px 16px; font-size: 13px;">
1116 <div class="mxchat-accordion-wrapper">
1117 <div class="mxchat-content-preview">
1118 <span class="preview-text"><?php echo esc_html($content_preview); ?></span>
1119 <?php if (mb_strlen($content) > $preview_length) : ?>
1120 <button class="mxchat-expand-toggle" type="button">
1121 <span class="dashicons dashicons-arrow-down-alt2"></span>
1122 </button>
1123 <?php endif; ?>
1124 </div>
1125 <div class="mxchat-content-full" style="display: none;">
1126 <div class="content-view">
1127 <?php
1128 if (preg_match('/[\x{0590}-\x{05FF}]/u', $content)) {
1129 echo '<div dir="rtl" lang="he" class="rtl-content">';
1130 echo wp_kses_post(wpautop($content));
1131 echo '</div>';
1132 } else {
1133 echo wp_kses_post(wpautop($content));
1134 }
1135 ?>
1136 </div>
1137 </div>
1138 </div>
1139 </td>
1140 <td class="mxchat-url-cell" style="padding: 12px 16px; font-size: 13px;">
1141 <?php
1142 $actual_source = $source_url;
1143 if (strpos($source_url, '_ungrouped_') === 0) {
1144 $actual_source = $prompt->source_url ?? '';
1145 }
1146 if (!empty($actual_source) && strpos($actual_source, 'mxchat://') !== 0) : ?>
1147 <a href="<?php echo esc_url($actual_source); ?>" target="_blank" style="color: var(--mxch-primary); text-decoration: none;">
1148 <span class="dashicons dashicons-external" style="font-size: 14px;"></span>
1149 <?php esc_html_e('View', 'mxchat'); ?>
1150 </a>
1151 <?php else : ?>
1152 <span style="color: var(--mxch-text-muted);"><?php esc_html_e('Manual', 'mxchat'); ?></span>
1153 <?php endif; ?>
1154 </td>
1155 <td style="padding: 12px 16px; white-space: nowrap;">
1156 <button type="button"
1157 class="mxch-btn mxch-btn-ghost mxch-btn-sm mxchat-inspect-entry-btn"
1158 data-source-url="<?php echo esc_attr($prompt->source_url ?? ''); ?>"
1159 data-entry-id="<?php echo esc_attr($prompt->id); ?>"
1160 data-data-source="<?php echo esc_attr($data_source); ?>"
1161 data-bot-id="<?php echo esc_attr($current_bot_id); ?>"
1162 data-nonce="<?php echo wp_create_nonce('mxchat_inspect_entry_nonce'); ?>"
1163 title="<?php esc_attr_e('View indexed content', 'mxchat'); ?>">
1164 <span class="dashicons dashicons-visibility" style="font-size: 14px;"></span>
1165 </button>
1166 <?php if ($data_source !== 'pinecone') : ?>
1167 <button type="button"
1168 class="mxch-btn mxch-btn-ghost mxch-btn-sm mxchat-edit-entry-btn"
1169 data-source-url="<?php echo esc_attr($prompt->source_url ?? ''); ?>"
1170 data-entry-id="<?php echo esc_attr($prompt->id); ?>"
1171 data-data-source="<?php echo esc_attr($data_source); ?>"
1172 data-bot-id="<?php echo esc_attr($current_bot_id); ?>"
1173 data-nonce="<?php echo wp_create_nonce('mxchat_edit_entry_nonce'); ?>"
1174 title="<?php esc_attr_e('Edit content', 'mxchat'); ?>">
1175 <span class="dashicons dashicons-edit" style="font-size: 14px;"></span>
1176 </button>
1177 <?php endif; ?>
1178 <?php if ($data_source === 'pinecone') : ?>
1179 <button type="button" class="mxch-btn mxch-btn-ghost mxch-btn-sm delete-button-ajax" data-vector-id="<?php echo esc_attr($prompt->id); ?>" data-bot-id="<?php echo esc_attr($current_bot_id); ?>" data-nonce="<?php echo wp_create_nonce('mxchat_delete_pinecone_prompt_nonce'); ?>" style="color: var(--mxch-error);">
1180 <span class="dashicons dashicons-trash" style="font-size: 14px;"></span>
1181 </button>
1182 <?php else : ?>
1183 <button type="button" class="mxch-btn mxch-btn-ghost mxch-btn-sm delete-button-wordpress" data-entry-id="<?php echo esc_attr($prompt->id); ?>" data-bot-id="<?php echo esc_attr($current_bot_id); ?>" data-nonce="<?php echo wp_create_nonce('mxchat_delete_wordpress_prompt_nonce'); ?>" style="color: var(--mxch-error);">
1184 <span class="dashicons dashicons-trash" style="font-size: 14px;"></span>
1185 </button>
1186 <?php endif; ?>
1187 </td>
1188 </tr>
1189 <?php endif; ?>
1190 <?php endforeach; ?>
1191 <?php endif; ?>
1192 </tbody>
1193 </table>
1194 </div>
1195
1196 <!-- Pagination wrapper - always present so JS can populate it after processing -->
1197 <div id="mxchat-kb-pagination" class="mxchat-kb-pagination-wrapper" style="<?php echo $total_pages > 1 ? 'padding: 16px; border-top: 1px solid var(--mxch-card-border); text-align: center;' : ''; ?>" data-current-page="<?php echo esc_attr($current_page); ?>" data-total-pages="<?php echo esc_attr($total_pages); ?>" data-search="<?php echo esc_attr($search_query); ?>" data-content-type="<?php echo esc_attr($content_type_filter); ?>">
1198 <?php if ($total_pages > 1) : ?>
1199 <div class="mxchat-ajax-pagination" data-current-page="<?php echo esc_attr($current_page); ?>" data-total-pages="<?php echo esc_attr($total_pages); ?>">
1200 <?php if ($current_page > 1) : ?>
1201 <a href="#" class="mxchat-page-link" data-page="<?php echo ($current_page - 1); ?>"><?php esc_html_e('&laquo; Previous', 'mxchat'); ?></a>
1202 <?php endif; ?>
1203
1204 <?php
1205 $start_page = max(1, $current_page - 2);
1206 $end_page = min($total_pages, $current_page + 2);
1207
1208 if ($start_page > 1) {
1209 echo '<a href="#" class="mxchat-page-link" data-page="1">1</a> ';
1210 if ($start_page > 2) {
1211 echo '<span class="mxchat-page-dots">...</span> ';
1212 }
1213 }
1214
1215 for ($i = $start_page; $i <= $end_page; $i++) {
1216 if ($i == $current_page) {
1217 echo '<span class="mxchat-page-current">' . $i . '</span> ';
1218 } else {
1219 echo '<a href="#" class="mxchat-page-link" data-page="' . $i . '">' . $i . '</a> ';
1220 }
1221 }
1222
1223 if ($end_page < $total_pages) {
1224 if ($end_page < $total_pages - 1) {
1225 echo '<span class="mxchat-page-dots">...</span> ';
1226 }
1227 echo '<a href="#" class="mxchat-page-link" data-page="' . $total_pages . '">' . $total_pages . '</a> ';
1228 }
1229 ?>
1230
1231 <?php if ($current_page < $total_pages) : ?>
1232 <a href="#" class="mxchat-page-link" data-page="<?php echo ($current_page + 1); ?>"><?php esc_html_e('Next &raquo;', 'mxchat'); ?></a>
1233 <?php endif; ?>
1234 </div>
1235 <?php endif; ?>
1236 </div>
1237 </div>
1238 </div>
1239 </div>
1240 <?php
1241 }
1242
1243 /**
1244 * Render Auto-Sync Section
1245 */
1246 function mxchat_render_auto_sync_section($knowledge_manager) {
1247 ?>
1248 <div id="auto-sync" class="mxch-section">
1249 <div class="mxch-content-header">
1250 <h1 class="mxch-content-title"><?php esc_html_e('Auto-Sync Settings', 'mxchat'); ?></h1>
1251 <p class="mxch-content-subtitle"><?php esc_html_e('Automatically sync WordPress content to your knowledge base when published or updated.', 'mxchat'); ?></p>
1252 </div>
1253
1254 <div class="mxch-card">
1255 <div class="mxch-card-body mxchat-autosave-section">
1256 <div class="mxch-field">
1257 <div style="display: flex; align-items: center; gap: 12px; margin-bottom: 16px;">
1258 <label class="mxchat-toggle-switch">
1259 <input type="checkbox" name="mxchat_auto_sync_posts" class="mxchat-autosave-field" value="1" data-nonce="<?php echo wp_create_nonce('mxchat_prompts_setting_nonce'); ?>" <?php checked(get_option('mxchat_auto_sync_posts', '0'), '1'); ?>>
1260 <span class="mxchat-toggle-slider"></span>
1261 </label>
1262 <span style="font-weight: 500;"><?php esc_html_e('Auto-sync Posts', 'mxchat'); ?></span>
1263 </div>
1264 <div style="display: flex; align-items: center; gap: 12px;">
1265 <label class="mxchat-toggle-switch">
1266 <input type="checkbox" name="mxchat_auto_sync_pages" class="mxchat-autosave-field" value="1" data-nonce="<?php echo wp_create_nonce('mxchat_prompts_setting_nonce'); ?>" <?php checked(get_option('mxchat_auto_sync_pages', '0'), '1'); ?>>
1267 <span class="mxchat-toggle-slider"></span>
1268 </label>
1269 <span style="font-weight: 500;"><?php esc_html_e('Auto-sync Pages', 'mxchat'); ?></span>
1270 </div>
1271 </div>
1272
1273 <div class="mxch-field" style="margin-top: 24px; padding-top: 20px; border-top: 1px solid var(--mxch-card-border);">
1274 <div style="display: flex; align-items: center; gap: 12px;">
1275 <label class="mxchat-toggle-switch">
1276 <input type="checkbox" name="mxchat_auto_sync_acf_pdfs" class="mxchat-autosave-field" value="1" data-nonce="<?php echo wp_create_nonce('mxchat_prompts_setting_nonce'); ?>" <?php checked(get_option('mxchat_auto_sync_acf_pdfs', '0'), '1'); ?>>
1277 <span class="mxchat-toggle-slider"></span>
1278 </label>
1279 <span style="font-weight: 500;"><?php esc_html_e('Extract text from PDFs in ACF fields on save', 'mxchat'); ?></span>
1280 </div>
1281 <p class="mxch-field-description" style="margin-top: 8px;"><?php esc_html_e('When ON, editor saves re-extract every PDF referenced in ACF fields. Default OFF — saves stay fast and don\'t re-parse the same PDFs on every edit. The manual content selector has its own per-batch checkbox; this setting only controls the auto-sync path.', 'mxchat'); ?></p>
1282 </div>
1283
1284 <div style="margin-top: 24px;">
1285 <button id="mxchat-custom-post-types-toggle" class="mxch-btn mxch-btn-secondary">
1286 <?php esc_html_e('Advanced Custom Post Sync Settings', 'mxchat'); ?>
1287 <span style="margin-left: 5px;"></span>
1288 </button>
1289 <div id="mxchat-custom-post-types-container" style="display: none; margin-top: 16px; padding: 16px; background: #f8fafc; border-radius: var(--mxch-radius-md);">
1290 <h4 style="margin: 0 0 12px 0;"><?php esc_html_e('Sync Custom Post Types', 'mxchat'); ?></h4>
1291 <?php
1292 $post_types = $knowledge_manager->mxchat_get_public_post_types();
1293 unset($post_types['post'], $post_types['page']);
1294
1295 if (!empty($post_types)) {
1296 foreach ($post_types as $post_type => $label) {
1297 $option_name = 'mxchat_auto_sync_' . $post_type;
1298 $is_enabled = get_option($option_name, '0');
1299 ?>
1300 <div style="display: flex; align-items: center; gap: 12px; margin-bottom: 8px;">
1301 <label class="mxchat-toggle-switch">
1302 <input type="checkbox" name="<?php echo esc_attr($option_name); ?>" class="mxchat-autosave-field" value="1" data-nonce="<?php echo wp_create_nonce('mxchat_prompts_setting_nonce'); ?>" <?php checked($is_enabled, '1'); ?>>
1303 <span class="mxchat-toggle-slider"></span>
1304 </label>
1305 <span><?php echo esc_html($label); ?> (<?php echo esc_html($post_type); ?>)</span>
1306 </div>
1307 <?php
1308 }
1309 } else {
1310 echo '<p style="color: var(--mxch-text-muted);">' . esc_html__('No custom post types found.', 'mxchat') . '</p>';
1311 }
1312 ?>
1313 </div>
1314 </div>
1315 </div>
1316 </div>
1317 </div>
1318 <?php
1319 }
1320
1321 /**
1322 * Render Chunking Section
1323 */
1324 function mxchat_render_chunking_section() {
1325 $chunking_settings = MxChat_Chunker::get_settings();
1326 $chunking_enabled = $chunking_settings['chunking_enabled'];
1327 $chunk_size = $chunking_settings['chunk_size'];
1328 ?>
1329 <div id="chunking" class="mxch-section">
1330 <div class="mxch-content-header">
1331 <h1 class="mxch-content-title"><?php esc_html_e('Content Chunking', 'mxchat'); ?></h1>
1332 <p class="mxch-content-subtitle"><?php esc_html_e('Split large content into smaller segments for more accurate semantic search.', 'mxchat'); ?></p>
1333 </div>
1334
1335 <div class="mxch-card">
1336 <div class="mxch-card-body mxchat-autosave-section">
1337 <div class="mxch-notice mxch-notice-info" style="margin-bottom: 20px;">
1338 <svg class="mxch-notice-icon" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></svg>
1339 <span><?php esc_html_e('Chunking improves retrieval quality for long documents. All chunks are reassembled before sending to the AI. Only applies to new submissions.', 'mxchat'); ?></span>
1340 </div>
1341
1342 <div class="mxch-field">
1343 <div style="display: flex; align-items: center; gap: 12px; margin-bottom: 20px;">
1344 <label class="mxchat-toggle-switch">
1345 <input type="checkbox" name="mxchat_chunking_enabled" id="mxchat_chunking_enabled" class="mxchat-autosave-field" value="1" data-option-name="mxchat_chunking_enabled" data-nonce="<?php echo wp_create_nonce('mxchat_prompts_setting_nonce'); ?>" <?php checked($chunking_enabled, true); ?>>
1346 <span class="mxchat-toggle-slider"></span>
1347 </label>
1348 <span style="font-weight: 500;"><?php esc_html_e('Enable Content Chunking', 'mxchat'); ?></span>
1349 </div>
1350 </div>
1351
1352 <div class="mxch-field">
1353 <label class="mxch-field-label" for="mxchat_chunk_size"><?php esc_html_e('Chunk Size (characters)', 'mxchat'); ?></label>
1354 <input type="number" name="mxchat_chunk_size" id="mxchat_chunk_size" class="mxch-input mxchat-autosave-field" value="<?php echo esc_attr($chunk_size); ?>" min="1000" max="10000" step="500" data-option-name="mxchat_chunk_size" data-nonce="<?php echo wp_create_nonce('mxchat_prompts_setting_nonce'); ?>" style="max-width: 200px;">
1355 <p class="mxch-field-description"><?php esc_html_e('Recommended: 4000 characters (~1000 tokens). Range: 1000-10000. Larger chunks preserve more context.', 'mxchat'); ?></p>
1356 </div>
1357 </div>
1358 </div>
1359 </div>
1360 <?php
1361 }
1362
1363 /**
1364 * Render Role Restrictions Section
1365 */
1366 function mxchat_render_role_restrictions_section($knowledge_manager) {
1367 $role_options = array(
1368 'public' => __('Public (Everyone)', 'mxchat'),
1369 'logged_in' => __('Logged In Users', 'mxchat'),
1370 'subscriber' => __('Subscribers & Above', 'mxchat'),
1371 'contributor' => __('Contributors & Above', 'mxchat'),
1372 'author' => __('Authors & Above', 'mxchat'),
1373 'editor' => __('Editors & Above', 'mxchat'),
1374 'administrator' => __('Administrators Only', 'mxchat')
1375 );
1376 ?>
1377 <div id="role-restrictions" class="mxch-section">
1378 <div class="mxch-content-header">
1379 <h1 class="mxch-content-title"><?php esc_html_e('Role-Based Content Restrictions', 'mxchat'); ?></h1>
1380 <p class="mxch-content-subtitle"><?php esc_html_e('Automatically restrict content access based on WordPress tags.', 'mxchat'); ?></p>
1381 </div>
1382
1383 <div class="mxch-card">
1384 <div class="mxch-card-body">
1385 <div class="mxch-notice mxch-notice-info" style="margin-bottom: 20px;">
1386 <svg class="mxch-notice-icon" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></svg>
1387 <span><strong><?php esc_html_e('How it works:', 'mxchat'); ?></strong> <?php esc_html_e('Add a tag below and select which role should have access. Content with that tag will be restricted to that role level.', 'mxchat'); ?></span>
1388 </div>
1389
1390 <h4 style="margin: 0 0 16px 0;"><?php esc_html_e('Add Tag-Role Mapping', 'mxchat'); ?></h4>
1391 <div style="display: flex; gap: 15px; align-items: flex-end; flex-wrap: wrap; margin-bottom: 24px;">
1392 <div class="mxch-field" style="flex: 1; min-width: 200px; margin-bottom: 0;">
1393 <label class="mxch-field-label" for="mxchat-tag-input"><?php esc_html_e('Tag (name or slug)', 'mxchat'); ?></label>
1394 <input type="text" id="mxchat-tag-input" class="mxch-input" placeholder="<?php esc_attr_e('e.g., Premium Content or premium-content', 'mxchat'); ?>">
1395 <span class="mxch-field-hint"><?php esc_html_e('Enter an existing post tag by its display name or its slug.', 'mxchat'); ?></span>
1396 </div>
1397 <div class="mxch-field" style="flex: 1; min-width: 200px; margin-bottom: 0;">
1398 <label class="mxch-field-label" for="mxchat-role-select"><?php esc_html_e('Required Role', 'mxchat'); ?></label>
1399 <select id="mxchat-role-select" class="mxch-select">
1400 <?php foreach ($role_options as $role_key => $role_label) : ?>
1401 <option value="<?php echo esc_attr($role_key); ?>"><?php echo esc_html($role_label); ?></option>
1402 <?php endforeach; ?>
1403 </select>
1404 </div>
1405 <button type="button" id="mxchat-add-tag-role" class="mxch-btn mxch-btn-primary">
1406 <span class="dashicons dashicons-plus-alt" style="font-size: 16px;"></span>
1407 <?php esc_html_e('Add Mapping', 'mxchat'); ?>
1408 </button>
1409 </div>
1410
1411 <h4 style="margin: 24px 0 16px 0;"><?php esc_html_e('Current Tag-Role Mappings', 'mxchat'); ?></h4>
1412 <div id="mxchat-mappings-container">
1413 <div class="mxchat-loading-mappings" style="text-align: center; padding: 20px; color: var(--mxch-text-muted);">
1414 <?php esc_html_e('Loading mappings...', 'mxchat'); ?>
1415 </div>
1416 </div>
1417 <div id="mxchat-no-mappings" style="display: none; text-align: center; padding: 40px; color: var(--mxch-text-muted);">
1418 <span class="dashicons dashicons-tag" style="font-size: 48px; opacity: 0.3;"></span>
1419 <p><?php esc_html_e('No tag-role mappings yet. Add your first mapping above.', 'mxchat'); ?></p>
1420 </div>
1421
1422 <div style="margin-top: 30px; padding-top: 24px; border-top: 1px solid var(--mxch-card-border);">
1423 <h4 style="margin: 0 0 12px 0;"><?php esc_html_e('Bulk Update Existing Content', 'mxchat'); ?></h4>
1424 <p class="mxch-field-description" style="margin-bottom: 16px;"><?php esc_html_e('Apply role restrictions to all existing content that has the mapped tags.', 'mxchat'); ?></p>
1425 <button type="button" id="mxchat-bulk-update-roles" class="mxch-btn mxch-btn-secondary">
1426 <span class="dashicons dashicons-update" style="font-size: 16px;"></span>
1427 <?php esc_html_e('Update All Existing Content', 'mxchat'); ?>
1428 </button>
1429 <div id="mxchat-bulk-update-progress" style="display: none; margin-top: 15px;">
1430 <div style="height: 8px; background: #e2e8f0; border-radius: 4px; overflow: hidden;">
1431 <div class="mxchat-progress-fill" style="height: 100%; background: var(--mxch-primary); width: 0%; transition: width 0.3s;"></div>
1432 </div>
1433 <p class="mxchat-progress-text" style="margin-top: 10px; color: var(--mxch-text-secondary);"><?php esc_html_e('Processing...', 'mxchat'); ?></p>
1434 </div>
1435 <div id="mxchat-bulk-update-result" style="display: none; margin-top: 15px;"></div>
1436 </div>
1437 </div>
1438 </div>
1439 </div>
1440 <?php
1441 }
1442
1443 /**
1444 * Render ACF Fields Section
1445 */
1446 function mxchat_render_acf_fields_section($knowledge_manager) {
1447 $excluded_fields = get_option('mxchat_acf_excluded_fields', array());
1448 if (!is_array($excluded_fields)) {
1449 $excluded_fields = array();
1450 }
1451 ?>
1452 <div id="acf-fields" class="mxch-section">
1453 <div class="mxch-content-header">
1454 <h1 class="mxch-content-title"><?php esc_html_e('ACF Field Settings', 'mxchat'); ?></h1>
1455 <p class="mxch-content-subtitle"><?php esc_html_e('Control which Advanced Custom Fields are included in knowledge base embeddings.', 'mxchat'); ?></p>
1456 </div>
1457
1458 <div class="mxch-card">
1459 <div class="mxch-card-body mxchat-autosave-section">
1460 <?php if (!function_exists('acf_get_field_groups')): ?>
1461 <div class="mxch-notice mxch-notice-info">
1462 <svg class="mxch-notice-icon" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></svg>
1463 <span><?php esc_html_e('Advanced Custom Fields (ACF) plugin is not detected. Install and activate ACF to use this feature.', 'mxchat'); ?></span>
1464 </div>
1465 <?php else:
1466 $all_acf_fields = $knowledge_manager->mxchat_get_all_acf_fields();
1467
1468 if (empty($all_acf_fields)): ?>
1469 <div class="mxch-notice mxch-notice-info">
1470 <svg class="mxch-notice-icon" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></svg>
1471 <span><?php esc_html_e('No ACF field groups found. Create field groups in ACF to control which fields are included in embeddings.', 'mxchat'); ?></span>
1472 </div>
1473 <?php else: ?>
1474 <div class="mxch-notice mxch-notice-info" style="margin-bottom: 20px;">
1475 <svg class="mxch-notice-icon" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></svg>
1476 <span><?php esc_html_e('Toggle fields ON to include them in knowledge base embeddings. Toggle OFF to exclude sensitive or irrelevant fields.', 'mxchat'); ?></span>
1477 </div>
1478
1479 <?php foreach ($all_acf_fields as $group_title => $fields): ?>
1480 <div class="mxchat-acf-field-group" style="margin-bottom: 24px;">
1481 <h4 style="margin: 0 0 12px 0; color: var(--mxch-text-primary); font-weight: 600;">
1482 <?php echo esc_html($group_title); ?>
1483 </h4>
1484 <div style="background: #f8fafc; border-radius: var(--mxch-radius-md); padding: 16px;">
1485 <?php foreach ($fields as $field):
1486 $field_name = $field['name'];
1487 $is_enabled = !in_array($field_name, $excluded_fields);
1488 ?>
1489 <div style="display: flex; align-items: center; justify-content: space-between; padding: 8px 0; border-bottom: 1px solid #e2e8f0;">
1490 <div>
1491 <span style="font-weight: 500;"><?php echo esc_html($field['label']); ?></span>
1492 <span style="color: var(--mxch-text-muted); font-size: 12px; margin-left: 8px;">(<?php echo esc_html($field_name); ?>)</span>
1493 <span style="color: var(--mxch-text-muted); font-size: 11px; margin-left: 8px; background: #e2e8f0; padding: 2px 6px; border-radius: 4px;"><?php echo esc_html($field['type']); ?></span>
1494 </div>
1495 <label class="mxchat-toggle-switch">
1496 <input type="checkbox"
1497 name="mxchat_acf_field_<?php echo esc_attr($field_name); ?>"
1498 class="mxchat-autosave-field"
1499 value="on"
1500 data-nonce="<?php echo wp_create_nonce('mxchat_prompts_setting_nonce'); ?>"
1501 <?php checked($is_enabled, true); ?>>
1502 <span class="mxchat-toggle-slider"></span>
1503 </label>
1504 </div>
1505 <?php endforeach; ?>
1506 </div>
1507 </div>
1508 <?php endforeach; ?>
1509 <?php endif; ?>
1510 <?php endif; ?>
1511 </div>
1512 </div>
1513 </div>
1514 <?php
1515 }
1516
1517 /**
1518 * Render Custom Meta Section
1519 */
1520 function mxchat_render_custom_meta_section() {
1521 $whitelist = get_option('mxchat_custom_meta_whitelist', '');
1522 ?>
1523 <div id="custom-meta" class="mxch-section">
1524 <div class="mxch-content-header">
1525 <h1 class="mxch-content-title"><?php esc_html_e('Custom Post Meta', 'mxchat'); ?></h1>
1526 <p class="mxch-content-subtitle"><?php esc_html_e('Include custom post meta fields (non-ACF) in knowledge base embeddings.', 'mxchat'); ?></p>
1527 </div>
1528
1529 <div class="mxch-card">
1530 <div class="mxch-card-body mxchat-autosave-section">
1531 <div class="mxch-notice mxch-notice-info" style="margin-bottom: 20px;">
1532 <svg class="mxch-notice-icon" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></svg>
1533 <span><?php esc_html_e('Enter post meta keys to include in embeddings. This is useful for OptionTree fields, theme meta boxes, or any custom post meta that is not managed by ACF.', 'mxchat'); ?></span>
1534 </div>
1535
1536 <!-- Meta-key discovery scanner (plan-mxchat-20260709-fe8e4e): mirror the ACF picker's
1537 discover-for-you UX for non-ACF meta, so owners click keys instead of typing them blind. -->
1538 <div class="mxch-field mxchat-meta-scan">
1539 <label class="mxch-field-label"><?php esc_html_e('Discover meta keys', 'mxchat'); ?></label>
1540 <p class="mxch-field-description" style="margin-top: 0;">
1541 <?php esc_html_e('Scan your published content for custom meta keys, then click any you want to include. This gives non-ACF meta the same discover-and-click experience as the ACF picker.', 'mxchat'); ?>
1542 </p>
1543 <div class="mxchat-meta-scan-controls">
1544 <button type="button" id="mxchat-scan-meta-btn" class="mxch-btn mxch-btn-secondary" data-nonce="<?php echo esc_attr(wp_create_nonce('mxchat_prompts_setting_nonce')); ?>">
1545 <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
1546 <span class="mxchat-scan-label"><?php esc_html_e('Scan my content for meta keys', 'mxchat'); ?></span>
1547 </button>
1548 <label class="mxchat-meta-scan-internal">
1549 <input type="checkbox" id="mxchat-scan-internal">
1550 <?php esc_html_e('Include internal keys (underscore-prefixed)', 'mxchat'); ?>
1551 </label>
1552 </div>
1553 <div id="mxchat-meta-scan-results" class="mxchat-meta-scan-results" aria-live="polite"></div>
1554 </div>
1555
1556 <div class="mxch-field">
1557 <label class="mxch-field-label" for="mxchat_custom_meta_whitelist">
1558 <?php esc_html_e('Meta Key Whitelist', 'mxchat'); ?>
1559 </label>
1560 <textarea
1561 name="mxchat_custom_meta_whitelist"
1562 id="mxchat_custom_meta_whitelist"
1563 class="mxch-textarea mxchat-autosave-field"
1564 rows="6"
1565 placeholder="speaker_profession&#10;speaker_company&#10;event_location&#10;_custom_field_key"
1566 data-nonce="<?php echo wp_create_nonce('mxchat_prompts_setting_nonce'); ?>"
1567 style="font-family: monospace;"
1568 ><?php echo esc_textarea($whitelist); ?></textarea>
1569 <p class="mxch-field-description">
1570 <?php esc_html_e('Enter one meta key per line. These fields will be appended to the content during embedding. Text values are added as-is; array or repeater values are flattened into a comma-separated list. Empty values are skipped.', 'mxchat'); ?>
1571 </p>
1572 </div>
1573
1574 <div style="margin-top: 20px; padding: 16px; background: #f8fafc; border-radius: var(--mxch-radius-md);">
1575 <h4 style="margin: 0 0 12px 0; font-size: 14px;"><?php esc_html_e('How to find meta keys:', 'mxchat'); ?></h4>
1576 <ul style="margin: 0; padding-left: 20px; color: var(--mxch-text-secondary); font-size: 13px;">
1577 <li><?php esc_html_e('Check your theme documentation for meta key names', 'mxchat'); ?></li>
1578 <li><?php esc_html_e('Look in the wp_postmeta database table', 'mxchat'); ?></li>
1579 <li><?php esc_html_e('Use the Scan my content for meta keys button above to discover the keys on your published content automatically', 'mxchat'); ?></li>
1580 <li><?php esc_html_e('Meta keys starting with underscore (_) are usually hidden/internal', 'mxchat'); ?></li>
1581 </ul>
1582 </div>
1583 </div>
1584 </div>
1585 </div>
1586 <?php
1587 }
1588
1589 /**
1590 * Render Pinecone Section
1591 */
1592 function mxchat_render_pinecone_section() {
1593 $pinecone_options = get_option('mxchat_pinecone_addon_options', array());
1594 $use_pinecone = $pinecone_options['mxchat_use_pinecone'] ?? '0';
1595 ?>
1596 <div id="pinecone" class="mxch-section">
1597 <div class="mxch-content-header">
1598 <h1 class="mxch-content-title"><?php esc_html_e('Pinecone Vector Database', 'mxchat'); ?></h1>
1599 <p class="mxch-content-subtitle"><?php esc_html_e('Configure Pinecone for enhanced search performance with larger knowledge bases.', 'mxchat'); ?></p>
1600 </div>
1601
1602 <div class="mxch-card">
1603 <div class="mxch-card-body">
1604 <div class="mxch-notice mxch-notice-info" style="margin-bottom: 20px;">
1605 <svg class="mxch-notice-icon" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></svg>
1606 <span><strong><?php esc_html_e('Pinecone is optional.', 'mxchat'); ?></strong> <?php esc_html_e('MxChat works without it. When enabled, content is stored in Pinecone for faster similarity searches.', 'mxchat'); ?></span>
1607 </div>
1608
1609 <form method="post" action="options.php">
1610 <?php settings_fields('mxchat_pinecone_addon_options'); ?>
1611
1612 <div class="mxch-field">
1613 <div style="display: flex; align-items: center; gap: 12px; margin-bottom: 20px;">
1614 <label class="mxchat-toggle-switch">
1615 <input type="checkbox" name="mxchat_pinecone_addon_options[mxchat_use_pinecone]" value="1" <?php checked($use_pinecone, '1'); ?>>
1616 <span class="mxchat-toggle-slider"></span>
1617 </label>
1618 <span style="font-weight: 500;"><?php esc_html_e('Enable Pinecone Database', 'mxchat'); ?></span>
1619 </div>
1620 </div>
1621
1622 <div class="mxchat-pinecone-settings" <?php echo $use_pinecone ? '' : 'style="display: none;"'; ?>>
1623 <?php if ($use_pinecone) : ?>
1624 <div class="mxch-notice mxch-notice-success" style="margin-bottom: 20px;">
1625 <svg class="mxch-notice-icon" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>
1626 <span><?php esc_html_e('Pinecone is enabled. All new knowledge base content will be stored in Pinecone.', 'mxchat'); ?></span>
1627 </div>
1628 <?php endif; ?>
1629
1630 <div class="mxch-field">
1631 <label class="mxch-field-label" for="mxchat_pinecone_api_key">
1632 <?php esc_html_e('Pinecone API Key', 'mxchat'); ?> <span class="mxch-field-label-required">*</span>
1633 </label>
1634 <input type="password" id="mxchat_pinecone_api_key" name="mxchat_pinecone_addon_options[mxchat_pinecone_api_key]" value="<?php echo esc_attr($pinecone_options['mxchat_pinecone_api_key'] ?? ''); ?>" class="mxch-input" placeholder="pcsk_...">
1635 <p class="mxch-field-description">
1636 <?php esc_html_e('Found in your Pinecone dashboard under API Keys.', 'mxchat'); ?>
1637 <a href="https://app.pinecone.io/" target="_blank"><?php esc_html_e('Open Pinecone Dashboard', 'mxchat'); ?></a>
1638 </p>
1639 </div>
1640
1641 <div class="mxch-field">
1642 <label class="mxch-field-label" for="mxchat_pinecone_environment"><?php esc_html_e('Region', 'mxchat'); ?></label>
1643 <input type="text" id="mxchat_pinecone_environment" name="mxchat_pinecone_addon_options[mxchat_pinecone_environment]" value="<?php echo esc_attr($pinecone_options['mxchat_pinecone_environment'] ?? ''); ?>" class="mxch-input" placeholder="e.g., gcp-starter">
1644 <p class="mxch-field-description"><?php esc_html_e('Your Pinecone environment/region (e.g., gcp-starter, us-west1-gcp)', 'mxchat'); ?></p>
1645 </div>
1646
1647 <div class="mxch-field">
1648 <label class="mxch-field-label" for="mxchat_pinecone_index">
1649 <?php esc_html_e('Index Name', 'mxchat'); ?> <span class="mxch-field-label-required">*</span>
1650 </label>
1651 <input type="text" id="mxchat_pinecone_index" name="mxchat_pinecone_addon_options[mxchat_pinecone_index]" value="<?php echo esc_attr($pinecone_options['mxchat_pinecone_index'] ?? ''); ?>" class="mxch-input" placeholder="e.g., my-wordpress-vectors">
1652 <p class="mxch-field-description"><?php esc_html_e('The name of your Pinecone index. Must be created in your Pinecone dashboard first.', 'mxchat'); ?></p>
1653 </div>
1654
1655 <div class="mxch-field">
1656 <label class="mxch-field-label" for="mxchat_pinecone_host">
1657 <?php esc_html_e('Pinecone Host', 'mxchat'); ?> <span class="mxch-field-label-required">*</span>
1658 </label>
1659 <input type="text" id="mxchat_pinecone_host" name="mxchat_pinecone_addon_options[mxchat_pinecone_host]" value="<?php echo esc_attr($pinecone_options['mxchat_pinecone_host'] ?? ''); ?>" class="mxch-input" placeholder="e.g., my-index-xyz123.svc.pinecone.io">
1660 <p class="mxch-field-description"><?php esc_html_e('The hostname from your Pinecone index URL (exclude https://). Found in index details.', 'mxchat'); ?></p>
1661 </div>
1662 </div>
1663
1664 <?php submit_button(__('Save Pinecone Settings', 'mxchat'), 'primary', 'submit', true, array('class' => 'mxch-btn mxch-btn-primary')); ?>
1665 </form>
1666 </div>
1667 </div>
1668 </div>
1669 <?php
1670 }
1671
1672 /**
1673 * Render OpenAI Vector Store Section
1674 */
1675 function mxchat_render_openai_vectorstore_section() {
1676 $vectorstore_options = get_option('mxchat_openai_vectorstore_options', array());
1677 $use_vectorstore = $vectorstore_options['mxchat_use_openai_vectorstore'] ?? '0';
1678 $vectorstore_ids = $vectorstore_options['mxchat_vectorstore_ids'] ?? '';
1679 $max_results = $vectorstore_options['mxchat_vectorstore_max_results'] ?? 5;
1680
1681 // Get current chat model to check compatibility
1682 $mxchat_options = get_option('mxchat_options', array());
1683 $current_model = $mxchat_options['model'] ?? 'gpt-5.1-chat-latest';
1684 $is_openai_model = preg_match('/^(gpt-|o1-|o3-)/', $current_model);
1685 ?>
1686 <div id="openai-vectorstore" class="mxch-section">
1687 <div class="mxch-content-header">
1688 <h1 class="mxch-content-title"><?php esc_html_e('OpenAI Vector Store', 'mxchat'); ?></h1>
1689 <p class="mxch-content-subtitle"><?php esc_html_e('Use OpenAI\'s hosted vector database for knowledge retrieval. Your documents are stored and searched directly on OpenAI\'s platform.', 'mxchat'); ?></p>
1690 </div>
1691
1692 <div class="mxch-card">
1693 <div class="mxch-card-body">
1694 <div class="mxch-notice mxch-notice-info" style="margin-bottom: 20px;">
1695 <svg class="mxch-notice-icon" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></svg>
1696 <span><strong><?php esc_html_e('Requires OpenAI Chat Model.', 'mxchat'); ?></strong> <?php esc_html_e('Vector Store search only works with OpenAI models (gpt-5.1-chat-latest, gpt-5-mini, etc.). Create Vector Stores in your OpenAI Dashboard.', 'mxchat'); ?></span>
1697 </div>
1698
1699 <?php if (!$is_openai_model && $use_vectorstore === '1'): ?>
1700 <div class="mxch-notice mxch-notice-warning" style="margin-bottom: 20px;">
1701 <svg class="mxch-notice-icon" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>
1702 <span><strong><?php esc_html_e('Model Incompatible:', 'mxchat'); ?></strong> <?php printf(esc_html__('Your current chat model (%s) is not an OpenAI model. Vector Store search will not work until you switch to an OpenAI model.', 'mxchat'), esc_html($current_model)); ?></span>
1703 </div>
1704 <?php endif; ?>
1705
1706 <form method="post" action="options.php" id="mxchat-vectorstore-form">
1707 <?php settings_fields('mxchat_openai_vectorstore_options'); ?>
1708
1709 <div class="mxch-field">
1710 <div style="display: flex; align-items: center; gap: 12px; margin-bottom: 20px;">
1711 <label class="mxchat-toggle-switch">
1712 <input type="checkbox" name="mxchat_openai_vectorstore_options[mxchat_use_openai_vectorstore]" value="1" <?php checked($use_vectorstore, '1'); ?> id="mxchat_use_openai_vectorstore">
1713 <span class="mxchat-toggle-slider"></span>
1714 </label>
1715 <span style="font-weight: 500;"><?php esc_html_e('Enable OpenAI Vector Store', 'mxchat'); ?></span>
1716 </div>
1717 </div>
1718
1719 <div class="mxchat-vectorstore-settings" <?php echo $use_vectorstore === '1' ? '' : 'style="display: none;"'; ?>>
1720 <?php if ($use_vectorstore === '1'): ?>
1721 <div class="mxch-notice mxch-notice-success" style="margin-bottom: 20px;">
1722 <svg class="mxch-notice-icon" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>
1723 <span><?php esc_html_e('OpenAI Vector Store is enabled. Queries will search your Vector Store for relevant content.', 'mxchat'); ?></span>
1724 </div>
1725 <?php endif; ?>
1726
1727 <div class="mxch-field">
1728 <label class="mxch-field-label" for="mxchat_vectorstore_ids">
1729 <?php esc_html_e('Vector Store ID(s)', 'mxchat'); ?> <span class="mxch-field-label-required">*</span>
1730 </label>
1731 <input type="text" id="mxchat_vectorstore_ids" name="mxchat_openai_vectorstore_options[mxchat_vectorstore_ids]" value="<?php echo esc_attr($vectorstore_ids); ?>" class="mxch-input" placeholder="vs_abc123xyz">
1732 <p class="mxch-field-description">
1733 <?php esc_html_e('Enter your Vector Store ID from the OpenAI Dashboard. For multiple stores, separate with commas.', 'mxchat'); ?>
1734 <a href="https://platform.openai.com/storage/vector_stores" target="_blank"><?php esc_html_e('Open OpenAI Vector Store Dashboard', 'mxchat'); ?></a>
1735 </p>
1736 </div>
1737
1738 <div class="mxch-field">
1739 <label class="mxch-field-label" for="mxchat_vectorstore_max_results">
1740 <?php esc_html_e('Max Results', 'mxchat'); ?>
1741 </label>
1742 <select id="mxchat_vectorstore_max_results" name="mxchat_openai_vectorstore_options[mxchat_vectorstore_max_results]" class="mxch-input" style="width: auto;">
1743 <?php for ($i = 1; $i <= 10; $i++): ?>
1744 <option value="<?php echo $i; ?>" <?php selected($max_results, $i); ?>><?php echo $i; ?></option>
1745 <?php endfor; ?>
1746 </select>
1747 <p class="mxch-field-description"><?php esc_html_e('Maximum number of content chunks to retrieve from the Vector Store.', 'mxchat'); ?></p>
1748 </div>
1749 </div>
1750
1751 <?php submit_button(__('Save Vector Store Settings', 'mxchat'), 'primary', 'submit', true, array('class' => 'mxch-btn mxch-btn-primary')); ?>
1752 </form>
1753 </div>
1754 </div>
1755 </div>
1756 <?php
1757 }
1758
1759 /**
1760 * Render Content Selector Modal
1761 */
1762 function mxchat_render_content_selector_modal() {
1763 ?>
1764 <div id="mxchat-kb-content-selector-modal" class="mxchat-kb-modal">
1765 <div class="mxchat-kb-modal-content">
1766 <div class="mxchat-kb-modal-header">
1767 <h3>
1768 <?php esc_html_e('Select WordPress Content', 'mxchat'); ?><br>
1769 <span class="mxchat-kb-header-note"><?php esc_html_e('(Content imported here will be tagged "In Knowledge Base")', 'mxchat'); ?></span>
1770 </h3>
1771 <span class="mxchat-kb-modal-close">&times;</span>
1772 </div>
1773 <div class="mxchat-kb-modal-filters">
1774 <div class="mxchat-kb-search-group">
1775 <input type="text" id="mxchat-kb-content-search" placeholder="<?php esc_attr_e('Search...', 'mxchat'); ?>">
1776 </div>
1777 <div class="mxchat-kb-filter-group">
1778 <select id="mxchat-kb-content-type-filter">
1779 <option value="all"><?php esc_html_e('All Content Types', 'mxchat'); ?></option>
1780 <option value="post"><?php esc_html_e('Posts', 'mxchat'); ?></option>
1781 <option value="page"><?php esc_html_e('Pages', 'mxchat'); ?></option>
1782 <?php
1783 $post_types = get_post_types(array('public' => true), 'objects');
1784 foreach ($post_types as $post_type) {
1785 if (!in_array($post_type->name, array('post', 'page'))) {
1786 echo '<option value="' . esc_attr($post_type->name) . '">' . esc_html($post_type->label) . '</option>';
1787 }
1788 }
1789 ?>
1790 </select>
1791 <select id="mxchat-kb-content-status-filter">
1792 <option value="publish"><?php esc_html_e('Published', 'mxchat'); ?></option>
1793 <option value="draft"><?php esc_html_e('Drafts', 'mxchat'); ?></option>
1794 <option value="all"><?php esc_html_e('All Statuses', 'mxchat'); ?></option>
1795 </select>
1796 <select id="mxchat-kb-processed-filter">
1797 <option value="all"><?php esc_html_e('All Content', 'mxchat'); ?></option>
1798 <option value="processed"><?php esc_html_e('In Knowledge Base', 'mxchat'); ?></option>
1799 <option value="unprocessed"><?php esc_html_e('Not In Knowledge Base', 'mxchat'); ?></option>
1800 </select>
1801 </div>
1802 </div>
1803 <div class="mxchat-kb-content-selection">
1804 <div class="mxchat-kb-selection-header">
1805 <label>
1806 <input type="checkbox" id="mxchat-kb-select-all">
1807 <?php esc_html_e('Select All on Page', 'mxchat'); ?>
1808 </label>
1809 <span class="mxchat-kb-selection-count">0 <?php esc_html_e('selected', 'mxchat'); ?></span>
1810 <span class="mxchat-kb-selection-hint" style="margin-left: auto; font-size: 12px; color: var(--mxch-text-muted);">
1811 <span class="dashicons dashicons-info-outline" style="font-size: 14px; vertical-align: middle;"></span>
1812 <?php esc_html_e('Selections persist across pages', 'mxchat'); ?>
1813 </span>
1814 </div>
1815 <div class="mxchat-kb-content-list">
1816 <div class="mxchat-kb-loading">
1817 <span class="mxchat-kb-spinner is-active"></span>
1818 <?php esc_html_e('Loading content...', 'mxchat'); ?>
1819 </div>
1820 </div>
1821 </div>
1822 <div class="mxchat-kb-modal-footer">
1823 <div class="mxchat-kb-pagination"></div>
1824 <div class="mxchat-kb-acf-pdf-option" style="display:flex; align-items:flex-start; gap:8px; padding:8px 0; font-size:12px; color: var(--mxch-text-secondary, #64748b);">
1825 <input type="checkbox" id="mxchat-kb-acf-pdf-extract" style="margin-top:2px;">
1826 <label for="mxchat-kb-acf-pdf-extract" style="cursor:pointer; line-height:1.4;">
1827 <strong style="color: var(--mxch-text-primary, #1a1a2e);"><?php esc_html_e('Also extract text from PDFs linked in ACF File/Image fields', 'mxchat'); ?></strong><br>
1828 <span><?php esc_html_e('Adds 200-500ms per post and ~30 KB to each KB entry. Recommended only if your ACF setup uses PDF attachments for primary content.', 'mxchat'); ?></span>
1829 </label>
1830 </div>
1831 <div class="mxchat-kb-footer-actions">
1832 <button type="button" id="mxchat-kb-process-selected" class="mxchat-kb-button-primary" disabled>
1833 <?php esc_html_e('Process Selected Content', 'mxchat'); ?>
1834 <span class="mxchat-kb-selected-count">(0)</span>
1835 </button>
1836 <button type="button" class="mxchat-kb-button-secondary mxchat-kb-modal-close">
1837 <?php esc_html_e('Cancel', 'mxchat'); ?>
1838 </button>
1839 </div>
1840 </div>
1841 </div>
1842 </div>
1843 <?php
1844 }
1845
1846 /**
1847 * Render Edit Entry Modal
1848 */
1849 function mxchat_render_edit_entry_modal() {
1850 ?>
1851 <div id="mxchat-kb-edit-modal" class="mxchat-kb-modal">
1852 <div class="mxchat-kb-modal-content mxchat-kb-edit-modal-content">
1853 <div class="mxchat-kb-modal-header">
1854 <h3 id="mxchat-kb-edit-title"><?php esc_html_e('Edit Knowledge Entry', 'mxchat'); ?></h3>
1855 <span class="mxchat-kb-modal-close" id="mxchat-kb-edit-close">&times;</span>
1856 </div>
1857 <div class="mxchat-kb-edit-body">
1858 <div class="mxchat-kb-edit-source" id="mxchat-kb-edit-source"></div>
1859 <textarea id="mxchat-kb-edit-textarea" class="mxchat-kb-edit-textarea" placeholder="<?php esc_attr_e('Loading content...', 'mxchat'); ?>"></textarea>
1860 <div class="mxchat-kb-edit-meta">
1861 <span id="mxchat-kb-edit-charcount"></span>
1862 <span id="mxchat-kb-edit-chunkinfo"></span>
1863 </div>
1864 </div>
1865 <div class="mxchat-kb-edit-footer">
1866 <div class="mxchat-kb-edit-notice" id="mxchat-kb-edit-notice"></div>
1867 <div class="mxchat-kb-edit-actions">
1868 <button type="button" id="mxchat-kb-edit-cancel" class="mxch-btn mxch-btn-ghost">
1869 <?php esc_html_e('Cancel', 'mxchat'); ?>
1870 </button>
1871 <button type="button" id="mxchat-kb-edit-save" class="mxch-btn mxch-btn-primary">
1872 <span class="mxchat-kb-edit-save-text"><?php esc_html_e('Save & Re-embed', 'mxchat'); ?></span>
1873 <span class="mxchat-kb-edit-save-spinner" style="display:none;">
1874 <span class="mxchat-kb-spinner is-active" style="margin: 0;"></span>
1875 <?php esc_html_e('Saving...', 'mxchat'); ?>
1876 </span>
1877 </button>
1878 </div>
1879 </div>
1880 </div>
1881 </div>
1882 <?php
1883 }
1884
1885 /**
1886 * Render the read-only Knowledge Inspector modal (plan-mxchat-20260628-d8cb4b).
1887 * Shows exactly what was indexed for an entry: the per-chunk stored text + lengths
1888 * and (for Pinecone) the stored metadata fields, rendered with the Testing tab's
1889 * own card components so the two surfaces feel like one system. Self-contained
1890 * markup + JS so it doesn't entangle the edit-modal init.
1891 */
1892 function mxchat_render_inspect_entry_modal() {
1893 ?>
1894 <div id="mxchat-kb-inspect-modal" class="mxchat-kb-modal">
1895 <div class="mxchat-kb-modal-content mxchat-kb-inspect-modal-content">
1896 <div class="mxchat-kb-modal-header">
1897 <h3 id="mxchat-kb-inspect-title"><?php esc_html_e('Indexed Content', 'mxchat'); ?></h3>
1898 <span class="mxchat-kb-modal-close" id="mxchat-kb-inspect-close">&times;</span>
1899 </div>
1900 <div class="mxchat-kb-inspect-body">
1901 <div class="mxchat-kb-edit-source" id="mxchat-kb-inspect-source"></div>
1902 <div class="mxch-testing-results" id="mxchat-kb-inspect-results">
1903 <div class="mxch-testing-no-data"><?php esc_html_e('Loading indexed content…', 'mxchat'); ?></div>
1904 </div>
1905 </div>
1906 <div class="mxchat-kb-edit-footer">
1907 <div class="mxchat-kb-edit-notice" id="mxchat-kb-inspect-notice"></div>
1908 <div class="mxchat-kb-edit-actions">
1909 <button type="button" id="mxchat-kb-inspect-close-btn" class="mxch-btn mxch-btn-primary">
1910 <?php esc_html_e('Close', 'mxchat'); ?>
1911 </button>
1912 </div>
1913 </div>
1914 </div>
1915 </div>
1916 <script>
1917 (function() {
1918 'use strict';
1919 document.addEventListener('DOMContentLoaded', function() {
1920 var modal = document.getElementById('mxchat-kb-inspect-modal');
1921 if (!modal) return;
1922 var closeX = document.getElementById('mxchat-kb-inspect-close');
1923 var closeBtn = document.getElementById('mxchat-kb-inspect-close-btn');
1924 var sourceEl = document.getElementById('mxchat-kb-inspect-source');
1925 var resultsEl= document.getElementById('mxchat-kb-inspect-results');
1926 var titleEl = document.getElementById('mxchat-kb-inspect-title');
1927 var noticeEl = document.getElementById('mxchat-kb-inspect-notice');
1928
1929 var I18N = {
1930 title: <?php echo wp_json_encode( esc_html__('Indexed Content', 'mxchat') ); ?>,
1931 loading: <?php echo wp_json_encode( esc_html__('Loading indexed content…', 'mxchat') ); ?>,
1932 manual: <?php echo wp_json_encode( esc_html__('Manual Content', 'mxchat') ); ?>,
1933 source: <?php echo wp_json_encode( esc_html__('Source', 'mxchat') ); ?>,
1934 store: <?php echo wp_json_encode( esc_html__('Storage', 'mxchat') ); ?>,
1935 type: <?php echo wp_json_encode( esc_html__('Content type', 'mxchat') ); ?>,
1936 chunksLbl:<?php echo wp_json_encode( esc_html__('Chunks', 'mxchat') ); ?>,
1937 totalLen: <?php echo wp_json_encode( esc_html__('Total indexed length', 'mxchat') ); ?>,
1938 chunk: <?php echo wp_json_encode( esc_html__('Chunk', 'mxchat') ); ?>,
1939 indexed: <?php echo wp_json_encode( esc_html__('Indexed', 'mxchat') ); ?>,
1940 chars: <?php echo wp_json_encode( esc_html__('chars', 'mxchat') ); ?>,
1941 showText: <?php echo wp_json_encode( esc_html__('Show indexed text', 'mxchat') ); ?>,
1942 hideText: <?php echo wp_json_encode( esc_html__('Hide indexed text', 'mxchat') ); ?>,
1943 wpStore: <?php echo wp_json_encode( esc_html__('Local WordPress database', 'mxchat') ); ?>,
1944 pcStore: <?php echo wp_json_encode( esc_html__('Pinecone', 'mxchat') ); ?>,
1945 meta: <?php echo wp_json_encode( esc_html__('Vector metadata', 'mxchat') ); ?>,
1946 loadFail: <?php echo wp_json_encode( esc_html__('Failed to load indexed content.', 'mxchat') ); ?>,
1947 netErr: <?php echo wp_json_encode( esc_html__('Network error: ', 'mxchat') ); ?>
1948 };
1949
1950 function esc(str) {
1951 var d = document.createElement('div');
1952 d.textContent = (str === null || str === undefined) ? '' : String(str);
1953 return d.innerHTML;
1954 }
1955 function fmt(n) { try { return Number(n).toLocaleString(); } catch (e) { return n; } }
1956
1957 document.addEventListener('click', function(e) {
1958 var btn = e.target.closest('.mxchat-inspect-entry-btn');
1959 if (btn) { e.preventDefault(); open(btn); }
1960 });
1961 if (closeX) closeX.addEventListener('click', close);
1962 if (closeBtn) closeBtn.addEventListener('click', close);
1963 modal.addEventListener('click', function(e) { if (e.target === modal) close(); });
1964 document.addEventListener('keydown', function(e) {
1965 if (e.key === 'Escape' && modal.classList.contains('active')) close();
1966 });
1967
1968 function close() { modal.classList.remove('active'); }
1969
1970 function open(btn) {
1971 var entry = {
1972 sourceUrl: btn.getAttribute('data-source-url') || '',
1973 entryId: btn.getAttribute('data-entry-id') || '',
1974 dataSource: btn.getAttribute('data-data-source') || 'wordpress',
1975 botId: btn.getAttribute('data-bot-id') || 'default',
1976 nonce: btn.getAttribute('data-nonce') || ''
1977 };
1978
1979 titleEl.textContent = I18N.title;
1980 noticeEl.textContent = '';
1981 noticeEl.className = 'mxchat-kb-edit-notice';
1982 resultsEl.innerHTML = '<div class="mxch-testing-no-data">' + esc(I18N.loading) + '</div>';
1983
1984 if (entry.sourceUrl && entry.sourceUrl.indexOf('mxchat://') !== 0 && entry.sourceUrl.indexOf('_ungrouped_') !== 0) {
1985 sourceEl.innerHTML = esc(I18N.source) + ': <a href="' + esc(entry.sourceUrl) + '" target="_blank" rel="noopener">' + esc(entry.sourceUrl) + '</a>';
1986 } else {
1987 sourceEl.textContent = I18N.manual;
1988 }
1989
1990 modal.classList.add('active');
1991
1992 var fd = new FormData();
1993 fd.append('action', 'mxchat_inspect_entry');
1994 fd.append('nonce', entry.nonce);
1995 fd.append('source_url', entry.sourceUrl);
1996 fd.append('entry_id', entry.entryId);
1997 fd.append('data_source', entry.dataSource);
1998 fd.append('bot_id', entry.botId);
1999
2000 fetch(ajaxurl, { method: 'POST', body: fd })
2001 .then(function(r) { return r.json(); })
2002 .then(function(resp) {
2003 if (resp && resp.success) {
2004 render(resp.data);
2005 } else {
2006 var msg = (resp && resp.data && resp.data.message) || I18N.loadFail;
2007 resultsEl.innerHTML = '';
2008 showNotice(msg, 'error');
2009 }
2010 })
2011 .catch(function(err) {
2012 resultsEl.innerHTML = '';
2013 showNotice(I18N.netErr + err.message, 'error');
2014 });
2015 }
2016
2017 function render(data) {
2018 var storeLabel = data.store === 'pinecone' ? I18N.pcStore : I18N.wpStore;
2019 var html = '';
2020
2021 // Summary card — what this entry is + how much was indexed.
2022 html += '<div class="match-card above-threshold">' +
2023 '<div class="match-header">' +
2024 '<div class="match-title"><span class="status-icon">&#128230;</span> ' + esc(data.chunk_count) + ' ' + esc(I18N.chunksLbl.toLowerCase()) + '</div>' +
2025 '<span class="context-label">' + esc(storeLabel) + '</span>' +
2026 '</div>' +
2027 '<div class="mxch-testing-field"><label>' + esc(I18N.type) + '</label><code>' + esc(data.content_type || '') + '</code> ' +
2028 '&nbsp; <label style="display:inline; text-transform:none; letter-spacing:0; font-weight:600;">' + esc(I18N.totalLen) + ':</label> ' + esc(fmt(data.assembled_length)) + ' ' + esc(I18N.chars) + '</div>';
2029 if (data.metadata_note) {
2030 html += '<div class="match-source"><span class="source-icon">&#8505;&#65039;</span> ' + esc(data.metadata_note) + '</div>';
2031 }
2032 html += '</div>';
2033
2034 // One card per stored chunk — the actual embedded text.
2035 var chunks = data.chunks || [];
2036 chunks.forEach(function(chunk, i) {
2037 var num = (chunk.index !== null && chunk.index !== undefined) ? (chunk.index + 1) : (i + 1);
2038 var metaRows = '';
2039 if (chunk.metadata && typeof chunk.metadata === 'object') {
2040 var keys = Object.keys(chunk.metadata);
2041 if (keys.length) {
2042 metaRows += '<div class="chunk-detail-row chunk-used" style="display:block;"><span class="chunk-detail-num">' + esc(I18N.meta) + '</span></div>';
2043 keys.forEach(function(k) {
2044 metaRows += '<div class="chunk-detail-row"><span class="chunk-detail-num">' + esc(k) + '</span><span class="chunk-detail-score">' + esc(chunk.metadata[k]) + '</span></div>';
2045 });
2046 }
2047 }
2048 html += '<div class="match-card above-threshold">' +
2049 '<div class="match-header">' +
2050 '<div class="match-title"><span class="status-icon">&#10003;</span> ' + esc(I18N.chunk) + ' ' + esc(num) +
2051 '<span class="chunk-summary">' + esc(fmt(chunk.length)) + ' ' + esc(I18N.chars) + '</span>' +
2052 '</div>' +
2053 '<span class="context-label">' + esc(I18N.indexed) + '</span>' +
2054 '</div>' +
2055 '<span class="chunk-expand-toggle" data-chunk="' + i + '">&#9654; ' + esc(I18N.showText) + '</span>' +
2056 '<div class="chunk-details" data-chunk="' + i + '">' +
2057 '<pre class="mxch-kb-inspect-chunk-text">' + esc(chunk.text) + '</pre>' +
2058 metaRows +
2059 '</div>' +
2060 '</div>';
2061 });
2062
2063 resultsEl.innerHTML = html;
2064
2065 resultsEl.querySelectorAll('.chunk-expand-toggle').forEach(function(toggle) {
2066 toggle.addEventListener('click', function() {
2067 var id = this.getAttribute('data-chunk');
2068 var details = resultsEl.querySelector('.chunk-details[data-chunk="' + id + '"]');
2069 if (!details) return;
2070 var expanded = details.classList.toggle('expanded');
2071 this.innerHTML = (expanded ? '&#9660; ' + esc(I18N.hideText) : '&#9654; ' + esc(I18N.showText));
2072 });
2073 });
2074 }
2075
2076 function showNotice(msg, type) {
2077 noticeEl.textContent = msg;
2078 noticeEl.className = 'mxchat-kb-edit-notice' + (type ? ' ' + type : '');
2079 }
2080 });
2081 })();
2082 </script>
2083 <?php
2084 }
2085
2086 /**
2087 * Render Knowledge Page Navigation Scripts
2088 */
2089 function mxchat_render_knowledge_page_scripts() {
2090 ?>
2091 <script>
2092 (function() {
2093 'use strict';
2094
2095 // Wait for DOM
2096 document.addEventListener('DOMContentLoaded', function() {
2097 initKnowledgeNavigation();
2098 initMobileMenu();
2099 initImportOptions();
2100 initCustomPostTypesToggle();
2101 // Sitemap detection is now initialized when user clicks the Sitemap Import option
2102 });
2103
2104 function initKnowledgeNavigation() {
2105 const navLinks = document.querySelectorAll('.mxch-nav-link[data-target], .mxch-mobile-nav-link[data-target]');
2106 const sections = document.querySelectorAll('.mxch-section');
2107
2108 function showSection(targetId) {
2109 // Hide all sections
2110 sections.forEach(section => section.classList.remove('active'));
2111 // Show target section
2112 const target = document.getElementById(targetId);
2113 if (target) {
2114 target.classList.add('active');
2115 }
2116 // Update nav links
2117 navLinks.forEach(link => {
2118 link.classList.toggle('active', link.dataset.target === targetId);
2119 });
2120 // Update URL hash
2121 history.replaceState(null, null, '#' + targetId);
2122 }
2123
2124 navLinks.forEach(link => {
2125 link.addEventListener('click', function(e) {
2126 e.preventDefault();
2127 const targetId = this.dataset.target;
2128 showSection(targetId);
2129 // Close mobile menu if open
2130 const mobileMenu = document.querySelector('.mxch-mobile-menu');
2131 const overlay = document.querySelector('.mxch-mobile-overlay');
2132 if (mobileMenu) mobileMenu.classList.remove('open');
2133 if (overlay) overlay.classList.remove('open');
2134 });
2135 });
2136
2137 // Handle initial hash
2138 const hash = window.location.hash.substring(1);
2139 if (hash && document.getElementById(hash)) {
2140 showSection(hash);
2141 }
2142 }
2143
2144 function initMobileMenu() {
2145 const menuBtn = document.querySelector('.mxch-mobile-menu-btn');
2146 const closeBtn = document.querySelector('.mxch-mobile-menu-close');
2147 const overlay = document.querySelector('.mxch-mobile-overlay');
2148 const menu = document.querySelector('.mxch-mobile-menu');
2149
2150 if (menuBtn && menu) {
2151 menuBtn.addEventListener('click', function() {
2152 menu.classList.add('open');
2153 if (overlay) overlay.classList.add('open');
2154 });
2155 }
2156
2157 if (closeBtn && menu) {
2158 closeBtn.addEventListener('click', function() {
2159 menu.classList.remove('open');
2160 if (overlay) overlay.classList.remove('open');
2161 });
2162 }
2163
2164 if (overlay && menu) {
2165 overlay.addEventListener('click', function() {
2166 menu.classList.remove('open');
2167 overlay.classList.remove('open');
2168 });
2169 }
2170 }
2171
2172 function initImportOptions() {
2173 const importBoxes = document.querySelectorAll('.mxchat-import-box');
2174 const urlInputArea = document.getElementById('mxchat-url-input-area');
2175 const contentInputArea = document.getElementById('mxchat-content-input-area');
2176 const urlInput = document.getElementById('sitemap_url');
2177 const importTypeField = document.getElementById('import_type');
2178 const descriptionText = document.getElementById('url-description-text');
2179
2180 // Sitemap detection elements
2181 const sitemapsLoading = document.getElementById('mxchat-sitemaps-loading');
2182 const detectedSitemaps = document.getElementById('mxchat-detected-sitemaps');
2183 const noSitemaps = document.getElementById('mxchat-no-sitemaps');
2184
2185 // Helper to hide all sitemap detection UI
2186 function hideSitemapDetection() {
2187 if (sitemapsLoading) sitemapsLoading.style.display = 'none';
2188 if (detectedSitemaps) detectedSitemaps.style.display = 'none';
2189 if (noSitemaps) noSitemaps.style.display = 'none';
2190 }
2191
2192 importBoxes.forEach(box => {
2193 box.addEventListener('click', function() {
2194 const option = this.dataset.option;
2195 const type = this.dataset.type;
2196
2197 // Remove active from all boxes
2198 importBoxes.forEach(b => b.classList.remove('active'));
2199 this.classList.add('active');
2200
2201 // Handle different import types
2202 if (option === 'wordpress') {
2203 // WordPress content selector - handled by existing modal
2204 hideSitemapDetection();
2205 return;
2206 }
2207
2208 if (option === 'content') {
2209 if (urlInputArea) urlInputArea.style.display = 'none';
2210 if (contentInputArea) contentInputArea.style.display = 'block';
2211 hideSitemapDetection();
2212 } else if (type === 'sitemap' || type === 'url' || type === 'pdf') {
2213 if (contentInputArea) contentInputArea.style.display = 'none';
2214 if (urlInputArea) urlInputArea.style.display = 'block';
2215
2216 if (urlInput) {
2217 urlInput.placeholder = this.dataset.placeholder || 'Enter URL here';
2218 }
2219 if (importTypeField) {
2220 importTypeField.value = type;
2221 }
2222 if (descriptionText) {
2223 const descriptions = {
2224 'sitemap': '<?php echo esc_js(__('Use a content-specific sub-sitemap (e.g., post-sitemap.xml), not the main sitemap index.', 'mxchat')); ?>',
2225 'url': '<?php echo esc_js(__('Enter the URL of any webpage to import its content.', 'mxchat')); ?>',
2226 'pdf': '<?php echo esc_js(__('Enter the URL of a publicly accessible PDF file.', 'mxchat')); ?>'
2227 };
2228 descriptionText.textContent = descriptions[type] || '';
2229 }
2230
2231 // Only show sitemap detection when Sitemap Import is selected
2232 if (type === 'sitemap') {
2233 // Initialize sitemap detection (function is defined in knowledge-processing.js)
2234 if (typeof window.mxchatInitSitemapDetection === 'function') {
2235 window.mxchatInitSitemapDetection();
2236 }
2237 } else {
2238 hideSitemapDetection();
2239 }
2240 }
2241 });
2242 });
2243 }
2244
2245 function initCustomPostTypesToggle() {
2246 const toggleBtn = document.getElementById('mxchat-custom-post-types-toggle');
2247 const container = document.getElementById('mxchat-custom-post-types-container');
2248
2249 if (toggleBtn && container) {
2250 toggleBtn.addEventListener('click', function() {
2251 const isHidden = container.style.display === 'none';
2252 container.style.display = isHidden ? 'block' : 'none';
2253 const icon = this.querySelector('span:last-child');
2254 if (icon) {
2255 icon.textContent = isHidden ? '' : '';
2256 }
2257 });
2258 }
2259 }
2260
2261 // Sitemap detection is now handled by knowledge-processing.js
2262 // It will be initialized when user clicks "Sitemap Import" option
2263
2264 // ─── Edit Entry Modal ────────────────────────────────────
2265 initEditModal();
2266
2267 function initEditModal() {
2268 var modal = document.getElementById('mxchat-kb-edit-modal');
2269 var textarea = document.getElementById('mxchat-kb-edit-textarea');
2270 var saveBtn = document.getElementById('mxchat-kb-edit-save');
2271 var cancelBtn = document.getElementById('mxchat-kb-edit-cancel');
2272 var closeBtn = document.getElementById('mxchat-kb-edit-close');
2273 var notice = document.getElementById('mxchat-kb-edit-notice');
2274 var charCount = document.getElementById('mxchat-kb-edit-charcount');
2275 var chunkInfo = document.getElementById('mxchat-kb-edit-chunkinfo');
2276 var sourceEl = document.getElementById('mxchat-kb-edit-source');
2277 var titleEl = document.getElementById('mxchat-kb-edit-title');
2278 var saveText = saveBtn ? saveBtn.querySelector('.mxchat-kb-edit-save-text') : null;
2279 var saveSpin = saveBtn ? saveBtn.querySelector('.mxchat-kb-edit-save-spinner') : null;
2280
2281 if (!modal) return;
2282
2283 var currentEntry = {};
2284
2285 // Bind edit buttons (delegated)
2286 document.addEventListener('click', function(e) {
2287 var btn = e.target.closest('.mxchat-edit-entry-btn');
2288 if (btn) {
2289 e.preventDefault();
2290 openEditModal(btn);
2291 }
2292 });
2293
2294 // Close handlers
2295 if (closeBtn) closeBtn.addEventListener('click', closeModal);
2296 if (cancelBtn) cancelBtn.addEventListener('click', closeModal);
2297 modal.addEventListener('click', function(e) {
2298 if (e.target === modal) closeModal();
2299 });
2300
2301 // Character count
2302 if (textarea) {
2303 textarea.addEventListener('input', function() {
2304 updateCharCount();
2305 });
2306 }
2307
2308 // Save handler
2309 if (saveBtn) saveBtn.addEventListener('click', saveContent);
2310
2311 function openEditModal(btn) {
2312 currentEntry = {
2313 sourceUrl: btn.getAttribute('data-source-url') || '',
2314 entryId: btn.getAttribute('data-entry-id') || '',
2315 dataSource: btn.getAttribute('data-data-source') || 'wordpress',
2316 botId: btn.getAttribute('data-bot-id') || 'default',
2317 nonce: btn.getAttribute('data-nonce') || ''
2318 };
2319
2320 // Reset state
2321 textarea.value = '';
2322 textarea.placeholder = 'Loading content...';
2323 textarea.disabled = true;
2324 saveBtn.disabled = true;
2325 notice.textContent = '';
2326 notice.className = 'mxchat-kb-edit-notice';
2327 charCount.textContent = '';
2328 chunkInfo.textContent = '';
2329
2330 // Show source
2331 if (currentEntry.sourceUrl && currentEntry.sourceUrl.indexOf('mxchat://') !== 0) {
2332 sourceEl.innerHTML = 'Source: <a href="' + escapeHtml(currentEntry.sourceUrl) + '" target="_blank">' + escapeHtml(truncate(currentEntry.sourceUrl, 60)) + '</a>';
2333 } else {
2334 sourceEl.textContent = 'Manual Content';
2335 }
2336
2337 // Show modal
2338 modal.classList.add('active');
2339
2340 // Fetch content
2341 var formData = new FormData();
2342 formData.append('action', 'mxchat_get_entry_content');
2343 formData.append('nonce', currentEntry.nonce);
2344 formData.append('source_url', currentEntry.sourceUrl);
2345 formData.append('entry_id', currentEntry.entryId);
2346 formData.append('data_source', currentEntry.dataSource);
2347 formData.append('bot_id', currentEntry.botId);
2348
2349 fetch(ajaxurl, { method: 'POST', body: formData })
2350 .then(function(r) { return r.json(); })
2351 .then(function(resp) {
2352 if (resp.success) {
2353 textarea.value = resp.data.content || '';
2354 textarea.disabled = false;
2355 textarea.placeholder = 'Edit your content here...';
2356 saveBtn.disabled = false;
2357 currentEntry.contentType = resp.data.content_type || 'content';
2358
2359 updateCharCount();
2360
2361 if (resp.data.is_chunked) {
2362 chunkInfo.textContent = resp.data.chunk_count + ' chunks — will be re-chunked on save';
2363 titleEl.textContent = 'Edit Knowledge Entry (' + resp.data.chunk_count + ' chunks)';
2364 } else {
2365 chunkInfo.textContent = '';
2366 titleEl.textContent = 'Edit Knowledge Entry';
2367 }
2368
2369 textarea.focus();
2370 } else {
2371 textarea.placeholder = '';
2372 showNotice((resp.data && resp.data.message) || 'Failed to load content.', 'error');
2373 }
2374 })
2375 .catch(function(err) {
2376 textarea.placeholder = '';
2377 showNotice('Network error: ' + err.message, 'error');
2378 });
2379 }
2380
2381 function saveContent() {
2382 if (!textarea.value.trim()) {
2383 showNotice('Content cannot be empty.', 'error');
2384 return;
2385 }
2386
2387 saveBtn.disabled = true;
2388 if (saveText) saveText.style.display = 'none';
2389 if (saveSpin) saveSpin.style.display = '';
2390 showNotice('Saving and re-embedding...', '');
2391
2392 var formData = new FormData();
2393 formData.append('action', 'mxchat_save_entry_content');
2394 formData.append('nonce', currentEntry.nonce);
2395 formData.append('source_url', currentEntry.sourceUrl);
2396 formData.append('entry_id', currentEntry.entryId);
2397 formData.append('content', textarea.value);
2398 formData.append('data_source', currentEntry.dataSource);
2399 formData.append('bot_id', currentEntry.botId);
2400 formData.append('content_type', currentEntry.contentType || 'content');
2401
2402 fetch(ajaxurl, { method: 'POST', body: formData })
2403 .then(function(r) { return r.json(); })
2404 .then(function(resp) {
2405 if (saveText) saveText.style.display = '';
2406 if (saveSpin) saveSpin.style.display = 'none';
2407 saveBtn.disabled = false;
2408
2409 if (resp.success) {
2410 showNotice('Saved successfully!', 'success');
2411 setTimeout(function() {
2412 closeModal();
2413 window.location.reload();
2414 }, 1000);
2415 } else {
2416 showNotice((resp.data && resp.data.message) || 'Save failed.', 'error');
2417 }
2418 })
2419 .catch(function(err) {
2420 if (saveText) saveText.style.display = '';
2421 if (saveSpin) saveSpin.style.display = 'none';
2422 saveBtn.disabled = false;
2423 showNotice('Network error: ' + err.message, 'error');
2424 });
2425 }
2426
2427 function closeModal() {
2428 modal.classList.remove('active');
2429 }
2430
2431 function updateCharCount() {
2432 var len = textarea.value.length;
2433 charCount.textContent = len.toLocaleString() + ' characters';
2434 }
2435
2436 function showNotice(msg, type) {
2437 notice.textContent = msg;
2438 notice.className = 'mxchat-kb-edit-notice' + (type ? ' ' + type : '');
2439 }
2440
2441 function escapeHtml(str) {
2442 var div = document.createElement('div');
2443 div.textContent = str;
2444 return div.innerHTML;
2445 }
2446
2447 function truncate(str, max) {
2448 return str.length > max ? str.substring(0, max) + '...' : str;
2449 }
2450 }
2451
2452 })();
2453 </script>
2454 <?php
2455 }
2456